Skip to content

Commit ea453bb

Browse files
Rollup merge of #130885 - RalfJung:interp-error-discard, r=oli-obk
panic when an interpreter error gets unintentionally discarded One important invariant of Miri is that when an interpreter error is raised (*in particular* a UB error), those must not be discarded: it's not okay to just check `foo().is_err()` and then continue executing. This seems to catch new contributors by surprise fairly regularly, so this PR tries to make it so that *if* this ever happens, we get a panic rather than a silent missed UB bug. The interpreter error type now contains a "guard" that panics on drop, and that is explicitly passed to `mem::forget` when an error is deliberately discarded. Fixesrust-lang/miri#3855
2 parents 1d71891 + c4ce8c1 commit ea453bb

102 files changed

Lines changed: 1576 additions & 1339 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

‎compiler/rustc_const_eval/src/const_eval/dummy_machine.rs‎

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,8 @@ use rustc_middle::{bug, span_bug, ty};
66
use rustc_span::def_id::DefId;
77

88
usecrate::interpret::{
9-
self,HasStaticRootDefId,ImmTy,Immediate,InterpCx,PointerArithmetic, throw_machine_stop,
9+
self,HasStaticRootDefId,ImmTy,Immediate,InterpCx,PointerArithmetic, interp_ok,
10+
throw_machine_stop,
1011
};
1112

1213
/// Macro for machine-specific `InterpError` without allocation.
@@ -79,7 +80,7 @@ impl<'tcx> interpret::Machine<'tcx> for DummyMachine {
7980
throw_machine_stop_str!("can't access mutable globals in ConstProp");
8081
}
8182

82-
Ok(())
83+
interp_ok(())
8384
}
8485

