Skip to content

Add new lint macros_hiding_unsafe_code #7469

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

Closed

Conversation

flip1995
Copy link
Member

This lint only triggers if the unsafe_op_in_unsafe_fn lint is enabled.

This lint is placed in the restriction group, since it suggests code,
that requires to allow the unused_unsafe lint locally. Instead of
allowing this lint, the unsafe block in the macro could be removed.

Closes #7323

changelog: Add new lint [macros_hiding_unsafe_code]

This lint only triggers if the `unsafe_op_in_unsafe_fn` lint is enabled.

This lint is placed in the restriction group, since it suggests code,
that requires to allow the `unused_unsafe` lint locally. Instead of
allowing this lint, the unsafe block in the macro could be removed.
@flip1995 flip1995 force-pushed the macros-hiding-unsafe-code branch from b409333 to eba8a3a Compare July 15, 2021 11:08
@flip1995
Copy link
Member Author

r? @camsteffen (highfive down again? 🤔)

@flip1995 flip1995 added the S-waiting-on-review Status: Awaiting review from the assignee but also interested parties label Jul 15, 2021
) {
if is_unsafe_fn(fn_kind) {
self.in_unsafe_fn = false;
}
Copy link
Contributor

Choose a reason for hiding this comment

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

I think this needs to be adjusted for nested functions.

unsafe fn f() {
    fn g() {}
    unsafe_macro!();
}

Copy link
Member Author

Choose a reason for hiding this comment

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

Ah right. Your example shouldn't break this code, but

unsafe fn f() {
    unsafe fn g() {}
    unsafe_macro!();
}

will.

Copy link
Contributor

@popzxc popzxc left a comment

Choose a reason for hiding this comment

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

While I totally like the idea of this lint, I think its design should be thought through a bit more carefully.
This lint should not suggest anything a user normally won't do.

Additionally, if some library has such a macro already, dependent crates will have no choice but to either get rid of dependency or add a bunch of allow(...), since they can't directly fix the issue.

Logically, it's the macro fault that it hides unsafe code, so the lint should be placed on the macro. Not sure whether it can be implemented though.

/// itself. This lint only triggers in functions where the `unsafe_op_in_unsafe_fn` lint is
/// enabled.
///
/// **Why is this bad?** This hides an unsafe operation inside a macro call. This is against
Copy link
Contributor

Choose a reason for hiding this comment

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

I don't think that being against the intention of lint is a strong motivation since it's the nature of the lint to catch what it's supposed to catch. Like, chicken and egg problem.

I'd emphasize that the macro containing an unsafe can hide the fact that it uses unsafe, and thus makes it easier to use in the context where required invariants are not held.

Copy link
Member Author

Choose a reason for hiding this comment

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

Good point, I'll word this differently.

/// **Known problems:**
/// - In some cases the intention of the macro is to actually hide the unsafety, because the
/// macro itself ensures the safety of the `unsafe` operation.
/// - For local macros, either an `#[allow(unused_unsafe)]` has to be added to the new unsafe
Copy link
Contributor

Choose a reason for hiding this comment

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

IMHO, suggesting introducing an unused unsafe block is not a good thing to do; and suggesting allow(unused_unsafe) is worse.

Imagine the following chain of actions:

  1. Someone introduces a macro with an unsafe code.
  2. In the use places it gets wrapped with #[allow(unused_unsafe)] unsafe { ... }.
  3. Unsafe code is removed from macro.

There is a good chance that this unused_unsafe will remain there unnoticed.

Copy link
Member Author

Choose a reason for hiding this comment

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

Yes, I also think this isn't nice. But I don't really see how else to handle this, without rendering the lint completely useless.

Copy link
Member Author

@flip1995 flip1995 Jul 19, 2021

Choose a reason for hiding this comment

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

When implementing the lint and noticing, that this is necessary, I also thought about whether we should allow lints that contradict rustc lints at all. We have lints contradicting other Clippy lints in the restriction group. But we don't have lints that contradict rustc lints (yet).

@bors
Copy link
Contributor

bors commented Jul 19, 2021

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

@flip1995
Copy link
Member Author

I think its design should be thought through a bit more carefully.

I'm also not really satisfied with how the lint currently behaves.

if some library has such a macro already, dependent crates will have no choice but to either get rid of dependency or add a bunch of allow(...), since they can't directly fix the issue.

I don't think this is a problem. I placed this lint in restriction, so that you'll have to enable it explicitly if you want it. If a dependent crate uses this lint and uses an external macro, that hides unsafe operations, the dependent crate probably wants to wrap this macro in an unsafe block, rather than adding allows.

We could just not lint on external macros, but as I just explained, this lint is supposed to restrict what you can and cannot do with macros that hide unsafe code, no matter the origin.

Logically, it's the macro fault that it hides unsafe code, so the lint should be placed on the macro.

With that, I think you would run into the problem with dependent crates. If you would lint a macro definition and tell the user to remove an unsafe block from the macro, all dependent crates using that macro would now have to add an unsafe block around the macro.

We could just not lint exported macros, but I'm not even sure if we're able to lint macro definitions at all, since Clippy is run after macro expansion.

@flip1995
Copy link
Member Author

flip1995 commented Jul 19, 2021

I'll address all comments and further lint design decisions once we have consensus on how the lint should behave.

@popzxc
Copy link
Contributor

popzxc commented Jul 19, 2021

With that, I think you would run into the problem with dependent crates. If you would lint a macro definition and tell the user to remove an unsafe block from the macro, all dependent crates using that macro would now have to add an unsafe block around the macro.

Actually, I think that's the way it should work. If the crate author thinks that unsafe is justified and always valid, they'll just put an allow statement on it and leave it as-is. No problem here.
Otherwise, they'll prepare a semver major release explaining that they discovered a potentially dangerous place, so since now users have to wrap the macro usage in unsafe. It's also not a problem, and even a good thing, since now the API of the dependency crate is more transparent.

@camsteffen
Copy link
Contributor

I see this lint as an extra precaution for macro users. Library authors with macros should not worry about triggering the lint, and they should not allow it. Users of this lint should expect false positives. A macro should have unsafe {} if and only if it can guarantee safety. If the guarantee is good, the lint is a false positive. If the guarantee is bad, the macro is wrong. But we can only lint expanded code. I think we should still lint for external macros.

For the lint suggestion, I'm not sure it makes sense to add an unsafe block to appease this lint when it will be deemed unused by rustc. I think it would be better to just allow the lint? This also can be a lint that you run manually to see what you see and just ignore the false positives. I see this lint as being in conflict with rustc's unsafe rules, in its false positive cases, so there is no "fix" to be made.

@camsteffen
Copy link
Contributor

Another reason to not suggest adding unsafe {} is that it could widen the scope of unsafe {}.

@flip1995
Copy link
Member Author

Thanks for all the input!

What do you think about suggesting to add an unsafe block only for

  1. external macros
  2. optionally: if the unused_unsafe lint is allowed

But for local macros the correct fix would be to either remove the unsafe block from the macro and put it around every macro call (or the macro argument that makes the unsafe block necessary), OR allow this lint on the macro calls as a marker, that the macro is unsafe. But without producing an auto-applicable suggestion.

@popzxc
Copy link
Contributor

popzxc commented Jul 22, 2021

Sounds reasonable to me.

@camsteffen
Copy link
Contributor

camsteffen commented Jul 22, 2021

For local macros, yes I agree.

For external macros, I guess it is more reasonable to add unsafe {}, but I think allow-this-lint is still the preferred way to suppress because of the conflict with unused_unsafe. Maybe output something like this:

help: suppress this lint by writing `#[allow(macros_hiding_unsafe_code)] { macro_name!(..) }`
help: or write `unsafe { macro_name!(..) }`, but this will trigger the `unused_unsafe` lint

@camsteffen
Copy link
Contributor

I think macros_hiding_unsafe would be a better name. Technically it is looking for an unsafe block, where _unsafe_code kinda makes it sound like macros can just call unsafe functions anywhere.

@flip1995
Copy link
Member Author

For external macros, I guess it is more reasonable to add unsafe {}, but I think allow-this-lint is still the preferred way to suppress because of the conflict with unused_unsafe. Maybe output something like this:

The unused_unsafe lint doesn't trigger for external macros. I've added a test for this here. Do you still think suggesting to just allow this lint in this case makes more sense, than suggesting unsafe code. I'm good with suggesting both alternatives.

I think macros_hiding_unsafe would be a better name. Technically it is looking for an unsafe block, where _unsafe_code kinda makes it sound like macros can just call unsafe functions anywhere.

Ah yes, thanks for the better name! Now I know what irritated me with the name that I chose 😄

@camsteffen
Copy link
Contributor

The unused_unsafe lint doesn't trigger for external macros.

Huh. Isn't that a bug?

@bjorn3

This comment has been minimized.

@camsteffen

This comment has been minimized.

@bjorn3

This comment has been minimized.

@camsteffen

This comment has been minimized.

@flip1995 flip1995 added S-waiting-on-author Status: This is awaiting some action from the author. (Use `@rustbot ready` to update this status) and removed S-waiting-on-review Status: Awaiting review from the assignee but also interested parties labels Jul 27, 2021
@flip1995
Copy link
Member Author

This has been open way to long with no update by me. This is pretty low on my priority list, so I'm closing it for now, maybe I'll get back to it some time in the future.

@flip1995 flip1995 closed this Sep 30, 2021
@flip1995 flip1995 added S-inactive-closed Status: Closed due to inactivity and removed S-waiting-on-author Status: This is awaiting some action from the author. (Use `@rustbot ready` to update this status) labels Sep 30, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
S-inactive-closed Status: Closed due to inactivity
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Macros wrapping expressions in unsafe blocks when using unsafe_op_in_unsafe_fn
5 participants