1
1
use arrayvec:: ArrayVec ;
2
2
use 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} ;
4
4
use clippy_utils:: macros:: {
5
5
FormatArgsStorage , FormatParamUsage , MacroCall , find_format_arg_expr, format_arg_removal_span,
6
6
format_placeholder_format_span, is_assert_macro, is_format_macro, is_panic, matching_root_macro_call,
@@ -16,16 +16,18 @@ use rustc_ast::{
16
16
FormatPlaceholder , FormatTrait ,
17
17
} ;
18
18
use rustc_attr_data_structures:: RustcVersion ;
19
- use rustc_data_structures:: fx:: FxHashMap ;
19
+ use rustc_data_structures:: fx:: { FxHashMap , FxHashSet } ;
20
20
use rustc_errors:: Applicability ;
21
21
use rustc_errors:: SuggestionStyle :: { CompletelyHidden , ShowCode } ;
22
22
use rustc_hir:: { Expr , ExprKind , LangItem } ;
23
23
use rustc_lint:: { LateContext , LateLintPass , LintContext } ;
24
24
use 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 } ;
26
26
use rustc_session:: impl_lint_pass;
27
27
use rustc_span:: edition:: Edition :: Edition2021 ;
28
28
use rustc_span:: { Span , Symbol , sym} ;
29
+ use rustc_trait_selection:: infer:: TyCtxtInferExt ;
30
+ use rustc_trait_selection:: traits:: { Obligation , ObligationCause , Selection , SelectionContext } ;
29
31
30
32
declare_clippy_lint ! {
31
33
/// ### What it does
@@ -194,12 +196,41 @@ declare_clippy_lint! {
194
196
"use of a format specifier that has no effect"
195
197
}
196
198
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
+
197
227
impl_lint_pass ! ( FormatArgs <' _> => [
198
228
FORMAT_IN_FORMAT_ARGS ,
199
229
TO_STRING_IN_FORMAT_ARGS ,
200
230
UNINLINED_FORMAT_ARGS ,
201
231
UNNECESSARY_DEBUG_FORMATTING ,
202
232
UNUSED_FORMAT_SPECS ,
233
+ POINTER_FORMAT ,
203
234
] ) ;
204
235
205
236
#[ allow( clippy:: struct_field_names) ]
@@ -208,6 +239,8 @@ pub struct FormatArgs<'tcx> {
208
239
msrv : Msrv ,
209
240
ignore_mixed : bool ,
210
241
ty_msrv_map : FxHashMap < Ty < ' tcx > , Option < RustcVersion > > ,
242
+ has_derived_debug : FxHashMap < Ty < ' tcx > , bool > ,
243
+ has_pointer_format : FxHashMap < Ty < ' tcx > , bool > ,
211
244
}
212
245
213
246
impl < ' tcx > FormatArgs < ' tcx > {
@@ -218,6 +251,8 @@ impl<'tcx> FormatArgs<'tcx> {
218
251
msrv : conf. msrv ,
219
252
ignore_mixed : conf. allow_mixed_uninlined_format_args ,
220
253
ty_msrv_map,
254
+ has_derived_debug : FxHashMap :: default ( ) ,
255
+ has_pointer_format : FxHashMap :: default ( ) ,
221
256
}
222
257
}
223
258
}
@@ -228,14 +263,16 @@ impl<'tcx> LateLintPass<'tcx> for FormatArgs<'tcx> {
228
263
&& is_format_macro ( cx, macro_call. def_id )
229
264
&& let Some ( format_args) = self . format_args . get ( cx, expr, macro_call. expn )
230
265
{
231
- let linter = FormatArgsExpr {
266
+ let mut linter = FormatArgsExpr {
232
267
cx,
233
268
expr,
234
269
macro_call : & macro_call,
235
270
format_args,
236
271
ignore_mixed : self . ignore_mixed ,
237
272
msrv : & self . msrv ,
238
273
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 ,
239
276
} ;
240
277
241
278
linter. check_templates ( ) ;
@@ -255,10 +292,12 @@ struct FormatArgsExpr<'a, 'tcx> {
255
292
ignore_mixed : bool ,
256
293
msrv : & ' a Msrv ,
257
294
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 > ,
258
297
}
259
298
260
299
impl < ' tcx > FormatArgsExpr < ' _ , ' tcx > {
261
- fn check_templates ( & self ) {
300
+ fn check_templates ( & mut self ) {
262
301
for piece in & self . format_args . template {
263
302
if let FormatArgsPiece :: Placeholder ( placeholder) = piece
264
303
&& let Ok ( index) = placeholder. argument . index
@@ -279,6 +318,17 @@ impl<'tcx> FormatArgsExpr<'_, 'tcx> {
279
318
if placeholder. format_trait == FormatTrait :: Debug {
280
319
let name = self . cx . tcx . item_name ( self . macro_call . def_id ) ;
281
320
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" ) ;
282
332
}
283
333
}
284
334
}
@@ -559,6 +609,70 @@ impl<'tcx> FormatArgsExpr<'_, 'tcx> {
559
609
560
610
false
561
611
}
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
+ }
562
676
}
563
677
564
678
fn make_ty_msrv_map ( tcx : TyCtxt < ' _ > ) -> FxHashMap < Ty < ' _ > , Option < RustcVersion > > {
0 commit comments