8586
fnfind_mir_or_eval_fn(
@@ -127,7 +128,7 @@ impl<'tcx> interpret::Machine<'tcx> for DummyMachine {
127128
right:&interpret::ImmTy<'tcx,Self::Provenance>,
128129
) -> interpret::InterpResult<'tcx,ImmTy<'tcx,Self::Provenance>>{
129130
use rustc_middle::mir::BinOp::*;
130-
Ok(match bin_op {
131+
interp_ok(match bin_op {
131132
Eq | Ne | Lt | Le | Gt | Ge => {
132133
// Types can differ, e.g. fn ptrs with different `for`.
133134
assert_eq!(left.layout.abi, right.layout.abi);

‎compiler/rustc_const_eval/src/const_eval/eval_queries.rs‎

Lines changed: 31 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ use crate::const_eval::CheckAlignment;
2020
usecrate::interpret::{
2121
CtfeValidationMode,GlobalId,Immediate,InternKind,InternResult,InterpCx,InterpError,
2222
InterpResult,MPlaceTy,MemoryKind,OpTy,RefTracking,StackPopCleanup, create_static_alloc,
23-
eval_nullary_intrinsic, intern_const_alloc_recursive, throw_exhaust,
23+
eval_nullary_intrinsic, intern_const_alloc_recursive,interp_ok,throw_exhaust,
2424
};
2525
usecrate::{CTRL_C_RECEIVED, errors};
2626

@@ -98,19 +98,19 @@ fn eval_body_using_ecx<'tcx, R: InterpretationResult<'tcx>>(
9898
returnErr(ecx
9999
.tcx
100100
.dcx()
101-
.emit_err(errors::DanglingPtrInFinal{span: ecx.tcx.span,kind: intern_kind })
102-
.into());
101+
.emit_err(errors::DanglingPtrInFinal{span: ecx.tcx.span,kind: intern_kind }))
102+
.into();
103103
}
104104
Err(InternResult::FoundBadMutablePointer) => {
105105
returnErr(ecx
106106
.tcx
107107
.dcx()
108-
.emit_err(errors::MutablePtrInFinal{span: ecx.tcx.span,kind: intern_kind })
109-
.into());
108+
.emit_err(errors::MutablePtrInFinal{span: ecx.tcx.span,kind: intern_kind }))
109+
.into();
110110
}
111111
}
112112

113-
Ok(R::make_result(ret, ecx))
113+
interp_ok(R::make_result(ret, ecx))
114114
}
115115

116116
/// The `InterpCx` is only meant to be used to do field and index projections into constants for
@@ -147,7 +147,8 @@ pub fn mk_eval_cx_for_const_val<'tcx>(
147147
ty:Ty<'tcx>,
148148
) -> Option<(CompileTimeInterpCx<'tcx>,OpTy<'tcx>)>{
149149
let ecx = mk_eval_cx_to_read_const_val(tcx.tcx, tcx.span, param_env,CanAccessMutGlobal::No);
150-
let op = ecx.const_val_to_op(val, ty,None).ok()?;
150+
// FIXME: is it a problem to discard the error here?
151+
let op = ecx.const_val_to_op(val, ty,None).discard_err()?;
151152
Some((ecx, op))
152153
}
153154

@@ -185,12 +186,16 @@ pub(super) fn op_to_const<'tcx>(
185186
_ => false,
186187
};
187188
let immediate = if force_as_immediate {
188-
match ecx.read_immediate(op){
189+
match ecx.read_immediate(op).report_err(){
189190
Ok(imm) => Right(imm),
190-
Err(err)if !for_diagnostics => {
191-
panic!("normalization works on validated constants: {err:?}")
191+
Err(err) => {
192+
if for_diagnostics {
193+
// This discard the error, but for diagnostics that's okay.
194+
op.as_mplace_or_imm()
195+
}else{
196+
panic!("normalization works on validated constants: {err:?}")
197+
}
192198
}
193-
_ => op.as_mplace_or_imm(),
194199
}
195200
}else{
196201
op.as_mplace_or_imm()
@@ -283,17 +288,19 @@ pub fn eval_to_const_value_raw_provider<'tcx>(
283288
let ty::FnDef(_, args) = ty.kind()else{
284289
bug!("intrinsic with type {:?}", ty);
285290
};
286-
returneval_nullary_intrinsic(tcx, key.param_env, def_id, args).map_err(|error| {
287-
let span = tcx.def_span(def_id);
288-
289-
super::report(
290-
tcx,
291-
error.into_kind(),
292-
span,
293-
|| (span,vec![]),
294-
|span, _| errors::NullaryIntrinsicError{ span },
295-
)
296-
});
291+
returneval_nullary_intrinsic(tcx, key.param_env, def_id, args).report_err().map_err(
292+
|error| {
293+
let span = tcx.def_span(def_id);
294+
295+
super::report(
296+
tcx,
297+
error.into_kind(),
298+
span,
299+
|| (span,vec![]),
300+
|span, _| errors::NullaryIntrinsicError{ span },
301+
)
302+
},
303+
);
297304
}
298305

299306
tcx.eval_to_allocation_raw(key).map(|val| turn_into_const_value(tcx, val, key))
@@ -376,6 +383,7 @@ fn eval_in_interpreter<'tcx, R: InterpretationResult<'tcx>>(
376383
);
377384
let res = ecx.load_mir(cid.instance.def, cid.promoted);
378385
res.and_then(|body| eval_body_using_ecx(&mut ecx, cid, body))
386+
.report_err()
379387
.map_err(|error| report_eval_error(&ecx, cid, error))
380388
}
381389

@@ -400,6 +408,7 @@ fn const_validate_mplace<'tcx>(
400408
}
401409
};
402410
ecx.const_validate_operand(&mplace.into(), path,&mut ref_tracking, mode)
411+
.report_err()
403412
// Instead of just reporting the `InterpError` via the usual machinery, we give a more targeted
404413
// error about the validation failure.
405414
.map_err(|error| report_validation_error(&ecx, cid, error, alloc_id))?;

‎compiler/rustc_const_eval/src/const_eval/machine.rs‎

