compiler: unnest rustc_const_eval::check_consts

This commit is contained in:
Jubilee Young
2024-05-24 01:33:13 -07:00
parent db6ec2618a
commit 87048a46fc
10 changed files with 3 additions and 4 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,141 @@
//! Check the bodies of `const`s, `static`s and `const fn`s for illegal operations.
//!
//! This module will eventually replace the parts of `qualify_consts.rs` that check whether a local
//! has interior mutability or needs to be dropped, as well as the visitor that emits errors when
//! it finds operations that are invalid in a certain context.
use rustc_attr as attr;
use rustc_errors::DiagCtxt;
use rustc_hir as hir;
use rustc_hir::def_id::{DefId, LocalDefId};
use rustc_middle::bug;
use rustc_middle::mir;
use rustc_middle::ty::{self, PolyFnSig, TyCtxt};
use rustc_span::Symbol;
pub use self::qualifs::Qualif;
pub mod check;
mod ops;
pub mod post_drop_elaboration;
pub mod qualifs;
mod resolver;
/// Information about the item currently being const-checked, as well as a reference to the global
/// context.
pub struct ConstCx<'mir, 'tcx> {
pub body: &'mir mir::Body<'tcx>,
pub tcx: TyCtxt<'tcx>,
pub param_env: ty::ParamEnv<'tcx>,
pub const_kind: Option<hir::ConstContext>,
}
impl<'mir, 'tcx> ConstCx<'mir, 'tcx> {
pub fn new(tcx: TyCtxt<'tcx>, body: &'mir mir::Body<'tcx>) -> Self {
let def_id = body.source.def_id().expect_local();
let param_env = tcx.param_env(def_id);
Self::new_with_param_env(tcx, body, param_env)
}
pub fn new_with_param_env(
tcx: TyCtxt<'tcx>,
body: &'mir mir::Body<'tcx>,
param_env: ty::ParamEnv<'tcx>,
) -> Self {
let const_kind = tcx.hir().body_const_context(body.source.def_id().expect_local());
ConstCx { body, tcx, param_env, const_kind }
}
pub(crate) fn dcx(&self) -> &'tcx DiagCtxt {
self.tcx.dcx()
}
pub fn def_id(&self) -> LocalDefId {
self.body.source.def_id().expect_local()
}
/// Returns the kind of const context this `Item` represents (`const`, `static`, etc.).
///
/// Panics if this `Item` is not const.
pub fn const_kind(&self) -> hir::ConstContext {
self.const_kind.expect("`const_kind` must not be called on a non-const fn")
}
pub fn is_const_stable_const_fn(&self) -> bool {
self.const_kind == Some(hir::ConstContext::ConstFn)
&& self.tcx.features().staged_api
&& is_const_stable_const_fn(self.tcx, self.def_id().to_def_id())
}
fn is_async(&self) -> bool {
self.tcx.asyncness(self.def_id()).is_async()
}
pub fn fn_sig(&self) -> PolyFnSig<'tcx> {
let did = self.def_id().to_def_id();
if self.tcx.is_closure_like(did) {
let ty = self.tcx.type_of(did).instantiate_identity();
let ty::Closure(_, args) = ty.kind() else { bug!("type_of closure not ty::Closure") };
args.as_closure().sig()
} else {
self.tcx.fn_sig(did).instantiate_identity()
}
}
}
pub fn rustc_allow_const_fn_unstable(
tcx: TyCtxt<'_>,
def_id: LocalDefId,
feature_gate: Symbol,
) -> bool {
let attrs = tcx.hir().attrs(tcx.local_def_id_to_hir_id(def_id));
attr::rustc_allow_const_fn_unstable(tcx.sess, attrs).any(|name| name == feature_gate)
}
/// Returns `true` if the given `const fn` is "const-stable".
///
/// Panics if the given `DefId` does not refer to a `const fn`.
///
/// Const-stability is only relevant for `const fn` within a `staged_api` crate. Only "const-stable"
/// functions can be called in a const-context by users of the stable compiler. "const-stable"
/// functions are subject to more stringent restrictions than "const-unstable" functions: They
/// cannot use unstable features and can only call other "const-stable" functions.
pub fn is_const_stable_const_fn(tcx: TyCtxt<'_>, def_id: DefId) -> bool {
// A default body in a `#[const_trait]` is not const-stable because const
// trait fns currently cannot be const-stable. We shouldn't
// restrict default bodies to only call const-stable functions.
if tcx.is_const_default_method(def_id) {
return false;
}
// Const-stability is only relevant for `const fn`.
assert!(tcx.is_const_fn_raw(def_id));
// A function is only const-stable if it has `#[rustc_const_stable]` or it the trait it belongs
// to is const-stable.
match tcx.lookup_const_stability(def_id) {
Some(stab) => stab.is_const_stable(),
None if is_parent_const_stable_trait(tcx, def_id) => {
// Remove this when `#![feature(const_trait_impl)]` is stabilized,
// returning `true` unconditionally.
tcx.dcx().span_delayed_bug(
tcx.def_span(def_id),
"trait implementations cannot be const stable yet",
);
true
}
None => false, // By default, items are not const stable.
}
}
fn is_parent_const_stable_trait(tcx: TyCtxt<'_>, def_id: DefId) -> bool {
let local_def_id = def_id.expect_local();
let hir_id = tcx.local_def_id_to_hir_id(local_def_id);
let parent_owner_id = tcx.parent_hir_id(hir_id).owner;
if !tcx.is_const_trait_impl_raw(parent_owner_id.to_def_id()) {
return false;
}
tcx.lookup_const_stability(parent_owner_id).is_some_and(|stab| stab.is_const_stable())
}

View File

