Skip to content

Commit c50b9d1

Browse files
committed
Centralize bounds, alignment and NULL checking for memory accesses in one function: memory.check_ptr_access
That function also takes care of converting a Scalar to a Pointer, should that be needed. Not all accesses need that though: if the access has size 0, None is returned. Everyone accessing memory based on a Scalar should use this method to get the Pointer they need. All operations on the Allocation work on Pointer inputs and expect all the checks to have happened (and will ICE if the bounds are violated). The operations on Memory work on Scalar inputs and do the checks themselves. The only other public method to check pointers is memory.ptr_may_be_null, which is needed in a few places. With this, we can make all the other methods (tests for a pointer being in-bounds and checking alignment) private helper methods, used to implement the two public methods. That maks the public API surface much easier to use and harder to mis-use. While I am at it, this also removes the assumption that the vtable part of a `dyn Trait`-fat-pointer is a `Pointer` (as opposed to a pointer cast to an integer, stored as raw bits).
1 parent 305930c commit c50b9d1

File tree

8 files changed

+214
-151
lines changed

8 files changed

+214
-151
lines changed

src/librustc/mir/interpret/allocation.rs

Lines changed: 51 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ use crate::ty::layout::{Size, Align};
88
use syntax::ast::Mutability;
99
use std::{iter, fmt::{self, Display}};
1010
use crate::mir;
11-
use std::ops::{Deref, DerefMut};
11+
use std::ops::{Range, Deref, DerefMut};
1212
use rustc_data_structures::sorted_map::SortedMap;
1313
use rustc_macros::HashStable;
1414
use rustc_target::abi::HasDataLayout;
@@ -146,54 +146,48 @@ impl<Tag> Allocation<Tag> {
146146

147147
impl<'tcx> ::serialize::UseSpecializedDecodable for &'tcx Allocation {}
148148

149-
/// Alignment and bounds checks
150-
impl<'tcx, Tag, Extra> Allocation<Tag, Extra> {
151-
/// Checks if the pointer is "in-bounds". Notice that a pointer pointing at the end
152-
/// of an allocation (i.e., at the first *inaccessible* location) *is* considered
153-
/// in-bounds! This follows C's/LLVM's rules.
154-
/// If you want to check bounds before doing a memory access, better use `check_bounds`.
155-
fn check_bounds_ptr(
149+
/// Byte accessors
150+
impl<'tcx, Tag: Copy, Extra: AllocationExtra<Tag>> Allocation<Tag, Extra> {
151+
/// Just a small local helper function to avoid a bit of code repetition.
152+
/// Returns the range of this allocation that was meant.
153+
#[inline]
154+
fn check_bounds(
156155
&self,
157-
ptr: Pointer<Tag>,
158-
msg: CheckInAllocMsg,
159-
) -> InterpResult<'tcx> {
160-
let allocation_size = self.bytes.len() as u64;
161-
ptr.check_in_alloc(Size::from_bytes(allocation_size), msg)
156+
offset: Size,
157+
size: Size
158+
) -> Range<usize> {
159+
let end = offset + size; // this does overflow checking
160+
assert_eq!(
161+
end.bytes() as usize as u64, end.bytes(),
162+
"cannot handle this access on this host architecture"
163+
);
164+
let end = end.bytes() as usize;
165+
assert!(
166+
end <= self.bytes.len(),
167+
"Out-of-bounds access at offset {}, size {} in allocation of size {}",
168+
offset.bytes(), size.bytes(), self.bytes.len()
169+
);
170+
(offset.bytes() as usize)..end
162171
}
163172

164-
/// Checks if the memory range beginning at `ptr` and of size `Size` is "in-bounds".
165-
#[inline(always)]
166-
pub fn check_bounds(
167-
&self,
168-
cx: &impl HasDataLayout,
169-
ptr: Pointer<Tag>,
170-
size: Size,
171-
msg: CheckInAllocMsg,
172-
) -> InterpResult<'tcx> {
173-
// if ptr.offset is in bounds, then so is ptr (because offset checks for overflow)
174-
self.check_bounds_ptr(ptr.offset(size, cx)?, msg)
175-
}
176-
}
177-
178-
/// Byte accessors
179-
impl<'tcx, Tag: Copy, Extra: AllocationExtra<Tag>> Allocation<Tag, Extra> {
180173
/// The last argument controls whether we error out when there are undefined
181174
/// or pointer bytes. You should never call this, call `get_bytes` or
182175
/// `get_bytes_with_undef_and_ptr` instead,
183176
///
184177
/// This function also guarantees that the resulting pointer will remain stable
185178
/// even when new allocations are pushed to the `HashMap`. `copy_repeatedly` relies
186179
/// on that.
180+
///
181+
/// It is the callers responsibility to check bounds and alignment beforehand.
187182
fn get_bytes_internal(
188183
&self,
189184
cx: &impl HasDataLayout,
190185
ptr: Pointer<Tag>,
191186
size: Size,
192187
check_defined_and_ptr: bool,
193-
msg: CheckInAllocMsg,
194188
) -> InterpResult<'tcx, &[u8]>
195189
{
196-
self.check_bounds(cx, ptr, size, msg)?;
190+
let range = self.check_bounds(ptr.offset, size);
197191

198192
if check_defined_and_ptr {
199193
self.check_defined(ptr, size)?;
@@ -205,12 +199,13 @@ impl<'tcx, Tag: Copy, Extra: AllocationExtra<Tag>> Allocation<Tag, Extra> {
205199

206200
AllocationExtra::memory_read(self, ptr, size)?;
207201

208-
assert_eq!(ptr.offset.bytes() as usize as u64, ptr.offset.bytes());
209-
assert_eq!(size.bytes() as usize as u64, size.bytes());
210-
let offset = ptr.offset.bytes() as usize;
211-
Ok(&self.bytes[offset..offset + size.bytes() as usize])
202+
Ok(&self.bytes[range])
212203
}
213204

205+
/// Check that these bytes are initialized and not pointer bytes, and then return them
206+
/// as a slice.
207+
///
208+
/// It is the callers responsibility to check bounds and alignment beforehand.
214209
#[inline]
215210
pub fn get_bytes(
216211
&self,
@@ -219,11 +214,13 @@ impl<'tcx, Tag: Copy, Extra: AllocationExtra<Tag>> Allocation<Tag, Extra> {
219214
size: Size,
220215
) -> InterpResult<'tcx, &[u8]>
221216
{
222-
self.get_bytes_internal(cx, ptr, size, true, CheckInAllocMsg::MemoryAccessTest)
217+
self.get_bytes_internal(cx, ptr, size, true)
223218
}
224219

225220
/// It is the caller's responsibility to handle undefined and pointer bytes.
226221
/// However, this still checks that there are no relocations on the *edges*.
222+
///
223+
/// It is the callers responsibility to check bounds and alignment beforehand.
227224
#[inline]
228225
pub fn get_bytes_with_undef_and_ptr(
229226
&self,
@@ -232,30 +229,28 @@ impl<'tcx, Tag: Copy, Extra: AllocationExtra<Tag>> Allocation<Tag, Extra> {
232229
size: Size,
233230
) -> InterpResult<'tcx, &[u8]>
234231
{
235-
self.get_bytes_internal(cx, ptr, size, false, CheckInAllocMsg::MemoryAccessTest)
232+
self.get_bytes_internal(cx, ptr, size, false)
236233
}
237234

238235
/// Just calling this already marks everything as defined and removes relocations,
239236
/// so be sure to actually put data there!
237+
///
238+
/// It is the callers responsibility to check bounds and alignment beforehand.
240239
pub fn get_bytes_mut(
241240
&mut self,
242241
cx: &impl HasDataLayout,
243242
ptr: Pointer<Tag>,
244243
size: Size,
245244
) -> InterpResult<'tcx, &mut [u8]>
246245
{
247-
assert_ne!(size.bytes(), 0, "0-sized accesses should never even get a `Pointer`");
248-
self.check_bounds(cx, ptr, size, CheckInAllocMsg::MemoryAccessTest)?;
246+
let range = self.check_bounds(ptr.offset, size);
249247

250248
self.mark_definedness(ptr, size, true);
251249
self.clear_relocations(cx, ptr, size)?;
252250

253251
AllocationExtra::memory_written(self, ptr, size)?;
254252

255-
assert_eq!(ptr.offset.bytes() as usize as u64, ptr.offset.bytes());
256-
assert_eq!(size.bytes() as usize as u64, size.bytes());
257-
let offset = ptr.offset.bytes() as usize;
258-
Ok(&mut self.bytes[offset..offset + size.bytes() as usize])
253+
Ok(&mut self.bytes[range])
259254
}
260255
}
261256

