diff --git a/compiler/rustc_lint/messages.ftl b/compiler/rustc_lint/messages.ftl index 867b937d4090d..150ce7a1b36d1 100644 --- a/compiler/rustc_lint/messages.ftl +++ b/compiler/rustc_lint/messages.ftl @@ -58,6 +58,13 @@ lint_builtin_allow_internal_unsafe = lint_builtin_anonymous_params = anonymous parameters are deprecated and will be removed in the next edition .suggestion = try naming the parameter or explicitly ignoring it +lint_builtin_black_box_zst_call = use of `black_box` on the zero-sized type `{$ty}` has no effect + .label = zero-sized value passed here + +lint_builtin_black_box_zst_help = if this is a function, coerce it to a function pointer first + +lint_builtin_black_box_zst_note = zero-sized values have no runtime representation, so the compiler can ignore the call + lint_builtin_clashing_extern_diff_name = `{$this}` redeclares `{$orig}` with a different signature .previous_decl_label = `{$orig}` previously declared here .mismatch_label = this signature doesn't match the previous declaration diff --git a/compiler/rustc_lint/src/builtin.rs b/compiler/rustc_lint/src/builtin.rs index e2a061cab680a..ce602bcb2f9d6 100644 --- a/compiler/rustc_lint/src/builtin.rs +++ b/compiler/rustc_lint/src/builtin.rs @@ -50,16 +50,17 @@ use rustc_trait_selection::traits::{self}; use crate::errors::BuiltinEllipsisInclusiveRangePatterns; use crate::lints::{ - BuiltinAnonymousParams, BuiltinConstNoMangle, BuiltinDerefNullptr, BuiltinDoubleNegations, - BuiltinDoubleNegationsAddParens, BuiltinEllipsisInclusiveRangePatternsLint, - BuiltinExplicitOutlives, BuiltinExplicitOutlivesSuggestion, BuiltinFeatureIssueNote, - BuiltinIncompleteFeatures, BuiltinIncompleteFeaturesHelp, BuiltinInternalFeatures, - BuiltinKeywordIdents, BuiltinMissingCopyImpl, BuiltinMissingDebugImpl, BuiltinMissingDoc, - BuiltinMutablesTransmutes, BuiltinNoMangleGeneric, BuiltinNonShorthandFieldPatterns, - BuiltinSpecialModuleNameUsed, BuiltinTrivialBounds, BuiltinTypeAliasBounds, - BuiltinUngatedAsyncFnTrackCaller, BuiltinUnpermittedTypeInit, BuiltinUnpermittedTypeInitSub, - BuiltinUnreachablePub, BuiltinUnsafe, BuiltinUnstableFeatures, BuiltinUnusedDocComment, - BuiltinUnusedDocCommentSub, BuiltinWhileTrue, InvalidAsmLabel, + BuiltinAnonymousParams, BuiltinBlackBoxZstCall, BuiltinConstNoMangle, BuiltinDerefNullptr, + BuiltinDoubleNegations, BuiltinDoubleNegationsAddParens, + BuiltinEllipsisInclusiveRangePatternsLint, BuiltinExplicitOutlives, + BuiltinExplicitOutlivesSuggestion, BuiltinFeatureIssueNote, BuiltinIncompleteFeatures, + BuiltinIncompleteFeaturesHelp, BuiltinInternalFeatures, BuiltinKeywordIdents, + BuiltinMissingCopyImpl, BuiltinMissingDebugImpl, BuiltinMissingDoc, BuiltinMutablesTransmutes, + BuiltinNoMangleGeneric, BuiltinNonShorthandFieldPatterns, BuiltinSpecialModuleNameUsed, + BuiltinTrivialBounds, BuiltinTypeAliasBounds, BuiltinUngatedAsyncFnTrackCaller, + BuiltinUnpermittedTypeInit, BuiltinUnpermittedTypeInitSub, BuiltinUnreachablePub, + BuiltinUnsafe, BuiltinUnstableFeatures, BuiltinUnusedDocComment, BuiltinUnusedDocCommentSub, + BuiltinWhileTrue, InvalidAsmLabel, }; use crate::{ EarlyContext, EarlyLintPass, LateContext, LateLintPass, Level, LintContext, @@ -3110,6 +3111,74 @@ impl<'tcx> LateLintPass<'tcx> for AsmLabels { } } +declare_lint! { + /// The `black_box_zst_calls` lint detects calls to `core::hint::black_box` + /// with a zero-sized type (ZST). + /// + /// Zero-sized types (like `()`, unit structs, or function items) have no + /// runtime representation, so `black_box` cannot block the optimizer because + /// there is no value to hide. + /// + /// ### Example + /// + /// ```rust,compile_fail + /// #![deny(black_box_zst_calls)] + /// use std::hint::black_box; + /// + /// fn main() { + /// // This does nothing because `()` has no size. + /// black_box(()); + /// } + /// ``` + /// + /// {{produces}} + /// + /// ### Explanation + /// + /// `black_box` is intended to treat a value as "unknown" to the optimizer. + /// However, if the value has size 0, the optimizer knows exactly what it is + /// (it's nothing!) and can optimize around it anyway. + pub BLACK_BOX_ZST_CALLS, + Warn, + "usage of `black_box` with zero-sized types" +} + +declare_lint_pass!(BlackBoxZstCalls => [BLACK_BOX_ZST_CALLS]); + +impl<'tcx> LateLintPass<'tcx> for BlackBoxZstCalls { + fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &hir::Expr<'tcx>) { + let hir::ExprKind::Call(callee, args) = expr.kind else { return }; + if args.len() != 1 { + return; + } + + let hir::ExprKind::Path(ref qpath) = callee.kind else { return }; + let Some(def_id) = cx.qpath_res(qpath, callee.hir_id).opt_def_id() else { return }; + + if !cx.tcx.is_diagnostic_item(sym::black_box, def_id) { + return; + } + + let arg = &args[0]; + let arg_ty = cx.typeck_results().expr_ty_adjusted(arg); + + if !is_zst(cx, arg_ty) { + return; + } + + let ty_name = with_no_trimmed_paths!(arg_ty.to_string()); + cx.emit_span_lint( + BLACK_BOX_ZST_CALLS, + expr.span, + BuiltinBlackBoxZstCall { arg_span: arg.span, ty: ty_name }, + ); + } +} + +fn is_zst<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> bool { + cx.tcx.layout_of(cx.typing_env().as_query_input(ty)).is_ok_and(|layout| layout.is_zst()) +} + declare_lint! { /// The `special_module_name` lint detects module /// declarations for files that have a special meaning. diff --git a/compiler/rustc_lint/src/lib.rs b/compiler/rustc_lint/src/lib.rs index 80b32645a8953..e3ed8005a4371 100644 --- a/compiler/rustc_lint/src/lib.rs +++ b/compiler/rustc_lint/src/lib.rs @@ -201,6 +201,7 @@ late_lint_methods!( InvalidFromUtf8: InvalidFromUtf8, VariantSizeDifferences: VariantSizeDifferences, PathStatements: PathStatements, + BlackBoxZstCalls: BlackBoxZstCalls, LetUnderscore: LetUnderscore, InvalidReferenceCasting: InvalidReferenceCasting, ImplicitAutorefs: ImplicitAutorefs, diff --git a/compiler/rustc_lint/src/lints.rs b/compiler/rustc_lint/src/lints.rs index a20d90e1227e9..810e92bd50587 100644 --- a/compiler/rustc_lint/src/lints.rs +++ b/compiler/rustc_lint/src/lints.rs @@ -118,6 +118,16 @@ pub(crate) struct BuiltinNonShorthandFieldPatterns { pub prefix: &'static str, } +#[derive(LintDiagnostic)] +#[diag(lint_builtin_black_box_zst_call)] +#[note(lint_builtin_black_box_zst_note)] +#[help(lint_builtin_black_box_zst_help)] +pub(crate) struct BuiltinBlackBoxZstCall { + #[label] + pub arg_span: Span, + pub ty: String, +} + #[derive(LintDiagnostic)] pub(crate) enum BuiltinUnsafe { #[diag(lint_builtin_allow_internal_unsafe)] diff --git a/library/core/src/hint.rs b/library/core/src/hint.rs index 4c050b49bf7eb..6ae5bc2f37e59 100644 --- a/library/core/src/hint.rs +++ b/library/core/src/hint.rs @@ -478,6 +478,7 @@ pub fn spin_loop() { #[inline] #[stable(feature = "bench_black_box", since = "1.66.0")] #[rustc_const_stable(feature = "const_black_box", since = "1.86.0")] +#[rustc_diagnostic_item = "black_box"] pub const fn black_box(dummy: T) -> T { crate::intrinsics::black_box(dummy) } diff --git a/library/std/src/sys/backtrace.rs b/library/std/src/sys/backtrace.rs index 858a95882b39f..b6ec143440539 100644 --- a/library/std/src/sys/backtrace.rs +++ b/library/std/src/sys/backtrace.rs @@ -182,6 +182,7 @@ where let result = f(); // prevent this frame from being tail-call optimised away + #[allow(black_box_zst_calls)] crate::hint::black_box(()); result diff --git a/tests/ui/lint/lint-black-box-zst-call.rs b/tests/ui/lint/lint-black-box-zst-call.rs new file mode 100644 index 0000000000000..b684c9a828d4a --- /dev/null +++ b/tests/ui/lint/lint-black-box-zst-call.rs @@ -0,0 +1,13 @@ +#![deny(black_box_zst_calls)] + +use std::hint::black_box; + +fn add(a: u32, b: u32) -> u32 { + a + b +} + +fn main() { + let add_bb = black_box(add); + //~^ ERROR use of `black_box` on the zero-sized type + let _ = add_bb(1, 2); +} diff --git a/tests/ui/lint/lint-black-box-zst-call.stderr b/tests/ui/lint/lint-black-box-zst-call.stderr new file mode 100644 index 0000000000000..36921f91403b8 --- /dev/null +++ b/tests/ui/lint/lint-black-box-zst-call.stderr @@ -0,0 +1,18 @@ +error: use of `black_box` on the zero-sized type `fn(u32, u32) -> u32 {add}` has no effect + --> $DIR/lint-black-box-zst-call.rs:10:18 + | +LL | let add_bb = black_box(add); + | ^^^^^^^^^^---^ + | | + | zero-sized value passed here + | + = note: zero-sized values have no runtime representation, so the compiler can ignore the call + = help: if this is a function, coerce it to a function pointer first +note: the lint level is defined here + --> $DIR/lint-black-box-zst-call.rs:1:9 + | +LL | #![deny(black_box_zst_calls)] + | ^^^^^^^^^^^^^^^^^^^ + +error: aborting due to 1 previous error +