1 //! The `Visitor` responsible for actually checking a `mir::Body` for invalid operations.
3 use rustc::hir::{self, def_id::DefId};
4 use rustc::mir::visit::{PlaceContext, Visitor, MutatingUseContext, NonMutatingUseContext};
6 use rustc::ty::cast::CastTy;
7 use rustc::ty::{self, TyCtxt};
8 use rustc_index::bit_set::BitSet;
9 use rustc_target::spec::abi::Abi;
10 use syntax::symbol::sym;
13 use std::cell::RefCell;
17 use crate::dataflow as old_dataflow;
18 use super::{Item, Qualif, is_lang_panic_fn};
19 use super::resolver::{FlowSensitiveResolver, IndirectlyMutableResults, QualifResolver};
20 use super::qualifs::{HasMutInterior, NeedsDrop};
21 use super::ops::{self, NonConstOp};
23 #[derive(Copy, Clone, Debug, PartialEq, Eq)]
24 pub enum CheckOpResult {
30 /// What kind of item we are in.
31 #[derive(Copy, Clone, Debug, PartialEq, Eq)]
35 /// A `static mut` item.
37 /// A `const fn` item.
39 /// A `const` item or an anonymous constant (e.g. in array lengths).
44 /// Returns the validation mode for the item with the given `DefId`, or `None` if this item
45 /// does not require validation (e.g. a non-const `fn`).
46 pub fn for_item(tcx: TyCtxt<'tcx>, def_id: DefId) -> Option<Self> {
47 use hir::BodyOwnerKind as HirKind;
49 let hir_id = tcx.hir().as_local_hir_id(def_id).unwrap();
51 let mode = match tcx.hir().body_owner_kind(hir_id) {
52 HirKind::Closure => return None,
54 HirKind::Fn if tcx.is_const_fn(def_id) => Mode::ConstFn,
55 HirKind::Fn => return None,
57 HirKind::Const => Mode::Const,
59 HirKind::Static(hir::MutImmutable) => Mode::Static,
60 HirKind::Static(hir::MutMutable) => Mode::StaticMut,
66 pub fn is_static(self) -> bool {
68 Mode::Static | Mode::StaticMut => true,
69 Mode::ConstFn | Mode::Const => false,
73 /// Returns `true` if the value returned by this item must be `Sync`.
75 /// This returns false for `StaticMut` since all accesses to one are `unsafe` anyway.
76 pub fn requires_sync(self) -> bool {
79 Mode::ConstFn | Mode::Const | Mode::StaticMut => false,
84 impl fmt::Display for Mode {
85 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
87 Mode::Const => write!(f, "constant"),
88 Mode::Static | Mode::StaticMut => write!(f, "static"),
89 Mode::ConstFn => write!(f, "constant function"),
94 pub struct Qualifs<'a, 'mir, 'tcx> {
95 has_mut_interior: FlowSensitiveResolver<'a, 'mir, 'tcx, HasMutInterior>,
96 needs_drop: FlowSensitiveResolver<'a, 'mir, 'tcx, NeedsDrop>,
99 pub struct Validator<'a, 'mir, 'tcx> {
100 item: &'a Item<'mir, 'tcx>,
101 qualifs: Qualifs<'a, 'mir, 'tcx>,
103 /// The span of the current statement.
106 /// True if the local was assigned the result of an illegal borrow (`ops::MutBorrow`).
108 /// This is used to hide errors from {re,}borrowing the newly-assigned local, instead pointing
109 /// the user to the place where the illegal borrow occurred. This set is only populated once an
110 /// error has been emitted, so it will never cause an erroneous `mir::Body` to pass validation.
112 /// FIXME(ecstaticmorse): assert at the end of checking that if `tcx.has_errors() == false`,
113 /// this set is empty. Note that if we start removing locals from
114 /// `derived_from_illegal_borrow`, just checking at the end won't be enough.
115 derived_from_illegal_borrow: BitSet<Local>,
117 errors: Vec<(Span, String)>,
119 /// Whether to actually emit errors or just store them in `errors`.
120 pub(crate) suppress_errors: bool,
123 impl Deref for Validator<'_, 'mir, 'tcx> {
124 type Target = Item<'mir, 'tcx>;
126 fn deref(&self) -> &Self::Target {
131 pub fn compute_indirectly_mutable_locals<'mir, 'tcx>(
132 item: &Item<'mir, 'tcx>,
133 ) -> RefCell<IndirectlyMutableResults<'mir, 'tcx>> {
134 let dead_unwinds = BitSet::new_empty(item.body.basic_blocks().len());
136 let indirectly_mutable_locals = old_dataflow::do_dataflow(
142 old_dataflow::IndirectlyMutableLocals::new(item.tcx, item.body, item.param_env),
143 |_, local| old_dataflow::DebugFormatted::new(&local),
146 let indirectly_mutable_locals = old_dataflow::DataflowResultsCursor::new(
147 indirectly_mutable_locals,
151 RefCell::new(indirectly_mutable_locals)
154 impl Validator<'a, 'mir, 'tcx> {
156 item: &'a Item<'mir, 'tcx>,
157 indirectly_mutable_locals: &'a RefCell<IndirectlyMutableResults<'mir, 'tcx>>,
159 let dead_unwinds = BitSet::new_empty(item.body.basic_blocks().len());
161 let needs_drop = FlowSensitiveResolver::new(
164 indirectly_mutable_locals,
168 let has_mut_interior = FlowSensitiveResolver::new(
171 indirectly_mutable_locals,
175 let qualifs = Qualifs {
181 span: item.body.span,
185 derived_from_illegal_borrow: BitSet::new_empty(item.body.local_decls.len()),
186 suppress_errors: false,
190 /// Resets the `QualifResolver`s used by this `Validator` and returns them so they can be
192 pub fn into_qualifs(mut self) -> Qualifs<'a, 'mir, 'tcx> {
193 self.qualifs.needs_drop.reset();
194 self.qualifs.has_mut_interior.reset();
198 pub fn take_errors(&mut self) -> Vec<(Span, String)> {
199 std::mem::replace(&mut self.errors, vec![])
202 /// Emits an error at the given `span` if an expression cannot be evaluated in the current
203 /// context. Returns `Forbidden` if an error was emitted.
204 pub fn check_op_spanned<O>(&mut self, op: O, span: Span) -> CheckOpResult
206 O: NonConstOp + fmt::Debug
208 trace!("check_op: op={:?}", op);
210 if op.is_allowed_in_item(self) {
211 return CheckOpResult::Allowed;
214 // If an operation is supported in miri (and is not already controlled by a feature gate) it
215 // can be turned on with `-Zunleash-the-miri-inside-of-you`.
216 let is_unleashable = O::IS_SUPPORTED_IN_MIRI
217 && O::feature_gate(self.tcx).is_none();
219 if is_unleashable && self.tcx.sess.opts.debugging_opts.unleash_the_miri_inside_of_you {
220 self.tcx.sess.span_warn(span, "skipping const checks");
221 return CheckOpResult::Unleashed;
224 if !self.suppress_errors {
225 op.emit_error(self, span);
228 self.errors.push((span, format!("{:?}", op)));
229 CheckOpResult::Forbidden
232 /// Emits an error if an expression cannot be evaluated in the current context.
233 pub fn check_op(&mut self, op: impl NonConstOp + fmt::Debug) -> CheckOpResult {
234 let span = self.span;
235 self.check_op_spanned(op, span)
239 impl Visitor<'tcx> for Validator<'_, 'mir, 'tcx> {
240 fn visit_rvalue(&mut self, rvalue: &Rvalue<'tcx>, location: Location) {
241 trace!("visit_rvalue: rvalue={:?} location={:?}", rvalue, location);
243 // Check nested operands and places.
244 if let Rvalue::Ref(_, kind, ref place) = *rvalue {
245 // Special-case reborrows to be more like a copy of a reference.
246 let mut reborrow_place = None;
247 if let box [proj_base @ .., elem] = &place.projection {
248 if *elem == ProjectionElem::Deref {
249 let base_ty = Place::ty_from(&place.base, proj_base, self.body, self.tcx).ty;
250 if let ty::Ref(..) = base_ty.kind {
251 reborrow_place = Some(proj_base);
256 if let Some(proj) = reborrow_place {
257 let ctx = match kind {
258 BorrowKind::Shared => PlaceContext::NonMutatingUse(
259 NonMutatingUseContext::SharedBorrow,
261 BorrowKind::Shallow => PlaceContext::NonMutatingUse(
262 NonMutatingUseContext::ShallowBorrow,
264 BorrowKind::Unique => PlaceContext::NonMutatingUse(
265 NonMutatingUseContext::UniqueBorrow,
267 BorrowKind::Mut { .. } => PlaceContext::MutatingUse(
268 MutatingUseContext::Borrow,
271 self.visit_place_base(&place.base, ctx, location);
272 self.visit_projection(&place.base, proj, ctx, location);
274 self.super_rvalue(rvalue, location);
277 self.super_rvalue(rvalue, location);
283 Rvalue::UnaryOp(UnOp::Neg, _) |
284 Rvalue::UnaryOp(UnOp::Not, _) |
285 Rvalue::NullaryOp(NullOp::SizeOf, _) |
286 Rvalue::CheckedBinaryOp(..) |
287 Rvalue::Cast(CastKind::Pointer(_), ..) |
288 Rvalue::Discriminant(..) |
291 Rvalue::Aggregate(..) => {}
293 Rvalue::Cast(CastKind::Misc, ref operand, cast_ty) => {
294 let operand_ty = operand.ty(self.body, self.tcx);
295 let cast_in = CastTy::from_ty(operand_ty).expect("bad input type for cast");
296 let cast_out = CastTy::from_ty(cast_ty).expect("bad output type for cast");
298 if let (CastTy::Ptr(_), CastTy::Int(_))
299 | (CastTy::FnPtr, CastTy::Int(_)) = (cast_in, cast_out) {
300 self.check_op(ops::RawPtrToIntCast);
304 Rvalue::BinaryOp(op, ref lhs, _) => {
305 if let ty::RawPtr(_) | ty::FnPtr(..) = lhs.ty(self.body, self.tcx).kind {
306 assert!(op == BinOp::Eq || op == BinOp::Ne ||
307 op == BinOp::Le || op == BinOp::Lt ||
308 op == BinOp::Ge || op == BinOp::Gt ||
309 op == BinOp::Offset);
312 self.check_op(ops::RawPtrComparison);
316 Rvalue::NullaryOp(NullOp::Box, _) => {
317 self.check_op(ops::HeapAllocation);
324 place_base: &PlaceBase<'tcx>,
325 context: PlaceContext,
329 "visit_place_base: place_base={:?} context={:?} location={:?}",
334 self.super_place_base(place_base, context, location);
337 PlaceBase::Local(_) => {}
338 PlaceBase::Static(box Static{ kind: StaticKind::Promoted(_, _), .. }) => {
339 bug!("Promotion must be run after const validation");
342 PlaceBase::Static(box Static{ kind: StaticKind::Static, def_id, .. }) => {
343 let is_thread_local = self.tcx.has_attr(*def_id, sym::thread_local);
345 self.check_op(ops::ThreadLocalAccess);
346 } else if self.mode == Mode::Static && context.is_mutating_use() {
347 // this is not strictly necessary as miri will also bail out
348 // For interior mutability we can't really catch this statically as that
349 // goes through raw pointers and intermediate temporaries, so miri has
350 // to catch this anyway
352 self.tcx.sess.span_err(
354 "cannot mutate statics in the initializer of another static",
357 self.check_op(ops::StaticAccess);
363 fn visit_assign(&mut self, dest: &Place<'tcx>, rvalue: &Rvalue<'tcx>, location: Location) {
364 trace!("visit_assign: dest={:?} rvalue={:?} location={:?}", dest, rvalue, location);
366 // Error on mutable borrows or shared borrows of values with interior mutability.
368 // This replicates the logic at the start of `assign` in the old const checker. Note that
369 // it depends on `HasMutInterior` being set for mutable borrows as well as values with
370 // interior mutability.
371 if let Rvalue::Ref(_, kind, ref borrowed_place) = *rvalue {
372 let rvalue_has_mut_interior = HasMutInterior::in_rvalue(
374 self.qualifs.has_mut_interior.get(),
378 if rvalue_has_mut_interior {
379 let is_derived_from_illegal_borrow = match *borrowed_place {
380 // If an unprojected local was borrowed and its value was the result of an
381 // illegal borrow, suppress this error and mark the result of this borrow as
383 Place { base: PlaceBase::Local(borrowed_local), projection: box [] }
384 if self.derived_from_illegal_borrow.contains(borrowed_local) => true,
386 // Otherwise proceed normally: check the legality of a mutable borrow in this
388 _ => self.check_op(ops::MutBorrow(kind)) == CheckOpResult::Forbidden,
391 // When the target of the assignment is a local with no projections, mark it as
392 // derived from an illegal borrow if necessary.
394 // FIXME: should we also clear `derived_from_illegal_borrow` when a local is
395 // assigned a new value?
396 if is_derived_from_illegal_borrow {
397 if let Place { base: PlaceBase::Local(dest), projection: box [] } = *dest {
398 self.derived_from_illegal_borrow.insert(dest);
404 self.super_assign(dest, rvalue, location);
409 place_base: &PlaceBase<'tcx>,
410 proj: &[PlaceElem<'tcx>],
411 context: PlaceContext,
415 "visit_place_projection: proj={:?} context={:?} location={:?}",
420 self.super_projection(place_base, proj, context, location);
422 let (elem, proj_base) = match proj.split_last() {
428 ProjectionElem::Deref => {
429 if context.is_mutating_use() {
430 self.check_op(ops::MutDeref);
433 let base_ty = Place::ty_from(place_base, proj_base, self.body, self.tcx).ty;
434 if let ty::RawPtr(_) = base_ty.kind {
435 self.check_op(ops::RawPtrDeref);
439 ProjectionElem::ConstantIndex {..} |
440 ProjectionElem::Subslice {..} |
441 ProjectionElem::Field(..) |
442 ProjectionElem::Index(_) => {
443 let base_ty = Place::ty_from(place_base, proj_base, self.body, self.tcx).ty;
444 match base_ty.ty_adt_def() {
445 Some(def) if def.is_union() => {
446 self.check_op(ops::UnionAccess);
453 ProjectionElem::Downcast(..) => {
454 self.check_op(ops::Downcast);
460 fn visit_source_info(&mut self, source_info: &SourceInfo) {
461 trace!("visit_source_info: source_info={:?}", source_info);
462 self.span = source_info.span;
465 fn visit_statement(&mut self, statement: &Statement<'tcx>, location: Location) {
466 trace!("visit_statement: statement={:?} location={:?}", statement, location);
468 self.qualifs.needs_drop.visit_statement(statement, location);
469 self.qualifs.has_mut_interior.visit_statement(statement, location);
471 match statement.kind {
472 StatementKind::Assign(..) => {
473 self.super_statement(statement, location);
475 StatementKind::FakeRead(FakeReadCause::ForMatchedPlace, _) => {
476 self.check_op(ops::IfOrMatch);
478 // FIXME(eddyb) should these really do nothing?
479 StatementKind::FakeRead(..) |
480 StatementKind::SetDiscriminant { .. } |
481 StatementKind::StorageLive(_) |
482 StatementKind::StorageDead(_) |
483 StatementKind::InlineAsm {..} |
484 StatementKind::Retag { .. } |
485 StatementKind::AscribeUserType(..) |
486 StatementKind::Nop => {}
490 fn visit_terminator(&mut self, terminator: &Terminator<'tcx>, location: Location) {
491 trace!("visit_terminator: terminator={:?} location={:?}", terminator, location);
493 self.qualifs.needs_drop.visit_terminator(terminator, location);
494 self.qualifs.has_mut_interior.visit_terminator(terminator, location);
496 self.super_terminator(terminator, location);
499 fn visit_terminator_kind(&mut self, kind: &TerminatorKind<'tcx>, location: Location) {
500 trace!("visit_terminator_kind: kind={:?} location={:?}", kind, location);
501 self.super_terminator_kind(kind, location);
504 TerminatorKind::Call { func, .. } => {
505 let fn_ty = func.ty(self.body, self.tcx);
507 let def_id = match fn_ty.kind {
508 ty::FnDef(def_id, _) => def_id,
511 self.check_op(ops::FnCallIndirect);
515 self.check_op(ops::FnCallOther);
520 // At this point, we are calling a function whose `DefId` is known...
522 if let Abi::RustIntrinsic | Abi::PlatformIntrinsic = self.tcx.fn_sig(def_id).abi() {
523 assert!(!self.tcx.is_const_fn(def_id));
525 if self.tcx.item_name(def_id) == sym::transmute {
526 self.check_op(ops::Transmute);
530 // To preserve the current semantics, we return early, allowing all
531 // intrinsics (except `transmute`) to pass unchecked to miri.
533 // FIXME: We should keep a whitelist of allowed intrinsics (or at least a
534 // blacklist of unimplemented ones) and fail here instead.
538 if self.tcx.is_const_fn(def_id) {
542 if is_lang_panic_fn(self.tcx, def_id) {
543 self.check_op(ops::Panic);
544 } else if let Some(feature) = self.tcx.is_unstable_const_fn(def_id) {
545 // Exempt unstable const fns inside of macros with
546 // `#[allow_internal_unstable]`.
547 if !self.span.allows_unstable(feature) {
548 self.check_op(ops::FnCallUnstable(def_id, feature));
551 self.check_op(ops::FnCallNonConst(def_id));
556 // Forbid all `Drop` terminators unless the place being dropped is a local with no
557 // projections that cannot be `NeedsDrop`.
558 | TerminatorKind::Drop { location: dropped_place, .. }
559 | TerminatorKind::DropAndReplace { location: dropped_place, .. }
561 let mut err_span = self.span;
563 // Check to see if the type of this place can ever have a drop impl. If not, this
564 // `Drop` terminator is frivolous.
565 let ty_needs_drop = dropped_place
566 .ty(self.body, self.tcx)
568 .needs_drop(self.tcx, self.param_env);
574 let needs_drop = if let Place {
575 base: PlaceBase::Local(local),
578 // Use the span where the local was declared as the span of the drop error.
579 err_span = self.body.local_decls[local].source_info.span;
580 self.qualifs.needs_drop.contains(local)
586 self.check_op_spanned(ops::LiveDrop, err_span);