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 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
2 changes: 1 addition & 1 deletion compiler/rustc_borrowck/src/type_check/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1776,7 +1776,7 @@ impl<'a, 'tcx> TypeChecker<'a, 'tcx> {
// `Sized` bound in no way depends on precise regions, so this
// shouldn't affect `is_sized`.
let erased_ty = tcx.erase_regions(ty);
if !erased_ty.is_sized(tcx.at(span), self.param_env) {
if !erased_ty.is_sized(tcx, self.param_env) {
// in current MIR construction, all non-control-flow rvalue
// expressions evaluate through `as_temp` or `into` a return
// slot or local, so to find all unsized rvalues it is enough
Expand Down
6 changes: 1 addition & 5 deletions compiler/rustc_codegen_cranelift/src/base.rs
Original file line number Diff line number Diff line change
Expand Up @@ -770,11 +770,7 @@ fn codegen_stmt<'tcx>(
lval.write_cvalue(fx, CValue::by_val(operand, box_layout));
}
Rvalue::NullaryOp(null_op, ty) => {
assert!(
lval.layout()
.ty
.is_sized(fx.tcx.at(stmt.source_info.span), ParamEnv::reveal_all())
);
assert!(lval.layout().ty.is_sized(fx.tcx, ParamEnv::reveal_all()));
let layout = fx.layout_of(fx.monomorphize(ty));
let val = match null_op {
NullOp::SizeOf => layout.size.bytes(),
Expand Down
3 changes: 1 addition & 2 deletions compiler/rustc_codegen_cranelift/src/constant.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ use rustc_middle::middle::codegen_fn_attrs::CodegenFnAttrFlags;
use rustc_middle::mir::interpret::{
read_target_uint, AllocId, ConstAllocation, ConstValue, ErrorHandled, GlobalAlloc, Scalar,
};
use rustc_span::DUMMY_SP;

use cranelift_module::*;

Expand Down Expand Up @@ -291,7 +290,7 @@ fn data_id_for_static(
let is_mutable = if tcx.is_mutable_static(def_id) {
true
} else {
!ty.is_freeze(tcx.at(DUMMY_SP), ParamEnv::reveal_all())
!ty.is_freeze(tcx, ParamEnv::reveal_all())
};
let align = tcx.layout_of(ParamEnv::reveal_all().and(ty)).unwrap().align.pref.bytes();

Expand Down
7 changes: 3 additions & 4 deletions compiler/rustc_codegen_ssa/src/traits/type_.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ use crate::common::TypeKind;
use crate::mir::place::PlaceRef;
use rustc_middle::ty::layout::TyAndLayout;
use rustc_middle::ty::{self, Ty};
use rustc_span::DUMMY_SP;
use rustc_target::abi::call::{ArgAbi, CastTarget, FnAbi, Reg};
use rustc_target::abi::{AddressSpace, Integer};

Expand Down Expand Up @@ -75,16 +74,16 @@ pub trait DerivedTypeMethods<'tcx>: BaseTypeMethods<'tcx> + MiscMethods<'tcx> {
}

fn type_is_sized(&self, ty: Ty<'tcx>) -> bool {
ty.is_sized(self.tcx().at(DUMMY_SP), ty::ParamEnv::reveal_all())
ty.is_sized(self.tcx(), ty::ParamEnv::reveal_all())
}

fn type_is_freeze(&self, ty: Ty<'tcx>) -> bool {
ty.is_freeze(self.tcx().at(DUMMY_SP), ty::ParamEnv::reveal_all())
ty.is_freeze(self.tcx(), ty::ParamEnv::reveal_all())
}

fn type_has_metadata(&self, ty: Ty<'tcx>) -> bool {
let param_env = ty::ParamEnv::reveal_all();
if ty.is_sized(self.tcx().at(DUMMY_SP), param_env) {
if ty.is_sized(self.tcx(), param_env) {
return false;
}

Expand Down
4 changes: 2 additions & 2 deletions compiler/rustc_const_eval/src/const_eval/valtrees.rs
Original file line number Diff line number Diff line change
Expand Up @@ -212,7 +212,7 @@ fn create_pointee_place<'tcx>(
) -> MPlaceTy<'tcx> {
let tcx = ecx.tcx.tcx;

if !ty.is_sized(ecx.tcx, ty::ParamEnv::empty()) {
if !ty.is_sized(*ecx.tcx, ty::ParamEnv::empty()) {
// We need to create `Allocation`s for custom DSTs

let (unsized_inner_ty, num_elems) = get_info_on_unsized_field(ty, valtree, tcx);
Expand Down Expand Up @@ -398,7 +398,7 @@ fn valtree_into_mplace<'tcx>(

let mut place_inner = match ty.kind() {
ty::Str | ty::Slice(_) => ecx.mplace_index(&place, i as u64).unwrap(),
_ if !ty.is_sized(ecx.tcx, ty::ParamEnv::empty())
_ if !ty.is_sized(*ecx.tcx, ty::ParamEnv::empty())
&& i == branches.len() - 1 =>
{
// Note: For custom DSTs we need to manually process the last unsized field.
Expand Down
2 changes: 1 addition & 1 deletion compiler/rustc_const_eval/src/interpret/eval_context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -468,7 +468,7 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {

#[inline]
pub fn type_is_freeze(&self, ty: Ty<'tcx>) -> bool {
ty.is_freeze(self.tcx, self.param_env)
ty.is_freeze(*self.tcx, self.param_env)
}

pub fn load_mir(
Expand Down
2 changes: 1 addition & 1 deletion compiler/rustc_const_eval/src/interpret/intern.rs
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ fn intern_shallow<'rt, 'mir, 'tcx, M: CompileTimeMachine<'mir, 'tcx, const_eval:
if let InternMode::Static(mutability) = mode {
// For this, we need to take into account `UnsafeCell`. When `ty` is `None`, we assume
// no interior mutability.
let frozen = ty.map_or(true, |ty| ty.is_freeze(ecx.tcx, ecx.param_env));
let frozen = ty.map_or(true, |ty| ty.is_freeze(*ecx.tcx, ecx.param_env));
// For statics, allocation mutability is the combination of place mutability and
// type mutability.
// The entire allocation needs to be mutable if it contains an `UnsafeCell` anywhere.
Expand Down
3 changes: 1 addition & 2 deletions compiler/rustc_const_eval/src/interpret/validity.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ use rustc_middle::mir::interpret::InterpError;
use rustc_middle::ty;
use rustc_middle::ty::layout::{LayoutOf, TyAndLayout};
use rustc_span::symbol::{sym, Symbol};
use rustc_span::DUMMY_SP;
use rustc_target::abi::{Abi, Scalar as ScalarAbi, Size, VariantIdx, Variants, WrappingRange};

use std::hash::Hash;
Expand Down Expand Up @@ -726,7 +725,7 @@ impl<'rt, 'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> ValueVisitor<'mir, 'tcx, M>
) -> InterpResult<'tcx> {
// Special check preventing `UnsafeCell` inside unions in the inner part of constants.
if matches!(self.ctfe_mode, Some(CtfeValidationMode::Const { inner: true, .. })) {
if !op.layout.ty.is_freeze(self.ecx.tcx.at(DUMMY_SP), self.ecx.param_env) {
if !op.layout.ty.is_freeze(*self.ecx.tcx, self.ecx.param_env) {
throw_validation_failure!(self.path, { "`UnsafeCell` in a `const`" });
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ use rustc_infer::infer::TyCtxtInferExt;
use rustc_middle::mir;
use rustc_middle::mir::*;
use rustc_middle::ty::{self, subst::SubstsRef, AdtDef, Ty};
use rustc_span::DUMMY_SP;
use rustc_trait_selection::traits::{
self, ImplSource, Obligation, ObligationCause, SelectionContext,
};
Expand Down Expand Up @@ -92,7 +91,7 @@ impl Qualif for HasMutInterior {
}

fn in_any_value_of_ty<'tcx>(cx: &ConstCx<'_, 'tcx>, ty: Ty<'tcx>) -> bool {
!ty.is_freeze(cx.tcx.at(DUMMY_SP), cx.param_env)
!ty.is_freeze(cx.tcx, cx.param_env)
}

fn in_adt_inherently<'tcx>(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ use rustc_middle::mir::{self, BasicBlock, Local, Location, Statement, StatementK
use rustc_mir_dataflow::fmt::DebugWithContext;
use rustc_mir_dataflow::JoinSemiLattice;
use rustc_mir_dataflow::{Analysis, AnalysisDomain, CallReturnPlaces};
use rustc_span::DUMMY_SP;

use std::fmt;
use std::marker::PhantomData;
Expand Down Expand Up @@ -120,10 +119,7 @@ where
///
/// [rust-lang/unsafe-code-guidelines#134]: https://github.com/rust-lang/unsafe-code-guidelines/issues/134
fn shared_borrow_allows_mutation(&self, place: mir::Place<'tcx>) -> bool {
!place
.ty(self.ccx.body, self.ccx.tcx)
.ty
.is_freeze(self.ccx.tcx.at(DUMMY_SP), self.ccx.param_env)
!place.ty(self.ccx.body, self.ccx.tcx).ty.is_freeze(self.ccx.tcx, self.ccx.param_env)
}
}

Expand Down
3 changes: 1 addition & 2 deletions compiler/rustc_const_eval/src/transform/validate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -235,9 +235,8 @@ impl<'a, 'tcx> Visitor<'tcx> for TypeChecker<'a, 'tcx> {
// `Operand::Copy` is only supposed to be used with `Copy` types.
if let Operand::Copy(place) = operand {
let ty = place.ty(&self.body.local_decls, self.tcx).ty;
let span = self.body.source_info(location).span;

if !ty.is_copy_modulo_regions(self.tcx.at(span), self.param_env) {
if !ty.is_copy_modulo_regions(self.tcx, self.param_env) {
self.fail(location, format!("`Operand::Copy` with non-`Copy` type {}", ty));
}
}
Expand Down
2 changes: 1 addition & 1 deletion compiler/rustc_hir_analysis/src/check/check.rs
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ fn check_union_fields(tcx: TyCtxt<'_>, span: Span, item_def_id: LocalDefId) -> b
_ => {
// Fallback case: allow `ManuallyDrop` and things that are `Copy`.
ty.ty_adt_def().is_some_and(|adt_def| adt_def.is_manually_drop())
|| ty.is_copy_modulo_regions(tcx.at(span), param_env)
|| ty.is_copy_modulo_regions(tcx, param_env)
}
}
}
Expand Down
4 changes: 2 additions & 2 deletions compiler/rustc_hir_analysis/src/check/intrinsicck.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ impl<'a, 'tcx> InlineAsmCtxt<'a, 'tcx> {
fn is_thin_ptr_ty(&self, ty: Ty<'tcx>) -> bool {
// Type still may have region variables, but `Sized` does not depend
// on those, so just erase them before querying.
if ty.is_sized(self.tcx.at(DUMMY_SP), self.param_env) {
if ty.is_sized(self.tcx, self.param_env) {
return true;
}
if let ty::Foreign(..) = ty.kind() {
Expand Down Expand Up @@ -128,7 +128,7 @@ impl<'a, 'tcx> InlineAsmCtxt<'a, 'tcx> {

// Check that the type implements Copy. The only case where this can
// possibly fail is for SIMD types which don't #[derive(Copy)].
if !ty.is_copy_modulo_regions(self.tcx.at(expr.span), self.param_env) {
if !ty.is_copy_modulo_regions(self.tcx, self.param_env) {
let msg = "arguments for inline assembly must be copyable";
let mut err = self.tcx.sess.struct_span_err(expr.span, msg);
err.note(&format!("`{ty}` does not implement the Copy trait"));
Expand Down
2 changes: 1 addition & 1 deletion compiler/rustc_lint/src/builtin.rs
Original file line number Diff line number Diff line change
Expand Up @@ -752,7 +752,7 @@ impl<'tcx> LateLintPass<'tcx> for MissingCopyImplementations {
return;
}
let param_env = ty::ParamEnv::empty();
if ty.is_copy_modulo_regions(cx.tcx.at(item.span), param_env) {
if ty.is_copy_modulo_regions(cx.tcx, param_env) {
return;
}
if can_type_implement_copy(
Expand Down
125 changes: 125 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,125 @@
use crate::{LateContext, LateLintPass, LintContext};

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

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<'_>) {
let tck = cx.typeck_results();

let t = tck.expr_ty(expr);

// t = &mut t_pointee
if let &ty::Ref(.., t_pointee, hir::Mutability::Mut) = t.kind()
// // `t -> u` / reference -> pointer adjustments
&& let adjustments = tck.adjustments()
&& let Some(adjustments) = adjustments.get(expr.hir_id)
&& let [a, b] = &**adjustments
&& let Adjust::Deref(None) = a.kind
&& let Adjust::Borrow(AutoBorrow::RawPtr(_)) = b.kind
&& let u = b.target
// u = *const _
&& let ty::RawPtr(ty::TypeAndMut { mutbl: hir::Mutability::Not, ..}) = u.kind()
// 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))
{
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(
expr.span.shrink_to_hi(),
"to save write provenance, cast to `*mut` pointer first",
format!(" as *mut _"),
Applicability::MachineApplicable
)
.span_suggestion(
expr.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
6 changes: 3 additions & 3 deletions compiler/rustc_lint/src/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ use rustc_middle::ty::subst::SubstsRef;
use rustc_middle::ty::{self, AdtKind, DefIdTree, Ty, TyCtxt, TypeSuperVisitable, TypeVisitable};
use rustc_span::source_map;
use rustc_span::symbol::sym;
use rustc_span::{Span, Symbol, DUMMY_SP};
use rustc_span::{Span, Symbol};
use rustc_target::abi::{Abi, WrappingRange};
use rustc_target::abi::{Integer, TagEncoding, Variants};
use rustc_target::spec::abi::Abi as SpecAbi;
Expand Down Expand Up @@ -931,7 +931,7 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> {
match *ty.kind() {
ty::Adt(def, substs) => {
if def.is_box() && matches!(self.mode, CItemKind::Definition) {
if ty.boxed_ty().is_sized(tcx.at(DUMMY_SP), self.cx.param_env) {
if ty.boxed_ty().is_sized(tcx, self.cx.param_env) {
return FfiSafe;
} else {
return FfiUnsafe {
Expand Down Expand Up @@ -1082,7 +1082,7 @@ impl<'a, 'tcx> ImproperCTypesVisitor<'a, 'tcx> {
ty::RawPtr(ty::TypeAndMut { ty, .. }) | ty::Ref(_, ty, _)
if {
matches!(self.mode, CItemKind::Definition)
&& ty.is_sized(self.cx.tcx.at(DUMMY_SP), self.cx.param_env)
&& ty.is_sized(self.cx.tcx, self.cx.param_env)
} =>
{
FfiSafe
Expand Down
4 changes: 2 additions & 2 deletions compiler/rustc_middle/src/ty/layout.rs
Original file line number Diff line number Diff line change
Expand Up @@ -830,7 +830,7 @@ where
} else {
match mt {
hir::Mutability::Not => {
if ty.is_freeze(tcx.at(DUMMY_SP), cx.param_env()) {
if ty.is_freeze(tcx, cx.param_env()) {
PointerKind::Frozen
} else {
PointerKind::SharedMutable
Expand All @@ -841,7 +841,7 @@ where
// noalias, as another pointer to the structure can be obtained, that
// is not based-on the original reference. We consider all !Unpin
// types to be potentially self-referential here.
if ty.is_unpin(tcx.at(DUMMY_SP), cx.param_env()) {
if ty.is_unpin(tcx, cx.param_env()) {
PointerKind::UniqueBorrowed
} else {
PointerKind::UniqueBorrowedPinned
Expand Down
Loading