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,8 @@ 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 > ,
243+ has_pointer_format : FxHashMap < Ty < ' tcx > , bool > ,
211244}
212245
213246impl < ' tcx > FormatArgs < ' tcx > {
@@ -218,6 +251,8 @@ impl<'tcx> FormatArgs<'tcx> {
218251 msrv : conf. msrv ,
219252 ignore_mixed : conf. allow_mixed_uninlined_format_args ,
220253 ty_msrv_map,
254+ has_derived_debug : FxHashMap :: default ( ) ,
255+ has_pointer_format : FxHashMap :: default ( ) ,
221256 }
222257 }
223258}
@@ -228,14 +263,16 @@ impl<'tcx> LateLintPass<'tcx> for FormatArgs<'tcx> {
228263 && is_format_macro ( cx, macro_call. def_id )
229264 && let Some ( format_args) = self . format_args . get ( cx, expr, macro_call. expn )
230265 {
231- let linter = FormatArgsExpr {
266+ let mut linter = FormatArgsExpr {
232267 cx,
233268 expr,
234269 macro_call : & macro_call,
235270 format_args,
236271 ignore_mixed : self . ignore_mixed ,
237272 msrv : & self . msrv ,
238273 ty_msrv_map : & self . ty_msrv_map ,
274+ has_derived_debug : & mut self . has_derived_debug ,
275+ has_pointer_format : & mut self . has_pointer_format ,
239276 } ;
240277
241278 linter. check_templates ( ) ;
@@ -255,10 +292,12 @@ struct FormatArgsExpr<'a, 'tcx> {
255292 ignore_mixed : bool ,
256293 msrv : & ' a Msrv ,
257294 ty_msrv_map : & ' a FxHashMap < Ty < ' tcx > , Option < RustcVersion > > ,
295+ has_derived_debug : & ' a mut FxHashMap < Ty < ' tcx > , bool > ,
296+ has_pointer_format : & ' a mut FxHashMap < Ty < ' tcx > , bool > ,
258297}
259298
260299impl < ' tcx > FormatArgsExpr < ' _ , ' tcx > {
261- fn check_templates ( & self ) {
300+ fn check_templates ( & mut self ) {
262301 for piece in & self . format_args . template {
263302 if let FormatArgsPiece :: Placeholder ( placeholder) = piece
264303 && let Ok ( index) = placeholder. argument . index
@@ -279,6 +318,17 @@ impl<'tcx> FormatArgsExpr<'_, 'tcx> {
279318 if placeholder. format_trait == FormatTrait :: Debug {
280319 let name = self . cx . tcx . item_name ( self . macro_call . def_id ) ;
281320 self . check_unnecessary_debug_formatting ( name, arg_expr) ;
321+ if let Some ( span) = placeholder. span
322+ && self . has_pointer_debug ( self . cx . typeck_results ( ) . expr_ty ( arg_expr) )
323+ {
324+ span_lint ( self . cx , POINTER_FORMAT , span, "pointer formatting detected" ) ;
325+ }
326+ }
327+
328+ if placeholder. format_trait == FormatTrait :: Pointer
329+ && let Some ( span) = placeholder. span
330+ {
331+ span_lint ( self . cx , POINTER_FORMAT , span, "pointer formatting detected" ) ;
282332 }
283333 }
284334 }
@@ -559,6 +609,70 @@ impl<'tcx> FormatArgsExpr<'_, 'tcx> {
559609
560610 false
561611 }
612+
613+ fn has_pointer_debug ( & mut self , ty : Ty < ' tcx > ) -> bool {
614+ let cx = self . cx ;
615+ let tcx = cx. tcx ;
616+ let typing_env = cx. typing_env ( ) ;
617+ let ty = tcx. normalize_erasing_regions ( typing_env, ty) ;
618+ if let Some ( known) = self . has_pointer_format . get ( & ty) {
619+ return * known;
620+ }
621+ let mut visited = FxHashSet :: default ( ) ;
622+ let mut open: Vec < Ty < ' _ > > = Vec :: new ( ) ;
623+ open. push ( ty) ;
624+ while let Some ( next_ty) = open. pop ( ) {
625+ match next_ty. kind ( ) {
626+ ty:: RawPtr ( ..) | ty:: FnPtr ( ..) | ty:: FnDef ( ..) => {
627+ self . has_pointer_format . insert ( ty, true ) ;
628+ return true ;
629+ } ,
630+ ty:: Ref ( _, t, _) | ty:: Slice ( t) | ty:: Array ( t, _) => {
631+ open. push ( tcx. normalize_erasing_regions ( typing_env, * t) ) ;
632+ } ,
633+ ty:: Tuple ( ts) => open. extend ( ts. iter ( ) . map ( |ty| tcx. normalize_erasing_regions ( typing_env, ty) ) ) ,
634+ ty:: Adt ( adt, args) => {
635+ // avoid infinite recursion
636+ if !visited. insert ( adt. did ( ) ) {
637+ continue ;
638+ }
639+ let has_derived_debug = if let Some ( known) = self . has_derived_debug . get ( & next_ty) {
640+ * known
641+ } else {
642+ let Some ( trait_id) = tcx. get_diagnostic_item ( sym:: Debug ) else {
643+ continue ;
644+ } ;
645+ let ( infcx, param_env) = tcx. infer_ctxt ( ) . build_with_typing_env ( typing_env) ;
646+ let trait_ref = TraitRef :: new ( tcx, trait_id, [ GenericArg :: from ( next_ty) ] ) ;
647+ let obligation = Obligation {
648+ cause : ObligationCause :: dummy ( ) ,
649+ param_env,
650+ recursion_depth : 0 ,
651+ predicate : trait_ref. upcast ( tcx) ,
652+ } ;
653+ let selection = SelectionContext :: new ( & infcx) . select ( & obligation) ;
654+ let Ok ( Some ( Selection :: UserDefined ( data) ) ) = selection else {
655+ continue ;
656+ } ;
657+ let has_derived_debug = tcx. has_attr ( data. impl_def_id , sym:: automatically_derived) ;
658+ self . has_derived_debug . insert ( next_ty, has_derived_debug) ;
659+ has_derived_debug
660+ } ;
661+ // we currently only look into derived impls because those will
662+ // debug-format the types fields which is easy enough to pull off
663+ if has_derived_debug {
664+ open. extend (
665+ adt. all_fields ( )
666+ . map ( |f| tcx. normalize_erasing_regions ( typing_env, f. ty ( tcx, args) ) ) ,
667+ ) ;
668+ }
669+ } ,
670+ _ => ( ) ,
671+ }
672+ }
673+ self . has_pointer_format . insert ( ty, false ) ;
674+ false
675+ }
562676}
563677
564678fn make_ty_msrv_map ( tcx : TyCtxt < ' _ > ) -> FxHashMap < Ty < ' _ > , Option < RustcVersion > > {
0 commit comments