Skip to content

Commit aa59587

Browse files
committed
Implement lint against dangerous implicit autorefs
1 parent c1cb4b8 commit aa59587

File tree

8 files changed

+598
-1
lines changed

8 files changed

+598
-1
lines changed

Diff for: compiler/rustc_lint/messages.ftl

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

Diff for: compiler/rustc_lint/src/autorefs.rs

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

Diff for: compiler/rustc_lint/src/lib.rs

+3
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@
3737

3838
mod async_closures;
3939
mod async_fn_in_trait;
40+
mod autorefs;
4041
pub mod builtin;
4142
mod context;
4243
mod dangling;
@@ -84,6 +85,7 @@ mod utils;
8485

8586
use async_closures::AsyncClosureUsage;
8687
use async_fn_in_trait::AsyncFnInTrait;
88+
use autorefs::*;
8789
use builtin::*;
8890
use dangling::*;
8991
use default_could_be_derived::DefaultCouldBeDerived;
@@ -203,6 +205,7 @@ late_lint_methods!(
203205
PathStatements: PathStatements,
204206
LetUnderscore: LetUnderscore,
205207
InvalidReferenceCasting: InvalidReferenceCasting,
208+
ImplicitAutorefs: ImplicitAutorefs,
206209
// Depends on referenced function signatures in expressions
207210
UnusedResults: UnusedResults,
208211
UnitBindings: UnitBindings,

Diff for: compiler/rustc_lint/src/lints.rs

+20
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,26 @@ pub(crate) enum ShadowedIntoIterDiagSub {
5555
},
5656
}
5757

58+
// autorefs.rs
59+
#[derive(LintDiagnostic)]
60+
#[diag(lint_implicit_unsafe_autorefs)]
61+
#[note]
62+
pub(crate) struct ImplicitUnsafeAutorefsDiag {
63+
#[subdiagnostic]
64+
pub suggestion: ImplicitUnsafeAutorefsSuggestion,
65+
}
66+
67+
#[derive(Subdiagnostic)]
68+
#[multipart_suggestion(lint_suggestion, applicability = "maybe-incorrect")]
69+
pub(crate) struct ImplicitUnsafeAutorefsSuggestion {
70+
pub mutbl: &'static str,
71+
pub deref: &'static str,
72+
#[suggestion_part(code = "({mutbl}{deref}")]
73+
pub start_span: Span,
74+
#[suggestion_part(code = ")")]
75+
pub end_span: Span,
76+
}
77+
5878
// builtin.rs
5979
#[derive(LintDiagnostic)]
6080
#[diag(lint_builtin_while_true)]

Diff for: library/alloc/src/vec/mod.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -2588,7 +2588,7 @@ impl<T, A: Allocator> Vec<T, A> {
25882588
#[inline]
25892589
#[track_caller]
25902590
unsafe fn append_elements(&mut self, other: *const [T]) {
2591-
let count = unsafe { (*other).len() };
2591+
let count = other.len();
25922592
self.reserve(count);
25932593
let len = self.len();
25942594
unsafe { ptr::copy_nonoverlapping(other as *const T, self.as_mut_ptr().add(len), count) };

Diff for: tests/ui/lint/implicit_autorefs.fixed

+99
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
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::ops::Deref;
8+
9+
unsafe fn test_const(ptr: *const [u8]) {
10+
let _ = (&(*ptr))[..16];
11+
//~^ WARN implicit auto-ref
12+
}
13+
14+
struct Test {
15+
field: [u8],
16+
}
17+
18+
unsafe fn test_field(ptr: *const Test) -> *const [u8] {
19+
let l = (&(*ptr).field).len();
20+
//~^ WARN implicit auto-ref
21+
22+
&raw const (&(*ptr).field)[..l - 1]
23+
//~^ WARN implicit auto-ref
24+
}
25+
26+
unsafe fn test_builtin_index(a: *mut [String]) {
27+
_ = (&(*a)[0]).len();
28+
//~^ WARN implicit auto-ref
29+
30+
_ = (&(&(*a))[..1][0]).len();
31+
//~^ WARN implicit auto-ref
32+
//~^^ WARN implicit auto-ref
33+
}
34+
35+
unsafe fn test_overloaded_deref_const(ptr: *const ManuallyDrop<Test>) {
36+
let _ = (&(*ptr)).field;
37+
//~^ WARN implicit auto-ref
38+
let _ = &raw const (&(*ptr)).field;
39+
//~^ WARN implicit auto-ref
40+
}
41+
42+
unsafe fn test_overloaded_deref_mut(ptr: *mut ManuallyDrop<Test>) {
43+
let _ = (&(*ptr)).field;
44+
//~^ WARN implicit auto-ref
45+
}
46+
47+
unsafe fn test_double_overloaded_deref_const(ptr: *const ManuallyDrop<ManuallyDrop<Test>>) {
48+
let _ = (&(*ptr)).field;
49+
//~^ WARN implicit auto-ref
50+
}
51+
52+
unsafe fn test_manually_overloaded_deref() {
53+
struct W<T>(T);
54+
55+
impl<T> Deref for W<T> {
56+
type Target = T;
57+
fn deref(&self) -> &T { &self.0 }
58+
}
59+
60+
let w: W<i32> = W(5);
61+
let w = &raw const w;
62+
let _p: *const i32 = &raw const *(&**w);
63+
//~^ WARN implicit auto-ref
64+
}
65+
66+
struct Test2 {
67+
// derefs to [u8]
68+
field: &'static [u8]
69+
}
70+
71+
fn test_more_manual_deref(ptr: *const Test2) -> usize {
72+
unsafe { (&(*ptr).field).len() }
73+
//~^ WARN implicit auto-ref
74+
}
75+
76+
unsafe fn test_no_attr(ptr: *mut ManuallyDrop<u8>) {
77+
ptr.write(ManuallyDrop::new(1)); // should not warn, as `ManuallyDrop::write` is not
78+
// annotated with `#[rustc_no_implicit_auto_ref]`
79+
}
80+
81+
unsafe fn test_vec_get(ptr: *mut Vec<u8>) {
82+
let _ = (&(*ptr)).get(0);
83+
//~^ WARN implicit auto-ref
84+
let _ = (&(*ptr)).get_unchecked(0);
85+
//~^ WARN implicit auto-ref
86+
let _ = (&mut (*ptr)).get_mut(0);
87+
//~^ WARN implicit auto-ref
88+
let _ = (&mut (*ptr)).get_unchecked_mut(0);
89+
//~^ WARN implicit auto-ref
90+
}
91+
92+
unsafe fn test_string(ptr: *mut String) {
93+
let _ = (&(*ptr)).len();
94+
//~^ WARN implicit auto-ref
95+
let _ = (&(*ptr)).is_empty();
96+
//~^ WARN implicit auto-ref
97+
}
98+
99+
fn main() {}

0 commit comments

Comments
 (0)