Auto merge of #94934 - Lireer:const-prop-lint, r=oli-obk

Separate const prop lints from optimizations

r? `@oli-obk`

Separates lints and optimizations during const prop by moving the lints into their own file and checking them during post borrowck cleanup.
Thanks to `@oli-obk` for mentoring me.
This commit is contained in:
bors
2022-03-24 20:22:27 +00:00
30 changed files with 1308 additions and 239 deletions
+23 -177
View File
@@ -6,30 +6,25 @@
use rustc_ast::Mutability;
use rustc_data_structures::fx::FxHashSet;
use rustc_hir::def::DefKind;
use rustc_hir::HirId;
use rustc_index::bit_set::BitSet;
use rustc_index::vec::IndexVec;
use rustc_middle::mir::visit::{
MutVisitor, MutatingUseContext, NonMutatingUseContext, PlaceContext, Visitor,
};
use rustc_middle::mir::{
AssertKind, BasicBlock, BinOp, Body, Constant, ConstantKind, Local, LocalDecl, LocalKind,
Location, Operand, Place, Rvalue, SourceInfo, SourceScope, SourceScopeData, Statement,
StatementKind, Terminator, TerminatorKind, UnOp, RETURN_PLACE,
BasicBlock, BinOp, Body, Constant, ConstantKind, Local, LocalDecl, LocalKind, Location,
Operand, Place, Rvalue, SourceInfo, Statement, StatementKind, Terminator, TerminatorKind, UnOp,
RETURN_PLACE,
};
use rustc_middle::ty::layout::{LayoutError, LayoutOf, LayoutOfHelpers, TyAndLayout};
use rustc_middle::ty::subst::{InternalSubsts, Subst};
use rustc_middle::ty::{
self, ConstInt, ConstKind, Instance, ParamEnv, ScalarInt, Ty, TyCtxt, TypeFoldable,
};
use rustc_session::lint;
use rustc_middle::ty::{self, ConstKind, Instance, ParamEnv, Ty, TyCtxt, TypeFoldable};
use rustc_span::{def_id::DefId, Span};
use rustc_target::abi::{HasDataLayout, Size, TargetDataLayout};
use rustc_target::spec::abi::Abi;
use rustc_trait_selection::traits;
use crate::MirPass;
use rustc_const_eval::const_eval::ConstEvalErr;
use rustc_const_eval::interpret::{
self, compile_time_machine, AllocId, ConstAllocation, ConstValue, CtfeValidationMode, Frame,
ImmTy, Immediate, InterpCx, InterpResult, LocalState, LocalValue, MemPlace, MemoryKind, OpTy,
@@ -318,9 +313,8 @@ struct ConstPropagator<'mir, 'tcx> {
ecx: InterpCx<'mir, 'tcx, ConstPropMachine<'mir, 'tcx>>,
tcx: TyCtxt<'tcx>,
param_env: ParamEnv<'tcx>,
// FIXME(eddyb) avoid cloning these two fields more than once,
// by accessing them through `ecx` instead.
source_scopes: IndexVec<SourceScope, SourceScopeData<'tcx>>,
// FIXME(eddyb) avoid cloning this field more than once,
// by accessing it through `ecx` instead.
local_decls: IndexVec<Local, LocalDecl<'tcx>>,
// Because we have `MutVisitor` we can't obtain the `SourceInfo` from a `Location`. So we store
// the last known `SourceInfo` here and just keep revisiting it.
@@ -412,9 +406,8 @@ fn new(
ecx,
tcx,
param_env,
// FIXME(eddyb) avoid cloning these two fields more than once,
// by accessing them through `ecx` instead.
source_scopes: body.source_scopes.clone(),
// FIXME(eddyb) avoid cloning this field more than once,
// by accessing it through `ecx` instead.
//FIXME(wesleywiser) we can't steal this because `Visitor::super_visit_body()` needs it
local_decls: body.local_decls.clone(),
source_info: None,
@@ -445,10 +438,6 @@ fn remove_const(ecx: &mut InterpCx<'mir, 'tcx, ConstPropMachine<'mir, 'tcx>>, lo
LocalState { value: LocalValue::Unallocated, layout: Cell::new(None) };
}
fn lint_root(&self, source_info: SourceInfo) -> Option<HirId> {
source_info.scope.lint_root(&self.source_scopes)
}
fn use_ecx<F, T>(&mut self, f: F) -> Option<T>
where
F: FnOnce(&mut Self) -> InterpResult<'tcx, T>,
@@ -471,45 +460,13 @@ fn use_ecx<F, T>(&mut self, f: F) -> Option<T>
}
/// Returns the value, if any, of evaluating `c`.
fn eval_constant(&mut self, c: &Constant<'tcx>, source_info: SourceInfo) -> Option<OpTy<'tcx>> {
fn eval_constant(&mut self, c: &Constant<'tcx>) -> Option<OpTy<'tcx>> {
// FIXME we need to revisit this for #67176
if c.needs_subst() {
return None;
}
match self.ecx.mir_const_to_op(&c.literal, None) {
Ok(op) => Some(op),
Err(error) => {
let tcx = self.ecx.tcx.at(c.span);
let err = ConstEvalErr::new(&self.ecx, error, Some(c.span));
if let Some(lint_root) = self.lint_root(source_info) {
let lint_only = match c.literal {
ConstantKind::Ty(ct) => match ct.val() {
// Promoteds must lint and not error as the user didn't ask for them
ConstKind::Unevaluated(ty::Unevaluated {
def: _,
substs: _,
promoted: Some(_),
}) => true,
// Out of backwards compatibility we cannot report hard errors in unused
// generic functions using associated constants of the generic parameters.
_ => c.literal.needs_subst(),
},
ConstantKind::Val(_, ty) => ty.needs_subst(),
};
if lint_only {
// Out of backwards compatibility we cannot report hard errors in unused
// generic functions using associated constants of the generic parameters.
err.report_as_lint(tcx, "erroneous constant used", lint_root, Some(c.span));
} else {
err.report_as_error(tcx, "erroneous constant used");
}
} else {
err.report_as_error(tcx, "erroneous constant used");
}
None
}
}
self.ecx.mir_const_to_op(&c.literal, None).ok()
}
/// Returns the value, if any, of evaluating `place`.
@@ -520,49 +477,22 @@ fn eval_place(&mut self, place: Place<'tcx>) -> Option<OpTy<'tcx>> {
/// Returns the value, if any, of evaluating `op`. Calls upon `eval_constant`
/// or `eval_place`, depending on the variant of `Operand` used.
fn eval_operand(&mut self, op: &Operand<'tcx>, source_info: SourceInfo) -> Option<OpTy<'tcx>> {
fn eval_operand(&mut self, op: &Operand<'tcx>) -> Option<OpTy<'tcx>> {
match *op {
Operand::Constant(ref c) => self.eval_constant(c, source_info),
Operand::Constant(ref c) => self.eval_constant(c),
Operand::Move(place) | Operand::Copy(place) => self.eval_place(place),
}
}
fn report_assert_as_lint(
&self,
lint: &'static lint::Lint,
source_info: SourceInfo,
message: &'static str,
panic: AssertKind<impl std::fmt::Debug>,
) {
if let Some(lint_root) = self.lint_root(source_info) {
self.tcx.struct_span_lint_hir(lint, lint_root, source_info.span, |lint| {
let mut err = lint.build(message);
err.span_label(source_info.span, format!("{:?}", panic));
err.emit();
});
}
}
fn check_unary_op(
&mut self,
op: UnOp,
arg: &Operand<'tcx>,
source_info: SourceInfo,
) -> Option<()> {
if let (val, true) = self.use_ecx(|this| {
fn check_unary_op(&mut self, op: UnOp, arg: &Operand<'tcx>) -> Option<()> {
if self.use_ecx(|this| {
let val = this.ecx.read_immediate(&this.ecx.eval_operand(arg, None)?)?;
let (_res, overflow, _ty) = this.ecx.overflowing_unary_op(op, &val)?;
Ok((val, overflow))
Ok(overflow)
})? {
// `AssertKind` only has an `OverflowNeg` variant, so make sure that is
// appropriate to use.
assert_eq!(op, UnOp::Neg, "Neg is the only UnOp that can overflow");
self.report_assert_as_lint(
lint::builtin::ARITHMETIC_OVERFLOW,
source_info,
"this arithmetic operation will overflow",
AssertKind::OverflowNeg(val.to_const_int()),
);
return None;
}
@@ -574,7 +504,6 @@ fn check_binary_op(
op: BinOp,
left: &Operand<'tcx>,
right: &Operand<'tcx>,
source_info: SourceInfo,
) -> Option<()> {
let r = self.use_ecx(|this| this.ecx.read_immediate(&this.ecx.eval_operand(right, None)?));
let l = self.use_ecx(|this| this.ecx.read_immediate(&this.ecx.eval_operand(left, None)?));
@@ -589,25 +518,6 @@ fn check_binary_op(
let r_bits = r.to_scalar().ok();
let r_bits = r_bits.and_then(|r| r.to_bits(right_size).ok());
if r_bits.map_or(false, |b| b >= left_size.bits() as u128) {
debug!("check_binary_op: reporting assert for {:?}", source_info);
self.report_assert_as_lint(
lint::builtin::ARITHMETIC_OVERFLOW,
source_info,
"this arithmetic operation will overflow",
AssertKind::Overflow(
op,
match l {
Some(l) => l.to_const_int(),
// Invent a dummy value, the diagnostic ignores it anyway
None => ConstInt::new(
ScalarInt::try_from_uint(1_u8, left_size).unwrap(),
left_ty.is_signed(),
left_ty.is_ptr_sized_integral(),
),
},
r.to_const_int(),
),
);
return None;
}
}
@@ -618,12 +528,6 @@ fn check_binary_op(
let (_res, overflow, _ty) = this.ecx.overflowing_binary_op(op, l, r)?;
Ok(overflow)
})? {
self.report_assert_as_lint(
lint::builtin::ARITHMETIC_OVERFLOW,
source_info,
"this arithmetic operation will overflow",
AssertKind::Overflow(op, l.to_const_int(), r.to_const_int()),
);
return None;
}
}
@@ -656,12 +560,7 @@ fn propagate_operand(&mut self, operand: &mut Operand<'tcx>) {
}
}
fn const_prop(
&mut self,
rvalue: &Rvalue<'tcx>,
source_info: SourceInfo,
place: Place<'tcx>,
) -> Option<()> {
fn const_prop(&mut self, rvalue: &Rvalue<'tcx>, place: Place<'tcx>) -> Option<()> {
// Perform any special handling for specific Rvalue types.
// Generally, checks here fall into one of two categories:
// 1. Additional checking to provide useful lints to the user
@@ -676,11 +575,11 @@ fn const_prop(
// lint.
Rvalue::UnaryOp(op, arg) => {
trace!("checking UnaryOp(op = {:?}, arg = {:?})", op, arg);
self.check_unary_op(*op, arg, source_info)?;
self.check_unary_op(*op, arg)?;
}
Rvalue::BinaryOp(op, box (left, right)) => {
trace!("checking BinaryOp(op = {:?}, left = {:?}, right = {:?})", op, left, right);
self.check_binary_op(*op, left, right, source_info)?;
self.check_binary_op(*op, left, right)?;
}
Rvalue::CheckedBinaryOp(op, box (left, right)) => {
trace!(
@@ -689,7 +588,7 @@ fn const_prop(
left,
right
);
self.check_binary_op(*op, left, right, source_info)?;
self.check_binary_op(*op, left, right)?;
}
// Do not try creating references (#67862)
@@ -1071,7 +970,7 @@ fn visit_operand(&mut self, operand: &mut Operand<'tcx>, location: Location) {
fn visit_constant(&mut self, constant: &mut Constant<'tcx>, location: Location) {
trace!("visit_constant: {:?}", constant);
self.super_constant(constant, location);
self.eval_constant(constant, self.source_info.unwrap());
self.eval_constant(constant);
}
fn visit_statement(&mut self, statement: &mut Statement<'tcx>, location: Location) {
@@ -1080,7 +979,7 @@ fn visit_statement(&mut self, statement: &mut Statement<'tcx>, location: Locatio
self.source_info = Some(source_info);
if let StatementKind::Assign(box (place, ref mut rval)) = statement.kind {
let can_const_prop = self.ecx.machine.can_const_prop[place.local];
if let Some(()) = self.const_prop(rval, source_info, place) {
if let Some(()) = self.const_prop(rval, place) {
// This will return None if the above `const_prop` invocation only "wrote" a
// type whose creation requires no write. E.g. a generator whose initial state
// consists solely of uninitialized memory (so it doesn't capture any locals).
@@ -1164,57 +1063,12 @@ fn visit_terminator(&mut self, terminator: &mut Terminator<'tcx>, location: Loca
self.source_info = Some(source_info);
self.super_terminator(terminator, location);
match &mut terminator.kind {
TerminatorKind::Assert { expected, ref msg, ref mut cond, .. } => {
if let Some(ref value) = self.eval_operand(&cond, source_info) {
TerminatorKind::Assert { expected, ref mut cond, .. } => {
if let Some(ref value) = self.eval_operand(&cond) {
trace!("assertion on {:?} should be {:?}", value, expected);
let expected = ScalarMaybeUninit::from(Scalar::from_bool(*expected));
let value_const = self.ecx.read_scalar(&value).unwrap();
if expected != value_const {
enum DbgVal<T> {
Val(T),
Underscore,
}
impl<T: std::fmt::Debug> std::fmt::Debug for DbgVal<T> {
fn fmt(&self, fmt: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Val(val) => val.fmt(fmt),
Self::Underscore => fmt.write_str("_"),
}
}
}
let mut eval_to_int = |op| {
// This can be `None` if the lhs wasn't const propagated and we just
// triggered the assert on the value of the rhs.
self.eval_operand(op, source_info).map_or(DbgVal::Underscore, |op| {
DbgVal::Val(self.ecx.read_immediate(&op).unwrap().to_const_int())
})
};
let msg = match msg {
AssertKind::DivisionByZero(op) => {
Some(AssertKind::DivisionByZero(eval_to_int(op)))
}
AssertKind::RemainderByZero(op) => {
Some(AssertKind::RemainderByZero(eval_to_int(op)))
}
AssertKind::Overflow(bin_op @ (BinOp::Div | BinOp::Rem), op1, op2) => {
// Division overflow is *UB* in the MIR, and different than the
// other overflow checks.
Some(AssertKind::Overflow(
*bin_op,
eval_to_int(op1),
eval_to_int(op2),
))
}
AssertKind::BoundsCheck { ref len, ref index } => {
let len = eval_to_int(len);
let index = eval_to_int(index);
Some(AssertKind::BoundsCheck { len, index })
}
// Remaining overflow errors are already covered by checks on the binary operators.
AssertKind::Overflow(..) | AssertKind::OverflowNeg(_) => None,
// Need proper const propagator for these.
_ => None,
};
// Poison all places this operand references so that further code
// doesn't use the invalid value
match cond {
@@ -1223,14 +1077,6 @@ fn fmt(&self, fmt: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
}
Operand::Constant(_) => {}
}
if let Some(msg) = msg {
self.report_assert_as_lint(
lint::builtin::UNCONDITIONAL_PANIC,
source_info,
"this operation will panic at runtime",
msg,
);
}
} else {
if self.should_const_prop(value) {
if let ScalarMaybeUninit::Scalar(scalar) = value_const {
@@ -0,0 +1,1035 @@
//! Propagates constants for early reporting of statically known
//! assertion failures
use std::cell::Cell;
use rustc_ast::Mutability;
use rustc_data_structures::fx::FxHashSet;
use rustc_hir::def::DefKind;
use rustc_hir::HirId;
use rustc_index::bit_set::BitSet;
use rustc_index::vec::IndexVec;
use rustc_middle::mir::visit::{MutatingUseContext, NonMutatingUseContext, PlaceContext, Visitor};
use rustc_middle::mir::{
AssertKind, BasicBlock, BinOp, Body, Constant, ConstantKind, Local, LocalDecl, LocalKind,
Location, Operand, Place, Rvalue, SourceInfo, SourceScope, SourceScopeData, Statement,
StatementKind, Terminator, TerminatorKind, UnOp, RETURN_PLACE,
};
use rustc_middle::ty::layout::{LayoutError, LayoutOf, LayoutOfHelpers, TyAndLayout};
use rustc_middle::ty::subst::{InternalSubsts, Subst};
use rustc_middle::ty::{
self, ConstInt, ConstKind, Instance, ParamEnv, ScalarInt, Ty, TyCtxt, TypeFoldable,
};
use rustc_session::lint;
use rustc_span::{def_id::DefId, Span};
use rustc_target::abi::{HasDataLayout, Size, TargetDataLayout};
use rustc_target::spec::abi::Abi;
use rustc_trait_selection::traits;
use crate::MirLint;
use rustc_const_eval::const_eval::ConstEvalErr;
use rustc_const_eval::interpret::{
self, compile_time_machine, AllocId, ConstAllocation, Frame, ImmTy, InterpCx, InterpResult,
LocalState, LocalValue, MemPlace, MemoryKind, OpTy, Operand as InterpOperand, PlaceTy, Scalar,
ScalarMaybeUninit, StackPopCleanup, StackPopUnwind,
};
/// The maximum number of bytes that we'll allocate space for a local or the return value.
/// Needed for #66397, because otherwise we eval into large places and that can cause OOM or just
/// Severely regress performance.
const MAX_ALLOC_LIMIT: u64 = 1024;
/// Macro for machine-specific `InterpError` without allocation.
/// (These will never be shown to the user, but they help diagnose ICEs.)
macro_rules! throw_machine_stop_str {
($($tt:tt)*) => {{
// We make a new local type for it. The type itself does not carry any information,
// but its vtable (for the `MachineStopType` trait) does.
struct Zst;
// Printing this type shows the desired string.
impl std::fmt::Display for Zst {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, $($tt)*)
}
}
impl rustc_middle::mir::interpret::MachineStopType for Zst {}
throw_machine_stop!(Zst)
}};
}
pub struct ConstProp;
impl<'tcx> MirLint<'tcx> for ConstProp {
fn run_lint(&self, tcx: TyCtxt<'tcx>, body: &Body<'tcx>) {
// will be evaluated by miri and produce its errors there
if body.source.promoted.is_some() {
return;
}
let def_id = body.source.def_id().expect_local();
let is_fn_like = tcx.hir().get_by_def_id(def_id).fn_kind().is_some();
let is_assoc_const = tcx.def_kind(def_id) == DefKind::AssocConst;
// Only run const prop on functions, methods, closures and associated constants
if !is_fn_like && !is_assoc_const {
// skip anon_const/statics/consts because they'll be evaluated by miri anyway
trace!("ConstProp skipped for {:?}", def_id);
return;
}
let is_generator = tcx.type_of(def_id.to_def_id()).is_generator();
// FIXME(welseywiser) const prop doesn't work on generators because of query cycles
// computing their layout.
if is_generator {
trace!("ConstProp skipped for generator {:?}", def_id);
return;
}
// Check if it's even possible to satisfy the 'where' clauses
// for this item.
// This branch will never be taken for any normal function.
// However, it's possible to `#!feature(trivial_bounds)]` to write
// a function with impossible to satisfy clauses, e.g.:
// `fn foo() where String: Copy {}`
//
// We don't usually need to worry about this kind of case,
// since we would get a compilation error if the user tried
// to call it. However, since we can do const propagation
// even without any calls to the function, we need to make
// sure that it even makes sense to try to evaluate the body.
// If there are unsatisfiable where clauses, then all bets are
// off, and we just give up.
//
// We manually filter the predicates, skipping anything that's not
// "global". We are in a potentially generic context
// (e.g. we are evaluating a function without substituting generic
// parameters, so this filtering serves two purposes:
//
// 1. We skip evaluating any predicates that we would
// never be able prove are unsatisfiable (e.g. `<T as Foo>`
// 2. We avoid trying to normalize predicates involving generic
// parameters (e.g. `<T as Foo>::MyItem`). This can confuse
// the normalization code (leading to cycle errors), since
// it's usually never invoked in this way.
let predicates = tcx
.predicates_of(def_id.to_def_id())
.predicates
.iter()
.filter_map(|(p, _)| if p.is_global() { Some(*p) } else { None });
if traits::impossible_predicates(
tcx,
traits::elaborate_predicates(tcx, predicates).map(|o| o.predicate).collect(),
) {
trace!("ConstProp skipped for {:?}: found unsatisfiable predicates", def_id);
return;
}
trace!("ConstProp starting for {:?}", def_id);
let dummy_body = &Body::new(
body.source,
body.basic_blocks().clone(),
body.source_scopes.clone(),
body.local_decls.clone(),
Default::default(),
body.arg_count,
Default::default(),
body.span,
body.generator_kind(),
body.tainted_by_errors,
);
// FIXME(oli-obk, eddyb) Optimize locals (or even local paths) to hold
// constants, instead of just checking for const-folding succeeding.
// That would require a uniform one-def no-mutation analysis
// and RPO (or recursing when needing the value of a local).
let mut optimization_finder = ConstPropagator::new(body, dummy_body, tcx);
optimization_finder.visit_body(body);
trace!("ConstProp done for {:?}", def_id);
}
}
struct ConstPropMachine<'mir, 'tcx> {
/// The virtual call stack.
stack: Vec<Frame<'mir, 'tcx>>,
/// `OnlyInsideOwnBlock` locals that were written in the current block get erased at the end.
written_only_inside_own_block_locals: FxHashSet<Local>,
/// Locals that need to be cleared after every block terminates.
only_propagate_inside_block_locals: BitSet<Local>,
can_const_prop: IndexVec<Local, ConstPropMode>,
}
impl ConstPropMachine<'_, '_> {
fn new(
only_propagate_inside_block_locals: BitSet<Local>,
can_const_prop: IndexVec<Local, ConstPropMode>,
) -> Self {
Self {
stack: Vec::new(),
written_only_inside_own_block_locals: Default::default(),
only_propagate_inside_block_locals,
can_const_prop,
}
}
}
impl<'mir, 'tcx> interpret::Machine<'mir, 'tcx> for ConstPropMachine<'mir, 'tcx> {
compile_time_machine!(<'mir, 'tcx>);
const PANIC_ON_ALLOC_FAIL: bool = true; // all allocations are small (see `MAX_ALLOC_LIMIT`)
type MemoryKind = !;
type MemoryExtra = ();
fn load_mir(
_ecx: &InterpCx<'mir, 'tcx, Self>,
_instance: ty::InstanceDef<'tcx>,
) -> InterpResult<'tcx, &'tcx Body<'tcx>> {
throw_machine_stop_str!("calling functions isn't supported in ConstProp")
}
fn find_mir_or_eval_fn(
_ecx: &mut InterpCx<'mir, 'tcx, Self>,
_instance: ty::Instance<'tcx>,
_abi: Abi,
_args: &[OpTy<'tcx>],
_ret: Option<(&PlaceTy<'tcx>, BasicBlock)>,
_unwind: StackPopUnwind,
) -> InterpResult<'tcx, Option<(&'mir Body<'tcx>, ty::Instance<'tcx>)>> {
Ok(None)
}
fn call_intrinsic(
_ecx: &mut InterpCx<'mir, 'tcx, Self>,
_instance: ty::Instance<'tcx>,
_args: &[OpTy<'tcx>],
_ret: Option<(&PlaceTy<'tcx>, BasicBlock)>,
_unwind: StackPopUnwind,
) -> InterpResult<'tcx> {
throw_machine_stop_str!("calling intrinsics isn't supported in ConstProp")
}
fn assert_panic(
_ecx: &mut InterpCx<'mir, 'tcx, Self>,
_msg: &rustc_middle::mir::AssertMessage<'tcx>,
_unwind: Option<rustc_middle::mir::BasicBlock>,
) -> InterpResult<'tcx> {
bug!("panics terminators are not evaluated in ConstProp")
}
fn binary_ptr_op(
_ecx: &InterpCx<'mir, 'tcx, Self>,
_bin_op: BinOp,
_left: &ImmTy<'tcx>,
_right: &ImmTy<'tcx>,
) -> InterpResult<'tcx, (Scalar, bool, Ty<'tcx>)> {
// We can't do this because aliasing of memory can differ between const eval and llvm
throw_machine_stop_str!("pointer arithmetic or comparisons aren't supported in ConstProp")
}
fn access_local(
_ecx: &InterpCx<'mir, 'tcx, Self>,
frame: &Frame<'mir, 'tcx, Self::PointerTag, Self::FrameExtra>,
local: Local,
) -> InterpResult<'tcx, InterpOperand<Self::PointerTag>> {
let l = &frame.locals[local];
if l.value == LocalValue::Unallocated {
throw_machine_stop_str!("tried to access an uninitialized local")
}
l.access()
}
fn access_local_mut<'a>(
ecx: &'a mut InterpCx<'mir, 'tcx, Self>,
frame: usize,
local: Local,
) -> InterpResult<'tcx, Result<&'a mut LocalValue<Self::PointerTag>, MemPlace<Self::PointerTag>>>
{
if ecx.machine.can_const_prop[local] == ConstPropMode::NoPropagation {
throw_machine_stop_str!("tried to write to a local that is marked as not propagatable")
}
if frame == 0 && ecx.machine.only_propagate_inside_block_locals.contains(local) {
trace!(
"mutating local {:?} which is restricted to its block. \
Will remove it from const-prop after block is finished.",
local
);
ecx.machine.written_only_inside_own_block_locals.insert(local);
}
ecx.machine.stack[frame].locals[local].access_mut()
}
fn before_access_global(
_memory_extra: &(),
_alloc_id: AllocId,
alloc: ConstAllocation<'tcx, Self::PointerTag, Self::AllocExtra>,
_static_def_id: Option<DefId>,
is_write: bool,
) -> InterpResult<'tcx> {
if is_write {
throw_machine_stop_str!("can't write to global");
}
// If the static allocation is mutable, then we can't const prop it as its content
// might be different at runtime.
if alloc.inner().mutability == Mutability::Mut {
throw_machine_stop_str!("can't access mutable globals in ConstProp");
}
Ok(())
}
#[inline(always)]
fn init_frame_extra(
_ecx: &mut InterpCx<'mir, 'tcx, Self>,
frame: Frame<'mir, 'tcx>,
) -> InterpResult<'tcx, Frame<'mir, 'tcx>> {
Ok(frame)
}
#[inline(always)]
fn stack<'a>(
ecx: &'a InterpCx<'mir, 'tcx, Self>,
) -> &'a [Frame<'mir, 'tcx, Self::PointerTag, Self::FrameExtra>] {
&ecx.machine.stack
}
#[inline(always)]
fn stack_mut<'a>(
ecx: &'a mut InterpCx<'mir, 'tcx, Self>,
) -> &'a mut Vec<Frame<'mir, 'tcx, Self::PointerTag, Self::FrameExtra>> {
&mut ecx.machine.stack
}
}
/// Finds optimization opportunities on the MIR.
struct ConstPropagator<'mir, 'tcx> {
ecx: InterpCx<'mir, 'tcx, ConstPropMachine<'mir, 'tcx>>,
tcx: TyCtxt<'tcx>,
param_env: ParamEnv<'tcx>,
// FIXME(eddyb) avoid cloning these two fields more than once,
// by accessing them through `ecx` instead.
source_scopes: IndexVec<SourceScope, SourceScopeData<'tcx>>,
local_decls: IndexVec<Local, LocalDecl<'tcx>>,
// Because we have `MutVisitor` we can't obtain the `SourceInfo` from a `Location`. So we store
// the last known `SourceInfo` here and just keep revisiting it.
source_info: Option<SourceInfo>,
}
impl<'tcx> LayoutOfHelpers<'tcx> for ConstPropagator<'_, 'tcx> {
type LayoutOfResult = Result<TyAndLayout<'tcx>, LayoutError<'tcx>>;
#[inline]
fn handle_layout_err(&self, err: LayoutError<'tcx>, _: Span, _: Ty<'tcx>) -> LayoutError<'tcx> {
err
}
}
impl HasDataLayout for ConstPropagator<'_, '_> {
#[inline]
fn data_layout(&self) -> &TargetDataLayout {
&self.tcx.data_layout
}
}
impl<'tcx> ty::layout::HasTyCtxt<'tcx> for ConstPropagator<'_, 'tcx> {
#[inline]
fn tcx(&self) -> TyCtxt<'tcx> {
self.tcx
}
}
impl<'tcx> ty::layout::HasParamEnv<'tcx> for ConstPropagator<'_, 'tcx> {
#[inline]
fn param_env(&self) -> ty::ParamEnv<'tcx> {
self.param_env
}
}
impl<'mir, 'tcx> ConstPropagator<'mir, 'tcx> {
fn new(
body: &Body<'tcx>,
dummy_body: &'mir Body<'tcx>,
tcx: TyCtxt<'tcx>,
) -> ConstPropagator<'mir, 'tcx> {
let def_id = body.source.def_id();
let substs = &InternalSubsts::identity_for_item(tcx, def_id);
let param_env = tcx.param_env_reveal_all_normalized(def_id);
let span = tcx.def_span(def_id);
// FIXME: `CanConstProp::check` computes the layout of all locals, return those layouts
// so we can write them to `ecx.frame_mut().locals.layout, reducing the duplication in
// `layout_of` query invocations.
let can_const_prop = CanConstProp::check(tcx, param_env, body);
let mut only_propagate_inside_block_locals = BitSet::new_empty(can_const_prop.len());
for (l, mode) in can_const_prop.iter_enumerated() {
if *mode == ConstPropMode::OnlyInsideOwnBlock {
only_propagate_inside_block_locals.insert(l);
}
}
let mut ecx = InterpCx::new(
tcx,
span,
param_env,
ConstPropMachine::new(only_propagate_inside_block_locals, can_const_prop),
(),
);
let ret = ecx
.layout_of(body.return_ty().subst(tcx, substs))
.ok()
// Don't bother allocating memory for ZST types which have no values
// or for large values.
.filter(|ret_layout| {
!ret_layout.is_zst() && ret_layout.size < Size::from_bytes(MAX_ALLOC_LIMIT)
})
.map(|ret_layout| {
ecx.allocate(ret_layout, MemoryKind::Stack)
.expect("couldn't perform small allocation")
.into()
});
ecx.push_stack_frame(
Instance::new(def_id, substs),
dummy_body,
ret.as_ref(),
StackPopCleanup::Root { cleanup: false },
)
.expect("failed to push initial stack frame");
ConstPropagator {
ecx,
tcx,
param_env,
// FIXME(eddyb) avoid cloning these two fields more than once,
// by accessing them through `ecx` instead.
source_scopes: body.source_scopes.clone(),
//FIXME(wesleywiser) we can't steal this because `Visitor::super_visit_body()` needs it
local_decls: body.local_decls.clone(),
source_info: None,
}
}
fn get_const(&self, place: Place<'tcx>) -> Option<OpTy<'tcx>> {
let op = match self.ecx.eval_place_to_op(place, None) {
Ok(op) => op,
Err(e) => {
trace!("get_const failed: {}", e);
return None;
}
};
// Try to read the local as an immediate so that if it is representable as a scalar, we can
// handle it as such, but otherwise, just return the value as is.
Some(match self.ecx.try_read_immediate(&op) {
Ok(Ok(imm)) => imm.into(),
_ => op,
})
}
/// Remove `local` from the pool of `Locals`. Allows writing to them,
/// but not reading from them anymore.
fn remove_const(ecx: &mut InterpCx<'mir, 'tcx, ConstPropMachine<'mir, 'tcx>>, local: Local) {
ecx.frame_mut().locals[local] =
LocalState { value: LocalValue::Unallocated, layout: Cell::new(None) };
}
fn lint_root(&self, source_info: SourceInfo) -> Option<HirId> {
source_info.scope.lint_root(&self.source_scopes)
}
fn use_ecx<F, T>(&mut self, f: F) -> Option<T>
where
F: FnOnce(&mut Self) -> InterpResult<'tcx, T>,
{
match f(self) {
Ok(val) => Some(val),
Err(error) => {
trace!("InterpCx operation failed: {:?}", error);
// Some errors shouldn't come up because creating them causes
// an allocation, which we should avoid. When that happens,
// dedicated error variants should be introduced instead.
assert!(
!error.kind().formatted_string(),
"const-prop encountered formatting error: {}",
error
);
None
}
}
}
/// Returns the value, if any, of evaluating `c`.
fn eval_constant(&mut self, c: &Constant<'tcx>, source_info: SourceInfo) -> Option<OpTy<'tcx>> {
// FIXME we need to revisit this for #67176
if c.needs_subst() {
return None;
}
match self.ecx.mir_const_to_op(&c.literal, None) {
Ok(op) => Some(op),
Err(error) => {
let tcx = self.ecx.tcx.at(c.span);
let err = ConstEvalErr::new(&self.ecx, error, Some(c.span));
if let Some(lint_root) = self.lint_root(source_info) {
let lint_only = match c.literal {
ConstantKind::Ty(ct) => match ct.val() {
// Promoteds must lint and not error as the user didn't ask for them
ConstKind::Unevaluated(ty::Unevaluated {
def: _,
substs: _,
promoted: Some(_),
}) => true,
// Out of backwards compatibility we cannot report hard errors in unused
// generic functions using associated constants of the generic parameters.
_ => c.literal.needs_subst(),
},
ConstantKind::Val(_, ty) => ty.needs_subst(),
};
if lint_only {
// Out of backwards compatibility we cannot report hard errors in unused
// generic functions using associated constants of the generic parameters.
err.report_as_lint(tcx, "erroneous constant used", lint_root, Some(c.span));
} else {
err.report_as_error(tcx, "erroneous constant used");
}
} else {
err.report_as_error(tcx, "erroneous constant used");
}
None
}
}
}
/// Returns the value, if any, of evaluating `place`.
fn eval_place(&mut self, place: Place<'tcx>) -> Option<OpTy<'tcx>> {
trace!("eval_place(place={:?})", place);
self.use_ecx(|this| this.ecx.eval_place_to_op(place, None))
}
/// Returns the value, if any, of evaluating `op`. Calls upon `eval_constant`
/// or `eval_place`, depending on the variant of `Operand` used.
fn eval_operand(&mut self, op: &Operand<'tcx>, source_info: SourceInfo) -> Option<OpTy<'tcx>> {
match *op {
Operand::Constant(ref c) => self.eval_constant(c, source_info),
Operand::Move(place) | Operand::Copy(place) => self.eval_place(place),
}
}
fn report_assert_as_lint(
&self,
lint: &'static lint::Lint,
source_info: SourceInfo,
message: &'static str,
panic: AssertKind<impl std::fmt::Debug>,
) {
if let Some(lint_root) = self.lint_root(source_info) {
self.tcx.struct_span_lint_hir(lint, lint_root, source_info.span, |lint| {
let mut err = lint.build(message);
err.span_label(source_info.span, format!("{:?}", panic));
err.emit();
});
}
}
fn check_unary_op(
&mut self,
op: UnOp,
arg: &Operand<'tcx>,
source_info: SourceInfo,
) -> Option<()> {
if let (val, true) = self.use_ecx(|this| {
let val = this.ecx.read_immediate(&this.ecx.eval_operand(arg, None)?)?;
let (_res, overflow, _ty) = this.ecx.overflowing_unary_op(op, &val)?;
Ok((val, overflow))
})? {
// `AssertKind` only has an `OverflowNeg` variant, so make sure that is
// appropriate to use.
assert_eq!(op, UnOp::Neg, "Neg is the only UnOp that can overflow");
self.report_assert_as_lint(
lint::builtin::ARITHMETIC_OVERFLOW,
source_info,
"this arithmetic operation will overflow",
AssertKind::OverflowNeg(val.to_const_int()),
);
return None;
}
Some(())
}
fn check_binary_op(
&mut self,
op: BinOp,
left: &Operand<'tcx>,
right: &Operand<'tcx>,
source_info: SourceInfo,
) -> Option<()> {
let r = self.use_ecx(|this| this.ecx.read_immediate(&this.ecx.eval_operand(right, None)?));
let l = self.use_ecx(|this| this.ecx.read_immediate(&this.ecx.eval_operand(left, None)?));
// Check for exceeding shifts *even if* we cannot evaluate the LHS.
if op == BinOp::Shr || op == BinOp::Shl {
let r = r?;
// We need the type of the LHS. We cannot use `place_layout` as that is the type
// of the result, which for checked binops is not the same!
let left_ty = left.ty(&self.local_decls, self.tcx);
let left_size = self.ecx.layout_of(left_ty).ok()?.size;
let right_size = r.layout.size;
let r_bits = r.to_scalar().ok();
let r_bits = r_bits.and_then(|r| r.to_bits(right_size).ok());
if r_bits.map_or(false, |b| b >= left_size.bits() as u128) {
debug!("check_binary_op: reporting assert for {:?}", source_info);
self.report_assert_as_lint(
lint::builtin::ARITHMETIC_OVERFLOW,
source_info,
"this arithmetic operation will overflow",
AssertKind::Overflow(
op,
match l {
Some(l) => l.to_const_int(),
// Invent a dummy value, the diagnostic ignores it anyway
None => ConstInt::new(
ScalarInt::try_from_uint(1_u8, left_size).unwrap(),
left_ty.is_signed(),
left_ty.is_ptr_sized_integral(),
),
},
r.to_const_int(),
),
);
return None;
}
}
if let (Some(l), Some(r)) = (&l, &r) {
// The remaining operators are handled through `overflowing_binary_op`.
if self.use_ecx(|this| {
let (_res, overflow, _ty) = this.ecx.overflowing_binary_op(op, l, r)?;
Ok(overflow)
})? {
self.report_assert_as_lint(
lint::builtin::ARITHMETIC_OVERFLOW,
source_info,
"this arithmetic operation will overflow",
AssertKind::Overflow(op, l.to_const_int(), r.to_const_int()),
);
return None;
}
}
Some(())
}
fn const_prop(
&mut self,
rvalue: &Rvalue<'tcx>,
source_info: SourceInfo,
place: Place<'tcx>,
) -> Option<()> {
// Perform any special handling for specific Rvalue types.
// Generally, checks here fall into one of two categories:
// 1. Additional checking to provide useful lints to the user
// - In this case, we will do some validation and then fall through to the
// end of the function which evals the assignment.
// 2. Working around bugs in other parts of the compiler
// - In this case, we'll return `None` from this function to stop evaluation.
match rvalue {
// Additional checking: give lints to the user if an overflow would occur.
// We do this here and not in the `Assert` terminator as that terminator is
// only sometimes emitted (overflow checks can be disabled), but we want to always
// lint.
Rvalue::UnaryOp(op, arg) => {
trace!("checking UnaryOp(op = {:?}, arg = {:?})", op, arg);
self.check_unary_op(*op, arg, source_info)?;
}
Rvalue::BinaryOp(op, box (left, right)) => {
trace!("checking BinaryOp(op = {:?}, left = {:?}, right = {:?})", op, left, right);
self.check_binary_op(*op, left, right, source_info)?;
}
Rvalue::CheckedBinaryOp(op, box (left, right)) => {
trace!(
"checking CheckedBinaryOp(op = {:?}, left = {:?}, right = {:?})",
op,
left,
right
);
self.check_binary_op(*op, left, right, source_info)?;
}
// Do not try creating references (#67862)
Rvalue::AddressOf(_, place) | Rvalue::Ref(_, _, place) => {
trace!("skipping AddressOf | Ref for {:?}", place);
// This may be creating mutable references or immutable references to cells.
// If that happens, the pointed to value could be mutated via that reference.
// Since we aren't tracking references, the const propagator loses track of what
// value the local has right now.
// Thus, all locals that have their reference taken
// must not take part in propagation.
Self::remove_const(&mut self.ecx, place.local);
return None;
}
Rvalue::ThreadLocalRef(def_id) => {
trace!("skipping ThreadLocalRef({:?})", def_id);
return None;
}
// There's no other checking to do at this time.
Rvalue::Aggregate(..)
| Rvalue::Use(..)
| Rvalue::Repeat(..)
| Rvalue::Len(..)
| Rvalue::Cast(..)
| Rvalue::ShallowInitBox(..)
| Rvalue::Discriminant(..)
| Rvalue::NullaryOp(..) => {}
}
// FIXME we need to revisit this for #67176
if rvalue.needs_subst() {
return None;
}
self.use_ecx(|this| this.ecx.eval_rvalue_into_place(rvalue, place))
}
}
/// The mode that `ConstProp` is allowed to run in for a given `Local`.
#[derive(Clone, Copy, Debug, PartialEq)]
enum ConstPropMode {
/// The `Local` can be propagated into and reads of this `Local` can also be propagated.
FullConstProp,
/// The `Local` can only be propagated into and from its own block.
OnlyInsideOwnBlock,
/// The `Local` can be propagated into but reads cannot be propagated.
OnlyPropagateInto,
/// The `Local` cannot be part of propagation at all. Any statement
/// referencing it either for reading or writing will not get propagated.
NoPropagation,
}
struct CanConstProp {
can_const_prop: IndexVec<Local, ConstPropMode>,
// False at the beginning. Once set, no more assignments are allowed to that local.
found_assignment: BitSet<Local>,
// Cache of locals' information
local_kinds: IndexVec<Local, LocalKind>,
}
impl CanConstProp {
/// Returns true if `local` can be propagated
fn check<'tcx>(
tcx: TyCtxt<'tcx>,
param_env: ParamEnv<'tcx>,
body: &Body<'tcx>,
) -> IndexVec<Local, ConstPropMode> {
let mut cpv = CanConstProp {
can_const_prop: IndexVec::from_elem(ConstPropMode::FullConstProp, &body.local_decls),
found_assignment: BitSet::new_empty(body.local_decls.len()),
local_kinds: IndexVec::from_fn_n(
|local| body.local_kind(local),
body.local_decls.len(),
),
};
for (local, val) in cpv.can_const_prop.iter_enumerated_mut() {
let ty = body.local_decls[local].ty;
match tcx.layout_of(param_env.and(ty)) {
Ok(layout) if layout.size < Size::from_bytes(MAX_ALLOC_LIMIT) => {}
// Either the layout fails to compute, then we can't use this local anyway
// or the local is too large, then we don't want to.
_ => {
*val = ConstPropMode::NoPropagation;
continue;
}
}
// Cannot use args at all
// Cannot use locals because if x < y { y - x } else { x - y } would
// lint for x != y
// FIXME(oli-obk): lint variables until they are used in a condition
// FIXME(oli-obk): lint if return value is constant
if cpv.local_kinds[local] == LocalKind::Arg {
*val = ConstPropMode::OnlyPropagateInto;
trace!(
"local {:?} can't be const propagated because it's a function argument",
local
);
} else if cpv.local_kinds[local] == LocalKind::Var {
*val = ConstPropMode::OnlyInsideOwnBlock;
trace!(
"local {:?} will only be propagated inside its block, because it's a user variable",
local
);
}
}
cpv.visit_body(&body);
cpv.can_const_prop
}
}
impl Visitor<'_> for CanConstProp {
fn visit_local(&mut self, &local: &Local, context: PlaceContext, _: Location) {
use rustc_middle::mir::visit::PlaceContext::*;
match context {
// Projections are fine, because `&mut foo.x` will be caught by
// `MutatingUseContext::Borrow` elsewhere.
MutatingUse(MutatingUseContext::Projection)
// These are just stores, where the storing is not propagatable, but there may be later
// mutations of the same local via `Store`
| MutatingUse(MutatingUseContext::Call)
| MutatingUse(MutatingUseContext::AsmOutput)
// Actual store that can possibly even propagate a value
| MutatingUse(MutatingUseContext::Store) => {
if !self.found_assignment.insert(local) {
match &mut self.can_const_prop[local] {
// If the local can only get propagated in its own block, then we don't have
// to worry about multiple assignments, as we'll nuke the const state at the
// end of the block anyway, and inside the block we overwrite previous
// states as applicable.
ConstPropMode::OnlyInsideOwnBlock => {}
ConstPropMode::NoPropagation => {}
ConstPropMode::OnlyPropagateInto => {}
other @ ConstPropMode::FullConstProp => {
trace!(
"local {:?} can't be propagated because of multiple assignments. Previous state: {:?}",
local, other,
);
*other = ConstPropMode::OnlyInsideOwnBlock;
}
}
}
}
// Reading constants is allowed an arbitrary number of times
NonMutatingUse(NonMutatingUseContext::Copy)
| NonMutatingUse(NonMutatingUseContext::Move)
| NonMutatingUse(NonMutatingUseContext::Inspect)
| NonMutatingUse(NonMutatingUseContext::Projection)
| NonUse(_) => {}
// These could be propagated with a smarter analysis or just some careful thinking about
// whether they'd be fine right now.
MutatingUse(MutatingUseContext::Yield)
| MutatingUse(MutatingUseContext::Drop)
| MutatingUse(MutatingUseContext::Retag)
// These can't ever be propagated under any scheme, as we can't reason about indirect
// mutation.
| NonMutatingUse(NonMutatingUseContext::SharedBorrow)
| NonMutatingUse(NonMutatingUseContext::ShallowBorrow)
| NonMutatingUse(NonMutatingUseContext::UniqueBorrow)
| NonMutatingUse(NonMutatingUseContext::AddressOf)
| MutatingUse(MutatingUseContext::Borrow)
| MutatingUse(MutatingUseContext::AddressOf) => {
trace!("local {:?} can't be propagaged because it's used: {:?}", local, context);
self.can_const_prop[local] = ConstPropMode::NoPropagation;
}
}
}
}
impl<'tcx> Visitor<'tcx> for ConstPropagator<'_, 'tcx> {
fn visit_body(&mut self, body: &Body<'tcx>) {
for (bb, data) in body.basic_blocks().iter_enumerated() {
self.visit_basic_block_data(bb, data);
}
}
fn visit_operand(&mut self, operand: &Operand<'tcx>, location: Location) {
self.super_operand(operand, location);
}
fn visit_constant(&mut self, constant: &Constant<'tcx>, location: Location) {
trace!("visit_constant: {:?}", constant);
self.super_constant(constant, location);
self.eval_constant(constant, self.source_info.unwrap());
}
fn visit_statement(&mut self, statement: &Statement<'tcx>, location: Location) {
trace!("visit_statement: {:?}", statement);
let source_info = statement.source_info;
self.source_info = Some(source_info);
if let StatementKind::Assign(box (place, ref rval)) = statement.kind {
let can_const_prop = self.ecx.machine.can_const_prop[place.local];
if let Some(()) = self.const_prop(rval, source_info, place) {
match can_const_prop {
ConstPropMode::OnlyInsideOwnBlock => {
trace!(
"found local restricted to its block. \
Will remove it from const-prop after block is finished. Local: {:?}",
place.local
);
}
ConstPropMode::OnlyPropagateInto | ConstPropMode::NoPropagation => {
trace!("can't propagate into {:?}", place);
if place.local != RETURN_PLACE {
Self::remove_const(&mut self.ecx, place.local);
}
}
ConstPropMode::FullConstProp => {}
}
} else {
// Const prop failed, so erase the destination, ensuring that whatever happens
// from here on, does not know about the previous value.
// This is important in case we have
// ```rust
// let mut x = 42;
// x = SOME_MUTABLE_STATIC;
// // x must now be uninit
// ```
// FIXME: we overzealously erase the entire local, because that's easier to
// implement.
trace!(
"propagation into {:?} failed.
Nuking the entire site from orbit, it's the only way to be sure",
place,
);
Self::remove_const(&mut self.ecx, place.local);
}
} else {
match statement.kind {
StatementKind::SetDiscriminant { ref place, .. } => {
match self.ecx.machine.can_const_prop[place.local] {
ConstPropMode::FullConstProp | ConstPropMode::OnlyInsideOwnBlock => {
if self.use_ecx(|this| this.ecx.statement(statement)).is_some() {
trace!("propped discriminant into {:?}", place);
} else {
Self::remove_const(&mut self.ecx, place.local);
}
}
ConstPropMode::OnlyPropagateInto | ConstPropMode::NoPropagation => {
Self::remove_const(&mut self.ecx, place.local);
}
}
}
StatementKind::StorageLive(local) | StatementKind::StorageDead(local) => {
let frame = self.ecx.frame_mut();
frame.locals[local].value =
if let StatementKind::StorageLive(_) = statement.kind {
LocalValue::Unallocated
} else {
LocalValue::Dead
};
}
_ => {}
}
}
self.super_statement(statement, location);
}
fn visit_terminator(&mut self, terminator: &Terminator<'tcx>, location: Location) {
let source_info = terminator.source_info;
self.source_info = Some(source_info);
self.super_terminator(terminator, location);
match &terminator.kind {
TerminatorKind::Assert { expected, ref msg, ref cond, .. } => {
if let Some(ref value) = self.eval_operand(&cond, source_info) {
trace!("assertion on {:?} should be {:?}", value, expected);
let expected = ScalarMaybeUninit::from(Scalar::from_bool(*expected));
let value_const = self.ecx.read_scalar(&value).unwrap();
if expected != value_const {
enum DbgVal<T> {
Val(T),
Underscore,
}
impl<T: std::fmt::Debug> std::fmt::Debug for DbgVal<T> {
fn fmt(&self, fmt: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Val(val) => val.fmt(fmt),
Self::Underscore => fmt.write_str("_"),
}
}
}
let mut eval_to_int = |op| {
// This can be `None` if the lhs wasn't const propagated and we just
// triggered the assert on the value of the rhs.
self.eval_operand(op, source_info).map_or(DbgVal::Underscore, |op| {
DbgVal::Val(self.ecx.read_immediate(&op).unwrap().to_const_int())
})
};
let msg = match msg {
AssertKind::DivisionByZero(op) => {
Some(AssertKind::DivisionByZero(eval_to_int(op)))
}
AssertKind::RemainderByZero(op) => {
Some(AssertKind::RemainderByZero(eval_to_int(op)))
}
AssertKind::Overflow(bin_op @ (BinOp::Div | BinOp::Rem), op1, op2) => {
// Division overflow is *UB* in the MIR, and different than the
// other overflow checks.
Some(AssertKind::Overflow(
*bin_op,
eval_to_int(op1),
eval_to_int(op2),
))
}
AssertKind::BoundsCheck { ref len, ref index } => {
let len = eval_to_int(len);
let index = eval_to_int(index);
Some(AssertKind::BoundsCheck { len, index })
}
// Remaining overflow errors are already covered by checks on the binary operators.
AssertKind::Overflow(..) | AssertKind::OverflowNeg(_) => None,
// Need proper const propagator for these.
_ => None,
};
// Poison all places this operand references so that further code
// doesn't use the invalid value
match cond {
Operand::Move(ref place) | Operand::Copy(ref place) => {
Self::remove_const(&mut self.ecx, place.local);
}
Operand::Constant(_) => {}
}
if let Some(msg) = msg {
self.report_assert_as_lint(
lint::builtin::UNCONDITIONAL_PANIC,
source_info,
"this operation will panic at runtime",
msg,
);
}
}
}
}
// None of these have Operands to const-propagate.
TerminatorKind::Goto { .. }
| TerminatorKind::Resume
| TerminatorKind::Abort
| TerminatorKind::Return
| TerminatorKind::Unreachable
| TerminatorKind::Drop { .. }
| TerminatorKind::DropAndReplace { .. }
| TerminatorKind::Yield { .. }
| TerminatorKind::GeneratorDrop
| TerminatorKind::FalseEdge { .. }
| TerminatorKind::FalseUnwind { .. }
| TerminatorKind::SwitchInt { .. }
| TerminatorKind::Call { .. }
| TerminatorKind::InlineAsm { .. } => {}
}
// We remove all Locals which are restricted in propagation to their containing blocks and
// which were modified in the current block.
// Take it out of the ecx so we can get a mutable reference to the ecx for `remove_const`.
let mut locals = std::mem::take(&mut self.ecx.machine.written_only_inside_own_block_locals);
for &local in locals.iter() {
Self::remove_const(&mut self.ecx, local);
}
locals.clear();
// Put it back so we reuse the heap of the storage
self.ecx.machine.written_only_inside_own_block_locals = locals;
if cfg!(debug_assertions) {
// Ensure we are correctly erasing locals with the non-debug-assert logic.
for local in self.ecx.machine.only_propagate_inside_block_locals.iter() {
assert!(
self.get_const(local.into()).is_none()
|| self
.layout_of(self.local_decls[local].ty)
.map_or(true, |layout| layout.is_zst())
)
}
}
}
}
+2
View File
@@ -49,6 +49,7 @@
mod const_debuginfo;
mod const_goto;
mod const_prop;
mod const_prop_lint;
mod coverage;
mod deaggregator;
mod deduplicate_blocks;
@@ -430,6 +431,7 @@ fn run_post_borrowck_cleanup_passes<'tcx>(tcx: TyCtxt<'tcx>, body: &mut Body<'tc
// `Deaggregator` is conceptually part of MIR building, some backends rely on it happening
// and it can help optimizations.
&deaggregator::Deaggregator,
&Lint(const_prop_lint::ConstProp),
];
pm::run_passes(tcx, body, post_borrowck_cleanup);
+1 -1
View File
@@ -10,7 +10,7 @@
// CHECK: @STATIC = {{.*}}, align 4
// This checks the constants from inline_enum_const
// CHECK: @alloc9 = {{.*}}, align 2
// CHECK: @alloc12 = {{.*}}, align 2
// This checks the constants from {low,high}_align_const, they share the same
// constant, but the alignment differs, so the higher one should be used
@@ -4,12 +4,12 @@ trait Foo {
const BAR: u32;
}
const IMPL_REF_BAR: u32 = GlobalImplRef::BAR; //~ ERROR E0391
const IMPL_REF_BAR: u32 = GlobalImplRef::BAR;
struct GlobalImplRef;
impl GlobalImplRef {
const BAR: u32 = IMPL_REF_BAR;
const BAR: u32 = IMPL_REF_BAR; //~ ERROR E0391
}
fn main() {}
@@ -1,9 +1,15 @@
error[E0391]: cycle detected when simplifying constant for the type system `IMPL_REF_BAR`
error[E0391]: cycle detected when elaborating drops for `<impl at $DIR/issue-24949-assoc-const-static-recursion-impl.rs:11:1: 13:2>::BAR`
--> $DIR/issue-24949-assoc-const-static-recursion-impl.rs:12:5
|
LL | const BAR: u32 = IMPL_REF_BAR;
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
= note: ...which requires normalizing `IMPL_REF_BAR`...
note: ...which requires simplifying constant for the type system `IMPL_REF_BAR`...
--> $DIR/issue-24949-assoc-const-static-recursion-impl.rs:7:1
|
LL | const IMPL_REF_BAR: u32 = GlobalImplRef::BAR;
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
note: ...which requires simplifying constant for the type system `IMPL_REF_BAR`...
--> $DIR/issue-24949-assoc-const-static-recursion-impl.rs:7:1
|
@@ -35,8 +41,7 @@ note: ...which requires caching mir of `<impl at $DIR/issue-24949-assoc-const-st
|
LL | const BAR: u32 = IMPL_REF_BAR;
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
= note: ...which requires normalizing `IMPL_REF_BAR`...
= note: ...which again requires simplifying constant for the type system `IMPL_REF_BAR`, completing the cycle
= note: ...which again requires elaborating drops for `<impl at $DIR/issue-24949-assoc-const-static-recursion-impl.rs:11:1: 13:2>::BAR`, completing the cycle
= note: cycle used when running analysis passes on this crate
error: aborting due to previous error
@@ -5,10 +5,10 @@ trait Foo {
}
trait FooDefault {
const BAR: u32 = DEFAULT_REF_BAR;
const BAR: u32 = DEFAULT_REF_BAR; //~ ERROR E0391
}
const DEFAULT_REF_BAR: u32 = <GlobalDefaultRef>::BAR; //~ ERROR E0391
const DEFAULT_REF_BAR: u32 = <GlobalDefaultRef>::BAR;
struct GlobalDefaultRef;
@@ -1,9 +1,15 @@
error[E0391]: cycle detected when simplifying constant for the type system `DEFAULT_REF_BAR`
error[E0391]: cycle detected when elaborating drops for `FooDefault::BAR`
--> $DIR/issue-24949-assoc-const-static-recursion-trait-default.rs:8:5
|
LL | const BAR: u32 = DEFAULT_REF_BAR;
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
= note: ...which requires normalizing `DEFAULT_REF_BAR`...
note: ...which requires simplifying constant for the type system `DEFAULT_REF_BAR`...
--> $DIR/issue-24949-assoc-const-static-recursion-trait-default.rs:11:1
|
LL | const DEFAULT_REF_BAR: u32 = <GlobalDefaultRef>::BAR;
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
note: ...which requires simplifying constant for the type system `DEFAULT_REF_BAR`...
--> $DIR/issue-24949-assoc-const-static-recursion-trait-default.rs:11:1
|
@@ -35,8 +41,7 @@ note: ...which requires caching mir of `FooDefault::BAR` for CTFE...
|
LL | const BAR: u32 = DEFAULT_REF_BAR;
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
= note: ...which requires normalizing `DEFAULT_REF_BAR`...
= note: ...which again requires simplifying constant for the type system `DEFAULT_REF_BAR`, completing the cycle
= note: ...which again requires elaborating drops for `FooDefault::BAR`, completing the cycle
= note: cycle used when running analysis passes on this crate
error: aborting due to previous error
@@ -4,12 +4,12 @@ trait Foo {
const BAR: u32;
}
const TRAIT_REF_BAR: u32 = <GlobalTraitRef>::BAR; //~ ERROR E0391
const TRAIT_REF_BAR: u32 = <GlobalTraitRef>::BAR;
struct GlobalTraitRef;
impl Foo for GlobalTraitRef {
const BAR: u32 = TRAIT_REF_BAR;
const BAR: u32 = TRAIT_REF_BAR; //~ ERROR E0391
}
fn main() {}
@@ -1,9 +1,15 @@
error[E0391]: cycle detected when simplifying constant for the type system `TRAIT_REF_BAR`
error[E0391]: cycle detected when elaborating drops for `<impl at $DIR/issue-24949-assoc-const-static-recursion-trait.rs:11:1: 13:2>::BAR`
--> $DIR/issue-24949-assoc-const-static-recursion-trait.rs:12:5
|
LL | const BAR: u32 = TRAIT_REF_BAR;
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
= note: ...which requires normalizing `TRAIT_REF_BAR`...
note: ...which requires simplifying constant for the type system `TRAIT_REF_BAR`...
--> $DIR/issue-24949-assoc-const-static-recursion-trait.rs:7:1
|
LL | const TRAIT_REF_BAR: u32 = <GlobalTraitRef>::BAR;
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
note: ...which requires simplifying constant for the type system `TRAIT_REF_BAR`...
--> $DIR/issue-24949-assoc-const-static-recursion-trait.rs:7:1
|
@@ -35,8 +41,7 @@ note: ...which requires caching mir of `<impl at $DIR/issue-24949-assoc-const-st
|
LL | const BAR: u32 = TRAIT_REF_BAR;
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
= note: ...which requires normalizing `TRAIT_REF_BAR`...
= note: ...which again requires simplifying constant for the type system `TRAIT_REF_BAR`, completing the cycle
= note: ...which again requires elaborating drops for `<impl at $DIR/issue-24949-assoc-const-static-recursion-trait.rs:11:1: 13:2>::BAR`, completing the cycle
= note: cycle used when running analysis passes on this crate
error: aborting due to previous error
+3 -6
View File
@@ -1,18 +1,15 @@
// build-fail
// build-pass
// compile-flags: -Zmir-opt-level=3
// Overflow can't be detected by const prop
// could only be detected after optimizations
#![deny(warnings)]
fn main() {
let _ = add(u8::MAX, 1);
//~^ NOTE in this expansion of inlined source
//~| NOTE in this expansion of inlined source
}
#[inline(always)]
fn add(x: u8, y: u8) -> u8 {
x + y
//~^ ERROR this arithmetic operation will overflow
//~| NOTE attempt to compute `u8::MAX + 1_u8`, which would overflow
//~| NOTE `#[deny(arithmetic_overflow)]` on by default
}
@@ -1,13 +0,0 @@
error: this arithmetic operation will overflow
--> $DIR/inline_spans.rs:14:5
|
LL | let _ = add(u8::MAX, 1);
| --------------- in this inlined function call
...
LL | x + y
| ^^^^^ attempt to compute `u8::MAX + 1_u8`, which would overflow
|
= note: `#[deny(arithmetic_overflow)]` on by default
error: aborting due to previous error
@@ -21,6 +21,7 @@ LL | let x: &'static i32 = &X;
| ^ referenced constant has errors
query stack during panic:
#0 [try_normalize_mir_const_after_erasing_regions] normalizing `main::promoted[1]`
#1 [optimized_mir] optimizing MIR for `main`
#2 [collect_and_partition_mono_items] collect_and_partition_mono_items
#1 [mir_drops_elaborated_and_const_checked] elaborating drops for `main`
#2 [optimized_mir] optimizing MIR for `main`
#3 [collect_and_partition_mono_items] collect_and_partition_mono_items
end of query stack
@@ -2,7 +2,7 @@ error[E0080]: evaluation of constant value failed
--> $DIR/issue-49296.rs:9:16
|
LL | const X: u64 = *wat(42);
| ^^^^^^^^ pointer to alloc2 was dereferenced after this allocation got freed
| ^^^^^^^^ pointer to alloc3 was dereferenced after this allocation got freed
error: aborting due to previous error
@@ -13,4 +13,5 @@ impl PrintName {
fn main() {
let _ = PrintName::VOID;
//~^ ERROR erroneous constant used [E0080]
}
@@ -6,6 +6,12 @@ LL | const VOID: ! = panic!();
|
= note: this error originates in the macro `$crate::panic::panic_2015` (in Nightly builds, run with -Z macro-backtrace for more info)
error: aborting due to previous error
error[E0080]: erroneous constant used
--> $DIR/panic-assoc-never-type.rs:15:13
|
LL | let _ = PrintName::VOID;
| ^^^^^^^^^^^^^^^ referenced constant has errors
error: aborting due to 2 previous errors
For more information about this error, try `rustc --explain E0080`.
@@ -1,3 +1,45 @@
warning: this arithmetic operation will overflow
--> $DIR/promoted_errors.rs:15:5
|
LL | 0 - 1
| ^^^^^ attempt to compute `0_u32 - 1_u32`, which would overflow
|
note: the lint level is defined here
--> $DIR/promoted_errors.rs:11:20
|
LL | #![warn(const_err, arithmetic_overflow, unconditional_panic)]
| ^^^^^^^^^^^^^^^^^^^
warning: this operation will panic at runtime
--> $DIR/promoted_errors.rs:21:5
|
LL | 1 / 0
| ^^^^^ attempt to divide `1_i32` by zero
|
note: the lint level is defined here
--> $DIR/promoted_errors.rs:11:41
|
LL | #![warn(const_err, arithmetic_overflow, unconditional_panic)]
| ^^^^^^^^^^^^^^^^^^^
warning: this operation will panic at runtime
--> $DIR/promoted_errors.rs:27:5
|
LL | 1 / (1 - 1)
| ^^^^^^^^^^^ attempt to divide `1_i32` by zero
warning: this operation will panic at runtime
--> $DIR/promoted_errors.rs:31:5
|
LL | 1 / (false as i32)
| ^^^^^^^^^^^^^^^^^^ attempt to divide `1_i32` by zero
warning: this operation will panic at runtime
--> $DIR/promoted_errors.rs:35:5
|
LL | [1, 2, 3][4]
| ^^^^^^^^^^^^ index out of bounds: the length is 3 but the index is 4
warning: any use of this value will cause an error
--> $DIR/promoted_errors.rs:15:5
|
@@ -6,7 +48,7 @@ LL | 0 - 1
| |
| attempt to compute `0_u32 - 1_u32`, which would overflow
| inside `overflow` at $DIR/promoted_errors.rs:15:5
| inside `X` at $DIR/promoted_errors.rs:38:29
| inside `X` at $DIR/promoted_errors.rs:43:29
...
LL | / const X: () = {
LL | | let _x: &'static u32 = &overflow();
@@ -26,7 +68,7 @@ LL | #![warn(const_err, arithmetic_overflow, unconditional_panic)]
= note: for more information, see issue #71800 <https://github.com/rust-lang/rust/issues/71800>
warning: any use of this value will cause an error
--> $DIR/promoted_errors.rs:38:28
--> $DIR/promoted_errors.rs:43:28
|
LL | / const X: () = {
LL | | let _x: &'static u32 = &overflow();
@@ -41,5 +83,5 @@ LL | | };
= warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release!
= note: for more information, see issue #71800 <https://github.com/rust-lang/rust/issues/71800>
warning: 2 warnings emitted
warning: 7 warnings emitted
@@ -1,12 +1,54 @@
warning: this arithmetic operation will overflow
--> $DIR/promoted_errors.rs:15:5
|
LL | 0 - 1
| ^^^^^ attempt to compute `0_u32 - 1_u32`, which would overflow
|
note: the lint level is defined here
--> $DIR/promoted_errors.rs:11:20
|
LL | #![warn(const_err, arithmetic_overflow, unconditional_panic)]
| ^^^^^^^^^^^^^^^^^^^
warning: this operation will panic at runtime
--> $DIR/promoted_errors.rs:21:5
|
LL | 1 / 0
| ^^^^^ attempt to divide `1_i32` by zero
|
note: the lint level is defined here
--> $DIR/promoted_errors.rs:11:41
|
LL | #![warn(const_err, arithmetic_overflow, unconditional_panic)]
| ^^^^^^^^^^^^^^^^^^^
warning: this operation will panic at runtime
--> $DIR/promoted_errors.rs:27:5
|
LL | 1 / (1 - 1)
| ^^^^^^^^^^^ attempt to divide `1_i32` by zero
warning: this operation will panic at runtime
--> $DIR/promoted_errors.rs:31:5
|
LL | 1 / (false as i32)
| ^^^^^^^^^^^^^^^^^^ attempt to divide `1_i32` by zero
warning: this operation will panic at runtime
--> $DIR/promoted_errors.rs:35:5
|
LL | [1, 2, 3][4]
| ^^^^^^^^^^^^ index out of bounds: the length is 3 but the index is 4
warning: any use of this value will cause an error
--> $DIR/promoted_errors.rs:20:5
--> $DIR/promoted_errors.rs:21:5
|
LL | 1 / 0
| ^^^^^
| |
| attempt to divide `1_i32` by zero
| inside `div_by_zero1` at $DIR/promoted_errors.rs:20:5
| inside `X` at $DIR/promoted_errors.rs:41:29
| inside `div_by_zero1` at $DIR/promoted_errors.rs:21:5
| inside `X` at $DIR/promoted_errors.rs:46:29
...
LL | / const X: () = {
LL | | let _x: &'static u32 = &overflow();
@@ -26,7 +68,7 @@ LL | #![warn(const_err, arithmetic_overflow, unconditional_panic)]
= note: for more information, see issue #71800 <https://github.com/rust-lang/rust/issues/71800>
warning: any use of this value will cause an error
--> $DIR/promoted_errors.rs:41:28
--> $DIR/promoted_errors.rs:46:28
|
LL | / const X: () = {
LL | | let _x: &'static u32 = &overflow();
@@ -42,5 +84,5 @@ LL | | };
= warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release!
= note: for more information, see issue #71800 <https://github.com/rust-lang/rust/issues/71800>
warning: 2 warnings emitted
warning: 7 warnings emitted
@@ -1,3 +1,45 @@
warning: this arithmetic operation will overflow
--> $DIR/promoted_errors.rs:15:5
|
LL | 0 - 1
| ^^^^^ attempt to compute `0_u32 - 1_u32`, which would overflow
|
note: the lint level is defined here
--> $DIR/promoted_errors.rs:11:20
|
LL | #![warn(const_err, arithmetic_overflow, unconditional_panic)]
| ^^^^^^^^^^^^^^^^^^^
warning: this operation will panic at runtime
--> $DIR/promoted_errors.rs:21:5
|
LL | 1 / 0
| ^^^^^ attempt to divide `1_i32` by zero
|
note: the lint level is defined here
--> $DIR/promoted_errors.rs:11:41
|
LL | #![warn(const_err, arithmetic_overflow, unconditional_panic)]
| ^^^^^^^^^^^^^^^^^^^
warning: this operation will panic at runtime
--> $DIR/promoted_errors.rs:27:5
|
LL | 1 / (1 - 1)
| ^^^^^^^^^^^ attempt to divide `1_i32` by zero
warning: this operation will panic at runtime
--> $DIR/promoted_errors.rs:31:5
|
LL | 1 / (false as i32)
| ^^^^^^^^^^^^^^^^^^ attempt to divide `1_i32` by zero
warning: this operation will panic at runtime
--> $DIR/promoted_errors.rs:35:5
|
LL | [1, 2, 3][4]
| ^^^^^^^^^^^^ index out of bounds: the length is 3 but the index is 4
warning: any use of this value will cause an error
--> $DIR/promoted_errors.rs:15:5
|
@@ -6,7 +48,7 @@ LL | 0 - 1
| |
| attempt to compute `0_u32 - 1_u32`, which would overflow
| inside `overflow` at $DIR/promoted_errors.rs:15:5
| inside `X` at $DIR/promoted_errors.rs:38:29
| inside `X` at $DIR/promoted_errors.rs:43:29
...
LL | / const X: () = {
LL | | let _x: &'static u32 = &overflow();
@@ -26,7 +68,7 @@ LL | #![warn(const_err, arithmetic_overflow, unconditional_panic)]
= note: for more information, see issue #71800 <https://github.com/rust-lang/rust/issues/71800>
warning: any use of this value will cause an error
--> $DIR/promoted_errors.rs:38:28
--> $DIR/promoted_errors.rs:43:28
|
LL | / const X: () = {
LL | | let _x: &'static u32 = &overflow();
@@ -41,5 +83,5 @@ LL | | };
= warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release!
= note: for more information, see issue #71800 <https://github.com/rust-lang/rust/issues/71800>
warning: 2 warnings emitted
warning: 7 warnings emitted
@@ -15,20 +15,25 @@ const fn overflow() -> u32 {
0 - 1
//[opt_with_overflow_checks,noopt]~^ WARN any use of this value will cause an error
//[opt_with_overflow_checks,noopt]~| WARN this was previously accepted by the compiler
//~^^^ WARN this arithmetic operation will overflow
}
const fn div_by_zero1() -> i32 {
1 / 0
//[opt]~^ WARN any use of this value will cause an error
//[opt]~| WARN this was previously accepted by the compiler but is being phased out
//~^^^ WARN this operation will panic at runtime
}
const fn div_by_zero2() -> i32 {
1 / (1 - 1)
//~^ WARN this operation will panic at runtime
}
const fn div_by_zero3() -> i32 {
1 / (false as i32)
//~^ WARN this operation will panic at runtime
}
const fn oob() -> i32 {
[1, 2, 3][4]
//~^ WARN this operation will panic at runtime
}
// An unused constant containing failing promoteds.
@@ -28,6 +28,7 @@ const fn read_field3() -> Field3 {
const FIELD3: Field3 = unsafe { UNION.field3 };
//~^ ERROR it is undefined behavior to use this value
FIELD3
//~^ ERROR erroneous constant used [E0080]
}
fn main() {
@@ -9,6 +9,12 @@ LL | const FIELD3: Field3 = unsafe { UNION.field3 };
__ __ __ __ __ __ __ __ │ ░░░░░░░░
}
error: aborting due to previous error
error[E0080]: erroneous constant used
--> $DIR/union-const-eval-field.rs:30:5
|
LL | FIELD3
| ^^^^^^ referenced constant has errors
error: aborting due to 2 previous errors
For more information about this error, try `rustc --explain E0080`.
+2
View File
@@ -1,5 +1,7 @@
const fn foo() { (||{})() }
//~^ ERROR cannot call non-const closure
//~| ERROR erroneous constant used [const_err]
//~| WARNING this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release!
const fn bad(input: fn()) {
input()
+12 -2
View File
@@ -8,11 +8,21 @@ LL | const fn foo() { (||{})() }
= note: calls in constant functions are limited to constant functions, tuple structs and tuple variants
error: function pointers are not allowed in const fn
--> $DIR/issue-56164.rs:5:5
--> $DIR/issue-56164.rs:7:5
|
LL | input()
| ^^^^^^^
error: aborting due to 2 previous errors
error: erroneous constant used
--> $DIR/issue-56164.rs:1:18
|
LL | const fn foo() { (||{})() }
| ^^^^^^ referenced constant has errors
|
= note: `#[deny(const_err)]` on by default
= warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release!
= note: for more information, see issue #71800 <https://github.com/rust-lang/rust/issues/71800>
error: aborting due to 3 previous errors
For more information about this error, try `rustc --explain E0015`.
+4 -1
View File
@@ -8,7 +8,10 @@
//~^ ERROR: argument to `panic!()` in a const context must have type `&str`
const fn _foo() {
panic!(&1); //~ ERROR: argument to `panic!()` in a const context must have type `&str`
panic!(&1);
//~^ ERROR: argument to `panic!()` in a const context must have type `&str`
//~| ERROR: erroneous constant used [const_err]
//~| WARNING: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release!
}
// ensure that conforming panics don't cause an error
+11 -1
View File
@@ -22,5 +22,15 @@ LL | panic!(&1);
|
= note: this error originates in the macro `$crate::panic::panic_2015` (in Nightly builds, run with -Z macro-backtrace for more info)
error: aborting due to 3 previous errors
error: erroneous constant used
--> $DIR/issue-66693.rs:11:12
|
LL | panic!(&1);
| ^^ referenced constant has errors
|
= note: `#[deny(const_err)]` on by default
= warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release!
= note: for more information, see issue #71800 <https://github.com/rust-lang/rust/issues/71800>
error: aborting due to 4 previous errors
+4 -2
View File
@@ -4,11 +4,13 @@
//[opt_with_overflow_checks]compile-flags: -C overflow-checks=on -O
// build-pass
#[allow(arithmetic_overflow)]
const fn assert_static<T>(_: &'static T) {}
const fn fail() -> i32 { 1/0 }
#[allow(unconditional_panic)]
const fn fail() -> i32 {
1/0
}
const C: i32 = {
// Promoted that fails to evaluate in dead code -- this must work
// (for backwards compatibility reasons).
@@ -2,7 +2,6 @@
//~| NOTE ...which requires computing layout of `core::option::Option<<S as Mirror>::It>`...
//~| NOTE ...which requires computing layout of `core::option::Option<S>`...
//~| NOTE ...which again requires computing layout of `S`, completing the cycle
//~| NOTE cycle used when computing layout of `core::option::Option<S>`
// build-fail
@@ -15,5 +14,6 @@ impl<T: ?Sized> Mirror for T {
struct S(Option<<S as Mirror>::It>);
fn main() {
//~^ NOTE cycle used when elaborating drops for `main`
let _s = S(None);
}
@@ -3,7 +3,11 @@ error[E0391]: cycle detected when computing layout of `S`
= note: ...which requires computing layout of `core::option::Option<<S as Mirror>::It>`...
= note: ...which requires computing layout of `core::option::Option<S>`...
= note: ...which again requires computing layout of `S`, completing the cycle
= note: cycle used when computing layout of `core::option::Option<S>`
note: cycle used when elaborating drops for `main`
--> $DIR/issue-26548-recursion-via-normalize.rs:16:1
|
LL | fn main() {
| ^^^^^^^^^
error: aborting due to previous error
@@ -0,0 +1,10 @@
error: this operation will panic at runtime
--> $DIR/ice-5497.rs:9:22
|
LL | const OOB: i32 = [1][1] + T::OOB;
| ^^^^^^ index out of bounds: the length is 1 but the index is 1
|
= note: `#[deny(unconditional_panic)]` on by default
error: aborting due to previous error