Skip to content

Commit 2059112

Browse files
authored
Rollup merge of #71610 - divergentdave:InvalidUndefBytes-range, r=RalfJung
InvalidUndefBytes: Track size of undef region used This PR adds a size to `UndefinedBehaviorInfo::InvalidUndefBytes`, to keep track of how many undefined bytes in a row were accessed, and changes a few methods to pass this information through. This additional information will eventually be used in Miri to improve diagnostics for this UB error. See also rust-lang/miri#1354 for prior discussion. I expect Miri will break the next time its submodule is updated, due to this change to the `InvalidUndefBytes`. (The current commit for src/tools/miri predates rust-lang/miri#1354, and thus does not try to destructure the `InvalidUndefBytes` variant) I have a corresponding change prepared for that repository. r? @RalfJung
2 parents c60b675 + 0148a7f commit 2059112

File tree

4 files changed

+67
-25
lines changed

4 files changed

+67
-25
lines changed

src/librustc_middle/mir/interpret/allocation.rs

+25-12
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ use rustc_target::abi::{Align, HasDataLayout, Size};
1111

1212
use super::{
1313
read_target_uint, write_target_uint, AllocId, InterpResult, Pointer, Scalar, ScalarMaybeUninit,
14+
UninitBytesAccess,
1415
};
1516

1617
#[derive(Clone, Debug, Eq, PartialEq, PartialOrd, Ord, Hash, RustcEncodable, RustcDecodable)]
@@ -545,17 +546,23 @@ impl<'tcx, Tag: Copy, Extra> Allocation<Tag, Extra> {
545546
impl<'tcx, Tag: Copy, Extra> Allocation<Tag, Extra> {
546547
/// Checks whether the given range is entirely defined.
547548
///
548-
/// Returns `Ok(())` if it's defined. Otherwise returns the index of the byte
549-
/// at which the first undefined access begins.
550-
fn is_defined(&self, ptr: Pointer<Tag>, size: Size) -> Result<(), Size> {
549+
/// Returns `Ok(())` if it's defined. Otherwise returns the range of byte
550+
/// indexes of the first contiguous undefined access.
551+
fn is_defined(&self, ptr: Pointer<Tag>, size: Size) -> Result<(), Range<Size>> {
551552
self.init_mask.is_range_initialized(ptr.offset, ptr.offset + size) // `Size` addition
552553
}
553554

554-
/// Checks that a range of bytes is defined. If not, returns the `ReadUndefBytes`
555-
/// error which will report the first byte which is undefined.
555+
/// Checks that a range of bytes is defined. If not, returns the `InvalidUndefBytes`
556+
/// error which will report the first range of bytes which is undefined.
556557
fn check_defined(&self, ptr: Pointer<Tag>, size: Size) -> InterpResult<'tcx> {
557-
self.is_defined(ptr, size)
558-
.or_else(|idx| throw_ub!(InvalidUninitBytes(Some(Pointer::new(ptr.alloc_id, idx)))))
558+
self.is_defined(ptr, size).or_else(|idx_range| {
559+
throw_ub!(InvalidUninitBytes(Some(Box::new(UninitBytesAccess {
560+
access_ptr: ptr.erase_tag(),
561+
access_size: size,
562+
uninit_ptr: Pointer::new(ptr.alloc_id, idx_range.start),
563+
uninit_size: idx_range.end - idx_range.start, // `Size` subtraction
564+
}))))
565+
})
559566
}
560567

