Represent the raw pointer for a array length check as a new kind of fake borrow
This commit is contained in:
parent
057313b7a6
commit
eeecb56b73
26 changed files with 199 additions and 89 deletions
|
@ -1284,15 +1284,18 @@ impl<'a, 'tcx> MirBorrowckCtxt<'a, '_, 'tcx> {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
&Rvalue::RawPtr(mutability, place) => {
|
&Rvalue::RawPtr(kind, place) => {
|
||||||
let access_kind = match mutability {
|
let access_kind = match kind {
|
||||||
Mutability::Mut => (
|
RawPtrKind::Mut => (
|
||||||
Deep,
|
Deep,
|
||||||
Write(WriteKind::MutableBorrow(BorrowKind::Mut {
|
Write(WriteKind::MutableBorrow(BorrowKind::Mut {
|
||||||
kind: MutBorrowKind::Default,
|
kind: MutBorrowKind::Default,
|
||||||
})),
|
})),
|
||||||
),
|
),
|
||||||
Mutability::Not => (Deep, Read(ReadKind::Borrow(BorrowKind::Shared))),
|
RawPtrKind::Const => (Deep, Read(ReadKind::Borrow(BorrowKind::Shared))),
|
||||||
|
RawPtrKind::FakeForPtrMetadata => {
|
||||||
|
(Shallow(Some(ArtificialField::ArrayLength)), Read(ReadKind::Copy))
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
self.access_place(
|
self.access_place(
|
||||||
|
|
|
@ -3,11 +3,7 @@ use std::ops::ControlFlow;
|
||||||
use rustc_data_structures::graph::dominators::Dominators;
|
use rustc_data_structures::graph::dominators::Dominators;
|
||||||
use rustc_middle::bug;
|
use rustc_middle::bug;
|
||||||
use rustc_middle::mir::visit::Visitor;
|
use rustc_middle::mir::visit::Visitor;
|
||||||
use rustc_middle::mir::{
|
use rustc_middle::mir::*;
|
||||||
self, BasicBlock, Body, BorrowKind, FakeBorrowKind, InlineAsmOperand, Location, Mutability,
|
|
||||||
NonDivergingIntrinsic, Operand, Place, Rvalue, Statement, StatementKind, Terminator,
|
|
||||||
TerminatorKind,
|
|
||||||
};
|
|
||||||
use rustc_middle::ty::TyCtxt;
|
use rustc_middle::ty::TyCtxt;
|
||||||
use tracing::debug;
|
use tracing::debug;
|
||||||
|
|
||||||
|
@ -60,7 +56,7 @@ impl<'a, 'tcx> Visitor<'tcx> for LoanInvalidationsGenerator<'a, 'tcx> {
|
||||||
StatementKind::Intrinsic(box NonDivergingIntrinsic::Assume(op)) => {
|
StatementKind::Intrinsic(box NonDivergingIntrinsic::Assume(op)) => {
|
||||||
self.consume_operand(location, op);
|
self.consume_operand(location, op);
|
||||||
}
|
}
|
||||||
StatementKind::Intrinsic(box NonDivergingIntrinsic::CopyNonOverlapping(mir::CopyNonOverlapping {
|
StatementKind::Intrinsic(box NonDivergingIntrinsic::CopyNonOverlapping(CopyNonOverlapping {
|
||||||
src,
|
src,
|
||||||
dst,
|
dst,
|
||||||
count,
|
count,
|
||||||
|
@ -273,15 +269,18 @@ impl<'a, 'tcx> LoanInvalidationsGenerator<'a, 'tcx> {
|
||||||
self.access_place(location, place, access_kind, LocalMutationIsAllowed::No);
|
self.access_place(location, place, access_kind, LocalMutationIsAllowed::No);
|
||||||
}
|
}
|
||||||
|
|
||||||
&Rvalue::RawPtr(mutability, place) => {
|
&Rvalue::RawPtr(kind, place) => {
|
||||||
let access_kind = match mutability {
|
let access_kind = match kind {
|
||||||
Mutability::Mut => (
|
RawPtrKind::Mut => (
|
||||||
Deep,
|
Deep,
|
||||||
Write(WriteKind::MutableBorrow(BorrowKind::Mut {
|
Write(WriteKind::MutableBorrow(BorrowKind::Mut {
|
||||||
kind: mir::MutBorrowKind::Default,
|
kind: MutBorrowKind::Default,
|
||||||
})),
|
})),
|
||||||
),
|
),
|
||||||
Mutability::Not => (Deep, Read(ReadKind::Borrow(BorrowKind::Shared))),
|
RawPtrKind::Const => (Deep, Read(ReadKind::Borrow(BorrowKind::Shared))),
|
||||||
|
RawPtrKind::FakeForPtrMetadata => {
|
||||||
|
(Shallow(Some(ArtificialField::ArrayLength)), Read(ReadKind::Copy))
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
self.access_place(location, place, access_kind, LocalMutationIsAllowed::No);
|
self.access_place(location, place, access_kind, LocalMutationIsAllowed::No);
|
||||||
|
|
|
@ -612,9 +612,10 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
|
||||||
mir::Rvalue::CopyForDeref(place) => {
|
mir::Rvalue::CopyForDeref(place) => {
|
||||||
self.codegen_operand(bx, &mir::Operand::Copy(place))
|
self.codegen_operand(bx, &mir::Operand::Copy(place))
|
||||||
}
|
}
|
||||||
mir::Rvalue::RawPtr(mutability, place) => {
|
mir::Rvalue::RawPtr(kind, place) => {
|
||||||
let mk_ptr =
|
let mk_ptr = move |tcx: TyCtxt<'tcx>, ty: Ty<'tcx>| {
|
||||||
move |tcx: TyCtxt<'tcx>, ty: Ty<'tcx>| Ty::new_ptr(tcx, ty, mutability);
|
Ty::new_ptr(tcx, ty, kind.to_mutbl_lossy())
|
||||||
|
};
|
||||||
self.codegen_place_to_pointer(bx, place, mk_ptr)
|
self.codegen_place_to_pointer(bx, place, mk_ptr)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -518,7 +518,7 @@ impl<'tcx> Visitor<'tcx> for Checker<'_, 'tcx> {
|
||||||
}
|
}
|
||||||
|
|
||||||
Rvalue::Ref(_, BorrowKind::Mut { .. }, place)
|
Rvalue::Ref(_, BorrowKind::Mut { .. }, place)
|
||||||
| Rvalue::RawPtr(Mutability::Mut, place) => {
|
| Rvalue::RawPtr(RawPtrKind::Mut, place) => {
|
||||||
// Inside mutable statics, we allow arbitrary mutable references.
|
// Inside mutable statics, we allow arbitrary mutable references.
|
||||||
// We've allowed `static mut FOO = &mut [elements];` for a long time (the exact
|
// We've allowed `static mut FOO = &mut [elements];` for a long time (the exact
|
||||||
// reasons why are lost to history), and there is no reason to restrict that to
|
// reasons why are lost to history), and there is no reason to restrict that to
|
||||||
|
@ -536,7 +536,7 @@ impl<'tcx> Visitor<'tcx> for Checker<'_, 'tcx> {
|
||||||
}
|
}
|
||||||
|
|
||||||
Rvalue::Ref(_, BorrowKind::Shared | BorrowKind::Fake(_), place)
|
Rvalue::Ref(_, BorrowKind::Shared | BorrowKind::Fake(_), place)
|
||||||
| Rvalue::RawPtr(Mutability::Not, place) => {
|
| Rvalue::RawPtr(RawPtrKind::Const, place) => {
|
||||||
let borrowed_place_has_mut_interior = qualifs::in_place::<HasMutInterior, _>(
|
let borrowed_place_has_mut_interior = qualifs::in_place::<HasMutInterior, _>(
|
||||||
self.ccx,
|
self.ccx,
|
||||||
&mut |local| self.qualifs.has_mut_interior(self.ccx, local, location),
|
&mut |local| self.qualifs.has_mut_interior(self.ccx, local, location),
|
||||||
|
@ -548,6 +548,12 @@ impl<'tcx> Visitor<'tcx> for Checker<'_, 'tcx> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Rvalue::RawPtr(RawPtrKind::FakeForPtrMetadata, place) => {
|
||||||
|
// These are only inserted for slice length, so the place must already be indirect.
|
||||||
|
// This implies we do not have to worry about whether the borrow escapes.
|
||||||
|
assert!(place.is_indirect(), "fake borrows are always indirect");
|
||||||
|
}
|
||||||
|
|
||||||
Rvalue::Cast(
|
Rvalue::Cast(
|
||||||
CastKind::PointerCoercion(
|
CastKind::PointerCoercion(
|
||||||
PointerCoercion::MutToConstPointer
|
PointerCoercion::MutToConstPointer
|
||||||
|
@ -600,12 +606,8 @@ impl<'tcx> Visitor<'tcx> for Checker<'_, 'tcx> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
UnOp::PtrMetadata => {
|
UnOp::PtrMetadata => {
|
||||||
if !ty.is_ref() && !ty.is_unsafe_ptr() {
|
// Getting the metadata from a pointer is always const.
|
||||||
span_bug!(
|
// We already validated the type is valid in the validator.
|
||||||
self.span,
|
|
||||||
"non-pointer type in `Rvalue::UnaryOp({op:?})`: {ty:?}",
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,7 +9,6 @@ use rustc_middle::ty::layout::FnAbiOf;
|
||||||
use rustc_middle::ty::{self, Instance, Ty};
|
use rustc_middle::ty::{self, Instance, Ty};
|
||||||
use rustc_middle::{bug, mir, span_bug};
|
use rustc_middle::{bug, mir, span_bug};
|
||||||
use rustc_span::source_map::Spanned;
|
use rustc_span::source_map::Spanned;
|
||||||
use rustc_span::{DesugaringKind, Span};
|
|
||||||
use rustc_target::callconv::FnAbi;
|
use rustc_target::callconv::FnAbi;
|
||||||
use tracing::{info, instrument, trace};
|
use tracing::{info, instrument, trace};
|
||||||
|
|
||||||
|
@ -81,9 +80,7 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
|
||||||
use rustc_middle::mir::StatementKind::*;
|
use rustc_middle::mir::StatementKind::*;
|
||||||
|
|
||||||
match &stmt.kind {
|
match &stmt.kind {
|
||||||
Assign(box (place, rvalue)) => {
|
Assign(box (place, rvalue)) => self.eval_rvalue_into_place(rvalue, *place)?,
|
||||||
self.eval_rvalue_into_place(rvalue, *place, stmt.source_info.span)?
|
|
||||||
}
|
|
||||||
|
|
||||||
SetDiscriminant { place, variant_index } => {
|
SetDiscriminant { place, variant_index } => {
|
||||||
let dest = self.eval_place(**place)?;
|
let dest = self.eval_place(**place)?;
|
||||||
|
@ -162,7 +159,6 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
|
||||||
&mut self,
|
&mut self,
|
||||||
rvalue: &mir::Rvalue<'tcx>,
|
rvalue: &mir::Rvalue<'tcx>,
|
||||||
place: mir::Place<'tcx>,
|
place: mir::Place<'tcx>,
|
||||||
span: Span,
|
|
||||||
) -> InterpResult<'tcx> {
|
) -> InterpResult<'tcx> {
|
||||||
let dest = self.eval_place(place)?;
|
let dest = self.eval_place(place)?;
|
||||||
// FIXME: ensure some kind of non-aliasing between LHS and RHS?
|
// FIXME: ensure some kind of non-aliasing between LHS and RHS?
|
||||||
|
@ -241,7 +237,7 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
|
||||||
self.write_immediate(*val, &dest)?;
|
self.write_immediate(*val, &dest)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
RawPtr(_, place) => {
|
RawPtr(kind, place) => {
|
||||||
// Figure out whether this is an addr_of of an already raw place.
|
// Figure out whether this is an addr_of of an already raw place.
|
||||||
let place_base_raw = if place.is_indirect_first_projection() {
|
let place_base_raw = if place.is_indirect_first_projection() {
|
||||||
let ty = self.frame().body.local_decls[place.local].ty;
|
let ty = self.frame().body.local_decls[place.local].ty;
|
||||||
|
@ -254,13 +250,9 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
|
||||||
let src = self.eval_place(place)?;
|
let src = self.eval_place(place)?;
|
||||||
let place = self.force_allocation(&src)?;
|
let place = self.force_allocation(&src)?;
|
||||||
let mut val = ImmTy::from_immediate(place.to_ref(self), dest.layout);
|
let mut val = ImmTy::from_immediate(place.to_ref(self), dest.layout);
|
||||||
if !place_base_raw
|
if !place_base_raw && !kind.is_fake() {
|
||||||
&& span.desugaring_kind() != Some(DesugaringKind::IndexBoundsCheckReborrow)
|
// If this was not already raw, it needs retagging -- except for "fake"
|
||||||
{
|
// raw borrows whose defining property is that they do not get retagged.
|
||||||
// If this was not already raw, it needs retagging.
|
|
||||||
// As a special hack, we exclude the desugared `PtrMetadata(&raw const *_n)`
|
|
||||||
// from indexing. (Really we should not do any retag on `&raw` but that does not
|
|
||||||
// currently work with Stacked Borrows.)
|
|
||||||
val = M::retag_ptr_value(self, mir::RetagKind::Raw, &val)?;
|
val = M::retag_ptr_value(self, mir::RetagKind::Raw, &val)?;
|
||||||
}
|
}
|
||||||
self.write_immediate(*val, &dest)?;
|
self.write_immediate(*val, &dest)?;
|
||||||
|
|
|
@ -180,6 +180,59 @@ pub enum BorrowKind {
|
||||||
Mut { kind: MutBorrowKind },
|
Mut { kind: MutBorrowKind },
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, TyEncodable, TyDecodable)]
|
||||||
|
#[derive(Hash, HashStable)]
|
||||||
|
pub enum RawPtrKind {
|
||||||
|
Mut,
|
||||||
|
Const,
|
||||||
|
/// Creates a raw pointer to a place that will only be used to access its metadata,
|
||||||
|
/// not the data behind the pointer. Note that this limitation is *not* enforced
|
||||||
|
/// by the validator.
|
||||||
|
///
|
||||||
|
/// The borrow checker allows overlap of these raw pointers with references to the
|
||||||
|
/// data. This is sound even if the pointer is "misused" since any such use is anyway
|
||||||
|
/// unsafe. In terms of the operational semantics (i.e., Miri), this is equivalent
|
||||||
|
/// to `RawPtrKind::Mut`, but will never incur a retag.
|
||||||
|
FakeForPtrMetadata,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<Mutability> for RawPtrKind {
|
||||||
|
fn from(other: Mutability) -> Self {
|
||||||
|
match other {
|
||||||
|
Mutability::Mut => RawPtrKind::Mut,
|
||||||
|
Mutability::Not => RawPtrKind::Const,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RawPtrKind {
|
||||||
|
pub fn is_fake(self) -> bool {
|
||||||
|
match self {
|
||||||
|
RawPtrKind::Mut | RawPtrKind::Const => false,
|
||||||
|
RawPtrKind::FakeForPtrMetadata => true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn to_mutbl_lossy(self) -> Mutability {
|
||||||
|
match self {
|
||||||
|
RawPtrKind::Mut => Mutability::Mut,
|
||||||
|
RawPtrKind::Const => Mutability::Not,
|
||||||
|
|
||||||
|
// We have no type corresponding to a fake borrow, so use
|
||||||
|
// `*const` as an approximation.
|
||||||
|
RawPtrKind::FakeForPtrMetadata => Mutability::Not,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn ptr_str(self) -> &'static str {
|
||||||
|
match self {
|
||||||
|
RawPtrKind::Mut => "mut",
|
||||||
|
RawPtrKind::Const => "const",
|
||||||
|
RawPtrKind::FakeForPtrMetadata => "const (fake)",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, TyEncodable, TyDecodable)]
|
#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, TyEncodable, TyDecodable)]
|
||||||
#[derive(Hash, HashStable)]
|
#[derive(Hash, HashStable)]
|
||||||
pub enum MutBorrowKind {
|
pub enum MutBorrowKind {
|
||||||
|
@ -1356,7 +1409,7 @@ pub enum Rvalue<'tcx> {
|
||||||
///
|
///
|
||||||
/// Like with references, the semantics of this operation are heavily dependent on the aliasing
|
/// Like with references, the semantics of this operation are heavily dependent on the aliasing
|
||||||
/// model.
|
/// model.
|
||||||
RawPtr(Mutability, Place<'tcx>),
|
RawPtr(RawPtrKind, Place<'tcx>),
|
||||||
|
|
||||||
/// Yields the length of the place, as a `usize`.
|
/// Yields the length of the place, as a `usize`.
|
||||||
///
|
///
|
||||||
|
|
|
@ -206,9 +206,9 @@ impl<'tcx> Rvalue<'tcx> {
|
||||||
let place_ty = place.ty(local_decls, tcx).ty;
|
let place_ty = place.ty(local_decls, tcx).ty;
|
||||||
Ty::new_ref(tcx, reg, place_ty, bk.to_mutbl_lossy())
|
Ty::new_ref(tcx, reg, place_ty, bk.to_mutbl_lossy())
|
||||||
}
|
}
|
||||||
Rvalue::RawPtr(mutability, ref place) => {
|
Rvalue::RawPtr(kind, ref place) => {
|
||||||
let place_ty = place.ty(local_decls, tcx).ty;
|
let place_ty = place.ty(local_decls, tcx).ty;
|
||||||
Ty::new_ptr(tcx, place_ty, mutability)
|
Ty::new_ptr(tcx, place_ty, kind.to_mutbl_lossy())
|
||||||
}
|
}
|
||||||
Rvalue::Len(..) => tcx.types.usize,
|
Rvalue::Len(..) => tcx.types.usize,
|
||||||
Rvalue::Cast(.., ty) => ty,
|
Rvalue::Cast(.., ty) => ty,
|
||||||
|
|
|
@ -15,6 +15,7 @@ TrivialTypeTraversalImpls! {
|
||||||
SourceScopeLocalData,
|
SourceScopeLocalData,
|
||||||
UserTypeAnnotationIndex,
|
UserTypeAnnotationIndex,
|
||||||
BorrowKind,
|
BorrowKind,
|
||||||
|
RawPtrKind,
|
||||||
CastKind,
|
CastKind,
|
||||||
BasicBlock,
|
BasicBlock,
|
||||||
SwitchTargets,
|
SwitchTargets,
|
||||||
|
|
|
@ -685,12 +685,15 @@ macro_rules! make_mir_visitor {
|
||||||
|
|
||||||
Rvalue::RawPtr(m, path) => {
|
Rvalue::RawPtr(m, path) => {
|
||||||
let ctx = match m {
|
let ctx = match m {
|
||||||
Mutability::Mut => PlaceContext::MutatingUse(
|
RawPtrKind::Mut => PlaceContext::MutatingUse(
|
||||||
MutatingUseContext::RawBorrow
|
MutatingUseContext::RawBorrow
|
||||||
),
|
),
|
||||||
Mutability::Not => PlaceContext::NonMutatingUse(
|
RawPtrKind::Const => PlaceContext::NonMutatingUse(
|
||||||
NonMutatingUseContext::RawBorrow
|
NonMutatingUseContext::RawBorrow
|
||||||
),
|
),
|
||||||
|
RawPtrKind::FakeForPtrMetadata => PlaceContext::NonMutatingUse(
|
||||||
|
NonMutatingUseContext::Inspect
|
||||||
|
),
|
||||||
};
|
};
|
||||||
self.visit_place(path, ctx, location);
|
self.visit_place(path, ctx, location);
|
||||||
}
|
}
|
||||||
|
|
|
@ -253,7 +253,7 @@ impl<'a, 'tcx> ParseCtxt<'a, 'tcx> {
|
||||||
Rvalue::Ref(self.tcx.lifetimes.re_erased, *borrow_kind, self.parse_place(*arg)?)
|
Rvalue::Ref(self.tcx.lifetimes.re_erased, *borrow_kind, self.parse_place(*arg)?)
|
||||||
),
|
),
|
||||||
ExprKind::RawBorrow { mutability, arg } => Ok(
|
ExprKind::RawBorrow { mutability, arg } => Ok(
|
||||||
Rvalue::RawPtr(*mutability, self.parse_place(*arg)?)
|
Rvalue::RawPtr((*mutability).into(), self.parse_place(*arg)?)
|
||||||
),
|
),
|
||||||
ExprKind::Binary { op, lhs, rhs } => Ok(
|
ExprKind::Binary { op, lhs, rhs } => Ok(
|
||||||
Rvalue::BinaryOp(*op, Box::new((self.parse_operand(*lhs)?, self.parse_operand(*rhs)?)))
|
Rvalue::BinaryOp(*op, Box::new((self.parse_operand(*lhs)?, self.parse_operand(*rhs)?)))
|
||||||
|
|
|
@ -11,7 +11,7 @@ use rustc_middle::mir::*;
|
||||||
use rustc_middle::thir::*;
|
use rustc_middle::thir::*;
|
||||||
use rustc_middle::ty::{self, AdtDef, CanonicalUserTypeAnnotation, Ty, Variance};
|
use rustc_middle::ty::{self, AdtDef, CanonicalUserTypeAnnotation, Ty, Variance};
|
||||||
use rustc_middle::{bug, span_bug};
|
use rustc_middle::{bug, span_bug};
|
||||||
use rustc_span::{DesugaringKind, Span};
|
use rustc_span::Span;
|
||||||
use tracing::{debug, instrument, trace};
|
use tracing::{debug, instrument, trace};
|
||||||
|
|
||||||
use crate::builder::ForGuard::{OutsideGuard, RefWithinGuard};
|
use crate::builder::ForGuard::{OutsideGuard, RefWithinGuard};
|
||||||
|
@ -643,8 +643,6 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
|
||||||
source_info: SourceInfo,
|
source_info: SourceInfo,
|
||||||
) -> Operand<'tcx> {
|
) -> Operand<'tcx> {
|
||||||
let place_ty = place.ty(&self.local_decls, self.tcx).ty;
|
let place_ty = place.ty(&self.local_decls, self.tcx).ty;
|
||||||
let usize_ty = self.tcx.types.usize;
|
|
||||||
|
|
||||||
match place_ty.kind() {
|
match place_ty.kind() {
|
||||||
ty::Array(_elem_ty, len_const) => {
|
ty::Array(_elem_ty, len_const) => {
|
||||||
// We know how long an array is, so just use that as a constant
|
// We know how long an array is, so just use that as a constant
|
||||||
|
@ -668,27 +666,18 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
|
||||||
// the MIR we're building here needs to pass NLL later.
|
// the MIR we're building here needs to pass NLL later.
|
||||||
Operand::Copy(Place::from(place.local))
|
Operand::Copy(Place::from(place.local))
|
||||||
} else {
|
} else {
|
||||||
let len_span = self.tcx.with_stable_hashing_context(|hcx| {
|
|
||||||
let span = source_info.span;
|
|
||||||
span.mark_with_reason(
|
|
||||||
None,
|
|
||||||
DesugaringKind::IndexBoundsCheckReborrow,
|
|
||||||
span.edition(),
|
|
||||||
hcx,
|
|
||||||
)
|
|
||||||
});
|
|
||||||
let ptr_ty = Ty::new_imm_ptr(self.tcx, place_ty);
|
let ptr_ty = Ty::new_imm_ptr(self.tcx, place_ty);
|
||||||
let slice_ptr = self.temp(ptr_ty, span);
|
let slice_ptr = self.temp(ptr_ty, span);
|
||||||
self.cfg.push_assign(
|
self.cfg.push_assign(
|
||||||
block,
|
block,
|
||||||
SourceInfo { span: len_span, ..source_info },
|
source_info,
|
||||||
slice_ptr,
|
slice_ptr,
|
||||||
Rvalue::RawPtr(Mutability::Not, place),
|
Rvalue::RawPtr(RawPtrKind::FakeForPtrMetadata, place),
|
||||||
);
|
);
|
||||||
Operand::Move(slice_ptr)
|
Operand::Move(slice_ptr)
|
||||||
};
|
};
|
||||||
|
|
||||||
let len = self.temp(usize_ty, span);
|
let len = self.temp(self.tcx.types.usize, span);
|
||||||
self.cfg.push_assign(
|
self.cfg.push_assign(
|
||||||
block,
|
block,
|
||||||
source_info,
|
source_info,
|
||||||
|
|
|
@ -303,7 +303,7 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
|
||||||
hir::Mutability::Not => this.as_read_only_place(block, arg),
|
hir::Mutability::Not => this.as_read_only_place(block, arg),
|
||||||
hir::Mutability::Mut => this.as_place(block, arg),
|
hir::Mutability::Mut => this.as_place(block, arg),
|
||||||
};
|
};
|
||||||
let address_of = Rvalue::RawPtr(mutability, unpack!(block = place));
|
let address_of = Rvalue::RawPtr(mutability.into(), unpack!(block = place));
|
||||||
this.cfg.push_assign(block, source_info, destination, address_of);
|
this.cfg.push_assign(block, source_info, destination, address_of);
|
||||||
block.unit()
|
block.unit()
|
||||||
}
|
}
|
||||||
|
|
|
@ -700,7 +700,7 @@ where
|
||||||
statements: vec![
|
statements: vec![
|
||||||
self.assign(
|
self.assign(
|
||||||
ptr,
|
ptr,
|
||||||
Rvalue::RawPtr(Mutability::Mut, tcx.mk_place_index(self.place, cur)),
|
Rvalue::RawPtr(RawPtrKind::Mut, tcx.mk_place_index(self.place, cur)),
|
||||||
),
|
),
|
||||||
self.assign(
|
self.assign(
|
||||||
cur.into(),
|
cur.into(),
|
||||||
|
@ -816,7 +816,7 @@ where
|
||||||
|
|
||||||
let mut delegate_block = BasicBlockData {
|
let mut delegate_block = BasicBlockData {
|
||||||
statements: vec![
|
statements: vec![
|
||||||
self.assign(Place::from(array_ptr), Rvalue::RawPtr(Mutability::Mut, self.place)),
|
self.assign(Place::from(array_ptr), Rvalue::RawPtr(RawPtrKind::Mut, self.place)),
|
||||||
self.assign(
|
self.assign(
|
||||||
Place::from(slice_ptr),
|
Place::from(slice_ptr),
|
||||||
Rvalue::Cast(
|
Rvalue::Cast(
|
||||||
|
|
|
@ -192,7 +192,7 @@ enum AggregateTy<'tcx> {
|
||||||
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
|
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
|
||||||
enum AddressKind {
|
enum AddressKind {
|
||||||
Ref(BorrowKind),
|
Ref(BorrowKind),
|
||||||
Address(Mutability),
|
Address(RawPtrKind),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Eq, Hash)]
|
#[derive(Debug, PartialEq, Eq, Hash)]
|
||||||
|
@ -504,7 +504,9 @@ impl<'body, 'tcx> VnState<'body, 'tcx> {
|
||||||
mplace.layout.ty,
|
mplace.layout.ty,
|
||||||
bk.to_mutbl_lossy(),
|
bk.to_mutbl_lossy(),
|
||||||
),
|
),
|
||||||
AddressKind::Address(mutbl) => Ty::new_ptr(self.tcx, mplace.layout.ty, mutbl),
|
AddressKind::Address(mutbl) => {
|
||||||
|
Ty::new_ptr(self.tcx, mplace.layout.ty, mutbl.to_mutbl_lossy())
|
||||||
|
}
|
||||||
};
|
};
|
||||||
let layout = self.ecx.layout_of(ty).ok()?;
|
let layout = self.ecx.layout_of(ty).ok()?;
|
||||||
ImmTy::from_immediate(pointer, layout).into()
|
ImmTy::from_immediate(pointer, layout).into()
|
||||||
|
|
|
@ -125,7 +125,7 @@ impl<'tcx> crate::MirPass<'tcx> for EnumSizeOpt {
|
||||||
source_info,
|
source_info,
|
||||||
kind: StatementKind::Assign(Box::new((
|
kind: StatementKind::Assign(Box::new((
|
||||||
dst,
|
dst,
|
||||||
Rvalue::RawPtr(Mutability::Mut, *lhs),
|
Rvalue::RawPtr(RawPtrKind::Mut, *lhs),
|
||||||
))),
|
))),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -146,7 +146,7 @@ impl<'tcx> crate::MirPass<'tcx> for EnumSizeOpt {
|
||||||
source_info,
|
source_info,
|
||||||
kind: StatementKind::Assign(Box::new((
|
kind: StatementKind::Assign(Box::new((
|
||||||
src,
|
src,
|
||||||
Rvalue::RawPtr(Mutability::Not, *rhs),
|
Rvalue::RawPtr(RawPtrKind::Const, *rhs),
|
||||||
))),
|
))),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -2,17 +2,11 @@ use std::iter;
|
||||||
|
|
||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
use rustc_abi::{FieldIdx, VariantIdx};
|
use rustc_abi::{FieldIdx, VariantIdx};
|
||||||
use rustc_ast::Mutability;
|
|
||||||
use rustc_const_eval::interpret;
|
use rustc_const_eval::interpret;
|
||||||
use rustc_hir::def_id::DefId;
|
use rustc_hir::def_id::DefId;
|
||||||
use rustc_hir::lang_items::LangItem;
|
use rustc_hir::lang_items::LangItem;
|
||||||
use rustc_index::{Idx, IndexVec};
|
use rustc_index::{Idx, IndexVec};
|
||||||
use rustc_middle::mir::{
|
use rustc_middle::mir::*;
|
||||||
BasicBlock, BasicBlockData, Body, CallSource, CastKind, CoercionSource, Const, ConstOperand,
|
|
||||||
ConstValue, Local, LocalDecl, MirSource, Operand, Place, PlaceElem, RETURN_PLACE, Rvalue,
|
|
||||||
SourceInfo, Statement, StatementKind, Terminator, TerminatorKind, UnwindAction,
|
|
||||||
UnwindTerminateReason,
|
|
||||||
};
|
|
||||||
use rustc_middle::ty::adjustment::PointerCoercion;
|
use rustc_middle::ty::adjustment::PointerCoercion;
|
||||||
use rustc_middle::ty::util::{AsyncDropGlueMorphology, Discr};
|
use rustc_middle::ty::util::{AsyncDropGlueMorphology, Discr};
|
||||||
use rustc_middle::ty::{self, Ty, TyCtxt};
|
use rustc_middle::ty::{self, Ty, TyCtxt};
|
||||||
|
@ -345,7 +339,7 @@ impl<'tcx> AsyncDestructorCtorShimBuilder<'tcx> {
|
||||||
.tcx
|
.tcx
|
||||||
.mk_place_elems(&[PlaceElem::Deref, PlaceElem::Field(field, field_ty)]),
|
.mk_place_elems(&[PlaceElem::Deref, PlaceElem::Field(field, field_ty)]),
|
||||||
};
|
};
|
||||||
self.put_temp_rvalue(Rvalue::RawPtr(Mutability::Mut, place))
|
self.put_temp_rvalue(Rvalue::RawPtr(RawPtrKind::Mut, place))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// If given Self is an enum puts `to_drop: *mut FieldTy` on top of
|
/// If given Self is an enum puts `to_drop: *mut FieldTy` on top of
|
||||||
|
@ -365,7 +359,7 @@ impl<'tcx> AsyncDestructorCtorShimBuilder<'tcx> {
|
||||||
PlaceElem::Field(field, field_ty),
|
PlaceElem::Field(field, field_ty),
|
||||||
]),
|
]),
|
||||||
};
|
};
|
||||||
self.put_temp_rvalue(Rvalue::RawPtr(Mutability::Mut, place))
|
self.put_temp_rvalue(Rvalue::RawPtr(RawPtrKind::Mut, place))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// If given Self is an enum puts `to_drop: *mut FieldTy` on top of
|
/// If given Self is an enum puts `to_drop: *mut FieldTy` on top of
|
||||||
|
|
|
@ -232,6 +232,18 @@ impl<'tcx> Stable<'tcx> for mir::Mutability {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<'tcx> Stable<'tcx> for mir::RawPtrKind {
|
||||||
|
type T = stable_mir::mir::RawPtrKind;
|
||||||
|
fn stable(&self, _: &mut Tables<'_>) -> Self::T {
|
||||||
|
use mir::RawPtrKind::*;
|
||||||
|
match *self {
|
||||||
|
Const => stable_mir::mir::RawPtrKind::Const,
|
||||||
|
Mut => stable_mir::mir::RawPtrKind::Mut,
|
||||||
|
FakeForPtrMetadata => stable_mir::mir::RawPtrKind::FakeForPtrMetadata,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl<'tcx> Stable<'tcx> for mir::BorrowKind {
|
impl<'tcx> Stable<'tcx> for mir::BorrowKind {
|
||||||
type T = stable_mir::mir::BorrowKind;
|
type T = stable_mir::mir::BorrowKind;
|
||||||
fn stable(&self, tables: &mut Tables<'_>) -> Self::T {
|
fn stable(&self, tables: &mut Tables<'_>) -> Self::T {
|
||||||
|
|
|
@ -1163,9 +1163,6 @@ pub enum DesugaringKind {
|
||||||
WhileLoop,
|
WhileLoop,
|
||||||
/// `async Fn()` bound modifier
|
/// `async Fn()` bound modifier
|
||||||
BoundModifier,
|
BoundModifier,
|
||||||
/// Marks a `&raw const *_1` needed as part of getting the length of a mutable
|
|
||||||
/// slice for the bounds check, so that MIRI's retag handling can recognize it.
|
|
||||||
IndexBoundsCheckReborrow,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl DesugaringKind {
|
impl DesugaringKind {
|
||||||
|
@ -1182,7 +1179,6 @@ impl DesugaringKind {
|
||||||
DesugaringKind::ForLoop => "`for` loop",
|
DesugaringKind::ForLoop => "`for` loop",
|
||||||
DesugaringKind::WhileLoop => "`while` loop",
|
DesugaringKind::WhileLoop => "`while` loop",
|
||||||
DesugaringKind::BoundModifier => "trait bound modifier",
|
DesugaringKind::BoundModifier => "trait bound modifier",
|
||||||
DesugaringKind::IndexBoundsCheckReborrow => "slice indexing",
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -457,7 +457,7 @@ pub enum Rvalue {
|
||||||
///
|
///
|
||||||
/// This is generated by pointer casts like `&v as *const _` or raw address of expressions like
|
/// This is generated by pointer casts like `&v as *const _` or raw address of expressions like
|
||||||
/// `&raw v` or `addr_of!(v)`.
|
/// `&raw v` or `addr_of!(v)`.
|
||||||
AddressOf(Mutability, Place),
|
AddressOf(RawPtrKind, Place),
|
||||||
|
|
||||||
/// Creates an aggregate value, like a tuple or struct.
|
/// Creates an aggregate value, like a tuple or struct.
|
||||||
///
|
///
|
||||||
|
@ -577,7 +577,7 @@ impl Rvalue {
|
||||||
}
|
}
|
||||||
Rvalue::AddressOf(mutability, place) => {
|
Rvalue::AddressOf(mutability, place) => {
|
||||||
let place_ty = place.ty(locals)?;
|
let place_ty = place.ty(locals)?;
|
||||||
Ok(Ty::new_ptr(place_ty, *mutability))
|
Ok(Ty::new_ptr(place_ty, mutability.to_mutable_lossy()))
|
||||||
}
|
}
|
||||||
Rvalue::Len(..) => Ok(Ty::usize_ty()),
|
Rvalue::Len(..) => Ok(Ty::usize_ty()),
|
||||||
Rvalue::Cast(.., ty) => Ok(*ty),
|
Rvalue::Cast(.., ty) => Ok(*ty),
|
||||||
|
@ -903,6 +903,24 @@ impl BorrowKind {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Copy, Clone, Debug, Eq, PartialEq, Serialize)]
|
||||||
|
pub enum RawPtrKind {
|
||||||
|
Mut,
|
||||||
|
Const,
|
||||||
|
FakeForPtrMetadata,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RawPtrKind {
|
||||||
|
pub fn to_mutable_lossy(self) -> Mutability {
|
||||||
|
match self {
|
||||||
|
RawPtrKind::Mut { .. } => Mutability::Mut,
|
||||||
|
RawPtrKind::Const => Mutability::Not,
|
||||||
|
// FIXME: There's no type corresponding to a shallow borrow, so use `&` as an approximation.
|
||||||
|
RawPtrKind::FakeForPtrMetadata => Mutability::Not,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Copy, Clone, Debug, Eq, PartialEq, Serialize)]
|
#[derive(Copy, Clone, Debug, Eq, PartialEq, Serialize)]
|
||||||
pub enum MutBorrowKind {
|
pub enum MutBorrowKind {
|
||||||
Default,
|
Default,
|
||||||
|
|
|
@ -6,7 +6,9 @@ use std::{fmt, io, iter};
|
||||||
use fmt::{Display, Formatter};
|
use fmt::{Display, Formatter};
|
||||||
|
|
||||||
use super::{AggregateKind, AssertMessage, BinOp, BorrowKind, FakeBorrowKind, TerminatorKind};
|
use super::{AggregateKind, AssertMessage, BinOp, BorrowKind, FakeBorrowKind, TerminatorKind};
|
||||||
use crate::mir::{Operand, Place, Rvalue, StatementKind, UnwindAction, VarDebugInfoContents};
|
use crate::mir::{
|
||||||
|
Operand, Place, RawPtrKind, Rvalue, StatementKind, UnwindAction, VarDebugInfoContents,
|
||||||
|
};
|
||||||
use crate::ty::{AdtKind, IndexedVal, MirConst, Ty, TyConst};
|
use crate::ty::{AdtKind, IndexedVal, MirConst, Ty, TyConst};
|
||||||
use crate::{Body, CrateDef, Mutability, with};
|
use crate::{Body, CrateDef, Mutability, with};
|
||||||
|
|
||||||
|
@ -325,7 +327,7 @@ fn pretty_ty_const(ct: &TyConst) -> String {
|
||||||
fn pretty_rvalue<W: Write>(writer: &mut W, rval: &Rvalue) -> io::Result<()> {
|
fn pretty_rvalue<W: Write>(writer: &mut W, rval: &Rvalue) -> io::Result<()> {
|
||||||
match rval {
|
match rval {
|
||||||
Rvalue::AddressOf(mutability, place) => {
|
Rvalue::AddressOf(mutability, place) => {
|
||||||
write!(writer, "&raw {} {:?}", pretty_mut(*mutability), place)
|
write!(writer, "&raw {} {:?}", pretty_raw_ptr_kind(*mutability), place)
|
||||||
}
|
}
|
||||||
Rvalue::Aggregate(aggregate_kind, operands) => {
|
Rvalue::Aggregate(aggregate_kind, operands) => {
|
||||||
// FIXME: Add pretty_aggregate function that returns a pretty string
|
// FIXME: Add pretty_aggregate function that returns a pretty string
|
||||||
|
@ -437,3 +439,11 @@ fn pretty_mut(mutability: Mutability) -> &'static str {
|
||||||
Mutability::Mut => "mut ",
|
Mutability::Mut => "mut ",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn pretty_raw_ptr_kind(kind: RawPtrKind) -> &'static str {
|
||||||
|
match kind {
|
||||||
|
RawPtrKind::Const => "const",
|
||||||
|
RawPtrKind::Mut => "mut",
|
||||||
|
RawPtrKind::FakeForPtrMetadata => "const (fake)",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -308,7 +308,7 @@ pub trait MirVisitor {
|
||||||
fn super_rvalue(&mut self, rvalue: &Rvalue, location: Location) {
|
fn super_rvalue(&mut self, rvalue: &Rvalue, location: Location) {
|
||||||
match rvalue {
|
match rvalue {
|
||||||
Rvalue::AddressOf(mutability, place) => {
|
Rvalue::AddressOf(mutability, place) => {
|
||||||
let pcx = PlaceContext { is_mut: *mutability == Mutability::Mut };
|
let pcx = PlaceContext { is_mut: *mutability == RawPtrKind::Mut };
|
||||||
self.visit_place(place, pcx, location);
|
self.visit_place(place, pcx, location);
|
||||||
}
|
}
|
||||||
Rvalue::Aggregate(_, operands) => {
|
Rvalue::Aggregate(_, operands) => {
|
||||||
|
|
35
src/tools/miri/tests/pass/disjoint-array-accesses.rs
Normal file
35
src/tools/miri/tests/pass/disjoint-array-accesses.rs
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
// This is a regression test for issue #135671 where a MIR refactor about arrays and their lengths
|
||||||
|
// unexpectedly caused borrowck errors for disjoint borrows of array elements, for which we had no
|
||||||
|
// tests. This is a collection of a few code samples from that issue.
|
||||||
|
|
||||||
|
//@revisions: stack tree
|
||||||
|
//@[tree]compile-flags: -Zmiri-tree-borrows
|
||||||
|
|
||||||
|
struct Test {
|
||||||
|
a: i32,
|
||||||
|
b: i32,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn one() {
|
||||||
|
let inputs: &mut [_] = &mut [Test { a: 0, b: 0 }];
|
||||||
|
let a = &mut inputs[0].a;
|
||||||
|
let b = &mut inputs[0].b;
|
||||||
|
|
||||||
|
*a = 0;
|
||||||
|
*b = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn two() {
|
||||||
|
let slice = &mut [(0, 0)][..];
|
||||||
|
std::mem::swap(&mut slice[0].0, &mut slice[0].1);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn three(a: &mut [(i32, i32)], i: usize, j: usize) -> (&mut i32, &mut i32) {
|
||||||
|
(&mut a[i].0, &mut a[j].1)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
one();
|
||||||
|
two();
|
||||||
|
three(&mut [(1, 2), (3, 4)], 0, 1);
|
||||||
|
}
|
|
@ -14,7 +14,7 @@ fn index_custom(_1: &WithSliceTail, _2: usize) -> &i32 {
|
||||||
StorageLive(_3);
|
StorageLive(_3);
|
||||||
StorageLive(_4);
|
StorageLive(_4);
|
||||||
_4 = copy _2;
|
_4 = copy _2;
|
||||||
_5 = &raw const ((*_1).1: [i32]);
|
_5 = &raw const (fake) ((*_1).1: [i32]);
|
||||||
_6 = PtrMetadata(move _5);
|
_6 = PtrMetadata(move _5);
|
||||||
_7 = Lt(copy _4, copy _6);
|
_7 = Lt(copy _4, copy _6);
|
||||||
assert(move _7, "index out of bounds: the length is {} but the index is {}", move _6, copy _4) -> [success: bb1, unwind: bb2];
|
assert(move _7, "index out of bounds: the length is {} but the index is {}", move _6, copy _4) -> [success: bb1, unwind: bb2];
|
||||||
|
|
|
@ -14,7 +14,7 @@ fn index_mut_slice(_1: &mut [i32], _2: usize) -> &i32 {
|
||||||
StorageLive(_3);
|
StorageLive(_3);
|
||||||
StorageLive(_4);
|
StorageLive(_4);
|
||||||
_4 = copy _2;
|
_4 = copy _2;
|
||||||
_5 = &raw const (*_1);
|
_5 = &raw const (fake) (*_1);
|
||||||
_6 = PtrMetadata(move _5);
|
_6 = PtrMetadata(move _5);
|
||||||
_7 = Lt(copy _4, copy _6);
|
_7 = Lt(copy _4, copy _6);
|
||||||
assert(move _7, "index out of bounds: the length is {} but the index is {}", move _6, copy _4) -> [success: bb1, unwind: bb2];
|
assert(move _7, "index out of bounds: the length is {} but the index is {}", move _6, copy _4) -> [success: bb1, unwind: bb2];
|
||||||
|
|
|
@ -54,7 +54,7 @@ struct WithSliceTail(f64, [i32]);
|
||||||
// EMIT_MIR index_array_and_slice.index_custom.built.after.mir
|
// EMIT_MIR index_array_and_slice.index_custom.built.after.mir
|
||||||
fn index_custom(custom: &WithSliceTail, index: usize) -> &i32 {
|
fn index_custom(custom: &WithSliceTail, index: usize) -> &i32 {
|
||||||
// CHECK: bb0:
|
// CHECK: bb0:
|
||||||
// CHECK: [[PTR:_.+]] = &raw const ((*_1).1: [i32]);
|
// CHECK: [[PTR:_.+]] = &raw const (fake) ((*_1).1: [i32]);
|
||||||
// CHECK: [[LEN:_.+]] = PtrMetadata(move [[PTR]]);
|
// CHECK: [[LEN:_.+]] = PtrMetadata(move [[PTR]]);
|
||||||
// CHECK: [[LT:_.+]] = Lt(copy _2, copy [[LEN]]);
|
// CHECK: [[LT:_.+]] = Lt(copy _2, copy [[LEN]]);
|
||||||
// CHECK: assert(move [[LT]], "index out of bounds{{.+}}", move [[LEN]], copy _2) -> [success: bb1,
|
// CHECK: assert(move [[LT]], "index out of bounds{{.+}}", move [[LEN]], copy _2) -> [success: bb1,
|
||||||
|
|
|
@ -18,7 +18,7 @@ fn foo(_1: Box<[T]>) -> T {
|
||||||
StorageLive(_3);
|
StorageLive(_3);
|
||||||
StorageLive(_4);
|
StorageLive(_4);
|
||||||
_4 = const 0_usize;
|
_4 = const 0_usize;
|
||||||
_5 = &raw const (*_1);
|
_5 = &raw const (fake) (*_1);
|
||||||
_6 = PtrMetadata(move _5);
|
_6 = PtrMetadata(move _5);
|
||||||
_7 = Lt(copy _4, copy _6);
|
_7 = Lt(copy _4, copy _6);
|
||||||
assert(move _7, "index out of bounds: the length is {} but the index is {}", move _6, copy _4) -> [success: bb1, unwind: bb5];
|
assert(move _7, "index out of bounds: the length is {} but the index is {}", move _6, copy _4) -> [success: bb1, unwind: bb5];
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue