Skip to content

Commit ab88c19

Browse files
authored
Rollup merge of #101061 - RalfJung:panic-on-uninit, r=oli-obk
panic-on-uninit: adjust checks to 0x01-filling Now that `mem::uninitiailized` actually fills memory with `0x01` (#99182), we can make it panic in a few less cases without risking a lot more UB -- which hopefully slightly improves compatibility with some old code, and which might increase the chance that we can check inside arrays in the future. We detect almost all of these with our lint, so authors of such code should still be warned -- but if this happens deep inside a dependency, the panic can be quite interruptive, so it might be better not to do it when there is no risk of LLVM UB. Therefore, adjust the `might_permit_raw_init` logic to care primarily about LLVM UB. To my knowledge, it actually covers all cases of LLVM UB now. Fixes #66151 Cc ``@5225225``
2 parents f8f5019 + a0131f0 commit ab88c19

File tree

10 files changed

+393
-240
lines changed

10 files changed

+393
-240
lines changed

compiler/rustc_const_eval/src/lib.rs

+2-4
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,6 @@ extern crate rustc_middle;
3232
pub mod const_eval;
3333
mod errors;
3434
pub mod interpret;
35-
mod might_permit_raw_init;
3635
pub mod transform;
3736
pub mod util;
3837

@@ -61,7 +60,6 @@ pub fn provide(providers: &mut Providers) {
6160
const_eval::deref_mir_constant(tcx, param_env, value)
6261
};
6362
providers.permits_uninit_init =
64-
|tcx, ty| might_permit_raw_init::might_permit_raw_init(tcx, ty, InitKind::Uninit);
65-
providers.permits_zero_init =
66-
|tcx, ty| might_permit_raw_init::might_permit_raw_init(tcx, ty, InitKind::Zero);
63+
|tcx, ty| util::might_permit_raw_init(tcx, ty, InitKind::UninitMitigated0x01Fill);
64+
providers.permits_zero_init = |tcx, ty| util::might_permit_raw_init(tcx, ty, InitKind::Zero);
6765
}

compiler/rustc_const_eval/src/might_permit_raw_init.rs

