Skip to content

Commit b9f4523

Browse files
committed
miri: implement more restrictive trivial-ABI checks
1 parent 887804d commit b9f4523

8 files changed

Lines changed: 165 additions & 22 deletions

File tree

compiler/rustc_const_eval/src/interpret/call.rs

Lines changed: 70 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -70,27 +70,75 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
7070
})
7171
}
7272

73-
/// Find the wrapped inner type of a transparent wrapper.
74-
/// Must not be called on 1-ZST (as they don't have a uniquely defined "wrapped field").
73+
/// Returns whether the given type has trivial ABI.
74+
fn has_trivial_abi(&self, layout: TyAndLayout<'tcx>) -> InterpResult<'tcx, bool> {
75+
if !layout.is_1zst() {
76+
return interp_ok(false);
77+
}
78+
match *layout.ty.kind() {
79+
ty::Array(elem, _len) => self.has_trivial_abi(self.layout_of(elem)?),
80+
ty::Tuple(..)
81+
| ty::Never
82+
| ty::FnDef(..)
83+
| ty::Closure(..)
84+
| ty::Coroutine(..)
85+
| ty::CoroutineClosure(..) => interp_ok(true),
86+
ty::Adt(adt_def, _args) => {
87+
if adt_def.repr().transparent() {
88+
// All fields must have trivial ABI.
89+
(0..layout.fields.count()).try_fold(true, |acc, idx| {
90+
interp_ok(acc && self.has_trivial_abi(layout.field(self, idx))?)
91+
})
92+
} else if adt_def.repr().c() {
93+
interp_ok(false)
94+
} else {
95+
// Must be repr(Rust).
96+
interp_ok(true)
97+
}
98+
}
99+
ty::Alias(..) => panic!("non-normalized type"),
100+
_ => interp_ok(false),
101+
}
102+
}
103+
104+
/// Find the wrapped inner type of a transparent wrapper by going for the unique
105+
/// non-trivial-ABI field.
75106
///
76107
/// We work with `TyAndLayout` here since that makes it much easier to iterate over all fields.
77108
fn unfold_transparent(
78109
&self,
79110
layout: TyAndLayout<'tcx>,
80111
may_unfold: impl Fn(AdtDef<'tcx>) -> bool,
81-
) -> TyAndLayout<'tcx> {
112+
) -> InterpResult<'tcx, TyAndLayout<'tcx>> {
82113
match layout.ty.kind() {
83114
ty::Adt(adt_def, _) if adt_def.repr().transparent() && may_unfold(*adt_def) => {
84115
assert_matches!(layout.variants, rustc_abi::Variants::Single { .. });
85-
// Find the non-1-ZST field, and recurse.
86-
let (_, field) = layout.non_1zst_field(self).unwrap();
116+
// Look for non-trivial-ABI field(s).
117+
let mut found = None;
118+
for idx in 0..layout.fields.count() {
119+
let field = layout.field(self, idx);
120+
if self.has_trivial_abi(field)? {
121+
continue;
122+
}
123+
// Found a non-trivial ABI field!
124+
if found.is_some() {
125+
// There is more than one such field.
126+
// FIXME: we should just panic here. But currently such repr(transparent)
127+
// types are still accepted. We just don't treat them as transparent.
128+
return interp_ok(layout);
129+
}
130+
found = Some(field);
131+
}
132+
let Some(field) = found else {
133+
// All fields have trivial ABI. That means this type is effectively `()`.
134+
return interp_ok(self.layout_of(self.tcx.types.unit)?);
135+
};
136+
// Recurse.
87137
self.unfold_transparent(field, may_unfold)
88138
}
89-
ty::Pat(base, _) => self.layout_of(*base).expect(
90-
"if the layout of a pattern type could be computed, so can the layout of its base",
91-
),
139+
ty::Pat(base, _) => interp_ok(self.layout_of(*base)?),
92140
// Not a transparent type, no further unfolding.
93-
_ => layout,
141+
_ => interp_ok(layout),
94142
}
95143
}
96144

@@ -145,7 +193,7 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
145193
let inner = self.unfold_transparent(inner, /* may_unfold */ |def| {
146194
// Stop at NPO types so that we don't miss that attribute in the check below!
147195
def.is_struct() && !is_npo(def)
148-
});
196+
})?;
149197
interp_ok(match inner.ty.kind() {
150198
ty::Ref(..) | ty::FnPtr(..) => {
151199
// Option<&T> behaves like &T, and same for fn()
@@ -154,7 +202,7 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
154202
ty::Adt(def, _) if is_npo(*def) => {
155203
// Once we found a `nonnull_optimization_guaranteed` type, further strip off
156204
// newtype structs from it to find the underlying ABI type.
157-
self.unfold_transparent(inner, /* may_unfold */ |def| def.is_struct())
205+
self.unfold_transparent(inner, /* may_unfold */ |def| def.is_struct())?
158206
}
159207
_ => {
160208
// Everything else we do not unfold.
@@ -175,16 +223,21 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
175223
if caller.ty == callee.ty {
176224
return interp_ok(true);
177225
}
178-
// 1-ZST are compatible with all 1-ZST (and with nothing else).
179-
if caller.is_1zst() || callee.is_1zst() {
180-
return interp_ok(caller.is_1zst() && callee.is_1zst());
226+
// Handle trivial-ABI types.
227+
if self.has_trivial_abi(caller)? && self.has_trivial_abi(callee)? {
228+
return interp_ok(true);
181229
}
182230
// Unfold newtypes and NPO optimizations.
183231
let unfold = |layout: TyAndLayout<'tcx>| {
184-
self.unfold_npo(self.unfold_transparent(layout, /* may_unfold */ |_def| true))
232+
self.unfold_transparent(layout, /* may_unfold */ |_def| true)
233+
.and_then(|f| self.unfold_npo(f))
185234
};
186235
let caller = unfold(caller)?;
187236
let callee = unfold(callee)?;
237+
// Not-quite-so-fast path: if the types are equal now, they are compatible.
238+
if caller.ty == callee.ty {
239+
return interp_ok(true);
240+
}
188241
// Now see if these inner types are compatible.
189242

190243
// Compatible pointer types. For thin pointers, we have to accept even non-`repr(transparent)`
@@ -240,8 +293,8 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
240293
return interp_ok(caller == callee);
241294
}
242295

243-
// Fall back to exact equality.
244-
interp_ok(caller == callee)
296+
// The rest is incompatible.
297+
interp_ok(false)
245298
}
246299

247300
/// Returns a `bool` saying whether the two arguments are ABI-compatible.
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
fn callee(_s: [u8; 0]) {}
2+
//~^ ERROR: type [u8; 0] passing argument of type ()
3+
4+
fn main() {
5+
let fnptr: fn([u8; 0]) = callee;
6+
let fnptr: fn(()) = unsafe { std::mem::transmute(fnptr) };
7+
fnptr(());
8+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
error: Undefined Behavior: calling a function whose parameter #1 has type [u8; 0] passing argument of type ()
2+
--> tests/fail/function_pointers/abi_mismatch_zst_array.rs:LL:CC
3+
|
4+
LL | fn callee(_s: [u8; 0]) {}
5+
| ^^ Undefined Behavior occurred here
6+
|
7+
= help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
8+
= help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
9+
= help: this means these two types are not *guaranteed* to be ABI-compatible across all targets
10+
= help: if you think this code should be accepted anyway, please report an issue with Miri
11+
= note: stack backtrace:
12+
0: callee
13+
at tests/fail/function_pointers/abi_mismatch_zst_array.rs:LL:CC
14+
1: main
15+
at tests/fail/function_pointers/abi_mismatch_zst_array.rs:LL:CC
16+
17+
note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace
18+
19+
error: aborting due to 1 previous error
20+
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
#[repr(C)]
2+
struct C;
3+
4+
fn callee() {}
5+
//~^ ERROR: return type () passing return place of type C
6+
7+
fn main() {
8+
let fnptr: fn() -> () = callee;
9+
let fnptr: fn() -> C = unsafe { std::mem::transmute(fnptr) };
10+
fnptr();
11+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
error: Undefined Behavior: calling a function with return type () passing return place of type C
2+
--> tests/fail/function_pointers/abi_mismatch_zst_repr_C.rs:LL:CC
3+
|
4+
LL | fn callee() {}
5+
| ^ Undefined Behavior occurred here
6+
|
7+
= help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
8+
= help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
9+
= help: this means these two types are not *guaranteed* to be ABI-compatible across all targets
10+
= help: if you think this code should be accepted anyway, please report an issue with Miri
11+
= note: stack backtrace:
12+
0: callee
13+
at tests/fail/function_pointers/abi_mismatch_zst_repr_C.rs:LL:CC
14+
1: main
15+
at tests/fail/function_pointers/abi_mismatch_zst_repr_C.rs:LL:CC
16+
17+
note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace
18+
19+
error: aborting due to 1 previous error
20+
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
#[repr(transparent)]
2+
struct Wrap([u8; 0]);
3+
4+
fn callee(_s: Wrap) {}
5+
//~^ ERROR: type Wrap passing argument of type ()
6+
7+
fn main() {
8+
let fnptr: fn(Wrap) = callee;
9+
let fnptr: fn(()) = unsafe { std::mem::transmute(fnptr) };
10+
fnptr(());
11+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
error: Undefined Behavior: calling a function whose parameter #1 has type Wrap passing argument of type ()
2+
--> tests/fail/function_pointers/abi_mismatch_zst_transparent_array.rs:LL:CC
3+
|
4+
LL | fn callee(_s: Wrap) {}
5+
| ^^ Undefined Behavior occurred here
6+
|
7+
= help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
8+
= help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
9+
= help: this means these two types are not *guaranteed* to be ABI-compatible across all targets
10+
= help: if you think this code should be accepted anyway, please report an issue with Miri
11+
= note: stack backtrace:
12+
0: callee
13+
at tests/fail/function_pointers/abi_mismatch_zst_transparent_array.rs:LL:CC
14+
1: main
15+
at tests/fail/function_pointers/abi_mismatch_zst_transparent_array.rs:LL:CC
16+
17+
note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace
18+
19+
error: aborting due to 1 previous error
20+

src/tools/miri/tests/pass/function_calls/abi_compat.rs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -62,19 +62,19 @@ fn test_abi_newtype<T: Copy + Default>() {
6262
struct Wrapper2a<T>((), T);
6363
#[repr(transparent)]
6464
#[derive(Copy, Clone)]
65-
struct Wrapper3<T>(Zst, T, [u8; 0]);
65+
struct Wrapper3<T>(Zst, T, [(); 0]);
6666
#[repr(transparent)]
6767
#[derive(Copy, Clone)]
6868
enum Wrapper4<T> {
69-
V(Zst, T, [u8; 0]),
69+
V(Zst, T, [(); 10]),
7070
}
7171

7272
let t = T::default();
7373
test_abi_compat(t, Wrapper(t));
7474
test_abi_compat(t, Wrapper2(t, ()));
7575
test_abi_compat(t, Wrapper2a((), t));
7676
test_abi_compat(t, Wrapper3(Zst, t, []));
77-
test_abi_compat(t, Wrapper4::V(Zst, t, []));
77+
test_abi_compat(t, Wrapper4::V(Zst, t, [(); _]));
7878
// MaybeUninit is `repr(transparent)`; that covers the `union` case.
7979
test_abi_compat(t, mem::MaybeUninit::new(t));
8080
}
@@ -100,8 +100,8 @@ fn main() {
100100
test_abi_compat(&0u32, &([true; 4], [0u32; 0]));
101101
// - `fn` types
102102
test_abi_compat(main as fn(), id::<i32> as fn(i32) -> i32);
103-
// - 1-ZST
104-
test_abi_compat((), [0u8; 0]);
103+
// - trivial-ABI types
104+
test_abi_compat((), [(); 0]);
105105

106106
// Guaranteed null-pointer-layout optimizations:
107107
// - Guaranteed Option<X> null-pointer-optimizations (RFC 3391).

0 commit comments

Comments
 (0)