Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

added a lint against function item references #76269

Merged
merged 8 commits into from
Oct 27, 2020

Conversation

ayrtonm
Copy link
Contributor

@ayrtonm ayrtonm commented Sep 3, 2020

this lint suggests casting function references to *const ()
closes #75239
r? @RalfJung

@rust-highfive rust-highfive added the S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. label Sep 3, 2020
@jyn514 jyn514 added A-lint Area: Lints (warnings about flaws in source code) such as unused_mut. T-compiler Relevant to the compiler team, which will review and decide on the PR/issue. labels Sep 3, 2020
@jyn514
Copy link
Member

jyn514 commented Sep 3, 2020

Please add a test for this in src/test/ui. There are instructions at https://rustc-dev-guide.rust-lang.org/tests/adding.html#ui.

if let hir::ExprKind::AddrOf(hir::BorrowKind::Ref, _, referent) = e.kind {
if let hir::ExprKind::Path(qpath) = &referent.kind {
if let Some(def_id) = cx.qpath_res(qpath, referent.hir_id).opt_def_id() {
cx.tcx.hir().get_if_local(def_id).map(|node| {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, why is this only looking in the current crate? I'd think we'd want to link on &std::env::var or something too.

Copy link
Contributor Author

@ayrtonm ayrtonm Sep 4, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point, checking references to functions in other crates is probably important. I'm not sure I understand how to check other crates though. I've been looking through the docs for hir::map::Map and ty::TyCtxt, but if someone could give me a few pointers or help out that'd be great.

Copy link
Member

@jyn514 jyn514 Sep 4, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think this needs to work on the HIR at all. Try type_of instead. type_of returns a Ty, so you could call matches!(ty.kind, TyKind::FnDef(_, _)): https://doc.rust-lang.org/nightly/nightly-rustc/rustc_middle/ty/enum.TyKind.html

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, I think @jyn514 is right -- going through HIR here is unexpected.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually, this is a design decision... do we want the lint to fire on

let fn_item = function_name;
let _val = &fn_item;

If yes, this should be type-based. If no, we have to instead check if the referent is the name of a function.

@ollie27
Copy link
Member

ollie27 commented Sep 3, 2020

Shouldn't we suggest casting to a proper function pointer type rather than *const ()?

@tesuji
Copy link
Contributor

tesuji commented Sep 3, 2020

Shouldn't we suggest casting to a proper function pointer type

Sometimes function pointer type is really long.

@ollie27
Copy link
Member

ollie27 commented Sep 3, 2020

Sometimes function pointer type is really long.

We could always suggest using underscores to prevent the types getting too long e.g. as fn(_, _) -> _.

@ayrtonm
Copy link
Contributor Author

ayrtonm commented Sep 4, 2020

The lint breaks the following tests, so does it make sense to just add #![allow(function_references)] to not clutter them or should we add warn annotations?

custom-test-frameworks-simple.rs
enum-discriminant/actually_not_an_enum-discriminant.rs
functions-closures/fn-item-type-zero-sized.rs
issues/issue-16922-rpass.rs
issues/issue-20847.rs
issues/issue-22933-1.rs
issues/issue-25757.rs
trivial_casts-rpass.rs
unboxed-closures/unboxed-closures-extern-fn-hr.rs
unboxed-closures/unboxed-closures-extern-fn.rs

Also compiler/rustc_typeck/src/check/mod.rs:776:37 triggers the lint, so I'm guessing there should be an allow attribute on that as well?

@jyn514
Copy link
Member

jyn514 commented Sep 4, 2020

Also compiler/rustc_typeck/src/check/mod.rs:776:37 triggers the lint, so I'm guessing there should be an allow attribute on that as well?

That code looks like this:

fn adt_destructor(tcx: TyCtxt<'_>, def_id: DefId) -> Option<ty::Destructor> {
    tcx.calculate_dtor(def_id, &mut dropck::check_drop_impl)
}

and calculate_dtor takes &mut dyn FnMut(Self, DefId) -> Result<(), ErrorReported>. Maybe this shouldn't lint if it's being used as a dyn Fn*?

@RalfJung
Copy link
Member

RalfJung commented Sep 5, 2020

The lint breaks the following tests, so does it make sense to just add #![allow(function_references)] to not clutter them or should we add warn annotations?

custom-test-frameworks-simple.rs
enum-discriminant/actually_not_an_enum-discriminant.rs
functions-closures/fn-item-type-zero-sized.rs
issues/issue-16922-rpass.rs
issues/issue-20847.rs
issues/issue-22933-1.rs
issues/issue-25757.rs
trivial_casts-rpass.rs
unboxed-closures/unboxed-closures-extern-fn-hr.rs
unboxed-closures/unboxed-closures-extern-fn.rs

Also compiler/rustc_typeck/src/check/mod.rs:776:37 triggers the lint, so I'm guessing there should be an allow attribute on that as well?

actually_not_an_enum-discriminant.rs should have the lint allowed for the line that triggers it, that looks deliberate.

16922, 20847, 22933, 25757 all seem deliberate... and they make me wonder if we need to improve the lint. They are all of the form &fn_name as &dyn Something, but sometimes the as is implicit. Taking a reference here is crucial. The same thing is probably happening with custom test frameworks, and it also explains the rustc_typeck failure.

So given that we have at least 5 false positives in the rustc codebase and test suite, we cannot leave the lint as-is, we have to find something smarter. The key thing that happens in all these cases is that the &{fn item} gets coerced or cast to an &dyn Trait; that should be something that we can detect, I think... the only problem is that to detect this we have to look "outside" of the ExprKind::AddrOf, and I do not know how to do that.

@oli-obk @Manishearth summoning some clippy people that can hopefully help with suggestions for how to implement such a lint. :) Basically we want to detect &expr where expr satisfies some conditions (we got that part covered) and the reference is not immediately cast/coerced to &dyn Trait -- how can we implement the latter check?

@oli-obk
Copy link
Contributor

oli-obk commented Sep 7, 2020

We could change the lint to a MIR lint that triggers on a CastKind::Misc where the casted value's type is a reference to a function item zst. This would ensure that we only ever lint the cast and never the creation.

@RalfJung
Copy link
Member

RalfJung commented Sep 7, 2020

We don't really have MIR lints as a fixed notion though, do we? We have a few ad-hoc constructions that plug into different parts of the MIR pipeline, see #72515.

@oli-obk
Copy link
Contributor

oli-obk commented Sep 7, 2020

The way clippy does this is to have a normal lint that loads the mir of any function definition it sees and then analyzes that. Having a good point in the optimization pipeline is something @rust-lang/wg-mir-opt should come up with. Until we have made an informed decision, just putting it next to

check_consts::post_drop_elaboration::check_live_drops(tcx, def.did, &body);
should be fine

@RalfJung
Copy link
Member

RalfJung commented Sep 7, 2020

Shouldn't we rather add it to one of the three places where we already have a MIR lint? E.g.

&check_packed_ref::CheckPackedRef,

@oli-obk
Copy link
Contributor

oli-obk commented Sep 7, 2020

Oh there they are :D I thought we had that and didn't find it and then thought I imagined it. Yea, that's a good place to get deterministic MIR

@RalfJung
Copy link
Member

RalfJung commented Sep 7, 2020

Not sure what you mean by "deterministic".^^

@oli-obk
Copy link
Contributor

oli-obk commented Sep 7, 2020

Optimizations can change things in unexpected ways, we don't want to optimize out some code on the current platform and then only lint on other platforms. So we need to do it before the main optimization pipeline

@ayrtonm
Copy link
Contributor Author

ayrtonm commented Sep 11, 2020

It's still very much a WIP, but I changed lint to work with MIR. It now excludes &fn_name as &dyn Trait (implicit and explicit) and (&fn_name)() (function calls). This first MIR implementation emits a lint for

let fn_item = function_name;
let _val = &fn_item;

but I think it makes more sense to exclude it in this case. I also think we should exclude it when a function reference appears alone on the rhs of a regular let binding like let zst_ref = &function_name. This broke one or two existing tests where zst_ref was just used by casting/coercion later on. It'd be nice to get a second opinion on this though.

I don't quite understand how to work with generics/ParamEnv in the compiler, so the coercion that happens in the first argument of parameterized_call_fn below doesn't exclude the lint yet.

fn parameterized_call_fn<F: Fn(u32) -> u32>(f: &F, x: u32) { f(x); }
parameterized_call_fn(&fn_name, 1);

It's also why almost all of these tests are failing.

consts/const_constructor/const-construct-call.rs#const_fn
consts/const_constructor/const-construct-call.rs#min_const_fn
enum-discriminant/actually_not_an_enum-discriminant.rs
functions-closures/fn-item-type-zero-sized.rs
issues/issue-20847.rs
lint/function-references.rs
mir/mir_calls_to_shims.rs
nll/promoted-bounds.rs
nll/user-annotations/adt-tuple-struct-calls.rs
nll/user-annotations/promoted-annotation.rs
struct-ctor-mangling.rs
unboxed-closures/unboxed-closures-extern-fn-hr.rs
unboxed-closures/unboxed-closures-extern-fn.rs

I also had another minor doubt. This implementation compares the spans of function references with function calls/casts/coercions to decide what function references to exclude. This works, but feels kinda janky... is there a better way to check if two MIR rvalues refer to the same thing in the source?

Copy link
Contributor

@oli-obk oli-obk left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I also think we should exclude it when a function reference appears alone on the rhs of a regular let binding like let zst_ref = &function_name. This broke one or two existing tests where zst_ref was just used by casting/coercion later on. It'd be nice to get a second opinion on this though.

Good point. So, since we're only trying to avoid casts from references to fn defintions to raw pointers or ints, we may be able to simplify the logic down to just looking at Rvalue::Cast and nothing else. Not sure if that works, let me know what you think:

Is this missing some cases that we should lint or linting in cases that we should not lint?

compiler/rustc_mir/src/transform/function_references.rs Outdated Show resolved Hide resolved
compiler/rustc_mir/src/transform/function_references.rs Outdated Show resolved Hide resolved
@RalfJung
Copy link
Member

I also think we should exclude it when a function reference appears alone on the rhs of a regular let binding like let zst_ref = &function_name.

I guess it is reasonable to start conservative and lint less; we can crank it up later if we feel we should.

Is this missing some cases that we should lint or linting in cases that we should not lint?

Will this lint on println!("{:p}", &fun);, which explicitly came up in the original issue?

@bors
Copy link
Contributor

bors commented Oct 26, 2020

☔ The latest upstream changes (presumably #78324) made this pull request unmergeable. Please resolve the merge conflicts.

Note that reviewers usually do not review pull requests until merge conflicts are resolved! Once you resolve the conflicts, you should change the labels applied by bors to indicate that your PR is ready for review. Post this as a comment to change the labels:

@rustbot modify labels: +S-waiting-on-review -S-waiting-on-author

@oli-obk
Copy link
Contributor

oli-obk commented Oct 26, 2020

Sorry, I did not see that force push. Please leave a message when you force push

Copy link
Contributor

@oli-obk oli-obk left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just a few small things, then this is ready to go.

compiler/rustc_session/src/lint/builtin.rs Outdated Show resolved Hide resolved
@ayrtonm
Copy link
Contributor Author

ayrtonm commented Oct 27, 2020

Sorry, I did not see that force push. Please leave a message when you force push

My bad, I didn't realize you wouldn't get a notification.

So the lint message could be something like "taking a reference to a function item does not give a function pointer" and the suggestion could be "cast ident to obtain a function pointer: ident as fn()"?

@oli-obk
Copy link
Contributor

oli-obk commented Oct 27, 2020

So the lint message could be something like "taking a reference to a function item does not give a function pointer" and the suggestion could be "cast ident to obtain a function pointer: ident as fn()"?

yes. that looks right.

@ayrtonm
Copy link
Contributor Author

ayrtonm commented Oct 27, 2020

Just made the changes @oli-obk requested. I left the suggestion's applicability Unspecified instead of MaybeIncorrect since in cases like transmute((&foo,)), the exact substitution won't result in valid Rust code. The problem is that getting the subset of the span that should be replaced from MIR isn't straightforward, but these cases should be rare in practice.

@ayrtonm ayrtonm requested a review from oli-obk October 27, 2020 15:03
this lint suggests casting function references to `*const ()`
Working with MIR let's us exclude expressions like `&fn_name as &dyn Something`
and `(&fn_name)()`. Also added ABI, unsafety and whether a function is variadic
in the lint suggestion, included the `&` in the span of the lint and updated the
test.
The lint checks arguments in calls to `transmute` or functions that have
`Pointer` as a trait bound and displays a warning if the argument is a function
reference. Also checks for `std::fmt::Pointer::fmt` to handle formatting macros
although it doesn't depend on the exact expansion of the macro or formatting
internals. `std::fmt::Pointer` and `std::fmt::Pointer::fmt` were also added as
diagnostic items and symbols.
Removed test for the unhandled case of calls to `fn f<T>(x: &T)` where `x` is a
function reference and is formatted as a pointer in `f`. This compiles since
`&T` implements `Pointer`, but is unlikely to occur in practice. Also tweaked
the lint's wording and modified tests accordingly.
When a function argument bound by `Pointer` is an associated type, we only
perform substitutions using the parameters from the callsite but don't attempt
to normalize since it may not succeed. A simplified version of the scenario that
triggered this error was added as a test case. Also fixed `Pointer::fmt` which
was being double-counted when called outside of macros and added a test case for
this.
Added documentation for `function_item_references` lint to the rustc book and
fixed comments in the lint checker itself.
… message

Also updated tests accordingly and tweaked some wording in the lint declaration.
@oli-obk
Copy link
Contributor

oli-obk commented Oct 27, 2020

@bors r+

Thanks!

@bors
Copy link
Contributor

bors commented Oct 27, 2020

📌 Commit c791c64 has been approved by oli-obk

@bors bors added S-waiting-on-bors Status: Waiting on bors to run and complete tests. Bors will change the label on completion. and removed S-waiting-on-author Status: This is awaiting some action (such as code changes or more information) from the author. labels Oct 27, 2020
@bors
Copy link
Contributor

bors commented Oct 27, 2020

⌛ Testing commit c791c64 with merge 07e968b...

@bors
Copy link
Contributor

bors commented Oct 27, 2020

☀️ Test successful - checks-actions
Approved by: oli-obk
Pushing 07e968b to master...

@bors bors added the merged-by-bors This PR was explicitly merged by bors. label Oct 27, 2020
@bors bors merged commit 07e968b into rust-lang:master Oct 27, 2020
@rustbot rustbot added this to the 1.49.0 milestone Oct 27, 2020
@jyn514 jyn514 changed the title added a lint against function references added a lint against function item references Oct 27, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-lint Area: Lints (warnings about flaws in source code) such as unused_mut. merged-by-bors This PR was explicitly merged by bors. S-waiting-on-bors Status: Waiting on bors to run and complete tests. Bors will change the label on completion. T-compiler Relevant to the compiler team, which will review and decide on the PR/issue.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Printing function address is confusing due to per-function ZST "singleton type"
9 participants