@@ -276,9 +271,10 @@ impl<'tcx, Tag: Copy, Extra: AllocationExtra<Tag>> Allocation<Tag, Extra> {
276271
let size_with_null = Size::from_bytes((size + 1) as u64);
277272
// Go through `get_bytes` for checks and AllocationExtra hooks.
278273
// We read the null, so we include it in the request, but we want it removed
279-
// from the result!
274+
// from the result, so we do subslicing.
280275
Ok(&self.get_bytes(cx, ptr, size_with_null)?[..size])
281276
}
277+
// This includes the case where `offset` is out-of-bounds to begin with.
282278
None => err!(UnterminatedCString(ptr.erase_tag())),
283279
}
284280
}
@@ -306,7 +302,7 @@ impl<'tcx, Tag: Copy, Extra: AllocationExtra<Tag>> Allocation<Tag, Extra> {
306302

307303
/// Writes `src` to the memory starting at `ptr.offset`.
308304
///
309-
/// Will do bounds checks on the allocation.
305+
/// It is the callers responsibility to check bounds and alignment beforehand.
310306
pub fn write_bytes(
311307
&mut self,
312308
cx: &impl HasDataLayout,
@@ -320,6 +316,8 @@ impl<'tcx, Tag: Copy, Extra: AllocationExtra<Tag>> Allocation<Tag, Extra> {
320316
}
321317

