Skip to content

Commit

Permalink
compile-time evaluation: emit a lint when a write through an immutabl…
Browse files Browse the repository at this point in the history
…e pointer occurs
  • Loading branch information
RalfJung committed Nov 26, 2023
1 parent a3234b4 commit f25c190
Show file tree
Hide file tree
Showing 16 changed files with 380 additions and 186 deletions.
3 changes: 3 additions & 0 deletions compiler/rustc_const_eval/messages.ftl
Original file line number Diff line number Diff line change
Expand Up @@ -461,6 +461,9 @@ const_eval_validation_uninhabited_val = {$front_matter}: encountered a value of
const_eval_validation_uninit = {$front_matter}: encountered uninitialized memory, but {$expected}
const_eval_validation_unsafe_cell = {$front_matter}: encountered `UnsafeCell` in a `const`
const_eval_write_through_immutable_pointer =
writing through a pointer that was derived from a shared (immutable) reference
const_eval_write_to_read_only =
writing to {$allocation} which is read-only
const_eval_zst_pointer_out_of_bounds =
Expand Down
44 changes: 36 additions & 8 deletions compiler/rustc_const_eval/src/const_eval/error.rs
Original file line number Diff line number Diff line change
@@ -1,14 +1,16 @@
use std::mem;

use rustc_errors::{DiagnosticArgValue, DiagnosticMessage, IntoDiagnostic, IntoDiagnosticArg};
use rustc_hir::CRATE_HIR_ID;
use rustc_middle::mir::AssertKind;
use rustc_middle::query::TyCtxtAt;
use rustc_middle::ty::TyCtxt;
use rustc_middle::ty::{layout::LayoutError, ConstInt};
use rustc_span::{ErrorGuaranteed, Span, Symbol, DUMMY_SP};

use super::InterpCx;
use super::{CompileTimeInterpreter, InterpCx};
use crate::errors::{self, FrameNote, ReportErrorExt};
use crate::interpret::{ErrorHandled, InterpError, InterpErrorInfo, Machine, MachineStopType};
use crate::interpret::{ErrorHandled, InterpError, InterpErrorInfo, MachineStopType};

/// The CTFE machine has some custom error kinds.
#[derive(Clone, Debug)]
Expand Down Expand Up @@ -57,16 +59,20 @@ impl<'tcx> Into<InterpErrorInfo<'tcx>> for ConstEvalErrKind {
}
}