-44
This file was deleted.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,151 @@
1+
use rustc_middle::ty::layout::{LayoutCx, LayoutOf, TyAndLayout};
2+
use rustc_middle::ty::{ParamEnv, TyCtxt};
3+
use rustc_session::Limit;
4+
use rustc_target::abi::{Abi, FieldsShape, InitKind, Scalar, Variants};
5+
6+
use crate::const_eval::CompileTimeInterpreter;
7+
use crate::interpret::{InterpCx, MemoryKind, OpTy};
8+
9+
/// Determines if this type permits "raw" initialization by just transmuting some memory into an
10+
/// instance of `T`.
11+
///
12+
/// `init_kind` indicates if the memory is zero-initialized or left uninitialized. We assume
13+
/// uninitialized memory is mitigated by filling it with 0x01, which reduces the chance of causing
14+
/// LLVM UB.
15+
///
16+
/// By default we check whether that operation would cause *LLVM UB*, i.e., whether the LLVM IR we
17+
/// generate has UB or not. This is a mitigation strategy, which is why we are okay with accepting
18+
/// Rust UB as long as there is no risk of miscompilations. The `strict_init_checks` can be set to
19+
/// do a full check against Rust UB instead (in which case we will also ignore the 0x01-filling and
20+
/// to the full uninit check).
21+
pub fn might_permit_raw_init<'tcx>(
22+
tcx: TyCtxt<'tcx>,
23+
ty: TyAndLayout<'tcx>,
24+
kind: InitKind,
25+
) -> bool {
26+
if tcx.sess.opts.unstable_opts.strict_init_checks {
27+
might_permit_raw_init_strict(ty, tcx, kind)
28+
} else {
29+
let layout_cx = LayoutCx { tcx, param_env: ParamEnv::reveal_all() };
30+
might_permit_raw_init_lax(ty, &layout_cx, kind)
31+
}
32+
}
33+
34+
/// Implements the 'strict' version of the `might_permit_raw_init` checks; see that function for
35+
/// details.
36+
fn might_permit_raw_init_strict<'tcx>(
37+
ty: TyAndLayout<'tcx>,
38+
tcx: TyCtxt<'tcx>,
39+
kind: InitKind,
40+
) -> bool {
41+
let machine = CompileTimeInterpreter::new(
42+
Limit::new(0),
43+
/*can_access_statics:*/ false,
44+
/*check_alignment:*/ true,
45+
);
46+
47+
let mut cx = InterpCx::new(tcx, rustc_span::DUMMY_SP, ParamEnv::reveal_all(), machine);
48+
49+
let allocated = cx
50+
.allocate(ty, MemoryKind::Machine(crate::const_eval::MemoryKind::Heap))
51+
.expect("OOM: failed to allocate for uninit check");
52+
53+
if kind == InitKind::Zero {
54+
cx.write_bytes_ptr(
55+
allocated.ptr,
56+
std::iter::repeat(0_u8).take(ty.layout.size().bytes_usize()),
57+
)
58+
.expect("failed to write bytes for zero valid check");
59+
}
60+
61+
let ot: OpTy<'_, _> = allocated.into();
62+
63+
// Assume that if it failed, it's a validation failure.
64+
// This does *not* actually check that references are dereferenceable, but since all types that
65+
// require dereferenceability also require non-null, we don't actually get any false negatives
66+
// due to this.
67+
cx.validate_operand(&ot).is_ok()
68+
}
69+
70+
/// Implements the 'lax' (default) version of the `might_permit_raw_init` checks; see that function for
71+
/// details.
72+
fn might_permit_raw_init_lax<'tcx>(
73+
this: TyAndLayout<'tcx>,
74+
cx: &LayoutCx<'tcx, TyCtxt<'tcx>>,
75+
init_kind: InitKind,
76+
) -> bool {
77+
let scalar_allows_raw_init = move |s: Scalar| -> bool {
78+
match init_kind {
79+
InitKind::Zero => {
80+
// The range must contain 0.
81+
s.valid_range(cx).contains(0)
82+
}
83+
InitKind::UninitMitigated0x01Fill => {
84+
// The range must include an 0x01-filled buffer.
85+
let mut val: u128 = 0x01;
86+
for _ in 1..s.size(cx).bytes() {
87+
// For sizes >1, repeat the 0x01.
88+
val = (val << 8) | 0x01;
89+
}
90+
s.valid_range(cx).contains(val)
91+
}
92+
}
93+
};
94+
95+
// Check the ABI.
96+
let valid = match this.abi {
97+
Abi::Uninhabited => false, // definitely UB
98+
Abi::Scalar(s) => scalar_allows_raw_init(s),
99+
Abi::ScalarPair(s1, s2) => scalar_allows_raw_init(s1) && scalar_allows_raw_init(s2),
100+
Abi::Vector { element: s, count } => count == 0 || scalar_allows_raw_init(s),
101+
Abi::Aggregate { .. } => true, // Fields are checked below.
102+
};
103+
if !valid {
104+
// This is definitely not okay.
105+
return false;
106+
}
107+
108+
// Special magic check for references and boxes (i.e., special pointer types).
109+
if let Some(pointee) = this.ty.builtin_deref(false) {
110+
let pointee = cx.layout_of(pointee.ty).expect("need to be able to compute layouts");
111+
// We need to ensure that the LLVM attributes `aligned` and `dereferenceable(size)` are satisfied.
112+
if pointee.align.abi.bytes() > 1 {
113+
// 0x01-filling is not aligned.
114+
return false;
115+
}
116+
if pointee.size.bytes() > 0 {
117+
// A 'fake' integer pointer is not sufficiently dereferenceable.
118+
return false;
119+
}
120+
}
121+
122+
// If we have not found an error yet, we need to recursively descend into fields.
123+
match &this.fields {
124+
FieldsShape::Primitive | FieldsShape::Union { .. } => {}
125+
FieldsShape::Array { .. } => {
126+
// Arrays never have scalar layout in LLVM, so if the array is not actually
127+
// accessed, there is no LLVM UB -- therefore we can skip this.
128+
}
129+
FieldsShape::Arbitrary { offsets, .. } => {
130+
for idx in 0..offsets.len() {
131+
if !might_permit_raw_init_lax(this.field(cx, idx), cx, init_kind) {
132+
// We found a field that is unhappy with this kind of initialization.
133+
return false;
134+
}
135+
}
136+
}
137+
}
138+
139+
match &this.variants {
140+
Variants::Single { .. } => {
141+
// All fields of this single variant have already been checked above, there is nothing
142+
// else to do.
143+
}
144+
Variants::Multiple { .. } => {
145+
// We cannot tell LLVM anything about the details of this multi-variant layout, so
146+
// invalid values "hidden" inside the variant cannot cause LLVM trouble.
147+
}
148+
}
149+
150+
true
151+
}