@@ -0,0 +1,653 @@
//! Concrete error types for all operations which may be invalid in a certain const context.
use hir::def_id::LocalDefId;
use hir::{ConstContext, LangItem};
use rustc_errors::{codes::*, Diag};
use rustc_hir as hir;
use rustc_hir::def_id::DefId;
use rustc_infer::infer::TyCtxtInferExt;
use rustc_infer::traits::{ImplSource, Obligation, ObligationCause};
use rustc_middle::mir::{self, CallSource};
use rustc_middle::span_bug;
use rustc_middle::ty::print::{with_no_trimmed_paths, PrintTraitRefExt as _};
use rustc_middle::ty::{
self, suggest_constraining_type_param, Closure, FnDef, FnPtr, GenericArgKind, GenericArgsRef,
Param, TraitRef, Ty,
};
use rustc_middle::util::{call_kind, CallDesugaringKind, CallKind};
use rustc_session::parse::feature_err;
use rustc_span::symbol::sym;
use rustc_span::{BytePos, Pos, Span, Symbol};
use rustc_trait_selection::traits::SelectionContext;
use tracing::debug;
use super::ConstCx;
use crate::errors;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Status {
Allowed,
Unstable(Symbol),
Forbidden,
}
#[derive(Clone, Copy)]
pub enum DiagImportance {
/// An operation that must be removed for const-checking to pass.
Primary,
/// An operation that causes const-checking to fail, but is usually a side-effect of a `Primary` operation elsewhere.
Secondary,
}
/// An operation that is not *always* allowed in a const context.
pub trait NonConstOp<'tcx>: std::fmt::Debug {
/// Returns an enum indicating whether this operation is allowed within the given item.
fn status_in_item(&self, _ccx: &ConstCx<'_, 'tcx>) -> Status {
Status::Forbidden
}
fn importance(&self) -> DiagImportance {
DiagImportance::Primary
}
fn build_error(&self, ccx: &ConstCx<'_, 'tcx>, span: Span) -> Diag<'tcx>;
}
#[derive(Debug)]
pub struct FloatingPointOp;
impl<'tcx> NonConstOp<'tcx> for FloatingPointOp {
fn status_in_item(&self, ccx: &ConstCx<'_, 'tcx>) -> Status {
if ccx.const_kind() == hir::ConstContext::ConstFn {
Status::Unstable(sym::const_fn_floating_point_arithmetic)
} else {
Status::Allowed
}
}
#[allow(rustc::untranslatable_diagnostic)] // FIXME: make this translatable
fn build_error(&self, ccx: &ConstCx<'_, 'tcx>, span: Span) -> Diag<'tcx> {
feature_err(
&ccx.tcx.sess,
sym::const_fn_floating_point_arithmetic,
span,
format!("floating point arithmetic is not allowed in {}s", ccx.const_kind()),
)
}
}
/// A function call where the callee is a pointer.
#[derive(Debug)]
pub struct FnCallIndirect;
impl<'tcx> NonConstOp<'tcx> for FnCallIndirect {
fn build_error(&self, ccx: &ConstCx<'_, 'tcx>, span: Span) -> Diag<'tcx> {
ccx.dcx().create_err(errors::UnallowedFnPointerCall { span, kind: ccx.const_kind() })
}
}
/// A function call where the callee is not marked as `const`.
#[derive(Debug, Clone, Copy)]
pub struct FnCallNonConst<'tcx> {
pub caller: LocalDefId,
pub callee: DefId,
pub args: GenericArgsRef<'tcx>,
pub span: Span,
pub call_source: CallSource,
pub feature: Option<Symbol>,
}
impl<'tcx> NonConstOp<'tcx> for FnCallNonConst<'tcx> {
// FIXME: make this translatable
#[allow(rustc::diagnostic_outside_of_impl)]
#[allow(rustc::untranslatable_diagnostic)]
fn build_error(&self, ccx: &ConstCx<'_, 'tcx>, _: Span) -> Diag<'tcx> {
let FnCallNonConst { caller, callee, args, span, call_source, feature } = *self;
let ConstCx { tcx, param_env, body, .. } = *ccx;
let diag_trait = |err, self_ty: Ty<'_>, trait_id| {
let trait_ref = TraitRef::from_method(tcx, trait_id, args);
match self_ty.kind() {
Param(param_ty) => {
debug!(?param_ty);
if let Some(generics) = tcx.hir_node_by_def_id(caller).generics() {
let constraint = with_no_trimmed_paths!(format!(
"~const {}",
trait_ref.print_only_trait_path()
));
suggest_constraining_type_param(
tcx,
generics,
err,
param_ty.name.as_str(),
&constraint,
None,
None,
);
}
}
ty::Adt(..) => {
let obligation =
Obligation::new(tcx, ObligationCause::dummy(), param_env, trait_ref);
let infcx = tcx.infer_ctxt().build();
let mut selcx = SelectionContext::new(&infcx);
let implsrc = selcx.select(&obligation);
if let Ok(Some(ImplSource::UserDefined(data))) = implsrc {
// FIXME(effects) revisit this
if !tcx.is_const_trait_impl_raw(data.impl_def_id) {
let span = tcx.def_span(data.impl_def_id);
err.subdiagnostic(tcx.dcx(), errors::NonConstImplNote { span });
}
}
}
_ => {}
}
};
let call_kind =
call_kind(tcx, ccx.param_env, callee, args, span, call_source.from_hir_call(), None);
debug!(?call_kind);
let mut err = match call_kind {
CallKind::Normal { desugaring: Some((kind, self_ty)), .. } => {
macro_rules! error {
($err:ident) => {
tcx.dcx().create_err(errors::$err {
span,
ty: self_ty,
kind: ccx.const_kind(),
})
};
}
let mut err = match kind {
CallDesugaringKind::ForLoopIntoIter => {
error!(NonConstForLoopIntoIter)
}
CallDesugaringKind::QuestionBranch => {
error!(NonConstQuestionBranch)
}
CallDesugaringKind::QuestionFromResidual => {
error!(NonConstQuestionFromResidual)
}
CallDesugaringKind::TryBlockFromOutput => {
error!(NonConstTryBlockFromOutput)
}
CallDesugaringKind::Await => {
error!(NonConstAwait)
}
};
diag_trait(&mut err, self_ty, kind.trait_def_id(tcx));
err
}
CallKind::FnCall { fn_trait_id, self_ty } => {
let note = match self_ty.kind() {
FnDef(def_id, ..) => {
let span = tcx.def_span(*def_id);
if ccx.tcx.is_const_fn_raw(*def_id) {
span_bug!(span, "calling const FnDef errored when it shouldn't");
}
Some(errors::NonConstClosureNote::FnDef { span })
}
FnPtr(..) => Some(errors::NonConstClosureNote::FnPtr),
Closure(..) => Some(errors::NonConstClosureNote::Closure),
_ => None,
};
let mut err = tcx.dcx().create_err(errors::NonConstClosure {
span,
kind: ccx.const_kind(),
note,
});
diag_trait(&mut err, self_ty, fn_trait_id);
err
}
CallKind::Operator { trait_id, self_ty, .. } => {
let mut err = if let CallSource::MatchCmp = call_source {
tcx.dcx().create_err(errors::NonConstMatchEq {
span,
kind: ccx.const_kind(),
ty: self_ty,
})
} else {
let mut sugg = None;
if Some(trait_id) == ccx.tcx.lang_items().eq_trait() {
match (args[0].unpack(), args[1].unpack()) {
(GenericArgKind::Type(self_ty), GenericArgKind::Type(rhs_ty))
if self_ty == rhs_ty
&& self_ty.is_ref()
&& self_ty.peel_refs().is_primitive() =>
{
let mut num_refs = 0;
let mut tmp_ty = self_ty;
while let rustc_middle::ty::Ref(_, inner_ty, _) = tmp_ty.kind() {
num_refs += 1;
tmp_ty = *inner_ty;
}
let deref = "*".repeat(num_refs);
if let Ok(call_str) =
ccx.tcx.sess.source_map().span_to_snippet(span)
{
if let Some(eq_idx) = call_str.find("==") {
if let Some(rhs_idx) = call_str[(eq_idx + 2)..]
.find(|c: char| !c.is_whitespace())
{
let rhs_pos = span.lo()
+ BytePos::from_usize(eq_idx + 2 + rhs_idx);
let rhs_span = span.with_lo(rhs_pos).with_hi(rhs_pos);
sugg = Some(errors::ConsiderDereferencing {
deref,
span: span.shrink_to_lo(),
rhs_span,
});
}
}
}
}
_ => {}
}
}
tcx.dcx().create_err(errors::NonConstOperator {
span,
kind: ccx.const_kind(),
sugg,
})
};
diag_trait(&mut err, self_ty, trait_id);
err
}
CallKind::DerefCoercion { deref_target, deref_target_ty, self_ty } => {
// Check first whether the source is accessible (issue #87060)
let target = if tcx.sess.source_map().is_span_accessible(deref_target) {
Some(deref_target)
} else {
None
};
let mut err = tcx.dcx().create_err(errors::NonConstDerefCoercion {
span,
ty: self_ty,
kind: ccx.const_kind(),
target_ty: deref_target_ty,
deref_target: target,
});
diag_trait(&mut err, self_ty, tcx.require_lang_item(LangItem::Deref, Some(span)));
err
}
_ if tcx.opt_parent(callee) == tcx.get_diagnostic_item(sym::ArgumentMethods) => {
ccx.dcx().create_err(errors::NonConstFmtMacroCall { span, kind: ccx.const_kind() })
}
_ => ccx.dcx().create_err(errors::NonConstFnCall {
span,
def_path_str: ccx.tcx.def_path_str_with_args(callee, args),
kind: ccx.const_kind(),
}),
};
err.note(format!(
"calls in {}s are limited to constant functions, \
tuple structs and tuple variants",
ccx.const_kind(),
));
if let Some(feature) = feature {
ccx.tcx.disabled_nightly_features(
&mut err,
body.source.def_id().as_local().map(|local| ccx.tcx.local_def_id_to_hir_id(local)),
[(String::new(), feature)],
);
}
if let ConstContext::Static(_) = ccx.const_kind() {
err.note("consider wrapping this expression in `Lazy::new(|| ...)` from the `once_cell` crate: https://crates.io/crates/once_cell");
}
err
}
}
/// A call to an `#[unstable]` const fn or `#[rustc_const_unstable]` function.
///
/// Contains the name of the feature that would allow the use of this function.
#[derive(Debug)]
pub struct FnCallUnstable(pub DefId, pub Option<Symbol>);
impl<'tcx> NonConstOp<'tcx> for FnCallUnstable {
fn build_error(&self, ccx: &ConstCx<'_, 'tcx>, span: Span) -> Diag<'tcx> {
let FnCallUnstable(def_id, feature) = *self;
let mut err = ccx
.dcx()
.create_err(errors::UnstableConstFn { span, def_path: ccx.tcx.def_path_str(def_id) });
// FIXME: make this translatable
#[allow(rustc::untranslatable_diagnostic)]
if ccx.is_const_stable_const_fn() {
err.help("const-stable functions can only call other const-stable functions");
} else if ccx.tcx.sess.is_nightly_build() {
if let Some(feature) = feature {
err.help(format!("add `#![feature({feature})]` to the crate attributes to enable"));
}
}
err
}
}
#[derive(Debug)]
pub struct Coroutine(pub hir::CoroutineKind);
impl<'tcx> NonConstOp<'tcx> for Coroutine {
fn status_in_item(&self, _: &ConstCx<'_, 'tcx>) -> Status {
if let hir::CoroutineKind::Desugared(
hir::CoroutineDesugaring::Async,
hir::CoroutineSource::Block,
) = self.0
{
Status::Unstable(sym::const_async_blocks)
} else {
Status::Forbidden
}
}
fn build_error(&self, ccx: &ConstCx<'_, 'tcx>, span: Span) -> Diag<'tcx> {
let msg = format!("{:#}s are not allowed in {}s", self.0, ccx.const_kind());
if let hir::CoroutineKind::Desugared(
hir::CoroutineDesugaring::Async,
hir::CoroutineSource::Block,
) = self.0
{
ccx.tcx.sess.create_feature_err(
errors::UnallowedOpInConstContext { span, msg },
sym::const_async_blocks,
)
} else {
ccx.dcx().create_err(errors::UnallowedOpInConstContext { span, msg })
}
}
}
#[derive(Debug)]
pub struct HeapAllocation;
impl<'tcx> NonConstOp<'tcx> for HeapAllocation {
fn build_error(&self, ccx: &ConstCx<'_, 'tcx>, span: Span) -> Diag<'tcx> {
ccx.dcx().create_err(errors::UnallowedHeapAllocations {
span,
kind: ccx.const_kind(),
teach: ccx.tcx.sess.teach(E0010).then_some(()),
})
}
}
#[derive(Debug)]
pub struct InlineAsm;
impl<'tcx> NonConstOp<'tcx> for InlineAsm {
fn build_error(&self, ccx: &ConstCx<'_, 'tcx>, span: Span) -> Diag<'tcx> {
ccx.dcx().create_err(errors::UnallowedInlineAsm { span, kind: ccx.const_kind() })
}
}
#[derive(Debug)]
pub struct LiveDrop<'tcx> {
pub dropped_at: Option<Span>,
pub dropped_ty: Ty<'tcx>,
}
impl<'tcx> NonConstOp<'tcx> for LiveDrop<'tcx> {
fn build_error(&self, ccx: &ConstCx<'_, 'tcx>, span: Span) -> Diag<'tcx> {
ccx.dcx().create_err(errors::LiveDrop {
span,
dropped_ty: self.dropped_ty,
kind: ccx.const_kind(),
dropped_at: self.dropped_at,
})
}
}
#[derive(Debug)]
/// A borrow of a type that contains an `UnsafeCell` somewhere. The borrow never escapes to
/// the final value of the constant.
pub struct TransientCellBorrow;
impl<'tcx> NonConstOp<'tcx> for TransientCellBorrow {
fn status_in_item(&self, _: &ConstCx<'_, 'tcx>) -> Status {
Status::Unstable(sym::const_refs_to_cell)
}
fn build_error(&self, ccx: &ConstCx<'_, 'tcx>, span: Span) -> Diag<'tcx> {
ccx.tcx
.sess
.create_feature_err(errors::InteriorMutabilityBorrow { span }, sym::const_refs_to_cell)
}
}
#[derive(Debug)]
/// A borrow of a type that contains an `UnsafeCell` somewhere. The borrow might escape to
/// the final value of the constant, and thus we cannot allow this (for now). We may allow
/// it in the future for static items.
pub struct CellBorrow;
impl<'tcx> NonConstOp<'tcx> for CellBorrow {
fn importance(&self) -> DiagImportance {
// Most likely the code will try to do mutation with these borrows, which
// triggers its own errors. Only show this one if that does not happen.
DiagImportance::Secondary
}
fn build_error(&self, ccx: &ConstCx<'_, 'tcx>, span: Span) -> Diag<'tcx> {
// FIXME: Maybe a more elegant solution to this if else case
if let hir::ConstContext::Static(_) = ccx.const_kind() {
ccx.dcx().create_err(errors::InteriorMutableDataRefer {
span,
opt_help: Some(()),
kind: ccx.const_kind(),
teach: ccx.tcx.sess.teach(E0492).then_some(()),
})
} else {
ccx.dcx().create_err(errors::InteriorMutableDataRefer {
span,
opt_help: None,
kind: ccx.const_kind(),
teach: ccx.tcx.sess.teach(E0492).then_some(()),
})
}
}
}
#[derive(Debug)]
/// This op is for `&mut` borrows in the trailing expression of a constant
/// which uses the "enclosing scopes rule" to leak its locals into anonymous
/// static or const items.
pub struct MutBorrow(pub hir::BorrowKind);
impl<'tcx> NonConstOp<'tcx> for MutBorrow {
fn status_in_item(&self, _ccx: &ConstCx<'_, 'tcx>) -> Status {
Status::Forbidden
}
fn importance(&self) -> DiagImportance {
// Most likely the code will try to do mutation with these borrows, which
// triggers its own errors. Only show this one if that does not happen.
DiagImportance::Secondary
}
fn build_error(&self, ccx: &ConstCx<'_, 'tcx>, span: Span) -> Diag<'tcx> {
match self.0 {
hir::BorrowKind::Raw => ccx.tcx.dcx().create_err(errors::UnallowedMutableRaw {
span,
kind: ccx.const_kind(),
teach: ccx.tcx.sess.teach(E0764).then_some(()),
}),
hir::BorrowKind::Ref => ccx.dcx().create_err(errors::UnallowedMutableRefs {
span,
kind: ccx.const_kind(),
teach: ccx.tcx.sess.teach(E0764).then_some(()),
}),
}
}
}
#[derive(Debug)]
pub struct TransientMutBorrow(pub hir::BorrowKind);
impl<'tcx> NonConstOp<'tcx> for TransientMutBorrow {
fn status_in_item(&self, _: &ConstCx<'_, 'tcx>) -> Status {
Status::Unstable(sym::const_mut_refs)
}
fn build_error(&self, ccx: &ConstCx<'_, 'tcx>, span: Span) -> Diag<'tcx> {
let kind = ccx.const_kind();
match self.0 {
hir::BorrowKind::Raw => ccx
.tcx
.sess
.create_feature_err(errors::TransientMutRawErr { span, kind }, sym::const_mut_refs),
hir::BorrowKind::Ref => ccx.tcx.sess.create_feature_err(
errors::TransientMutBorrowErr { span, kind },
sym::const_mut_refs,
),
}
}
}
#[derive(Debug)]
pub struct MutDeref;
impl<'tcx> NonConstOp<'tcx> for MutDeref {
fn status_in_item(&self, _: &ConstCx<'_, 'tcx>) -> Status {
Status::Unstable(sym::const_mut_refs)
}
fn importance(&self) -> DiagImportance {
// Usually a side-effect of a `TransientMutBorrow` somewhere.
DiagImportance::Secondary
}
fn build_error(&self, ccx: &ConstCx<'_, 'tcx>, span: Span) -> Diag<'tcx> {
ccx.tcx.sess.create_feature_err(
errors::MutDerefErr { span, kind: ccx.const_kind() },
sym::const_mut_refs,
)
}
}
/// A call to a `panic()` lang item where the first argument is _not_ a `&str`.
#[derive(Debug)]
pub struct PanicNonStr;
impl<'tcx> NonConstOp<'tcx> for PanicNonStr {
fn build_error(&self, ccx: &ConstCx<'_, 'tcx>, span: Span) -> Diag<'tcx> {
ccx.dcx().create_err(errors::PanicNonStrErr { span })
}
}
/// Comparing raw pointers for equality.
/// Not currently intended to ever be allowed, even behind a feature gate: operation depends on
/// allocation base addresses that are not known at compile-time.
#[derive(Debug)]
pub struct RawPtrComparison;
impl<'tcx> NonConstOp<'tcx> for RawPtrComparison {
fn build_error(&self, ccx: &ConstCx<'_, 'tcx>, span: Span) -> Diag<'tcx> {
// FIXME(const_trait_impl): revert to span_bug?
ccx.dcx().create_err(errors::RawPtrComparisonErr { span })
}
}
#[derive(Debug)]
pub struct RawMutPtrDeref;
impl<'tcx> NonConstOp<'tcx> for RawMutPtrDeref {
fn status_in_item(&self, _: &ConstCx<'_, '_>) -> Status {
Status::Unstable(sym::const_mut_refs)
}
#[allow(rustc::untranslatable_diagnostic)] // FIXME: make this translatable
fn build_error(&self, ccx: &ConstCx<'_, 'tcx>, span: Span) -> Diag<'tcx> {
feature_err(
&ccx.tcx.sess,
sym::const_mut_refs,
span,
format!("dereferencing raw mutable pointers in {}s is unstable", ccx.const_kind(),),
)
}
}
/// Casting raw pointer or function pointer to an integer.
/// Not currently intended to ever be allowed, even behind a feature gate: operation depends on
/// allocation base addresses that are not known at compile-time.
#[derive(Debug)]
pub struct RawPtrToIntCast;
impl<'tcx> NonConstOp<'tcx> for RawPtrToIntCast {
fn build_error(&self, ccx: &ConstCx<'_, 'tcx>, span: Span) -> Diag<'tcx> {
ccx.dcx().create_err(errors::RawPtrToIntErr { span })
}
}
/// An access to a (non-thread-local) `static`.
#[derive(Debug)]
pub struct StaticAccess;
impl<'tcx> NonConstOp<'tcx> for StaticAccess {
fn status_in_item(&self, ccx: &ConstCx<'_, 'tcx>) -> Status {
if let hir::ConstContext::Static(_) = ccx.const_kind() {
Status::Allowed
} else {
Status::Unstable(sym::const_refs_to_static)
}
}
#[allow(rustc::untranslatable_diagnostic)] // FIXME: make this translatable
fn build_error(&self, ccx: &ConstCx<'_, 'tcx>, span: Span) -> Diag<'tcx> {
let mut err = feature_err(
&ccx.tcx.sess,
sym::const_refs_to_static,
span,
format!("referencing statics in {}s is unstable", ccx.const_kind(),),
);
// FIXME: make this translatable
#[allow(rustc::untranslatable_diagnostic)]
err
.note("`static` and `const` variables can refer to other `const` variables. A `const` variable, however, cannot refer to a `static` variable.")
.help("to fix this, the value can be extracted to a `const` and then used.");
err
}
}
/// An access to a thread-local `static`.
#[derive(Debug)]
pub struct ThreadLocalAccess;
impl<'tcx> NonConstOp<'tcx> for ThreadLocalAccess {
fn build_error(&self, ccx: &ConstCx<'_, 'tcx>, span: Span) -> Diag<'tcx> {
ccx.dcx().create_err(errors::ThreadLocalAccessErr { span })
}
}
/// Types that cannot appear in the signature or locals of a `const fn`.
pub mod mut_ref {
use super::*;
#[derive(Debug)]
pub struct MutRef(pub mir::LocalKind);
impl<'tcx> NonConstOp<'tcx> for MutRef {
fn status_in_item(&self, _ccx: &ConstCx<'_, 'tcx>) -> Status {
Status::Unstable(sym::const_mut_refs)
}
fn importance(&self) -> DiagImportance {
match self.0 {
mir::LocalKind::Temp => DiagImportance::Secondary,
mir::LocalKind::ReturnPointer | mir::LocalKind::Arg => DiagImportance::Primary,
}
}
#[allow(rustc::untranslatable_diagnostic)] // FIXME: make this translatable
fn build_error(&self, ccx: &ConstCx<'_, 'tcx>, span: Span) -> Diag<'tcx> {
feature_err(
&ccx.tcx.sess,
sym::const_mut_refs,
span,
format!("mutable references are not allowed in {}s", ccx.const_kind()),
)
}
}
}

