-
Notifications
You must be signed in to change notification settings - Fork 13k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
avoid promoting division, modulo and indexing operations that could fail #80579
Changes from all commits
5be27b7
69a997b
f62cecd
0c7fd2c
ccaabc9
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -415,10 +415,11 @@ impl<'tcx> Validator<'_, 'tcx> { | |
// FIXME(eddyb) maybe cache this? | ||
fn validate_local(&self, local: Local) -> Result<(), Unpromotable> { | ||
if let TempState::Defined { location: loc, .. } = self.temps[local] { | ||
let num_stmts = self.body[loc.block].statements.len(); | ||
let block = &self.body[loc.block]; | ||
let num_stmts = block.statements.len(); | ||
|
||
if loc.statement_index < num_stmts { | ||
let statement = &self.body[loc.block].statements[loc.statement_index]; | ||
let statement = &block.statements[loc.statement_index]; | ||
match &statement.kind { | ||
StatementKind::Assign(box (_, rhs)) => self.validate_rvalue(rhs), | ||
_ => { | ||
|
@@ -430,7 +431,7 @@ impl<'tcx> Validator<'_, 'tcx> { | |
} | ||
} | ||
} else { | ||
let terminator = self.body[loc.block].terminator(); | ||
let terminator = block.terminator(); | ||
match &terminator.kind { | ||
TerminatorKind::Call { func, args, .. } => self.validate_call(func, args), | ||
TerminatorKind::Yield { .. } => Err(Unpromotable), | ||
|
@@ -452,22 +453,15 @@ impl<'tcx> Validator<'_, 'tcx> { | |
match elem { | ||
ProjectionElem::Deref => { | ||
let mut promotable = false; | ||
// The `is_empty` predicate is introduced to exclude the case | ||
// where the projection operations are [ .field, * ]. | ||
// The reason is because promotion will be illegal if field | ||
// accesses precede the dereferencing. | ||
// We need to make sure this is a `Deref` of a local with no further projections. | ||
// Discussion can be found at | ||
// https://github.com/rust-lang/rust/pull/74945#discussion_r463063247 | ||
// There may be opportunity for generalization, but this needs to be | ||
// accounted for. | ||
if place_base.projection.is_empty() { | ||
if let Some(local) = place_base.as_local() { | ||
// This is a special treatment for cases like *&STATIC where STATIC is a | ||
// global static variable. | ||
// This pattern is generated only when global static variables are directly | ||
// accessed and is qualified for promotion safely. | ||
if let TempState::Defined { location, .. } = | ||
self.temps[place_base.local] | ||
{ | ||
if let TempState::Defined { location, .. } = self.temps[local] { | ||
let def_stmt = self.body[location.block] | ||
.statements | ||
.get(location.statement_index); | ||
|
@@ -505,6 +499,50 @@ impl<'tcx> Validator<'_, 'tcx> { | |
ProjectionElem::ConstantIndex { .. } | ProjectionElem::Subslice { .. } => {} | ||
|
||
ProjectionElem::Index(local) => { | ||
if !self.explicit { | ||
let mut promotable = false; | ||
// Only accept if we can predict the index and are indexing an array. | ||
let val = if let TempState::Defined { location: loc, .. } = | ||
self.temps[local] | ||
{ | ||
let block = &self.body[loc.block]; | ||
if loc.statement_index < block.statements.len() { | ||
let statement = &block.statements[loc.statement_index]; | ||
match &statement.kind { | ||
StatementKind::Assign(box ( | ||
_, | ||
Rvalue::Use(Operand::Constant(c)), | ||
)) => c.literal.try_eval_usize(self.tcx, self.param_env), | ||
_ => None, | ||
} | ||
} else { | ||
None | ||
} | ||
} else { | ||
None | ||
}; | ||
if let Some(idx) = val { | ||
// Determine the type of the thing we are indexing. | ||
let ty = place_base.ty(self.body, self.tcx).ty; | ||
match ty.kind() { | ||
ty::Array(_, len) => { | ||
// It's an array; determine its length. | ||
if let Some(len) = | ||
len.try_eval_usize(self.tcx, self.param_env) | ||
{ | ||
// If the index is in-bounds, go ahead. | ||
if idx < len { | ||
promotable = true; | ||
} | ||
} | ||
} | ||
_ => {} | ||
} | ||
} | ||
if !promotable { | ||
return Err(Unpromotable); | ||
} | ||
} | ||
self.validate_local(local)?; | ||
} | ||
|
||
|
@@ -589,9 +627,7 @@ impl<'tcx> Validator<'_, 'tcx> { | |
|
||
fn validate_rvalue(&self, rvalue: &Rvalue<'tcx>) -> Result<(), Unpromotable> { | ||
match rvalue { | ||
Rvalue::Use(operand) | ||
| Rvalue::Repeat(operand, _) | ||
| Rvalue::UnaryOp(UnOp::Not | UnOp::Neg, operand) => { | ||
Rvalue::Use(operand) | Rvalue::Repeat(operand, _) => { | ||
self.validate_operand(operand)?; | ||
} | ||
|
||
|
@@ -616,10 +652,26 @@ impl<'tcx> Validator<'_, 'tcx> { | |
self.validate_operand(operand)?; | ||
} | ||
|
||
Rvalue::NullaryOp(op, _) => match op { | ||
NullOp::Box => return Err(Unpromotable), | ||
NullOp::SizeOf => {} | ||
}, | ||
|
||
Rvalue::UnaryOp(op, operand) => { | ||
match op { | ||
// These operations can never fail. | ||
UnOp::Neg | UnOp::Not => {} | ||
} | ||
|
||
self.validate_operand(operand)?; | ||
} | ||
|
||
Rvalue::BinaryOp(op, lhs, rhs) | Rvalue::CheckedBinaryOp(op, lhs, rhs) => { | ||
let op = *op; | ||
if let ty::RawPtr(_) | ty::FnPtr(..) = lhs.ty(self.body, self.tcx).kind() { | ||
// raw pointer operations are not allowed inside consts and thus not promotable | ||
let lhs_ty = lhs.ty(self.body, self.tcx); | ||
|
||
if let ty::RawPtr(_) | ty::FnPtr(..) = lhs_ty.kind() { | ||
// Raw and fn pointer operations are not allowed inside consts and thus not promotable. | ||
assert!(matches!( | ||
op, | ||
BinOp::Eq | ||
|
@@ -634,7 +686,22 @@ impl<'tcx> Validator<'_, 'tcx> { | |
} | ||
|
||
match op { | ||
// FIXME: reject operations that can fail -- namely, division and modulo. | ||
BinOp::Div | BinOp::Rem => { | ||
if !self.explicit && lhs_ty.is_integral() { | ||
// Integer division: the RHS must be a non-zero const. | ||
let const_val = match rhs { | ||
Operand::Constant(c) => { | ||
c.literal.try_eval_bits(self.tcx, self.param_env, lhs_ty) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The one concern I have with this is that it could make promotion dependent on the value of a constant in another crate. I wonder if it is worth to somehow ensure that this is either a literal or a crate-local constant. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think it's fine. If the constant is evauable at this point, then it must be part of the public API of the other crate. Changing it can already cause breaking changes without taking promotion into account. The diagnostic is just a bit more suprising with promotion |
||
} | ||
_ => None, | ||
}; | ||
match const_val { | ||
Some(x) if x != 0 => {} // okay | ||
_ => return Err(Unpromotable), // value not known or 0 -- not okay | ||
} | ||
} | ||
} | ||
// The remaining operations can never fail. | ||
BinOp::Eq | ||
| BinOp::Ne | ||
| BinOp::Le | ||
|
@@ -645,8 +712,6 @@ impl<'tcx> Validator<'_, 'tcx> { | |
| BinOp::Add | ||
| BinOp::Sub | ||
| BinOp::Mul | ||
| BinOp::Div | ||
| BinOp::Rem | ||
| BinOp::BitXor | ||
| BinOp::BitAnd | ||
| BinOp::BitOr | ||
|
@@ -658,11 +723,6 @@ impl<'tcx> Validator<'_, 'tcx> { | |
self.validate_operand(rhs)?; | ||
} | ||
|
||
Rvalue::NullaryOp(op, _) => match op { | ||
NullOp::Box => return Err(Unpromotable), | ||
NullOp::SizeOf => {} | ||
}, | ||
|
||
Rvalue::AddressOf(_, place) => { | ||
// We accept `&raw *`, i.e., raw reborrows -- creating a raw pointer is | ||
// no problem, only using it is. | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,18 +1,26 @@ | ||
error: reaching this expression at runtime will panic or abort | ||
--> $DIR/const-eval-query-stack.rs:19:28 | ||
warning: any use of this value will cause an error | ||
--> $DIR/const-eval-query-stack.rs:20:16 | ||
| | ||
LL | let x: &'static i32 = &(1 / 0); | ||
| -^^^^^^^ | ||
| | | ||
| dividing by zero | ||
LL | const X: i32 = 1 / 0; | ||
| ---------------^^^^^- | ||
| | | ||
| attempt to divide `1_i32` by zero | ||
| | ||
note: the lint level is defined here | ||
--> $DIR/const-eval-query-stack.rs:19:8 | ||
| | ||
= note: `#[deny(const_err)]` on by default | ||
LL | #[warn(const_err)] | ||
| ^^^^^^^^^ | ||
|
||
error[E0080]: evaluation of constant expression failed | ||
--> $DIR/const-eval-query-stack.rs:23:27 | ||
| | ||
LL | let x: &'static i32 = &X; | ||
| ^- | ||
| | | ||
| referenced constant has errors | ||
query stack during panic: | ||
#0 [eval_to_allocation_raw] const-evaluating + checking `main::promoted[1]` | ||
#1 [eval_to_const_value_raw] simplifying constant for the type system `main::promoted[1]` | ||
#2 [eval_to_const_value_raw] simplifying constant for the type system `main::promoted[1]` | ||
#3 [normalize_generic_arg_after_erasing_regions] normalizing `main::promoted[1]` | ||
#4 [optimized_mir] optimizing MIR for `main` | ||
#5 [collect_and_partition_mono_items] collect_and_partition_mono_items | ||
#0 [normalize_generic_arg_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 | ||
end of query stack |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Will this work even if
len
is a constant defined in another crate? If so, this could lead to extremely confusing errors in the unlikely event that the upstream crate changes the value to something that makes us unable to perform promotion.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, it will, and I wondered about the same thing: #80579 (comment).