pub fn get_span_and_frames<'tcx, 'mir, M: Machine<'mir, 'tcx>>(
ecx: &InterpCx<'mir, 'tcx, M>,
pub fn get_span_and_frames<'tcx, 'mir>(
tcx: TyCtxtAt<'tcx>,
machine: &CompileTimeInterpreter<'mir, 'tcx>,
) -> (Span, Vec<errors::FrameNote>)
where
'tcx: 'mir,
{
let mut stacktrace = ecx.generate_stacktrace();
let mut stacktrace =
InterpCx::<CompileTimeInterpreter<'mir, 'tcx>>::generate_stacktrace_from_stack(
&machine.stack,
);
// Filter out `requires_caller_location` frames.
stacktrace.retain(|frame| !frame.instance.def.requires_caller_location(*ecx.tcx));
let span = stacktrace.first().map(|f| f.span).unwrap_or(ecx.tcx.span);
stacktrace.retain(|frame| !frame.instance.def.requires_caller_location(*tcx));
let span = stacktrace.first().map(|f| f.span).unwrap_or(tcx.span);

let mut frames = Vec::new();

Expand All @@ -87,7 +93,7 @@ where

let mut last_frame: Option<errors::FrameNote> = None;
for frame_info in &stacktrace {
let frame = frame_info.as_note(*ecx.tcx);
let frame = frame_info.as_note(*tcx);
match last_frame.as_mut() {
Some(last_frame)
if last_frame.span == frame.span
Expand Down Expand Up @@ -156,3 +162,25 @@ where
}
}
}

/// Emit a lint from a const-eval situation.
// Even if this is unused, please don't remove it -- chances are we will need to emit a lint during const-eval again in the future!
pub(super) fn lint<'tcx, 'mir, L>(
tcx: TyCtxtAt<'tcx>,
machine: &CompileTimeInterpreter<'mir, 'tcx>,
lint: &'static rustc_session::lint::Lint,
decorator: impl FnOnce(Vec<errors::FrameNote>) -> L,
) where
L: for<'a> rustc_errors::DecorateLint<'a, ()>,
{
let (span, frames) = get_span_and_frames(tcx, machine);

tcx.emit_spanned_lint(
lint,
// We use the root frame for this so the crate that defines the const defines whether the
// lint is emitted.
machine.stack.first().and_then(|frame| frame.lint_root()).unwrap_or(CRATE_HIR_ID),
span,
decorator(frames),
);
}
4 changes: 2 additions & 2 deletions compiler/rustc_const_eval/src/const_eval/eval_queries.rs
Original file line number Diff line number Diff line change
Expand Up @@ -338,7 +338,7 @@ pub fn eval_in_interpreter<'mir, 'tcx>(
*ecx.tcx,
error,
None,
|| super::get_span_and_frames(&ecx),
|| super::get_span_and_frames(ecx.tcx, &ecx.machine),
|span, frames| ConstEvalError {
span,
error_kind: kind,
Expand Down Expand Up @@ -419,7 +419,7 @@ pub fn const_report_error<'mir, 'tcx>(
*ecx.tcx,
error,
None,
|| crate::const_eval::get_span_and_frames(ecx),
|| crate::const_eval::get_span_and_frames(ecx.tcx, &ecx.machine),
move |span, frames| errors::UndefinedBehavior { span, ub_note, frames, raw_bytes },
)
}
65 changes: 52 additions & 13 deletions compiler/rustc_const_eval/src/const_eval/machine.rs
Original file line number Diff line number Diff line change
@@ -1,30 +1,30 @@
use rustc_hir::def::DefKind;
use rustc_hir::LangItem;
use rustc_middle::mir;
use rustc_middle::mir::interpret::PointerArithmetic;
use rustc_middle::ty::layout::{FnAbiOf, TyAndLayout};
use rustc_middle::ty::{self, TyCtxt};
use rustc_span::Span;
use std::borrow::Borrow;
use std::fmt;
use std::hash::Hash;
use std::ops::ControlFlow;

use rustc_ast::Mutability;
use rustc_data_structures::fx::FxIndexMap;
use rustc_data_structures::fx::IndexEntry;
use std::fmt;

use rustc_ast::Mutability;
use rustc_hir::def::DefKind;
use rustc_hir::def_id::DefId;
use rustc_hir::LangItem;
use rustc_middle::mir;
use rustc_middle::mir::AssertMessage;
use rustc_middle::query::TyCtxtAt;
use rustc_middle::ty;
use rustc_middle::ty::layout::{FnAbiOf, TyAndLayout};
use rustc_session::lint::builtin::WRITES_THROUGH_IMMUTABLE_POINTER;
use rustc_span::symbol::{sym, Symbol};
use rustc_span::Span;
use rustc_target::abi::{Align, Size};
use rustc_target::spec::abi::Abi as CallAbi;

use crate::errors::{LongRunning, LongRunningWarn};
use crate::fluent_generated as fluent;
use crate::interpret::{
self, compile_time_machine, AllocId, ConstAllocation, FnArg, FnVal, Frame, ImmTy, InterpCx,
InterpResult, OpTy, PlaceTy, Pointer, Scalar,
self, compile_time_machine, AllocId, AllocRange, ConstAllocation, CtfeProvenance, FnArg, FnVal,
Frame, ImmTy, InterpCx, InterpResult, OpTy, PlaceTy, Pointer, PointerArithmetic, Scalar,
};

use super::error::*;
Expand Down Expand Up @@ -671,7 +671,7 @@ impl<'mir, 'tcx> interpret::Machine<'mir, 'tcx> for CompileTimeInterpreter<'mir,
}

fn before_access_global(
_tcx: TyCtxt<'tcx>,
_tcx: TyCtxtAt<'tcx>,
machine: &Self,
alloc_id: AllocId,
alloc: ConstAllocation<'tcx>,
Expand Down Expand Up @@ -708,6 +708,45 @@ impl<'mir, 'tcx> interpret::Machine<'mir, 'tcx> for CompileTimeInterpreter<'mir,
}
}
}

