1
+ use std:: collections:: hash_map:: Entry ;
2
+
1
3
use arrayvec:: ArrayVec ;
2
4
use 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} ;
4
6
use clippy_utils:: macros:: {
5
7
FormatArgsStorage , FormatParamUsage , MacroCall , find_format_arg_expr, format_arg_removal_span,
6
8
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};
22
24
use rustc_hir:: { Expr , ExprKind , LangItem } ;
23
25
use rustc_lint:: { LateContext , LateLintPass , LintContext } ;
24
26
use 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 } ;
26
28
use rustc_session:: impl_lint_pass;
27
29
use rustc_span:: edition:: Edition :: Edition2021 ;
28
30
use rustc_span:: { Span , Symbol , sym} ;
31
+ use rustc_trait_selection:: infer:: TyCtxtInferExt ;
32
+ use rustc_trait_selection:: traits:: { Obligation , ObligationCause , Selection , SelectionContext } ;
29
33
30
34
declare_clippy_lint ! {
31
35
/// ### What it does
@@ -194,12 +198,41 @@ declare_clippy_lint! {
194
198
"use of a format specifier that has no effect"
195
199
}
196
200
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
+
197
229
impl_lint_pass ! ( FormatArgs <' _> => [
198
230
FORMAT_IN_FORMAT_ARGS ,
199
231
TO_STRING_IN_FORMAT_ARGS ,
200
232
UNINLINED_FORMAT_ARGS ,
201
233
UNNECESSARY_DEBUG_FORMATTING ,
202
234
UNUSED_FORMAT_SPECS ,
235
+ POINTER_FORMAT ,
203
236
] ) ;
204
237
205
238
#[ allow( clippy:: struct_field_names) ]
@@ -208,6 +241,8 @@ pub struct FormatArgs<'tcx> {
208
241
msrv : Msrv ,
209
242
ignore_mixed : bool ,
210
243
ty_msrv_map : FxHashMap < Ty < ' tcx > , Option < RustcVersion > > ,
244
+ has_derived_debug : FxHashMap < Ty < ' tcx > , bool > ,
245
+ has_pointer_format : FxHashMap < Ty < ' tcx > , bool > ,
211
246
}
212
247
213
248
impl < ' tcx > FormatArgs < ' tcx > {
@@ -218,6 +253,8 @@ impl<'tcx> FormatArgs<'tcx> {
218
253
msrv : conf. msrv ,
219
254
ignore_mixed : conf. allow_mixed_uninlined_format_args ,
220
255
ty_msrv_map,
256
+ has_derived_debug : FxHashMap :: default ( ) ,
257
+ has_pointer_format : FxHashMap :: default ( ) ,
221
258
}
222
259
}
223
260
}
@@ -228,14 +265,16 @@ impl<'tcx> LateLintPass<'tcx> for FormatArgs<'tcx> {
228
265
&& is_format_macro ( cx, macro_call. def_id )
229
266
&& let Some ( format_args) = self . format_args . get ( cx, expr, macro_call. expn )
230
267
{
231
- let linter = FormatArgsExpr {
268
+ let mut linter = FormatArgsExpr {
232
269
cx,
233
270
expr,
234
271
macro_call : & macro_call,
235
272
format_args,
236
273
ignore_mixed : self . ignore_mixed ,
237
274
msrv : & self . msrv ,
238
275
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 ,
239
278
} ;
240
279
241
280
linter. check_templates ( ) ;
@@ -255,10 +294,12 @@ struct FormatArgsExpr<'a, 'tcx> {
255
294
ignore_mixed : bool ,
256
295
msrv : & ' a Msrv ,
257
296
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 > ,
258
299
}
259
300
260
301
impl < ' tcx > FormatArgsExpr < ' _ , ' tcx > {
261
- fn check_templates ( & self ) {
302
+ fn check_templates ( & mut self ) {
262
303
for piece in & self . format_args . template {
263
304
if let FormatArgsPiece :: Placeholder ( placeholder) = piece
264
305
&& let Ok ( index) = placeholder. argument . index
@@ -279,6 +320,17 @@ impl<'tcx> FormatArgsExpr<'_, 'tcx> {
279
320
if placeholder. format_trait == FormatTrait :: Debug {
280
321
let name = self . cx . tcx . item_name ( self . macro_call . def_id ) ;
281
322
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" ) ;
282
334
}
283
335
}
284
336
}
@@ -559,6 +611,58 @@ impl<'tcx> FormatArgsExpr<'_, 'tcx> {
559
611
560
612
false
561
613
}
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
+ }
562
666
}
563
667
564
668
fn make_ty_msrv_map ( tcx : TyCtxt < ' _ > ) -> FxHashMap < Ty < ' _ > , Option < RustcVersion > > {
0 commit comments