11use arrayvec:: ArrayVec ;
22use clippy_config:: Conf ;
3- use clippy_utils:: diagnostics:: { span_lint_and_sugg, span_lint_and_then} ;
3+ use clippy_utils:: diagnostics:: { span_lint , span_lint_and_sugg, span_lint_and_then} ;
44use clippy_utils:: macros:: {
55 FormatArgsStorage , FormatParamUsage , MacroCall , find_format_arg_expr, format_arg_removal_span,
66 format_placeholder_format_span, is_assert_macro, is_format_macro, is_panic, matching_root_macro_call,
@@ -16,16 +16,18 @@ use rustc_ast::{
1616 FormatPlaceholder , FormatTrait ,
1717} ;
1818use rustc_attr_data_structures:: RustcVersion ;
19- use rustc_data_structures:: fx:: FxHashMap ;
19+ use rustc_data_structures:: fx:: { FxHashMap , FxHashSet } ;
2020use rustc_errors:: Applicability ;
2121use rustc_errors:: SuggestionStyle :: { CompletelyHidden , ShowCode } ;
2222use rustc_hir:: { Expr , ExprKind , LangItem } ;
2323use rustc_lint:: { LateContext , LateLintPass , LintContext } ;
2424use rustc_middle:: ty:: adjustment:: { Adjust , Adjustment } ;
25- use rustc_middle:: ty:: { List , Ty , TyCtxt } ;
25+ use rustc_middle:: ty:: { self , GenericArg , List , TraitRef , Ty , TyCtxt , Upcast } ;
2626use rustc_session:: impl_lint_pass;
2727use rustc_span:: edition:: Edition :: Edition2021 ;
2828use rustc_span:: { Span , Symbol , sym} ;
29+ use rustc_trait_selection:: infer:: TyCtxtInferExt ;
30+ use rustc_trait_selection:: traits:: { Obligation , ObligationCause , Selection , SelectionContext } ;
2931
3032declare_clippy_lint ! {
3133 /// ### What it does
@@ -194,12 +196,41 @@ declare_clippy_lint! {
194196 "use of a format specifier that has no effect"
195197}
196198
199+ declare_clippy_lint ! {
200+ /// ### What it does
201+ /// Detects [pointer format] as well as `Debug` formatting of raw pointers or function pointers
202+ /// or any types that have a derived `Debug` impl that recursively contains them.
203+ ///
204+ /// ### Why restrict this?
205+ /// The addresses are only useful in very specific contexts, and certain projects may want to keep addresses of
206+ /// certain data structures or functions from prying hacker eyes as an additional line of security.
207+ ///
208+ /// ### Known problems
209+ /// The lint currently only looks through derived `Debug` implementations. Checking whether a manual
210+ /// implementation prints an address is left as an exercise to the next lint implementer.
211+ ///
212+ /// ### Example
213+ /// ```no_run
214+ /// let foo = &0_u32;
215+ /// fn bar() {}
216+ /// println!("{:p}", foo);
217+ /// let _ = format!("{:?}", &(bar as fn()));
218+ /// ```
219+ ///
220+ /// [pointer format]: https://doc.rust-lang.org/std/fmt/index.html#formatting-traits
221+ #[ clippy:: version = "1.88.0" ]
222+ pub POINTER_FORMAT ,
223+ restriction,
224+ "formatting a pointer"
225+ }
226+
197227impl_lint_pass ! ( FormatArgs <' _> => [
198228 FORMAT_IN_FORMAT_ARGS ,
199229 TO_STRING_IN_FORMAT_ARGS ,
200230 UNINLINED_FORMAT_ARGS ,
201231 UNNECESSARY_DEBUG_FORMATTING ,
202232 UNUSED_FORMAT_SPECS ,
233+ POINTER_FORMAT ,
203234] ) ;
204235
205236#[ allow( clippy:: struct_field_names) ]
@@ -208,6 +239,7 @@ pub struct FormatArgs<'tcx> {
208239 msrv : Msrv ,
209240 ignore_mixed : bool ,
210241 ty_msrv_map : FxHashMap < Ty < ' tcx > , Option < RustcVersion > > ,
242+ has_derived_debug : FxHashMap < Ty < ' tcx > , bool > ,
211243}
212244
213245impl < ' tcx > FormatArgs < ' tcx > {
@@ -218,6 +250,7 @@ impl<'tcx> FormatArgs<'tcx> {
218250 msrv : conf. msrv ,
219251 ignore_mixed : conf. allow_mixed_uninlined_format_args ,
220252 ty_msrv_map,
253+ has_derived_debug : FxHashMap :: default ( ) ,
221254 }
222255 }
223256}
@@ -228,14 +261,15 @@ impl<'tcx> LateLintPass<'tcx> for FormatArgs<'tcx> {
228261 && is_format_macro ( cx, macro_call. def_id )
229262 && let Some ( format_args) = self . format_args . get ( cx, expr, macro_call. expn )
230263 {
231- let linter = FormatArgsExpr {
264+ let mut linter = FormatArgsExpr {
232265 cx,
233266 expr,
234267 macro_call : & macro_call,
235268 format_args,
236269 ignore_mixed : self . ignore_mixed ,
237270 msrv : & self . msrv ,
238271 ty_msrv_map : & self . ty_msrv_map ,
272+ has_derived_debug : & mut self . has_derived_debug ,
239273 } ;
240274
241275 linter. check_templates ( ) ;
@@ -255,10 +289,11 @@ struct FormatArgsExpr<'a, 'tcx> {
255289 ignore_mixed : bool ,
256290 msrv : & ' a Msrv ,
257291 ty_msrv_map : & ' a FxHashMap < Ty < ' tcx > , Option < RustcVersion > > ,
292+ has_derived_debug : & ' a mut FxHashMap < Ty < ' tcx > , bool > ,
258293}
259294
260295impl < ' tcx > FormatArgsExpr < ' _ , ' tcx > {
261- fn check_templates ( & self ) {
296+ fn check_templates ( & mut self ) {
262297 for piece in & self . format_args . template {
263298 if let FormatArgsPiece :: Placeholder ( placeholder) = piece
264299 && let Ok ( index) = placeholder. argument . index
@@ -279,6 +314,17 @@ impl<'tcx> FormatArgsExpr<'_, 'tcx> {
279314 if placeholder. format_trait == FormatTrait :: Debug {
280315 let name = self . cx . tcx . item_name ( self . macro_call . def_id ) ;
281316 self . check_unnecessary_debug_formatting ( name, arg_expr) ;
317+ if let Some ( span) = placeholder. span
318+ && self . has_pointer_debug ( self . cx . typeck_results ( ) . expr_ty ( arg_expr) )
319+ {
320+ span_lint ( self . cx , POINTER_FORMAT , span, "pointer formatting detected" ) ;
321+ }
322+ }
323+
324+ if placeholder. format_trait == FormatTrait :: Pointer
325+ && let Some ( span) = placeholder. span
326+ {
327+ span_lint ( self . cx , POINTER_FORMAT , span, "pointer formatting detected" ) ;
282328 }
283329 }
284330 }
@@ -559,6 +605,59 @@ impl<'tcx> FormatArgsExpr<'_, 'tcx> {
559605
560606 false
561607 }
608+
609+ fn has_pointer_debug ( & mut self , ty : Ty < ' tcx > ) -> bool {
610+ let mut visited = FxHashSet :: default ( ) ;
611+ let mut open: Vec < Ty < ' _ > > = Vec :: new ( ) ;
612+ open. push ( ty) ;
613+ let mut contains = Vec :: new ( ) ;
614+ while let Some ( ty) = open. pop ( ) {
615+ match ty. kind ( ) {
616+ ty:: RawPtr ( ..) | ty:: FnPtr ( ..) | ty:: FnDef ( ..) => return true ,
617+ ty:: Ref ( _, t, _) | ty:: Slice ( t) | ty:: Array ( t, _) => open. push ( * t) ,
618+ ty:: Tuple ( ts) => open. extend_from_slice ( ts) ,
619+ ty:: Adt ( adt, args) => {
620+ // avoid infinite recursion
621+ if !visited. insert ( adt. did ( ) ) {
622+ continue ;
623+ }
624+ let cx = self . cx ;
625+ let tcx = cx. tcx ;
626+ let has_derived_debug = if let Some ( known) = self . has_derived_debug . get ( & ty) {
627+ * known
628+ } else {
629+ let Some ( trait_id) = tcx. get_diagnostic_item ( sym:: Debug ) else {
630+ continue ;
631+ } ;
632+ let ( infcx, param_env) = tcx. infer_ctxt ( ) . build_with_typing_env ( cx. typing_env ( ) ) ;
633+ let ty = tcx. erase_regions ( ty) ;
634+ let trait_ref = TraitRef :: new ( tcx, trait_id, [ GenericArg :: from ( ty) ] ) ;
635+ let obligation = Obligation {
636+ cause : ObligationCause :: dummy ( ) ,
637+ param_env,
638+ recursion_depth : 0 ,
639+ predicate : trait_ref. upcast ( tcx) ,
640+ } ;
641+ let selection = SelectionContext :: new ( & infcx) . select ( & obligation) ;
642+ let Ok ( Some ( Selection :: UserDefined ( data) ) ) = selection else {
643+ continue ;
644+ } ;
645+ let has_derived_debug = tcx. has_attr ( data. impl_def_id , sym:: automatically_derived) ;
646+ self . has_derived_debug . insert ( ty, has_derived_debug) ;
647+ has_derived_debug
648+ } ;
649+ // we currently only look into derived impls because those will
650+ // debug-format the types fields which is easy enough to pull off
651+ if has_derived_debug {
652+ contains. push ( ty) ;
653+ open. extend ( adt. all_fields ( ) . map ( |f| f. ty ( tcx, args) ) ) ;
654+ }
655+ } ,
656+ _ => ( ) ,
657+ }
658+ }
659+ false
660+ }
562661}
563662
564663fn make_ty_msrv_map ( tcx : TyCtxt < ' _ > ) -> FxHashMap < Ty < ' _ > , Option < RustcVersion > > {
0 commit comments