Skip to content

Commit 454ce31

Browse files
committed
Implement lint against dangerous implicit autorefs
1 parent 45f3514 commit 454ce31

File tree

8 files changed

+451
-1
lines changed

8 files changed

+451
-1
lines changed

compiler/rustc_lint/messages.ftl

+4
Original file line numberDiff line numberDiff line change
@@ -280,6 +280,10 @@ lint_impl_trait_overcaptures = `{$self_ty}` will capture more lifetimes than pos
280280
lint_impl_trait_redundant_captures = all possible in-scope parameters are already captured, so `use<...>` syntax is redundant
281281
.suggestion = remove the `use<...>` syntax
282282
283+
lint_implicit_unsafe_autorefs = implicit auto-ref creates a reference to a dereference of a raw pointer
284+
.note = creating a reference requires the pointer to be valid and imposes aliasing requirements
285+
.suggestion = try using a raw pointer method instead; or if this reference is intentional, make it explicit
286+
283287
lint_improper_ctypes = `extern` {$desc} uses type `{$ty}`, which is not FFI-safe
284288
.label = not FFI-safe
285289
.note = the type is defined here

compiler/rustc_lint/src/autorefs.rs

+180
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,180 @@
1+
use rustc_ast::{BorrowKind, UnOp};
2+
use rustc_hir::{Expr, ExprKind, Mutability};
3+
use rustc_middle::ty::{
4+
adjustment::{Adjust, Adjustment, AutoBorrow, OverloadedDeref},
5+
TyCtxt, TypeckResults,
6+
};
7+
use rustc_session::declare_lint;
8+
use rustc_session::declare_lint_pass;
9+
use rustc_span::sym;
10+
11+
use crate::{
12+
lints::{ImplicitUnsafeAutorefsDiag, ImplicitUnsafeAutorefsSuggestion},
13+
LateContext, LateLintPass, LintContext,
14+
};
15+
16+
declare_lint! {
17+
/// The `dangerous_implicit_autorefs` lint checks for implicitly taken references
18+
/// to dereferences of raw pointers.
19+
///
20+
/// ### Example
21+
///
22+
/// ```rust
23+
/// use std::ptr::addr_of_mut;
24+
///
25+
/// unsafe fn fun(ptr: *mut [u8]) -> *mut [u8] {
26+
/// addr_of_mut!((*ptr)[..16])
27+
/// // ^^^^^^ this calls `IndexMut::index_mut(&mut ..., ..16)`,
28+
/// // implicitly creating a reference
29+
/// }
30+
/// ```
31+
///
32+
/// {{produces}}
33+
///
34+
/// ### Explanation
35+
///
36+
/// When working with raw pointers it's usually undesirable to create references,
37+
/// since they inflict a lot of safety requirement. Unfortunately, it's possible
38+
/// to take a reference to a dereference of a raw pointer implicitly, which inflicts
39+
/// the usual reference requirements without you even knowing that.
40+
///
41+
/// If you are sure, you can soundly take a reference, then you can take it explicitly:
42+
/// ```rust
43+
/// # use std::ptr::addr_of_mut;
44+
/// unsafe fn fun(ptr: *mut [u8]) -> *mut [u8] {
45+
/// addr_of_mut!((&mut *ptr)[..16])
46+
/// }
47+
/// ```
48+
///
49+
/// Otherwise try to find an alternative way to achive your goals that work only with
50+
/// raw pointers:
51+
/// ```rust
52+
/// #![feature(slice_ptr_get)]
53+
///
54+
/// unsafe fn fun(ptr: *mut [u8]) -> *mut [u8] {
55+
/// ptr.get_unchecked_mut(..16)
56+
/// }
57+
/// ```
58+
pub DANGEROUS_IMPLICIT_AUTOREFS,
59+
Warn,
60+
"implicit reference to a dereference of a raw pointer",
61+
report_in_external_macro
62+
}
63+
64+
declare_lint_pass!(ImplicitAutorefs => [DANGEROUS_IMPLICIT_AUTOREFS]);
65+
66+
impl<'tcx> LateLintPass<'tcx> for ImplicitAutorefs {
67+
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
68+
// This logic has mostly been taken from
69+
// https://github.com/rust-lang/rust/pull/103735#issuecomment-1370420305
70+
71+
// 4. Either of the following:
72+
// a. A deref followed by any non-deref place projection (that intermediate
73+
// deref will typically be auto-inserted)
74+
// b. A method call annotated with `#[rustc_no_implicit_refs]`.
75+
// c. A deref followed by a `addr_of!` or `addr_of_mut!`.
76+
let mut is_coming_from_deref = false;
77+
let inner = match expr.kind {
78+
ExprKind::AddrOf(BorrowKind::Raw, _, inner) => match inner.kind {
79+
ExprKind::Unary(UnOp::Deref, inner) => {
80+
is_coming_from_deref = true;
81+
inner
82+
}
83+
_ => return,
84+
},
85+
ExprKind::Index(base, _idx, _) => base,
86+
ExprKind::MethodCall(_, inner, _, _)
87+
if let Some(def_id) = cx.typeck_results().type_dependent_def_id(expr.hir_id)
88+
&& cx.tcx.has_attr(def_id, sym::rustc_no_implicit_autorefs) =>
89+
{
90+
inner
91+
}
92+
ExprKind::Call(path, [expr, ..])
93+
if let ExprKind::Path(ref qpath) = path.kind
94+
&& let Some(def_id) = cx.qpath_res(qpath, path.hir_id).opt_def_id()
95+
&& cx.tcx.has_attr(def_id, sym::rustc_no_implicit_autorefs) =>
96+
{
97+
expr
98+
}
99+
ExprKind::Field(inner, _) => {
100+
let typeck = cx.typeck_results();
101+
let adjustments_table = typeck.adjustments();
102+
if let Some(adjustments) = adjustments_table.get(inner.hir_id)
103+
&& let [adjustment] = &**adjustments
104+
&& let &Adjust::Deref(Some(OverloadedDeref { .. })) = &adjustment.kind
105+
{
106+
inner
107+
} else {
108+
return;
109+
}
110+
}
111+
_ => return,
112+
};
113+
114+
let typeck = cx.typeck_results();
115+
let adjustments_table = typeck.adjustments();
116+
117+
if let Some(adjustments) = adjustments_table.get(inner.hir_id)
118+
&& let [adjustment] = &**adjustments
119+
// 3. An automatically inserted reference.
120+
&& let Some((mutbl, _implicit_borrow)) = has_implicit_borrow(adjustment)
121+
&& let ExprKind::Unary(UnOp::Deref, dereferenced) =
122+
// 2. Any number of place projections
123+
peel_place_mappers(cx.tcx, typeck, inner).kind
124+
// 1. Deref of a raw pointer
125+
&& typeck.expr_ty(dereferenced).is_unsafe_ptr()
126+
{
127+
cx.emit_span_lint(
128+
DANGEROUS_IMPLICIT_AUTOREFS,
129+
expr.span.source_callsite(),
130+
ImplicitUnsafeAutorefsDiag {
131+
suggestion: ImplicitUnsafeAutorefsSuggestion {
132+
mutbl: mutbl.ref_prefix_str(),
133+
deref: if is_coming_from_deref { "*" } else { "" },
134+
start_span: inner.span.shrink_to_lo(),
135+
end_span: inner.span.shrink_to_hi(),
136+
},
137+
},
138+
)
139+
}
140+
}
141+
}
142+
143+
/// Peels expressions from `expr` that can map a place.
144+
fn peel_place_mappers<'tcx>(
145+
_tcx: TyCtxt<'tcx>,
146+
_typeck: &TypeckResults<'tcx>,
147+
mut expr: &'tcx Expr<'tcx>,
148+
) -> &'tcx Expr<'tcx> {
149+
loop {
150+
match expr.kind {
151+
ExprKind::Index(base, _idx, _) => {
152+
expr = &base;
153+
}
154+
ExprKind::Field(e, _) => expr = &e,
155+
_ => break expr,
156+
}
157+
}
158+
}
159+
160+
enum ImplicitBorrowKind {
161+
Deref,
162+
Borrow,
163+
}
164+
165+
/// Test if some adjustment has some implicit borrow
166+
///
167+
/// Returns `Some(mutability)` if the argument adjustment has implicit borrow in it.
168+
fn has_implicit_borrow(
169+
Adjustment { kind, .. }: &Adjustment<'_>,
170+
) -> Option<(Mutability, ImplicitBorrowKind)> {
171+
match kind {
172+
&Adjust::Deref(Some(OverloadedDeref { mutbl, .. })) => {
173+
Some((mutbl, ImplicitBorrowKind::Deref))
174+
}
175+
&Adjust::Borrow(AutoBorrow::Ref(_, mutbl)) => {
176+
Some((mutbl.into(), ImplicitBorrowKind::Borrow))
177+
}
178+
_ => None,
179+
}
180+
}

