Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement a lint for implicit autoref of raw pointer dereference - take 2 #123239

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions compiler/rustc_feature/src/builtin_attrs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -882,6 +882,10 @@ pub const BUILTIN_ATTRIBUTES: &[BuiltinAttribute] = &[
EncodeCrossCrate::Yes,
"#[rustc_never_returns_null_ptr] is used to mark functions returning non-null pointers."
),
rustc_attr!(
rustc_no_implicit_autorefs, AttributeType::Normal, template!(Word), ErrorFollowing, EncodeCrossCrate::Yes,
"#[rustc_no_implicit_autorefs] is used to mark functions that should not autorefs in raw pointers context."
),
rustc_attr!(
rustc_coherence_is_core, AttributeType::CrateLevel, template!(Word), ErrorFollowing, EncodeCrossCrate::No,
"#![rustc_coherence_is_core] allows inherent methods on builtin types, only intended to be used in `core`."
Expand Down
4 changes: 4 additions & 0 deletions compiler/rustc_lint/messages.ftl
Original file line number Diff line number Diff line change
Expand Up @@ -351,6 +351,10 @@ lint_impl_trait_overcaptures = `{$self_ty}` will capture more lifetimes than pos
lint_impl_trait_redundant_captures = all possible in-scope parameters are already captured, so `use<...>` syntax is redundant
.suggestion = remove the `use<...>` syntax

lint_implicit_unsafe_autorefs = implicit auto-ref creates a reference to a dereference of a raw pointer
.note = creating a reference requires the pointer to be valid and imposes aliasing requirements
.suggestion = try using a raw pointer method instead; or if this reference is intentional, make it explicit

lint_improper_ctypes = `extern` {$desc} uses type `{$ty}`, which is not FFI-safe
.label = not FFI-safe
.note = the type is defined here
Expand Down
175 changes: 175 additions & 0 deletions compiler/rustc_lint/src/autorefs.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,175 @@
use rustc_ast::{BorrowKind, UnOp};
use rustc_hir::{Expr, ExprKind, Mutability};
use rustc_middle::ty::adjustment::{Adjust, Adjustment, AutoBorrow, OverloadedDeref};
use rustc_middle::ty::{TyCtxt, TypeckResults};
use rustc_session::{declare_lint, declare_lint_pass};
use rustc_span::sym;

use crate::lints::{ImplicitUnsafeAutorefsDiag, ImplicitUnsafeAutorefsSuggestion};
use crate::{LateContext, LateLintPass, LintContext};

declare_lint! {
/// The `dangerous_implicit_autorefs` lint checks for implicitly taken references
/// to dereferences of raw pointers.
///
/// ### Example
///
/// ```rust
/// use std::ptr::addr_of_mut;
///
/// unsafe fn fun(ptr: *mut [u8]) -> *mut [u8] {
/// addr_of_mut!((*ptr)[..16])
/// // ^^^^^^ this calls `IndexMut::index_mut(&mut ..., ..16)`,
/// // implicitly creating a reference
/// }
/// ```
///
/// {{produces}}
///
/// ### Explanation
///
/// When working with raw pointers it's usually undesirable to create references,
/// since they inflict a lot of safety requirement. Unfortunately, it's possible
/// to take a reference to a dereference of a raw pointer implicitly, which inflicts
/// the usual reference requirements without you even knowing that.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
/// the usual reference requirements without you even knowing that.
/// the usual reference requirements potentially without you realizing that.

More neutral, less “condescending” imo.

///
/// If you are sure, you can soundly take a reference, then you can take it explicitly:
/// ```rust
/// # use std::ptr::addr_of_mut;
/// unsafe fn fun(ptr: *mut [u8]) -> *mut [u8] {
/// addr_of_mut!((&mut *ptr)[..16])
/// }
/// ```
///
/// Otherwise try to find an alternative way to achive your goals that work only with
/// raw pointers:
/// ```rust
/// #![feature(slice_ptr_get)]
///
/// unsafe fn fun(ptr: *mut [u8]) -> *mut [u8] {
/// ptr.get_unchecked_mut(..16)
/// }
/// ```
pub DANGEROUS_IMPLICIT_AUTOREFS,
Warn,
"implicit reference to a dereference of a raw pointer",
report_in_external_macro
}

declare_lint_pass!(ImplicitAutorefs => [DANGEROUS_IMPLICIT_AUTOREFS]);

impl<'tcx> LateLintPass<'tcx> for ImplicitAutorefs {
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
// This logic has mostly been taken from
// https://github.com/rust-lang/rust/pull/103735#issuecomment-1370420305

// 4. Either of the following:
// a. A deref followed by any non-deref place projection (that intermediate
// deref will typically be auto-inserted)
// b. A method call annotated with `#[rustc_no_implicit_refs]`.
// c. A deref followed by a `addr_of!` or `addr_of_mut!`.
let mut is_coming_from_deref = false;
let inner = match expr.kind {
ExprKind::AddrOf(BorrowKind::Raw, _, inner) => match inner.kind {
ExprKind::Unary(UnOp::Deref, inner) => {
is_coming_from_deref = true;
inner
}
_ => return,
},
ExprKind::Index(base, _idx, _) => base,
ExprKind::MethodCall(_, inner, _, _)
if let Some(def_id) = cx.typeck_results().type_dependent_def_id(expr.hir_id)
&& cx.tcx.has_attr(def_id, sym::rustc_no_implicit_autorefs) =>
{
inner
}
ExprKind::Call(path, [expr, ..])
if let ExprKind::Path(ref qpath) = path.kind
&& let Some(def_id) = cx.qpath_res(qpath, path.hir_id).opt_def_id()
&& cx.tcx.has_attr(def_id, sym::rustc_no_implicit_autorefs) =>
{
expr
}
ExprKind::Field(inner, _) => {
let typeck = cx.typeck_results();
let adjustments_table = typeck.adjustments();
if let Some(adjustments) = adjustments_table.get(inner.hir_id)
&& let [adjustment] = &**adjustments
&& let &Adjust::Deref(Some(OverloadedDeref { .. })) = &adjustment.kind
{
inner
} else {
return;
}
}
_ => return,
};

let typeck = cx.typeck_results();
let adjustments_table = typeck.adjustments();

if let Some(adjustments) = adjustments_table.get(inner.hir_id)
&& let [adjustment] = &**adjustments
// 3. An automatically inserted reference.
&& let Some((mutbl, _implicit_borrow)) = has_implicit_borrow(adjustment)
&& let ExprKind::Unary(UnOp::Deref, dereferenced) =
// 2. Any number of place projections
peel_place_mappers(cx.tcx, typeck, inner).kind
// 1. Deref of a raw pointer
&& typeck.expr_ty(dereferenced).is_unsafe_ptr()
{
cx.emit_span_lint(
DANGEROUS_IMPLICIT_AUTOREFS,
expr.span.source_callsite(),
ImplicitUnsafeAutorefsDiag {
suggestion: ImplicitUnsafeAutorefsSuggestion {
mutbl: mutbl.ref_prefix_str(),
deref: if is_coming_from_deref { "*" } else { "" },
start_span: inner.span.shrink_to_lo(),
end_span: inner.span.shrink_to_hi(),
},
},
)
}
}
}

/// Peels expressions from `expr` that can map a place.
fn peel_place_mappers<'tcx>(
_tcx: TyCtxt<'tcx>,
_typeck: &TypeckResults<'tcx>,
mut expr: &'tcx Expr<'tcx>,
) -> &'tcx Expr<'tcx> {
loop {
match expr.kind {
ExprKind::Index(base, _idx, _) => {
expr = &base;
}
ExprKind::Field(e, _) => expr = &e,
_ => break expr,
}
}
}

enum ImplicitBorrowKind {
Deref,
Borrow,
}

/// Test if some adjustment has some implicit borrow
///
/// Returns `Some(mutability)` if the argument adjustment has implicit borrow in it.
fn has_implicit_borrow(
Adjustment { kind, .. }: &Adjustment<'_>,
) -> Option<(Mutability, ImplicitBorrowKind)> {
match kind {
&Adjust::Deref(Some(OverloadedDeref { mutbl, .. })) => {
Some((mutbl, ImplicitBorrowKind::Deref))
}
&Adjust::Borrow(AutoBorrow::Ref(_, mutbl)) => {
Some((mutbl.into(), ImplicitBorrowKind::Borrow))
}
_ => None,
}
}
3 changes: 3 additions & 0 deletions compiler/rustc_lint/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@

mod async_closures;
mod async_fn_in_trait;
mod autorefs;
pub mod builtin;
mod context;
mod deref_into_dyn_supertrait;
Expand Down Expand Up @@ -90,6 +91,7 @@ mod unused;

