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

Add lint against (some) interior mutable consts #132146

Open
wants to merge 6 commits into
base: master
Choose a base branch
from

Conversation

Urgau
Copy link
Member

@Urgau Urgau commented Oct 25, 2024

interior_mutable_consts

warn-by-default

The interior_mutable_consts lint detects instance where const-items have a interior mutable type, which silently does nothing.

Example

use std::sync::Once;

// SAFETY: should only be call once
unsafe extern "C" fn ffi_init() { /* ... */ }

const A: Once = Once::new(); // using `A` will always creates temporaries and
                             // never modify it-self on use, should be a
                             // static-item instead

fn init() {
    A.call_once(|| unsafe {
        ffi_init();  // unsound, as the `call_once` is on a temporary
                     // and not on a shared variable
    })
}
warning: interior mutability in `const` item have no effect to the `const` item it-self
  --> $DIR/interior-mutable-types-in-consts.rs:10:1
   |
LL | const A: Once = Once::new();
   | ^^^^^^^^^----^^^^^^^^^^^^^^^
   |          |
   |          `Once` is an interior mutable type
   |
   = note: each usage of a `const` item creates a new temporary
   = note: only the temporaries and never the original `const` item `A` will be modified
   = help: for use as an initializer, consider using an inline-const `const { /* ... */ }` at the usage site instead
   = note: `#[warn(interior_mutable_consts)]` on by default
help: for a shared instance of `A`, consider using a `static` item instead
   |
LL | static A: Once = Once::new();
   | ~~~~~~
help: alternatively consider allowing the lint
   |
LL | #[allow(interior_mutable_consts)] const A: Once = Once::new();
   | +++++++++++++++++++++++++++++++++

Explanation

Using a const-item with an interior mutable type has no effect as const-item are essentially inlined wherever they are used, meaning that they are copied directly into the relevant context when used rendering modification through interior mutability ineffective across usage of that const-item.

The current implementation of this lint only warns on significant std and core interior mutable types, like Once, AtomicI32, ... this is done out of prudence and may be extended in the future.


It should be noted that the lint in not immunized against false-positives in particular when those immutable const-items are used as "init" const for const arrays (i.e. [INIT; 10]), which is why the lint recommends inline-const instead.

It should also be noted that this is NOT an uplift of the more general clippy::declare_interior_mutable_const lint, which works for all interior mutable types, but has even more false-positives than the currently proposed lint.

A simple Github Search reveals many instance where the user probably wanted to use a static-item instead.


@rustbot labels +I-lang-nominated +T-lang -S-waiting-on-review
cc @AngelicosPhosphoros @kupiakos
r? compiler

Fixes IRLO - Forbidding creation of constant mutexes, etc
Fixes #132028
Fixes #40543

@rustbot rustbot added S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. T-compiler Relevant to the compiler team, which will review and decide on the PR/issue. T-libs Relevant to the library team, which will review and decide on the PR/issue. I-lang-nominated Nominated for discussion during a lang team meeting. T-lang Relevant to the language team, which will review and decide on the PR/issue. labels Oct 25, 2024
@taiki-e
Copy link
Member

taiki-e commented Oct 25, 2024

This lint seems to be the same as (or limited version of) clippy::declare_interior_mutable_const?