Lines changed: 34 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,8 @@ use crate::fluent_generated as fluent;
2424
usecrate::interpret::{
2525
self,AllocId,AllocRange,ConstAllocation,CtfeProvenance,FnArg,Frame,GlobalAlloc,ImmTy,
2626
InterpCx,InterpResult,MPlaceTy,OpTy,Pointer,PointerArithmetic,RangeSet,Scalar,
27-
StackPopCleanup, compile_time_machine,err_ub, throw_exhaust, throw_inval,throw_ub_custom,
28-
throw_unsup, throw_unsup_format,
27+
StackPopCleanup, compile_time_machine,interp_ok, throw_exhaust, throw_inval,throw_ub,
28+
throw_ub_custom,throw_unsup, throw_unsup_format,
2929
};
3030

3131
/// When hitting this many interpreted terminators we emit a deny by default lint
@@ -247,7 +247,7 @@ impl<'tcx> CompileTimeInterpCx<'tcx> {
247247
let msg = Symbol::intern(self.read_str(&msg_place)?);
248248
let span = self.find_closest_untracked_caller_location();
249249
let(file, line, col) = self.location_triple_for_span(span);
250-
returnErr(ConstEvalErrKind::Panic{ msg, file, line, col }.into());
250+
returnErr(ConstEvalErrKind::Panic{ msg, file, line, col }).into();
251251
}elseifself.tcx.is_lang_item(def_id,LangItem::PanicFmt){
252252
// For panic_fmt, call const_panic_fmt instead.
253253
let const_def_id = self.tcx.require_lang_item(LangItem::ConstPanicFmt,None);
@@ -259,16 +259,16 @@ impl<'tcx> CompileTimeInterpCx<'tcx> {
259259
self.cur_span(),
260260
);
261261

262-
returnOk(Some(new_instance));
262+
returninterp_ok(Some(new_instance));
263263
}elseifself.tcx.is_lang_item(def_id,LangItem::AlignOffset){
264264
let args = self.copy_fn_args(args);
265265
// For align_offset, we replace the function call if the pointer has no address.
266266
matchself.align_offset(instance,&args, dest, ret)? {
267-
ControlFlow::Continue(()) => returnOk(Some(instance)),
268-
ControlFlow::Break(()) => returnOk(None),
267+
ControlFlow::Continue(()) => returninterp_ok(Some(instance)),
268+
ControlFlow::Break(()) => returninterp_ok(None),
269269
}
270270
}
271-
Ok(Some(instance))
271+
interp_ok(Some(instance))
272272
}
273273

274274
/// `align_offset(ptr, target_align)` needs special handling in const eval, because the pointer
@@ -323,25 +323,25 @@ impl<'tcx> CompileTimeInterpCx<'tcx> {
323323
dest,
324324
StackPopCleanup::Goto{ ret,unwind: mir::UnwindAction::Unreachable},
325325
)?;
326-
Ok(ControlFlow::Break(()))
326+
interp_ok(ControlFlow::Break(()))
327327
}else{
328328
// Not alignable in const, return `usize::MAX`.
329329
let usize_max = Scalar::from_target_usize(self.target_usize_max(),self);
330330
self.write_scalar(usize_max, dest)?;
331331
self.return_to_block(ret)?;
332-
Ok(ControlFlow::Break(()))
332+
interp_ok(ControlFlow::Break(()))
333333
}
334334
}
335335
Err(_addr) => {
336336
// The pointer has an address, continue with function call.
337-
Ok(ControlFlow::Continue(()))
337+
interp_ok(ControlFlow::Continue(()))
338338
}
339339
}
340340
}
341341

