|
1 | | -use rustc_abi::{Align, Size}; |
2 | | -use rustc_ast::expand::allocator::AllocatorKind; |
| 1 | +use rustc_abi::{Align, AlignFromBytesError, CanonAbi, Size}; |
| 2 | +use rustc_ast::expand::allocator::SpecialAllocatorMethod; |
| 3 | +use rustc_middle::ty::Ty; |
| 4 | +use rustc_span::Symbol; |
| 5 | +use rustc_target::callconv::FnAbi; |
3 | 6 |
|
4 | 7 | use crate::*; |
5 | 8 |
|
@@ -54,30 +57,100 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { |
54 | 57 | Align::from_bytes(prev_power_of_two(size)).unwrap() |
55 | 58 | } |
56 | 59 |
|
57 | | - /// Emulates calling the internal __rust_* allocator functions |
58 | | - fn emulate_allocator( |
| 60 | + /// Check some basic requirements for this allocation request: |
| 61 | + /// non-zero size, power-of-two alignment. |
| 62 | + fn check_rust_alloc_request(&self, size: u64, align: u64) -> InterpResult<'tcx> { |
| 63 | + let this = self.eval_context_ref(); |
| 64 | + if size == 0 { |
| 65 | + throw_ub_format!("creating allocation with size 0"); |
| 66 | + } |
| 67 | + if size > this.max_size_of_val().bytes() { |
| 68 | + throw_ub_format!("creating an allocation larger than half the address space"); |
| 69 | + } |
| 70 | + if let Err(e) = Align::from_bytes(align) { |
| 71 | + match e { |
| 72 | + AlignFromBytesError::TooLarge(_) => { |
| 73 | + throw_unsup_format!( |
| 74 | + "creating allocation with alignment {align} exceeding rustc's maximum \ |
| 75 | + supported value" |
| 76 | + ); |
| 77 | + } |
| 78 | + AlignFromBytesError::NotPowerOfTwo(_) => { |
| 79 | + throw_ub_format!("creating allocation with non-power-of-two alignment {align}"); |
| 80 | + } |
| 81 | + } |
| 82 | + } |
| 83 | + |
| 84 | + interp_ok(()) |
| 85 | + } |
| 86 | + |
| 87 | + fn rust_special_allocator_method( |
59 | 88 | &mut self, |
60 | | - default: impl FnOnce(&mut MiriInterpCx<'tcx>) -> InterpResult<'tcx>, |
61 | | - ) -> InterpResult<'tcx, EmulateItemResult> { |
| 89 | + method: SpecialAllocatorMethod, |
| 90 | + link_name: Symbol, |
| 91 | + abi: &FnAbi<'tcx, Ty<'tcx>>, |
| 92 | + args: &[OpTy<'tcx>], |
| 93 | + dest: &PlaceTy<'tcx>, |
| 94 | + ) -> InterpResult<'tcx> { |
62 | 95 | let this = self.eval_context_mut(); |
63 | 96 |
|
64 | | - let Some(allocator_kind) = this.tcx.allocator_kind(()) else { |
65 | | - // in real code, this symbol does not exist without an allocator |
66 | | - return interp_ok(EmulateItemResult::NotSupported); |
67 | | - }; |
| 97 | + match method { |
| 98 | + SpecialAllocatorMethod::Alloc | SpecialAllocatorMethod::AllocZeroed => { |
| 99 | + let [size, align] = |
| 100 | + this.check_shim_sig_lenient(abi, CanonAbi::Rust, link_name, args)?; |
| 101 | + let size = this.read_target_usize(size)?; |
| 102 | + let align = this.read_target_usize(align)?; |
| 103 | + |
| 104 | + this.check_rust_alloc_request(size, align)?; |
68 | 105 |
|
69 | | - match allocator_kind { |
70 | | - AllocatorKind::Global => { |
71 | | - // When `#[global_allocator]` is used, `__rust_*` is defined by the macro expansion |
72 | | - // of this attribute. As such we have to call an exported Rust function, |
73 | | - // and not execute any Miri shim. Somewhat unintuitively doing so is done |
74 | | - // by returning `NotSupported`, which triggers the `lookup_exported_symbol` |
75 | | - // fallback case in `emulate_foreign_item`. |
76 | | - interp_ok(EmulateItemResult::NotSupported) |
| 106 | + let ptr = this.allocate_ptr( |
| 107 | + Size::from_bytes(size), |
| 108 | + Align::from_bytes(align).unwrap(), |
| 109 | + MiriMemoryKind::Rust.into(), |
| 110 | + if matches!(method, SpecialAllocatorMethod::AllocZeroed) { |
| 111 | + AllocInit::Zero |
| 112 | + } else { |
| 113 | + AllocInit::Uninit |
| 114 | + }, |
| 115 | + )?; |
| 116 | + |
| 117 | + this.write_pointer(ptr, dest) |
| 118 | + } |
| 119 | + SpecialAllocatorMethod::Dealloc => { |
| 120 | + let [ptr, old_size, align] = |
| 121 | + this.check_shim_sig_lenient(abi, CanonAbi::Rust, link_name, args)?; |
| 122 | + let ptr = this.read_pointer(ptr)?; |
| 123 | + let old_size = this.read_target_usize(old_size)?; |
| 124 | + let align = this.read_target_usize(align)?; |
| 125 | + |
| 126 | + // No need to check old_size/align; we anyway check that they match the allocation. |
| 127 | + this.deallocate_ptr( |
| 128 | + ptr, |
| 129 | + Some((Size::from_bytes(old_size), Align::from_bytes(align).unwrap())), |
| 130 | + MiriMemoryKind::Rust.into(), |
| 131 | + ) |
77 | 132 | } |
78 | | - AllocatorKind::Default => { |
79 | | - default(this)?; |
80 | | - interp_ok(EmulateItemResult::NeedsReturn) |
| 133 | + SpecialAllocatorMethod::Realloc => { |
| 134 | + let [ptr, old_size, align, new_size] = |
| 135 | + this.check_shim_sig_lenient(abi, CanonAbi::Rust, link_name, args)?; |
| 136 | + let ptr = this.read_pointer(ptr)?; |
| 137 | + let old_size = this.read_target_usize(old_size)?; |
| 138 | + let align = this.read_target_usize(align)?; |
| 139 | + let new_size = this.read_target_usize(new_size)?; |
| 140 | + // No need to check old_size; we anyway check that they match the allocation. |
| 141 | + |
| 142 | + this.check_rust_alloc_request(new_size, align)?; |
| 143 | + |
| 144 | + let align = Align::from_bytes(align).unwrap(); |
| 145 | + let new_ptr = this.reallocate_ptr( |
| 146 | + ptr, |
| 147 | + Some((Size::from_bytes(old_size), align)), |
| 148 | + Size::from_bytes(new_size), |
| 149 | + align, |
| 150 | + MiriMemoryKind::Rust.into(), |
| 151 | + AllocInit::Uninit, |
| 152 | + )?; |
| 153 | + this.write_pointer(new_ptr, dest) |
81 | 154 | } |
82 | 155 | } |
83 | 156 | } |
|
0 commit comments