compiler/rustc_const_eval/src/util/mod.rs

+2
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,10 @@ mod alignment;
33
mod call_kind;
44
pub mod collect_writes;
55
mod find_self_call;
6+
mod might_permit_raw_init;
67

78
pub use self::aggregate::expand_aggregate;
89
pub use self::alignment::is_disaligned;
910
pub use self::call_kind::{call_kind, CallDesugaringKind, CallKind};
1011
pub use self::find_self_call::find_self_call;
12+
pub use self::might_permit_raw_init::might_permit_raw_init;

compiler/rustc_target/src/abi/mod.rs

+1-69
Original file line numberDiff line numberDiff line change
@@ -1392,7 +1392,7 @@ pub struct PointeeInfo {
13921392
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
13931393
pub enum InitKind {
13941394
Zero,
1395-
Uninit,
1395+
UninitMitigated0x01Fill,
13961396
}
13971397

13981398
/// Trait that needs to be implemented by the higher-level type representation
@@ -1498,72 +1498,4 @@ impl<'a, Ty> TyAndLayout<'a, Ty> {
14981498
Abi::Aggregate { sized } => sized && self.size.bytes() == 0,
14991499
}
15001500
}
1501-
1502-
/// Determines if this type permits "raw" initialization by just transmuting some
1503-
/// memory into an instance of `T`.
1504-
///
1505-
/// `init_kind` indicates if the memory is zero-initialized or left uninitialized.
1506-
///
1507-
/// This code is intentionally conservative, and will not detect
1508-
/// * zero init of an enum whose 0 variant does not allow zero initialization
1509-
/// * making uninitialized types who have a full valid range (ints, floats, raw pointers)
1510-
/// * Any form of invalid value being made inside an array (unless the value is uninhabited)
1511-
///
1512-
/// A strict form of these checks that uses const evaluation exists in
1513-
/// `rustc_const_eval::might_permit_raw_init`, and a tracking issue for making these checks
1514-
/// stricter is <https://github.com/rust-lang/rust/issues/66151>.
1515-
///
1516-
/// FIXME: Once all the conservatism is removed from here, and the checks are ran by default,
1517-
/// we can use the const evaluation checks always instead.
1518-
pub fn might_permit_raw_init<C>(self, cx: &C, init_kind: InitKind) -> bool
1519-
where
1520-
Self: Copy,
1521-
Ty: TyAbiInterface<'a, C>,
1522-
C: HasDataLayout,
1523-
{
1524-
let scalar_allows_raw_init = move |s: Scalar| -> bool {
1525-
match init_kind {
1526-
InitKind::Zero => {
1527-
// The range must contain 0.
1528-
s.valid_range(cx).contains(0)
1529-
}
1530-
InitKind::Uninit => {
1531-
// The range must include all values.
1532-
s.is_always_valid(cx)
1533-
}
1534-
}
1535-
};
1536-
1537-
// Check the ABI.
1538-
let valid = match self.abi {
1539-
Abi::Uninhabited => false, // definitely UB
1540-
Abi::Scalar(s) => scalar_allows_raw_init(s),
1541-
Abi::ScalarPair(s1, s2) => scalar_allows_raw_init(s1) && scalar_allows_raw_init(s2),
1542-
Abi::Vector { element: s, count } => count == 0 || scalar_allows_raw_init(s),
1543-
Abi::Aggregate { .. } => true, // Fields are checked below.
1544-
};
1545-
if !valid {
1546-
// This is definitely not okay.
1547-
return false;
1548-
}
1549-
1550-
// If we have not found an error yet, we need to recursively descend into fields.
1551-
match &self.fields {
1552-
FieldsShape::Primitive | FieldsShape::Union { .. } => {}
1553-
FieldsShape::Array { .. } => {
1554-
// FIXME(#66151): For now, we are conservative and do not check arrays by default.
1555-
}
1556-
FieldsShape::Arbitrary { offsets, .. } => {
1557-
for idx in 0..offsets.len() {
1558-
if !self.field(cx, idx).might_permit_raw_init(cx, init_kind) {
1559-
// We found a field that is unhappy with this kind of initialization.
1560-
return false;
1561-
}
1562-
}
1563-
}
1564-
}
1565-
1566-
// FIXME(#66151): For now, we are conservative and do not check `self.variants`.
1567-
true
1568-
}
15691501
}

src/test/ui/consts/assert-type-intrinsics.rs

