-
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
Allow constants to refer statics in const eval #71332
Changes from 1 commit
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 |
---|---|---|
@@ -1,9 +1,9 @@ | ||
use super::{error_to_const_error, CompileTimeEvalContext, CompileTimeInterpreter, MemoryExtra}; | ||
use crate::interpret::eval_nullary_intrinsic; | ||
use crate::interpret::{ | ||
intern_const_alloc_recursive, Allocation, ConstValue, GlobalId, Immediate, InternKind, | ||
InterpCx, InterpResult, MPlaceTy, MemoryKind, OpTy, RawConst, RefTracking, Scalar, | ||
ScalarMaybeUndef, StackPopCleanup, | ||
intern_const_alloc_recursive, AllocId, Allocation, ConstValue, GlobalAlloc, GlobalId, | ||
Immediate, InternKind, InterpCx, InterpResult, MPlaceTy, MemoryKind, OpTy, RawConst, | ||
RefTracking, Scalar, ScalarMaybeUndef, StackPopCleanup, | ||
}; | ||
use rustc_hir::def::DefKind; | ||
use rustc_middle::mir; | ||
|
@@ -94,10 +94,13 @@ pub(super) fn mk_eval_cx<'mir, 'tcx>( | |
) | ||
} | ||
|
||
pub(super) fn op_to_const<'tcx>( | ||
#[derive(Debug)] | ||
pub(super) struct RefersToStatic; | ||
|
||
pub(super) fn try_op_to_const<'tcx>( | ||
ecx: &CompileTimeEvalContext<'_, 'tcx>, | ||
op: OpTy<'tcx>, | ||
) -> ConstValue<'tcx> { | ||
) -> Result<ConstValue<'tcx>, RefersToStatic> { | ||
// We do not have value optimizations for everything. | ||
// Only scalars and slices, since they are very common. | ||
// Note that further down we turn scalars of undefined bits back to `ByRef`. These can result | ||
|
@@ -128,10 +131,16 @@ pub(super) fn op_to_const<'tcx>( | |
op.try_as_mplace(ecx) | ||
}; | ||
|
||
let alloc_map = ecx.tcx.alloc_map.lock(); | ||
let try_unwrap_memory = |alloc_id: AllocId| match alloc_map.get(alloc_id) { | ||
Some(GlobalAlloc::Memory(mem)) => Ok(mem), | ||
Some(GlobalAlloc::Static(_)) => Err(RefersToStatic), | ||
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. This breaks our assumption, that any constant of scalar type or 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. Oh... pattern matching assumes that references in consts can only point to immutable memory? That's... a rather big assumption! It was true so far but only due to extreme conservatism on the side of our static const checks. However, I feel like the fact that 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. If the 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. How'd that look like though? When a const takes an Though when we have I think allowing references in consts in patterns was a big mistake... do we permit this for all types or just 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.
Hold up :D we need special rules for pattern constants and const generics anyway. @eddyb talked to me about this already. E.g. we can't non-normalized padding or non-interned references in constants used in const generics. We do need a separate interning & normalization for such constants anyway. Not sure how we can detect references into statics though, maybe we need a new flag in 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 opened rust-lang/const-eval#42 for the soundness concerns. We probably have to get that answered before allowing consts pointing to mutable statics in our dynamic checks. Sorry @rpjohnst, I was entirely unaware of this particular minefield. For now, could you remove those changes from this PR? I.e., dynamically permit consts pointing to immutable statics, but make no effort to permit consts pointing to mutable statics? 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. As long as we do it behind a feature gate, that's perfectly fine in my book. 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. @rpjohnst's use-case doesn't need consts pointing to mutable statics though. I think this change is part of this PR because I recommended them to do that, because I thought we could weaken our dynamic checks all the way to "just prevent reads from mutable statics, everything else is fine". I was wrong as I didn't know about pattern-matching-soundness. In light of this new information, I'd prefer it we didn't weaken our dynamic checks quite so far. 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. Sure, will do. To clarify- do we want these dynamic checks behind a feature gate as well? Or can we rely on the static check and just feature gate any changes there? 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. If you avoid touching validity (beyond what is necessary to enable consts to point to immutable statics), I don't think any feature gates are needed for weakening the dynamic checks. |
||
_ => bug!("expected allocation ID {} to point to memory", alloc_id), | ||
}; | ||
let to_const_value = |mplace: MPlaceTy<'_>| match mplace.ptr { | ||
Scalar::Ptr(ptr) => { | ||
let alloc = ecx.tcx.alloc_map.lock().unwrap_memory(ptr.alloc_id); | ||
ConstValue::ByRef { alloc, offset: ptr.offset } | ||
let alloc = try_unwrap_memory(ptr.alloc_id)?; | ||
Ok(ConstValue::ByRef { alloc, offset: ptr.offset }) | ||
} | ||
Scalar::Raw { data, .. } => { | ||
assert!(mplace.layout.is_zst()); | ||
|
@@ -141,22 +150,20 @@ pub(super) fn op_to_const<'tcx>( | |
"this MPlaceTy must come from `try_as_mplace` being used on a zst, so we know what | ||
value this integer address must have", | ||
); | ||
ConstValue::Scalar(Scalar::zst()) | ||
Ok(ConstValue::Scalar(Scalar::zst())) | ||
} | ||
}; | ||
match immediate { | ||
Ok(mplace) => to_const_value(mplace), | ||
Ok(mplace) => Ok(to_const_value(mplace)?), | ||
// see comment on `let try_as_immediate` above | ||
Err(imm) => match *imm { | ||
Immediate::Scalar(x) => match x { | ||
ScalarMaybeUndef::Scalar(s) => ConstValue::Scalar(s), | ||
ScalarMaybeUndef::Undef => to_const_value(op.assert_mem_place(ecx)), | ||
ScalarMaybeUndef::Scalar(s) => Ok(ConstValue::Scalar(s)), | ||
ScalarMaybeUndef::Undef => Ok(to_const_value(op.assert_mem_place(ecx))?), | ||
}, | ||
Immediate::ScalarPair(a, b) => { | ||
let (data, start) = match a.not_undef().unwrap() { | ||
Scalar::Ptr(ptr) => { | ||
(ecx.tcx.alloc_map.lock().unwrap_memory(ptr.alloc_id), ptr.offset.bytes()) | ||
} | ||
Scalar::Ptr(ptr) => (try_unwrap_memory(ptr.alloc_id)?, ptr.offset.bytes()), | ||
Scalar::Raw { .. } => ( | ||
ecx.tcx | ||
.intern_const_alloc(Allocation::from_byte_aligned_bytes(b"" as &[u8])), | ||
|
@@ -166,7 +173,7 @@ pub(super) fn op_to_const<'tcx>( | |
let len = b.to_machine_usize(&ecx.tcx.tcx).unwrap(); | ||
let start = start.try_into().unwrap(); | ||
let len: usize = len.try_into().unwrap(); | ||
ConstValue::Slice { data, start, end: start + len } | ||
Ok(ConstValue::Slice { data, start, end: start + len }) | ||
} | ||
}, | ||
} | ||
|
@@ -198,17 +205,20 @@ fn validate_and_turn_into_const<'tcx>( | |
} | ||
} | ||
// Now that we validated, turn this into a proper constant. | ||
// Statics/promoteds are always `ByRef`, for the rest `op_to_const` decides | ||
// whether they become immediates. | ||
if is_static || cid.promoted.is_some() { | ||
// Statics/promoteds are always `ByRef`, for the rest `try_op_to_const` | ||
// decides whether they become immediates. | ||
let value = if !is_static && !cid.promoted.is_some() { | ||
try_op_to_const(&ecx, mplace.into()) | ||
} else { | ||
Err(RefersToStatic) | ||
}; | ||
Ok(value.unwrap_or_else(|RefersToStatic| { | ||
let ptr = mplace.ptr.assert_ptr(); | ||
Ok(ConstValue::ByRef { | ||
ConstValue::ByRef { | ||
alloc: ecx.tcx.alloc_map.lock().unwrap_memory(ptr.alloc_id), | ||
offset: ptr.offset, | ||
}) | ||
} else { | ||
Ok(op_to_const(&ecx, mplace.into())) | ||
} | ||
} | ||
})) | ||
})(); | ||
|
||
val.map_err(|error| { | ||
|
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
|
@@ -8,7 +8,6 @@ use std::hash::Hash; | |||||
use rustc_data_structures::fx::FxHashMap; | ||||||
|
||||||
use rustc_ast::ast::Mutability; | ||||||
use rustc_hir::def_id::DefId; | ||||||
use rustc_middle::mir::AssertMessage; | ||||||
use rustc_span::symbol::Symbol; | ||||||
|
||||||
|
@@ -353,7 +352,6 @@ impl<'mir, 'tcx> interpret::Machine<'mir, 'tcx> for CompileTimeInterpreter { | |||||
memory_extra: &MemoryExtra, | ||||||
alloc_id: AllocId, | ||||||
allocation: &Allocation, | ||||||
static_def_id: Option<DefId>, | ||||||
is_write: bool, | ||||||
) -> InterpResult<'tcx> { | ||||||
if is_write { | ||||||
|
@@ -368,16 +366,14 @@ impl<'mir, 'tcx> interpret::Machine<'mir, 'tcx> for CompileTimeInterpreter { | |||||
if memory_extra.can_access_statics { | ||||||
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. This should be rename to |
||||||
// Machine configuration allows us read from anything (e.g., `static` initializer). | ||||||
Ok(()) | ||||||
} else if static_def_id.is_some() { | ||||||
// Machine configuration does not allow us to read statics | ||||||
} else if allocation.mutability != Mutability::Not { | ||||||
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.
Suggested change
|
||||||
// Machine configuration does not allow us to read mutable statics | ||||||
// (e.g., `const` initializer). | ||||||
// This is unsound because the content of this allocation may be different now and | ||||||
// at run-time, so if we permit reading it now we might return the wrong value. | ||||||
Err(ConstEvalErrKind::ConstAccessesStatic.into()) | ||||||
} else { | ||||||
// Immutable global, this read is fine. | ||||||
// But make sure we never accept a read from something mutable, that would be | ||||||
// unsound. The reason is that as the content of this allocation may be different | ||||||
// now and at run-time, so if we permit reading now we might return the wrong value. | ||||||
assert_eq!(allocation.mutability, Mutability::Not); | ||||||
Ok(()) | ||||||
} | ||||||
} | ||||||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -411,11 +411,11 @@ impl<'rt, 'mir, 'tcx, M: Machine<'mir, 'tcx>> ValidityVisitor<'rt, 'mir, 'tcx, M | |
if !did.is_local() || self.ecx.tcx.is_foreign_item(did) { | ||
return Ok(()); | ||
} | ||
// FIXME: Statics referenced from consts are skipped. | ||
// This avoids spurious "const accesses static" errors | ||
// unrelated to validity, but is similarly unsound. | ||
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'd merge this with the if !did.is_local() || self.ecx.tcx.is_static(did) { The comment should also be updated:
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. But your check is stricter in the sense that it validates more, so maybe @oli-obk would prefer that one. I just think it's generally not worth recursing into other globals -- this can only possibly catch a bug if people did some crazy unsafe stuff, and those people asked for it when they get post-monomorphization errors. So I'd actually be fine to just always bail for any 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 went with the stricter check because there are tests for this, for statics-pointing-to-statics:
Happy to collapse it into the first check if we're okay ditching those tests. 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. Yeah I think it is okay to not catch the failure in double_check2. We already do not catch it cross-crate. But let's wait what @oli-obk thinks -- and in fact let's Cc @rust-lang/wg-const-eval and see what everyone else thinks :D 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. wait, how do we not catch this cross crate? Don't we revisit statics from other crates if we encounter them at a different type of non-zero offset? 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. No we don't, as far as I know. |
||
if !self.may_ref_to_static && self.ecx.tcx.is_static(did) { | ||
throw_validation_failure!( | ||
oli-obk marked this conversation as resolved.
Show resolved
Hide resolved
|
||
format_args!("a {} pointing to a static variable", kind), | ||
self.path | ||
); | ||
return Ok(()); | ||
} | ||
} | ||
} | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,10 +1,10 @@ | ||
// check-pass | ||
// compile-flags: -Zunleash-the-miri-inside-of-you | ||
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. This test should be moved to the miri-unleashed folder. 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. Also it should then be adjusted to check that reading the static actually works. Like, const TEST_VAL: u8 = *TEST;
fn main() { assert_eq!(TEST_VAL, 4); } |
||
|
||
#![allow(dead_code)] | ||
|
||
const TEST: &u8 = &MY_STATIC; | ||
//~^ skipping const checks | ||
//~| it is undefined behavior to use this value | ||
|
||
static MY_STATIC: u8 = 4; | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,17 +1,8 @@ | ||
warning: skipping const checks | ||
--> $DIR/const-points-to-static.rs:5:20 | ||
--> $DIR/const-points-to-static.rs:6:20 | ||
| | ||
LL | const TEST: &u8 = &MY_STATIC; | ||
| ^^^^^^^^^ | ||
|
||
error[E0080]: it is undefined behavior to use this value | ||
--> $DIR/const-points-to-static.rs:5:1 | ||
| | ||
LL | const TEST: &u8 = &MY_STATIC; | ||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ type validation failed: encountered a reference pointing to a static variable | ||
| | ||
= note: The rules on what exactly is undefined behavior aren't clear, so this check might be overzealous. Please open an issue on the rustc repository if you believe it should not be considered undefined behavior. | ||
|
||
error: aborting due to previous error; 1 warning emitted | ||
warning: 1 warning emitted | ||
|
||
For more information about this error, try `rustc --explain E0080`. |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,8 +1,9 @@ | ||
// check-pass | ||
// compile-flags: -Zunleash-the-miri-inside-of-you | ||
|
||
#![allow(dead_code)] | ||
|
||
const TEST: u8 = MY_STATIC; //~ ERROR any use of this value will cause an error | ||
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. This, too, should get an assertion in main checking the value that was read. 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. Why am I cc'ed? 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. Didn't you do const-prop stuff? Or am I mixing that up with someone else? 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. Oh I think I meant @wesleywiser, sorry for that! 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 this was added just to document the current state when |
||
const TEST: u8 = MY_STATIC; | ||
//~^ skipping const checks | ||
|
||
static MY_STATIC: u8 = 4; | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,18 +1,8 @@ | ||
warning: skipping const checks | ||
--> $DIR/const-prop-read-static-in-const.rs:5:18 | ||
--> $DIR/const-prop-read-static-in-const.rs:6:18 | ||
| | ||
LL | const TEST: u8 = MY_STATIC; | ||
| ^^^^^^^^^ | ||
|
||
error: any use of this value will cause an error | ||
--> $DIR/const-prop-read-static-in-const.rs:5:18 | ||
| | ||
LL | const TEST: u8 = MY_STATIC; | ||
| -----------------^^^^^^^^^- | ||
| | | ||
| constant accesses static | ||
| | ||
= note: `#[deny(const_err)]` on by default | ||
|
||
error: aborting due to previous error; 1 warning emitted | ||
warning: 1 warning emitted | ||
|
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.
I don't think we need a new error type here. There are tons of errors that can happen in const-eval, why is this one so special that it has to shop up here? There is no precedent for anything like this.
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.
This is not actually an error. This is a signal that we should turn this into a
ByRef
constant instead of trying to useScalar
orSlice
. At the very least it must be documented to explain this properly, but imo you an also just useOption
as the return type oftry_op_to_const