Skip to content

Commit 4d0dd02

Browse files
committed
Auto merge of #80579 - RalfJung:no-fallible-promotion, r=oli-obk
avoid promoting division, modulo and indexing operations that could fail For division, `x / y` will still be promoted if `y` is a non-zero integer literal; however, `1/(1+1)` will not be promoted any more. While at it, also see if we can reject promoting floating-point arithmetic (which are [complicated](rust-lang/unsafe-code-guidelines#237) so maybe we should not promote them). This will need a crater run to see if there's code out there that relies on these things being promoted. If we can land this, promoteds in `fn`/`const fn` cannot fail to evaluate any more, which should let us do some simplifications in codegen/Miri! Cc rust-lang/rfcs#3027 Fixes #61821 r? `@oli-obk`
2 parents 4153fa8 + ccaabc9 commit 4d0dd02

14 files changed

+331
-333
lines changed

compiler/rustc_lint_defs/src/builtin.rs

+7-26
Original file line numberDiff line numberDiff line change
@@ -238,41 +238,22 @@ declare_lint! {
238238
///
239239
/// ```rust,compile_fail
240240
/// #![allow(unconditional_panic)]
241-
/// let x: &'static i32 = &(1 / 0);
241+
/// const C: i32 = 1/0;
242242
/// ```
243243
///
244244
/// {{produces}}
245245
///
246246
/// ### Explanation
247247
///
248-
/// This lint detects code that is very likely incorrect. If this lint is
249-
/// allowed, then the code will not be evaluated at compile-time, and
250-
/// instead continue to generate code to evaluate at runtime, which may
251-
/// panic during runtime.
248+
/// This lint detects constants that fail to evaluate. Allowing the lint will accept the
249+
/// constant declaration, but any use of this constant will still lead to a hard error. This is
250+
/// a future incompatibility lint; the plan is to eventually entirely forbid even declaring
251+
/// constants that cannot be evaluated. See [issue #71800] for more details.
252252
///
253-
/// Note that this lint may trigger in either inside or outside of a
254-
/// [const context]. Outside of a [const context], the compiler can
255-
/// sometimes evaluate an expression at compile-time in order to generate
256-
/// more efficient code. As the compiler becomes better at doing this, it
257-
/// needs to decide what to do when it encounters code that it knows for
258-
/// certain will panic or is otherwise incorrect. Making this a hard error
259-
/// would prevent existing code that exhibited this behavior from
260-
/// compiling, breaking backwards-compatibility. However, this is almost
261-
/// certainly incorrect code, so this is a deny-by-default lint. For more
262-
/// details, see [RFC 1229] and [issue #28238].
263-
///
264-
/// Note that there are several other more specific lints associated with
265-
/// compile-time evaluation, such as [`arithmetic_overflow`],
266-
/// [`unconditional_panic`].
267-
///
268-
/// [const context]: https://doc.rust-lang.org/reference/const_eval.html#const-context
269-
/// [RFC 1229]: https://github.com/rust-lang/rfcs/blob/master/text/1229-compile-time-asserts.md
270-
/// [issue #28238]: https://github.com/rust-lang/rust/issues/28238
271-
/// [`arithmetic_overflow`]: deny-by-default.html#arithmetic-overflow
272-
/// [`unconditional_panic`]: deny-by-default.html#unconditional-panic
253+
/// [issue #71800]: https://github.com/rust-lang/rust/issues/71800
273254
pub CONST_ERR,
274255
Deny,
275-
"constant evaluation detected erroneous expression",
256+
"constant evaluation encountered erroneous expression",
276257
report_in_external_macro
277258
}
278259

compiler/rustc_mir/src/transform/promote_consts.rs

+86-26
Original file line numberDiff line numberDiff line change
@@ -415,10 +415,11 @@ impl<'tcx> Validator<'_, 'tcx> {
415415
// FIXME(eddyb) maybe cache this?
416416
fn validate_local(&self, local: Local) -> Result<(), Unpromotable> {
417417
if let TempState::Defined { location: loc, .. } = self.temps[local] {
418-
let num_stmts = self.body[loc.block].statements.len();
418+
let block = &self.body[loc.block];
419+
let num_stmts = block.statements.len();
419420

420421
if loc.statement_index < num_stmts {
421-
let statement = &self.body[loc.block].statements[loc.statement_index];
422+
let statement = &block.statements[loc.statement_index];
422423
match &statement.kind {
423424
StatementKind::Assign(box (_, rhs)) => self.validate_rvalue(rhs),
424425
_ => {
@@ -430,7 +431,7 @@ impl<'tcx> Validator<'_, 'tcx> {
430431
}
431432
}
432433
} else {
433-
let terminator = self.body[loc.block].terminator();
434+
let terminator = block.terminator();
434435
match &terminator.kind {
435436
TerminatorKind::Call { func, args, .. } => self.validate_call(func, args),
436437
TerminatorKind::Yield { .. } => Err(Unpromotable),
@@ -452,22 +453,15 @@ impl<'tcx> Validator<'_, 'tcx> {
452453
match elem {
453454
ProjectionElem::Deref => {
454455
let mut promotable = false;
455-
// The `is_empty` predicate is introduced to exclude the case
456-
// where the projection operations are [ .field, * ].
457-
// The reason is because promotion will be illegal if field
458-
// accesses precede the dereferencing.
456+
// We need to make sure this is a `Deref` of a local with no further projections.
459457
// Discussion can be found at
460458
// https://github.com/rust-lang/rust/pull/74945#discussion_r463063247
461-
// There may be opportunity for generalization, but this needs to be
462-
// accounted for.
463-
if place_base.projection.is_empty() {
459+
if let Some(local) = place_base.as_local() {
464460
// This is a special treatment for cases like *&STATIC where STATIC is a
465461
// global static variable.
466462
// This pattern is generated only when global static variables are directly
467463
// accessed and is qualified for promotion safely.
468-
if let TempState::Defined { location, .. } =
469-
self.temps[place_base.local]
470-
{
464+
if let TempState::Defined { location, .. } = self.temps[local] {
471465
let def_stmt = self.body[location.block]
472466
.statements
473467
.get(location.statement_index);
@@ -505,6 +499,50 @@ impl<'tcx> Validator<'_, 'tcx> {
505499
ProjectionElem::ConstantIndex { .. } | ProjectionElem::Subslice { .. } => {}
506500

507501
ProjectionElem::Index(local) => {
502+
if !self.explicit {
503+
let mut promotable = false;
504+
// Only accept if we can predict the index and are indexing an array.
505+
let val = if let TempState::Defined { location: loc, .. } =
506+
self.temps[local]
507+
{
508+
let block = &self.body[loc.block];
509+
if loc.statement_index < block.statements.len() {
510+
let statement = &block.statements[loc.statement_index];
511+
match &statement.kind {
512+
StatementKind::Assign(box (
513+
_,
514+
Rvalue::Use(Operand::Constant(c)),
515+
)) => c.literal.try_eval_usize(self.tcx, self.param_env),
516+
_ => None,
517+
}
518+
} else {
519+
None
520+
}
521+
} else {
522+
None
523+
};
524+
if let Some(idx) = val {
525+
// Determine the type of the thing we are indexing.
526+
let ty = place_base.ty(self.body, self.tcx).ty;
527+
match ty.kind() {
528+
ty::Array(_, len) => {
529+
// It's an array; determine its length.
530+
if let Some(len) =
531+
len.try_eval_usize(self.tcx, self.param_env)
532+
{
533+
// If the index is in-bounds, go ahead.
534+
if idx < len {
535+
promotable = true;
536+
}
537+
}
538+
}
539+
_ => {}
540+
}
541+
}
542+
if !promotable {
543+
return Err(Unpromotable);
544+
}
545+
}
508546
self.validate_local(local)?;
509547
}
510548

@@ -589,9 +627,7 @@ impl<'tcx> Validator<'_, 'tcx> {
589627

590628
fn validate_rvalue(&self, rvalue: &Rvalue<'tcx>) -> Result<(), Unpromotable> {
591629
match rvalue {
592-
Rvalue::Use(operand)
593-
| Rvalue::Repeat(operand, _)
594-
| Rvalue::UnaryOp(UnOp::Not | UnOp::Neg, operand) => {
630+
Rvalue::Use(operand) | Rvalue::Repeat(operand, _) => {
595631
self.validate_operand(operand)?;
596632
}
597633

@@ -616,10 +652,26 @@ impl<'tcx> Validator<'_, 'tcx> {
616652
self.validate_operand(operand)?;
617653
}
618654

655+
Rvalue::NullaryOp(op, _) => match op {
656+
NullOp::Box => return Err(Unpromotable),
657+
NullOp::SizeOf => {}
658+
},
659+
660+
Rvalue::UnaryOp(op, operand) => {
661+
match op {
662+
// These operations can never fail.
663+
UnOp::Neg | UnOp::Not => {}
664+
}
665+
666+
self.validate_operand(operand)?;
667+
}
668+
619669
Rvalue::BinaryOp(op, lhs, rhs) | Rvalue::CheckedBinaryOp(op, lhs, rhs) => {
620670
let op = *op;
621-
if let ty::RawPtr(_) | ty::FnPtr(..) = lhs.ty(self.body, self.tcx).kind() {
622-
// raw pointer operations are not allowed inside consts and thus not promotable
671+
let lhs_ty = lhs.ty(self.body, self.tcx);
672+
673+
if let ty::RawPtr(_) | ty::FnPtr(..) = lhs_ty.kind() {
674+
// Raw and fn pointer operations are not allowed inside consts and thus not promotable.
623675
assert!(matches!(
624676
op,
625677
BinOp::Eq
@@ -634,7 +686,22 @@ impl<'tcx> Validator<'_, 'tcx> {
634686
}
635687

636688
match op {
637-
// FIXME: reject operations that can fail -- namely, division and modulo.
689+
BinOp::Div | BinOp::Rem => {
690+
if !self.explicit && lhs_ty.is_integral() {
691+
// Integer division: the RHS must be a non-zero const.
692+
let const_val = match rhs {
693+
Operand::Constant(c) => {
694+
c.literal.try_eval_bits(self.tcx, self.param_env, lhs_ty)
695+
}
696+
_ => None,
697+
};
698+
match const_val {
699+
Some(x) if x != 0 => {} // okay
700+
_ => return Err(Unpromotable), // value not known or 0 -- not okay
701+
}
702+
}
703+
}
704+
// The remaining operations can never fail.
638705
BinOp::Eq
639706
| BinOp::Ne
640707
| BinOp::Le
@@ -645,8 +712,6 @@ impl<'tcx> Validator<'_, 'tcx> {
645712
| BinOp::Add
646713
| BinOp::Sub
647714
| BinOp::Mul
648-
| BinOp::Div
649-
| BinOp::Rem
650715
| BinOp::BitXor
651716
| BinOp::BitAnd
652717
| BinOp::BitOr
@@ -658,11 +723,6 @@ impl<'tcx> Validator<'_, 'tcx> {
658723
self.validate_operand(rhs)?;
659724
}
660725

661-
Rvalue::NullaryOp(op, _) => match op {
662-
NullOp::Box => return Err(Unpromotable),
663-
NullOp::SizeOf => {}
664-
},
665-
666726
Rvalue::AddressOf(_, place) => {
667727
// We accept `&raw *`, i.e., raw reborrows -- creating a raw pointer is
668728
// no problem, only using it is.

src/test/ui/consts/array-literal-index-oob.rs

-2
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,4 @@
66
fn main() {
77
&{ [1, 2, 3][4] };
88
//~^ WARN operation will panic
9-
//~| WARN reaching this expression at runtime will panic or abort
10-
//~| WARN erroneous constant used [const_err]
119
}

src/test/ui/consts/array-literal-index-oob.stderr

+1-21
Original file line numberDiff line numberDiff line change
@@ -10,25 +10,5 @@ note: the lint level is defined here
1010
LL | #![warn(const_err, unconditional_panic)]
1111
| ^^^^^^^^^^^^^^^^^^^
1212

13-
warning: reaching this expression at runtime will panic or abort
14-
--> $DIR/array-literal-index-oob.rs:7:8
15-
|
16-
LL | &{ [1, 2, 3][4] };
17-
| ---^^^^^^^^^^^^--
18-
| |
19-
| indexing out of bounds: the len is 3 but the index is 4
20-
|
21-
note: the lint level is defined here
22-
--> $DIR/array-literal-index-oob.rs:4:9
23-
|
24-
LL | #![warn(const_err, unconditional_panic)]
25-
| ^^^^^^^^^
26-
27-
warning: erroneous constant used
28-
--> $DIR/array-literal-index-oob.rs:7:5
29-
|
30-
LL | &{ [1, 2, 3][4] };
31-
| ^^^^^^^^^^^^^^^^^ referenced constant has errors
32-
33-
warning: 3 warnings emitted
13+
warning: 1 warning emitted
3414

src/test/ui/consts/const-eval/const-eval-query-stack.rs

+7-3
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
// compile-flags: -Ztreat-err-as-bug
1+
//~ERROR constructed but no error reported
2+
// compile-flags: -Ztreat-err-as-bug=2
23
// build-fail
34
// failure-status: 101
45
// rustc-env:RUST_BACKTRACE=1
@@ -15,8 +16,11 @@
1516

1617
#![allow(unconditional_panic)]
1718

19+
#[warn(const_err)]
20+
const X: i32 = 1 / 0; //~WARN any use of this value will cause an error
21+
1822
fn main() {
19-
let x: &'static i32 = &(1 / 0);
20-
//~^ ERROR reaching this expression at runtime will panic or abort [const_err]
23+
let x: &'static i32 = &X;
24+
//~^ ERROR evaluation of constant expression failed
2125
println!("x={}", x);
2226
}
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,26 @@
1-
error: reaching this expression at runtime will panic or abort
2-
--> $DIR/const-eval-query-stack.rs:19:28
1+
warning: any use of this value will cause an error
2+
--> $DIR/const-eval-query-stack.rs:20:16
33
|
4-
LL | let x: &'static i32 = &(1 / 0);
5-
| -^^^^^^^
6-
| |
7-
| dividing by zero
4+
LL | const X: i32 = 1 / 0;
5+
| ---------------^^^^^-
6+
| |
7+
| attempt to divide `1_i32` by zero
8+
|
9+
note: the lint level is defined here
10+
--> $DIR/const-eval-query-stack.rs:19:8
811
|
9-
= note: `#[deny(const_err)]` on by default
12+
LL | #[warn(const_err)]
13+
| ^^^^^^^^^
1014

15+
error[E0080]: evaluation of constant expression failed
16+
--> $DIR/const-eval-query-stack.rs:23:27
17+
|
18+
LL | let x: &'static i32 = &X;
19+
| ^-
20+
| |
21+
| referenced constant has errors
1122
query stack during panic:
12-
#0 [eval_to_allocation_raw] const-evaluating + checking `main::promoted[1]`
13-
#1 [eval_to_const_value_raw] simplifying constant for the type system `main::promoted[1]`
14-
#2 [eval_to_const_value_raw] simplifying constant for the type system `main::promoted[1]`
15-
#3 [normalize_generic_arg_after_erasing_regions] normalizing `main::promoted[1]`
16-
#4 [optimized_mir] optimizing MIR for `main`
17-
#5 [collect_and_partition_mono_items] collect_and_partition_mono_items
23+
#0 [normalize_generic_arg_after_erasing_regions] normalizing `main::promoted[1]`
24+
#1 [optimized_mir] optimizing MIR for `main`
25+
#2 [collect_and_partition_mono_items] collect_and_partition_mono_items
1826
end of query stack

0 commit comments

Comments
 (0)