Skip to content

Commit e64dd8e

Browse files
committed
new restriction lint: pointer_format
1 parent c12bc22 commit e64dd8e

File tree

5 files changed

+223
-4
lines changed

5 files changed

+223
-4
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6163,6 +6163,7 @@ Released 2018-09-13
61636163
[`pathbuf_init_then_push`]: https://rust-lang.github.io/rust-clippy/master/index.html#pathbuf_init_then_push
61646164
[`pattern_type_mismatch`]: https://rust-lang.github.io/rust-clippy/master/index.html#pattern_type_mismatch
61656165
[`permissions_set_readonly_false`]: https://rust-lang.github.io/rust-clippy/master/index.html#permissions_set_readonly_false
6166+
[`pointer_format`]: https://rust-lang.github.io/rust-clippy/master/index.html#pointer_format
61666167
[`pointers_in_nomem_asm_block`]: https://rust-lang.github.io/rust-clippy/master/index.html#pointers_in_nomem_asm_block
61676168
[`positional_named_format_parameters`]: https://rust-lang.github.io/rust-clippy/master/index.html#positional_named_format_parameters
61686169
[`possible_missing_comma`]: https://rust-lang.github.io/rust-clippy/master/index.html#possible_missing_comma

clippy_lints/src/declared_lints.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,7 @@ pub static LINTS: &[&crate::LintInfo] = &[
166166
crate::floating_point_arithmetic::SUBOPTIMAL_FLOPS_INFO,
167167
crate::format::USELESS_FORMAT_INFO,
168168
crate::format_args::FORMAT_IN_FORMAT_ARGS_INFO,
169+
crate::format_args::POINTER_FORMAT_INFO,
169170
crate::format_args::TO_STRING_IN_FORMAT_ARGS_INFO,
170171
crate::format_args::UNINLINED_FORMAT_ARGS_INFO,
171172
crate::format_args::UNNECESSARY_DEBUG_FORMATTING_INFO,

clippy_lints/src/format_args.rs

Lines changed: 108 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
1+
use std::collections::hash_map::Entry;
2+
13
use arrayvec::ArrayVec;
24
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};
46
use 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};
2224
use rustc_hir::{Expr, ExprKind, LangItem};
2325
use rustc_lint::{LateContext, LateLintPass, LintContext};
2426
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};
2628
use rustc_session::impl_lint_pass;
2729
use rustc_span::edition::Edition::Edition2021;
2830
use rustc_span::{Span, Symbol, sym};
31+
use rustc_trait_selection::infer::TyCtxtInferExt;
32+
use rustc_trait_selection::traits::{Obligation, ObligationCause, Selection, SelectionContext};
2933

3034
declare_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+
197229
impl_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

213248
impl<'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

260301
impl<'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

564668
fn make_ty_msrv_map(tcx: TyCtxt<'_>) -> FxHashMap<Ty<'_>, Option<RustcVersion>> {

tests/ui/pointer_format.rs

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
#![warn(clippy::pointer_format)]
2+
3+
use core::fmt::Debug;
4+
use core::marker::PhantomData;
5+
6+
#[derive(Debug)]
7+
struct ContainsPointerDeep {
8+
w: WithPointer,
9+
}
10+
11+
struct ManualDebug {
12+
ptr: *const u8,
13+
}
14+
15+
#[derive(Debug)]
16+
struct WithPointer {
17+
len: usize,
18+
ptr: *const u8,
19+
}
20+
21+
impl std::fmt::Debug for ManualDebug {
22+
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
23+
f.write_str("ManualDebug")
24+
}
25+
}
26+
27+
trait Foo {
28+
type Assoc: Foo + Debug;
29+
}
30+
31+
#[derive(Debug)]
32+
struct S<T: Foo + 'static>(&'static S<T::Assoc>, PhantomData<T>);
33+
34+
#[allow(unused)]
35+
fn unbounded<T: Foo + Debug + 'static>(s: &S<T>) {
36+
format!("{s:?}");
37+
}
38+
39+
fn main() {
40+
let m = &(main as fn());
41+
let g = &0;
42+
let o = &format!("{m:p}");
43+
//~^ pointer_format
44+
let _ = format!("{m:?}");
45+
//~^ pointer_format
46+
println!("{g:p}");
47+
//~^ pointer_format
48+
panic!("{o:p}");
49+
//~^ pointer_format
50+
let answer = 42;
51+
let x = &raw const answer;
52+
let arr = [0u8; 8];
53+
let with_ptr = WithPointer { len: 8, ptr: &arr as _ };
54+
let _ = format!("{x:?}");
55+
//~^ pointer_format
56+
print!("{with_ptr:?}");
57+
//~^ pointer_format
58+
let container = ContainsPointerDeep { w: with_ptr };
59+
print!("{container:?}");
60+
//~^ pointer_format
61+
62+
let no_pointer = "foo";
63+
println!("{no_pointer:?}");
64+
let manual_debug = ManualDebug { ptr: &arr as _ };
65+
println!("{manual_debug:?}");
66+
}

tests/ui/pointer_format.stderr

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
error: pointer formatting detected
2+
--> tests/ui/pointer_format.rs:42:23
3+
|
4+
LL | let o = &format!("{m:p}");
5+
| ^^^^^
6+
|
7+
= note: `-D clippy::pointer-format` implied by `-D warnings`
8+
= help: to override `-D warnings` add `#[allow(clippy::pointer_format)]`
9+
10+
error: pointer formatting detected
11+
--> tests/ui/pointer_format.rs:44:22
12+
|
13+
LL | let _ = format!("{m:?}");
14+
| ^^^^^
15+
16+
error: pointer formatting detected
17+
--> tests/ui/pointer_format.rs:46:15
18+
|
19+
LL | println!("{g:p}");
20+
| ^^^^^
21+
22+
error: pointer formatting detected
23+
--> tests/ui/pointer_format.rs:48:13
24+
|
25+
LL | panic!("{o:p}");
26+
| ^^^^^
27+
28+
error: pointer formatting detected
29+
--> tests/ui/pointer_format.rs:54:22
30+
|
31+
LL | let _ = format!("{x:?}");
32+
| ^^^^^
33+
34+
error: pointer formatting detected
35+
--> tests/ui/pointer_format.rs:56:13
36+
|
37+
LL | print!("{with_ptr:?}");
38+
| ^^^^^^^^^^^^
39+
40+
error: pointer formatting detected
41+
--> tests/ui/pointer_format.rs:59:13
42+
|
43+
LL | print!("{container:?}");
44+
| ^^^^^^^^^^^^^
45+
46+
error: aborting due to 7 previous errors
47+

0 commit comments

Comments
 (0)