compile-time evaluation: emit a lint when a write through an immutable pointer occurs
This commit is contained in:
parent
cb86303342
commit
4d93590d59
16 changed files with 380 additions and 185 deletions
|
@ -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)]
|
||||
|
@ -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();
|
||||
|
||||
|
@ -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
|
||||
|
@ -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),
|
||||
);
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
@ -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 },
|
||||
)
|
||||
}
|
||||
|
|
|
@ -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::*;
|
||||
|
@ -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>,
|
||||
|
@ -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
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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::{Align, Size};
|
||||
use rustc_target::spec::abi::Abi as CallAbi;
|
||||
|
@ -293,7 +294,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>,
|
||||
|
@ -388,7 +389,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),
|
||||
|
@ -400,7 +401,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),
|
||||
|
@ -412,7 +413,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),
|
||||
|
@ -515,7 +516,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 = !;
|
||||
|
||||
|
@ -597,6 +598,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()))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -339,7 +339,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),
|
||||
|
@ -561,7 +561,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,
|
||||
|
@ -626,7 +626,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
|
||||
|
@ -687,13 +687,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)
|
||||
}
|
||||
|
@ -1133,7 +1133,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),
|
||||
|
@ -1163,7 +1163,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),
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue