Skip to content

Commit ac89e27

Browse files
committed
Auto merge of rust-lang#117351 - RalfJung:fn-ptr-sized-wf, r=<try>
wf: ensure that non-Rust-functions have sized arguments, and everyone has sized return types I do *not* know what I am doing, I have not touched this code before at all. We do get a bunch of extra diagnostics since we now check every occurrence of a fn ptr type for whether it makes some basic sense, whereas before we only complained when the fn ptr was being called. I think there's probably a way to now remove some of the sizedness check on the HIR side and rely on WF instead, but I couldn't figure it out. This fixes an ICE so maybe it's okay if the diagnostics aren't great from the start. This is a breaking change if anyone uses the type `fn() -> str` or `extern "C" fn(str)`, or similar bogus function types, anywhere. We should probably crater this. This does *not* reject the use of the type `fn(str)` on stable, (a) to reduce the amount of breakage, (b) since I don't know if WF-checking can depend on feature flags without causing havoc since a crate with less features might see types from a crate with more features that are suddenly not WF any more, and (c) since it's not required to ensure basic sanity of the ABI. This PR introduces an assertion in our ABI computation logic which checks that the computed ABI makes sense, and for `extern "C" fn(str)` there is no sensible ABI, and we *do* compute the ABI even for function pointers we never call, so we need to start rejecting that code. `fn(str)` has a sensible ABI, we just don't make it available on stable. Fixes rust-lang#115845
2 parents bbcc169 + 1d0b64e commit ac89e27

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

45 files changed

+471
-244
lines changed

Diff for: compiler/rustc_codegen_llvm/src/abi.rs

