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 write!(f, "{perm:?} {retag}", retag = kind.summary()),
96 #[derive(Clone, Debug)]
103 pub struct TagHistory {
104 pub created: (String, SpanData),
105 pub invalidated: Option<(String, SpanData)>,
106 pub protected: Option<(String, SpanData)>,
109 pub struct DiagnosticCxBuilder<'ecx, 'mir, 'tcx> {
110 operation: Operation,
111 machine: &'ecx MiriMachine<'mir, 'tcx>,
114 pub struct DiagnosticCx<'history, 'ecx, 'mir, 'tcx> {
115 operation: Operation,
116 machine: &'ecx MiriMachine<'mir, 'tcx>,
117 history: &'history mut AllocHistory,
121 impl<'ecx, 'mir, 'tcx> DiagnosticCxBuilder<'ecx, 'mir, 'tcx> {
122 pub fn build<'history>(
124 history: &'history mut AllocHistory,
126 ) -> DiagnosticCx<'history, 'ecx, 'mir, 'tcx> {
127 DiagnosticCx { operation: self.operation, machine: self.machine, history, offset }
131 machine: &'ecx MiriMachine<'mir, 'tcx>,
134 orig_tag: ProvenanceExtra,
138 Operation::Retag(RetagOp { cause, new_tag, orig_tag, range, permission: None });
140 DiagnosticCxBuilder { machine, operation }
144 machine: &'ecx MiriMachine<'mir, 'tcx>,
145 tag: ProvenanceExtra,
148 let operation = Operation::Access(AccessOp { kind: AccessKind::Read, tag, range });
149 DiagnosticCxBuilder { machine, operation }
153 machine: &'ecx MiriMachine<'mir, 'tcx>,
154 tag: ProvenanceExtra,
157 let operation = Operation::Access(AccessOp { kind: AccessKind::Write, tag, range });
158 DiagnosticCxBuilder { machine, operation }
161 pub fn dealloc(machine: &'ecx MiriMachine<'mir, 'tcx>, tag: ProvenanceExtra) -> Self {
162 let operation = Operation::Dealloc(DeallocOp { tag });
163 DiagnosticCxBuilder { machine, operation }
167 impl<'history, 'ecx, 'mir, 'tcx> DiagnosticCx<'history, 'ecx, 'mir, 'tcx> {
168 pub fn unbuild(self) -> DiagnosticCxBuilder<'ecx, 'mir, 'tcx> {
169 DiagnosticCxBuilder { machine: self.machine, operation: self.operation }
173 #[derive(Debug, Clone)]
180 #[derive(Debug, Clone)]
184 orig_tag: ProvenanceExtra,
186 permission: Option<Permission>,
189 #[derive(Debug, Clone, Copy, PartialEq)]
190 pub enum RetagCause {
197 #[derive(Debug, Clone)]
200 tag: ProvenanceExtra,
204 #[derive(Debug, Clone)]
206 tag: ProvenanceExtra,
210 pub fn new(id: AllocId, item: Item, machine: &MiriMachine<'_, '_>) -> Self {
213 base: (item, machine.current_span()),
214 creations: SmallVec::new(),
215 invalidations: SmallVec::new(),
216 protectors: SmallVec::new(),
221 impl<'history, 'ecx, 'mir, 'tcx> DiagnosticCx<'history, 'ecx, 'mir, 'tcx> {
222 pub fn start_grant(&mut self, perm: Permission) {
223 let Operation::Retag(op) = &mut self.operation else {
224 unreachable!("start_grant must only be called during a retag, this is: {:?}", self.operation)
226 op.permission = Some(perm);
228 let last_creation = &mut self.history.creations.last_mut().unwrap();
229 match last_creation.retag.permission {
231 last_creation.retag.permission = Some(perm);
234 if previous != perm {
235 // 'Split up' the creation event.
236 let previous_range = last_creation.retag.range;
237 last_creation.retag.range = alloc_range(previous_range.start, self.offset);
238 let mut new_event = last_creation.clone();
239 new_event.retag.range = alloc_range(self.offset, previous_range.end());
240 new_event.retag.permission = Some(perm);
241 self.history.creations.push(new_event);
246 pub fn log_creation(&mut self) {
247 let Operation::Retag(op) = &self.operation else {
248 unreachable!("log_creation must only be called during a retag")
252 .push(Creation { retag: op.clone(), span: self.machine.current_span() });
255 pub fn log_invalidation(&mut self, tag: BorTag) {
256 let mut span = self.machine.current_span();
257 let (range, cause) = match &self.operation {
258 Operation::Retag(RetagOp { cause, range, permission, .. }) => {
259 if *cause == RetagCause::FnEntry {
260 span = self.machine.caller_span();
262 (*range, InvalidationCause::Retag(permission.unwrap(), *cause))
264 Operation::Access(AccessOp { kind, range, .. }) =>
265 (*range, InvalidationCause::Access(*kind)),
266 Operation::Dealloc(_) => {
267 // This can be reached, but never be relevant later since the entire allocation is
272 self.history.invalidations.push(Invalidation { tag, range, span, cause });
275 pub fn log_protector(&mut self) {
276 let Operation::Retag(op) = &self.operation else {
277 unreachable!("Protectors can only be created during a retag")
281 .push(Protection { tag: op.new_tag, span: self.machine.current_span() });
284 pub fn get_logs_relevant_to(
287 protector_tag: Option<BorTag>,
288 ) -> Option<TagHistory> {
289 let Some(created) = self.history
294 // First, look for a Creation event where the tag and the offset matches. This
295 // ensrues that we pick the right Creation event when a retag isn't uniform due to
297 let range = event.retag.range;
298 if event.retag.new_tag == tag
299 && self.offset >= range.start
300 && self.offset < (range.start + range.size)
302 Some(event.generate_diagnostic())
308 // If we didn't find anything with a matching offset, just return the event where
309 // the tag was created. This branch is hit when we use a tag at an offset that
310 // doesn't have the tag.
311 self.history.creations.iter().rev().find_map(|event| {
312 if event.retag.new_tag == tag {
313 Some(event.generate_diagnostic())
319 // If we didn't find a retag that created this tag, it might be the base tag of
321 if self.history.base.0.tag() == tag {
323 format!("{tag:?} was created here, as the base tag for {:?}", self.history.id),
324 self.history.base.1.data()
330 // But if we don't have a creation event, this is related to a wildcard, and there
331 // is really nothing we can do to help.
335 let invalidated = self.history.invalidations.iter().rev().find_map(|event| {
336 if event.tag == tag { Some(event.generate_diagnostic()) } else { None }
339 let protected = protector_tag
340 .and_then(|protector| {
341 self.history.protectors.iter().find(|protection| protection.tag == protector)
344 let protected_tag = protection.tag;
345 (format!("{protected_tag:?} is this argument"), protection.span.data())
348 Some(TagHistory { created, invalidated, protected })
351 /// Report a descriptive error when `new` could not be granted from `derived_from`.
352 #[inline(never)] // This is only called on fatal code paths
353 pub(super) fn grant_error(&self, stack: &Stack) -> InterpError<'tcx> {
354 let Operation::Retag(op) = &self.operation else {
355 unreachable!("grant_error should only be called during a retag")
358 op.permission.expect("`start_grant` must be called before calling `grant_error`");
359 let action = format!(
360 "trying to retag from {:?} for {:?} permission at {:?}[{:#x}]",
367 format!("{action}{}", error_cause(stack, op.orig_tag)),
368 Some(operation_summary(&op.cause.summary(), self.history.id, op.range)),
369 op.orig_tag.and_then(|orig_tag| self.get_logs_relevant_to(orig_tag, None)),
373 /// Report a descriptive error when `access` is not permitted based on `tag`.
374 #[inline(never)] // This is only called on fatal code paths
375 pub(super) fn access_error(&self, stack: &Stack) -> InterpError<'tcx> {
376 // Deallocation and retagging also do an access as part of their thing, so handle that here, too.
377 let op = match &self.operation {
378 Operation::Access(op) => op,
379 Operation::Retag(_) => return self.grant_error(stack),
380 Operation::Dealloc(_) => return self.dealloc_error(stack),
382 let action = format!(
383 "attempting a {access} using {tag:?} at {alloc_id:?}[{offset:#x}]",
386 alloc_id = self.history.id,
387 offset = self.offset.bytes(),
390 format!("{action}{}", error_cause(stack, op.tag)),
391 Some(operation_summary("an access", self.history.id, op.range)),
392 op.tag.and_then(|tag| self.get_logs_relevant_to(tag, None)),
396 #[inline(never)] // This is only called on fatal code paths
397 pub(super) fn protector_error(&self, item: &Item, kind: ProtectorKind) -> InterpError<'tcx> {
398 let protected = match kind {
399 ProtectorKind::WeakProtector => "weakly protected",
400 ProtectorKind::StrongProtector => "strongly protected",
408 frame.extra.borrow_tracker.as_ref().expect("we should have borrow tracking data")
410 .find(|frame| frame.protected_tags.contains(&item.tag()))
411 .map(|frame| frame.call_id)
412 .unwrap(); // FIXME: Surely we should find something, but a panic seems wrong here?
413 match self.operation {
414 Operation::Dealloc(_) =>
416 format!("deallocating while item {item:?} is {protected} by call {call_id:?}",),
420 Operation::Retag(RetagOp { orig_tag: tag, .. })
421 | Operation::Access(AccessOp { tag, .. }) =>
424 "not granting access to tag {tag:?} because that would remove {item:?} which is {protected} because it is an argument of call {call_id:?}",
427 tag.and_then(|tag| self.get_logs_relevant_to(tag, Some(item.tag()))),
432 #[inline(never)] // This is only called on fatal code paths
433 pub fn dealloc_error(&self, stack: &Stack) -> InterpError<'tcx> {
434 let Operation::Dealloc(op) = &self.operation else {
435 unreachable!("dealloc_error should only be called during a deallocation")
439 "attempting deallocation using {tag:?} at {alloc_id:?}{cause}",
441 alloc_id = self.history.id,
442 cause = error_cause(stack, op.tag),
445 op.tag.and_then(|tag| self.get_logs_relevant_to(tag, None)),
450 pub fn check_tracked_tag_popped(&self, item: &Item, global: &GlobalStateInner) {
451 if !global.tracked_pointer_tags.contains(&item.tag()) {
454 let cause = match self.operation {
455 Operation::Dealloc(_) => format!(" due to deallocation"),
456 Operation::Access(AccessOp { kind, tag, .. }) =>
457 format!(" due to {kind:?} access for {tag:?}"),
458 Operation::Retag(RetagOp { orig_tag, permission, new_tag, .. }) => {
459 let permission = permission
460 .expect("start_grant should set the current permission before popping a tag");
462 " due to {permission:?} retag from {orig_tag:?} (that retag created {new_tag:?})"
467 self.machine.emit_diagnostic(NonHaltingDiagnostic::PoppedPointerTag(*item, cause));
471 fn operation_summary(operation: &str, alloc_id: AllocId, alloc_range: AllocRange) -> String {
472 format!("this error occurs as part of {operation} at {alloc_id:?}{alloc_range:?}")
475 fn error_cause(stack: &Stack, prov_extra: ProvenanceExtra) -> &'static str {
476 if let ProvenanceExtra::Concrete(tag) = prov_extra {
478 .map(|i| stack.get(i).unwrap())
479 .any(|item| item.tag() == tag && item.perm() != Permission::Disabled)
481 ", but that tag only grants SharedReadOnly permission for this location"
483 ", but that tag does not exist in the borrow stack for this location"
486 ", but no exposed tags have suitable permission in the borrow stack for this location"
491 fn summary(&self) -> String {
493 RetagCause::Normal => "retag",
494 RetagCause::FnEntry => "function-entry retag",
495 RetagCause::FnReturnPlace => "return-place retag",
496 RetagCause::TwoPhase => "two-phase retag",