compiler/rustc_lint/src/lib.rs

+3
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ extern crate tracing;
4444

4545
mod array_into_iter;
4646
mod async_fn_in_trait;
47+
mod autorefs;
4748
pub mod builtin;
4849
mod context;
4950
mod deref_into_dyn_supertrait;
@@ -89,6 +90,7 @@ use rustc_middle::ty::TyCtxt;
8990

9091
use array_into_iter::ArrayIntoIter;
9192
use async_fn_in_trait::AsyncFnInTrait;
93+
use autorefs::*;
9294
use builtin::*;
9395
use deref_into_dyn_supertrait::*;
9496
use drop_forget_useless::*;
@@ -192,6 +194,7 @@ late_lint_methods!(
192194
PathStatements: PathStatements,
193195
LetUnderscore: LetUnderscore,
194196
InvalidReferenceCasting: InvalidReferenceCasting,
197+
ImplicitAutorefs: ImplicitAutorefs,
195198
// Depends on referenced function signatures in expressions
196199
UnusedResults: UnusedResults,
197200
UnitBindings: UnitBindings,

compiler/rustc_lint/src/lints.rs

+20
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,26 @@ pub enum ArrayIntoIterDiagSub {
5050
},
5151
}
5252

53+
// autorefs.rs
54+
#[derive(LintDiagnostic)]
55+
#[diag(lint_implicit_unsafe_autorefs)]
56+
#[note]
57+
pub struct ImplicitUnsafeAutorefsDiag {
58+
#[subdiagnostic]
59+
pub suggestion: ImplicitUnsafeAutorefsSuggestion,
60+
}
61+
62+
#[derive(Subdiagnostic)]
63+
#[multipart_suggestion(lint_suggestion, applicability = "maybe-incorrect")]
64+
pub struct ImplicitUnsafeAutorefsSuggestion {
65+
pub mutbl: &'static str,
66+
pub deref: &'static str,
67+
#[suggestion_part(code = "({mutbl}{deref}")]
68+
pub start_span: Span,
69+
#[suggestion_part(code = ")")]
70+
pub end_span: Span,
71+
}
72+
5373
// builtin.rs
5474
#[derive(LintDiagnostic)]
5575
#[diag(lint_builtin_while_true)]

