Skip to content

Add a lint against hidden reborrows in &mut -> *const casts #103652

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

Closed
Show file tree
Hide file tree
Changes from 1 commit
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
Prev Previous commit
Next Next commit
Implement hidden_reborrow_in_ptr_casts lint
  • Loading branch information
WaffleLapkin committed Oct 27, 2022
commit bdb34af3422b2cfb51a7fc0e0d48e55acf567ddc
119 changes: 119 additions & 0 deletions compiler/rustc_lint/src/hidden_reborrow_in_ptr_casts.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
use crate::{LateContext, LateLintPass, LintContext};

use hir::Expr;
use rustc_errors::Applicability;
use rustc_hir as hir;
use rustc_middle::ty::{self, Ty, TyCtxt};

declare_lint! {
/// The `hidden_reborrow_in_ptr_casts` lint checks for hidden reborrows in `&mut T` -> `*const T` casts.
///
/// ### Example
///
/// ```rust
/// _ = (&mut 0) as *const _;
/// ```
///
/// {{produces}}
///
/// ### Explanation
///
/// When casting `&mut T` to `*const T` what actually happens is
/// 1. `&mut T` is reborrowed into `&T`
/// 2. `&T` is casted to `*const T`
///
/// Because this goes through a `&T`, the resulting pointer does not have
/// write provenance. It is **undefined behaviuor** to write through the
/// resulting pointer or any pointers derived from it:
///
/// ```rust
/// let mut v = 0;
/// let v_mut = &mut v_mut
/// let ptr = v_mut as *const i32;
///
/// // UB
/// // unsafe { (ptr as *mut i32).write(1) };
/// ```
///
/// If you don't plan to write through the resulting pointer, you can
/// suppress this warning adding an explicit cast to a reference:
///
/// ```rust
/// let mut v = 0;
/// let v_mut = &mut v_mut
/// let ptr = v_mut as &i32 as *const i32;
///
/// assert_eq!(unsafe { ptr.read() }, 0);
/// ```
/// ```rust
/// let mut v = 0;
/// let v_mut = &mut v_mut
/// let ptr = &*v_mut as *const i32;
///
/// assert_eq!(unsafe { ptr.read() }, 0);
/// ```
///
/// If you want to keep the write provenance in a `*const` pointer, cast
/// to a `*mut` pointer first, to avoid intermidiate shared reference:
///
/// ```rust
/// let mut v = 0;
/// let v_mut = &mut v_mut
/// let ptr = v_mut as *mut i32 as *const i32;
///
/// // ok
/// unsafe { (ptr as *mut i32).write(1) };
/// assert_eq!(v, 1);
/// ```
pub HIDDEN_REBORROW_IN_PTR_CASTS,
Warn,
"hidden reborrow in a `&mut` -> `*const` cast that strips write provenance"
}

declare_lint_pass!(HiddenReborrowInPtrCasts => [HIDDEN_REBORROW_IN_PTR_CASTS]);

impl<'tcx> LateLintPass<'tcx> for HiddenReborrowInPtrCasts {
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
// `(e: t) as u`
if let hir::ExprKind::Cast(e, u) = expr.kind
&& let t = cx.typeck_results().expr_ty(e)
&& let u = cx.typeck_results().node_type(u.hir_id)
// t = &mut t_pointee
&& let &ty::Ref(.., t_pointee, mutbl) = t.kind()
&& mutbl == hir::Mutability::Mut
// t_pointee is freeze or have variables/generics that make it possibly !Freeze
&& (t_pointee.is_freeze(cx.tcx, cx.param_env) || has_molten_generics(t_pointee, cx.tcx, cx.param_env))
// u = *const _
&& let ty::RawPtr(ty::TypeAndMut { mutbl: hir::Mutability::Not, ..}) = u.kind()
{
let msg = "implicit reborrow results in a read-only pointer";
cx.struct_span_lint(HIDDEN_REBORROW_IN_PTR_CASTS, expr.span, msg, |lint| {
lint
.note("cast of `&mut` reference to `*const` pointer causes an implicit reborrow, which converts the reference to `&`, stripping write provenance")
.note("it is UB to write through the resulting pointer, even after casting it to `*mut`")
.span_suggestion(
e.span.shrink_to_hi(),
"to save write provenance, cast to `*mut` pointer first",
format!(" as *mut _"),
Applicability::MachineApplicable
)
.span_suggestion(
e.span.shrink_to_hi(),
"to make reborrow explicit, add cast to a shared reference",
format!(" as &_"),
Applicability::MachineApplicable
)
})
}
}
}

/// Returns `true` if the type is instantiated with at least one generic parameter
/// that itself is a generic parameter (for example of the outer function)
/// and is not freeze.
fn has_molten_generics<'tcx>(ty: Ty<'tcx>, tcx: TyCtxt<'tcx>, param_env: ty::ParamEnv<'tcx>) -> bool {
ty.walk().any(|arg| match arg.unpack() {
ty::GenericArgKind::Type(t) if matches!(t.kind(), ty::Param(..)) => !t.is_freeze(tcx, param_env),
_ => false,
})
}
3 changes: 3 additions & 0 deletions compiler/rustc_lint/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ mod enum_intrinsics_non_enums;
mod errors;
mod expect;
mod for_loops_over_fallibles;
mod hidden_reborrow_in_ptr_casts;
pub mod hidden_unicode_codepoints;
mod internal;
mod late;
Expand Down Expand Up @@ -88,6 +89,7 @@ use array_into_iter::ArrayIntoIter;
use builtin::*;
use enum_intrinsics_non_enums::EnumIntrinsicsNonEnums;
use for_loops_over_fallibles::*;
use hidden_reborrow_in_ptr_casts::*;
use hidden_unicode_codepoints::*;
use internal::*;
use let_underscore::*;
Expand Down Expand Up @@ -191,6 +193,7 @@ macro_rules! late_lint_mod_passes {
$args,
[
ForLoopsOverFallibles: ForLoopsOverFallibles,
HiddenReborrowInPtrCasts: HiddenReborrowInPtrCasts,
HardwiredLints: HardwiredLints,
ImproperCTypesDeclarations: ImproperCTypesDeclarations,
ImproperCTypesDefinitions: ImproperCTypesDefinitions,
Expand Down