AFAIK clippy::declare_interior_mutable_const is usually useless (It is not a lint that is triggered for code that may actually encounter problems, e.g., it is not triggered when a constant defined in the external crate is used incorrectly. clippy::borrow_interior_mutable_const covers actual footguns about interior_mutable_const and triggered for code that may actually encounter problems. see also rust-lang/rust-clippy#7665)

@rust-log-analyzer

This comment has been minimized.

@Urgau
Copy link
Member Author

Urgau commented Oct 25, 2024

This lint seems to be the same as (or limited version of) clippy::declare_interior_mutable_const?

Forgot to mentioned it (fixed), but yes it's a very-limited form of the clippy lint.

AFAIK clippy::declare_interior_mutable_const is usually useless (It is not a lint that is triggered for code that may actually encounter problems, e.g., it is not triggered when a constant defined in the external crate is used incorrectly.

I disagree that it is useless. It detects suspicious constants item, suspicious enough that they are probably a mistake and IMO warrants a warning.

In other words I think this lint detects potential issues at the root cause and not at the usage site. I would find it weird if we warned when using a const-item from an external crate, where the user can't do anything about it (except maybe creating an issue upstream) but I also think I could be convinced otherwise.

clippy::borrow_interior_mutable_const covers actual footguns about interior_mutable_const and triggered for code that may actually encounter problems.

I did not actually see this lint before, it's interesting and tackles the issue at another level; but IMO declaring a interior mutable const is a foot-gun whenever used or not, in particular if exposed and used from an external crate.

see also rust-lang/rust-clippy#7665)

This should use inline const, which is suggested by this lint.

@Urgau Urgau force-pushed the interior_mut_consts branch from f812ff2 to f1634d8 Compare October 25, 2024 17:50
@rustbot
Copy link
Collaborator

rustbot commented Oct 25, 2024

Some changes occurred in src/tools/clippy

cc @rust-lang/clippy

@rust-log-analyzer

This comment has been minimized.

@taiki-e
Copy link
Member

taiki-e commented Oct 25, 2024

@Urgau

In other words I think this lint detects potential issues at the root cause and not at the usage site. I would find it weird if we warned when using a const-item from an external crate, where the user can't do anything about it (except maybe creating an issue upstream).

Ah, we each seem to have a different case in mind. I was thinking of a case where const is correct and the usage referring to it is incorrect, and you seem to be thinking of a case where defining it as const is wrong and the usage referring to it is supposedly correct. I agree that this lint is indeed not necessarily useless in such a case.

This should use inline const, which is suggested by this lint.

It seems unfortunate that lint, for which the only two ways to deal with warnings are to ignore the warning or increase the MSRV, will be added as warn-by-default.


After reading your comment, I think it would be better to apply this lint only to public constants, and uplift clippy::borrow_interior_mutable_const to rustc to address other cases:

  • In cases where const is not correct (i.e., static is correct):
    • if const is private:
      • borrow_interior_mutable_const warns of a reference to const and the user can correct it on the fly.
      • interior_mutable_consts(declare_interior_mutable_const) doesn't need to do anything since borrow_interior_mutable_const can catch all problems.
    • if const is public:
      • borrow_interior_mutable_const warns of a reference to const. If it is defined in the external crate, the user cannot fix the underlying problem, but at least the problem can be avoided.
      • interior_mutable_consts(declare_interior_mutable_const) warns of a suspect definition of const in the crate that defines that const.
  • In cases where const is correct:
    • if const is private:
      • borrow_interior_mutable_const warns misuses of it.
      • interior_mutable_consts(declare_interior_mutable_const) doesn't need to do anything since borrow_interior_mutable_const can catch all misuses.
    • if const is public:
      • borrow_interior_mutable_const warns misuses of it.
      • interior_mutable_consts(declare_interior_mutable_const) warns of (correct) definition of const (false positive!).
        • However, in cases where const is correct, such as array initialization helpers, const is basically private, so the chances of encountering this false positive should be quite rare...

@rustbot
Copy link
Collaborator

rustbot commented Oct 25, 2024

Some changes occurred in compiler/rustc_codegen_cranelift

cc @bjorn3

@rust-log-analyzer

This comment has been minimized.

@Urgau Urgau force-pushed the interior_mut_consts branch from c059fe1 to 3e46a9a Compare October 25, 2024 20:35
@rust-log-analyzer

This comment has been minimized.

@Urgau Urgau force-pushed the interior_mut_consts branch from 3e46a9a to fe98149 Compare October 25, 2024 22:04
@bors

This comment was marked as resolved.

@Urgau Urgau force-pushed the interior_mut_consts branch from fe98149 to d29f661 Compare October 26, 2024 21:01
@Urgau
Copy link
Member Author

Urgau commented Oct 26, 2024

[..] you seem to be thinking of a case where defining it as const is wrong and the usage referring to it is supposedly correct.

Yes, there seems to be some of cases in the wildly were it's the case. (both of the linked issues are example of that)

Example of a soundness issue caused by using a const-item instead of a static-item
use std::sync::Once;

// SAFETY: should only be call once
unsafe extern "C" fn ffi_init() { /* ... */ }

const INIT_ONCE: Once = Once::new(); // using `B` will always creates temporaries and
                                     // never modify it-self on use, should be a
                                     // static-item instead

fn init() {
    INIT_ONCE.call_once(|| unsafe {
        ffi_init();  // unsound, as the `call_once` is on a temporary
                     // and not on a shared variable
    })
}

Updated the lint example with this one, to better describe the usefulness of the lint

After reading your comment, I think it would be better to apply this lint only to public constants, and uplift clippy::borrow_interior_mutable_const to rustc to address other cases

Interesting idea.

I'm not sure I like having the lint be dependant of the visibility of the const-item since it has nothing to do with the issue it-self, see my example above or #132028, where the issue is not the usage but the declaration, with your proposal we IMO would be linting at the wrong place. (We could lint at the declaration only if we found a usage, but that seems hacky and I rather avoid that)

@rust-log-analyzer

This comment has been minimized.

@Urgau Urgau force-pushed the interior_mut_consts branch from d29f661 to e46943e Compare October 26, 2024 21:43
@@ -1,5 +1,5 @@
#![warn(clippy::declare_interior_mutable_const)]

#![allow(interior_mutable_consts)]
Copy link
Member

Choose a reason for hiding this comment

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

How do those Clippy lints interact with the new rustc lint?

Copy link
Member Author

Choose a reason for hiding this comment

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

This proposed lint is similar to clippy::declare_interior_mutable_const, except that contrary to the Clippy lint which recurse into the type and can lint on arbitrary type that have interior mutability, this lint only checks if the type is marked with #[rustc_significant_interior_mutable_type] (which only concerns a few core/std types).

So for these types (and only these types) we would report a similar warning. (Preventing this duplicated warning should be a matter of checking if the type as the attribute).

Copy link
Member

Choose a reason for hiding this comment

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

I would prefer adding this check to the Clippy lints, over just allowing the rustc lint in the tests.

The tests could then be adapted to remove all test cases that get linted by rustc, except for maybe 1 to check that the Clippy lint doesn't actually trigger on those core/std types.

@Urgau Urgau force-pushed the interior_mut_consts branch from e46943e to 84243a7 Compare November 9, 2024 11:49
@lcnr lcnr added S-waiting-on-team Status: Awaiting decision from the relevant subteam (see the T-<team> label). and removed S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. labels Jan 21, 2025
@Amanieu Amanieu removed the T-libs Relevant to the library team, which will review and decide on the PR/issue. label Jan 22, 2025
@Urgau Urgau force-pushed the interior_mut_consts branch from 84243a7 to 8aaf94d Compare January 28, 2025 18:45
@rust-log-analyzer

This comment has been minimized.

@Urgau Urgau force-pushed the interior_mut_consts branch from 8aaf94d to 1d6f0e9 Compare January 28, 2025 19:50
@bors
Copy link
Contributor

bors commented Feb 25, 2025

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

@Urgau Urgau force-pushed the interior_mut_consts branch from 1d6f0e9 to 9e7f71c Compare March 5, 2025 18:02
@rustbot rustbot added the A-attributes Area: Attributes (`#[…]`, `#![…]`) label Mar 5, 2025
@rustbot
Copy link
Collaborator

rustbot commented Mar 5, 2025

Some changes occurred in compiler/rustc_passes/src/check_attr.rs

cc @jdonszelmann

/// unsafe extern "C" fn ffi_init() { /* ... */ }
///
/// const A: Once = Once::new(); // using `B` will always creates temporaries and
/// // never modify it-self on use, should be a
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
/// // never modify it-self on use, should be a
/// // never modify itself on use, should be a

@traviscross traviscross removed the T-compiler Relevant to the compiler team, which will review and decide on the PR/issue. label Mar 7, 2025
@@ -426,6 +426,14 @@ lint_incomplete_include =
lint_inner_macro_attribute_unstable = inner macro attributes are unstable
lint_interior_mutable_consts = interior mutability in `const` item have no effect to the `const` item itself
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
lint_interior_mutable_consts = interior mutability in `const` item have no effect to the `const` item itself
lint_interior_mutable_consts = interior mutability in `const` items have no effect on the `const` item itself

@traviscross
Copy link
Contributor

I'm trying to decide whether I like this or not. What I like about it is where it might help catch errors, of course. What I don't like about it is that probably I actually think it's fine to use a const item as a kind of "const alias" (or like a thunk const function), and so I'm hesitant to discourage that as much as this lint probably would.

That is, it seems maybe fine to use a const item to set up some interior mutable type how you want it, and then to use that in initializers, e.g. [INIT; 128], rather than having to e.g. wrap the logic in a const fn and then call that in a const block in the initializers.

Regarding this bit:

The current implementation of this lint only warns on significant std and core interior mutable types, like Once, AtomicI32, ... this is done out of prudence and may be extended in the future.

This could be troublesome for us. Each time we'd want to extend this, it could be a significant lint expansion in terms of the amount of code affected, but the fix is not machine-applicable.

@jamesmunns
Copy link
Member

In terms of "experience report", an almost identical repro of #132028 occurred on reddit today.

@Urgau
Copy link
Member Author

Urgau commented Mar 7, 2025

That is, it seems maybe fine to use a const item to set up some interior mutable type how you want it, and then to use that in initializers, e.g. [INIT; 128], rather than having to e.g. wrap the logic in a const fn and then call that in a const block in the initializers.

In the case of array initializer we should maybe recommend inline const [const { ... }; 128] instead of the const INIT pattern.

This could be troublesome for us. Each time we'd want to extend this, it could be a significant lint expansion in terms of the amount of code affected, but the fix is not machine-applicable.

If we only recommend the const to static suggestion we could make it a machine-applicable suggestion. The only reason it's not is because the lint currently produces a second suggestion which makes the two suggestions exclusive.

@traviscross
Copy link
Contributor

traviscross commented Mar 8, 2025

In the case of array initializer we should maybe recommend inline const [const { ... }; 128] instead of the const INIT pattern.

The point is that we could have:

const INIT: .. = {
    ..
    ..
    ..
    ..
};

let (x, y, z) = ([INIT; 128], [INIT; 128], [INIT; 128]);

Clearly we wouldn't just want to inline the body of the const initializer into each array initializer in const { .. } blocks. So we would need to refactor as, e.g.:

const fn make_init() -> .. {
    ..
    ..
    ..
    ..
}

let (x, y, z) = ([const { make_init() }; 128], [const { make_init() }; 128], [const { make_init() }; 128]);

That's what I'm not really sure it'd be great for us to impose.

Then, going a step further, it seems like anywhere we have const { make_init() } that we should be able to instead do:

const INIT: .. = make_init();
let (x, y, z) = ([INIT; 128], [INIT; 128], [INIT; 128]);

And I could see it being surprising if we warned against this kind of natural refactoring in this one case.

@Urgau
Copy link
Member Author

Urgau commented Mar 8, 2025

Hum, I see, thanks for the bigger example.

We could maybe have exception for local (not pub) interior mutable const used only as initializer (only array initializer?) and nothing else.

@bors

This comment was marked as resolved.

@Urgau Urgau force-pushed the interior_mut_consts branch from 9e7f71c to f504b26 Compare March 16, 2025 14:19
@rust-log-analyzer

This comment has been minimized.

@Urgau Urgau force-pushed the interior_mut_consts branch from f504b26 to 0c8f814 Compare March 16, 2025 15:08
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-attributes Area: Attributes (`#[…]`, `#![…]`) I-lang-nominated Nominated for discussion during a lang team meeting. S-waiting-on-team Status: Awaiting decision from the relevant subteam (see the T-<team> label). T-lang Relevant to the language team, which will review and decide on the PR/issue.
Projects
None yet