-
Notifications
You must be signed in to change notification settings - Fork 1.7k
Add a new lint to check for defining of read-only arrays on stack #12854
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
Changes from all commits
ac81677
33813eb
6745434
aa31918
40765c4
f1d40be
049338b
af4efdb
249ddae
81bc931
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,110 @@ | ||
use clippy_utils::diagnostics::span_lint_and_sugg; | ||
use clippy_utils::source::snippet_with_applicability; | ||
use clippy_utils::ty::{implements_trait, is_type_lang_item}; | ||
use clippy_utils::visitors::is_const_evaluatable; | ||
use hir::{BindingMode, ExprKind, LangItem, PatKind, Stmt, StmtKind}; | ||
use rustc_errors::Applicability; | ||
use rustc_hir as hir; | ||
use rustc_lint::{LateContext, LateLintPass}; | ||
use rustc_session::declare_lint_pass; | ||
|
||
declare_clippy_lint! { | ||
/// ### What it does | ||
/// Checks for defining of read-only arrays on stack. | ||
/// | ||
/// ### Why is this bad? | ||
/// `let array` puts array on the stack. As as intended, the compiler will | ||
/// initialize the array directly on the stack, which might make the | ||
/// generated binary bigger and slower. | ||
/// | ||
/// A read-only array should be defined as a `static` item or used a trick | ||
/// to made it into `.rodata` section of the compiled program. The use of | ||
/// the trick (`*&<array literal>`) will make rustc static-promote the | ||
/// array literal. Which means that the array now lives in the read-only | ||
/// section of the generated binary. The downside of the trick is that | ||
/// it is non-ergonomic and may not be very clear to readers of the code. | ||
/// | ||
/// ### Known problems | ||
/// | ||
/// ### Example | ||
/// ```rust,ignore | ||
/// let a: [u32; N] = [1, 3, 5, ...]; | ||
/// ``` | ||
/// | ||
/// Use instead: | ||
/// ```rust,ignore | ||
/// let a: [u32; N] = *&[1, 3, 5, ...]; | ||
/// // or | ||
/// static A: [u32; N] = [1, 3, 5, ...]; | ||
/// ``` | ||
#[clippy::version = "1.80.0"] | ||
pub LET_ARR_CONST, | ||
perf, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I feel like this should be (at most) a pedantic lint, at least for such a small threshold of 16 bytes. It seems like a micro optimization in cases like this one where it's just two string slices. It would also have a fair number of false positives when struct Buffer([MaybeUninit<u8>; 40]);
impl Buffer {
pub fn new() -> Self {
let bytes = [MaybeUninit::uninit(); 40];
Self(bytes)
}
} I don't think a static would really help here. There's no initialization code involved (taken from the itoa crate) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Thanks for letting me know. I haven't considered this case. I would try to exclude this in code. And for threshold of 16 bytes, I would try to come up with a micro benchmark. |
||
"declare a read-only array on stack" | ||
} | ||
|
||
declare_lint_pass!(LetArrConst => [LET_ARR_CONST]); | ||
|
||
// FIXME: See also LARGE_CONST_ARRAYS, LARGE_STACK_ARRAYS. | ||
impl<'tcx> LateLintPass<'tcx> for LetArrConst { | ||
fn check_stmt(&mut self, cx: &LateContext<'tcx>, stmt: &'tcx Stmt<'_>) { | ||
// let pat: ty = init; | ||
if let StmtKind::Let(local) = stmt.kind | ||
&& let PatKind::Binding(BindingMode::NONE, _, _name, None) = local.pat.kind | ||
&& let Some(init) = local.init | ||
&& !init.span.from_expansion() | ||
{ | ||
// LLVM optimizes the load of 16 byte as a single `mov`. | ||
// Bigger values make more `mov` instructions generated. | ||
// While changing code as this lint suggests, it becomes | ||
// a single load (`lea`) of an address in `.rodata`. | ||
const STACK_THRESHOLD: u64 = 16; | ||
// lint only `if size_of(init) > STACK_THRESHOLD` | ||
let ty = cx.typeck_results().expr_ty(init); | ||
if let Ok(layout) = cx.tcx.layout_of(cx.param_env.and(ty)) | ||
&& let size = layout.layout.size() | ||
&& size.bytes() <= STACK_THRESHOLD | ||
{ | ||
return; | ||
} | ||
|
||
let mut applicability = Applicability::MachineApplicable; | ||
let lang_items = cx.tcx.lang_items(); | ||
let Some(copy_id) = lang_items.copy_trait() else { | ||
return; | ||
}; | ||
|
||
// if init is [<Copy type>; N] | ||
if let ExprKind::Repeat(..) = init.kind { | ||
// `init` optimized as the same code. | ||
return; | ||
} | ||
|
||
// if init is [1, 2, 3, ...] | ||
let ExprKind::Array(items @ [ref expr, ..]) = init.kind else { | ||
return; | ||
}; | ||
|
||
let ty = cx.typeck_results().expr_ty(expr); | ||
// skip MaybeUnint and ManuallyDrop types | ||
if is_type_lang_item(cx, ty, LangItem::MaybeUninit) || is_type_lang_item(cx, ty, LangItem::ManuallyDrop) { | ||
return; | ||
} | ||
|
||
if implements_trait(cx, ty, copy_id, &[]) && items.iter().all(|expr| is_const_evaluatable(cx, expr)) { | ||
let msg = "using `static` to push the array to read-only section of program or try"; | ||
let snippet = snippet_with_applicability(cx, init.span, "_", &mut applicability); | ||
let sugg = format!("*&{snippet}"); | ||
span_lint_and_sugg( | ||
cx, | ||
LET_ARR_CONST, | ||
init.span, | ||
"declaring a read-only array on the stack", | ||
msg, | ||
sugg, | ||
applicability, | ||
); | ||
} | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,5 @@ | ||
#![warn(clippy::large_futures)] | ||
#![allow(clippy::let_arr_const)] | ||
|
||
fn main() {} | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,5 @@ | ||
#![warn(clippy::large_futures)] | ||
#![allow(clippy::let_arr_const)] | ||
|
||
fn main() {} | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,5 @@ | ||
#![warn(clippy::useless_vec)] | ||
#![allow(clippy::let_arr_const)] | ||
|
||
fn main() { | ||
let x = [0u8; 500]; | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,5 @@ | ||
#![warn(clippy::useless_vec)] | ||
#![allow(clippy::let_arr_const)] | ||
|
||
fn main() { | ||
let x = vec![0u8; 500]; | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.