1 use smallvec::SmallVec;
4 use rustc_middle::mir::interpret::{alloc_range, AllocId, AllocRange};
5 use rustc_span::{Span, SpanData};
6 use rustc_target::abi::Size;
8 use crate::stacked_borrows::{
9 err_sb_ub, AccessKind, GlobalStateInner, Permission, ProtectorKind, Stack,
13 use rustc_middle::mir::interpret::InterpError;
15 #[derive(Clone, Debug)]
16 pub struct AllocHistory {
19 creations: smallvec::SmallVec<[Creation; 1]>,
20 invalidations: smallvec::SmallVec<[Invalidation; 1]>,
21 protectors: smallvec::SmallVec<[Protection; 1]>,
24 #[derive(Clone, Debug)]
31 fn generate_diagnostic(&self) -> (String, SpanData) {
32 let tag = self.retag.new_tag;
33 if let Some(perm) = self.retag.permission {
36 "{tag:?} was created by a {:?} retag at offsets {:?}",
37 perm, self.retag.range,
42 assert!(self.retag.range.size == Size::ZERO);
45 "{tag:?} would have been created here, but this is a zero-size retag ({:?}) so the tag in question does not exist anywhere",
54 #[derive(Clone, Debug)]
59 cause: InvalidationCause,
62 #[derive(Clone, Debug)]
63 enum InvalidationCause {
65 Retag(Permission, RetagCause),
69 fn generate_diagnostic(&self) -> (String, SpanData) {
70 let message = if let InvalidationCause::Retag(_, RetagCause::FnEntry) = self.cause {
71 // For a FnEntry retag, our Span points at the caller.
72 // See `DiagnosticCx::log_invalidation`.
74 "{:?} was later invalidated at offsets {:?} by a {} inside this call",
75 self.tag, self.range, self.cause
79 "{:?} was later invalidated at offsets {:?} by a {}",
80 self.tag, self.range, self.cause
83 (message, self.span.data())
87 impl fmt::Display for InvalidationCause {
88 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
90 InvalidationCause::Access(kind) => write!(f, "{kind}"),
91 InvalidationCause::Retag(perm, kind) =>
92 if *kind == RetagCause::FnEntry {
93 write!(f, "{perm:?} FnEntry retag")
95 write!(f, "{perm:?} retag")
101 #[derive(Clone, Debug)]
108 pub struct TagHistory {
109 pub created: (String, SpanData),
110 pub invalidated: Option<(String, SpanData)>,
111 pub protected: Option<(String, SpanData)>,
114 pub struct DiagnosticCxBuilder<'ecx, 'mir, 'tcx> {
115 operation: Operation,
116 machine: &'ecx MiriMachine<'mir, 'tcx>,
119 pub struct DiagnosticCx<'history, 'ecx, 'mir, 'tcx> {
120 operation: Operation,
121 machine: &'ecx MiriMachine<'mir, 'tcx>,
122 history: &'history mut AllocHistory,
126 impl<'ecx, 'mir, 'tcx> DiagnosticCxBuilder<'ecx, 'mir, 'tcx> {
127 pub fn build<'history>(
129 history: &'history mut AllocHistory,
131 ) -> DiagnosticCx<'history, 'ecx, 'mir, 'tcx> {
132 DiagnosticCx { operation: self.operation, machine: self.machine, history, offset }
136 machine: &'ecx MiriMachine<'mir, 'tcx>,
139 orig_tag: ProvenanceExtra,
143 Operation::Retag(RetagOp { cause, new_tag, orig_tag, range, permission: None });
145 DiagnosticCxBuilder { machine, operation }
149 machine: &'ecx MiriMachine<'mir, 'tcx>,
150 tag: ProvenanceExtra,
153 let operation = Operation::Access(AccessOp { kind: AccessKind::Read, tag, range });
154 DiagnosticCxBuilder { machine, operation }
158 machine: &'ecx MiriMachine<'mir, 'tcx>,
159 tag: ProvenanceExtra,
162 let operation = Operation::Access(AccessOp { kind: AccessKind::Write, tag, range });
163 DiagnosticCxBuilder { machine, operation }
166 pub fn dealloc(machine: &'ecx MiriMachine<'mir, 'tcx>, tag: ProvenanceExtra) -> Self {
167 let operation = Operation::Dealloc(DeallocOp { tag });
168 DiagnosticCxBuilder { machine, operation }
172 impl<'history, 'ecx, 'mir, 'tcx> DiagnosticCx<'history, 'ecx, 'mir, 'tcx> {
173 pub fn unbuild(self) -> DiagnosticCxBuilder<'ecx, 'mir, 'tcx> {
174 DiagnosticCxBuilder { machine: self.machine, operation: self.operation }
178 #[derive(Debug, Clone)]
185 #[derive(Debug, Clone)]
189 orig_tag: ProvenanceExtra,
191 permission: Option<Permission>,
194 #[derive(Debug, Clone, Copy, PartialEq)]
195 pub enum RetagCause {
202 #[derive(Debug, Clone)]
205 tag: ProvenanceExtra,
209 #[derive(Debug, Clone)]
211 tag: ProvenanceExtra,
215 pub fn new(id: AllocId, item: Item, machine: &MiriMachine<'_, '_>) -> Self {
218 base: (item, machine.current_span()),
219 creations: SmallVec::new(),
220 invalidations: SmallVec::new(),
221 protectors: SmallVec::new(),
226 impl<'history, 'ecx, 'mir, 'tcx> DiagnosticCx<'history, 'ecx, 'mir, 'tcx> {
227 pub fn start_grant(&mut self, perm: Permission) {
228 let Operation::Retag(op) = &mut self.operation else {
229 unreachable!("start_grant must only be called during a retag, this is: {:?}", self.operation)
231 op.permission = Some(perm);
233 let last_creation = &mut self.history.creations.last_mut().unwrap();
234 match last_creation.retag.permission {
236 last_creation.retag.permission = Some(perm);
239 if previous != perm {
240 // 'Split up' the creation event.
241 let previous_range = last_creation.retag.range;
242 last_creation.retag.range = alloc_range(previous_range.start, self.offset);
243 let mut new_event = last_creation.clone();
244 new_event.retag.range = alloc_range(self.offset, previous_range.end());
245 new_event.retag.permission = Some(perm);
246 self.history.creations.push(new_event);
251 pub fn log_creation(&mut self) {
252 let Operation::Retag(op) = &self.operation else {
253 unreachable!("log_creation must only be called during a retag")
257 .push(Creation { retag: op.clone(), span: self.machine.current_span() });
260 pub fn log_invalidation(&mut self, tag: SbTag) {
261 let mut span = self.machine.current_span();
262 let (range, cause) = match &self.operation {
263 Operation::Retag(RetagOp { cause, range, permission, .. }) => {
264 if *cause == RetagCause::FnEntry {
265 span = self.machine.caller_span();
267 (*range, InvalidationCause::Retag(permission.unwrap(), *cause))
269 Operation::Access(AccessOp { kind, range, .. }) =>
270 (*range, InvalidationCause::Access(*kind)),
271 Operation::Dealloc(_) => {
272 // This can be reached, but never be relevant later since the entire allocation is
277 self.history.invalidations.push(Invalidation { tag, range, span, cause });
280 pub fn log_protector(&mut self) {
281 let Operation::Retag(op) = &self.operation else {
282 unreachable!("Protectors can only be created during a retag")
286 .push(Protection { tag: op.new_tag, span: self.machine.current_span() });
289 pub fn get_logs_relevant_to(
292 protector_tag: Option<SbTag>,
293 ) -> Option<TagHistory> {
294 let Some(created) = self.history
299 // First, look for a Creation event where the tag and the offset matches. This
300 // ensrues that we pick the right Creation event when a retag isn't uniform due to
302 let range = event.retag.range;
303 if event.retag.new_tag == tag
304 && self.offset >= range.start
305 && self.offset < (range.start + range.size)
307 Some(event.generate_diagnostic())
313 // If we didn't find anything with a matching offset, just return the event where
314 // the tag was created. This branch is hit when we use a tag at an offset that
315 // doesn't have the tag.
316 self.history.creations.iter().rev().find_map(|event| {
317 if event.retag.new_tag == tag {
318 Some(event.generate_diagnostic())
324 // If we didn't find a retag that created this tag, it might be the base tag of
326 if self.history.base.0.tag() == tag {
328 format!("{tag:?} was created here, as the base tag for {:?}", self.history.id),
329 self.history.base.1.data()
335 // But if we don't have a creation event, this is related to a wildcard, and there
336 // is really nothing we can do to help.
340 let invalidated = self.history.invalidations.iter().rev().find_map(|event| {
341 if event.tag == tag { Some(event.generate_diagnostic()) } else { None }
344 let protected = protector_tag
345 .and_then(|protector| {
346 self.history.protectors.iter().find(|protection| protection.tag == protector)
349 let protected_tag = protection.tag;
350 (format!("{protected_tag:?} is this argument"), protection.span.data())
353 Some(TagHistory { created, invalidated, protected })
356 /// Report a descriptive error when `new` could not be granted from `derived_from`.
357 #[inline(never)] // This is only called on fatal code paths
358 pub(super) fn grant_error(&self, stack: &Stack) -> InterpError<'tcx> {
359 let Operation::Retag(op) = &self.operation else {
360 unreachable!("grant_error should only be called during a retag")
363 op.permission.expect("`start_grant` must be called before calling `grant_error`");
364 let action = format!(
365 "trying to retag from {:?} for {:?} permission at {:?}[{:#x}]",
372 format!("{action}{}", error_cause(stack, op.orig_tag)),
373 Some(operation_summary(&op.cause.summary(), self.history.id, op.range)),
374 op.orig_tag.and_then(|orig_tag| self.get_logs_relevant_to(orig_tag, None)),
378 /// Report a descriptive error when `access` is not permitted based on `tag`.
379 #[inline(never)] // This is only called on fatal code paths
380 pub(super) fn access_error(&self, stack: &Stack) -> InterpError<'tcx> {
381 // Deallocation and retagging also do an access as part of their thing, so handle that here, too.
382 let op = match &self.operation {
383 Operation::Access(op) => op,
384 Operation::Retag(_) => return self.grant_error(stack),
385 Operation::Dealloc(_) => return self.dealloc_error(stack),
387 let action = format!(
388 "attempting a {access} using {tag:?} at {alloc_id:?}[{offset:#x}]",
391 alloc_id = self.history.id,
392 offset = self.offset.bytes(),
395 format!("{action}{}", error_cause(stack, op.tag)),
396 Some(operation_summary("an access", self.history.id, op.range)),
397 op.tag.and_then(|tag| self.get_logs_relevant_to(tag, None)),
401 #[inline(never)] // This is only called on fatal code paths
402 pub(super) fn protector_error(&self, item: &Item, kind: ProtectorKind) -> InterpError<'tcx> {
403 let protected = match kind {
404 ProtectorKind::WeakProtector => "weakly protected",
405 ProtectorKind::StrongProtector => "strongly protected",
413 frame.extra.stacked_borrows.as_ref().expect("we should have Stacked Borrows data")
415 .find(|frame| frame.protected_tags.contains(&item.tag()))
416 .map(|frame| frame.call_id)
417 .unwrap(); // FIXME: Surely we should find something, but a panic seems wrong here?
418 match self.operation {
419 Operation::Dealloc(_) =>
421 format!("deallocating while item {item:?} is {protected} by call {call_id:?}",),
425 Operation::Retag(RetagOp { orig_tag: tag, .. })
426 | Operation::Access(AccessOp { tag, .. }) =>
429 "not granting access to tag {tag:?} because that would remove {item:?} which is {protected} because it is an argument of call {call_id:?}",
432 tag.and_then(|tag| self.get_logs_relevant_to(tag, Some(item.tag()))),
437 #[inline(never)] // This is only called on fatal code paths
438 pub fn dealloc_error(&self, stack: &Stack) -> InterpError<'tcx> {
439 let Operation::Dealloc(op) = &self.operation else {
440 unreachable!("dealloc_error should only be called during a deallocation")
444 "attempting deallocation using {tag:?} at {alloc_id:?}{cause}",
446 alloc_id = self.history.id,
447 cause = error_cause(stack, op.tag),
450 op.tag.and_then(|tag| self.get_logs_relevant_to(tag, None)),
455 pub fn check_tracked_tag_popped(&self, item: &Item, global: &GlobalStateInner) {
456 if !global.tracked_pointer_tags.contains(&item.tag()) {
459 let summary = match self.operation {
460 Operation::Dealloc(_) => None,
461 Operation::Access(AccessOp { kind, tag, .. }) => Some((tag, kind)),
462 Operation::Retag(RetagOp { orig_tag, permission, .. }) => {
463 let kind = match permission
464 .expect("start_grant should set the current permission before popping a tag")
466 Permission::SharedReadOnly => AccessKind::Read,
467 Permission::Unique => AccessKind::Write,
468 Permission::SharedReadWrite | Permission::Disabled => {
469 panic!("Only SharedReadOnly and Unique retags can pop tags");
472 Some((orig_tag, kind))
475 self.machine.emit_diagnostic(NonHaltingDiagnostic::PoppedPointerTag(*item, summary));
479 fn operation_summary(operation: &str, alloc_id: AllocId, alloc_range: AllocRange) -> String {
480 format!("this error occurs as part of {operation} at {alloc_id:?}{alloc_range:?}")
483 fn error_cause(stack: &Stack, prov_extra: ProvenanceExtra) -> &'static str {
484 if let ProvenanceExtra::Concrete(tag) = prov_extra {
486 .map(|i| stack.get(i).unwrap())
487 .any(|item| item.tag() == tag && item.perm() != Permission::Disabled)
489 ", but that tag only grants SharedReadOnly permission for this location"
491 ", but that tag does not exist in the borrow stack for this location"
494 ", but no exposed tags have suitable permission in the borrow stack for this location"
499 fn summary(&self) -> String {
501 RetagCause::Normal => "retag",
502 RetagCause::FnEntry => "FnEntry retag",
503 RetagCause::FnReturn => "FnReturn retag",
504 RetagCause::TwoPhase => "two-phase retag",