compiler: unnest rustc_const_eval::check_consts
This commit is contained in:
1014
compiler/rustc_const_eval/src/check_consts/check.rs
Normal file
1014
compiler/rustc_const_eval/src/check_consts/check.rs
Normal file
File diff suppressed because it is too large
Load Diff
141
compiler/rustc_const_eval/src/check_consts/mod.rs
Normal file
141
compiler/rustc_const_eval/src/check_consts/mod.rs
Normal 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())
|
||||
}
|
||||
653
compiler/rustc_const_eval/src/check_consts/ops.rs
Normal file
653
compiler/rustc_const_eval/src/check_consts/ops.rs
Normal 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()),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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 { .. } => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
397
compiler/rustc_const_eval/src/check_consts/qualifs.rs
Normal file
397
compiler/rustc_const_eval/src/check_consts/qualifs.rs
Normal 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())
|
||||
}
|
||||
365
compiler/rustc_const_eval/src/check_consts/resolver.rs
Normal file
365
compiler/rustc_const_eval/src/check_consts/resolver.rs
Normal 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)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user