fn retag_ptr_value(
ecx: &mut InterpCx<'mir, 'tcx, Self>,
_kind: mir::RetagKind,
val: &ImmTy<'tcx, CtfeProvenance>,
) -> InterpResult<'tcx, ImmTy<'tcx, CtfeProvenance>> {
if let ty::Ref(_, ty, mutbl) = val.layout.ty.kind()
&& *mutbl == Mutability::Not
&& ty.is_freeze(*ecx.tcx, ecx.param_env)
{
// This is a frozen shared reference, mark it immutable.
let place = ecx.ref_to_mplace(val)?;
let new_place = place.map_provenance(|p| p.map(CtfeProvenance::as_immutable));
Ok(ImmTy::from_immediate(new_place.to_ref(ecx), val.layout))
} else {
Ok(val.clone())
}
}

fn before_memory_write(
tcx: TyCtxtAt<'tcx>,
machine: &mut Self,
_alloc_extra: &mut Self::AllocExtra,
(_alloc_id, immutable): (AllocId, bool),
range: AllocRange,
) -> InterpResult<'tcx> {
if range.size == Size::ZERO {
// Nothing to check.
return Ok(());
}
// Reject writes through immutable pointers.
if immutable {
super::lint(tcx, machine, WRITES_THROUGH_IMMUTABLE_POINTER, |frames| {
crate::errors::WriteThroughImmutablePointer { frames }
});
}
// Everything else is fine.
Ok(())
}
}

// Please do not add any code below the above `Machine` trait impl. I (oli-obk) plan more cleanups
Expand Down
7 changes: 7 additions & 0 deletions compiler/rustc_const_eval/src/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -402,6 +402,13 @@ pub struct ConstEvalError {
pub frame_notes: Vec<FrameNote>,
}

#[derive(LintDiagnostic)]
#[diag(const_eval_write_through_immutable_pointer)]
pub struct WriteThroughImmutablePointer {
#[subdiagnostic]
pub frames: Vec<FrameNote>,
}

