Skip to content

Commit fd691fc

Browse files
committed
feat(trusted_asref): new lint
1 parent d0a919d commit fd691fc

6 files changed

Lines changed: 370 additions & 0 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7406,6 +7406,7 @@ Released 2018-09-13
74067406
[`trim_split_whitespace`]: https://rust-lang.github.io/rust-clippy/master/index.html#trim_split_whitespace
74077407
[`trivial_regex`]: https://rust-lang.github.io/rust-clippy/master/index.html#trivial_regex
74087408
[`trivially_copy_pass_by_ref`]: https://rust-lang.github.io/rust-clippy/master/index.html#trivially_copy_pass_by_ref
7409+
[`trusted_asref`]: https://rust-lang.github.io/rust-clippy/master/index.html#trusted_asref
74097410
[`try_err`]: https://rust-lang.github.io/rust-clippy/master/index.html#try_err
74107411
[`tuple_array_conversions`]: https://rust-lang.github.io/rust-clippy/master/index.html#tuple_array_conversions
74117412
[`type_complexity`]: https://rust-lang.github.io/rust-clippy/master/index.html#type_complexity

clippy_lints/src/declared_lints.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -752,6 +752,7 @@ pub static LINTS: &[&::declare_clippy_lint::LintInfo] = &[
752752
crate::transmute::UNSOUND_COLLECTION_TRANSMUTE_INFO,
753753
crate::transmute::USELESS_TRANSMUTE_INFO,
754754
crate::transmute::WRONG_TRANSMUTE_INFO,
755+
crate::trusted_asref::TRUSTED_ASREF_INFO,
755756
crate::tuple_array_conversions::TUPLE_ARRAY_CONVERSIONS_INFO,
756757
crate::types::BORROWED_BOX_INFO,
757758
crate::types::BOX_COLLECTION_INFO,

clippy_lints/src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -363,6 +363,7 @@ mod toplevel_ref_arg;
363363
mod trailing_empty_array;
364364
mod trait_bounds;
365365
mod transmute;
366+
mod trusted_asref;
366367
mod tuple_array_conversions;
367368
mod types;
368369
mod unconditional_recursion;
@@ -862,6 +863,7 @@ rustc_lint::late_lint_methods!(
862863
ManualAssertEq: manual_assert_eq::ManualAssertEq = manual_assert_eq::ManualAssertEq,
863864
WithCapacityZero: with_capacity_zero::WithCapacityZero = with_capacity_zero::WithCapacityZero,
864865
RefPatterns: ref_patterns::RefPatterns = ref_patterns::RefPatterns,
866+
TrustedAsref: trusted_asref::TrustedAsref = trusted_asref::TrustedAsref,
865867
// add late passes here, used by `cargo dev new_lint`
866868
]]
867869
);