+3-40
Original file line numberDiff line numberDiff line change
@@ -348,50 +348,18 @@ impl<'ll, 'tcx> FnAbiLlvmExt<'ll, 'tcx> for FnAbi<'tcx, Ty<'tcx>> {
348348
PassMode::Direct(_) => {
349349
// ABI-compatible Rust types have the same `layout.abi` (up to validity ranges),
350350
// and for Scalar ABIs the LLVM type is fully determined by `layout.abi`,
351-
// guarnateeing that we generate ABI-compatible LLVM IR. Things get tricky for
352-
// aggregates...
353-
if matches!(arg.layout.abi, abi::Abi::Aggregate { .. }) {
354-
assert!(
355-
arg.layout.is_sized(),
356-
"`PassMode::Direct` for unsized type: {}",
357-
arg.layout.ty
358-
);
359-
// This really shouldn't happen, since `immediate_llvm_type` will use
360-
// `layout.fields` to turn this Rust type into an LLVM type. This means all
361-
// sorts of Rust type details leak into the ABI. However wasm sadly *does*
362-
// currently use this mode so we have to allow it -- but we absolutely
363-
// shouldn't let any more targets do that.
364-
// (Also see <https://github.com/rust-lang/rust/issues/115666>.)
365-
//
366-
// The unstable abi `PtxKernel` also uses Direct for now.
367-
// It needs to switch to something else before stabilization can happen.
368-
// (See issue: https://github.com/rust-lang/rust/issues/117271)
369-
assert!(
370-
matches!(&*cx.tcx.sess.target.arch, "wasm32" | "wasm64")
371-
|| self.conv == Conv::PtxKernel,
372-
"`PassMode::Direct` for aggregates only allowed on wasm and `extern \"ptx-kernel\"` fns\nProblematic type: {:#?}",
373-
arg.layout,
374-
);
375-
}
351+
// guarnateeing that we generate ABI-compatible LLVM IR.
376352
arg.layout.immediate_llvm_type(cx)
377353
}
378354
PassMode::Pair(..) => {
379355
// ABI-compatible Rust types have the same `layout.abi` (up to validity ranges),
380356
// so for ScalarPair we can easily be sure that we are generating ABI-compatible
381357
// LLVM IR.
382-
assert!(
383-
matches!(arg.layout.abi, abi::Abi::ScalarPair(..)),
384-
"PassMode::Pair for type {}",
385-
arg.layout.ty
386-
);
387358
llargument_tys.push(arg.layout.scalar_pair_element_llvm_type(cx, 0, true));
388359
llargument_tys.push(arg.layout.scalar_pair_element_llvm_type(cx, 1, true));
389360
continue;
390361
}
391-
PassMode::Indirect { attrs: _, meta_attrs: Some(_), on_stack } => {
392-
// `Indirect` with metadata is only for unsized types, and doesn't work with
393-
// on-stack passing.
394-
assert!(arg.layout.is_unsized() && !on_stack);
362+
PassMode::Indirect { attrs: _, meta_attrs: Some(_), on_stack: _ } => {
395363
// Construct the type of a (wide) pointer to `ty`, and pass its two fields.
396364
// Any two ABI-compatible unsized types have the same metadata type and
397365
// moreover the same metadata value leads to the same dynamic size and
@@ -402,13 +370,8 @@ impl<'ll, 'tcx> FnAbiLlvmExt<'ll, 'tcx> for FnAbi<'tcx, Ty<'tcx>> {
402370
llargument_tys.push(ptr_layout.scalar_pair_element_llvm_type(cx, 1, true));
403371
continue;
404372
}
405-
PassMode::Indirect { attrs: _, meta_attrs: None, on_stack: _ } => {
406-
assert!(arg.layout.is_sized());
407-
cx.type_ptr()
408-
}
373+
PassMode::Indirect { attrs: _, meta_attrs: None, on_stack: _ } => cx.type_ptr(),
409374
PassMode::Cast { cast, pad_i32 } => {
410-
// `Cast` means "transmute to `CastType`"; that only makes sense for sized types.
411-
assert!(arg.layout.is_sized());
412375
// add padding
413376
if *pad_i32 {
414377
llargument_tys.push(Reg::i32().llvm_type(cx));

Diff for: compiler/rustc_hir_analysis/src/check/wfcheck.rs

+1
Original file line numberDiff line numberDiff line change
@@ -1509,6 +1509,7 @@ fn check_fn_or_method<'tcx>(
15091509
let has_implicit_self = hir_decl.implicit_self != hir::ImplicitSelfKind::None;
15101510
let mut inputs = sig.inputs().iter().skip(if has_implicit_self { 1 } else { 0 });
15111511
// Check that the argument is a tuple and is sized
1512+
// FIXME: move this to WF check? Currently it is duplicated here and in `confirm_builtin_call` in callee.rs.
15121513
if let Some(ty) = inputs.next() {
15131514
wfcx.register_bound(
15141515
ObligationCause::new(span, wfcx.body_def_id, ObligationCauseCode::RustCall),

Diff for: compiler/rustc_hir_analysis/src/hir_wf_check.rs

+3
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,9 @@ pub fn provide(providers: &mut Providers) {
1313
*providers = Providers { diagnostic_hir_wf_check, ..*providers };
1414
}
1515

16+
/// HIR-based well-formedness check, for diagnostics only.
17+
/// This is run after there was a WF error, to try get a better message pointing out what went wrong
18+
/// here.
1619
// Ideally, this would be in `rustc_trait_selection`, but we
1720
// need access to `ItemCtxt`
1821
fn diagnostic_hir_wf_check<'tcx>(

Diff for: compiler/rustc_hir_typeck/src/callee.rs

+1
Original file line numberDiff line numberDiff line change
@@ -458,6 +458,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
458458
);
459459

460460
if fn_sig.abi == abi::Abi::RustCall {
461+
// FIXME: move this to WF check? Currently it is duplicated here and in `check_fn_or_method` in wfcheck.rs.
461462
let sp = arg_exprs.last().map_or(call_expr.span, |expr| expr.span);
462463
if let Some(ty) = fn_sig.inputs().last().copied() {
463464
self.register_bound(

Diff for: compiler/rustc_hir_typeck/src/check.rs

+4-1
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,10 @@ pub(super) fn check_fn<'a, 'tcx>(
9797
fcx.check_pat_top(&param.pat, param_ty, ty_span, None, None);
9898

9999
// Check that argument is Sized.
100-
if !params_can_be_unsized {
100+
// FIXME: can we share this (and the return type check below) with WF-checking on function
101+
// signatures? However, here we have much better spans available than if we fire an
102+
// obligation for our signature to be well-formed.
103+
if !params_can_be_unsized || !fn_sig.abi.supports_unsized_args() {
101104
fcx.require_type_is_sized(
102105
param_ty,
103106
param.pat.span,

Diff for: compiler/rustc_hir_typeck/src/expr.rs

+2
Original file line numberDiff line numberDiff line change
@@ -503,6 +503,8 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
503503
self.resolve_lang_item_path(lang_item, expr.span, expr.hir_id, hir_id).1
504504
}
505505

506+
/// Called for any way that a path is mentioned in an expression.
507+
/// If the path is used in a function call, `args` has the arguments, otherwise it is empty.
506508
pub(crate) fn check_expr_path(
507509
&self,
508510
qpath: &'tcx hir::QPath<'tcx>,

Diff for: compiler/rustc_target/src/abi/call/x86_64.rs

+4-6
Original file line numberDiff line numberDiff line change
@@ -153,9 +153,9 @@ fn reg_component(cls: &[Option<Class>], i: &mut usize, size: Size) -> Option<Reg
153153
}
154154
}
155155

156-
fn cast_target(cls: &[Option<Class>], size: Size) -> Option<CastTarget> {
156+
fn cast_target(cls: &[Option<Class>], size: Size) -> CastTarget {
157157
let mut i = 0;
158-
let lo = reg_component(cls, &mut i, size)?;
158+
let lo = reg_component(cls, &mut i, size).unwrap();
159159
let offset = Size::from_bytes(8) * (i as u64);
160160
let mut target = CastTarget::from(lo);
161161
if size > offset {
@@ -164,7 +164,7 @@ fn cast_target(cls: &[Option<Class>], size: Size) -> Option<CastTarget> {
164164
}
165165
}
166166
assert_eq!(reg_component(cls, &mut i, Size::ZERO), None);
167-
Some(target)
167+
target
168168
}
169169

170170
const MAX_INT_REGS: usize = 6; // RDI, RSI, RDX, RCX, R8, R9
@@ -227,9 +227,7 @@ where
227227
// split into sized chunks passed individually
228228
if arg.layout.is_aggregate() {
229229
let size = arg.layout.size;
230-
if let Some(cast_target) = cast_target(cls, size) {
231-
arg.cast_to(cast_target);
232-
}
230+
arg.cast_to(cast_target(cls, size));
233231
} else {
234232
arg.extend_integer_width_to(32);
235233
}

Diff for: compiler/rustc_target/src/spec/abi.rs

+9
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,15 @@ impl Abi {
8686
_ => false,
8787
}
8888
}
89+
90+
/// Whether this ABI can in principle support unsized arguments.
91+
/// There might be further restrictions such as nightly feature flags!
92+
pub fn supports_unsized_args(self) -> bool {
93+
match self {
94+
Self::Rust | Self::RustCall | Self::RustIntrinsic | Self::PlatformIntrinsic => true,
95+
_ => false,
96+
}
97+
}
8998
}
9099

91100
#[derive(Copy, Clone)]

Diff for: compiler/rustc_trait_selection/src/traits/error_reporting/suggestions.rs

+17-10
Original file line numberDiff line numberDiff line change
@@ -1863,7 +1863,10 @@ impl<'tcx> TypeErrCtxtExt<'tcx> for TypeErrCtxt<'_, 'tcx> {
18631863
);
18641864
}
18651865

1866-
let body = self.tcx.hir().body(self.tcx.hir().body_owned_by(obligation.cause.body_id));
1866+
let Some(body) = self.tcx.hir().maybe_body_owned_by(obligation.cause.body_id) else {
1867+
return false;
1868+
};
1869+
let body = self.tcx.hir().body(body);
18671870

18681871
let mut visitor = ReturnsVisitor::default();
18691872
visitor.visit_body(&body);
@@ -1922,15 +1925,19 @@ impl<'tcx> TypeErrCtxtExt<'tcx> for TypeErrCtxt<'_, 'tcx> {
19221925
// Point at all the `return`s in the function as they have failed trait bounds.
19231926
let mut visitor = ReturnsVisitor::default();
19241927
visitor.visit_body(&body);
1925-
let typeck_results = self.typeck_results.as_ref().unwrap();
1926-
for expr in &visitor.returns {
1927-
if let Some(returned_ty) = typeck_results.node_type_opt(expr.hir_id) {
1928-
let ty = self.resolve_vars_if_possible(returned_ty);
1929-
if ty.references_error() {
1930-
// don't print out the [type error] here
1931-
err.delay_as_bug();
1932-
} else {
1933-
err.span_label(expr.span, format!("this returned value is of type `{ty}`"));
1928+
if let Some(typeck_results) = self.typeck_results.as_ref() {
1929+
for expr in &visitor.returns {
1930+
if let Some(returned_ty) = typeck_results.node_type_opt(expr.hir_id) {
1931+
let ty = self.resolve_vars_if_possible(returned_ty);
1932+
if ty.references_error() {
1933+
// don't print out the [type error] here
1934+
err.delay_as_bug();
1935+
} else {
1936+
err.span_label(
1937+
expr.span,
1938+
format!("this returned value is of type `{ty}`"),
1939+
);
1940+
}
19341941
}
19351942
}
19361943
}

Diff for: compiler/rustc_trait_selection/src/traits/wf.rs

+20-3
Original file line numberDiff line numberDiff line change
@@ -727,9 +727,10 @@ impl<'a, 'tcx> WfPredicates<'a, 'tcx> {
727727
self.out.extend(obligations);
728728
}
729729

730-
ty::FnPtr(_) => {
731-
// let the loop iterate into the argument/return
732-
// types appearing in the fn signature
730+
ty::FnPtr(fn_sig) => {
731+
// The loop iterates into the argument/return types appearing in the fn
732+
// signature, but we need to do some extra checks.
733+
self.compute_fn_sig_obligations(fn_sig)
733734
}
734735

735736
ty::Alias(ty::Opaque, ty::AliasTy { def_id, args, .. }) => {
@@ -806,6 +807,22 @@ impl<'a, 'tcx> WfPredicates<'a, 'tcx> {
806807
}
807808
}
808809

810+
/// Add the obligations for this signature to be well-formed to `out`.
811+
fn compute_fn_sig_obligations(&mut self, sig: ty::PolyFnSig<'tcx>) {
812+
// The return type must always be sized.
813+
// FIXME(RalfJung): is skip_binder right? It's what the type walker used in `compute` also does.
814+
self.require_sized(sig.skip_binder().output(), traits::SizedReturnType);
815+
// For non-Rust ABIs, the argument type must always be sized.
816+
// FIXME(RalfJung): we don't do the Rust ABI check here, since that depends on feature gates
817+
// and it's not clear to me whether WF depending on feature gates (which can differ across
818+
// crates) is possible or not.
819+
if !sig.skip_binder().abi.supports_unsized_args() {
820+
for &arg in sig.skip_binder().inputs() {
821+
self.require_sized(arg, traits::SizedArgumentType(None));
822+
}
823+
}
824+
}
825+
809826
#[instrument(level = "debug", skip(self))]
810827
fn nominal_obligations(
811828
&mut self,

Diff for: compiler/rustc_ty_utils/src/abi.rs

+69
Original file line numberDiff line numberDiff line change
@@ -327,6 +327,74 @@ fn adjust_for_rust_scalar<'tcx>(
327327
}
328328
}
329329

330+
/// Ensure that the ABI makes basic sense.
331+
fn fn_abi_sanity_check<'tcx>(cx: &LayoutCx<'tcx, TyCtxt<'tcx>>, fn_abi: &FnAbi<'tcx, Ty<'tcx>>) {
332+
fn fn_arg_sanity_check<'tcx>(
333+
cx: &LayoutCx<'tcx, TyCtxt<'tcx>>,
334+
fn_abi: &FnAbi<'tcx, Ty<'tcx>>,
335+
arg: &ArgAbi<'tcx, Ty<'tcx>>,
336+
) {
337+
match &arg.mode {
338+
PassMode::Ignore => {}
339+
PassMode::Direct(_) => {
340+
// Here the Rust type is used to determine the actual ABI, so we have to be very
341+
// careful. Scalar/ScalarPair is fine, since backends will generally use
342+
// `layout.abi` and ignore everything else. We should just reject `Aggregate`
343+
// entirely here, but some targets need to be fixed first.
344+
if matches!(arg.layout.abi, Abi::Aggregate { .. }) {
345+
assert!(
346+
arg.layout.is_sized(),
347+
"`PassMode::Direct` for unsized type in ABI: {:#?}",
348+
fn_abi
349+
);
350+
// This really shouldn't happen, since `immediate_llvm_type` will use
351+
// `layout.fields` to turn this Rust type into an LLVM type. This means all
352+
// sorts of Rust type details leak into the ABI. However wasm sadly *does*
353+
// currently use this mode so we have to allow it -- but we absolutely
354+
// shouldn't let any more targets do that.
355+
// (Also see <https://github.com/rust-lang/rust/issues/115666>.)
356+
//
357+
// The unstable abi `PtxKernel` also uses Direct for now.
358+
// It needs to switch to something else before stabilization can happen.
359+
// (See issue: https://github.com/rust-lang/rust/issues/117271)
360+
assert!(
361+
matches!(&*cx.tcx.sess.target.arch, "wasm32" | "wasm64")
362+
|| fn_abi.conv == Conv::PtxKernel,
363+
"`PassMode::Direct` for aggregates only allowed on wasm and `extern \"ptx-kernel\"` fns\nProblematic type: {:#?}",
364+
arg.layout,
365+
);
366+
}
367+
}
368+
PassMode::Pair(_, _) => {
369+
// Similar to `Direct`, we need to make sure that backends use `layout.abi` and
370+
// ignore the rest of the layout.
371+
assert!(
372+
matches!(arg.layout.abi, Abi::ScalarPair(..)),
373+
"PassMode::Pair for type {}",
374+
arg.layout.ty
375+
);
376+
}
377+
PassMode::Cast { .. } => {
378+
// `Cast` means "transmute to `CastType`"; that only makes sense for sized types.
379+
assert!(arg.layout.is_sized());
380+
}
381+
PassMode::Indirect { meta_attrs: None, .. } => {
382+
// No metadata, must be sized.
383+
assert!(arg.layout.is_sized());
384+
}
385+
PassMode::Indirect { meta_attrs: Some(_), on_stack, .. } => {
386+
// With metadata. Must be unsized and not on the stack.
387+
assert!(arg.layout.is_unsized() && !on_stack);
388+
}
389+
}
390+
}
391+
392+
for arg in fn_abi.args.iter() {
393+
fn_arg_sanity_check(cx, fn_abi, arg);
394+
}
395+
fn_arg_sanity_check(cx, fn_abi, &fn_abi.ret);
396+
}
397+
330398
// FIXME(eddyb) perhaps group the signature/type-containing (or all of them?)
331399
// arguments of this method, into a separate `struct`.
332400
#[tracing::instrument(level = "debug", skip(cx, caller_location, fn_def_id, force_thin_self_ptr))]
@@ -453,6 +521,7 @@ fn fn_abi_new_uncached<'tcx>(
453521
};
454522
fn_abi_adjust_for_abi(cx, &mut fn_abi, sig.abi, fn_def_id)?;
455523
debug!("fn_abi_new_uncached = {:?}", fn_abi);
524+
fn_abi_sanity_check(cx, &fn_abi);
456525
Ok(cx.tcx.arena.alloc(fn_abi))
457526
}
458527

Diff for: tests/ui/abi/compatibility.rs

+21-8
Original file line numberDiff line numberDiff line change
@@ -37,9 +37,11 @@
3737
// revisions: wasi
3838
//[wasi] compile-flags: --target wasm32-wasi
3939
//[wasi] needs-llvm-components: webassembly
40-
// revisions: nvptx64
41-
//[nvptx64] compile-flags: --target nvptx64-nvidia-cuda
42-
//[nvptx64] needs-llvm-components: nvptx
40+
// FIXME: disabled on nvptx64 since the target ABI fails the sanity check
41+
/* revisions: nvptx64
42+
[nvptx64] compile-flags: --target nvptx64-nvidia-cuda
43+
[nvptx64] needs-llvm-components: nvptx
44+
*/
4345
#![feature(rustc_attrs, unsized_fn_params, transparent_unions)]
4446
#![cfg_attr(not(host), feature(no_core, lang_items), no_std, no_core)]
4547
#![allow(unused, improper_ctypes_definitions, internal_features)]
@@ -307,19 +309,30 @@ mod arrays {
307309
}
308310

309311
// Some tests with unsized types (not all wrappers are compatible with that).
312+
macro_rules! assert_abi_compatible_unsized {
313+
($name:ident, $t1:ty, $t2:ty) => {
314+
mod $name {
315+
use super::*;
316+
// Declaring a `type` doesn't even check well-formedness, so we also declare a function.
317+
fn check_wf(_x: $t1, _y: $t2) {}
318+
// Can only test arguments and only the Rust ABI, since it's unsized.
319+
#[rustc_abi(assert_eq)]
320+
type TestRust = (fn($t1), fn($t2));
321+
}
322+
};
323+
}
310324
macro_rules! test_transparent_unsized {
311325
($name:ident, $t:ty) => {
312326
mod $name {
313327
use super::*;
314-
assert_abi_compatible!(wrap1, $t, Wrapper1<$t>);
315-
assert_abi_compatible!(wrap1_reprc, ReprC1<$t>, ReprC1<Wrapper1<$t>>);
316-
assert_abi_compatible!(wrap2, $t, Wrapper2<$t>);
317-
assert_abi_compatible!(wrap2_reprc, ReprC1<$t>, ReprC1<Wrapper2<$t>>);
328+
assert_abi_compatible_unsized!(wrap1, $t, Wrapper1<$t>);
329+
assert_abi_compatible_unsized!(wrap1_reprc, ReprC1<$t>, ReprC1<Wrapper1<$t>>);
330+
assert_abi_compatible_unsized!(wrap2, $t, Wrapper2<$t>);
331+
assert_abi_compatible_unsized!(wrap2_reprc, ReprC1<$t>, ReprC1<Wrapper2<$t>>);
318332
}
319333
};
320334
}
321335

322-
#[cfg(not(any(target_arch = "mips64", target_arch = "sparc64")))]
323336
mod unsized_ {
324337
use super::*;
325338
test_transparent_unsized!(str_, str);

Diff for: tests/ui/abi/issue-94223.rs

-8
This file was deleted.

Diff for: tests/ui/associated-types/associated-types-unsized.fixed

-14
This file was deleted.

0 commit comments

Comments
 (0)