diff --git a/compiler/rustc_lint/messages.ftl b/compiler/rustc_lint/messages.ftl index 56a0a3ceebf5a..0731de065aef5 100644 --- a/compiler/rustc_lint/messages.ftl +++ b/compiler/rustc_lint/messages.ftl @@ -53,6 +53,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 810760e9f53e6..48162858dc455 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, @@ -3111,6 +3112,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 49929a0a9bc76..92258a91fa423 100644 --- a/compiler/rustc_lint/src/lib.rs +++ b/compiler/rustc_lint/src/lib.rs @@ -200,6 +200,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 5017ce7caa525..78cae3eb53548 100644 --- a/compiler/rustc_lint/src/lints.rs +++ b/compiler/rustc_lint/src/lints.rs @@ -119,6 +119,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 e2ac746d31497..9b9adad2770c0 100644 --- a/library/core/src/hint.rs +++ b/library/core/src/hint.rs @@ -469,6 +469,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 +