diff --git a/src/stacked_borrows.rs b/src/stacked_borrows.rs index 067e3bb8445..6b851cd65b5 100644 --- a/src/stacked_borrows.rs +++ b/src/stacked_borrows.rs @@ -62,18 +62,7 @@ pub enum BorStackItem { /// when there is no `UnsafeCell`. Shr, /// A barrier, tracking the function it belongs to by its index on the call stack - #[allow(dead_code)] // for future use - FnBarrier(usize) -} - -impl BorStackItem { - #[inline(always)] - pub fn is_fn_barrier(self) -> bool { - match self { - BorStackItem::FnBarrier(_) => true, - _ => false, - } - } + FnBarrier(CallId) } /// Extra per-location state @@ -130,6 +119,10 @@ impl BarrierTracking { pub fn end_call(&mut self, id: CallId) { assert!(self.active_calls.remove(&id)); } + + fn is_active(&self, id: CallId) -> bool { + self.active_calls.contains(&id) + } } /// Extra global machine state @@ -178,7 +171,11 @@ impl<'tcx> Stack { /// going to read or write. /// Returns the index of the item we matched, `None` if it was the frozen one. /// `kind` indicates which kind of reference is being dereferenced. - fn deref(&self, bor: Borrow, kind: RefKind) -> Result, String> { + fn deref( + &self, + bor: Borrow, + kind: RefKind, + ) -> Result, String> { // Exclude unique ref with frozen tag. if let (RefKind::Unique, Borrow::Shr(Some(_))) = (kind, bor) { return Err(format!("Encountered mutable reference with frozen tag ({:?})", bor)); @@ -200,7 +197,6 @@ impl<'tcx> Stack { // If we got here, we have to look for our item in the stack. for (idx, &itm) in self.borrows.iter().enumerate().rev() { match (itm, bor) { - (BorStackItem::FnBarrier(_), _) => break, (BorStackItem::Uniq(itm_t), Borrow::Uniq(bor_t)) if itm_t == bor_t => { // Found matching unique item. This satisfies U3. return Ok(Some(idx)) @@ -209,21 +205,25 @@ impl<'tcx> Stack { // Found matching shared/raw item. return Ok(Some(idx)) } - // Go on looking. + // Go on looking. We ignore barriers! When an `&mut` and an `&` alias, + // dereferencing the `&` is still possible (to reborrow), but doing + // an access is not. _ => {} } } // If we got here, we did not find our item. We have to error to satisfy U3. - Err(format!( - "Borrow being dereferenced ({:?}) does not exist on the stack, or is guarded by a barrier", - bor - )) + Err(format!("Borrow being dereferenced ({:?}) does not exist on the stack", bor)) } /// Perform an actual memory access using `bor`. We do not know any types here /// or whether things should be frozen, but we *do* know if this is reading /// or writing. - fn access(&mut self, bor: Borrow, is_write: bool) -> EvalResult<'tcx> { + fn access( + &mut self, + bor: Borrow, + is_write: bool, + barrier_tracking: &BarrierTracking, + ) -> EvalResult<'tcx> { // Check if we can match the frozen "item". // Not possible on writes! if self.is_frozen() { @@ -240,7 +240,12 @@ impl<'tcx> Stack { // Pop the stack until we have something matching. while let Some(&itm) = self.borrows.last() { match (itm, bor) { - (BorStackItem::FnBarrier(_), _) => break, + (BorStackItem::FnBarrier(call), _) if barrier_tracking.is_active(call) => { + return err!(MachineError(format!( + "Stopping looking for borrow being accessed ({:?}) because of barrier ({})", + bor, call + ))) + } (BorStackItem::Uniq(itm_t), Borrow::Uniq(bor_t)) if itm_t == bor_t => { // Found matching unique item. return Ok(()) @@ -265,7 +270,7 @@ impl<'tcx> Stack { } // If we got here, we did not find our item. err!(MachineError(format!( - "Borrow being accessed ({:?}) does not exist on the stack, or is guarded by a barrier", + "Borrow being accessed ({:?}) does not exist on the stack", bor ))) } @@ -275,18 +280,21 @@ impl<'tcx> Stack { /// is met: We cannot push `Uniq` onto frozen stacks. /// `kind` indicates which kind of reference is being created. fn create(&mut self, bor: Borrow, kind: RefKind) { - // First, push the item. We do this even if we will later freeze, because we - // will allow mutation of shared data at the expense of unfreezing. if self.frozen_since.is_some() { - // A frozen location, this should be impossible! - bug!("We should never try pushing to a frozen stack"); + // A frozen location? Possible if we create a barrier, then push again. + assert!(bor.is_shared(), "We should never try creating a unique borrow for a frozen stack"); + trace!("create: Not doing anything on frozen location"); + return; } - // First, push. + // First, push. We do this even if we will later freeze, because we + // will allow mutation of shared data at the expense of unfreezing. let itm = match bor { Borrow::Uniq(t) => BorStackItem::Uniq(t), Borrow::Shr(_) => BorStackItem::Shr, }; if *self.borrows.last().unwrap() == itm { + // This is just an optimization, no functional change: Avoid stacking + // multiple `Shr` on top of each other. assert!(bor.is_shared()); trace!("create: Sharing a shared location is a NOP"); } else { @@ -304,6 +312,21 @@ impl<'tcx> Stack { self.frozen_since = Some(bor_t); } } + + /// Add a barrier + fn barrier(&mut self, call: CallId) { + let itm = BorStackItem::FnBarrier(call); + if *self.borrows.last().unwrap() == itm { + // This is just an optimization, no functional change: Avoid stacking + // multiple identical barriers on top of each other. + // This can happen when a function receives several shared references + // that overlap. + trace!("barrier: Avoiding redundant extra barrier"); + } else { + trace!("barrier: Pushing barrier for call {}", call); + self.borrows.push(itm); + } + } } /// Higher-level per-location operations: deref, access, reborrow. @@ -330,6 +353,7 @@ impl<'tcx> Stacks { ptr: Pointer, size: Size, is_write: bool, + barrier_tracking: &BarrierTracking, ) -> EvalResult<'tcx> { trace!("{} access of tag {:?}: {:?}, size {}", if is_write { "read" } else { "write" }, @@ -339,7 +363,7 @@ impl<'tcx> Stacks { // are no accesses through other references, not even reads. let mut stacks = self.stacks.borrow_mut(); for stack in stacks.iter_mut(ptr.offset, size) { - stack.access(ptr.tag, is_write)?; + stack.access(ptr.tag, is_write, barrier_tracking)?; } Ok(()) } @@ -350,12 +374,19 @@ impl<'tcx> Stacks { &self, ptr: Pointer, size: Size, + mut barrier: Option, new_bor: Borrow, new_kind: RefKind, + barrier_tracking: &BarrierTracking, ) -> EvalResult<'tcx> { assert_eq!(new_bor.is_unique(), new_kind == RefKind::Unique); trace!("reborrow for tag {:?} to {:?} as {:?}: {:?}, size {}", ptr.tag, new_bor, new_kind, ptr, size.bytes()); + if new_kind == RefKind::Raw { + // No barrier for raw, including `&UnsafeCell`. They can rightfully + // alias with `&mut`. + barrier = None; + } let mut stacks = self.stacks.borrow_mut(); for stack in stacks.iter_mut(ptr.offset, size) { // Access source `ptr`, create new ref. @@ -364,21 +395,25 @@ impl<'tcx> Stacks { // the stack than the one we come from, just use that. // IOW, we check if `new_bor` *already* is "derived from" `ptr.tag`. // This also checks frozenness, if required. - let bor_redundant = match (ptr_idx, stack.deref(new_bor, new_kind)) { - // If the new borrow works with the frozen item, or else if it lives - // above the old one in the stack, our job here is done. - (_, Ok(None)) => true, - (Some(ptr_idx), Ok(Some(new_idx))) if new_idx >= ptr_idx => true, - // Otherwise we need to create a new borrow. - _ => false, - }; + let bor_redundant = barrier.is_none() && + match (ptr_idx, stack.deref(new_bor, new_kind)) { + // If the new borrow works with the frozen item, or else if it lives + // above the old one in the stack, our job here is done. + (_, Ok(None)) => true, + (Some(ptr_idx), Ok(Some(new_idx))) if new_idx >= ptr_idx => true, + // Otherwise we need to create a new borrow. + _ => false, + }; if bor_redundant { assert!(new_bor.is_shared(), "A unique reborrow can never be redundant"); trace!("reborrow is redundant"); continue; } // We need to do some actual work. - stack.access(ptr.tag, new_kind == RefKind::Unique)?; + stack.access(ptr.tag, new_kind == RefKind::Unique, barrier_tracking)?; + if let Some(call) = barrier { + stack.barrier(call); + } stack.create(new_bor, new_kind); } Ok(()) @@ -405,7 +440,7 @@ impl AllocationExtra for Stacks { ptr: Pointer, size: Size, ) -> EvalResult<'tcx> { - alloc.extra.access(ptr, size, /*is_write*/false) + alloc.extra.access(ptr, size, /*is_write*/false, &*alloc.extra.barrier_tracking.borrow()) } #[inline(always)] @@ -414,7 +449,7 @@ impl AllocationExtra for Stacks { ptr: Pointer, size: Size, ) -> EvalResult<'tcx> { - alloc.extra.access(ptr, size, /*is_write*/true) + alloc.extra.access(ptr, size, /*is_write*/true, &*alloc.extra.barrier_tracking.borrow()) } #[inline(always)] @@ -424,19 +459,18 @@ impl AllocationExtra for Stacks { size: Size, ) -> EvalResult<'tcx> { // This is like mutating - alloc.extra.access(ptr, size, /*is_write*/true) + alloc.extra.access(ptr, size, /*is_write*/true, &*alloc.extra.barrier_tracking.borrow()) // FIXME: Error out of there are any barriers? } } impl<'tcx> Stacks { /// Pushes the first item to the stacks. - pub fn first_item( + pub(crate) fn first_item( &mut self, itm: BorStackItem, size: Size ) { - assert!(!itm.is_fn_barrier()); for stack in self.stacks.get_mut().iter_mut(Size::ZERO, size) { assert!(stack.borrows.len() == 1); assert_eq!(stack.borrows.pop().unwrap(), BorStackItem::Shr); @@ -466,6 +500,7 @@ pub trait EvalContextExt<'tcx> { &mut self, place: MPlaceTy<'tcx, Borrow>, size: Size, + fn_barrier: bool, new_bor: Borrow ) -> EvalResult<'tcx, Pointer>; @@ -474,6 +509,7 @@ pub trait EvalContextExt<'tcx> { &mut self, ptr: ImmTy<'tcx, Borrow>, mutbl: Mutability, + fn_barrier: bool, ) -> EvalResult<'tcx, Immediate>; fn retag( @@ -566,7 +602,7 @@ impl<'a, 'mir, 'tcx> EvalContextExt<'tcx> for MiriEvalContext<'a, 'mir, 'tcx> { place: MPlaceTy<'tcx, Borrow>, size: Size, ) -> EvalResult<'tcx> { - self.reborrow(place, size, Borrow::default())?; + self.reborrow(place, size, /*fn_barrier*/ false, Borrow::default())?; Ok(()) } @@ -574,10 +610,12 @@ impl<'a, 'mir, 'tcx> EvalContextExt<'tcx> for MiriEvalContext<'a, 'mir, 'tcx> { &mut self, place: MPlaceTy<'tcx, Borrow>, size: Size, + fn_barrier: bool, new_bor: Borrow ) -> EvalResult<'tcx, Pointer> { let ptr = place.ptr.to_ptr()?; let new_ptr = Pointer::new_with_tag(ptr.alloc_id, ptr.offset, new_bor); + let barrier = if fn_barrier { Some(self.frame().extra) } else { None }; trace!("reborrow: Creating new reference for {:?} (pointee {}): {:?}", ptr, place.layout.ty, new_bor); @@ -589,12 +627,12 @@ impl<'a, 'mir, 'tcx> EvalContextExt<'tcx> for MiriEvalContext<'a, 'mir, 'tcx> { // Reference that cares about freezing. We need a frozen-sensitive reborrow. self.visit_freeze_sensitive(place, size, |cur_ptr, size, frozen| { let kind = if frozen { RefKind::Frozen } else { RefKind::Raw }; - alloc.extra.reborrow(cur_ptr, size, new_bor, kind) + alloc.extra.reborrow(cur_ptr, size, barrier, new_bor, kind, &*self.memory().extra.borrow()) })?; } else { // Just treat this as one big chunk. let kind = if new_bor.is_unique() { RefKind::Unique } else { RefKind::Raw }; - alloc.extra.reborrow(ptr, size, new_bor, kind)?; + alloc.extra.reborrow(ptr, size, barrier, new_bor, kind, &*self.memory().extra.borrow())?; } Ok(new_ptr) } @@ -603,6 +641,7 @@ impl<'a, 'mir, 'tcx> EvalContextExt<'tcx> for MiriEvalContext<'a, 'mir, 'tcx> { &mut self, val: ImmTy<'tcx, Borrow>, mutbl: Mutability, + fn_barrier: bool, ) -> EvalResult<'tcx, Immediate> { // We want a place for where the ptr *points to*, so we get one. let place = self.ref_to_mplace(val)?; @@ -622,7 +661,7 @@ impl<'a, 'mir, 'tcx> EvalContextExt<'tcx> for MiriEvalContext<'a, 'mir, 'tcx> { }; // Reborrow. - let new_ptr = self.reborrow(place, size, new_bor)?; + let new_ptr = self.reborrow(place, size, fn_barrier, new_bor)?; // Return new ptr let new_place = MemPlace { ptr: Scalar::Ptr(new_ptr), ..*place }; @@ -631,11 +670,9 @@ impl<'a, 'mir, 'tcx> EvalContextExt<'tcx> for MiriEvalContext<'a, 'mir, 'tcx> { fn retag( &mut self, - _fn_entry: bool, + fn_entry: bool, place: PlaceTy<'tcx, Borrow> ) -> EvalResult<'tcx> { - // TODO: Honor `fn_entry`. - // We need a visitor to visit all references. However, that requires // a `MemPlace`, so we have a fast path for reference types that // avoids allocating. @@ -648,18 +685,19 @@ impl<'a, 'mir, 'tcx> EvalContextExt<'tcx> for MiriEvalContext<'a, 'mir, 'tcx> { } { // fast path let val = self.read_immediate(self.place_to_op(place)?)?; - let val = self.retag_reference(val, mutbl)?; + let val = self.retag_reference(val, mutbl, fn_entry)?; self.write_immediate(val, place)?; return Ok(()); } let place = self.force_allocation(place)?; - let mut visitor = RetagVisitor { ecx: self }; + let mut visitor = RetagVisitor { ecx: self, fn_entry }; visitor.visit_value(place)?; // The actual visitor struct RetagVisitor<'ecx, 'a, 'mir, 'tcx> { ecx: &'ecx mut MiriEvalContext<'a, 'mir, 'tcx>, + fn_entry: bool, } impl<'ecx, 'a, 'mir, 'tcx> MutValueVisitor<'a, 'mir, 'tcx, Evaluator<'tcx>> @@ -684,7 +722,7 @@ impl<'a, 'mir, 'tcx> EvalContextExt<'tcx> for MiriEvalContext<'a, 'mir, 'tcx> { _ => return Ok(()), // nothing to do }; let val = self.ecx.read_immediate(place.into())?; - let val = self.ecx.retag_reference(val, mutbl)?; + let val = self.ecx.retag_reference(val, mutbl, self.fn_entry)?; self.ecx.write_immediate(val, place.into())?; Ok(()) } diff --git a/tests/compile-fail-fullmir/stacked_borrows/aliasing_mut1.rs b/tests/compile-fail-fullmir/stacked_borrows/aliasing_mut1.rs index e812e13e702..b82901985b7 100644 --- a/tests/compile-fail-fullmir/stacked_borrows/aliasing_mut1.rs +++ b/tests/compile-fail-fullmir/stacked_borrows/aliasing_mut1.rs @@ -1,12 +1,17 @@ -// ignore-test validation_op is disabled - #![allow(unused_variables)] -mod safe { - pub fn safe(x: &mut i32, y: &mut i32) {} //~ ERROR: in conflict with lock WriteLock -} +use std::mem; + +pub fn safe(x: &mut i32, y: &mut i32) {} //~ ERROR barrier fn main() { - let x = &mut 0 as *mut _; - unsafe { safe::safe(&mut *x, &mut *x) }; + let mut x = 0; + let xraw: *mut i32 = unsafe { mem::transmute(&mut x) }; + // We need to apply some tricky to be able to call `safe` with two mutable references + // with the same tag: We transmute both the fn ptr (to take raw ptrs) and the argument + // (to be raw, but still have the unique tag). + let safe_raw: fn(x: *mut i32, y: *mut i32) = unsafe { + mem::transmute::(safe) + }; + safe_raw(xraw, xraw); } diff --git a/tests/compile-fail-fullmir/stacked_borrows/aliasing_mut2.rs b/tests/compile-fail-fullmir/stacked_borrows/aliasing_mut2.rs index 36ebcc2b4ac..69caddfa8c3 100644 --- a/tests/compile-fail-fullmir/stacked_borrows/aliasing_mut2.rs +++ b/tests/compile-fail-fullmir/stacked_borrows/aliasing_mut2.rs @@ -1,12 +1,17 @@ -// ignore-test validation_op is disabled - #![allow(unused_variables)] -mod safe { - pub fn safe(x: &i32, y: &mut i32) {} //~ ERROR: in conflict with lock ReadLock -} +use std::mem; + +pub fn safe(x: &i32, y: &mut i32) {} //~ ERROR barrier fn main() { - let x = &mut 0 as *mut _; - unsafe { safe::safe(&*x, &mut *x) }; + let mut x = 0; + let xref = &mut x; + let xraw: *mut i32 = unsafe { mem::transmute_copy(&xref) }; + let xshr = &*xref; + // transmute fn ptr around so that we can avoid retagging + let safe_raw: fn(x: *const i32, y: *mut i32) = unsafe { + mem::transmute::(safe) + }; + safe_raw(xshr, xraw); } diff --git a/tests/compile-fail-fullmir/stacked_borrows/aliasing_mut3.rs b/tests/compile-fail-fullmir/stacked_borrows/aliasing_mut3.rs index ad50fbd61b4..d37f9e63f60 100644 --- a/tests/compile-fail-fullmir/stacked_borrows/aliasing_mut3.rs +++ b/tests/compile-fail-fullmir/stacked_borrows/aliasing_mut3.rs @@ -1,12 +1,17 @@ -// ignore-test validation_op is disabled - #![allow(unused_variables)] -mod safe { - pub fn safe(x: &mut i32, y: &i32) {} //~ ERROR: in conflict with lock WriteLock -} +use std::mem; + +pub fn safe(x: &mut i32, y: &i32) {} //~ ERROR does not exist on the stack fn main() { - let x = &mut 0 as *mut _; - unsafe { safe::safe(&mut *x, &*x) }; + let mut x = 0; + let xref = &mut x; + let xraw: *mut i32 = unsafe { mem::transmute_copy(&xref) }; + let xshr = &*xref; + // transmute fn ptr around so that we can avoid retagging + let safe_raw: fn(x: *mut i32, y: *const i32) = unsafe { + mem::transmute::(safe) + }; + safe_raw(xraw, xshr); } diff --git a/tests/compile-fail-fullmir/stacked_borrows/aliasing_mut4.rs b/tests/compile-fail-fullmir/stacked_borrows/aliasing_mut4.rs index a0f0a3cf975..bf65d6e2303 100644 --- a/tests/compile-fail-fullmir/stacked_borrows/aliasing_mut4.rs +++ b/tests/compile-fail-fullmir/stacked_borrows/aliasing_mut4.rs @@ -1,15 +1,19 @@ -// ignore-test validation_op is disabled - #![allow(unused_variables)] -mod safe { - use std::cell::Cell; +use std::mem; +use std::cell::Cell; - // Make sure &mut UnsafeCell also has a lock to it - pub fn safe(x: &mut Cell, y: &i32) {} //~ ERROR: in conflict with lock WriteLock -} +// Make sure &mut UnsafeCell also is exclusive +pub fn safe(x: &i32, y: &mut Cell) {} //~ ERROR barrier fn main() { - let x = &mut 0 as *mut _; - unsafe { safe::safe(&mut *(x as *mut _), &*x) }; + let mut x = 0; + let xref = &mut x; + let xraw: *mut i32 = unsafe { mem::transmute_copy(&xref) }; + let xshr = &*xref; + // transmute fn ptr around so that we can avoid retagging + let safe_raw: fn(x: *const i32, y: *mut Cell) = unsafe { + mem::transmute::), _>(safe) + }; + safe_raw(xshr, xraw as *mut _); } diff --git a/tests/compile-fail-fullmir/stacked_borrows/box_exclusive_violation1.rs b/tests/compile-fail-fullmir/stacked_borrows/box_exclusive_violation1.rs index bd0fec859d8..e8a182779ad 100644 --- a/tests/compile-fail-fullmir/stacked_borrows/box_exclusive_violation1.rs +++ b/tests/compile-fail-fullmir/stacked_borrows/box_exclusive_violation1.rs @@ -8,7 +8,7 @@ fn demo_mut_advanced_unique(mut our: Box) -> i32 { unknown_code_2(); // We know this will return 5 - *our //~ ERROR does not exist on the stack + *our } // Now comes the evil context @@ -21,7 +21,7 @@ fn unknown_code_1(x: &i32) { unsafe { } } fn unknown_code_2() { unsafe { - *LEAK = 7; + *LEAK = 7; //~ ERROR barrier } } fn main() { diff --git a/tests/compile-fail-fullmir/stacked_borrows/illegal_write1.rs b/tests/compile-fail-fullmir/stacked_borrows/illegal_write1.rs index b106cc8dc40..d0a23cb4448 100644 --- a/tests/compile-fail-fullmir/stacked_borrows/illegal_write1.rs +++ b/tests/compile-fail-fullmir/stacked_borrows/illegal_write1.rs @@ -1,12 +1,9 @@ -fn evil(x: &u32) { - // mutating shared ref without `UnsafeCell` - let x : *mut u32 = x as *const _ as *mut _; - unsafe { *x = 42; } -} - fn main() { let target = Box::new(42); // has an implicit raw - let ref_ = &*target; - evil(ref_); // invalidates shared ref, activates raw - let _x = *ref_; //~ ERROR is not frozen + let xref = &*target; + { + let x : *mut u32 = xref as *const _ as *mut _; + unsafe { *x = 42; } // invalidates shared ref, activates raw + } + let _x = *xref; //~ ERROR is not frozen } diff --git a/tests/compile-fail-fullmir/stacked_borrows/invalidate_against_barrier1.rs b/tests/compile-fail-fullmir/stacked_borrows/invalidate_against_barrier1.rs new file mode 100644 index 00000000000..fc0dbb9e131 --- /dev/null +++ b/tests/compile-fail-fullmir/stacked_borrows/invalidate_against_barrier1.rs @@ -0,0 +1,13 @@ +fn inner(x: *mut i32, _y: &mut i32) { + // If `x` and `y` alias, retagging is fine with this... but we really + // shouldn't be allowed to use `x` at all because `y` was assumed to be + // unique for the duration of this call. + let _val = unsafe { *x }; //~ ERROR barrier +} + +fn main() { + let mut x = 0; + let xraw = &mut x as *mut _; + let xref = unsafe { &mut *xraw }; + inner(xraw, xref); +} diff --git a/tests/compile-fail-fullmir/stacked_borrows/invalidate_against_barrier2.rs b/tests/compile-fail-fullmir/stacked_borrows/invalidate_against_barrier2.rs new file mode 100644 index 00000000000..a080c0958e4 --- /dev/null +++ b/tests/compile-fail-fullmir/stacked_borrows/invalidate_against_barrier2.rs @@ -0,0 +1,13 @@ +fn inner(x: *mut i32, _y: &i32) { + // If `x` and `y` alias, retagging is fine with this... but we really + // shouldn't be allowed to write to `x` at all because `y` was assumed to be + // immutable for the duration of this call. + unsafe { *x = 0 }; //~ ERROR barrier +} + +fn main() { + let mut x = 0; + let xraw = &mut x as *mut _; + let xref = unsafe { &*xraw }; + inner(xraw, xref); +} diff --git a/tests/compile-fail-fullmir/stacked_borrows/mut_exclusive_violation1.rs b/tests/compile-fail-fullmir/stacked_borrows/mut_exclusive_violation1.rs index fec699e35bc..3fe6b656742 100644 --- a/tests/compile-fail-fullmir/stacked_borrows/mut_exclusive_violation1.rs +++ b/tests/compile-fail-fullmir/stacked_borrows/mut_exclusive_violation1.rs @@ -21,7 +21,7 @@ fn unknown_code_1(x: &i32) { unsafe { } } fn unknown_code_2() { unsafe { - *LEAK = 7; //~ ERROR does not exist on the stack + *LEAK = 7; //~ ERROR barrier } } fn main() {