clippy_lints/src/trusted_asref.rs

Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
use clippy_utils::diagnostics::span_lint_and_then;
2+
use clippy_utils::res::MaybeDef;
3+
use clippy_utils::{SpanlessEq, hash_expr};
4+
use rustc_data_structures::fx::FxIndexMap;
5+
use rustc_hir as hir;
6+
use rustc_hir::intravisit::{Visitor, walk_expr};
7+
use rustc_lint::{LateContext, LateLintPass};
8+
use rustc_middle::ty;
9+
use rustc_session::declare_lint_pass;
10+
use rustc_span::sym;
11+
12+
declare_clippy_lint! {
13+
/// ### What it does
14+
/// Checks for unsafe code calling `.as_ref()` multiple times
15+
/// on the same generic receiver.
16+
///
17+
/// ### Why is this bad?
18+
/// Usually `AsRef::as_ref()` is pure and multiple calls will return a
19+
/// reference to the exact same value. However, unsafe code cannot always
20+
/// soundly rely on this.
21+
///
22+
/// ### Example
23+
/// ```no_run
24+
/// # unsafe fn ffi_call(_ptr: *mut u8, _len: usize) {}
25+
/// fn safe_ffi_call(data: impl AsRef<[u8]>) {
26+
/// unsafe {
27+
/// ffi_call(data.as_ref().as_ptr(), data.as_ref().len());
28+
/// }
29+
/// }
30+
/// ```
31+
/// Use instead:
32+
/// ```no_run
33+
/// # unsafe fn ffi_call(_ptr: *mut u8, _len: usize) {}
34+
/// fn safe_ffi_call(data: impl AsRef<[u8]>) {
35+
/// let data = data.as_ref();
36+
/// unsafe {
37+
/// ffi_call(data.as_ptr(), data.len());
38+
/// }
39+
/// }
40+
/// ```
41+
///
42+
/// ### Known problems
43+
/// False negatives: Such code can also be unsound when the receiver
44+
/// is a concrete type from a foreign crate.
45+
///
46+
/// False positives: Not all generic implementations are provided by
47+
/// foreign code. Not all unsafe code actually relies on the purity of
48+
/// `as_ref()`.
49+
#[clippy::version = "1.98.0"]
50+
pub TRUSTED_ASREF,
51+
suspicious,
52+
"unsafe block calls `.as_ref()` multiple times on the same generic receiver"
53+
}
54+
55+
declare_lint_pass!(TrustedAsref => [TRUSTED_ASREF]);
56+
57+
impl<'tcx> LateLintPass<'tcx> for TrustedAsref {
58+
fn check_block(&mut self, cx: &LateContext<'tcx>, block: &'tcx hir::Block<'tcx>) {
59+
if block.rules == hir::BlockCheckMode::UnsafeBlock(hir::UnsafeSource::UserProvided)
60+
&& !block.span.in_external_macro(cx.tcx.sess.source_map())
61+
{
62+
let mut visitor = ForeignAsRefCallVisitor {
63+
cx,
64+
receiver_buckets: FxIndexMap::default(),
65+
};
66+
visitor.visit_block(block);
67+
68+
for bucket in visitor.receiver_buckets.into_values() {
69+
for calls in &bucket {
70+
check_calls(cx, calls);
71+
}
72+
}
73+
}
74+
}
75+
}
76+
77+
fn check_calls(cx: &LateContext<'_>, calls: &Calls<'_>) {
78+
let [first_expr, other_exprs @ ..] = &calls.exprs[..] else {
79+
return;
80+
};
81+
82+
if other_exprs.is_empty() {
83+
return;
84+
}
85+
86+
span_lint_and_then(
87+
cx,
88+
TRUSTED_ASREF,
89+
first_expr.span,
90+
"unsafe block calls `as_ref` multiple times on the same receiver",
91+
|diag| {
92+
for expr in other_exprs {
93+
diag.span_note(expr.span, "called again here");
94+
}
95+
diag.help("consider calling `as_ref()` once and reusing the result to avoid potential unsoundness");
96+
},
97+
);
98+
}
99+
100+
struct Calls<'tcx> {
101+
receiver: &'tcx hir::Expr<'tcx>,
102+
exprs: Vec<&'tcx hir::Expr<'tcx>>,
103+
}
104+
105+
struct ForeignAsRefCallVisitor<'a, 'tcx> {
106+
cx: &'a LateContext<'tcx>,
107+
receiver_buckets: FxIndexMap<u64, Vec<Calls<'tcx>>>,
108+
}
109+
110+
impl<'tcx> Visitor<'tcx> for ForeignAsRefCallVisitor<'_, 'tcx> {
111+
fn visit_expr(&mut self, expr: &'tcx hir::Expr<'tcx>) {
112+
if matches!(
113+
expr.kind,
114+
hir::ExprKind::Block(
115+
hir::Block {
116+
rules: hir::BlockCheckMode::UnsafeBlock(_),
117+
..
118+
},
119+
_
120+
)
121+
) {
122+
return; // Do not descend into nested unsafe block
123+
}
124+
125+
if let hir::ExprKind::MethodCall(_, receiver, _, _) = expr.kind
126+
&& let Some(def_id) = self.cx.typeck_results().type_dependent_def_id(expr.hir_id)
127+
&& def_id.opt_parent(self.cx).is_diag_item(self.cx, sym::AsRef)
128+
&& let ty::Param(_) = self.cx.typeck_results().expr_ty(receiver).peel_refs().kind()
129+
{
130+
let receiver_bucket = self.receiver_buckets.entry(hash_expr(self.cx, receiver)).or_default();
131+
132+
let calls = match receiver_bucket
133+
.iter_mut()
134+
.find(|calls| SpanlessEq::new(self.cx).eq_expr(receiver.span.ctxt(), calls.receiver, receiver))
135+
{
136+
Some(calls) => calls,
137+
None => receiver_bucket.push_mut(Calls {
138+
receiver,
139+
exprs: Vec::new(),
140+
}),
141+
};
142+
143+
calls.exprs.push(expr);
144+
}
145+
146+
walk_expr(self, expr);
147+
}
148+
}

