Skip to content

Commit

Permalink
Auto merge of #86246 - FabianWolff:issue-83471, r=estebank
Browse files Browse the repository at this point in the history
Add basic checks for well-formedness of `fn`/`fn_mut` lang items

This pull request fixes #83471. Lang items are never actually checked for well-formedness (#9307). This means that one can get an ICE quite easily, e.g. as follows:
```rust
#![feature(lang_items)]
#[lang = "fn"]
trait MyFn {
    const call: i32 = 42;
}

fn main() {
    (|| 42)();
}
```
or this:
```rust
#![feature(lang_items)]
#[lang = "fn"]
trait MyFn {
    fn call(i: i32, j: i32);
}

fn main() {
    (|| 42)();
}
```
Ideally, there should probably be a more comprehensive strategy for checking lang items for well-formedness, but for the time being, I have added some rudimentary well-formedness checks that prevent #83471 and similar issues.
  • Loading branch information
bors committed Sep 24, 2021
2 parents 7342213 + cb6c139 commit 043972f
Show file tree
Hide file tree
Showing 6 changed files with 180 additions and 6 deletions.
18 changes: 12 additions & 6 deletions compiler/rustc_typeck/src/check/callee.rs
Original file line number Diff line number Diff line change
Expand Up @@ -246,12 +246,18 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
if borrow {
// Check for &self vs &mut self in the method signature. Since this is either
// the Fn or FnMut trait, it should be one of those.
let (region, mutbl) =
if let ty::Ref(r, _, mutbl) = method.sig.inputs()[0].kind() {
(r, mutbl)
} else {
span_bug!(call_expr.span, "input to call/call_mut is not a ref?");
};
let (region, mutbl) = if let ty::Ref(r, _, mutbl) =
method.sig.inputs()[0].kind()
{
(r, mutbl)
} else {
// The `fn`/`fn_mut` lang item is ill-formed, which should have
// caused an error elsewhere.
self.tcx
.sess
.delay_span_bug(call_expr.span, "input to call/call_mut is not a ref?");
return None;
};

let mutbl = match mutbl {
hir::Mutability::Not => AutoBorrowMutability::Not,
Expand Down
53 changes: 53 additions & 0 deletions compiler/rustc_typeck/src/check/wfcheck.rs
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,59 @@ pub fn check_trait_item(tcx: TyCtxt<'_>, def_id: LocalDefId) {
};
check_object_unsafe_self_trait_by_name(tcx, &trait_item);
check_associated_item(tcx, trait_item.hir_id(), span, method_sig);

let encl_trait_hir_id = tcx.hir().get_parent_item(hir_id);
let encl_trait = tcx.hir().expect_item(encl_trait_hir_id);
let encl_trait_def_id = encl_trait.def_id.to_def_id();
let fn_lang_item_name = if Some(encl_trait_def_id) == tcx.lang_items().fn_trait() {
Some("fn")
} else if Some(encl_trait_def_id) == tcx.lang_items().fn_mut_trait() {
Some("fn_mut")
} else {
None
};

if let (Some(fn_lang_item_name), "call") =
(fn_lang_item_name, trait_item.ident.name.to_ident_string().as_str())
{
// We are looking at the `call` function of the `fn` or `fn_mut` lang item.
// Do some rudimentary sanity checking to avoid an ICE later (issue #83471).
if let Some(hir::FnSig { decl, span, .. }) = method_sig {
if let &[self_ty, _] = &decl.inputs {
if !matches!(self_ty.kind, hir::TyKind::Rptr(_, _)) {
tcx.sess
.struct_span_err(
self_ty.span,
&format!(
"first argument of `call` in `{}` lang item must be a reference",
fn_lang_item_name
),
)
.emit();
}
} else {
tcx.sess
.struct_span_err(
*span,
&format!(
"`call` function in `{}` lang item takes exactly two arguments",
fn_lang_item_name
),
)
.emit();
}
} else {
tcx.sess
.struct_span_err(
trait_item.span,
&format!(
"`call` trait item in `{}` lang item must be a function",
fn_lang_item_name
),
)
.emit();
}
}
}

fn could_be_self(trait_def_id: LocalDefId, ty: &hir::Ty<'_>) -> bool {
Expand Down
27 changes: 27 additions & 0 deletions src/test/ui/lang-items/fn-fn_mut-call-ill-formed.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
// Make sure that an error is reported if the `call` function of the
// `fn`/`fn_mut` lang item is grossly ill-formed.

#![feature(lang_items)]
#![feature(no_core)]
#![no_core]

#[lang = "fn"]
trait MyFn<T> {
const call: i32 = 42;
//~^ ERROR: `call` trait item in `fn` lang item must be a function
}

#[lang = "fn_mut"]
trait MyFnMut<T> {
fn call(i: i32, j: i32) -> i32 { i + j }
//~^ ERROR: first argument of `call` in `fn_mut` lang item must be a reference
}

fn main() {
let a = || 42;
a();

let mut i = 0;
let mut b = || { i += 1; };
b();
}
14 changes: 14 additions & 0 deletions src/test/ui/lang-items/fn-fn_mut-call-ill-formed.stderr
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
error: `call` trait item in `fn` lang item must be a function
--> $DIR/fn-fn_mut-call-ill-formed.rs:10:5
|
LL | const call: i32 = 42;
| ^^^^^^^^^^^^^^^^^^^^^

error: first argument of `call` in `fn_mut` lang item must be a reference
--> $DIR/fn-fn_mut-call-ill-formed.rs:16:16
|
LL | fn call(i: i32, j: i32) -> i32 { i + j }
| ^^^

error: aborting due to 2 previous errors

23 changes: 23 additions & 0 deletions src/test/ui/lang-items/issue-83471.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
// Regression test for the ICE reported in issue #83471.

#![crate_type="lib"]
#![feature(no_core)]
#![no_core]

#[lang = "sized"]
//~^ ERROR: language items are subject to change [E0658]
trait Sized {}

#[lang = "fn"]
//~^ ERROR: language items are subject to change [E0658]
//~| ERROR: `fn` language item must be applied to a trait with 1 generic argument
trait Fn {
fn call(export_name);
//~^ ERROR: expected type
//~| WARNING: anonymous parameters are deprecated
//~| WARNING: this is accepted in the current edition
}
fn call_through_fn_trait() {
a()
//~^ ERROR: cannot find function
}
51 changes: 51 additions & 0 deletions src/test/ui/lang-items/issue-83471.stderr
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
error[E0573]: expected type, found built-in attribute `export_name`
--> $DIR/issue-83471.rs:15:13
|
LL | fn call(export_name);
| ^^^^^^^^^^^ not a type

error[E0425]: cannot find function `a` in this scope
--> $DIR/issue-83471.rs:21:5
|
LL | a()
| ^ not found in this scope

error[E0658]: language items are subject to change
--> $DIR/issue-83471.rs:7:1
|
LL | #[lang = "sized"]
| ^^^^^^^^^^^^^^^^^
|
= help: add `#![feature(lang_items)]` to the crate attributes to enable

error[E0658]: language items are subject to change
--> $DIR/issue-83471.rs:11:1
|
LL | #[lang = "fn"]
| ^^^^^^^^^^^^^^
|
= help: add `#![feature(lang_items)]` to the crate attributes to enable

warning: anonymous parameters are deprecated and will be removed in the next edition.
--> $DIR/issue-83471.rs:15:13
|
LL | fn call(export_name);
| ^^^^^^^^^^^ help: try naming the parameter or explicitly ignoring it: `_: export_name`
|
= note: `#[warn(anonymous_parameters)]` on by default
= warning: this is accepted in the current edition (Rust 2015) but is a hard error in Rust 2018!
= note: for more information, see issue #41686 <https://github.com/rust-lang/rust/issues/41686>

error[E0718]: `fn` language item must be applied to a trait with 1 generic argument
--> $DIR/issue-83471.rs:11:1
|
LL | #[lang = "fn"]
| ^^^^^^^^^^^^^^
...
LL | trait Fn {
| - this trait has 0 generic arguments

error: aborting due to 5 previous errors; 1 warning emitted

Some errors have detailed explanations: E0425, E0573, E0658, E0718.
For more information about an error, try `rustc --explain E0425`.

0 comments on commit 043972f

Please sign in to comment.