1+ use std:: collections:: hash_map:: Entry ;
2+
13use arrayvec:: ArrayVec ;
24use clippy_config:: Conf ;
3- use clippy_utils:: diagnostics:: { span_lint_and_sugg, span_lint_and_then} ;
5+ use clippy_utils:: diagnostics:: { span_lint , span_lint_and_sugg, span_lint_and_then} ;
46use clippy_utils:: macros:: {
57 FormatArgsStorage , FormatParamUsage , MacroCall , find_format_arg_expr, format_arg_removal_span,
68 format_placeholder_format_span, is_assert_macro, is_format_macro, is_panic, matching_root_macro_call,
@@ -22,10 +24,12 @@ use rustc_errors::SuggestionStyle::{CompletelyHidden, ShowCode};
2224use rustc_hir:: { Expr , ExprKind , LangItem } ;
2325use rustc_lint:: { LateContext , LateLintPass , LintContext } ;
2426use rustc_middle:: ty:: adjustment:: { Adjust , Adjustment } ;
25- use rustc_middle:: ty:: { List , Ty , TyCtxt } ;
27+ use rustc_middle:: ty:: { self , GenericArg , List , TraitRef , Ty , TyCtxt , Upcast } ;
2628use rustc_session:: impl_lint_pass;
2729use rustc_span:: edition:: Edition :: Edition2021 ;
2830use rustc_span:: { Span , Symbol , sym} ;
31+ use rustc_trait_selection:: infer:: TyCtxtInferExt ;
32+ use rustc_trait_selection:: traits:: { Obligation , ObligationCause , Selection , SelectionContext } ;
2933
3034declare_clippy_lint ! {
3135 /// ### What it does
@@ -194,12 +198,41 @@ declare_clippy_lint! {
194198 "use of a format specifier that has no effect"
195199}
196200
201+ declare_clippy_lint ! {
202+ /// ### What it does
203+ /// Detects [pointer format] as well as `Debug` formatting of raw pointers or function pointers
204+ /// or any types that have a derived `Debug` impl that recursively contains them.
205+ ///
206+ /// ### Why restrict this?
207+ /// The addresses are only useful in very specific contexts, and certain projects may want to keep addresses of
208+ /// certain data structures or functions from prying hacker eyes as an additional line of security.
209+ ///
210+ /// ### Known problems
211+ /// The lint currently only looks through derived `Debug` implementations. Checking whether a manual
212+ /// implementation prints an address is left as an exercise to the next lint implementer.
213+ ///
214+ /// ### Example
215+ /// ```no_run
216+ /// let foo = &0_u32;
217+ /// fn bar() {}
218+ /// println!("{:p}", foo);
219+ /// let _ = format!("{:?}", &(bar as fn()));
220+ /// ```
221+ ///
222+ /// [pointer format]: https://doc.rust-lang.org/std/fmt/index.html#formatting-traits
223+ #[ clippy:: version = "1.88.0" ]
224+ pub POINTER_FORMAT ,
225+ restriction,
226+ "formatting a pointer"
227+ }
228+
197229impl_lint_pass ! ( FormatArgs <' _> => [
198230 FORMAT_IN_FORMAT_ARGS ,
199231 TO_STRING_IN_FORMAT_ARGS ,
200232 UNINLINED_FORMAT_ARGS ,
201233 UNNECESSARY_DEBUG_FORMATTING ,
202234 UNUSED_FORMAT_SPECS ,
235+ POINTER_FORMAT ,
203236] ) ;
204237
205238#[ allow( clippy:: struct_field_names) ]
@@ -208,6 +241,8 @@ pub struct FormatArgs<'tcx> {
208241 msrv : Msrv ,
209242 ignore_mixed : bool ,
210243 ty_msrv_map : FxHashMap < Ty < ' tcx > , Option < RustcVersion > > ,
244+ has_derived_debug : FxHashMap < Ty < ' tcx > , bool > ,
245+ has_pointer_format : FxHashMap < Ty < ' tcx > , bool > ,
211246}
212247
213248impl < ' tcx > FormatArgs < ' tcx > {
@@ -218,6 +253,8 @@ impl<'tcx> FormatArgs<'tcx> {
218253 msrv : conf. msrv ,
219254 ignore_mixed : conf. allow_mixed_uninlined_format_args ,
220255 ty_msrv_map,
256+ has_derived_debug : FxHashMap :: default ( ) ,
257+ has_pointer_format : FxHashMap :: default ( ) ,
221258 }
222259 }
223260}
@@ -228,14 +265,16 @@ impl<'tcx> LateLintPass<'tcx> for FormatArgs<'tcx> {
228265 && is_format_macro ( cx, macro_call. def_id )
229266 && let Some ( format_args) = self . format_args . get ( cx, expr, macro_call. expn )
230267 {
231- let linter = FormatArgsExpr {
268+ let mut linter = FormatArgsExpr {
232269 cx,
233270 expr,
234271 macro_call : & macro_call,
235272 format_args,
236273 ignore_mixed : self . ignore_mixed ,
237274 msrv : & self . msrv ,
238275 ty_msrv_map : & self . ty_msrv_map ,
276+ has_derived_debug : & mut self . has_derived_debug ,
277+ has_pointer_format : & mut self . has_pointer_format ,
239278 } ;
240279
241280 linter. check_templates ( ) ;
@@ -255,10 +294,12 @@ struct FormatArgsExpr<'a, 'tcx> {
255294 ignore_mixed : bool ,
256295 msrv : & ' a Msrv ,
257296 ty_msrv_map : & ' a FxHashMap < Ty < ' tcx > , Option < RustcVersion > > ,
297+ has_derived_debug : & ' a mut FxHashMap < Ty < ' tcx > , bool > ,
298+ has_pointer_format : & ' a mut FxHashMap < Ty < ' tcx > , bool > ,
258299}
259300
260301impl < ' tcx > FormatArgsExpr < ' _ , ' tcx > {
261- fn check_templates ( & self ) {
302+ fn check_templates ( & mut self ) {
262303 for piece in & self . format_args . template {
263304 if let FormatArgsPiece :: Placeholder ( placeholder) = piece
264305 && let Ok ( index) = placeholder. argument . index
@@ -279,6 +320,17 @@ impl<'tcx> FormatArgsExpr<'_, 'tcx> {
279320 if placeholder. format_trait == FormatTrait :: Debug {
280321 let name = self . cx . tcx . item_name ( self . macro_call . def_id ) ;
281322 self . check_unnecessary_debug_formatting ( name, arg_expr) ;
323+ if let Some ( span) = placeholder. span
324+ && self . has_pointer_debug ( self . cx . typeck_results ( ) . expr_ty ( arg_expr) , 0 )
325+ {
326+ span_lint ( self . cx , POINTER_FORMAT , span, "pointer formatting detected" ) ;
327+ }
328+ }
329+
330+ if placeholder. format_trait == FormatTrait :: Pointer
331+ && let Some ( span) = placeholder. span
332+ {
333+ span_lint ( self . cx , POINTER_FORMAT , span, "pointer formatting detected" ) ;
282334 }
283335 }
284336 }
@@ -559,6 +611,58 @@ impl<'tcx> FormatArgsExpr<'_, 'tcx> {
559611
560612 false
561613 }
614+
615+ fn has_pointer_debug ( & mut self , ty : Ty < ' tcx > , depth : usize ) -> bool {
616+ let cx = self . cx ;
617+ let tcx = cx. tcx ;
618+ if !tcx. recursion_limit ( ) . value_within_limit ( depth) {
619+ return false ;
620+ }
621+ let depth = depth + 1 ;
622+ let typing_env = cx. typing_env ( ) ;
623+ let ty = tcx. normalize_erasing_regions ( typing_env, ty) ;
624+ match ty. kind ( ) {
625+ ty:: RawPtr ( ..) | ty:: FnPtr ( ..) | ty:: FnDef ( ..) => true ,
626+ ty:: Ref ( _, t, _) | ty:: Slice ( t) | ty:: Array ( t, _) => self . has_pointer_debug ( * t, depth) ,
627+ ty:: Tuple ( ts) => ts. iter ( ) . any ( |t| self . has_pointer_debug ( t, depth) ) ,
628+ ty:: Adt ( adt, args) => {
629+ match self . has_pointer_format . entry ( ty) {
630+ Entry :: Occupied ( o) => return * o. get ( ) ,
631+ Entry :: Vacant ( v) => v. insert ( false ) ,
632+ } ;
633+ let derived_debug = if let Some ( & known) = self . has_derived_debug . get ( & ty) {
634+ known
635+ } else {
636+ let Some ( trait_id) = tcx. get_diagnostic_item ( sym:: Debug ) else {
637+ return false ;
638+ } ;
639+ let ( infcx, param_env) = tcx. infer_ctxt ( ) . build_with_typing_env ( typing_env) ;
640+ let trait_ref = TraitRef :: new ( tcx, trait_id, [ GenericArg :: from ( ty) ] ) ;
641+ let obligation = Obligation {
642+ cause : ObligationCause :: dummy ( ) ,
643+ param_env,
644+ recursion_depth : 0 ,
645+ predicate : trait_ref. upcast ( tcx) ,
646+ } ;
647+ let selection = SelectionContext :: new ( & infcx) . select ( & obligation) ;
648+ let derived = if let Ok ( Some ( Selection :: UserDefined ( data) ) ) = selection {
649+ tcx. has_attr ( data. impl_def_id , sym:: automatically_derived)
650+ } else {
651+ false
652+ } ;
653+ self . has_derived_debug . insert ( ty, derived) ;
654+ derived
655+ } ;
656+ let pointer_debug = derived_debug
657+ && adt. all_fields ( ) . any ( |f| {
658+ self . has_pointer_debug ( tcx. normalize_erasing_regions ( typing_env, f. ty ( tcx, args) ) , depth)
659+ } ) ;
660+ self . has_pointer_format . insert ( ty, pointer_debug) ;
661+ pointer_debug
662+ } ,
663+ _ => false ,
664+ }
665+ }
562666}
563667
564668fn make_ty_msrv_map ( tcx : TyCtxt < ' _ > ) -> FxHashMap < Ty < ' _ > , Option < RustcVersion > > {
0 commit comments