+6-3
Original file line numberDiff line numberDiff line change
@@ -11,12 +11,15 @@ fn main() {
1111
use std::mem::MaybeUninit;
1212

1313
const _BAD1: () = unsafe {
14-
MaybeUninit::<!>::uninit().assume_init();
14+
intrinsics::assert_inhabited::<!>(); //~ERROR: any use of this value will cause an error
15+
//~^WARN: previously accepted
1516
};
1617
const _BAD2: () = {
17-
intrinsics::assert_uninit_valid::<bool>();
18+
intrinsics::assert_uninit_valid::<!>(); //~ERROR: any use of this value will cause an error
19+
//~^WARN: previously accepted
1820
};
1921
const _BAD3: () = {
20-
intrinsics::assert_zero_valid::<&'static i32>();
22+
intrinsics::assert_zero_valid::<&'static i32>(); //~ERROR: any use of this value will cause an error
23+
//~^WARN: previously accepted
2124
};
2225
}

src/test/ui/consts/assert-type-intrinsics.stderr

+12-12
Original file line numberDiff line numberDiff line change
@@ -3,26 +3,26 @@ error: any use of this value will cause an error
33
|
44
LL | const _BAD1: () = unsafe {
55
| ---------------
6-
LL | MaybeUninit::<!>::uninit().assume_init();
7-
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ aborted execution: attempted to instantiate uninhabited type `!`
6+
LL | intrinsics::assert_inhabited::<!>();
7+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ aborted execution: attempted to instantiate uninhabited type `!`
88
|
99
= warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release!
1010
= note: for more information, see issue #71800 <https://github.com/rust-lang/rust/issues/71800>
1111
= note: `#[deny(const_err)]` on by default
1212

1313
error: any use of this value will cause an error
14-
--> $DIR/assert-type-intrinsics.rs:17:9
14+
--> $DIR/assert-type-intrinsics.rs:18:9
1515
|
1616
LL | const _BAD2: () = {
1717
| ---------------
18-
LL | intrinsics::assert_uninit_valid::<bool>();
19-
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ aborted execution: attempted to leave type `bool` uninitialized, which is invalid
18+
LL | intrinsics::assert_uninit_valid::<!>();
19+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ aborted execution: attempted to instantiate uninhabited type `!`
2020
|
2121
= warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release!
2222
= note: for more information, see issue #71800 <https://github.com/rust-lang/rust/issues/71800>
2323

2424
error: any use of this value will cause an error
25-
--> $DIR/assert-type-intrinsics.rs:20:9
25+
--> $DIR/assert-type-intrinsics.rs:22:9
2626
|
2727
LL | const _BAD3: () = {
2828
| ---------------
@@ -40,29 +40,29 @@ error: any use of this value will cause an error
4040
|
4141
LL | const _BAD1: () = unsafe {
4242
| ---------------
43-
LL | MaybeUninit::<!>::uninit().assume_init();
44-
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ aborted execution: attempted to instantiate uninhabited type `!`
43+
LL | intrinsics::assert_inhabited::<!>();
44+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ aborted execution: attempted to instantiate uninhabited type `!`
4545
|
4646
= warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release!
4747
= note: for more information, see issue #71800 <https://github.com/rust-lang/rust/issues/71800>
4848
= note: `#[deny(const_err)]` on by default
4949

5050
Future breakage diagnostic:
5151
error: any use of this value will cause an error
52-
--> $DIR/assert-type-intrinsics.rs:17:9
52+
--> $DIR/assert-type-intrinsics.rs:18:9
5353
|
5454
LL | const _BAD2: () = {
5555
| ---------------
56-
LL | intrinsics::assert_uninit_valid::<bool>();
57-
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ aborted execution: attempted to leave type `bool` uninitialized, which is invalid
56+
LL | intrinsics::assert_uninit_valid::<!>();
57+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ aborted execution: attempted to instantiate uninhabited type `!`
5858
|
5959
= warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release!
6060
= note: for more information, see issue #71800 <https://github.com/rust-lang/rust/issues/71800>
6161
= note: `#[deny(const_err)]` on by default
6262

6363
Future breakage diagnostic:
6464
error: any use of this value will cause an error
65-
--> $DIR/assert-type-intrinsics.rs:20:9
65+
--> $DIR/assert-type-intrinsics.rs:22:9
6666
|
6767
LL | const _BAD3: () = {
6868
| ---------------

0 commit comments

Comments
 (0)