Skip to content

Commit 225d377

Browse files
committed
Auto merge of rust-lang#12276 - PartiallyTyped:hide-thread-local, r=blyxyas
Fix FP in `threadlocal!` when falling back to `os_local` **Issue**: The lint always triggers for some targets. **Why**: The lint assumes an `__init` function is not present for const initializers. This does not hold for all targets. Some targets always have an initializer function. **Fix**: If an initializer function exists, we check that it is not a `const` fn. Lint overview before the patch: 1. Outside `threadlocal!`, then exit early. 2. In `threadlocal!` and `__init` does not exist => is const already, exit early. 3. In `threadlocal!` and `__init` exists and `__init` body can be `const` => we lint. Lint overview after the patch: 1. Outside `threadlocal!`, then exit early. 2. In `threadlocal!` and `__init` does not exist => is const already, exit early. 3. In `threadlocal!` and `__init` exists and is `const fn` => is const already, exit early. 4. In `threadlocal!` and `__init` exists and is not `const fn` and `__init` body can be `const` => we lint. The patch adds step 3. changelog: Fixed fp in [`thread_local_initializer_can_be_made_const`] where fallback implementation of `threadlocal!` was always linted. ## Verifying the changes For each of these platforms [ mac, x86_64-windows-gnu, x86_64-windows-msvc ]: 1. switched to master, ran bless. Found a failure for windows-gnu. 2. switched to patch, ran bless. Success for all three platforms. ## How platform affects the lint: The compiler performs a [conditional compilation depending on platform features](https://doc.rust-lang.org/src/std/sys/common/thread_local/mod.rs.html). The `os_local` fallback includes a call to `__init`, without step 3, we lint in all cases. ```rust cfg_if::cfg_if! { if #[cfg(any(all(target_family = "wasm", not(target_feature = "atomics")), target_os = "uefi"))] { #[doc(hidden)] mod static_local; #[doc(hidden)] pub use static_local::{Key, thread_local_inner}; } else if #[cfg(target_thread_local)] { #[doc(hidden)] mod fast_local; #[doc(hidden)] pub use fast_local::{Key, thread_local_inner}; } else { #[doc(hidden)] mod os_local; #[doc(hidden)] pub use os_local::{Key, thread_local_inner}; } } ``` r? `@llogiq`
2 parents 3b8da4a + bb1ee87 commit 225d377

4 files changed

+71
-21
lines changed

clippy_lints/src/thread_local_initializer_can_be_made_const.rs

+44-20
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,11 @@
11
use clippy_config::msrvs::Msrv;
22
use clippy_utils::diagnostics::span_lint_and_sugg;
3-
use clippy_utils::fn_has_unsatisfiable_preds;
43
use clippy_utils::qualify_min_const_fn::is_min_const_fn;
54
use clippy_utils::source::snippet;
5+
use clippy_utils::{fn_has_unsatisfiable_preds, peel_blocks};
66
use rustc_errors::Applicability;
77
use rustc_hir::{intravisit, ExprKind};
8-
use rustc_lint::{LateContext, LateLintPass, LintContext};
9-
use rustc_middle::lint::in_external_macro;
8+
use rustc_lint::{LateContext, LateLintPass};
109
use rustc_session::impl_lint_pass;
1110
use rustc_span::sym::thread_local_macro;
1211

@@ -57,6 +56,31 @@ impl ThreadLocalInitializerCanBeMadeConst {
5756

5857
impl_lint_pass!(ThreadLocalInitializerCanBeMadeConst => [THREAD_LOCAL_INITIALIZER_CAN_BE_MADE_CONST]);
5958

59+
#[inline]
60+
fn is_thread_local_initializer(
61+
cx: &LateContext<'_>,
62+
fn_kind: rustc_hir::intravisit::FnKind<'_>,
63+
span: rustc_span::Span,
64+
) -> Option<bool> {
65+
let macro_def_id = span.source_callee()?.macro_def_id?;
66+
Some(
67+
cx.tcx.is_diagnostic_item(thread_local_macro, macro_def_id)
68+
&& matches!(fn_kind, intravisit::FnKind::ItemFn(..)),
69+
)
70+
}
71+
72+
#[inline]
73+
fn initializer_can_be_made_const(cx: &LateContext<'_>, defid: rustc_span::def_id::DefId, msrv: &Msrv) -> bool {
74+
// Building MIR for `fn`s with unsatisfiable preds results in ICE.
75+
if !fn_has_unsatisfiable_preds(cx, defid)
76+
&& let mir = cx.tcx.optimized_mir(defid)
77+
&& let Ok(()) = is_min_const_fn(cx.tcx, mir, msrv)
78+
{
79+
return true;
80+
}
81+
false
82+
}
83+
6084
impl<'tcx> LateLintPass<'tcx> for ThreadLocalInitializerCanBeMadeConst {
6185
fn check_fn(
6286
&mut self,
@@ -65,31 +89,31 @@ impl<'tcx> LateLintPass<'tcx> for ThreadLocalInitializerCanBeMadeConst {
6589
_: &'tcx rustc_hir::FnDecl<'tcx>,
6690
body: &'tcx rustc_hir::Body<'tcx>,
6791
span: rustc_span::Span,
68-
defid: rustc_span::def_id::LocalDefId,
92+
local_defid: rustc_span::def_id::LocalDefId,
6993
) {
70-
if in_external_macro(cx.sess(), span)
71-
&& let Some(callee) = span.source_callee()
72-
&& let Some(macro_def_id) = callee.macro_def_id
73-
&& cx.tcx.is_diagnostic_item(thread_local_macro, macro_def_id)
74-
&& let intravisit::FnKind::ItemFn(..) = fn_kind
75-
// Building MIR for `fn`s with unsatisfiable preds results in ICE.
76-
&& !fn_has_unsatisfiable_preds(cx, defid.to_def_id())
77-
&& let mir = cx.tcx.optimized_mir(defid.to_def_id())
78-
&& let Ok(()) = is_min_const_fn(cx.tcx, mir, &self.msrv)
79-
// this is the `__init` function emitted by the `thread_local!` macro
80-
// when the `const` keyword is not used. We avoid checking the `__init` directly
81-
// as that is not a public API.
82-
// we know that the function is const-qualifiable, so now we need only to get the
83-
// initializer expression to span-lint it.
94+
let defid = local_defid.to_def_id();
95+
if is_thread_local_initializer(cx, fn_kind, span).unwrap_or(false)
96+
// Some implementations of `thread_local!` include an initializer fn.
97+
// In the case of a const initializer, the init fn is also const,
98+
// so we can skip the lint in that case. This occurs only on some
99+
// backends due to conditional compilation:
100+
// https://doc.rust-lang.org/src/std/sys/common/thread_local/mod.rs.html
101+
// for details on this issue, see:
102+
// https://github.com/rust-lang/rust-clippy/pull/12276
103+
&& !cx.tcx.is_const_fn(defid)
104+
&& initializer_can_be_made_const(cx, defid, &self.msrv)
105+
// we know that the function is const-qualifiable, so now
106+
// we need only to get the initializer expression to span-lint it.
84107
&& let ExprKind::Block(block, _) = body.value.kind
85-
&& let Some(ret_expr) = block.expr
108+
&& let Some(unpeeled) = block.expr
109+
&& let ret_expr = peel_blocks(unpeeled)
86110
&& let initializer_snippet = snippet(cx, ret_expr.span, "thread_local! { ... }")
87111
&& initializer_snippet != "thread_local! { ... }"
88112
{
89113
span_lint_and_sugg(
90114
cx,
91115
THREAD_LOCAL_INITIALIZER_CAN_BE_MADE_CONST,
92-
ret_expr.span,
116+
unpeeled.span,
93117
"initializer for `thread_local` value can be made `const`",
94118
"replace with",
95119
format!("const {{ {initializer_snippet} }}"),

tests/ui/thread_local_initializer_can_be_made_const.fixed

+7
Original file line numberDiff line numberDiff line change
@@ -27,4 +27,11 @@ fn main() {
2727
}
2828
//~^^^^ ERROR: initializer for `thread_local` value can be made `const`
2929
//~^^^ ERROR: initializer for `thread_local` value can be made `const`
30+
31+
thread_local! {
32+
static PEEL_ME: i32 = const { 1 };
33+
//~^ ERROR: initializer for `thread_local` value can be made `const`
34+
static PEEL_ME_MANY: i32 = const { { let x = 1; x * x } };
35+
//~^ ERROR: initializer for `thread_local` value can be made `const`
36+
}
3037
}

tests/ui/thread_local_initializer_can_be_made_const.rs

+7
Original file line numberDiff line numberDiff line change
@@ -27,4 +27,11 @@ fn main() {
2727
}
2828
//~^^^^ ERROR: initializer for `thread_local` value can be made `const`
2929
//~^^^ ERROR: initializer for `thread_local` value can be made `const`
30+
31+
thread_local! {
32+
static PEEL_ME: i32 = { 1 };
33+
//~^ ERROR: initializer for `thread_local` value can be made `const`
34+
static PEEL_ME_MANY: i32 = { let x = 1; x * x };
35+
//~^ ERROR: initializer for `thread_local` value can be made `const`
36+
}
3037
}

tests/ui/thread_local_initializer_can_be_made_const.stderr

+13-1
Original file line numberDiff line numberDiff line change
@@ -25,5 +25,17 @@ error: initializer for `thread_local` value can be made `const`
2525
LL | static BUF_4_CAN_BE_MADE_CONST: RefCell<String> = RefCell::new(String::new());
2626
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace with: `const { RefCell::new(String::new()) }`
2727

28-
error: aborting due to 4 previous errors
28+
error: initializer for `thread_local` value can be made `const`
29+
--> tests/ui/thread_local_initializer_can_be_made_const.rs:32:31
30+
|
31+
LL | static PEEL_ME: i32 = { 1 };
32+
| ^^^^^ help: replace with: `const { 1 }`
33+
34+
error: initializer for `thread_local` value can be made `const`
35+
--> tests/ui/thread_local_initializer_can_be_made_const.rs:34:36
36+
|
37+
LL | static PEEL_ME_MANY: i32 = { let x = 1; x * x };
38+
| ^^^^^^^^^^^^^^^^^^^^ help: replace with: `const { { let x = 1; x * x } }`
39+
40+
error: aborting due to 6 previous errors
2941

0 commit comments

Comments
 (0)