tests/ui/trusted_asref.rs

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
#![warn(clippy::trusted_asref)]
2+
3+
unsafe fn use_slice<T>(_s: *const T, _len: usize) {}
4+
5+
unsafe fn use_ptr<T>(_s: *const T) {}
6+
7+
fn bad_generic<T: AsRef<str>>(s: T) {
8+
unsafe {
9+
use_slice(s.as_ref().as_ptr(), s.as_ref().len());
10+
//~^ trusted_asref
11+
}
12+
}
13+
14+
fn bad_impl(s: impl AsRef<[u8]>) {
15+
unsafe {
16+
use_slice(s.as_ref().as_ptr(), s.as_ref().len());
17+
//~^ trusted_asref
18+
}
19+
}
20+
21+
fn bad_where<T>(s: T)
22+
where
23+
T: AsRef<[u8]>,
24+
{
25+
unsafe {
26+
use_slice(s.as_ref().as_ptr(), s.as_ref().len());
27+
//~^ trusted_asref
28+
}
29+
}
30+
31+
fn bad_nested_unsafe<T: AsRef<str>>(s: T) {
32+
unsafe {
33+
use_slice(s.as_ref().as_ptr(), s.as_ref().len());
34+
//~^ trusted_asref
35+
unsafe {
36+
use_slice(s.as_ref().as_ptr(), s.as_ref().len());
37+
//~^ trusted_asref
38+
}
39+
}
40+
}
41+
42+
fn bad_multiple_expr<T: AsRef<[u8]>>(s: T) {
43+
unsafe {
44+
let p = s.as_ref().as_ptr();
45+
//~^ trusted_asref
46+
let len = s.as_ref().len();
47+
use_slice(p, len);
48+
}
49+
}
50+
51+
fn bad_multiple_calls<T: AsRef<[u8]>>(s: T) {
52+
unsafe {
53+
use_slice(s.as_ref().as_ptr(), s.as_ref().len());
54+
//~^ trusted_asref
55+
use_slice(s.as_ref().as_ptr(), s.as_ref().len());
56+
}
57+
}
58+
59+
fn good_generic<T: AsRef<str>>(s: T) {
60+
let s = s.as_ref();
61+
unsafe {
62+
use_slice(s.as_ptr(), s.len());
63+
}
64+
}
65+
66+
fn good_impl(s: impl AsRef<[u8]>) {
67+
let s = s.as_ref();
68+
unsafe {
69+
use_slice(s.as_ptr(), s.len());
70+
}
71+
}
72+
73+
fn good_where<T>(s: T)
74+
where
75+
T: AsRef<[u8]>,
76+
{
77+
let s = s.as_ref();
78+
unsafe {
79+
use_slice(s.as_ptr(), s.len());
80+
}
81+
}
82+
83+
fn good_trustworthy_std(s: String) {
84+
unsafe {
85+
use_slice(s.as_ptr(), s.len());
86+
}
87+
}
88+
89+
struct TrustworthyStr(String);
90+
91+
impl AsRef<str> for TrustworthyStr {
92+
fn as_ref(&self) -> &str {
93+
&self.0
94+
}
95+
}
96+
97+
fn good_trustworthy_crate_local(s: TrustworthyStr) {
98+
unsafe {
99+
use_slice(s.as_ref().as_ptr(), s.as_ref().len());
100+
}
101+
}
102+
103+
fn good_unrelated_calls(a: impl AsRef<u32>, b: impl AsRef<u32>) {
104+
unsafe {
105+
use_ptr(a.as_ref() as *const u32);
106+
use_ptr(b.as_ref() as *const u32);
107+
}
108+
}
109+
110+
fn good_safe(s: impl AsRef<str>) {
111+
let _ptr = s.as_ref().as_ptr();
112+
let _len = s.as_ref().len();
113+
}

0 commit comments

Comments
 (0)