Skip to content

Commit 206b1a1

Browse files
Quinn Sinclairblyxyas
Quinn Sinclair
authored andcommitted
Threadlocal_initializer_can_be_made_const will not trigger for unreachable initializers
This commit introduces a check to ensure that the lint won't trigger when the initializer is unreachable, such as: ``` thread_local! { static STATE: Cell<usize> = panic!(); } ``` This is achieved by looking at the unpeeled initializer expression and ensuring that the parent macro is not `panic!()`, `todo!()`, `unreachable!()`, `unimplemented!()`. fixes rust-lang#12637 changelog: [`threadlocal_initializer_can_be_made_const`] will no longer trigger on `unreachable` macros.
1 parent 2795a60 commit 206b1a1

3 files changed

+95
-7
lines changed

clippy_lints/src/thread_local_initializer_can_be_made_const.rs

+31-5
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,14 @@
11
use clippy_config::msrvs::{self, Msrv};
22
use clippy_utils::diagnostics::span_lint_and_sugg;
3+
use clippy_utils::macros::macro_backtrace;
34
use clippy_utils::qualify_min_const_fn::is_min_const_fn;
45
use clippy_utils::source::snippet;
56
use clippy_utils::{fn_has_unsatisfiable_preds, peel_blocks};
67
use rustc_errors::Applicability;
7-
use rustc_hir::{intravisit, ExprKind};
8+
use rustc_hir::{intravisit, Expr, ExprKind};
89
use rustc_lint::{LateContext, LateLintPass};
910
use rustc_session::impl_lint_pass;
10-
use rustc_span::sym::thread_local_macro;
11+
use rustc_span::sym::{self, thread_local_macro};
1112

1213
declare_clippy_lint! {
1314
/// ### What it does
@@ -69,6 +70,26 @@ fn is_thread_local_initializer(
6970
)
7071
}
7172