View File

@@ -0,0 +1,124 @@
use rustc_middle::mir::visit::Visitor;
use rustc_middle::mir::{self, BasicBlock, Location};
use rustc_middle::ty::{Ty, TyCtxt};
use rustc_span::{symbol::sym, Span};
use tracing::trace;
use super::check::Qualifs;
use super::ops::{self, NonConstOp};
use super::qualifs::{NeedsNonConstDrop, Qualif};
use super::ConstCx;
/// Returns `true` if we should use the more precise live drop checker that runs after drop
/// elaboration.
pub fn checking_enabled(ccx: &ConstCx<'_, '_>) -> bool {
// Const-stable functions must always use the stable live drop checker.
if ccx.is_const_stable_const_fn() {
return false;
}
ccx.tcx.features().const_precise_live_drops
}
/// Look for live drops in a const context.
///
/// This is separate from the rest of the const checking logic because it must run after drop
/// elaboration.
pub fn check_live_drops<'tcx>(tcx: TyCtxt<'tcx>, body: &mir::Body<'tcx>) {
let def_id = body.source.def_id().expect_local();
let const_kind = tcx.hir().body_const_context(def_id);
if const_kind.is_none() {
return;
}
if tcx.has_attr(def_id, sym::rustc_do_not_const_check) {
return;
}
let ccx = ConstCx { body, tcx, const_kind, param_env: tcx.param_env(def_id) };
if !checking_enabled(&ccx) {
return;
}
let mut visitor = CheckLiveDrops { ccx: &ccx, qualifs: Qualifs::default() };
visitor.visit_body(body);
}
struct CheckLiveDrops<'mir, 'tcx> {
ccx: &'mir ConstCx<'mir, 'tcx>,
qualifs: Qualifs<'mir, 'tcx>,
}
// So we can access `body` and `tcx`.
impl<'mir, 'tcx> std::ops::Deref for CheckLiveDrops<'mir, 'tcx> {
type Target = ConstCx<'mir, 'tcx>;
fn deref(&self) -> &Self::Target {
self.ccx
}
}
impl<'tcx> CheckLiveDrops<'_, 'tcx> {
fn check_live_drop(&self, span: Span, dropped_ty: Ty<'tcx>) {
ops::LiveDrop { dropped_at: None, dropped_ty }.build_error(self.ccx, span).emit();
}
}
impl<'tcx> Visitor<'tcx> for CheckLiveDrops<'_, 'tcx> {
fn visit_basic_block_data(&mut self, bb: BasicBlock, block: &mir::BasicBlockData<'tcx>) {
trace!("visit_basic_block_data: bb={:?} is_cleanup={:?}", bb, block.is_cleanup);
// Ignore drop terminators in cleanup blocks.
if block.is_cleanup {
return;
}
self.super_basic_block_data(bb, block);
}
fn visit_terminator(&mut self, terminator: &mir::Terminator<'tcx>, location: Location) {
trace!("visit_terminator: terminator={:?} location={:?}", terminator, location);
match &terminator.kind {
mir::TerminatorKind::Drop { place: dropped_place, .. } => {
let dropped_ty = dropped_place.ty(self.body, self.tcx).ty;
if !NeedsNonConstDrop::in_any_value_of_ty(self.ccx, dropped_ty) {
// Instead of throwing a bug, we just return here. This is because we have to
// run custom `const Drop` impls.
return;
}
if dropped_place.is_indirect() {
self.check_live_drop(terminator.source_info.span, dropped_ty);
return;
}
// Drop elaboration is not precise enough to accept code like
// `tests/ui/consts/control-flow/drop-pass.rs`; e.g., when an `Option<Vec<T>>` is
// initialized with `None` and never changed, it still emits drop glue.
// Hence we additionally check the qualifs here to allow more code to pass.
if self.qualifs.needs_non_const_drop(self.ccx, dropped_place.local, location) {
// Use the span where the dropped local was declared for the error.
let span = self.body.local_decls[dropped_place.local].source_info.span;
self.check_live_drop(span, dropped_ty);
}
}
mir::TerminatorKind::UnwindTerminate(_)
| mir::TerminatorKind::Call { .. }
| mir::TerminatorKind::Assert { .. }
| mir::TerminatorKind::FalseEdge { .. }
| mir::TerminatorKind::FalseUnwind { .. }
| mir::TerminatorKind::CoroutineDrop
| mir::TerminatorKind::Goto { .. }
| mir::TerminatorKind::InlineAsm { .. }
| mir::TerminatorKind::UnwindResume
| mir::TerminatorKind::Return
| mir::TerminatorKind::SwitchInt { .. }
| mir::TerminatorKind::Unreachable
| mir::TerminatorKind::Yield { .. } => {}
}
}
}