322318
/// Sets `count` bytes starting at `ptr.offset` with `val`. Basically `memset`.
319+
///
320+
/// It is the callers responsibility to check bounds and alignment beforehand.
323321
pub fn write_repeat(
324322
&mut self,
325323
cx: &impl HasDataLayout,
@@ -342,7 +340,7 @@ impl<'tcx, Tag: Copy, Extra: AllocationExtra<Tag>> Allocation<Tag, Extra> {
342340
/// * in oder to obtain a `Pointer` we need to check for ZSTness anyway due to integer pointers
343341
/// being valid for ZSTs
344342
///
345-
/// Note: This function does not do *any* alignment checks, you need to do these before calling
343+
/// It is the callers responsibility to check bounds and alignment beforehand.
346344
pub fn read_scalar(
347345
&self,
348346
cx: &impl HasDataLayout,
@@ -378,7 +376,9 @@ impl<'tcx, Tag: Copy, Extra: AllocationExtra<Tag>> Allocation<Tag, Extra> {
378376
Ok(ScalarMaybeUndef::Scalar(Scalar::from_uint(bits, size)))
379377
}
380378

381-
/// Note: This function does not do *any* alignment checks, you need to do these before calling
379+
/// Read a pointer-sized scalar.
380+
///
381+
/// It is the callers responsibility to check bounds and alignment beforehand.
382382
pub fn read_ptr_sized(
383383
&self,
384384
cx: &impl HasDataLayout,
@@ -395,7 +395,7 @@ impl<'tcx, Tag: Copy, Extra: AllocationExtra<Tag>> Allocation<Tag, Extra> {
395395
/// * in oder to obtain a `Pointer` we need to check for ZSTness anyway due to integer pointers
396396
/// being valid for ZSTs
397397
///
398-
/// Note: This function does not do *any* alignment checks, you need to do these before calling
398+
/// It is the callers responsibility to check bounds and alignment beforehand.
399399
pub fn write_scalar(
400400
&mut self,
401401
cx: &impl HasDataLayout,
@@ -435,7 +435,9 @@ impl<'tcx, Tag: Copy, Extra: AllocationExtra<Tag>> Allocation<Tag, Extra> {
435435
Ok(())
436436
}
437437

438-
/// Note: This function does not do *any* alignment checks, you need to do these before calling
438+
/// Write a pointer-sized scalar.
439+
///
440+
/// It is the callers responsibility to check bounds and alignment beforehand.
439441
pub fn write_ptr_sized(
440442
&mut self,
441443
cx: &impl HasDataLayout,

src/librustc_mir/interpret/eval_context.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -437,7 +437,7 @@ impl<'mir, 'tcx, M: Machine<'mir, 'tcx>> InterpretCx<'mir, 'tcx, M> {
437437
Ok(Some((size.align_to(align), align)))
438438
}
439439
ty::Dynamic(..) => {
440-
let vtable = metadata.expect("dyn trait fat ptr must have vtable").to_ptr()?;
440+
let vtable = metadata.expect("dyn trait fat ptr must have vtable");
441441
// the second entry in the vtable is the dynamic size of the object.
442442
Ok(Some(self.read_size_and_align_from_vtable(vtable)?))
443443
}

0 commit comments

Comments
 (0)