73+
fn is_unreachable(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
74+
if let Some(macro_call) = macro_backtrace(expr.span).next()
75+
&& let Some(diag_name) = cx.tcx.get_diagnostic_name(macro_call.def_id)
76+
{
77+
return (matches!(
78+
diag_name,
79+
sym::core_panic_macro
80+
| sym::std_panic_macro
81+
| sym::core_panic_2015_macro
82+
| sym::std_panic_2015_macro
83+
| sym::core_panic_2021_macro
84+
) && !cx.tcx.hir().is_inside_const_context(expr.hir_id))
85+
|| matches!(
86+
diag_name,
87+
sym::unimplemented_macro | sym::todo_macro | sym::unreachable_macro | sym::unreachable_2015_macro
88+
);
89+
}
90+
false
91+
}
92+
7293
#[inline]
7394
fn initializer_can_be_made_const(cx: &LateContext<'_>, defid: rustc_span::def_id::DefId, msrv: &Msrv) -> bool {
7495
// Building MIR for `fn`s with unsatisfiable preds results in ICE.
@@ -102,12 +123,17 @@ impl<'tcx> LateLintPass<'tcx> for ThreadLocalInitializerCanBeMadeConst {
102123
// for details on this issue, see:
103124
// https://github.com/rust-lang/rust-clippy/pull/12276
104125
&& !cx.tcx.is_const_fn(defid)
105-
&& initializer_can_be_made_const(cx, defid, &self.msrv)
106-
// we know that the function is const-qualifiable, so now
107-
// we need only to get the initializer expression to span-lint it.
108126
&& let ExprKind::Block(block, _) = body.value.kind
109127
&& let Some(unpeeled) = block.expr
110128
&& let ret_expr = peel_blocks(unpeeled)
129+
// A common pattern around threadlocal! is to make the value unreachable
130+
// to force an initialization before usage
131+
// https://github.com/rust-lang/rust-clippy/issues/12637
132+
// we ensure that this is reachable before we check in mir
133+
&& !is_unreachable(cx, ret_expr)
134+
&& initializer_can_be_made_const(cx, defid, &self.msrv)
135+
// we know that the function is const-qualifiable, so now
136+
// we need only to get the initializer expression to span-lint it.
111137
&& let initializer_snippet = snippet(cx, ret_expr.span, "thread_local! { ... }")
112138
&& initializer_snippet != "thread_local! { ... }"
113139
{

tests/ui/thread_local_initializer_can_be_made_const.fixed

+32-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
#![warn(clippy::thread_local_initializer_can_be_made_const)]
22

3-
use std::cell::RefCell;
3+
use std::cell::{Cell, RefCell};
44

55
fn main() {
66
// lint and suggest const
@@ -36,6 +36,37 @@ fn main() {
3636
}
3737
}
3838

39+
fn issue_12637() {
40+
/// The set methods on LocalKey<Cell<T>> and LocalKey<RefCell<T>> are
41+
/// guaranteed to bypass the thread_local's initialization expression.
42+
/// See rust-lang/rust#92122. Thus, = panic!() is a useful idiom for
43+
/// forcing the use of set on each thread before it accesses the thread local in any other
44+
/// manner.
45+
thread_local! {
46+
static STATE_12637_PANIC: Cell<usize> = panic!();
47+
}
48+
STATE_12637_PANIC.set(9);
49+
println!("{}", STATE_12637_PANIC.get());
50+
51+
thread_local! {
52+
static STATE_12637_TODO: Cell<usize> = todo!();
53+
}
54+
STATE_12637_TODO.set(9);
55+
println!("{}", STATE_12637_TODO.get());
56+
57+
thread_local! {
58+
static STATE_12637_UNIMPLEMENTED: Cell<usize> = unimplemented!();
59+
}
60+
STATE_12637_UNIMPLEMENTED.set(9);
61+
println!("{}", STATE_12637_UNIMPLEMENTED.get());
62+
63+
thread_local! {
64+
static STATE_12637_UNREACHABLE: Cell<usize> = unreachable!();
65+
}
66+
STATE_12637_UNREACHABLE.set(9);
67+
println!("{}", STATE_12637_UNREACHABLE.get());
68+
}
69+
3970
#[clippy::msrv = "1.58"]
4071
fn f() {
4172
thread_local! {

tests/ui/thread_local_initializer_can_be_made_const.rs

+32-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
#![warn(clippy::thread_local_initializer_can_be_made_const)]
22

3-
use std::cell::RefCell;
3+
use std::cell::{Cell, RefCell};
44

55
fn main() {
66
// lint and suggest const
@@ -36,6 +36,37 @@ fn main() {
3636
}
3737
}
3838

39+
fn issue_12637() {
40+
/// The set methods on LocalKey<Cell<T>> and LocalKey<RefCell<T>> are
41+
/// guaranteed to bypass the thread_local's initialization expression.
42+
/// See rust-lang/rust#92122. Thus, = panic!() is a useful idiom for
43+
/// forcing the use of set on each thread before it accesses the thread local in any other
44+
/// manner.
45+
thread_local! {
46+
static STATE_12637_PANIC: Cell<usize> = panic!();
47+
}
48+
STATE_12637_PANIC.set(9);
49+
println!("{}", STATE_12637_PANIC.get());
50+
51+
thread_local! {
52+
static STATE_12637_TODO: Cell<usize> = todo!();
53+
}
54+
STATE_12637_TODO.set(9);
55+
println!("{}", STATE_12637_TODO.get());
56+
57+
thread_local! {
58+
static STATE_12637_UNIMPLEMENTED: Cell<usize> = unimplemented!();
59+
}
60+
STATE_12637_UNIMPLEMENTED.set(9);
61+
println!("{}", STATE_12637_UNIMPLEMENTED.get());
62+
63+
thread_local! {
64+
static STATE_12637_UNREACHABLE: Cell<usize> = unreachable!();
65+
}
66+
STATE_12637_UNREACHABLE.set(9);
67+
println!("{}", STATE_12637_UNREACHABLE.get());
68+
}
69+
3970
#[clippy::msrv = "1.58"]
4071
fn f() {
4172
thread_local! {

0 commit comments

Comments
 (0)