View File

@@ -0,0 +1,397 @@
//! Structural const qualification.
//!
//! See the `Qualif` trait for more info.
use rustc_errors::ErrorGuaranteed;
use rustc_hir::LangItem;
use rustc_infer::infer::TyCtxtInferExt;
use rustc_middle::bug;
use rustc_middle::mir;
use rustc_middle::mir::*;
use rustc_middle::traits::BuiltinImplSource;
use rustc_middle::ty::{self, AdtDef, GenericArgsRef, Ty};
use rustc_trait_selection::traits::{
ImplSource, Obligation, ObligationCause, ObligationCtxt, SelectionContext,
};
use tracing::{instrument, trace};
use super::ConstCx;
pub fn in_any_value_of_ty<'tcx>(
cx: &ConstCx<'_, 'tcx>,
ty: Ty<'tcx>,
tainted_by_errors: Option<ErrorGuaranteed>,
) -> ConstQualifs {
ConstQualifs {
has_mut_interior: HasMutInterior::in_any_value_of_ty(cx, ty),
needs_drop: NeedsDrop::in_any_value_of_ty(cx, ty),
needs_non_const_drop: NeedsNonConstDrop::in_any_value_of_ty(cx, ty),
tainted_by_errors,
}
}
/// A "qualif"(-ication) is a way to look for something "bad" in the MIR that would disqualify some
/// code for promotion or prevent it from evaluating at compile time.
///
/// Normally, we would determine what qualifications apply to each type and error when an illegal
/// operation is performed on such a type. However, this was found to be too imprecise, especially
/// in the presence of `enum`s. If only a single variant of an enum has a certain qualification, we
/// needn't reject code unless it actually constructs and operates on the qualified variant.
///
/// To accomplish this, const-checking and promotion use a value-based analysis (as opposed to a
/// type-based one). Qualifications propagate structurally across variables: If a local (or a
/// projection of a local) is assigned a qualified value, that local itself becomes qualified.
pub trait Qualif {
/// The name of the file used to debug the dataflow analysis that computes this qualif.
const ANALYSIS_NAME: &'static str;
/// Whether this `Qualif` is cleared when a local is moved from.
const IS_CLEARED_ON_MOVE: bool = false;
/// Whether this `Qualif` might be evaluated after the promotion and can encounter a promoted.
const ALLOW_PROMOTED: bool = false;
/// Extracts the field of `ConstQualifs` that corresponds to this `Qualif`.
fn in_qualifs(qualifs: &ConstQualifs) -> bool;
/// Returns `true` if *any* value of the given type could possibly have this `Qualif`.
///
/// This function determines `Qualif`s when we cannot do a value-based analysis. Since qualif
/// propagation is context-insensitive, this includes function arguments and values returned
/// from a call to another function.
///
/// It also determines the `Qualif`s for primitive types.
fn in_any_value_of_ty<'tcx>(cx: &ConstCx<'_, 'tcx>, ty: Ty<'tcx>) -> bool;
/// Returns `true` if this `Qualif` is inherent to the given struct or enum.
///
/// By default, `Qualif`s propagate into ADTs in a structural way: An ADT only becomes
/// qualified if part of it is assigned a value with that `Qualif`. However, some ADTs *always*
/// have a certain `Qualif`, regardless of whether their fields have it. For example, a type
/// with a custom `Drop` impl is inherently `NeedsDrop`.
///
/// Returning `true` for `in_adt_inherently` but `false` for `in_any_value_of_ty` is unsound.
fn in_adt_inherently<'tcx>(
cx: &ConstCx<'_, 'tcx>,
adt: AdtDef<'tcx>,
args: GenericArgsRef<'tcx>,
) -> bool;
/// Returns `true` if this `Qualif` behaves sructurally for pointers and references:
/// the pointer/reference qualifies if and only if the pointee qualifies.
///
/// (This is currently `false` for all our instances, but that may change in the future. Also,
/// by keeping it abstract, the handling of `Deref` in `in_place` becomes more clear.)
fn deref_structural<'tcx>(cx: &ConstCx<'_, 'tcx>) -> bool;
}
/// Constant containing interior mutability (`UnsafeCell<T>`).
/// This must be ruled out to make sure that evaluating the constant at compile-time
/// and at *any point* during the run-time would produce the same result. In particular,
/// promotion of temporaries must not change program behavior; if the promoted could be
/// written to, that would be a problem.
pub struct HasMutInterior;
impl Qualif for HasMutInterior {
const ANALYSIS_NAME: &'static str = "flow_has_mut_interior";
fn in_qualifs(qualifs: &ConstQualifs) -> bool {
qualifs.has_mut_interior
}
fn in_any_value_of_ty<'tcx>(cx: &ConstCx<'_, 'tcx>, ty: Ty<'tcx>) -> bool {
!ty.is_freeze(cx.tcx, cx.param_env)
}
fn in_adt_inherently<'tcx>(
_cx: &ConstCx<'_, 'tcx>,
adt: AdtDef<'tcx>,
_: GenericArgsRef<'tcx>,
) -> bool {
// Exactly one type, `UnsafeCell`, has the `HasMutInterior` qualif inherently.
// It arises structurally for all other types.
adt.is_unsafe_cell()
}
fn deref_structural<'tcx>(_cx: &ConstCx<'_, 'tcx>) -> bool {
false
}
}
/// Constant containing an ADT that implements `Drop`.
/// This must be ruled out because implicit promotion would remove side-effects
/// that occur as part of dropping that value. N.B., the implicit promotion has
/// to reject const Drop implementations because even if side-effects are ruled
/// out through other means, the execution of the drop could diverge.
pub struct NeedsDrop;
impl Qualif for NeedsDrop {
const ANALYSIS_NAME: &'static str = "flow_needs_drop";
const IS_CLEARED_ON_MOVE: bool = true;
fn in_qualifs(qualifs: &ConstQualifs) -> bool {
qualifs.needs_drop
}
fn in_any_value_of_ty<'tcx>(cx: &ConstCx<'_, 'tcx>, ty: Ty<'tcx>) -> bool {
ty.needs_drop(cx.tcx, cx.param_env)
}
fn in_adt_inherently<'tcx>(
cx: &ConstCx<'_, 'tcx>,
adt: AdtDef<'tcx>,
_: GenericArgsRef<'tcx>,
) -> bool {
adt.has_dtor(cx.tcx)
}
fn deref_structural<'tcx>(_cx: &ConstCx<'_, 'tcx>) -> bool {
false
}
}
/// Constant containing an ADT that implements non-const `Drop`.
/// This must be ruled out because we cannot run `Drop` during compile-time.
pub struct NeedsNonConstDrop;
impl Qualif for NeedsNonConstDrop {
const ANALYSIS_NAME: &'static str = "flow_needs_nonconst_drop";
const IS_CLEARED_ON_MOVE: bool = true;
const ALLOW_PROMOTED: bool = true;
fn in_qualifs(qualifs: &ConstQualifs) -> bool {
qualifs.needs_non_const_drop
}
#[instrument(level = "trace", skip(cx), ret)]
fn in_any_value_of_ty<'tcx>(cx: &ConstCx<'_, 'tcx>, ty: Ty<'tcx>) -> bool {
// Avoid selecting for simple cases, such as builtin types.
if ty::util::is_trivially_const_drop(ty) {
return false;
}
// FIXME(effects): If `destruct` is not a `const_trait`,
// or effects are disabled in this crate, then give up.
let destruct_def_id = cx.tcx.require_lang_item(LangItem::Destruct, Some(cx.body.span));
if !cx.tcx.has_host_param(destruct_def_id) || !cx.tcx.features().effects {
return NeedsDrop::in_any_value_of_ty(cx, ty);
}
let obligation = Obligation::new(
cx.tcx,
ObligationCause::dummy_with_span(cx.body.span),
cx.param_env,
ty::TraitRef::new(
cx.tcx,
destruct_def_id,
[
ty::GenericArg::from(ty),
ty::GenericArg::from(cx.tcx.expected_host_effect_param_for_body(cx.def_id())),
],
),
);
let infcx = cx.tcx.infer_ctxt().build();
let mut selcx = SelectionContext::new(&infcx);
let Some(impl_src) = selcx.select(&obligation).ok().flatten() else {
// If we couldn't select a const destruct candidate, then it's bad
return true;
};
trace!(?impl_src);
if !matches!(
impl_src,
ImplSource::Builtin(BuiltinImplSource::Misc, _) | ImplSource::Param(_)
) {
// If our const destruct candidate is not ConstDestruct or implied by the param env,
// then it's bad
return true;
}
if impl_src.borrow_nested_obligations().is_empty() {
return false;
}
// If we had any errors, then it's bad
let ocx = ObligationCtxt::new(&infcx);
ocx.register_obligations(impl_src.nested_obligations());
let errors = ocx.select_all_or_error();
!errors.is_empty()
}
fn in_adt_inherently<'tcx>(
cx: &ConstCx<'_, 'tcx>,
adt: AdtDef<'tcx>,
_: GenericArgsRef<'tcx>,
) -> bool {
adt.has_non_const_dtor(cx.tcx)
}
fn deref_structural<'tcx>(_cx: &ConstCx<'_, 'tcx>) -> bool {
false
}
}
// FIXME: Use `mir::visit::Visitor` for the `in_*` functions if/when it supports early return.
/// Returns `true` if this `Rvalue` contains qualif `Q`.
pub fn in_rvalue<'tcx, Q, F>(
cx: &ConstCx<'_, 'tcx>,
in_local: &mut F,
rvalue: &Rvalue<'tcx>,
) -> bool
where
Q: Qualif,
F: FnMut(Local) -> bool,
{
match rvalue {
Rvalue::ThreadLocalRef(_) | Rvalue::NullaryOp(..) => {
Q::in_any_value_of_ty(cx, rvalue.ty(cx.body, cx.tcx))
}
Rvalue::Discriminant(place) | Rvalue::Len(place) => {
in_place::<Q, _>(cx, in_local, place.as_ref())
}
Rvalue::CopyForDeref(place) => in_place::<Q, _>(cx, in_local, place.as_ref()),
Rvalue::Use(operand)
| Rvalue::Repeat(operand, _)
| Rvalue::UnaryOp(_, operand)
| Rvalue::Cast(_, operand, _)
| Rvalue::ShallowInitBox(operand, _) => in_operand::<Q, _>(cx, in_local, operand),
Rvalue::BinaryOp(_, box (lhs, rhs)) => {
in_operand::<Q, _>(cx, in_local, lhs) || in_operand::<Q, _>(cx, in_local, rhs)
}
Rvalue::Ref(_, _, place) | Rvalue::AddressOf(_, place) => {
// Special-case reborrows to be more like a copy of the reference.
if let Some((place_base, ProjectionElem::Deref)) = place.as_ref().last_projection() {
let base_ty = place_base.ty(cx.body, cx.tcx).ty;
if let ty::Ref(..) = base_ty.kind() {
return in_place::<Q, _>(cx, in_local, place_base);
}
}
in_place::<Q, _>(cx, in_local, place.as_ref())
}
Rvalue::Aggregate(kind, operands) => {
// Return early if we know that the struct or enum being constructed is always
// qualified.
if let AggregateKind::Adt(adt_did, _, args, ..) = **kind {
let def = cx.tcx.adt_def(adt_did);
if Q::in_adt_inherently(cx, def, args) {
return true;
}
// Don't do any value-based reasoning for unions.
if def.is_union() && Q::in_any_value_of_ty(cx, rvalue.ty(cx.body, cx.tcx)) {
return true;
}
}
// Otherwise, proceed structurally...
operands.iter().any(|o| in_operand::<Q, _>(cx, in_local, o))
}
}
}
/// Returns `true` if this `Place` contains qualif `Q`.
pub fn in_place<'tcx, Q, F>(cx: &ConstCx<'_, 'tcx>, in_local: &mut F, place: PlaceRef<'tcx>) -> bool
where
Q: Qualif,
F: FnMut(Local) -> bool,
{
let mut place = place;
while let Some((place_base, elem)) = place.last_projection() {
match elem {
ProjectionElem::Index(index) if in_local(index) => return true,
ProjectionElem::Deref
| ProjectionElem::Subtype(_)
| ProjectionElem::Field(_, _)
| ProjectionElem::OpaqueCast(_)
| ProjectionElem::ConstantIndex { .. }
| ProjectionElem::Subslice { .. }
| ProjectionElem::Downcast(_, _)
| ProjectionElem::Index(_) => {}
}
let base_ty = place_base.ty(cx.body, cx.tcx);
let proj_ty = base_ty.projection_ty(cx.tcx, elem).ty;
if !Q::in_any_value_of_ty(cx, proj_ty) {
return false;
}
if matches!(elem, ProjectionElem::Deref) && !Q::deref_structural(cx) {
// We have to assume that this qualifies.
return true;
}
place = place_base;
}
assert!(place.projection.is_empty());
in_local(place.local)
}
/// Returns `true` if this `Operand` contains qualif `Q`.
pub fn in_operand<'tcx, Q, F>(
cx: &ConstCx<'_, 'tcx>,
in_local: &mut F,
operand: &Operand<'tcx>,
) -> bool
where
Q: Qualif,
F: FnMut(Local) -> bool,
{
let constant = match operand {
Operand::Copy(place) | Operand::Move(place) => {
return in_place::<Q, _>(cx, in_local, place.as_ref());
}
Operand::Constant(c) => c,
};
// Check the qualifs of the value of `const` items.
let uneval = match constant.const_ {
Const::Ty(ct)
if matches!(
ct.kind(),
ty::ConstKind::Param(_) | ty::ConstKind::Error(_) | ty::ConstKind::Value(_)
) =>
{
None
}
Const::Ty(c) => {
bug!("expected ConstKind::Param or ConstKind::Value here, found {:?}", c)
}
Const::Unevaluated(uv, _) => Some(uv),
Const::Val(..) => None,
};
if let Some(mir::UnevaluatedConst { def, args: _, promoted }) = uneval {
// Use qualifs of the type for the promoted. Promoteds in MIR body should be possible
// only for `NeedsNonConstDrop` with precise drop checking. This is the only const
// check performed after the promotion. Verify that with an assertion.
assert!(promoted.is_none() || Q::ALLOW_PROMOTED);
// Don't peek inside trait associated constants.
if promoted.is_none() && cx.tcx.trait_of_item(def).is_none() {
let qualifs = cx.tcx.at(constant.span).mir_const_qualif(def);
if !Q::in_qualifs(&qualifs) {
return false;
}
// Just in case the type is more specific than
// the definition, e.g., impl associated const
// with type parameters, take it into account.
}
}
// Otherwise use the qualifs of the type.
Q::in_any_value_of_ty(cx, constant.const_.ty())
}

