1 use smallvec::SmallVec;
4 use rustc_middle::mir::interpret::{alloc_range, AllocId, AllocRange, InterpError};
5 use rustc_span::{Span, SpanData};
6 use rustc_target::abi::Size;
8 use crate::borrow_tracker::{
9 stacked_borrows::{err_sb_ub, Permission},
10 AccessKind, GlobalStateInner, ProtectorKind,
14 #[derive(Clone, Debug)]
15 pub struct AllocHistory {
18 creations: smallvec::SmallVec<[Creation; 1]>,
19 invalidations: smallvec::SmallVec<[Invalidation; 1]>,
20 protectors: smallvec::SmallVec<[Protection; 1]>,
23 #[derive(Clone, Debug)]
30 fn generate_diagnostic(&self) -> (String, SpanData) {
31 let tag = self.retag.new_tag;
32 if let Some(perm) = self.retag.permission {
35 "{tag:?} was created by a {:?} retag at offsets {:?}",
36 perm, self.retag.range,
41 assert!(self.retag.range.size == Size::ZERO);
44 "{tag:?} would have been created here, but this is a zero-size retag ({:?}) so the tag in question does not exist anywhere",
53 #[derive(Clone, Debug)]
58 cause: InvalidationCause,
61 #[derive(Clone, Debug)]
62 enum InvalidationCause {
64 Retag(Permission, RetagCause),
68 fn generate_diagnostic(&self) -> (String, SpanData) {
69 let message = if let InvalidationCause::Retag(_, RetagCause::FnEntry) = self.cause {
70 // For a FnEntry retag, our Span points at the caller.
71 // See `DiagnosticCx::log_invalidation`.
73 "{:?} was later invalidated at offsets {:?} by a {} inside this call",
74 self.tag, self.range, self.cause
78 "{:?} was later invalidated at offsets {:?} by a {}",
79 self.tag, self.range, self.cause
82 (message, self.span.data())
86 impl fmt::Display for InvalidationCause {
87 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
89 InvalidationCause::Access(kind) => write!(f, "{kind}"),
90 InvalidationCause::Retag(perm, kind) =>
91 if *kind == RetagCause::FnEntry {
92 write!(f, "{perm:?} FnEntry retag")
94 write!(f, "{perm:?} retag")
100 #[derive(Clone, Debug)]
107 pub struct TagHistory {
108 pub created: (String, SpanData),
109 pub invalidated: Option<(String, SpanData)>,
110 pub protected: Option<(String, SpanData)>,
113 pub struct DiagnosticCxBuilder<'ecx, 'mir, 'tcx> {
114 operation: Operation,
115 machine: &'ecx MiriMachine<'mir, 'tcx>,
118 pub struct DiagnosticCx<'history, 'ecx, 'mir, 'tcx> {
119 operation: Operation,
120 machine: &'ecx MiriMachine<'mir, 'tcx>,
121 history: &'history mut AllocHistory,
125 impl<'ecx, 'mir, 'tcx> DiagnosticCxBuilder<'ecx, 'mir, 'tcx> {
126 pub fn build<'history>(
128 history: &'history mut AllocHistory,
130 ) -> DiagnosticCx<'history, 'ecx, 'mir, 'tcx> {
131 DiagnosticCx { operation: self.operation, machine: self.machine, history, offset }
135 machine: &'ecx MiriMachine<'mir, 'tcx>,
138 orig_tag: ProvenanceExtra,
142 Operation::Retag(RetagOp { cause, new_tag, orig_tag, range, permission: None });
144 DiagnosticCxBuilder { machine, operation }
148 machine: &'ecx MiriMachine<'mir, 'tcx>,
149 tag: ProvenanceExtra,
152 let operation = Operation::Access(AccessOp { kind: AccessKind::Read, tag, range });
153 DiagnosticCxBuilder { machine, operation }
157 machine: &'ecx MiriMachine<'mir, 'tcx>,
158 tag: ProvenanceExtra,
161 let operation = Operation::Access(AccessOp { kind: AccessKind::Write, tag, range });
162 DiagnosticCxBuilder { machine, operation }
165 pub fn dealloc(machine: &'ecx MiriMachine<'mir, 'tcx>, tag: ProvenanceExtra) -> Self {
166 let operation = Operation::Dealloc(DeallocOp { tag });
167 DiagnosticCxBuilder { machine, operation }
171 impl<'history, 'ecx, 'mir, 'tcx> DiagnosticCx<'history, 'ecx, 'mir, 'tcx> {
172 pub fn unbuild(self) -> DiagnosticCxBuilder<'ecx, 'mir, 'tcx> {
173 DiagnosticCxBuilder { machine: self.machine, operation: self.operation }
177 #[derive(Debug, Clone)]
184 #[derive(Debug, Clone)]
188 orig_tag: ProvenanceExtra,
190 permission: Option<Permission>,
193 #[derive(Debug, Clone, Copy, PartialEq)]
194 pub enum RetagCause {
201 #[derive(Debug, Clone)]
204 tag: ProvenanceExtra,
208 #[derive(Debug, Clone)]
210 tag: ProvenanceExtra,
214 pub fn new(id: AllocId, item: Item, machine: &MiriMachine<'_, '_>) -> Self {
217 base: (item, machine.current_span()),
218 creations: SmallVec::new(),
219 invalidations: SmallVec::new(),
220 protectors: SmallVec::new(),
225 impl<'history, 'ecx, 'mir, 'tcx> DiagnosticCx<'history, 'ecx, 'mir, 'tcx> {
226 pub fn start_grant(&mut self, perm: Permission) {
227 let Operation::Retag(op) = &mut self.operation else {
228 unreachable!("start_grant must only be called during a retag, this is: {:?}", self.operation)
230 op.permission = Some(perm);
232 let last_creation = &mut self.history.creations.last_mut().unwrap();
233 match last_creation.retag.permission {
235 last_creation.retag.permission = Some(perm);
238 if previous != perm {
239 // 'Split up' the creation event.
240 let previous_range = last_creation.retag.range;
241 last_creation.retag.range = alloc_range(previous_range.start, self.offset);
242 let mut new_event = last_creation.clone();
243 new_event.retag.range = alloc_range(self.offset, previous_range.end());
244 new_event.retag.permission = Some(perm);
245 self.history.creations.push(new_event);
250 pub fn log_creation(&mut self) {
251 let Operation::Retag(op) = &self.operation else {
252 unreachable!("log_creation must only be called during a retag")
256 .push(Creation { retag: op.clone(), span: self.machine.current_span() });
259 pub fn log_invalidation(&mut self, tag: BorTag) {
260 let mut span = self.machine.current_span();
261 let (range, cause) = match &self.operation {
262 Operation::Retag(RetagOp { cause, range, permission, .. }) => {
263 if *cause == RetagCause::FnEntry {
264 span = self.machine.caller_span();
266 (*range, InvalidationCause::Retag(permission.unwrap(), *cause))
268 Operation::Access(AccessOp { kind, range, .. }) =>
269 (*range, InvalidationCause::Access(*kind)),
270 Operation::Dealloc(_) => {
271 // This can be reached, but never be relevant later since the entire allocation is
276 self.history.invalidations.push(Invalidation { tag, range, span, cause });
279 pub fn log_protector(&mut self) {
280 let Operation::Retag(op) = &self.operation else {
281 unreachable!("Protectors can only be created during a retag")
285 .push(Protection { tag: op.new_tag, span: self.machine.current_span() });
288 pub fn get_logs_relevant_to(
291 protector_tag: Option<BorTag>,
292 ) -> Option<TagHistory> {
293 let Some(created) = self.history
298 // First, look for a Creation event where the tag and the offset matches. This
299 // ensrues that we pick the right Creation event when a retag isn't uniform due to
301 let range = event.retag.range;
302 if event.retag.new_tag == tag
303 && self.offset >= range.start
304 && self.offset < (range.start + range.size)
306 Some(event.generate_diagnostic())
312 // If we didn't find anything with a matching offset, just return the event where
313 // the tag was created. This branch is hit when we use a tag at an offset that
314 // doesn't have the tag.
315 self.history.creations.iter().rev().find_map(|event| {
316 if event.retag.new_tag == tag {
317 Some(event.generate_diagnostic())
323 // If we didn't find a retag that created this tag, it might be the base tag of
325 if self.history.base.0.tag() == tag {
327 format!("{tag:?} was created here, as the base tag for {:?}", self.history.id),
328 self.history.base.1.data()
334 // But if we don't have a creation event, this is related to a wildcard, and there
335 // is really nothing we can do to help.
339 let invalidated = self.history.invalidations.iter().rev().find_map(|event| {
340 if event.tag == tag { Some(event.generate_diagnostic()) } else { None }
343 let protected = protector_tag
344 .and_then(|protector| {
345 self.history.protectors.iter().find(|protection| protection.tag == protector)
348 let protected_tag = protection.tag;
349 (format!("{protected_tag:?} is this argument"), protection.span.data())
352 Some(TagHistory { created, invalidated, protected })
355 /// Report a descriptive error when `new` could not be granted from `derived_from`.
356 #[inline(never)] // This is only called on fatal code paths
357 pub(super) fn grant_error(&self, stack: &Stack) -> InterpError<'tcx> {
358 let Operation::Retag(op) = &self.operation else {
359 unreachable!("grant_error should only be called during a retag")
362 op.permission.expect("`start_grant` must be called before calling `grant_error`");
363 let action = format!(
364 "trying to retag from {:?} for {:?} permission at {:?}[{:#x}]",
371 format!("{action}{}", error_cause(stack, op.orig_tag)),
372 Some(operation_summary(&op.cause.summary(), self.history.id, op.range)),
373 op.orig_tag.and_then(|orig_tag| self.get_logs_relevant_to(orig_tag, None)),
377 /// Report a descriptive error when `access` is not permitted based on `tag`.
378 #[inline(never)] // This is only called on fatal code paths
379 pub(super) fn access_error(&self, stack: &Stack) -> InterpError<'tcx> {
380 // Deallocation and retagging also do an access as part of their thing, so handle that here, too.
381 let op = match &self.operation {
382 Operation::Access(op) => op,
383 Operation::Retag(_) => return self.grant_error(stack),
384 Operation::Dealloc(_) => return self.dealloc_error(stack),
386 let action = format!(
387 "attempting a {access} using {tag:?} at {alloc_id:?}[{offset:#x}]",
390 alloc_id = self.history.id,
391 offset = self.offset.bytes(),
394 format!("{action}{}", error_cause(stack, op.tag)),
395 Some(operation_summary("an access", self.history.id, op.range)),
396 op.tag.and_then(|tag| self.get_logs_relevant_to(tag, None)),
400 #[inline(never)] // This is only called on fatal code paths
401 pub(super) fn protector_error(&self, item: &Item, kind: ProtectorKind) -> InterpError<'tcx> {
402 let protected = match kind {
403 ProtectorKind::WeakProtector => "weakly protected",
404 ProtectorKind::StrongProtector => "strongly protected",
412 frame.extra.borrow_tracker.as_ref().expect("we should have borrow tracking data")
414 .find(|frame| frame.protected_tags.contains(&item.tag()))
415 .map(|frame| frame.call_id)
416 .unwrap(); // FIXME: Surely we should find something, but a panic seems wrong here?
417 match self.operation {
418 Operation::Dealloc(_) =>
420 format!("deallocating while item {item:?} is {protected} by call {call_id:?}",),
424 Operation::Retag(RetagOp { orig_tag: tag, .. })
425 | Operation::Access(AccessOp { tag, .. }) =>
428 "not granting access to tag {tag:?} because that would remove {item:?} which is {protected} because it is an argument of call {call_id:?}",
431 tag.and_then(|tag| self.get_logs_relevant_to(tag, Some(item.tag()))),
436 #[inline(never)] // This is only called on fatal code paths
437 pub fn dealloc_error(&self, stack: &Stack) -> InterpError<'tcx> {
438 let Operation::Dealloc(op) = &self.operation else {
439 unreachable!("dealloc_error should only be called during a deallocation")
443 "attempting deallocation using {tag:?} at {alloc_id:?}{cause}",
445 alloc_id = self.history.id,
446 cause = error_cause(stack, op.tag),
449 op.tag.and_then(|tag| self.get_logs_relevant_to(tag, None)),
454 pub fn check_tracked_tag_popped(&self, item: &Item, global: &GlobalStateInner) {
455 if !global.tracked_pointer_tags.contains(&item.tag()) {
458 let cause = match self.operation {
459 Operation::Dealloc(_) => format!(" due to deallocation"),
460 Operation::Access(AccessOp { kind, tag, .. }) =>
461 format!(" due to {kind:?} access for {tag:?}"),
462 Operation::Retag(RetagOp { orig_tag, permission, new_tag, .. }) => {
463 let permission = permission
464 .expect("start_grant should set the current permission before popping a tag");
466 " due to {permission:?} retag from {orig_tag:?} (that retag created {new_tag:?})"
471 self.machine.emit_diagnostic(NonHaltingDiagnostic::PoppedPointerTag(*item, cause));
475 fn operation_summary(operation: &str, alloc_id: AllocId, alloc_range: AllocRange) -> String {
476 format!("this error occurs as part of {operation} at {alloc_id:?}{alloc_range:?}")
479 fn error_cause(stack: &Stack, prov_extra: ProvenanceExtra) -> &'static str {
480 if let ProvenanceExtra::Concrete(tag) = prov_extra {
482 .map(|i| stack.get(i).unwrap())
483 .any(|item| item.tag() == tag && item.perm() != Permission::Disabled)
485 ", but that tag only grants SharedReadOnly permission for this location"
487 ", but that tag does not exist in the borrow stack for this location"
490 ", but no exposed tags have suitable permission in the borrow stack for this location"
495 fn summary(&self) -> String {
497 RetagCause::Normal => "retag",
498 RetagCause::FnEntry => "FnEntry retag",
499 RetagCause::FnReturn => "FnReturn retag",
500 RetagCause::TwoPhase => "two-phase retag",