Skip to content

Show the offset, length and memory of uninit read errors #142673

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 0 additions & 2 deletions compiler/rustc_const_eval/messages.ftl
Original file line number Diff line number Diff line change
Expand Up @@ -179,8 +179,6 @@ const_eval_invalid_transmute =

const_eval_invalid_uninit_bytes =
reading memory at {$alloc}{$access}, but memory is uninitialized at {$uninit}, and this operation requires initialized memory
const_eval_invalid_uninit_bytes_unknown =
using uninitialized data, but this operation requires initialized memory

const_eval_invalid_vtable_pointer =
using {$pointer} as vtable pointer but it does not point to a vtable
Expand Down
25 changes: 21 additions & 4 deletions compiler/rustc_const_eval/src/const_eval/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,16 @@ use std::mem;

use rustc_errors::{Diag, DiagArgName, DiagArgValue, DiagMessage, IntoDiagArg};
use rustc_middle::mir::AssertKind;
use rustc_middle::mir::interpret::{Provenance, ReportedErrorInfo};
use rustc_middle::mir::interpret::{Provenance, ReportedErrorInfo, UndefinedBehaviorInfo};
use rustc_middle::query::TyCtxtAt;
use rustc_middle::ty::ConstInt;
use rustc_middle::ty::layout::LayoutError;
use rustc_middle::ty::{ConstInt, TyCtxt};
use rustc_span::{Span, Symbol};

use super::CompileTimeMachine;
use crate::errors::{self, FrameNote, ReportErrorExt};
use crate::interpret::{
ErrorHandled, Frame, InterpErrorInfo, InterpErrorKind, MachineStopType, err_inval,
ErrorHandled, Frame, InterpCx, InterpErrorInfo, InterpErrorKind, MachineStopType, err_inval,
err_machine_stop,
};

Expand Down Expand Up @@ -135,7 +135,7 @@ pub fn get_span_and_frames<'tcx>(
/// You can use it to add a stacktrace of current execution according to
/// `get_span_and_frames` or just give context on where the const eval error happened.
pub(super) fn report<'tcx, C, F>(
tcx: TyCtxt<'tcx>,
ecx: &InterpCx<'tcx, CompileTimeMachine<'tcx>>,
error: InterpErrorKind<'tcx>,
span: Span,
get_span_and_frames: C,
Expand All @@ -145,6 +145,7 @@ where
C: FnOnce() -> (Span, Vec<FrameNote>),
F: FnOnce(&mut Diag<'_>, Span, Vec<FrameNote>),
{
let tcx = ecx.tcx.tcx;
// Special handling for certain errors
match error {
// Don't emit a new diagnostic for these errors, they are already reported elsewhere or
Expand All @@ -170,9 +171,25 @@ where
InterpErrorKind::ResourceExhaustion(_) | InterpErrorKind::InvalidProgram(_)
);

if let InterpErrorKind::UndefinedBehavior(UndefinedBehaviorInfo::InvalidUninitBytes(
alloc_id,
_access,
)) = error
{
let bytes = ecx.print_alloc_bytes_for_diagnostics(alloc_id);
let info = ecx.get_alloc_info(alloc_id);
let raw_bytes = errors::RawBytesNote {
size: info.size.bytes(),
align: info.align.bytes(),
bytes,
};
err.subdiagnostic(raw_bytes);
}

error.add_args(&mut err);

mk(&mut err, span, frames);

let g = err.emit();
let reported = if allowed_in_infallible {
ReportedErrorInfo::allowed_in_infallible(g)
Expand Down
13 changes: 9 additions & 4 deletions compiler/rustc_const_eval/src/const_eval/eval_queries.rs
Original file line number Diff line number Diff line change
Expand Up @@ -240,7 +240,7 @@ pub(super) fn op_to_const<'tcx>(
let meta = b.to_target_usize(ecx).expect(msg);
ConstValue::Slice { data, meta }
}
Immediate::Uninit => bug!("`Uninit` is not a valid value for {}", op.layout.ty),
Immediate::Uninit => bug!("`Zst` is not a valid value for {}", op.layout.ty),
},
}
}
Expand Down Expand Up @@ -293,7 +293,12 @@ pub fn eval_to_const_value_raw_provider<'tcx>(

