Skip to content

Taint the type of ill-formed (unsized) statics #144226

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

Merged
merged 6 commits into from
Jul 27, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion compiler/rustc_hir_analysis/src/check/check.rs
Original file line number Diff line number Diff line change
Expand Up @@ -767,7 +767,8 @@ pub(crate) fn check_item_type(tcx: TyCtxt<'_>, def_id: LocalDefId) -> Result<(),
DefKind::Static { .. } => {
check_static_inhabited(tcx, def_id);
check_static_linkage(tcx, def_id);
res = res.and(wfcheck::check_static_item(tcx, def_id));
let ty = tcx.type_of(def_id).instantiate_identity();
res = res.and(wfcheck::check_static_item(tcx, def_id, ty, true));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
res = res.and(wfcheck::check_static_item(tcx, def_id, ty, true));
res = res.and(wfcheck::check_static_item(tcx, def_id, ty, /* should_check_for_sync */ true));

}
DefKind::Const => res = res.and(wfcheck::check_const_item(tcx, def_id)),
_ => unreachable!(),
Expand Down
11 changes: 6 additions & 5 deletions compiler/rustc_hir_analysis/src/check/wfcheck.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1180,12 +1180,13 @@ fn check_item_fn(
}

#[instrument(level = "debug", skip(tcx))]
pub(super) fn check_static_item(
tcx: TyCtxt<'_>,
pub(crate) fn check_static_item<'tcx>(
tcx: TyCtxt<'tcx>,
item_id: LocalDefId,
ty: Ty<'tcx>,
should_check_for_sync: bool,
) -> Result<(), ErrorGuaranteed> {
enter_wf_checking_ctxt(tcx, item_id, |wfcx| {
let ty = tcx.type_of(item_id).instantiate_identity();
let span = tcx.ty_span(item_id);
let item_ty = wfcx.deeply_normalize(span, Some(WellFormedLoc::Ty(item_id)), ty);

Expand All @@ -1212,9 +1213,9 @@ pub(super) fn check_static_item(
}

// Ensure that the end result is `Sync` in a non-thread local `static`.
let should_check_for_sync = tcx.static_mutability(item_id.to_def_id())
== Some(hir::Mutability::Not)
let should_check_for_sync = should_check_for_sync
&& !is_foreign_item
&& tcx.static_mutability(item_id.to_def_id()) == Some(hir::Mutability::Not)
&& !tcx.is_thread_local_static(item_id.to_def_id());

if should_check_for_sync {
Expand Down
19 changes: 17 additions & 2 deletions compiler/rustc_hir_analysis/src/collect/type_of.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ use rustc_middle::{bug, span_bug};
use rustc_span::{DUMMY_SP, Ident, Span};

use super::{HirPlaceholderCollector, ItemCtxt, bad_placeholder};
use crate::check::wfcheck::check_static_item;
use crate::errors::TypeofReservedKeywordUsed;
use crate::hir_ty_lowering::HirTyLowerer;

Expand Down Expand Up @@ -217,7 +218,13 @@ pub(super) fn type_of(tcx: TyCtxt<'_>, def_id: LocalDefId) -> ty::EarlyBinder<'_
"static variable",
)
} else {
icx.lower_ty(ty)
let ty = icx.lower_ty(ty);
// MIR relies on references to statics being scalars.
// Verify that here to avoid ill-formed MIR.
match check_static_item(tcx, def_id, ty, false) {
Comment on lines +223 to +224
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// Verify that here to avoid ill-formed MIR.
match check_static_item(tcx, def_id, ty, false) {
// Verify that here to avoid ill-formed MIR.
// We skip the `Sync` check to avoid cycles for type-alias-impl-trait,
// relying on the fact that non-Sync statics don't ICE the rest of the compiler.
match check_static_item(tcx, def_id, ty, /* should_check_for_sync */ false) {

Ok(()) => ty,
Err(guar) => Ty::new_error(tcx, guar),
}
}
}
ItemKind::Const(ident, _, ty, body_id) => {
Expand Down Expand Up @@ -275,7 +282,15 @@ pub(super) fn type_of(tcx: TyCtxt<'_>, def_id: LocalDefId) -> ty::EarlyBinder<'_
let args = ty::GenericArgs::identity_for_item(tcx, def_id);
Ty::new_fn_def(tcx, def_id.to_def_id(), args)
}
ForeignItemKind::Static(t, _, _) => icx.lower_ty(t),
ForeignItemKind::Static(ty, _, _) => {
let ty = icx.lower_ty(ty);
// MIR relies on references to statics being scalars.
// Verify that here to avoid ill-formed MIR.
match check_static_item(tcx, def_id, ty, false) {
Comment on lines +288 to +289
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// Verify that here to avoid ill-formed MIR.
match check_static_item(tcx, def_id, ty, false) {
// Verify that here to avoid ill-formed MIR.
// We skip the `Sync` check to avoid cycles for type-alias-impl-trait,
// relying on the fact that non-Sync statics don't ICE the rest of the compiler.
match check_static_item(tcx, def_id, ty, /* should_check_for_sync */ false) {

Ok(()) => ty,
Err(guar) => Ty::new_error(tcx, guar),
}
}
ForeignItemKind::Type => Ty::new_foreign(tcx, def_id.to_def_id()),
},

Expand Down
9 changes: 0 additions & 9 deletions tests/crashes/121176.rs

This file was deleted.

10 changes: 0 additions & 10 deletions tests/crashes/129109.rs

This file was deleted.

9 changes: 0 additions & 9 deletions tests/crashes/130970.rs

This file was deleted.

9 changes: 0 additions & 9 deletions tests/crashes/131347.rs

This file was deleted.

4 changes: 0 additions & 4 deletions tests/ui/consts/const-unsized.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,10 @@ const CONST_FOO: str = *"foo";

static STATIC_1: dyn Debug + Sync = *(&1 as &(dyn Debug + Sync));
//~^ ERROR the size for values of type
//~| ERROR cannot move out of a shared reference

static STATIC_BAR: str = *"bar";
//~^ ERROR the size for values of type
//~| ERROR cannot move out of a shared reference

fn main() {
println!("{:?} {:?} {:?} {:?}", &CONST_0, &CONST_FOO, &STATIC_1, &STATIC_BAR);
//~^ ERROR: cannot move a value of type `str`
//~| ERROR: cannot move a value of type `dyn Debug + Sync`
}
32 changes: 4 additions & 28 deletions tests/ui/consts/const-unsized.stderr
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ LL | static STATIC_1: dyn Debug + Sync = *(&1 as &(dyn Debug + Sync));
= note: statics and constants must have a statically known size

error[E0277]: the size for values of type `str` cannot be known at compilation time
--> $DIR/const-unsized.rs:15:1
--> $DIR/const-unsized.rs:14:1
|
LL | static STATIC_BAR: str = *"bar";
| ^^^^^^^^^^^^^^^^^^^^^^ doesn't have a size known at compile-time
Expand All @@ -46,31 +46,7 @@ error[E0507]: cannot move out of a shared reference
LL | const CONST_FOO: str = *"foo";
| ^^^^^^ move occurs because value has type `str`, which does not implement the `Copy` trait

error[E0507]: cannot move out of a shared reference
--> $DIR/const-unsized.rs:11:37
|
LL | static STATIC_1: dyn Debug + Sync = *(&1 as &(dyn Debug + Sync));
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ move occurs because value has type `dyn Debug + Sync`, which does not implement the `Copy` trait

error[E0507]: cannot move out of a shared reference
--> $DIR/const-unsized.rs:15:26
|
LL | static STATIC_BAR: str = *"bar";
| ^^^^^^ move occurs because value has type `str`, which does not implement the `Copy` trait

error[E0161]: cannot move a value of type `dyn Debug + Sync`
--> $DIR/const-unsized.rs:20:38
|
LL | println!("{:?} {:?} {:?} {:?}", &CONST_0, &CONST_FOO, &STATIC_1, &STATIC_BAR);
| ^^^^^^^ the size of `dyn Debug + Sync` cannot be statically determined

error[E0161]: cannot move a value of type `str`
--> $DIR/const-unsized.rs:20:48
|
LL | println!("{:?} {:?} {:?} {:?}", &CONST_0, &CONST_FOO, &STATIC_1, &STATIC_BAR);
| ^^^^^^^^^ the size of `str` cannot be statically determined

error: aborting due to 10 previous errors
error: aborting due to 6 previous errors

Some errors have detailed explanations: E0161, E0277, E0507.
For more information about an error, try `rustc --explain E0161`.
Some errors have detailed explanations: E0277, E0507.
For more information about an error, try `rustc --explain E0277`.
1 change: 1 addition & 0 deletions tests/ui/coroutine/layout-error.rs
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@oli-obk is it okay for this to cycle-error? Having the type of a static depend on its value does seem quite cursed.^^

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is a static's type meaningfully different from a function's return type? Because it's not about its evaluated value, but just how opaque types work.

I don't think this use case is particularly unusual and expect people will want to be using TAITs like this.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is a static's type meaningfully different from a function's return type?

Hm... not really, the problem is just that the constraint on the type cannot be so easily expressed as a trait query. For return types it's T: Sized, but for static types it's... T: Pointee<Metadata = ()> or so?

Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ impl<F: Future> Task<F> {
}

pub type F = impl Future;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
pub type F = impl Future;
pub type F = impl Future + Sync;

Needing an extra annotation for an opaque type in a static item seems fine to me


#[define_opaque(F)]
fn foo()
where
Expand Down
2 changes: 1 addition & 1 deletion tests/ui/coroutine/layout-error.stderr
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
error[E0425]: cannot find value `Foo` in this scope
--> $DIR/layout-error.rs:26:17
--> $DIR/layout-error.rs:27:17
|
LL | let a = Foo;
| ^^^ not found in this scope
Expand Down
1 change: 0 additions & 1 deletion tests/ui/dyn-compatibility/taint-const-eval.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,5 @@ trait Qux {

static FOO: &(dyn Qux + Sync) = "desc";
//~^ ERROR the trait `Qux` is not dyn compatible
//~| ERROR the trait `Qux` is not dyn compatible

fn main() {}
26 changes: 1 addition & 25 deletions tests/ui/dyn-compatibility/taint-const-eval.stderr
Original file line number Diff line number Diff line change
Expand Up @@ -21,30 +21,6 @@ help: alternatively, consider constraining `bar` so it does not apply to trait o
LL | fn bar() where Self: Sized;
| +++++++++++++++++

error[E0038]: the trait `Qux` is not dyn compatible
--> $DIR/taint-const-eval.rs:7:15
|
LL | static FOO: &(dyn Qux + Sync) = "desc";
| ^^^^^^^^^^^^^^ `Qux` is not dyn compatible
|
note: for a trait to be dyn compatible it needs to allow building a vtable
for more information, visit <https://doc.rust-lang.org/reference/items/traits.html#dyn-compatibility>
--> $DIR/taint-const-eval.rs:4:8
|
LL | trait Qux {
| --- this trait is not dyn compatible...
LL | fn bar();
| ^^^ ...because associated function `bar` has no `self` parameter
= note: duplicate diagnostic emitted due to `-Z deduplicate-diagnostics=no`
help: consider turning `bar` into a method by giving it a `&self` argument
|
LL | fn bar(&self);
| +++++
help: alternatively, consider constraining `bar` so it does not apply to trait objects
|
LL | fn bar() where Self: Sized;
| +++++++++++++++++

error: aborting due to 2 previous errors
error: aborting due to 1 previous error

For more information about this error, try `rustc --explain E0038`.
1 change: 0 additions & 1 deletion tests/ui/extern/issue-36122-accessing-externed-dst.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,4 @@ fn main() {
static symbol: [usize]; //~ ERROR: the size for values of type
}
println!("{}", symbol[0]);
//~^ ERROR: extern static is unsafe
}
13 changes: 2 additions & 11 deletions tests/ui/extern/issue-36122-accessing-externed-dst.stderr
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,6 @@ LL | static symbol: [usize];
= help: the trait `Sized` is not implemented for `[usize]`
= note: statics and constants must have a statically known size

error[E0133]: use of extern static is unsafe and requires unsafe function or block
--> $DIR/issue-36122-accessing-externed-dst.rs:5:20
|
LL | println!("{}", symbol[0]);
| ^^^^^^ use of extern static
|
= note: extern statics are not controlled by the Rust type system: invalid data, aliasing violations or data races will cause undefined behavior

error: aborting due to 2 previous errors
error: aborting due to 1 previous error

Some errors have detailed explanations: E0133, E0277.
For more information about an error, try `rustc --explain E0133`.
For more information about this error, try `rustc --explain E0277`.
2 changes: 0 additions & 2 deletions tests/ui/layout/issue-84108.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,5 +14,3 @@ const BAR: (&Path, [u8], usize) = ("hello", [], 42);

static BAZ: ([u8], usize) = ([], 0);
//~^ ERROR the size for values of type `[u8]` cannot be known at compilation time
//~| ERROR the size for values of type `[u8]` cannot be known at compilation time
//~| ERROR mismatched types
21 changes: 1 addition & 20 deletions tests/ui/layout/issue-84108.stderr
Original file line number Diff line number Diff line change
Expand Up @@ -57,26 +57,7 @@ LL | const BAR: (&Path, [u8], usize) = ("hello", [], 42);
= note: expected slice `[u8]`
found array `[_; 0]`

error[E0277]: the size for values of type `[u8]` cannot be known at compilation time
--> $DIR/issue-84108.rs:15:13
|
LL | static BAZ: ([u8], usize) = ([], 0);
| ^^^^^^^^^^^^^ doesn't have a size known at compile-time
|
= help: the trait `Sized` is not implemented for `[u8]`
= note: only the last element of a tuple may have a dynamically sized type
= note: duplicate diagnostic emitted due to `-Z deduplicate-diagnostics=no`

error[E0308]: mismatched types
--> $DIR/issue-84108.rs:15:30
|
LL | static BAZ: ([u8], usize) = ([], 0);
| ^^ expected `[u8]`, found `[_; 0]`
|
= note: expected slice `[u8]`
found array `[_; 0]`

error: aborting due to 8 previous errors
error: aborting due to 6 previous errors

Some errors have detailed explanations: E0277, E0308, E0412.
For more information about an error, try `rustc --explain E0277`.
14 changes: 14 additions & 0 deletions tests/ui/mir/static-by-value-dyn.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
//! Regression test for #121176
//! KnownPanicsLint used to assert ABI compatibility in the interpreter,
//! which ICEs with unsized statics.
//@ needs-rustc-debug-assertions

use std::fmt::Debug;

static STATIC_1: dyn Debug + Sync = *();
Copy link
Member

@RalfJung RalfJung Jul 20, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For instance, this is a clearly ill-formed static. Nothing should ever look at its MIR. Trying to make the MIR interpreter APIs resistant against bogus MIR is a pointless game of whack-a-mole.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yea those cases are straight forward to prevent within type_of, we don't even need to compute the layout, just doing a sizedness check.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That doesn't quite work since we allow extern statics that have extern types, which are unsized.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well, we can check the tail manually for slices and dyn trait

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But that requires unfolding the type which will trigger the same cycle error, won't it?

//~^ ERROR the size for values of type `(dyn Debug + Sync + 'static)` cannot be known
//~| ERROR type `()` cannot be dereferenced

fn main() {
println!("{:?}", &STATIC_1);
}
19 changes: 19 additions & 0 deletions tests/ui/mir/static-by-value-dyn.stderr
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
error[E0277]: the size for values of type `(dyn Debug + Sync + 'static)` cannot be known at compilation time
--> $DIR/static-by-value-dyn.rs:8:1
|
LL | static STATIC_1: dyn Debug + Sync = *();
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ doesn't have a size known at compile-time
|
= help: the trait `Sized` is not implemented for `(dyn Debug + Sync + 'static)`
= note: statics and constants must have a statically known size

error[E0614]: type `()` cannot be dereferenced
--> $DIR/static-by-value-dyn.rs:8:37
|
LL | static STATIC_1: dyn Debug + Sync = *();
| ^^^ can't be dereferenced

error: aborting due to 2 previous errors

Some errors have detailed explanations: E0277, E0614.
For more information about an error, try `rustc --explain E0277`.
10 changes: 10 additions & 0 deletions tests/ui/mir/static-by-value-slice.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
//! Regression test for #140332
//! KnownPanicsLint used to assert ABI compatibility in the interpreter,
//! which ICEs with unsized statics.
static mut S: [i8] = ["Some thing"; 1];
//~^ ERROR the size for values of type `[i8]` cannot be known

fn main() {
assert_eq!(S, [0; 1]);
}
12 changes: 12 additions & 0 deletions tests/ui/mir/static-by-value-slice.stderr
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
error[E0277]: the size for values of type `[i8]` cannot be known at compilation time
--> $DIR/static-by-value-slice.rs:5:1
|
LL | static mut S: [i8] = ["Some thing"; 1];
| ^^^^^^^^^^^^^^^^^^ doesn't have a size known at compile-time
|
= help: the trait `Sized` is not implemented for `[i8]`
= note: statics and constants must have a statically known size

error: aborting due to 1 previous error

For more information about this error, try `rustc --explain E0277`.
15 changes: 15 additions & 0 deletions tests/ui/mir/static-by-value-str.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
//! Regression test for #139872
//! KnownPanicsLint used to assert ABI compatibility in the interpreter,
//! which ICEs with unsized statics.
enum E {
V16(u16),
V32(u32),
}

static C: (E, u16, str) = (E::V16(0xDEAD), 0x600D, 0xBAD);
//~^ ERROR the size for values of type `str` cannot be known

pub fn main() {
let (_, n, _) = C;
}
13 changes: 13 additions & 0 deletions tests/ui/mir/static-by-value-str.stderr
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
error[E0277]: the size for values of type `str` cannot be known at compilation time
--> $DIR/static-by-value-str.rs:10:1
|
LL | static C: (E, u16, str) = (E::V16(0xDEAD), 0x600D, 0xBAD);
| ^^^^^^^^^^^^^^^^^^^^^^^ doesn't have a size known at compile-time
|
= help: within `(E, u16, str)`, the trait `Sized` is not implemented for `str`
= note: required because it appears within the type `(E, u16, str)`
= note: statics and constants must have a statically known size

error: aborting due to 1 previous error

For more information about this error, try `rustc --explain E0277`.
13 changes: 13 additions & 0 deletions tests/ui/mir/unsized-extern-static.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
//! Regression test for #129109
//! MIR building used to produce erroneous constants when referring to statics of unsized type.
//@ compile-flags: -Zmir-enable-passes=+GVN -Zvalidate-mir

extern "C" {
pub static mut symbol: [i8];
//~^ ERROR the size for values of type `[i8]`
}

fn main() {
println!("C", unsafe { &symbol });
//~^ ERROR argument never used
}
Loading
Loading