342342
/// See documentation on the `ptr_guaranteed_cmp` intrinsic.
343343
fnguaranteed_cmp(&mutself,a:Scalar,b:Scalar) -> InterpResult<'tcx,u8>{
344-
Ok(match(a, b){
344+
interp_ok(match(a, b){
345345
// Comparisons between integers are always known.
346346
(Scalar::Int{ .. },Scalar::Int{ .. }) => {
347347
if a == b {
@@ -403,8 +403,8 @@ impl<'tcx> interpret::Machine<'tcx> for CompileTimeMachine<'tcx> {
403403
instance: ty::InstanceKind<'tcx>,
404404
) -> InterpResult<'tcx,&'tcx mir::Body<'tcx>>{
405405
match instance {
406-
ty::InstanceKind::Item(def) => Ok(ecx.tcx.mir_for_ctfe(def)),
407-
_ => Ok(ecx.tcx.instance_mir(instance)),
406+
ty::InstanceKind::Item(def) => interp_ok(ecx.tcx.mir_for_ctfe(def)),
407+
_ => interp_ok(ecx.tcx.instance_mir(instance)),
408408
}
409409
}
410410

@@ -422,7 +422,7 @@ impl<'tcx> interpret::Machine<'tcx> for CompileTimeMachine<'tcx> {
422422
// Replace some functions.
423423
letSome(instance) = ecx.hook_special_const_fn(orig_instance, args, dest, ret)? else{
424424
// Call has already been handled.
425-
returnOk(None);
425+
returninterp_ok(None);
426426
};
427427

428428
// Only check non-glue functions
@@ -444,14 +444,14 @@ impl<'tcx> interpret::Machine<'tcx> for CompileTimeMachine<'tcx> {
444444
// This is a const fn. Call it.
445445
// In case of replacement, we return the *original* instance to make backtraces work out
446446
// (and we hope this does not confuse the FnAbi checks too much).
447-
Ok(Some((ecx.load_mir(instance.def,None)?, orig_instance)))
447+
interp_ok(Some((ecx.load_mir(instance.def,None)?, orig_instance)))
448448
}
449449

450450
fnpanic_nounwind(ecx:&mutInterpCx<'tcx,Self>,msg:&str) -> InterpResult<'tcx>{
451451
let msg = Symbol::intern(msg);
452452
let span = ecx.find_closest_untracked_caller_location();
453453
let(file, line, col) = ecx.location_triple_for_span(span);
454-
Err(ConstEvalErrKind::Panic{ msg, file, line, col }.into())
454+
Err(ConstEvalErrKind::Panic{ msg, file, line, col }).into()
455455
}
456456

457457
fncall_intrinsic(
@@ -464,7 +464,7 @@ impl<'tcx> interpret::Machine<'tcx> for CompileTimeMachine<'tcx> {
464464
) -> InterpResult<'tcx,Option<ty::Instance<'tcx>>>{
465465
// Shared intrinsics.
466466
if ecx.eval_intrinsic(instance, args, dest, target)? {
467-
returnOk(None);
467+
returninterp_ok(None);
468468
}
469469
let intrinsic_name = ecx.tcx.item_name(instance.def_id());
470470

@@ -541,7 +541,7 @@ impl<'tcx> interpret::Machine<'tcx> for CompileTimeMachine<'tcx> {
541541
"intrinsic `{intrinsic_name}` is not supported at compile-time"
542542
);
543543
}
544-
returnOk(Some(ty::Instance{
544+
returninterp_ok(Some(ty::Instance{
545545
def: ty::InstanceKind::Item(instance.def_id()),
546546
args: instance.args,
547547
}));
@@ -550,7 +550,7 @@ impl<'tcx> interpret::Machine<'tcx> for CompileTimeMachine<'tcx> {
550550

551551
// Intrinsic is done, jump to next block.
552552
ecx.return_to_block(target)?;
553-
Ok(None)
553+
interp_ok(None)
554554
}
555555

556556
fnassert_panic(
@@ -581,7 +581,7 @@ impl<'tcx> interpret::Machine<'tcx> for CompileTimeMachine<'tcx> {
581581
}
582582
}
583583
};
584-
Err(ConstEvalErrKind::AssertFailure(err).into())
584+
Err(ConstEvalErrKind::AssertFailure(err)).into()
585585
}
586586

587587
fnbinary_ptr_op(
@@ -652,7 +652,7 @@ impl<'tcx> interpret::Machine<'tcx> for CompileTimeMachine<'tcx> {
652652
}
653653
}
654654

655-
Ok(())
655+
interp_ok(())
656656
}
657657

