From 404811b77b929a7a324b24af568feeaaf1ac3e61 Mon Sep 17 00:00:00 2001 From: xonx4l Date: Tue, 16 Dec 2025 02:51:07 +0530 Subject: [PATCH 1/6] feat:lint-black-box --- compiler/rustc_lint/messages.ftl | 7 ++ compiler/rustc_lint/src/builtin.rs | 93 +++++++++++++++++--- compiler/rustc_lint/src/lib.rs | 1 + compiler/rustc_lint/src/lints.rs | 10 +++ library/core/src/hint.rs | 1 + tests/ui/lint/lint-black-box-zst-call.rs | 13 +++ tests/ui/lint/lint-black-box-zst-call.stderr | 18 ++++ 7 files changed, 133 insertions(+), 10 deletions(-) create mode 100644 tests/ui/lint/lint-black-box-zst-call.rs create mode 100644 tests/ui/lint/lint-black-box-zst-call.stderr diff --git a/compiler/rustc_lint/messages.ftl b/compiler/rustc_lint/messages.ftl index 867b937d4090d..29561a9ff768b 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 = `black_box` on zero-sized callable `{$ty}` has no effect on call opacity + .label = zero-sized callable passed here + +lint_builtin_black_box_zst_help = coerce to a function pointer and call `black_box` on that pointer instead + +lint_builtin_black_box_zst_note = zero-sized callable values have no runtime representation, so the call still targets the original function directly + 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..3310e07af4db2 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,78 @@ impl<'tcx> LateLintPass<'tcx> for AsmLabels { } } +declare_lint! { + /// The `black_box_zst_call` lint detects calls to `core::hint::black_box` + /// where the argument is a zero-sized callable (e.g. a function item or + /// a capture-less closure). These values have no runtime representation, + /// so the black boxing does not make subsequent calls opaque to the + /// optimizer. + /// + /// ### Example + /// + /// ```rust,compile_fail + /// use std::hint::black_box; + /// + /// fn add(a: u32, b: u32) -> u32 { + /// a + b + /// } + /// + /// fn main() { + /// let add_bb = black_box(add); + /// let _ = add_bb(1, 2); + /// } + /// ``` + /// + /// {{produces}} + /// + /// ### Explanation + /// + /// Function items and capture-less closures are zero-sized. Passing them + /// to `black_box` does not force the optimizer to treat the subsequent + /// call as opaque. Coerce the callable to a function pointer and black_box + /// that pointer instead. + pub BLACK_BOX_ZST_CALLS, + Warn, + "calling `black_box` on zero-sized callables has no effect on opacity" +} + +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_callable_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_callable_zst<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> bool { + matches!(ty.kind(), ty::FnDef(..) | ty::Closure(..)) + && 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/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..ccf6257937d2e --- /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 `black_box` on zero-sized callable + 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..5c4e121f92f99 --- /dev/null +++ b/tests/ui/lint/lint-black-box-zst-call.stderr @@ -0,0 +1,18 @@ +error: `black_box` on zero-sized callable `fn(u32, u32) -> u32 {add}` has no effect on call opacity + --> $DIR/lint-black-box-zst-call.rs:10:18 + | +LL | let add_bb = black_box(add); + | ^^^^^^^^^^---^ + | | + | zero-sized callable passed here + | + = note: zero-sized callable values have no runtime representation, so the call still targets the original function directly + = help: coerce to a function pointer and call `black_box` on that pointer instead +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 + From c00ca79df9fcccad1f11553b908c230bf30ade8f Mon Sep 17 00:00:00 2001 From: xonx <119700621+xonx4l@users.noreply.github.com> Date: Fri, 23 Jan 2026 11:47:14 +0000 Subject: [PATCH 2/6] add suggestion --- compiler/rustc_lint/src/builtin.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/rustc_lint/src/builtin.rs b/compiler/rustc_lint/src/builtin.rs index 3310e07af4db2..4b68a71a10ca7 100644 --- a/compiler/rustc_lint/src/builtin.rs +++ b/compiler/rustc_lint/src/builtin.rs @@ -3112,7 +3112,7 @@ impl<'tcx> LateLintPass<'tcx> for AsmLabels { } declare_lint! { - /// The `black_box_zst_call` lint detects calls to `core::hint::black_box` + /// The `black_box_zst_calls` lint detects calls to `core::hint::black_box` /// where the argument is a zero-sized callable (e.g. a function item or /// a capture-less closure). These values have no runtime representation, /// so the black boxing does not make subsequent calls opaque to the From 3195e8f9e6f446b51853176c185bec257315cf89 Mon Sep 17 00:00:00 2001 From: xonx <119700621+xonx4l@users.noreply.github.com> Date: Sat, 24 Jan 2026 12:38:13 +0000 Subject: [PATCH 3/6] fix ci --- compiler/rustc_lint/src/builtin.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/compiler/rustc_lint/src/builtin.rs b/compiler/rustc_lint/src/builtin.rs index 4b68a71a10ca7..f0a0170885e4a 100644 --- a/compiler/rustc_lint/src/builtin.rs +++ b/compiler/rustc_lint/src/builtin.rs @@ -3121,6 +3121,7 @@ declare_lint! { /// ### Example /// /// ```rust,compile_fail + /// #![deny(black_box_zst_calls)] /// use std::hint::black_box; /// /// fn add(a: u32, b: u32) -> u32 { From cb9c7c74bc581ed7a354abd13d7ad5affdd3de00 Mon Sep 17 00:00:00 2001 From: xonx <119700621+xonx4l@users.noreply.github.com> Date: Wed, 28 Jan 2026 10:10:58 +0000 Subject: [PATCH 4/6] Update error message --- compiler/rustc_lint/messages.ftl | 8 ++++---- tests/ui/lint/lint-black-box-zst-call.rs | 2 +- tests/ui/lint/lint-black-box-zst-call.stderr | 8 ++++---- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/compiler/rustc_lint/messages.ftl b/compiler/rustc_lint/messages.ftl index 29561a9ff768b..150ce7a1b36d1 100644 --- a/compiler/rustc_lint/messages.ftl +++ b/compiler/rustc_lint/messages.ftl @@ -58,12 +58,12 @@ 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 = `black_box` on zero-sized callable `{$ty}` has no effect on call opacity - .label = zero-sized callable passed here +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 = coerce to a function pointer and call `black_box` on that pointer instead +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 callable values have no runtime representation, so the call still targets the original function directly +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 diff --git a/tests/ui/lint/lint-black-box-zst-call.rs b/tests/ui/lint/lint-black-box-zst-call.rs index ccf6257937d2e..b684c9a828d4a 100644 --- a/tests/ui/lint/lint-black-box-zst-call.rs +++ b/tests/ui/lint/lint-black-box-zst-call.rs @@ -8,6 +8,6 @@ fn add(a: u32, b: u32) -> u32 { fn main() { let add_bb = black_box(add); - //~^ ERROR `black_box` on zero-sized callable + //~^ 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 index 5c4e121f92f99..36921f91403b8 100644 --- a/tests/ui/lint/lint-black-box-zst-call.stderr +++ b/tests/ui/lint/lint-black-box-zst-call.stderr @@ -1,13 +1,13 @@ -error: `black_box` on zero-sized callable `fn(u32, u32) -> u32 {add}` has no effect on call opacity +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 callable passed here + | zero-sized value passed here | - = note: zero-sized callable values have no runtime representation, so the call still targets the original function directly - = help: coerce to a function pointer and call `black_box` on that pointer instead + = 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 | From a05eb7c76c61a021742293a1de175915194f3a82 Mon Sep 17 00:00:00 2001 From: xonx <119700621+xonx4l@users.noreply.github.com> Date: Fri, 30 Jan 2026 12:13:09 +0000 Subject: [PATCH 5/6] add:broader terms --- compiler/rustc_lint/src/builtin.rs | 33 +++++++++++++----------------- 1 file changed, 14 insertions(+), 19 deletions(-) diff --git a/compiler/rustc_lint/src/builtin.rs b/compiler/rustc_lint/src/builtin.rs index f0a0170885e4a..ce602bcb2f9d6 100644 --- a/compiler/rustc_lint/src/builtin.rs +++ b/compiler/rustc_lint/src/builtin.rs @@ -3113,10 +3113,11 @@ impl<'tcx> LateLintPass<'tcx> for AsmLabels { declare_lint! { /// The `black_box_zst_calls` lint detects calls to `core::hint::black_box` - /// where the argument is a zero-sized callable (e.g. a function item or - /// a capture-less closure). These values have no runtime representation, - /// so the black boxing does not make subsequent calls opaque to the - /// optimizer. + /// 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 /// @@ -3124,13 +3125,9 @@ declare_lint! { /// #![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); - /// let _ = add_bb(1, 2); + /// // This does nothing because `()` has no size. + /// black_box(()); /// } /// ``` /// @@ -3138,13 +3135,12 @@ declare_lint! { /// /// ### Explanation /// - /// Function items and capture-less closures are zero-sized. Passing them - /// to `black_box` does not force the optimizer to treat the subsequent - /// call as opaque. Coerce the callable to a function pointer and black_box - /// that pointer instead. + /// `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, - "calling `black_box` on zero-sized callables has no effect on opacity" + "usage of `black_box` with zero-sized types" } declare_lint_pass!(BlackBoxZstCalls => [BLACK_BOX_ZST_CALLS]); @@ -3166,7 +3162,7 @@ impl<'tcx> LateLintPass<'tcx> for BlackBoxZstCalls { let arg = &args[0]; let arg_ty = cx.typeck_results().expr_ty_adjusted(arg); - if !is_callable_zst(cx, arg_ty) { + if !is_zst(cx, arg_ty) { return; } @@ -3179,9 +3175,8 @@ impl<'tcx> LateLintPass<'tcx> for BlackBoxZstCalls { } } -fn is_callable_zst<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> bool { - matches!(ty.kind(), ty::FnDef(..) | ty::Closure(..)) - && cx.tcx.layout_of(cx.typing_env().as_query_input(ty)).is_ok_and(|layout| layout.is_zst()) +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! { From 43bc2c5d271b620d75fd676d04f9b937693e0a9a Mon Sep 17 00:00:00 2001 From: xonx <119700621+xonx4l@users.noreply.github.com> Date: Sat, 31 Jan 2026 19:49:34 +0000 Subject: [PATCH 6/6] add allow --- library/std/src/sys/backtrace.rs | 1 + 1 file changed, 1 insertion(+) 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