use async_closures::AsyncClosureUsage;
use async_fn_in_trait::AsyncFnInTrait;
use autorefs::*;
use builtin::*;
use deref_into_dyn_supertrait::*;
use drop_forget_useless::*;
Expand Down Expand Up @@ -207,6 +209,7 @@ late_lint_methods!(
PathStatements: PathStatements,
LetUnderscore: LetUnderscore,
InvalidReferenceCasting: InvalidReferenceCasting,
ImplicitAutorefs: ImplicitAutorefs,
// Depends on referenced function signatures in expressions
UnusedResults: UnusedResults,
UnitBindings: UnitBindings,
Expand Down
20 changes: 20 additions & 0 deletions compiler/rustc_lint/src/lints.rs
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,26 @@ pub(crate) enum ShadowedIntoIterDiagSub {
},
}

// autorefs.rs
#[derive(LintDiagnostic)]
#[diag(lint_implicit_unsafe_autorefs)]
#[note]
pub(crate) struct ImplicitUnsafeAutorefsDiag {
#[subdiagnostic]
pub suggestion: ImplicitUnsafeAutorefsSuggestion,
}

#[derive(Subdiagnostic)]
#[multipart_suggestion(lint_suggestion, applicability = "maybe-incorrect")]
pub(crate) struct ImplicitUnsafeAutorefsSuggestion {
pub mutbl: &'static str,
pub deref: &'static str,
#[suggestion_part(code = "({mutbl}{deref}")]
pub start_span: Span,
#[suggestion_part(code = ")")]
pub end_span: Span,
}