library/alloc/src/vec/mod.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -2141,7 +2141,7 @@ impl<T, A: Allocator> Vec<T, A> {
21412141
#[cfg(not(no_global_oom_handling))]
21422142
#[inline]
21432143
unsafe fn append_elements(&mut self, other: *const [T]) {
2144-
let count = unsafe { (*other).len() };
2144+
let count = other.len();
21452145
self.reserve(count);
21462146
let len = self.len();
21472147
unsafe { ptr::copy_nonoverlapping(other as *const T, self.as_mut_ptr().add(len), count) };

tests/ui/lint/implicit_autorefs.fixed

+66
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
//@ check-pass
2+
//@ run-rustfix
3+
4+
#![allow(dead_code)] // for the rustfix-ed code
5+
6+
use std::mem::ManuallyDrop;
7+
use std::ptr::addr_of_mut;
8+
use std::ptr::addr_of;
9+
use std::ops::Deref;
10+
11+
unsafe fn test_const(ptr: *const [u8]) -> *const [u8] {
12+
addr_of!((&(*ptr))[..16])
13+
//~^ WARN implicit auto-ref
14+
}
15+
16+
struct Test {
17+
field: [u8],
18+
}
19+
20+
unsafe fn test_field(ptr: *const Test) -> *const [u8] {
21+
let l = (&(*ptr).field).len();
22+
//~^ WARN implicit auto-ref
23+
24+
addr_of!((&(*ptr).field)[..l - 1])
25+
//~^ WARN implicit auto-ref
26+
}
27+
28+
unsafe fn test_builtin_index(a: *mut [String]) {
29+
_ = (&(*a)[0]).len();
30+
//~^ WARN implicit auto-ref
31+
32+
_ = (&(&(*a))[..1][0]).len();
33+
//~^ WARN implicit auto-ref
34+
//~^^ WARN implicit auto-ref
35+
}
36+
37+
unsafe fn test_overloaded_deref_const(ptr: *const ManuallyDrop<Test>) {
38+
_ = addr_of!((&(*ptr)).field);
39+
//~^ WARN implicit auto-ref
40+
}
41+
42+
unsafe fn test_overloaded_deref_mut(ptr: *mut ManuallyDrop<Test>) {
43+
_ = addr_of_mut!((&mut (*ptr)).field);
44+
//~^ WARN implicit auto-ref
45+
}
46+
47+
unsafe fn test_manually_overloaded_deref() {
48+
struct W<T>(T);
49+
50+
impl<T> Deref for W<T> {
51+
type Target = T;
52+
fn deref(&self) -> &T { &self.0 }
53+
}
54+
55+
let w: W<i32> = W(5);
56+
let w = addr_of!(w);
57+
let _p: *const i32 = addr_of!(*(&**w));
58+
//~^ WARN implicit auto-ref
59+
}
60+
61+
unsafe fn test_no_attr(ptr: *mut ManuallyDrop<u8>) {
62+
ptr.write(ManuallyDrop::new(1)); // should not warn, as `ManuallyDrop::write` is not
63+
// annotated with `#[rustc_no_implicit_auto_ref]`
64+
}
65+
66+
fn main() {}

tests/ui/lint/implicit_autorefs.rs

+66
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
//@ check-pass
2+
//@ run-rustfix
3+
4+
#![allow(dead_code)] // for the rustfix-ed code
5+
6+
use std::mem::ManuallyDrop;
7+
use std::ptr::addr_of_mut;
8+
use std::ptr::addr_of;
9+
use std::ops::Deref;
10+
11+
unsafe fn test_const(ptr: *const [u8]) -> *const [u8] {
12+
addr_of!((*ptr)[..16])
13+
//~^ WARN implicit auto-ref
14+
}
15+
16+
struct Test {
17+
field: [u8],
18+
}
19+
20+
unsafe fn test_field(ptr: *const Test) -> *const [u8] {
21+
let l = (*ptr).field.len();
22+
//~^ WARN implicit auto-ref
23+
24+
addr_of!((*ptr).field[..l - 1])
25+
//~^ WARN implicit auto-ref
26+
}
27+
28+
unsafe fn test_builtin_index(a: *mut [String]) {
29+
_ = (*a)[0].len();
30+
//~^ WARN implicit auto-ref
31+
32+
_ = (*a)[..1][0].len();
33+
//~^ WARN implicit auto-ref
34+
//~^^ WARN implicit auto-ref
35+
}
36+
37+
unsafe fn test_overloaded_deref_const(ptr: *const ManuallyDrop<Test>) {
38+
_ = addr_of!((*ptr).field);
39+
//~^ WARN implicit auto-ref
40+
}
41+
42+
unsafe fn test_overloaded_deref_mut(ptr: *mut ManuallyDrop<Test>) {
43+
_ = addr_of_mut!((*ptr).field);
44+
//~^ WARN implicit auto-ref
45+
}
46+
47+
unsafe fn test_manually_overloaded_deref() {
48+
struct W<T>(T);
49+
50+
impl<T> Deref for W<T> {
51+
type Target = T;
52+
fn deref(&self) -> &T { &self.0 }
53+
}
54+
55+
let w: W<i32> = W(5);
56+
let w = addr_of!(w);
57+
let _p: *const i32 = addr_of!(**w);
58+
//~^ WARN implicit auto-ref
59+
}
60+
61+
unsafe fn test_no_attr(ptr: *mut ManuallyDrop<u8>) {
62+
ptr.write(ManuallyDrop::new(1)); // should not warn, as `ManuallyDrop::write` is not
63+
// annotated with `#[rustc_no_implicit_auto_ref]`
64+
}
65+
66+
fn main() {}

0 commit comments

Comments
 (0)