@@ -26,8 +26,10 @@ pub type GlobalState = RefCell<GlobalStateInner>;
26
26
27
27
#[ derive( Clone , Debug ) ]
28
28
pub struct GlobalStateInner {
29
- /// This is used as a map between the address of each allocation and its `AllocId`.
30
- /// It is always sorted
29
+ /// This is used as a map between the address of each allocation and its `AllocId`. It is always
30
+ /// sorted. We cannot use a `HashMap` since we can be given an address that is offset from the
31
+ /// base address, and we need to find the `AllocId` it belongs to.
32
+ /// This is not the *full* inverse of `base_addr`; dead allocations have been removed.
31
33
int_to_ptr_map : Vec < ( u64 , AllocId ) > ,
32
34
/// The base address for each allocation. We cannot put that into
33
35
/// `AllocExtra` because function pointers also have a base address, and
@@ -62,10 +64,21 @@ impl GlobalStateInner {
62
64
}
63
65
}
64
66
65
- impl < ' mir , ' tcx > GlobalStateInner {
67
+ /// Shifts `addr` to make it aligned with `align` by rounding `addr` to the smallest multiple
68
+ /// of `align` that is larger or equal to `addr`
69
+ fn align_addr ( addr : u64 , align : u64 ) -> u64 {
70
+ match addr % align {
71
+ 0 => addr,
72
+ rem => addr. checked_add ( align) . unwrap ( ) - rem,
73
+ }
74
+ }
75
+
76
+ impl < ' mir , ' tcx : ' mir > EvalContextExtPriv < ' mir , ' tcx > for crate :: MiriInterpCx < ' mir , ' tcx > { }
77
+ trait EvalContextExtPriv < ' mir , ' tcx : ' mir > : crate :: MiriInterpCxExt < ' mir , ' tcx > {
66
78
// Returns the exposed `AllocId` that corresponds to the specified addr,
67
79
// or `None` if the addr is out of bounds
68
- fn alloc_id_from_addr ( ecx : & MiriInterpCx < ' mir , ' tcx > , addr : u64 ) -> Option < AllocId > {
80
+ fn alloc_id_from_addr ( & self , addr : u64 ) -> Option < AllocId > {
81
+ let ecx = self . eval_context_ref ( ) ;
69
82
let global_state = ecx. machine . intptrcast . borrow ( ) ;
70
83
assert ! ( global_state. provenance_mode != ProvenanceMode :: Strict ) ;
71
84
@@ -82,94 +95,40 @@ impl<'mir, 'tcx> GlobalStateInner {
82
95
let ( glb, alloc_id) = global_state. int_to_ptr_map [ pos - 1 ] ;
83
96
// This never overflows because `addr >= glb`
84
97
let offset = addr - glb;
85
- // If the offset exceeds the size of the allocation, don't use this `alloc_id`.
98
+ // We require this to be strict in-bounds of the allocation. This arm is only
99
+ // entered for addresses that are not the base address, so even zero-sized
100
+ // allocations will get recognized at their base address -- but all other
101
+ // allocations will *not* be recognized at their "end" address.
86
102
let size = ecx. get_alloc_info ( alloc_id) . 0 ;
87
- if offset <= size. bytes ( ) { Some ( alloc_id) } else { None }
103
+ if offset < size. bytes ( ) { Some ( alloc_id) } else { None }
88
104
}
89
105
} ?;
90
106
91
- // We only use this provenance if it has been exposed, *and* is still live .
107
+ // We only use this provenance if it has been exposed.
92
108
if global_state. exposed . contains ( & alloc_id) {
93
- let ( _size, _align, kind) = ecx. get_alloc_info ( alloc_id) ;
94
- match kind {
95
- AllocKind :: LiveData | AllocKind :: Function | AllocKind :: VTable => {
96
- return Some ( alloc_id) ;
97
- }
98
- AllocKind :: Dead => { }
99
- }
100
- }
101
-
102
- None
103
- }
104
-
105
- pub fn expose_ptr (
106
- ecx : & mut MiriInterpCx < ' mir , ' tcx > ,
107
- alloc_id : AllocId ,
108
- tag : BorTag ,
109
- ) -> InterpResult < ' tcx > {
110
- let global_state = ecx. machine . intptrcast . get_mut ( ) ;
111
- // In strict mode, we don't need this, so we can save some cycles by not tracking it.
112
- if global_state. provenance_mode != ProvenanceMode :: Strict {
113
- trace ! ( "Exposing allocation id {alloc_id:?}" ) ;
114
- global_state. exposed . insert ( alloc_id) ;
115
- if ecx. machine . borrow_tracker . is_some ( ) {
116
- ecx. expose_tag ( alloc_id, tag) ?;
117
- }
118
- }
119
- Ok ( ( ) )
120
- }
121
-
122
- pub fn ptr_from_addr_cast (
123
- ecx : & MiriInterpCx < ' mir , ' tcx > ,
124
- addr : u64 ,
125
- ) -> InterpResult < ' tcx , Pointer < Option < Provenance > > > {
126
- trace ! ( "Casting {:#x} to a pointer" , addr) ;
127
-
128
- // Potentially emit a warning.
129
- let global_state = ecx. machine . intptrcast . borrow ( ) ;
130
- match global_state. provenance_mode {
131
- ProvenanceMode :: Default => {
132
- // The first time this happens at a particular location, print a warning.
133
- thread_local ! {
134
- // `Span` is non-`Send`, so we use a thread-local instead.
135
- static PAST_WARNINGS : RefCell <FxHashSet <Span >> = RefCell :: default ( ) ;
136
- }
137
- PAST_WARNINGS . with_borrow_mut ( |past_warnings| {
138
- let first = past_warnings. is_empty ( ) ;
139
- if past_warnings. insert ( ecx. cur_span ( ) ) {
140
- // Newly inserted, so first time we see this span.
141
- ecx. emit_diagnostic ( NonHaltingDiagnostic :: Int2Ptr { details : first } ) ;
142
- }
143
- } ) ;
144
- }
145
- ProvenanceMode :: Strict => {
146
- throw_machine_stop ! ( TerminationInfo :: Int2PtrWithStrictProvenance ) ;
147
- }
148
- ProvenanceMode :: Permissive => { }
109
+ // This must still be live, since we remove allocations from `int_to_ptr_map` when they get freed.
110
+ debug_assert ! ( !matches!( ecx. get_alloc_info( alloc_id) . 2 , AllocKind :: Dead ) ) ;
111
+ Some ( alloc_id)
112
+ } else {
113
+ None
149
114
}
150
-
151
- // We do *not* look up the `AllocId` here! This is a `ptr as usize` cast, and it is
152
- // completely legal to do a cast and then `wrapping_offset` to another allocation and only
153
- // *then* do a memory access. So the allocation that the pointer happens to point to on a
154
- // cast is fairly irrelevant. Instead we generate this as a "wildcard" pointer, such that
155
- // *every time the pointer is used*, we do an `AllocId` lookup to find the (exposed)
156
- // allocation it might be referencing.
157
- Ok ( Pointer :: new ( Some ( Provenance :: Wildcard ) , Size :: from_bytes ( addr) ) )
158
115
}
159
116
160
- fn alloc_base_addr (
161
- ecx : & MiriInterpCx < ' mir , ' tcx > ,
162
- alloc_id : AllocId ,
163
- ) -> InterpResult < ' tcx , u64 > {
117
+ fn addr_from_alloc_id ( & self , alloc_id : AllocId ) -> InterpResult < ' tcx , u64 > {
118
+ let ecx = self . eval_context_ref ( ) ;
164
119
let mut global_state = ecx. machine . intptrcast . borrow_mut ( ) ;
165
120
let global_state = & mut * global_state;
166
121
167
122
Ok ( match global_state. base_addr . entry ( alloc_id) {
168
123
Entry :: Occupied ( entry) => * entry. get ( ) ,
169
124
Entry :: Vacant ( entry) => {
170
- // There is nothing wrong with a raw pointer being cast to an integer only after
171
- // it became dangling. Hence we allow dead allocations.
172
- let ( size, align, _kind) = ecx. get_alloc_info ( alloc_id) ;
125
+ let ( size, align, kind) = ecx. get_alloc_info ( alloc_id) ;
126
+ // This is either called immediately after allocation (and then cached), or when
127
+ // adjusting `tcx` pointers (which never get freed). So assert that we are looking
128
+ // at a live allocation. This also ensures that we never re-assign an address to an
129
+ // allocation that previously had an address, but then was freed and the address
130
+ // information was removed.
131
+ assert ! ( !matches!( kind, AllocKind :: Dead ) ) ;
173
132
174
133
// This allocation does not have a base address yet, pick one.
175
134
// Leave some space to the previous allocation, to give it some chance to be less aligned.
@@ -183,7 +142,7 @@ impl<'mir, 'tcx> GlobalStateInner {
183
142
. next_base_addr
184
143
. checked_add ( slack)
185
144
. ok_or_else ( || err_exhaust ! ( AddressSpaceFull ) ) ?;
186
- let base_addr = Self :: align_addr ( base_addr, align. bytes ( ) ) ;
145
+ let base_addr = align_addr ( base_addr, align. bytes ( ) ) ;
187
146
entry. insert ( base_addr) ;
188
147
trace ! (
189
148
"Assigning base address {:#x} to allocation {:?} (size: {}, align: {}, slack: {})" ,
@@ -205,6 +164,7 @@ impl<'mir, 'tcx> GlobalStateInner {
205
164
if global_state. next_base_addr > ecx. target_usize_max ( ) {
206
165
throw_exhaust ! ( AddressSpaceFull ) ;
207
166
}
167
+ // Also maintain the opposite mapping in `int_to_ptr_map`.
208
168
// Given that `next_base_addr` increases in each allocation, pushing the
209
169
// corresponding tuple keeps `int_to_ptr_map` sorted
210
170
global_state. int_to_ptr_map . push ( ( base_addr, alloc_id) ) ;
@@ -213,15 +173,71 @@ impl<'mir, 'tcx> GlobalStateInner {
213
173
}
214
174
} )
215
175
}
176
+ }
177
+
178
+ impl < ' mir , ' tcx : ' mir > EvalContextExt < ' mir , ' tcx > for crate :: MiriInterpCx < ' mir , ' tcx > { }
179
+ pub trait EvalContextExt < ' mir , ' tcx : ' mir > : crate :: MiriInterpCxExt < ' mir , ' tcx > {
180
+ fn expose_ptr ( & mut self , alloc_id : AllocId , tag : BorTag ) -> InterpResult < ' tcx > {
181
+ let ecx = self . eval_context_mut ( ) ;
182
+ let global_state = ecx. machine . intptrcast . get_mut ( ) ;
183
+ // In strict mode, we don't need this, so we can save some cycles by not tracking it.
184
+ if global_state. provenance_mode != ProvenanceMode :: Strict {
185
+ trace ! ( "Exposing allocation id {alloc_id:?}" ) ;
186
+ global_state. exposed . insert ( alloc_id) ;
187
+ if ecx. machine . borrow_tracker . is_some ( ) {
188
+ ecx. expose_tag ( alloc_id, tag) ?;
189
+ }
190
+ }
191
+ Ok ( ( ) )
192
+ }
193
+
194
+ fn ptr_from_addr_cast ( & self , addr : u64 ) -> InterpResult < ' tcx , Pointer < Option < Provenance > > > {
195
+ trace ! ( "Casting {:#x} to a pointer" , addr) ;
196
+
197
+ let ecx = self . eval_context_ref ( ) ;
198
+ let global_state = ecx. machine . intptrcast . borrow ( ) ;
199
+
200
+ // Potentially emit a warning.
201
+ match global_state. provenance_mode {
202
+ ProvenanceMode :: Default => {
203
+ // The first time this happens at a particular location, print a warning.
204
+ thread_local ! {
205
+ // `Span` is non-`Send`, so we use a thread-local instead.
206
+ static PAST_WARNINGS : RefCell <FxHashSet <Span >> = RefCell :: default ( ) ;
207
+ }
208
+ PAST_WARNINGS . with_borrow_mut ( |past_warnings| {
209
+ let first = past_warnings. is_empty ( ) ;
210
+ if past_warnings. insert ( ecx. cur_span ( ) ) {
211
+ // Newly inserted, so first time we see this span.
212
+ ecx. emit_diagnostic ( NonHaltingDiagnostic :: Int2Ptr { details : first } ) ;
213
+ }
214
+ } ) ;
215
+ }
216
+ ProvenanceMode :: Strict => {
217
+ throw_machine_stop ! ( TerminationInfo :: Int2PtrWithStrictProvenance ) ;
218
+ }
219
+ ProvenanceMode :: Permissive => { }
220
+ }
221
+
222
+ // We do *not* look up the `AllocId` here! This is a `ptr as usize` cast, and it is
223
+ // completely legal to do a cast and then `wrapping_offset` to another allocation and only
224
+ // *then* do a memory access. So the allocation that the pointer happens to point to on a
225
+ // cast is fairly irrelevant. Instead we generate this as a "wildcard" pointer, such that
226
+ // *every time the pointer is used*, we do an `AllocId` lookup to find the (exposed)
227
+ // allocation it might be referencing.
228
+ Ok ( Pointer :: new ( Some ( Provenance :: Wildcard ) , Size :: from_bytes ( addr) ) )
229
+ }
216
230
217
231
/// Convert a relative (tcx) pointer to a Miri pointer.
218
- pub fn ptr_from_rel_ptr (
219
- ecx : & MiriInterpCx < ' mir , ' tcx > ,
232
+ fn ptr_from_rel_ptr (
233
+ & self ,
220
234
ptr : Pointer < AllocId > ,
221
235
tag : BorTag ,
222
236
) -> InterpResult < ' tcx , Pointer < Provenance > > {
237
+ let ecx = self . eval_context_ref ( ) ;
238
+
223
239
let ( alloc_id, offset) = ptr. into_parts ( ) ; // offset is relative (AllocId provenance)
224
- let base_addr = GlobalStateInner :: alloc_base_addr ( ecx, alloc_id) ?;
240
+ let base_addr = ecx. addr_from_alloc_id ( alloc_id) ?;
225
241
226
242
// Add offset with the right kind of pointer-overflowing arithmetic.
227
243
let dl = ecx. data_layout ( ) ;
@@ -231,22 +247,21 @@ impl<'mir, 'tcx> GlobalStateInner {
231
247
232
248
/// When a pointer is used for a memory access, this computes where in which allocation the
233
249
/// access is going.
234
- pub fn ptr_get_alloc (
235
- ecx : & MiriInterpCx < ' mir , ' tcx > ,
236
- ptr : Pointer < Provenance > ,
237
- ) -> Option < ( AllocId , Size ) > {
250
+ fn ptr_get_alloc ( & self , ptr : Pointer < Provenance > ) -> Option < ( AllocId , Size ) > {
251
+ let ecx = self . eval_context_ref ( ) ;
252
+
238
253
let ( tag, addr) = ptr. into_parts ( ) ; // addr is absolute (Tag provenance)
239
254
240
255
let alloc_id = if let Provenance :: Concrete { alloc_id, .. } = tag {
241
256
alloc_id
242
257
} else {
243
258
// A wildcard pointer.
244
- GlobalStateInner :: alloc_id_from_addr ( ecx , addr. bytes ( ) ) ?
259
+ ecx . alloc_id_from_addr ( addr. bytes ( ) ) ?
245
260
} ;
246
261
247
262
// This cannot fail: since we already have a pointer with that provenance, rel_ptr_to_addr
248
- // must have been called in the past.
249
- let base_addr = GlobalStateInner :: alloc_base_addr ( ecx, alloc_id) . unwrap ( ) ;
263
+ // must have been called in the past, so we can just look up the address in the map .
264
+ let base_addr = ecx. addr_from_alloc_id ( alloc_id) . unwrap ( ) ;
250
265
251
266
// Wrapping "addr - base_addr"
252
267
#[ allow( clippy:: cast_possible_wrap) ] // we want to wrap here
@@ -256,14 +271,24 @@ impl<'mir, 'tcx> GlobalStateInner {
256
271
Size :: from_bytes ( ecx. overflowing_signed_offset ( addr. bytes ( ) , neg_base_addr) . 0 ) ,
257
272
) )
258
273
}
274
+ }
259
275
260
- /// Shifts `addr` to make it aligned with `align` by rounding `addr` to the smallest multiple
261
- /// of `align` that is larger or equal to `addr`
262
- fn align_addr ( addr : u64 , align : u64 ) -> u64 {
263
- match addr % align {
264
- 0 => addr,
265
- rem => addr. checked_add ( align) . unwrap ( ) - rem,
266
- }
276
+ impl GlobalStateInner {
277
+ pub fn free_alloc_id ( & mut self , dead_id : AllocId ) {
278
+ // We can *not* remove this from `base_addr`, since the interpreter design requires that we
279
+ // be able to retrieve an AllocId + offset for any memory access *before* we check if the
280
+ // access is valid. Specifically, `ptr_get_alloc` is called on each attempt at a memory
281
+ // access to determine the allocation ID and offset -- and there can still be pointers with
282
+ // `dead_id` that one can attempt to use for a memory access. `ptr_get_alloc` may return
283
+ // `None` only if the pointer truly has no provenance (this ensures consistent error
284
+ // messages).
285
+ // However, we *can* remove it from `int_to_ptr_map`, since any wildcard pointers that exist
286
+ // can no longer actually be accessing that address. This ensures `alloc_id_from_addr` never
287
+ // returns a dead allocation.
288
+ self . int_to_ptr_map . retain ( |& ( _, id) | id != dead_id) ;
289
+ // We can also remove it from `exposed`, since this allocation can anyway not be returned by
290
+ // `alloc_id_from_addr` any more.
291
+ self . exposed . remove ( & dead_id) ;
267
292
}
268
293
}
269
294
@@ -273,7 +298,7 @@ mod tests {
273
298
274
299
#[ test]
275
300
fn test_align_addr ( ) {
276
- assert_eq ! ( GlobalStateInner :: align_addr( 37 , 4 ) , 40 ) ;
277
- assert_eq ! ( GlobalStateInner :: align_addr( 44 , 4 ) , 44 ) ;
301
+ assert_eq ! ( align_addr( 37 , 4 ) , 40 ) ;
302
+ assert_eq ! ( align_addr( 44 , 4 ) , 44 ) ;
278
303
}
279
304
}
0 commit comments