// builtin.rs
#[derive(LintDiagnostic)]
#[diag(lint_builtin_while_true)]
Expand Down
3 changes: 3 additions & 0 deletions compiler/rustc_passes/src/check_attr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,9 @@ impl<'tcx> CheckAttrVisitor<'tcx> {
[sym::rustc_never_returns_null_ptr, ..] => {
self.check_applied_to_fn_or_method(hir_id, attr, span, target)
}
[sym::rustc_no_implicit_autorefs, ..] => {
self.check_applied_to_fn_or_method(hir_id, attr, span, target)
}
[sym::rustc_legacy_const_generics, ..] => {
self.check_rustc_legacy_const_generics(hir_id, attr, span, target, item)
}
Expand Down
1 change: 1 addition & 0 deletions compiler/rustc_span/src/symbol.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1702,6 +1702,7 @@ symbols! {
rustc_must_implement_one_of,
rustc_never_returns_null_ptr,
rustc_never_type_options,
rustc_no_implicit_autorefs,
rustc_no_mir_inline,
rustc_nonnull_optimization_guaranteed,
rustc_nounwind,
Expand Down
2 changes: 2 additions & 0 deletions library/alloc/src/string.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1817,6 +1817,7 @@ impl String {
#[stable(feature = "rust1", since = "1.0.0")]
#[rustc_const_unstable(feature = "const_vec_string_slice", issue = "129041")]
#[rustc_confusables("length", "size")]
#[cfg_attr(not(bootstrap), rustc_no_implicit_autorefs)]
pub const fn len(&self) -> usize {
self.vec.len()
}
Expand All @@ -1836,6 +1837,7 @@ impl String {
#[must_use]
#[stable(feature = "rust1", since = "1.0.0")]
#[rustc_const_unstable(feature = "const_vec_string_slice", issue = "129041")]
#[cfg_attr(not(bootstrap), rustc_no_implicit_autorefs)]
pub const fn is_empty(&self) -> bool {
self.len() == 0
}
Expand Down
2 changes: 1 addition & 1 deletion library/alloc/src/vec/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2549,7 +2549,7 @@ impl<T, A: Allocator> Vec<T, A> {
#[cfg(not(no_global_oom_handling))]
#[inline]
unsafe fn append_elements(&mut self, other: *const [T]) {
let count = unsafe { (*other).len() };
let count = other.len();
self.reserve(count);
let len = self.len();
unsafe { ptr::copy_nonoverlapping(other as *const T, self.as_mut_ptr().add(len), count) };
Expand Down
2 changes: 2 additions & 0 deletions library/core/src/ops/index.rs
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ pub trait Index<Idx: ?Sized> {
///
/// May panic if the index is out of bounds.
#[stable(feature = "rust1", since = "1.0.0")]
#[cfg_attr(not(bootstrap), rustc_no_implicit_autorefs)]
#[track_caller]
fn index(&self, index: Idx) -> &Self::Output;
}
Expand Down Expand Up @@ -171,6 +172,7 @@ pub trait IndexMut<Idx: ?Sized>: Index<Idx> {
///
/// May panic if the index is out of bounds.
#[stable(feature = "rust1", since = "1.0.0")]
#[cfg_attr(not(bootstrap), rustc_no_implicit_autorefs)]
#[track_caller]
fn index_mut(&mut self, index: Idx) -> &mut Self::Output;
}
2 changes: 1 addition & 1 deletion library/core/src/pin.rs
Original file line number Diff line number Diff line change
Expand Up @@ -676,7 +676,7 @@
//! let data_ptr = unpinned_src.data.as_ptr() as *const u8;
//! let slice_ptr = unpinned_src.slice.as_ptr() as *const u8;
//! let offset = slice_ptr.offset_from(data_ptr) as usize;
//! let len = (*unpinned_src.slice.as_ptr()).len();
//! let len = unpinned_src.slice.as_ptr().len();
//!
//! unpinned_self.slice = NonNull::from(&mut unpinned_self.data[offset..offset+len]);
//! }
Expand Down
6 changes: 6 additions & 0 deletions library/core/src/slice/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,7 @@ impl<T> [T] {
#[lang = "slice_len_fn"]
#[stable(feature = "rust1", since = "1.0.0")]
#[rustc_const_stable(feature = "const_slice_len", since = "1.39.0")]
#[cfg_attr(not(bootstrap), rustc_no_implicit_autorefs)]
#[inline]
#[must_use]
pub const fn len(&self) -> usize {
Expand All @@ -130,6 +131,7 @@ impl<T> [T] {
/// ```
#[stable(feature = "rust1", since = "1.0.0")]
#[rustc_const_stable(feature = "const_slice_is_empty", since = "1.39.0")]
#[cfg_attr(not(bootstrap), rustc_no_implicit_autorefs)]
#[inline]
#[must_use]
pub const fn is_empty(&self) -> bool {
Expand Down Expand Up @@ -598,6 +600,7 @@ impl<T> [T] {
/// assert_eq!(None, v.get(0..4));
/// ```
#[stable(feature = "rust1", since = "1.0.0")]
#[cfg_attr(not(bootstrap), rustc_no_implicit_autorefs)]
#[inline]
#[must_use]
pub fn get<I>(&self, index: I) -> Option<&I::Output>
Expand All @@ -623,6 +626,7 @@ impl<T> [T] {
/// assert_eq!(x, &[0, 42, 2]);
/// ```
#[stable(feature = "rust1", since = "1.0.0")]
#[cfg_attr(not(bootstrap), rustc_no_implicit_autorefs)]
#[inline]
#[must_use]
pub fn get_mut<I>(&mut self, index: I) -> Option<&mut I::Output>
Expand Down Expand Up @@ -660,6 +664,7 @@ impl<T> [T] {
/// }
/// ```
#[stable(feature = "rust1", since = "1.0.0")]
#[cfg_attr(not(bootstrap), rustc_no_implicit_autorefs)]
#[inline]
#[must_use]
pub unsafe fn get_unchecked<I>(&self, index: I) -> &I::Output
Expand Down Expand Up @@ -702,6 +707,7 @@ impl<T> [T] {
/// assert_eq!(x, &[1, 13, 4]);
/// ```
#[stable(feature = "rust1", since = "1.0.0")]
#[cfg_attr(not(bootstrap), rustc_no_implicit_autorefs)]
#[inline]
#[must_use]
pub unsafe fn get_unchecked_mut<I>(&mut self, index: I) -> &mut I::Output
Expand Down
2 changes: 2 additions & 0 deletions library/core/src/str/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,7 @@ impl str {
#[stable(feature = "rust1", since = "1.0.0")]
#[rustc_const_stable(feature = "const_str_len", since = "1.39.0")]
#[cfg_attr(not(test), rustc_diagnostic_item = "str_len")]
#[cfg_attr(not(bootstrap), rustc_no_implicit_autorefs)]
#[must_use]
#[inline]
pub const fn len(&self) -> usize {
Expand All @@ -154,6 +155,7 @@ impl str {
/// ```
#[stable(feature = "rust1", since = "1.0.0")]
#[rustc_const_stable(feature = "const_str_is_empty", since = "1.39.0")]
#[cfg_attr(not(bootstrap), rustc_no_implicit_autorefs)]
#[must_use]
#[inline]
pub const fn is_empty(&self) -> bool {
Expand Down
2 changes: 2 additions & 0 deletions src/tools/miri/tests/pass/dst-raw.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
// Test DST raw pointers

#![allow(dangerous_implicit_autorefs)]

trait Trait {
fn foo(&self) -> isize;
}
Expand Down
2 changes: 1 addition & 1 deletion tests/ui/dynamically-sized-types/dst-raw.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
//@ run-pass
// Test DST raw pointers


#![feature(unsized_tuple_coercion)]
#![allow(dangerous_implicit_autorefs)]

trait Trait {
fn foo(&self) -> isize;
Expand Down
Loading
Loading