#[derive(Diagnostic)]
#[diag(const_eval_nullary_intrinsic_fail)]
pub struct NullaryIntrinsicError {
Expand Down
15 changes: 8 additions & 7 deletions compiler/rustc_const_eval/src/interpret/machine.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,9 @@ use std::hash::Hash;
use rustc_apfloat::{Float, FloatConvert};
use rustc_ast::{InlineAsmOptions, InlineAsmTemplatePiece};
use rustc_middle::mir;
use rustc_middle::query::TyCtxtAt;
use rustc_middle::ty;
use rustc_middle::ty::layout::TyAndLayout;
use rustc_middle::ty::{self, TyCtxt};
use rustc_span::def_id::DefId;
use rustc_target::abi::Size;
use rustc_target::spec::abi::Abi as CallAbi;
Expand Down Expand Up @@ -285,7 +286,7 @@ pub trait Machine<'mir, 'tcx: 'mir>: Sized {
/// `def_id` is `Some` if this is the "lazy" allocation of a static.
#[inline]
fn before_access_global(
_tcx: TyCtxt<'tcx>,
_tcx: TyCtxtAt<'tcx>,
_machine: &Self,
_alloc_id: AllocId,
_allocation: ConstAllocation<'tcx>,
Expand Down Expand Up @@ -380,7 +381,7 @@ pub trait Machine<'mir, 'tcx: 'mir>: Sized {
/// need to mutate.
#[inline(always)]
fn before_memory_read(
_tcx: TyCtxt<'tcx>,
_tcx: TyCtxtAt<'tcx>,
_machine: &Self,
_alloc_extra: &Self::AllocExtra,
_prov: (AllocId, Self::ProvenanceExtra),
Expand All @@ -392,7 +393,7 @@ pub trait Machine<'mir, 'tcx: 'mir>: Sized {
/// Hook for performing extra checks on a memory write access.
#[inline(always)]
fn before_memory_write(
_tcx: TyCtxt<'tcx>,
_tcx: TyCtxtAt<'tcx>,
_machine: &mut Self,
_alloc_extra: &mut Self::AllocExtra,
_prov: (AllocId, Self::ProvenanceExtra),
Expand All @@ -404,7 +405,7 @@ pub trait Machine<'mir, 'tcx: 'mir>: Sized {
/// Hook for performing extra operations on a memory deallocation.
#[inline(always)]
fn before_memory_deallocation(
_tcx: TyCtxt<'tcx>,
_tcx: TyCtxtAt<'tcx>,
_machine: &mut Self,
_alloc_extra: &mut Self::AllocExtra,
_prov: (AllocId, Self::ProvenanceExtra),
Expand Down Expand Up @@ -507,7 +508,7 @@ pub trait Machine<'mir, 'tcx: 'mir>: Sized {
/// (CTFE and ConstProp) use the same instance. Here, we share that code.
pub macro compile_time_machine(<$mir: lifetime, $tcx: lifetime>) {
type Provenance = CtfeProvenance;
type ProvenanceExtra = (); // FIXME extract the "immutable" bool?
type ProvenanceExtra = bool; // the "immutable" flag

type ExtraFnVal = !;

Expand Down Expand Up @@ -595,6 +596,6 @@ pub macro compile_time_machine(<$mir: lifetime, $tcx: lifetime>) {
) -> Option<(AllocId, Size, Self::ProvenanceExtra)> {
// We know `offset` is relative to the allocation, so we can use `into_parts`.
let (prov, offset) = ptr.into_parts();
Some((prov.alloc_id(), offset, ()))
Some((prov.alloc_id(), offset, prov.immutable()))
}
}
14 changes: 7 additions & 7 deletions compiler/rustc_const_eval/src/interpret/memory.rs
Original file line number Diff line number Diff line change
Expand Up @@ -338,7 +338,7 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
// Let the machine take some extra action
let size = alloc.size();
M::before_memory_deallocation(
*self.tcx,
self.tcx,
&mut self.machine,
&mut alloc.extra,
(alloc_id, prov),
Expand Down Expand Up @@ -545,7 +545,7 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
(val, Some(def_id))
}
};
M::before_access_global(*self.tcx, &self.machine, id, alloc, def_id, is_write)?;
M::before_access_global(self.tcx, &self.machine, id, alloc, def_id, is_write)?;
// We got tcx memory. Let the machine initialize its "extra" stuff.
M::adjust_allocation(
self,
Expand Down Expand Up @@ -610,7 +610,7 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
)?;
if let Some((alloc_id, offset, prov, alloc)) = ptr_and_alloc {
let range = alloc_range(offset, size);
M::before_memory_read(*self.tcx, &self.machine, &alloc.extra, (alloc_id, prov), range)?;
M::before_memory_read(self.tcx, &self.machine, &alloc.extra, (alloc_id, prov), range)?;
Ok(Some(AllocRef { alloc, range, tcx: *self.tcx, alloc_id }))
} else {
// Even in this branch we have to be sure that we actually access the allocation, in
Expand Down Expand Up @@ -671,13 +671,13 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
{
let parts = self.get_ptr_access(ptr, size)?;
if let Some((alloc_id, offset, prov)) = parts {
let tcx = *self.tcx;
let tcx = self.tcx;
// FIXME: can we somehow avoid looking up the allocation twice here?
// We cannot call `get_raw_mut` inside `check_and_deref_ptr` as that would duplicate `&mut self`.
let (alloc, machine) = self.get_alloc_raw_mut(alloc_id)?;
let range = alloc_range(offset, size);
M::before_memory_write(tcx, machine, &mut alloc.extra, (alloc_id, prov), range)?;
Ok(Some(AllocRefMut { alloc, range, tcx, alloc_id }))
Ok(Some(AllocRefMut { alloc, range, tcx: *tcx, alloc_id }))
} else {
Ok(None)
}
Expand Down Expand Up @@ -1117,7 +1117,7 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
let src_alloc = self.get_alloc_raw(src_alloc_id)?;
let src_range = alloc_range(src_offset, size);
M::before_memory_read(
*tcx,
tcx,
&self.machine,
&src_alloc.extra,
(src_alloc_id, src_prov),
Expand Down Expand Up @@ -1147,7 +1147,7 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
let (dest_alloc, extra) = self.get_alloc_raw_mut(dest_alloc_id)?;
let dest_range = alloc_range(dest_offset, size * num_copies);
M::before_memory_write(
*tcx,
tcx,
extra,
&mut dest_alloc.extra,
(dest_alloc_id, dest_prov),
Expand Down

0 comments on commit f25c190

Please sign in to comment.