561568
pub fn mark_definedness(&mut self, ptr: Pointer<Tag>, size: Size, new_state: bool) {
@@ -758,19 +765,25 @@ impl InitMask {
758765

759766
/// Checks whether the range `start..end` (end-exclusive) is entirely initialized.
760767
///
761-
/// Returns `Ok(())` if it's initialized. Otherwise returns the index of the byte
762-
/// at which the first uninitialized access begins.
768+
/// Returns `Ok(())` if it's initialized. Otherwise returns a range of byte
769+
/// indexes for the first contiguous span of the uninitialized access.
763770
#[inline]
764-
pub fn is_range_initialized(&self, start: Size, end: Size) -> Result<(), Size> {
771+
pub fn is_range_initialized(&self, start: Size, end: Size) -> Result<(), Range<Size>> {
765772
if end > self.len {
766-
return Err(self.len);
773+
return Err(self.len..end);
767774
}
768775

769776
// FIXME(oli-obk): optimize this for allocations larger than a block.
770777
let idx = (start.bytes()..end.bytes()).map(Size::from_bytes).find(|&i| !self.get(i));
771778

772779
match idx {
773-
Some(idx) => Err(idx),
780+
Some(idx) => {
781+
let undef_end = (idx.bytes()..end.bytes())
782+
.map(Size::from_bytes)
783+
.find(|&i| self.get(i))
784+
.unwrap_or(end);
785+
Err(idx..undef_end)
786+
}
774787
None => Ok(()),
775788
}
776789
}

src/librustc_middle/mir/interpret/error.rs

+33-6
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ use crate::ty::query::TyCtxtAt;
66
use crate::ty::{self, layout, tls, FnSig, Ty};
77

88
use rustc_data_structures::sync::Lock;
9-
use rustc_errors::{struct_span_err, DiagnosticBuilder, ErrorReported};
9+
use rustc_errors::{pluralize, struct_span_err, DiagnosticBuilder, ErrorReported};
1010
use rustc_hir as hir;
1111
use rustc_hir::definitions::DefPathData;
1212
use rustc_macros::HashStable;
@@ -327,6 +327,19 @@ impl fmt::Display for CheckInAllocMsg {
327327
}
328328
}
329329

330+
/// Details of an access to uninitialized bytes where it is not allowed.
331+
#[derive(Debug)]
332+
pub struct UninitBytesAccess {
333+
/// Location of the original memory access.
334+
pub access_ptr: Pointer,
335+
/// Size of the original memory access.
336+
pub access_size: Size,
337+
/// Location of the first uninitialized byte that was accessed.
338+
pub uninit_ptr: Pointer,
339+
/// Number of consecutive uninitialized bytes that were accessed.
340+
pub uninit_size: Size,
341+
}
342+
330343
/// Error information for when the program caused Undefined Behavior.
331344
pub enum UndefinedBehaviorInfo<'tcx> {
332345
/// Free-form case. Only for errors that are never caught!
@@ -384,7 +397,7 @@ pub enum UndefinedBehaviorInfo<'tcx> {
384397
/// Using a string that is not valid UTF-8,
385398
InvalidStr(std::str::Utf8Error),
386399
/// Using uninitialized data where it is not allowed.
387-
InvalidUninitBytes(Option<Pointer>),
400+
InvalidUninitBytes(Option<Box<UninitBytesAccess>>),
388401
/// Working with a local that is not currently live.
389402
DeadLocal,
390403
/// Data size is not equal to target size.
@@ -455,10 +468,18 @@ impl fmt::Display for UndefinedBehaviorInfo<'_> {
455468
write!(f, "using {} as function pointer but it does not point to a function", p)
456469
}
457470
InvalidStr(err) => write!(f, "this string is not valid UTF-8: {}", err),
458-
InvalidUninitBytes(Some(p)) => write!(
471+
InvalidUninitBytes(Some(access)) => write!(
459472
f,
460-
"reading uninitialized memory at {}, but this operation requires initialized memory",
461-
p
473+
"reading {} byte{} of memory starting at {}, \
474+
but {} byte{} {} uninitialized starting at {}, \
475+
and this operation requires initialized memory",
476+
access.access_size.bytes(),
477+
pluralize!(access.access_size.bytes()),
478+
access.access_ptr,
479+
access.uninit_size.bytes(),
480+
pluralize!(access.uninit_size.bytes()),
481+
if access.uninit_size.bytes() != 1 { "are" } else { "is" },
482+
access.uninit_ptr,
462483
),
463484
InvalidUninitBytes(None) => write!(
464485
f,
@@ -556,6 +577,9 @@ impl dyn MachineStopType {
556577
}
557578
}
558579

580+
#[cfg(target_arch = "x86_64")]
581+
static_assert_size!(InterpError<'_>, 40);
582+
559583
pub enum InterpError<'tcx> {
560584
/// The program caused undefined behavior.
561585
UndefinedBehavior(UndefinedBehaviorInfo<'tcx>),
@@ -604,7 +628,10 @@ impl InterpError<'_> {
604628
InterpError::MachineStop(b) => mem::size_of_val::<dyn MachineStopType>(&**b) > 0,
605629
InterpError::Unsupported(UnsupportedOpInfo::Unsupported(_))
606630
| InterpError::UndefinedBehavior(UndefinedBehaviorInfo::ValidationFailure(_))
607-
| InterpError::UndefinedBehavior(UndefinedBehaviorInfo::Ub(_)) => true,
631+
| InterpError::UndefinedBehavior(UndefinedBehaviorInfo::Ub(_))
632+
| InterpError::UndefinedBehavior(UndefinedBehaviorInfo::InvalidUninitBytes(Some(_))) => {
633+
true
634+
}
608635
_ => false,
609636
}
610637
}

src/librustc_middle/mir/interpret/mod.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -119,7 +119,7 @@ use crate::ty::{self, Instance, Ty, TyCtxt};
119119
pub use self::error::{
120120
struct_error, CheckInAllocMsg, ConstEvalErr, ConstEvalRawResult, ConstEvalResult, ErrorHandled,
121121
FrameInfo, InterpError, InterpErrorInfo, InterpResult, InvalidProgramInfo, MachineStopType,
122-
ResourceExhaustionInfo, UndefinedBehaviorInfo, UnsupportedOpInfo,
122+
ResourceExhaustionInfo, UndefinedBehaviorInfo, UninitBytesAccess, UnsupportedOpInfo,
123123
};
124124

125125
pub use self::value::{get_slice_bytes, ConstValue, RawConst, Scalar, ScalarMaybeUninit};

src/librustc_mir/interpret/validity.rs

+8-6
Original file line numberDiff line numberDiff line change
@@ -366,7 +366,7 @@ impl<'rt, 'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> ValidityVisitor<'rt, 'mir, '
366366
let place = try_validation!(
367367
self.ecx.ref_to_mplace(value),
368368
self.path,
369-
err_ub!(InvalidUninitBytes(..)) => { "uninitialized {}", kind },
369+
err_ub!(InvalidUninitBytes { .. }) => { "uninitialized {}", kind },
370370
);
371371
if place.layout.is_unsized() {
372372
self.check_wide_ptr_meta(place.meta, place.layout)?;
@@ -514,7 +514,7 @@ impl<'rt, 'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> ValidityVisitor<'rt, 'mir, '
514514
let place = try_validation!(
515515
self.ecx.ref_to_mplace(self.ecx.read_immediate(value)?),
516516
self.path,
517-
err_ub!(InvalidUninitBytes(..)) => { "uninitialized raw pointer" },
517+
err_ub!(InvalidUninitBytes { .. } ) => { "uninitialized raw pointer" },
518518
);
519519
if place.layout.is_unsized() {
520520
self.check_wide_ptr_meta(place.meta, place.layout)?;
@@ -592,7 +592,7 @@ impl<'rt, 'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> ValidityVisitor<'rt, 'mir, '
592592
let value = try_validation!(
593593
value.not_undef(),
594594
self.path,
595-
err_ub!(InvalidUninitBytes(..)) => { "{}", value }
595+
err_ub!(InvalidUninitBytes { .. }) => { "{}", value }
596596
expected { "something {}", wrapping_range_format(valid_range, max_hi) },
597597
);
598598
let bits = match value.to_bits_or_ptr(op.layout.size, self.ecx) {
@@ -803,12 +803,14 @@ impl<'rt, 'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> ValueVisitor<'mir, 'tcx, M>
803803
// For some errors we might be able to provide extra information.
804804
// (This custom logic does not fit the `try_validation!` macro.)
805805
match err.kind {
806-
err_ub!(InvalidUninitBytes(Some(ptr))) => {
806+
err_ub!(InvalidUninitBytes(Some(access))) => {
807807
// Some byte was uninitialized, determine which
808808
// element that byte belongs to so we can
809809
// provide an index.
810-
let i = usize::try_from(ptr.offset.bytes() / layout.size.bytes())
811-
.unwrap();
810+
let i = usize::try_from(
811+
access.uninit_ptr.offset.bytes() / layout.size.bytes(),
812+
)
813+
.unwrap();
812814
self.path.push(PathElem::ArrayElem(i));
813815

814816
throw_validation_failure!(self.path, { "uninitialized bytes" })

0 commit comments

Comments
 (0)