Skip to content

Commit 21a95f9

Browse files
committed
wf: ensure that non-Rust-functions have sized arguments, and everyone has sized return types
1 parent bbcc169 commit 21a95f9

32 files changed

+261
-98
lines changed

compiler/rustc_hir_analysis/src/check/wfcheck.rs

Lines changed: 1 addition & 0 deletions
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),

compiler/rustc_hir_analysis/src/hir_wf_check.rs

Lines changed: 3 additions & 0 deletions
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>(

compiler/rustc_hir_typeck/src/callee.rs

Lines changed: 1 addition & 0 deletions
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(

compiler/rustc_hir_typeck/src/check.rs

Lines changed: 4 additions & 1 deletion
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,

compiler/rustc_hir_typeck/src/expr.rs

Lines changed: 2 additions & 0 deletions
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>,

compiler/rustc_target/src/abi/call/x86_64.rs

Lines changed: 4 additions & 6 deletions
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
}

compiler/rustc_target/src/spec/abi.rs

Lines changed: 9 additions & 0 deletions
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)]

compiler/rustc_trait_selection/src/traits/error_reporting/suggestions.rs

Lines changed: 17 additions & 10 deletions
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
}

compiler/rustc_trait_selection/src/traits/wf.rs

Lines changed: 20 additions & 3 deletions
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,

tests/ui/abi/compatibility.rs

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -307,19 +307,30 @@ mod arrays {
307307
}
308308

309309
// Some tests with unsized types (not all wrappers are compatible with that).
310+
macro_rules! assert_abi_compatible_unsized {
311+
($name:ident, $t1:ty, $t2:ty) => {
312+
mod $name {
313+
use super::*;
314+
// Declaring a `type` doesn't even check well-formedness, so we also declare a function.
315+
fn check_wf(_x: $t1, _y: $t2) {}
316+
// Can only test arguments and only the Rust ABI, since it's unsized.
317+
#[rustc_abi(assert_eq)]
318+
type TestRust = (fn($t1), fn($t2));
319+
}
320+
};
321+
}
310322
macro_rules! test_transparent_unsized {
311323
($name:ident, $t:ty) => {
312324
mod $name {
313325
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>>);
326+
assert_abi_compatible_unsized!(wrap1, $t, Wrapper1<$t>);
327+
assert_abi_compatible_unsized!(wrap1_reprc, ReprC1<$t>, ReprC1<Wrapper1<$t>>);
328+
assert_abi_compatible_unsized!(wrap2, $t, Wrapper2<$t>);
329+
assert_abi_compatible_unsized!(wrap2_reprc, ReprC1<$t>, ReprC1<Wrapper2<$t>>);
318330
}
319331
};
320332
}
321333

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

0 commit comments

Comments
 (0)