blob: 9ed1ca740b8e727e94853f61dc622743d94156a9 [file] [log] [blame]
//! A copy of the `Qualif` trait in `qualify_consts.rs` that is suitable for the new validator.
use rustc::mir::*;
use rustc::ty::{self, Ty};
use rustc::hir::def_id::DefId;
use syntax_pos::DUMMY_SP;
use super::{ConstKind, Item as ConstCx};
pub fn in_any_value_of_ty(cx: &ConstCx<'_, 'tcx>, ty: Ty<'tcx>) -> ConstQualifs {
ConstQualifs {
has_mut_interior: HasMutInterior::in_any_value_of_ty(cx, ty),
needs_drop: NeedsDrop::in_any_value_of_ty(cx, ty),
}
}
/// 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. So `return true` means
/// "I found something bad, no reason to go on searching". `false` is only returned if we
/// definitely cannot find anything bad anywhere.
///
/// The default implementations proceed structurally.
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;
fn in_qualifs(qualifs: &ConstQualifs) -> bool;
/// Return the qualification that is (conservatively) correct for any value
/// of the type.
fn in_any_value_of_ty(_cx: &ConstCx<'_, 'tcx>, _ty: Ty<'tcx>) -> bool;
fn in_static(_cx: &ConstCx<'_, 'tcx>, _def_id: DefId) -> bool {
// FIXME(eddyb) should we do anything here for value properties?
false
}
fn in_projection_structurally(
cx: &ConstCx<'_, 'tcx>,
per_local: &impl Fn(Local) -> bool,
place: PlaceRef<'_, 'tcx>,
) -> bool {
if let [proj_base @ .., elem] = place.projection {
let base_qualif = Self::in_place(cx, per_local, PlaceRef {
base: place.base,
projection: proj_base,
});
let qualif = base_qualif && Self::in_any_value_of_ty(
cx,
Place::ty_from(place.base, proj_base, cx.body, cx.tcx)
.projection_ty(cx.tcx, elem)
.ty,
);
match elem {
ProjectionElem::Deref |
ProjectionElem::Subslice { .. } |
ProjectionElem::Field(..) |
ProjectionElem::ConstantIndex { .. } |
ProjectionElem::Downcast(..) => qualif,
ProjectionElem::Index(local) => qualif || per_local(*local),
}
} else {
bug!("This should be called if projection is not empty");
}
}
fn in_projection(
cx: &ConstCx<'_, 'tcx>,
per_local: &impl Fn(Local) -> bool,
place: PlaceRef<'_, 'tcx>,
) -> bool {
Self::in_projection_structurally(cx, per_local, place)
}
fn in_place(
cx: &ConstCx<'_, 'tcx>,
per_local: &impl Fn(Local) -> bool,
place: PlaceRef<'_, 'tcx>,
) -> bool {
match place {
PlaceRef {
base: PlaceBase::Local(local),
projection: [],
} => per_local(*local),
PlaceRef {
base: PlaceBase::Static(_),
projection: [],
} => bug!("qualifying already promoted MIR"),
PlaceRef {
base: _,
projection: [.., _],
} => Self::in_projection(cx, per_local, place),
}
}
fn in_operand(
cx: &ConstCx<'_, 'tcx>,
per_local: &impl Fn(Local) -> bool,
operand: &Operand<'tcx>,
) -> bool {
match *operand {
Operand::Copy(ref place) |
Operand::Move(ref place) => Self::in_place(cx, per_local, place.as_ref()),
Operand::Constant(ref constant) => {
if let Some(static_) = constant.check_static_ptr(cx.tcx) {
Self::in_static(cx, static_)
} else if let ty::ConstKind::Unevaluated(def_id, _) = constant.literal.val {
// Don't peek inside trait associated constants.
if cx.tcx.trait_of_item(def_id).is_some() {
Self::in_any_value_of_ty(cx, constant.literal.ty)
} else {
let qualifs = cx.tcx.at(constant.span).mir_const_qualif(def_id);
let qualif = Self::in_qualifs(&qualifs);
// Just in case the type is more specific than
// the definition, e.g., impl associated const
// with type parameters, take it into account.
qualif && Self::in_any_value_of_ty(cx, constant.literal.ty)
}
} else {
false
}
}
}
}
fn in_rvalue_structurally(
cx: &ConstCx<'_, 'tcx>,
per_local: &impl Fn(Local) -> bool,
rvalue: &Rvalue<'tcx>,
) -> bool {
match *rvalue {
Rvalue::NullaryOp(..) => false,
Rvalue::Discriminant(ref place) |
Rvalue::Len(ref place) => Self::in_place(cx, per_local, place.as_ref()),
Rvalue::Use(ref operand) |
Rvalue::Repeat(ref operand, _) |
Rvalue::UnaryOp(_, ref operand) |
Rvalue::Cast(_, ref operand, _) => Self::in_operand(cx, per_local, operand),
Rvalue::BinaryOp(_, ref lhs, ref rhs) |
Rvalue::CheckedBinaryOp(_, ref lhs, ref rhs) => {
Self::in_operand(cx, per_local, lhs) || Self::in_operand(cx, per_local, rhs)
}
Rvalue::Ref(_, _, ref place) => {
// Special-case reborrows to be more like a copy of the reference.
if let &[ref proj_base @ .., elem] = place.projection.as_ref() {
if ProjectionElem::Deref == elem {
let base_ty = Place::ty_from(&place.base, proj_base, cx.body, cx.tcx).ty;
if let ty::Ref(..) = base_ty.kind {
return Self::in_place(cx, per_local, PlaceRef {
base: &place.base,
projection: proj_base,
});
}
}
}
Self::in_place(cx, per_local, place.as_ref())
}
Rvalue::Aggregate(_, ref operands) => {
operands.iter().any(|o| Self::in_operand(cx, per_local, o))
}
}
}
fn in_rvalue(
cx: &ConstCx<'_, 'tcx>,
per_local: &impl Fn(Local) -> bool,
rvalue: &Rvalue<'tcx>,
) -> bool {
Self::in_rvalue_structurally(cx, per_local, rvalue)
}
fn in_call(
cx: &ConstCx<'_, 'tcx>,
_per_local: &impl Fn(Local) -> bool,
_callee: &Operand<'tcx>,
_args: &[Operand<'tcx>],
return_ty: Ty<'tcx>,
) -> bool {
// Be conservative about the returned value of a const fn.
Self::in_any_value_of_ty(cx, return_ty)
}
}
/// 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(cx: &ConstCx<'_, 'tcx>, ty: Ty<'tcx>) -> bool {
!ty.is_freeze(cx.tcx, cx.param_env, DUMMY_SP)
}
fn in_rvalue(
cx: &ConstCx<'_, 'tcx>,
per_local: &impl Fn(Local) -> bool,
rvalue: &Rvalue<'tcx>,
) -> bool {
match *rvalue {
// Returning `true` for `Rvalue::Ref` indicates the borrow isn't
// allowed in constants (and the `Checker` will error), and/or it
// won't be promoted, due to `&mut ...` or interior mutability.
Rvalue::Ref(_, kind, ref place) => {
let ty = place.ty(cx.body, cx.tcx).ty;
if let BorrowKind::Mut { .. } = kind {
// In theory, any zero-sized value could be borrowed
// mutably without consequences.
match ty.kind {
// Inside a `static mut`, &mut [...] is also allowed.
| ty::Array(..)
| ty::Slice(_)
if cx.const_kind == Some(ConstKind::StaticMut)
=> {},
// FIXME(eddyb): We only return false for `&mut []` outside a const
// context which seems unnecessary given that this is merely a ZST.
| ty::Array(_, len)
if len.try_eval_usize(cx.tcx, cx.param_env) == Some(0)
&& cx.const_kind == None
=> {},
_ => return true,
}
}
}
Rvalue::Aggregate(ref kind, _) => {
if let AggregateKind::Adt(def, ..) = **kind {
if Some(def.did) == cx.tcx.lang_items().unsafe_cell_type() {
let ty = rvalue.ty(cx.body, cx.tcx);
assert_eq!(Self::in_any_value_of_ty(cx, ty), true);
return true;
}
}
}
_ => {}
}
Self::in_rvalue_structurally(cx, per_local, rvalue)
}
}
/// Constant containing an ADT that implements `Drop`.
/// This must be ruled out (a) because we cannot run `Drop` during compile-time
/// as that might not be a `const fn`, and (b) because implicit promotion would
/// remove side-effects that occur as part of dropping that value.
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(cx: &ConstCx<'_, 'tcx>, ty: Ty<'tcx>) -> bool {
ty.needs_drop(cx.tcx, cx.param_env)
}
fn in_rvalue(
cx: &ConstCx<'_, 'tcx>,
per_local: &impl Fn(Local) -> bool,
rvalue: &Rvalue<'tcx>,
) -> bool {
if let Rvalue::Aggregate(ref kind, _) = *rvalue {
if let AggregateKind::Adt(def, ..) = **kind {
if def.has_dtor(cx.tcx) {
return true;
}
}
}
Self::in_rvalue_structurally(cx, per_local, rvalue)
}
}