// FIXME(oli-obk): why don't we have any tests for this code path?
super::report(
tcx,
&InterpCx::new(
tcx,
tcx.def_span(def_id),
key.typing_env,
CompileTimeMachine::new(CanAccessMutGlobal::Yes, CheckAlignment::No),
),
error.into_kind(),
span,
|| (span, vec![]),
Expand Down Expand Up @@ -433,7 +438,7 @@ fn report_eval_error<'tcx>(
let instance = with_no_trimmed_paths!(cid.instance.to_string());

super::report(
*ecx.tcx,
ecx,
error,
DUMMY_SP,
|| super::get_span_and_frames(ecx.tcx, ecx.stack()),
Expand Down Expand Up @@ -473,7 +478,7 @@ fn report_validation_error<'tcx>(
errors::RawBytesNote { size: info.size.bytes(), align: info.align.bytes(), bytes };

crate::const_eval::report(
*ecx.tcx,
ecx,
error,
DUMMY_SP,
|| crate::const_eval::get_span_and_frames(ecx.tcx, ecx.stack()),
Expand Down
6 changes: 2 additions & 4 deletions compiler/rustc_const_eval/src/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -482,8 +482,7 @@ impl<'a> ReportErrorExt for UndefinedBehaviorInfo<'a> {
InvalidVTablePointer(_) => const_eval_invalid_vtable_pointer,
InvalidVTableTrait { .. } => const_eval_invalid_vtable_trait,
InvalidStr(_) => const_eval_invalid_str,
InvalidUninitBytes(None) => const_eval_invalid_uninit_bytes_unknown,
InvalidUninitBytes(Some(_)) => const_eval_invalid_uninit_bytes,
InvalidUninitBytes(..) => const_eval_invalid_uninit_bytes,
DeadLocal => const_eval_dead_local,
ScalarSizeMismatch(_) => const_eval_scalar_size_mismatch,
UninhabitedEnumVariantWritten(_) => const_eval_uninhabited_enum_variant_written,
Expand Down Expand Up @@ -515,7 +514,6 @@ impl<'a> ReportErrorExt for UndefinedBehaviorInfo<'a> {
| PointerArithOverflow
| InvalidMeta(InvalidMetaKind::SliceTooBig)
| InvalidMeta(InvalidMetaKind::TooBig)
| InvalidUninitBytes(None)
| DeadLocal
| UninhabitedEnumVariantWritten(_)
| UninhabitedEnumVariantRead(_) => {}
Expand Down Expand Up @@ -603,7 +601,7 @@ impl<'a> ReportErrorExt for UndefinedBehaviorInfo<'a> {
InvalidStr(err) => {
diag.arg("err", format!("{err}"));
}
InvalidUninitBytes(Some((alloc, info))) => {
InvalidUninitBytes(alloc, info) => {
diag.arg("alloc", alloc);
diag.arg("access", info.access);
diag.arg("uninit", info.bad);
Expand Down
5 changes: 2 additions & 3 deletions compiler/rustc_const_eval/src/interpret/cast.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ use tracing::trace;

use super::util::ensure_monomorphic_enough;
use super::{
FnVal, ImmTy, Immediate, InterpCx, Machine, OpTy, PlaceTy, err_inval, interp_ok, throw_ub,
FnVal, ImmTy, Immediate, InterpCx, Machine, OpTy, PlaceTy, err_inval, interp_ok,
throw_ub_custom,
};
use crate::fluent_generated as fluent;
Expand Down Expand Up @@ -212,14 +212,13 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
assert!(src.layout.ty.is_raw_ptr());
return match **src {
Immediate::ScalarPair(data, _) => interp_ok(ImmTy::from_scalar(data, cast_to)),
Immediate::Scalar(..) => span_bug!(
Immediate::Scalar(..) | Immediate::Uninit => span_bug!(
self.cur_span(),
"{:?} input to a fat-to-thin cast ({} -> {})",
*src,
src.layout.ty,
cast_to.ty
),
Immediate::Uninit => throw_ub!(InvalidUninitBytes(None)),
};
}
}
Expand Down
26 changes: 18 additions & 8 deletions compiler/rustc_const_eval/src/interpret/operand.rs
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,11 @@ impl<Prov: Provenance> Immediate<Prov> {
}
}
(Immediate::Uninit, _) => {
assert!(abi.is_sized(), "{msg}: unsized immediates are not a thing");
assert_matches!(
abi,
BackendRepr::Memory { sized: true },
"{msg}: unsized zsts are not a thing"
);
}
_ => {
bug!("{msg}: value {self:?} does not match ABI {abi:?})",)
Expand Down Expand Up @@ -267,7 +271,7 @@ impl<'tcx, Prov: Provenance> ImmTy<'tcx, Prov> {
match (imm, layout.backend_repr) {
(Immediate::Scalar(..), BackendRepr::Scalar(..)) => true,
(Immediate::ScalarPair(..), BackendRepr::ScalarPair(..)) => true,
(Immediate::Uninit, _) if layout.is_sized() => true,
(Immediate::Uninit, _) if layout.is_sized() && layout.is_zst() => true,
_ => false,
},
"immediate {imm:?} does not fit to layout {layout:?}",
Expand All @@ -276,8 +280,9 @@ impl<'tcx, Prov: Provenance> ImmTy<'tcx, Prov> {
}

#[inline]
pub fn uninit(layout: TyAndLayout<'tcx>) -> Self {
pub fn zst(layout: TyAndLayout<'tcx>) -> Self {
debug_assert!(layout.is_sized(), "immediates must be sized");
debug_assert!(layout.is_zst());
ImmTy { imm: Immediate::Uninit, layout }
}

Expand Down Expand Up @@ -382,15 +387,16 @@ impl<'tcx, Prov: Provenance> ImmTy<'tcx, Prov> {
// This makes several assumptions about what layouts we will encounter; we match what
// codegen does as good as we can (see `extract_field` in `rustc_codegen_ssa/src/mir/operand.rs`).
let inner_val: Immediate<_> = match (**self, self.layout.backend_repr) {
// If the entire value is uninit, then so is the field (can happen in ConstProp).
(Immediate::Uninit, _) => Immediate::Uninit,
// If the field is uninhabited, we can forget the data (can happen in ConstProp).
// `enum S { A(!), B, C }` is an example of an enum with Scalar layout that
// has an uninhabited variant, which means this case is possible.
_ if layout.is_uninhabited() => Immediate::Uninit,
// the field contains no information, can be left uninit
// (Scalar/ScalarPair can contain even aligned ZST, not just 1-ZST)
_ if layout.is_zst() => Immediate::Uninit,
// If the value is a zst, then so is the field (can happen in ConstProp).
// This is handled by the zst field read above.
(Immediate::Uninit, _) => unreachable!(),
// some fieldless enum variants can have non-zero size but still `Aggregate` ABI... try
// to detect those here and also give them no data
_ if matches!(layout.backend_repr, BackendRepr::Memory { .. })
Expand Down Expand Up @@ -572,8 +578,7 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
}

let Some(alloc) = self.get_place_alloc(mplace)? else {
// zero-sized type can be left uninit
return interp_ok(Some(ImmTy::uninit(mplace.layout)));
return interp_ok(Some(ImmTy::zst(mplace.layout)));
};

// It may seem like all types with `Scalar` or `ScalarPair` ABI are fair game at this point.
Expand Down Expand Up @@ -663,7 +668,12 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
}
let imm = self.read_immediate_raw(op)?.right().unwrap();
if matches!(*imm, Immediate::Uninit) {
throw_ub!(InvalidUninitBytes(None));
// The only other source of uninit immediates is locals, and those are only ever converted
// to `Operand`s.
span_bug!(
self.tcx.span,
"uninit immediate reads should have already errored when reading from memory"
)
}
interp_ok(imm)
}
Expand Down
2 changes: 1 addition & 1 deletion compiler/rustc_const_eval/src/interpret/operator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -496,7 +496,7 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
}
MemPlaceMeta::None => {
let unit_layout = self.layout_of(self.tcx.types.unit)?;
ImmTy::uninit(unit_layout)
ImmTy::zst(unit_layout)
}
})
}
Expand Down
2 changes: 1 addition & 1 deletion compiler/rustc_const_eval/src/interpret/place.rs
Original file line number Diff line number Diff line change
Expand Up @@ -722,7 +722,7 @@ where
// We don't have to reset padding here, `write_immediate` will anyway do a validation run.
interp_ok(())
}
Immediate::Uninit => alloc.write_uninit_full(),
Immediate::Uninit => unreachable!("zst have been skipped in the alloc load above"),
}
}

Expand Down
6 changes: 3 additions & 3 deletions compiler/rustc_const_eval/src/interpret/validity.rs
Original file line number Diff line number Diff line change
Expand Up @@ -397,7 +397,7 @@ impl<'rt, 'tcx, M: Machine<'tcx>> ValidityVisitor<'rt, 'tcx, M> {
interp_ok(try_validation!(
self.ecx.read_immediate(val),
self.path,
Ub(InvalidUninitBytes(None)) =>
Ub(InvalidUninitBytes(..)) =>
Uninit { expected },
// The `Unsup` cases can only occur during CTFE
Unsup(ReadPointerAsInt(_)) =>
Expand Down Expand Up @@ -1203,7 +1203,7 @@ impl<'rt, 'tcx, M: Machine<'tcx>> ValueVisitor<'tcx, M> for ValidityVisitor<'rt,
// For some errors we might be able to provide extra information.
// (This custom logic does not fit the `try_validation!` macro.)
match kind {
Ub(InvalidUninitBytes(Some((_alloc_id, access)))) | Unsup(ReadPointerAsInt(Some((_alloc_id, access)))) => {
Ub(InvalidUninitBytes(_alloc_id, access)) | Unsup(ReadPointerAsInt(Some((_alloc_id, access)))) => {
// Some byte was uninitialized, determine which
// element that byte belongs to so we can
// provide an index.
Expand All @@ -1213,7 +1213,7 @@ impl<'rt, 'tcx, M: Machine<'tcx>> ValueVisitor<'tcx, M> for ValidityVisitor<'rt,
.unwrap();
self.path.push(PathElem::ArrayElem(i));

if matches!(kind, Ub(InvalidUninitBytes(_))) {
if matches!(kind, Ub(InvalidUninitBytes(..))) {
err_validation_failure!(self.path, Uninit { expected })
} else {
err_validation_failure!(self.path, PointerAsInt { expected })
Expand Down
13 changes: 5 additions & 8 deletions compiler/rustc_middle/src/mir/interpret/allocation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -309,7 +309,7 @@ pub enum AllocError {
/// Partially copying a pointer.
ReadPartialPointer(Size),
/// Using uninitialized data where it is not allowed.
InvalidUninitBytes(Option<BadBytesAccess>),
InvalidUninitBytes(BadBytesAccess),
}
pub type AllocResult<T = ()> = Result<T, AllocError>;

Expand All @@ -336,7 +336,7 @@ impl AllocError {
UnsupportedOpInfo::ReadPartialPointer(Pointer::new(alloc_id, offset)),
),
InvalidUninitBytes(info) => InterpErrorKind::UndefinedBehavior(
UndefinedBehaviorInfo::InvalidUninitBytes(info.map(|b| (alloc_id, b))),
UndefinedBehaviorInfo::InvalidUninitBytes(alloc_id, info),
),
}
}
Expand Down Expand Up @@ -597,10 +597,7 @@ impl<Prov: Provenance, Extra, Bytes: AllocBytes> Allocation<Prov, Extra, Bytes>
range: AllocRange,
) -> AllocResult<&[u8]> {
self.init_mask.is_range_initialized(range).map_err(|uninit_range| {
AllocError::InvalidUninitBytes(Some(BadBytesAccess {
access: range,
bad: uninit_range,
}))
AllocError::InvalidUninitBytes(BadBytesAccess { access: range, bad: uninit_range })
})?;
if !Prov::OFFSET_IS_ADDR && !self.provenance.range_empty(range, cx) {
// Find the provenance.
Expand Down Expand Up @@ -700,8 +697,8 @@ impl<Prov: Provenance, Extra, Bytes: AllocBytes> Allocation<Prov, Extra, Bytes>
read_provenance: bool,
) -> AllocResult<Scalar<Prov>> {
// First and foremost, if anything is uninit, bail.
if self.init_mask.is_range_initialized(range).is_err() {
return Err(AllocError::InvalidUninitBytes(None));
if let Err(bad) = self.init_mask.is_range_initialized(range) {
return Err(AllocError::InvalidUninitBytes(BadBytesAccess { access: range, bad }));
}

// Get the integer part of the result. We HAVE TO check provenance before returning this!
Expand Down
2 changes: 1 addition & 1 deletion compiler/rustc_middle/src/mir/interpret/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -412,7 +412,7 @@ pub enum UndefinedBehaviorInfo<'tcx> {
/// Using a string that is not valid UTF-8,
InvalidStr(std::str::Utf8Error),
/// Using uninitialized data where it is not allowed.
InvalidUninitBytes(Option<(AllocId, BadBytesAccess)>),
InvalidUninitBytes(AllocId, BadBytesAccess),
/// Working with a local that is not currently live.
DeadLocal,
/// Data size is not equal to target size.
Expand Down
2 changes: 1 addition & 1 deletion compiler/rustc_mir_transform/src/gvn.rs
Original file line number Diff line number Diff line change
Expand Up @@ -407,7 +407,7 @@ impl<'body, 'tcx> VnState<'body, 'tcx> {
let variant = if ty.is_enum() { Some(variant) } else { None };
let ty = self.ecx.layout_of(ty).ok()?;
if ty.is_zst() {
ImmTy::uninit(ty).into()
ImmTy::zst(ty).into()
} else if matches!(kind, AggregateTy::RawPtr { .. }) {
// Pointers don't have fields, so don't `project_field` them.
let data = self.ecx.read_pointer(fields[0]).discard_err()?;
Expand Down
5 changes: 2 additions & 3 deletions src/tools/miri/src/concurrency/data_race.rs
Original file line number Diff line number Diff line change
Expand Up @@ -731,10 +731,9 @@ pub trait EvalContextExt<'tcx>: MiriInterpCxExt<'tcx> {
}

let scalar = this.allow_data_races_ref(move |this| this.read_scalar(place))?;
let buffered_scalar = this.buffered_atomic_read(place, atomic, scalar, || {
this.buffered_atomic_read(place, atomic, scalar, || {
this.validate_atomic_load(place, atomic)
})?;
interp_ok(buffered_scalar.ok_or_else(|| err_ub!(InvalidUninitBytes(None)))?)
})
}

/// Perform an atomic write operation at the memory location.
Expand Down
13 changes: 9 additions & 4 deletions src/tools/miri/src/concurrency/weak_memory.rs
Original file line number Diff line number Diff line change
Expand Up @@ -488,7 +488,7 @@ pub(super) trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
atomic: AtomicReadOrd,
latest_in_mo: Scalar,
validate: impl FnOnce() -> InterpResult<'tcx>,
) -> InterpResult<'tcx, Option<Scalar>> {
) -> InterpResult<'tcx, Scalar> {
let this = self.eval_context_ref();
'fallback: {
if let Some(global) = this.machine.data_race.as_vclocks_ref() {
Expand Down Expand Up @@ -518,15 +518,20 @@ pub(super) trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
ptr: place.ptr(),
});
}

return interp_ok(loaded);
return interp_ok(loaded.ok_or_else(|| {
let access = alloc_range(base_offset, place.layout.size);
err_ub!(InvalidUninitBytes(
alloc_id,
BadBytesAccess { access, bad: access }
))
})?);
}
}
}

// Race detector or weak memory disabled, simply read the latest value
validate()?;
interp_ok(Some(latest_in_mo))
interp_ok(latest_in_mo)
}

/// Add the given write to the store buffer. (Does not change machine memory.)
Expand Down
2 changes: 1 addition & 1 deletion src/tools/miri/src/diagnostics.rs
Original file line number Diff line number Diff line change
Expand Up @@ -411,7 +411,7 @@ pub fn report_error<'tcx>(
// Since `format_interp_error` consumes `e`, we compute the outut early.
let mut extra = String::new();
match e.kind() {
UndefinedBehavior(InvalidUninitBytes(Some((alloc_id, access)))) => {
UndefinedBehavior(InvalidUninitBytes(alloc_id, access)) => {
writeln!(
extra,
"Uninitialized memory occurred at {alloc_id:?}{range:?}, in this allocation:",
Expand Down
Loading
Loading