658658
#[inline(always)]
@@ -670,7 +670,7 @@ impl<'tcx> interpret::Machine<'tcx> for CompileTimeMachine<'tcx> {
670670
if !ecx.recursion_limit.value_within_limit(ecx.stack().len() + 1){
671671
throw_exhaust!(StackFrameLimitReached)
672672
}else{
673-
Ok(frame)
673+
interp_ok(frame)
674674
}
675675
}
676676

@@ -700,22 +700,22 @@ impl<'tcx> interpret::Machine<'tcx> for CompileTimeMachine<'tcx> {
700700
if is_write {
701701
// Write access. These are never allowed, but we give a targeted error message.
702702
match alloc.mutability{
703-
Mutability::Not => Err(err_ub!(WriteToReadOnly(alloc_id)).into()),
704-
Mutability::Mut => Err(ConstEvalErrKind::ModifiedGlobal.into()),
703+
Mutability::Not => throw_ub!(WriteToReadOnly(alloc_id)),
704+
Mutability::Mut => Err(ConstEvalErrKind::ModifiedGlobal).into(),
705705
}
706706
}else{
707707
// Read access. These are usually allowed, with some exceptions.
708708
if machine.can_access_mut_global == CanAccessMutGlobal::Yes{
709709
// Machine configuration allows us read from anything (e.g., `static` initializer).
710-
Ok(())
710+
interp_ok(())
711711
}elseif alloc.mutability == Mutability::Mut{
712712
// Machine configuration does not allow us to read statics (e.g., `const`
713713
// initializer).
714-
Err(ConstEvalErrKind::ConstAccessesMutGlobal.into())
714+
Err(ConstEvalErrKind::ConstAccessesMutGlobal).into()
715715
}else{
716716
// Immutable global, this read is fine.
717717
assert_eq!(alloc.mutability,Mutability::Not);
718-
Ok(())
718+
interp_ok(())
719719
}
720720
}
721721
}
@@ -748,9 +748,9 @@ impl<'tcx> interpret::Machine<'tcx> for CompileTimeMachine<'tcx> {
748748
// even when there is interior mutability.)
749749
place.map_provenance(CtfeProvenance::as_shared_ref)
750750
};
751-
Ok(ImmTy::from_immediate(new_place.to_ref(ecx), val.layout))
751+
interp_ok(ImmTy::from_immediate(new_place.to_ref(ecx), val.layout))
752752
}else{
753-
Ok(val.clone())
753+
interp_ok(val.clone())
754754
}
755755
}
756756

@@ -763,20 +763,20 @@ impl<'tcx> interpret::Machine<'tcx> for CompileTimeMachine<'tcx> {
763763
) -> InterpResult<'tcx>{
764764
if range.size == Size::ZERO{
765765
// Nothing to check.
766-
returnOk(());
766+
returninterp_ok(());
767767
}
768768
// Reject writes through immutable pointers.
769769
if immutable {
770-
returnErr(ConstEvalErrKind::WriteThroughImmutablePointer.into());
770+
returnErr(ConstEvalErrKind::WriteThroughImmutablePointer).into();
771771
}
772772
// Everything else is fine.
773-
Ok(())
773+
interp_ok(())
774774
}
775775

776776
fnbefore_alloc_read(ecx:&InterpCx<'tcx,Self>,alloc_id:AllocId) -> InterpResult<'tcx>{
777777
// Check if this is the currently evaluated static.
778778
ifSome(alloc_id) == ecx.machine.static_root_ids.map(|(id, _)| id){
779-
returnErr(ConstEvalErrKind::RecursiveStatic.into());
779+
returnErr(ConstEvalErrKind::RecursiveStatic).into();
780780
}
781781
// If this is another static, make sure we fire off the query to detect cycles.
782782
// But only do that when checks for static recursion are enabled.
@@ -788,7 +788,7 @@ impl<'tcx> interpret::Machine<'tcx> for CompileTimeMachine<'tcx> {
788788
ecx.ctfe_query(|tcx| tcx.eval_static_initializer(def_id))?;
789789
}
790790
}
791-
Ok(())
791+
interp_ok(())
792792
}
793793

794794
fncached_union_data_range<'e>(

0 commit comments

Comments
 (0)