-
-
Notifications
You must be signed in to change notification settings - Fork 14.2k
Prohibit cycles behind references while static initialization #149973
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
base: main
Are you sure you want to change the base?
Changes from all commits
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 |
|---|---|---|
|
|
@@ -861,6 +861,36 @@ impl<'tcx> interpret::Machine<'tcx> for CompileTimeMachine<'tcx> { | |
| interp_ok(()) | ||
| } | ||
|
|
||
| fn before_static_ref_eval( | ||
| tcx: TyCtxtAt<'tcx>, | ||
| machine: &Self, | ||
| ptr: Pointer<Self::Provenance>, | ||
| static_def_id: DefId, | ||
| ) -> InterpResult<'tcx> { | ||
| // We are only interested in immediate references to statics here. | ||
| // Indirect references should not be checked because: | ||
| // - they may be references to some other legitimate static reference | ||
| // (e.g. via a raw pointer), and | ||
| // - if they originate from an illegal static reference, that illegal | ||
| // reference must already appear in the body and will be checked there. | ||
| if ptr.provenance.shared_ref() { | ||
| return interp_ok(()); | ||
| } | ||
|
|
||
| // Check if this is the currently evaluated static. | ||
| if Some(static_def_id) == machine.static_root_ids.map(|(_, def_id)| def_id.into()) { | ||
| return Err(ConstEvalErrKind::RecursiveStatic).into(); | ||
| } | ||
|
|
||
| if !tcx.is_foreign_item(static_def_id) { | ||
| // Fire the query to detect cycles. We cannot restrict this to only when | ||
| // evaluating statics, since static reference cycles can also be formed | ||
| // through consts, especially promoted ones. | ||
|
Member
Author
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. Not skiping firing the query when static FOO: i32 = {
let x = &BAR;
42
};
const BAR: i32 = {
let x = &FOO;
42
};but I guess we might have to refute const cases as well because the following codes slightly modified from the original issue still make problems with it: enum Never {}
// &&X creates a promoted const
static X: &Never = weird(&&X);
const fn weird(a: &&&Never) -> &'static Never {
// SAFETY: our argument type has an unsatisfiable
// library invariant; therefore, this code is unreachable.
unsafe { std::hint::unreachable_unchecked() };
}// for privacy
mod mrow {
pub struct InBoundsIndex<const N: usize>(());
impl<const N: usize> InBoundsIndex<N> {
pub const fn new() -> Option<InBoundsIndex<N>> {
if N < 32 {
Some(Self(()))
} else {
None
}
}
}
}
use mrow::InBoundsIndex;
static IDX: InBoundsIndex<64> = {
FOO;
// THIS SHOULD PANIC!!! but we do UB first on the line above
InBoundsIndex::<64>::new().unwrap()
};
const FOO: () = {
let _x = index([0; 32], &IDX);
};
const fn index<const N: usize>(arr: [u8; 32], _: &InBoundsIndex<N>) -> u8 {
// SAFETY: InBoundsIndex can only be created by its new, which ensures N is < 32
unsafe { arr.as_ptr().add(N).read() }
} |
||
| tcx.eval_static_initializer(static_def_id)?; | ||
| } | ||
| interp_ok(()) | ||
| } | ||
|
|
||
| fn cached_union_data_range<'e>( | ||
| ecx: &'e mut InterpCx<'tcx, Self>, | ||
| ty: Ty<'tcx>, | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -3,11 +3,13 @@ | |
| //! The main entry point is the `step` method. | ||
| use std::iter; | ||
| use std::ops::Deref; | ||
|
|
||
| use either::Either; | ||
| use rustc_abi::{FIRST_VARIANT, FieldIdx}; | ||
| use rustc_data_structures::fx::FxHashSet; | ||
| use rustc_index::IndexSlice; | ||
| use rustc_middle::mir::interpret::{GlobalAlloc, Provenance, Scalar}; | ||
| use rustc_middle::ty::{self, Instance, Ty}; | ||
| use rustc_middle::{bug, mir, span_bug}; | ||
| use rustc_span::source_map::Spanned; | ||
|
|
@@ -217,9 +219,30 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> { | |
| } | ||
|
|
||
| Ref(_, borrow_kind, place) => { | ||
| let is_reborrow_of_ref = if let Some(local) = place.local_or_deref_local() | ||
| && place.is_indirect() | ||
| { | ||
| self.layout_of_local(self.frame(), local, None)?.ty.is_ref() | ||
| } else { | ||
| false | ||
| }; | ||
|
Member
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 looks like it will be Generally, I am not convinced by the implementation approach. This adds a very syntactic check to one specific operation that constructs references, but it's far from clear that this is enough. For instance, the code could take a raw pointer to a static, store it in a
Member
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 guess we'd say that the unsafe code needed to construct references this way is unsound... hm. Maybe that is fair but it needs good documentation. Also, what you implemented is somewhat different from what I sketched, not sure if that is deliberate. You seem to be trying to catch all In particular, your approach would seem to reject this: static X: &Never = weird(unsafe { &*&raw const X });I'm not sure that's a good idea. It's unprincipled: it's still possible to write unsafe code that evades your checks, so yo haven't gained any new guarantees. I would rather have a very clear line for which code gets accepted and which gets rejected.
Member
Author
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’m not a fan of this implementation either (I’ll elaborate a bit more in my next comment 😅). But everything else is exactly as you described - e.g., field projections - so I’ll try the other approach. |
||
|
|
||
| let src = self.eval_place(place)?; | ||
| let place = self.force_allocation(&src)?; | ||
| let val = ImmTy::from_immediate(place.to_ref(self), dest.layout); | ||
|
|
||
| // Check whether this forms a cycle involving a static reference. | ||
| // Ensure the place is not a reborrow from a raw pointer, since we do not want to | ||
| // forbid static reference cycles that go through raw pointers. | ||
| if is_reborrow_of_ref | ||
| && let Immediate::Scalar(Scalar::Ptr(ptr, _)) = val.deref() | ||
| && let Some(alloc_id) = ptr.provenance.get_alloc_id() | ||
| && let Some(GlobalAlloc::Static(def_id)) = | ||
| self.tcx.try_get_global_alloc(alloc_id) | ||
| { | ||
| M::before_static_ref_eval(self.tcx, &self.machine, *ptr, def_id)?; | ||
| } | ||
|
|
||
| // A fresh reference was created, make sure it gets retagged. | ||
| let val = M::retag_ptr_value( | ||
| self, | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,9 +1,8 @@ | ||
| //@ check-pass | ||
|
|
||
| struct S(pub &'static u32, pub u32); | ||
|
|
||
| const fn g(ss: &S) -> &u32 { &ss.1 } | ||
|
|
||
| static T: S = S(g(&T), 0); | ||
| //~^ ERROR: encountered static that tried to access itself during initialization | ||
|
|
||
| fn main () { } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,9 @@ | ||
| error[E0080]: encountered static that tried to access itself during initialization | ||
| --> $DIR/issue-47971.rs:5:19 | ||
| | | ||
| LL | static T: S = S(g(&T), 0); | ||
| | ^^ evaluation of `T` failed here | ||
|
|
||
| error: aborting due to 1 previous error | ||
|
|
||
| For more information about this error, try `rustc --explain E0080`. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,12 +1,11 @@ | ||
| //@ check-pass | ||
|
|
||
| struct Value { | ||
| values: &'static [&'static Value], | ||
| } | ||
|
|
||
| // This `static` recursively points to itself through a promoted (the slice). | ||
| static VALUE: Value = Value { | ||
| values: &[&VALUE], | ||
| //~^ ERROR: cycle detected when evaluating initializer of static `VALUE` | ||
| }; | ||
|
|
||
| fn main() {} |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,23 @@ | ||
| error[E0391]: cycle detected when evaluating initializer of static `VALUE` | ||
| --> $DIR/cycle-static-promoted.rs:7:13 | ||
| | | ||
| LL | values: &[&VALUE], | ||
| | ^^^^^^^^^ | ||
| | | ||
| note: ...which requires simplifying constant for the type system `VALUE::promoted[0]`... | ||
| --> $DIR/cycle-static-promoted.rs:6:1 | ||
| | | ||
| LL | static VALUE: Value = Value { | ||
| | ^^^^^^^^^^^^^^^^^^^ | ||
| note: ...which requires const-evaluating + checking `VALUE::promoted[0]`... | ||
| --> $DIR/cycle-static-promoted.rs:6:1 | ||
| | | ||
| LL | static VALUE: Value = Value { | ||
| | ^^^^^^^^^^^^^^^^^^^ | ||
| = note: ...which again requires evaluating initializer of static `VALUE`, completing the cycle | ||
| = note: cycle used when running analysis passes on crate `cycle_static_promoted` | ||
| = note: see https://rustc-dev-guide.rust-lang.org/overview.html#queries and https://rustc-dev-guide.rust-lang.org/query.html for more information | ||
|
|
||
| error: aborting due to 1 previous error | ||
|
|
||
| For more information about this error, try `rustc --explain E0391`. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,11 +1,10 @@ | ||
| //@ check-pass | ||
|
|
||
| struct Foo { | ||
| foo: Option<&'static Foo> | ||
| } | ||
|
|
||
| static FOO: Foo = Foo { | ||
| foo: Some(&FOO), | ||
| //~^ ERROR: encountered static that tried to access itself during initialization | ||
| }; | ||
|
|
||
| fn main() {} |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,9 @@ | ||
| error[E0080]: encountered static that tried to access itself during initialization | ||
| --> $DIR/static-cycle-error.rs:6:15 | ||
| | | ||
| LL | foo: Some(&FOO), | ||
| | ^^^^ evaluation of `FOO` failed here | ||
|
|
||
| error: aborting due to 1 previous error | ||
|
|
||
| For more information about this error, try `rustc --explain E0080`. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,25 +1,23 @@ | ||
| warning: creating a shared reference to mutable static | ||
| --> $DIR/static-recursive.rs:3:36 | ||
| error[E0391]: cycle detected when evaluating initializer of static `L1` | ||
| --> $DIR/static-recursive.rs:10:1 | ||
| | | ||
| LL | static mut S: *const u8 = unsafe { &S as *const *const u8 as *const u8 }; | ||
| | ^^ shared reference to mutable static | ||
| LL | static L1: StaticDoubleLinked = StaticDoubleLinked { prev: &L3, next: &L2, data: 1, head: true }; | ||
| | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | ||
| | | ||
| = note: for more information, see <https://doc.rust-lang.org/edition-guide/rust-2024/static-mut-references.html> | ||
| = note: shared references to mutable statics are dangerous; it's undefined behavior if the static is mutated or if a mutable reference is created for it while the shared reference lives | ||
| = note: `#[warn(static_mut_refs)]` (part of `#[warn(rust_2024_compatibility)]`) on by default | ||
| help: use `&raw const` instead to create a raw pointer | ||
| note: ...which requires evaluating initializer of static `L3`... | ||
| --> $DIR/static-recursive.rs:13:1 | ||
| | | ||
| LL | static mut S: *const u8 = unsafe { &raw const S as *const *const u8 as *const u8 }; | ||
| | +++++++++ | ||
|
|
||
| warning: creating a shared reference to mutable static | ||
| --> $DIR/static-recursive.rs:19:20 | ||
| | | ||
| LL | assert_eq!(S, *(S as *const *const u8)); | ||
| | ^ shared reference to mutable static | ||
| LL | static L3: StaticDoubleLinked = StaticDoubleLinked { prev: &L2, next: &L1, data: 3, head: false }; | ||
| | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | ||
| note: ...which requires evaluating initializer of static `L2`... | ||
| --> $DIR/static-recursive.rs:12:1 | ||
| | | ||
| = note: for more information, see <https://doc.rust-lang.org/edition-guide/rust-2024/static-mut-references.html> | ||
| = note: shared references to mutable statics are dangerous; it's undefined behavior if the static is mutated or if a mutable reference is created for it while the shared reference lives | ||
| LL | static L2: StaticDoubleLinked = StaticDoubleLinked { prev: &L1, next: &L3, data: 2, head: false }; | ||
| | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | ||
| = note: ...which again requires evaluating initializer of static `L1`, completing the cycle | ||
| = note: cycle used when running analysis passes on crate `static_recursive` | ||
| = note: see https://rustc-dev-guide.rust-lang.org/overview.html#queries and https://rustc-dev-guide.rust-lang.org/query.html for more information | ||
|
|
||
| warning: 2 warnings emitted | ||
| error: aborting due to 1 previous error | ||
|
|
||
| For more information about this error, try `rustc --explain E0391`. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,48 @@ | ||
| // Regression test for https://github.com/rust-lang/rust/issues/143047 | ||
|
|
||
| #[allow(static_mut_refs)] | ||
|
|
||
| static FOO: u8 = { | ||
| let x = &FOO; | ||
| //~^ ERROR: encountered static that tried to access itself during initialization | ||
| 0 | ||
| }; | ||
|
|
||
| static mut BAR: u8 = { | ||
| let x = unsafe { &mut BAR }; | ||
| //~^ ERROR: encountered static that tried to access itself during initialization | ||
| 0 | ||
| }; | ||
|
|
||
| static mut BAZ: u8 = { | ||
| let x: &u8 = unsafe { &*&raw const BAR }; | ||
| let y = &raw mut BAR; | ||
| let z: &mut u8 = unsafe { &mut *y }; | ||
| let w = &z; | ||
| let v = &w; | ||
| 0 | ||
| }; | ||
|
|
||
| static QUX: u8 = { | ||
| //~^ ERROR: cycle detected when evaluating initializer of static `QUX` | ||
| let x = &QUUX; | ||
| 0 | ||
| }; | ||
|
|
||
| static QUUX: u8 = { | ||
| let x = &QUUUX; | ||
| 0 | ||
| }; | ||
|
|
||
| static QUUUX: u8 = { | ||
| let x = &QUX; | ||
| 0 | ||
| }; | ||
|
|
||
| static PROMOTED: u8 = { | ||
| let x = &&&PROMOTED; | ||
| //~^ ERROR: cycle detected when evaluating initializer of static `PROMOTED` | ||
| 0 | ||
| }; | ||
|
|
||
| fn main() {} |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,56 @@ | ||
| error[E0080]: encountered static that tried to access itself during initialization | ||
| --> $DIR/static-ref-cycle-issue-143047.rs:6:13 | ||
| | | ||
| LL | let x = &FOO; | ||
| | ^^^^ evaluation of `FOO` failed here | ||
|
|
||
| error[E0080]: encountered static that tried to access itself during initialization | ||
| --> $DIR/static-ref-cycle-issue-143047.rs:12:22 | ||
| | | ||
| LL | let x = unsafe { &mut BAR }; | ||
| | ^^^^^^^^ evaluation of `BAR` failed here | ||
|
|
||
| error[E0391]: cycle detected when evaluating initializer of static `QUX` | ||
| --> $DIR/static-ref-cycle-issue-143047.rs:26:1 | ||
| | | ||
| LL | static QUX: u8 = { | ||
| | ^^^^^^^^^^^^^^ | ||
| | | ||
| note: ...which requires evaluating initializer of static `QUUX`... | ||
| --> $DIR/static-ref-cycle-issue-143047.rs:32:1 | ||
| | | ||
| LL | static QUUX: u8 = { | ||
| | ^^^^^^^^^^^^^^^ | ||
| note: ...which requires evaluating initializer of static `QUUUX`... | ||
| --> $DIR/static-ref-cycle-issue-143047.rs:37:1 | ||
| | | ||
| LL | static QUUUX: u8 = { | ||
| | ^^^^^^^^^^^^^^^^ | ||
| = note: ...which again requires evaluating initializer of static `QUX`, completing the cycle | ||
| = note: cycle used when running analysis passes on crate `static_ref_cycle_issue_143047` | ||
| = note: see https://rustc-dev-guide.rust-lang.org/overview.html#queries and https://rustc-dev-guide.rust-lang.org/query.html for more information | ||
|
|
||
| error[E0391]: cycle detected when evaluating initializer of static `PROMOTED` | ||
| --> $DIR/static-ref-cycle-issue-143047.rs:43:13 | ||
| | | ||
| LL | let x = &&&PROMOTED; | ||
| | ^^^^^^^^^^^ | ||
| | | ||
| note: ...which requires simplifying constant for the type system `PROMOTED::promoted[0]`... | ||
| --> $DIR/static-ref-cycle-issue-143047.rs:42:1 | ||
| | | ||
| LL | static PROMOTED: u8 = { | ||
| | ^^^^^^^^^^^^^^^^^^^ | ||
| note: ...which requires const-evaluating + checking `PROMOTED::promoted[0]`... | ||
| --> $DIR/static-ref-cycle-issue-143047.rs:42:1 | ||
| | | ||
| LL | static PROMOTED: u8 = { | ||
| | ^^^^^^^^^^^^^^^^^^^ | ||
| = note: ...which again requires evaluating initializer of static `PROMOTED`, completing the cycle | ||
| = note: cycle used when running analysis passes on crate `static_ref_cycle_issue_143047` | ||
| = note: see https://rustc-dev-guide.rust-lang.org/overview.html#queries and https://rustc-dev-guide.rust-lang.org/query.html for more information | ||
|
|
||
| error: aborting due to 4 previous errors | ||
|
|
||
| Some errors have detailed explanations: E0080, E0391. | ||
| For more information about an error, try `rustc --explain E0080`. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,28 @@ | ||
| // Regression test for https://github.com/rust-lang/rust/issues/143047 | ||
|
|
||
| mod mrow { | ||
| pub struct InBoundsIndex<const N: usize>(()); | ||
|
|
||
| impl<const N: usize> InBoundsIndex<N> { | ||
| pub const fn new() -> Option<InBoundsIndex<N>> { | ||
| if N < 32 { Some(Self(())) } else { None } | ||
| } | ||
| } | ||
| } | ||
|
|
||
| use mrow::InBoundsIndex; | ||
|
|
||
| static IDX: InBoundsIndex<64> = { | ||
| // The following line should cause a compilation error, otherwise it results in an | ||
| // undefined behavior. | ||
| index([0; 32], &IDX); | ||
| //~^ ERROR: encountered static that tried to access itself during initialization | ||
| InBoundsIndex::<64>::new().unwrap() | ||
| }; | ||
|
|
||
| const fn index<const N: usize>(arr: [u8; 32], _: &InBoundsIndex<N>) -> u8 { | ||
| // SAFETY: InBoundsIndex can only be created by its new, which ensures N is < 32 | ||
| unsafe { arr.as_ptr().add(N).read() } | ||
| } | ||
|
|
||
| fn main() {} |
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 quite understand what you mean by this. However, it all seems an artifact of trying to catch cases where the reference points to a static, rather than just the specific syntactic pattern
&STATIC.