View File

@@ -0,0 +1,365 @@
//! Propagate `Qualif`s between locals and query the results.
//!
//! This contains the dataflow analysis used to track `Qualif`s on complex control-flow graphs.
use rustc_index::bit_set::BitSet;
use rustc_middle::mir::visit::Visitor;
use rustc_middle::mir::{
self, BasicBlock, CallReturnPlaces, Local, Location, Statement, StatementKind, TerminatorEdges,
};
use rustc_mir_dataflow::fmt::DebugWithContext;
use rustc_mir_dataflow::JoinSemiLattice;
use rustc_mir_dataflow::{Analysis, AnalysisDomain};
use std::fmt;
use std::marker::PhantomData;
use super::{qualifs, ConstCx, Qualif};
/// A `Visitor` that propagates qualifs between locals. This defines the transfer function of
/// `FlowSensitiveAnalysis`.
///
/// To account for indirect assignments, data flow conservatively assumes that local becomes
/// qualified immediately after it is borrowed or its address escapes. The borrow must allow for
/// mutation, which includes shared borrows of places with interior mutability. The type of
/// borrowed place must contain the qualif.
struct TransferFunction<'a, 'mir, 'tcx, Q> {
ccx: &'a ConstCx<'mir, 'tcx>,
state: &'a mut State,
_qualif: PhantomData<Q>,
}
impl<'a, 'mir, 'tcx, Q> TransferFunction<'a, 'mir, 'tcx, Q>
where
Q: Qualif,
{
fn new(ccx: &'a ConstCx<'mir, 'tcx>, state: &'a mut State) -> Self {
TransferFunction { ccx, state, _qualif: PhantomData }
}
fn initialize_state(&mut self) {
self.state.qualif.clear();
self.state.borrow.clear();
for arg in self.ccx.body.args_iter() {
let arg_ty = self.ccx.body.local_decls[arg].ty;
if Q::in_any_value_of_ty(self.ccx, arg_ty) {
self.state.qualif.insert(arg);
}
}
}
fn assign_qualif_direct(&mut self, place: &mir::Place<'tcx>, mut value: bool) {
debug_assert!(!place.is_indirect());
if !value {
for (base, _elem) in place.iter_projections() {
let base_ty = base.ty(self.ccx.body, self.ccx.tcx);
if base_ty.ty.is_union() && Q::in_any_value_of_ty(self.ccx, base_ty.ty) {
value = true;
break;
}
}
}
match (value, place.as_ref()) {
(true, mir::PlaceRef { local, .. }) => {
self.state.qualif.insert(local);
}
// For now, we do not clear the qualif if a local is overwritten in full by
// an unqualified rvalue (e.g. `y = 5`). This is to be consistent
// with aggregates where we overwrite all fields with assignments, which would not
// get this feature.
(false, mir::PlaceRef { local: _, projection: &[] }) => {
// self.state.qualif.remove(*local);
}
_ => {}
}
}
fn apply_call_return_effect(
&mut self,
_block: BasicBlock,
return_places: CallReturnPlaces<'_, 'tcx>,
) {
return_places.for_each(|place| {
// We cannot reason about another function's internals, so use conservative type-based
// qualification for the result of a function call.
let return_ty = place.ty(self.ccx.body, self.ccx.tcx).ty;
let qualif = Q::in_any_value_of_ty(self.ccx, return_ty);
if !place.is_indirect() {
self.assign_qualif_direct(&place, qualif);
}
});
}
fn address_of_allows_mutation(&self) -> bool {
// Exact set of permissions granted by AddressOf is undecided. Conservatively assume that
// it might allow mutation until resolution of #56604.
true
}
fn ref_allows_mutation(&self, kind: mir::BorrowKind, place: mir::Place<'tcx>) -> bool {
match kind {
mir::BorrowKind::Mut { .. } => true,
mir::BorrowKind::Shared | mir::BorrowKind::Fake(_) => {
self.shared_borrow_allows_mutation(place)
}
}
}
/// `&` only allow mutation if the borrowed place is `!Freeze`.
///
/// This assumes that it is UB to take the address of a struct field whose type is
/// `Freeze`, then use pointer arithmetic to derive a pointer to a *different* field of
/// that same struct whose type is `!Freeze`. If we decide that this is not UB, we will
/// have to check the type of the borrowed **local** instead of the borrowed **place**
/// below. See [rust-lang/unsafe-code-guidelines#134].
///
/// [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, self.ccx.param_env)
}
}
impl<'tcx, Q> Visitor<'tcx> for TransferFunction<'_, '_, 'tcx, Q>
where
Q: Qualif,
{
fn visit_operand(&mut self, operand: &mir::Operand<'tcx>, location: Location) {
self.super_operand(operand, location);
if !Q::IS_CLEARED_ON_MOVE {
return;
}
// If a local with no projections is moved from (e.g. `x` in `y = x`), record that
// it no longer needs to be dropped.
if let mir::Operand::Move(place) = operand {
if let Some(local) = place.as_local() {
// For backward compatibility with the MaybeMutBorrowedLocals used in an earlier
// implementation we retain qualif if a local had been borrowed before. This might
// not be strictly necessary since the local is no longer initialized.
if !self.state.borrow.contains(local) {
self.state.qualif.remove(local);
}
}
}
}
fn visit_assign(
&mut self,
place: &mir::Place<'tcx>,
rvalue: &mir::Rvalue<'tcx>,
location: Location,
) {
let qualif =
qualifs::in_rvalue::<Q, _>(self.ccx, &mut |l| self.state.qualif.contains(l), rvalue);
if !place.is_indirect() {
self.assign_qualif_direct(place, qualif);
}
// We need to assign qualifs to the left-hand side before visiting `rvalue` since
// qualifs can be cleared on move.
self.super_assign(place, rvalue, location);
}
fn visit_rvalue(&mut self, rvalue: &mir::Rvalue<'tcx>, location: Location) {
self.super_rvalue(rvalue, location);
match rvalue {
mir::Rvalue::AddressOf(_mt, borrowed_place) => {
if !borrowed_place.is_indirect() && self.address_of_allows_mutation() {
let place_ty = borrowed_place.ty(self.ccx.body, self.ccx.tcx).ty;
if Q::in_any_value_of_ty(self.ccx, place_ty) {
self.state.qualif.insert(borrowed_place.local);
self.state.borrow.insert(borrowed_place.local);
}
}
}
mir::Rvalue::Ref(_, kind, borrowed_place) => {
if !borrowed_place.is_indirect() && self.ref_allows_mutation(*kind, *borrowed_place)
{
let place_ty = borrowed_place.ty(self.ccx.body, self.ccx.tcx).ty;
if Q::in_any_value_of_ty(self.ccx, place_ty) {
self.state.qualif.insert(borrowed_place.local);
self.state.borrow.insert(borrowed_place.local);
}
}
}
mir::Rvalue::Cast(..)
| mir::Rvalue::ShallowInitBox(..)
| mir::Rvalue::Use(..)
| mir::Rvalue::CopyForDeref(..)
| mir::Rvalue::ThreadLocalRef(..)
| mir::Rvalue::Repeat(..)
| mir::Rvalue::Len(..)
| mir::Rvalue::BinaryOp(..)
| mir::Rvalue::NullaryOp(..)
| mir::Rvalue::UnaryOp(..)
| mir::Rvalue::Discriminant(..)
| mir::Rvalue::Aggregate(..) => {}
}
}
fn visit_statement(&mut self, statement: &Statement<'tcx>, location: Location) {
match statement.kind {
StatementKind::StorageDead(local) => {
self.state.qualif.remove(local);
self.state.borrow.remove(local);
}
_ => self.super_statement(statement, location),
}
}
fn visit_terminator(&mut self, terminator: &mir::Terminator<'tcx>, location: Location) {
// The effect of assignment to the return place in `TerminatorKind::Call` is not applied
// here; that occurs in `apply_call_return_effect`.
// We ignore borrow on drop because custom drop impls are not allowed in consts.
// FIXME: Reconsider if accounting for borrows in drops is necessary for const drop.
self.super_terminator(terminator, location);
}
}
/// The dataflow analysis used to propagate qualifs on arbitrary CFGs.
pub(super) struct FlowSensitiveAnalysis<'a, 'mir, 'tcx, Q> {
ccx: &'a ConstCx<'mir, 'tcx>,
_qualif: PhantomData<Q>,
}
impl<'a, 'mir, 'tcx, Q> FlowSensitiveAnalysis<'a, 'mir, 'tcx, Q>
where
Q: Qualif,
{
pub(super) fn new(_: Q, ccx: &'a ConstCx<'mir, 'tcx>) -> Self {
FlowSensitiveAnalysis { ccx, _qualif: PhantomData }
}
fn transfer_function(&self, state: &'a mut State) -> TransferFunction<'a, 'mir, 'tcx, Q> {
TransferFunction::<Q>::new(self.ccx, state)
}
}
#[derive(Debug, PartialEq, Eq)]
pub(super) struct State {
/// Describes whether a local contains qualif.
pub qualif: BitSet<Local>,
/// Describes whether a local's address escaped and it might become qualified as a result an
/// indirect mutation.
pub borrow: BitSet<Local>,
}
impl Clone for State {
fn clone(&self) -> Self {
State { qualif: self.qualif.clone(), borrow: self.borrow.clone() }
}
// Data flow engine when possible uses `clone_from` for domain values.
// Providing an implementation will avoid some intermediate memory allocations.
fn clone_from(&mut self, other: &Self) {
self.qualif.clone_from(&other.qualif);
self.borrow.clone_from(&other.borrow);
}
}
impl State {
#[inline]
pub(super) fn contains(&self, local: Local) -> bool {
self.qualif.contains(local)
}
}
impl<C> DebugWithContext<C> for State {
fn fmt_with(&self, ctxt: &C, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str("qualif: ")?;
self.qualif.fmt_with(ctxt, f)?;
f.write_str(" borrow: ")?;
self.borrow.fmt_with(ctxt, f)?;
Ok(())
}
fn fmt_diff_with(&self, old: &Self, ctxt: &C, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if self == old {
return Ok(());
}
if self.qualif != old.qualif {
f.write_str("qualif: ")?;
self.qualif.fmt_diff_with(&old.qualif, ctxt, f)?;
f.write_str("\n")?;
}
if self.borrow != old.borrow {
f.write_str("borrow: ")?;
self.qualif.fmt_diff_with(&old.borrow, ctxt, f)?;
f.write_str("\n")?;
}
Ok(())
}
}
impl JoinSemiLattice for State {
fn join(&mut self, other: &Self) -> bool {
self.qualif.join(&other.qualif) || self.borrow.join(&other.borrow)
}
}
impl<'tcx, Q> AnalysisDomain<'tcx> for FlowSensitiveAnalysis<'_, '_, 'tcx, Q>
where
Q: Qualif,
{
type Domain = State;
const NAME: &'static str = Q::ANALYSIS_NAME;
fn bottom_value(&self, body: &mir::Body<'tcx>) -> Self::Domain {
State {
qualif: BitSet::new_empty(body.local_decls.len()),
borrow: BitSet::new_empty(body.local_decls.len()),
}
}
fn initialize_start_block(&self, _body: &mir::Body<'tcx>, state: &mut Self::Domain) {
self.transfer_function(state).initialize_state();
}
}
impl<'tcx, Q> Analysis<'tcx> for FlowSensitiveAnalysis<'_, '_, 'tcx, Q>
where
Q: Qualif,
{
fn apply_statement_effect(
&mut self,
state: &mut Self::Domain,
statement: &mir::Statement<'tcx>,
location: Location,
) {
self.transfer_function(state).visit_statement(statement, location);
}
fn apply_terminator_effect<'mir>(
&mut self,
state: &mut Self::Domain,
terminator: &'mir mir::Terminator<'tcx>,
location: Location,
) -> TerminatorEdges<'mir, 'tcx> {
self.transfer_function(state).visit_terminator(terminator, location);
terminator.edges()
}
fn apply_call_return_effect(
&mut self,
state: &mut Self::Domain,
block: BasicBlock,
return_places: CallReturnPlaces<'_, 'tcx>,
) {
self.transfer_function(state).apply_call_return_effect(block, return_places)
}
}