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::{err_sb_ub, AccessKind, GlobalStateInner, Permission, ProtectorKind};
11 use rustc_middle::mir::interpret::InterpError;
13 #[derive(Clone, Debug)]
14 pub struct AllocHistory {
17 creations: smallvec::SmallVec<[Creation; 1]>,
18 invalidations: smallvec::SmallVec<[Invalidation; 1]>,
19 protectors: smallvec::SmallVec<[Protection; 1]>,
22 #[derive(Clone, Debug)]
29 fn generate_diagnostic(&self) -> (String, SpanData) {
30 let tag = self.retag.new_tag;
31 if let Some(perm) = self.retag.permission {
34 "{tag:?} was created by a {:?} retag at offsets {:?}",
35 perm, self.retag.range,
40 assert!(self.retag.range.size == Size::ZERO);
43 "{tag:?} would have been created here, but this is a zero-size retag ({:?}) so the tag in question does not exist anywhere",
52 #[derive(Clone, Debug)]
57 cause: InvalidationCause,
60 #[derive(Clone, Debug)]
61 enum InvalidationCause {
63 Retag(Permission, RetagCause),
67 fn generate_diagnostic(&self) -> (String, SpanData) {
68 let message = if let InvalidationCause::Retag(_, RetagCause::FnEntry) = self.cause {
69 // For a FnEntry retag, our Span points at the caller.
70 // See `DiagnosticCx::log_invalidation`.
72 "{:?} was later invalidated at offsets {:?} by a {} inside this call",
73 self.tag, self.range, self.cause
77 "{:?} was later invalidated at offsets {:?} by a {}",
78 self.tag, self.range, self.cause
81 (message, self.span.data())
85 impl fmt::Display for InvalidationCause {
86 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
88 InvalidationCause::Access(kind) => write!(f, "{kind}"),
89 InvalidationCause::Retag(perm, kind) =>
90 if *kind == RetagCause::FnEntry {
91 write!(f, "{perm:?} FnEntry retag")
93 write!(f, "{perm:?} retag")
99 #[derive(Clone, Debug)]
106 pub struct TagHistory {
107 pub created: (String, SpanData),
108 pub invalidated: Option<(String, SpanData)>,
109 pub protected: Option<(String, SpanData)>,
112 pub struct DiagnosticCxBuilder<'ecx, 'mir, 'tcx> {
113 operation: Operation,
114 machine: &'ecx MiriMachine<'mir, 'tcx>,
117 pub struct DiagnosticCx<'history, 'ecx, 'mir, 'tcx> {
118 operation: Operation,
119 machine: &'ecx MiriMachine<'mir, 'tcx>,
120 history: &'history mut AllocHistory,
124 impl<'ecx, 'mir, 'tcx> DiagnosticCxBuilder<'ecx, 'mir, 'tcx> {
125 pub fn build<'history>(
127 history: &'history mut AllocHistory,
129 ) -> DiagnosticCx<'history, 'ecx, 'mir, 'tcx> {
130 DiagnosticCx { operation: self.operation, machine: self.machine, history, offset }
134 machine: &'ecx MiriMachine<'mir, 'tcx>,
137 orig_tag: ProvenanceExtra,
141 Operation::Retag(RetagOp { cause, new_tag, orig_tag, range, permission: None });
143 DiagnosticCxBuilder { machine, operation }
147 machine: &'ecx MiriMachine<'mir, 'tcx>,
148 tag: ProvenanceExtra,
151 let operation = Operation::Access(AccessOp { kind: AccessKind::Read, tag, range });
152 DiagnosticCxBuilder { machine, operation }
156 machine: &'ecx MiriMachine<'mir, 'tcx>,
157 tag: ProvenanceExtra,
160 let operation = Operation::Access(AccessOp { kind: AccessKind::Write, tag, range });
161 DiagnosticCxBuilder { machine, operation }
164 pub fn dealloc(machine: &'ecx MiriMachine<'mir, 'tcx>, tag: ProvenanceExtra) -> Self {
165 let operation = Operation::Dealloc(DeallocOp { tag });
166 DiagnosticCxBuilder { machine, operation }
170 impl<'history, 'ecx, 'mir, 'tcx> DiagnosticCx<'history, 'ecx, 'mir, 'tcx> {
171 pub fn unbuild(self) -> DiagnosticCxBuilder<'ecx, 'mir, 'tcx> {
172 DiagnosticCxBuilder { machine: self.machine, operation: self.operation }
176 #[derive(Debug, Clone)]
183 #[derive(Debug, Clone)]
187 orig_tag: ProvenanceExtra,
189 permission: Option<Permission>,
192 #[derive(Debug, Clone, Copy, PartialEq)]
193 pub enum RetagCause {
200 #[derive(Debug, Clone)]
203 tag: ProvenanceExtra,
207 #[derive(Debug, Clone)]
209 tag: ProvenanceExtra,
213 pub fn new(id: AllocId, item: Item, machine: &MiriMachine<'_, '_>) -> Self {
216 base: (item, machine.current_span()),
217 creations: SmallVec::new(),
218 invalidations: SmallVec::new(),
219 protectors: SmallVec::new(),
224 impl<'history, 'ecx, 'mir, 'tcx> DiagnosticCx<'history, 'ecx, 'mir, 'tcx> {
225 pub fn start_grant(&mut self, perm: Permission) {
226 let Operation::Retag(op) = &mut self.operation else {
227 unreachable!("start_grant must only be called during a retag, this is: {:?}", self.operation)
229 op.permission = Some(perm);
231 let last_creation = &mut self.history.creations.last_mut().unwrap();
232 match last_creation.retag.permission {
234 last_creation.retag.permission = Some(perm);
237 if previous != perm {
238 // 'Split up' the creation event.
239 let previous_range = last_creation.retag.range;
240 last_creation.retag.range = alloc_range(previous_range.start, self.offset);
241 let mut new_event = last_creation.clone();
242 new_event.retag.range = alloc_range(self.offset, previous_range.end());
243 new_event.retag.permission = Some(perm);
244 self.history.creations.push(new_event);
249 pub fn log_creation(&mut self) {
250 let Operation::Retag(op) = &self.operation else {
251 unreachable!("log_creation must only be called during a retag")
255 .push(Creation { retag: op.clone(), span: self.machine.current_span() });
258 pub fn log_invalidation(&mut self, tag: SbTag) {
259 let mut span = self.machine.current_span();
260 let (range, cause) = match &self.operation {
261 Operation::Retag(RetagOp { cause, range, permission, .. }) => {
262 if *cause == RetagCause::FnEntry {
263 span = self.machine.caller_span();
265 (*range, InvalidationCause::Retag(permission.unwrap(), *cause))
267 Operation::Access(AccessOp { kind, range, .. }) =>
268 (*range, InvalidationCause::Access(*kind)),
269 Operation::Dealloc(_) => {
270 // This can be reached, but never be relevant later since the entire allocation is
275 self.history.invalidations.push(Invalidation { tag, range, span, cause });
278 pub fn log_protector(&mut self) {
279 let Operation::Retag(op) = &self.operation else {
280 unreachable!("Protectors can only be created during a retag")
284 .push(Protection { tag: op.new_tag, span: self.machine.current_span() });
287 pub fn get_logs_relevant_to(
290 protector_tag: Option<SbTag>,
291 ) -> Option<TagHistory> {
292 let Some(created) = self.history
297 // First, look for a Creation event where the tag and the offset matches. This
298 // ensrues that we pick the right Creation event when a retag isn't uniform due to
300 let range = event.retag.range;
301 if event.retag.new_tag == tag
302 && self.offset >= range.start
303 && self.offset < (range.start + range.size)
305 Some(event.generate_diagnostic())
311 // If we didn't find anything with a matching offset, just return the event where
312 // the tag was created. This branch is hit when we use a tag at an offset that
313 // doesn't have the tag.
314 self.history.creations.iter().rev().find_map(|event| {
315 if event.retag.new_tag == tag {
316 Some(event.generate_diagnostic())
322 // If we didn't find a retag that created this tag, it might be the base tag of
324 if self.history.base.0.tag() == tag {
326 format!("{tag:?} was created here, as the base tag for {:?}", self.history.id),
327 self.history.base.1.data()
333 // But if we don't have a creation event, this is related to a wildcard, and there
334 // is really nothing we can do to help.
338 let invalidated = self.history.invalidations.iter().rev().find_map(|event| {
339 if event.tag == tag { Some(event.generate_diagnostic()) } else { None }
342 let protected = protector_tag
343 .and_then(|protector| {
344 self.history.protectors.iter().find(|protection| protection.tag == protector)
347 let protected_tag = protection.tag;
348 (format!("{protected_tag:?} is this argument"), protection.span.data())
351 Some(TagHistory { created, invalidated, protected })
354 /// Report a descriptive error when `new` could not be granted from `derived_from`.
355 #[inline(never)] // This is only called on fatal code paths
356 pub(super) fn grant_error(&self, stack: &Stack) -> InterpError<'tcx> {
357 let Operation::Retag(op) = &self.operation else {
358 unreachable!("grant_error should only be called during a retag")
361 op.permission.expect("`start_grant` must be called before calling `grant_error`");
362 let action = format!(
363 "trying to retag from {:?} for {:?} permission at {:?}[{:#x}]",
370 format!("{action}{}", error_cause(stack, op.orig_tag)),
371 Some(operation_summary(&op.cause.summary(), self.history.id, op.range)),
372 op.orig_tag.and_then(|orig_tag| self.get_logs_relevant_to(orig_tag, None)),
376 /// Report a descriptive error when `access` is not permitted based on `tag`.
377 #[inline(never)] // This is only called on fatal code paths
378 pub(super) fn access_error(&self, stack: &Stack) -> InterpError<'tcx> {
379 // Deallocation and retagging also do an access as part of their thing, so handle that here, too.
380 let op = match &self.operation {
381 Operation::Access(op) => op,
382 Operation::Retag(_) => return self.grant_error(stack),
383 Operation::Dealloc(_) => return self.dealloc_error(stack),
385 let action = format!(
386 "attempting a {access} using {tag:?} at {alloc_id:?}[{offset:#x}]",
389 alloc_id = self.history.id,
390 offset = self.offset.bytes(),
393 format!("{action}{}", error_cause(stack, op.tag)),
394 Some(operation_summary("an access", self.history.id, op.range)),
395 op.tag.and_then(|tag| self.get_logs_relevant_to(tag, None)),
399 #[inline(never)] // This is only called on fatal code paths
400 pub(super) fn protector_error(&self, item: &Item, kind: ProtectorKind) -> InterpError<'tcx> {
401 let protected = match kind {
402 ProtectorKind::WeakProtector => "weakly protected",
403 ProtectorKind::StrongProtector => "strongly protected",
411 frame.extra.stacked_borrows.as_ref().expect("we should have Stacked Borrows data")
413 .find(|frame| frame.protected_tags.contains(&item.tag()))
414 .map(|frame| frame.call_id)
415 .unwrap(); // FIXME: Surely we should find something, but a panic seems wrong here?
416 match self.operation {
417 Operation::Dealloc(_) =>
419 format!("deallocating while item {item:?} is {protected} by call {call_id:?}",),
423 Operation::Retag(RetagOp { orig_tag: tag, .. })
424 | Operation::Access(AccessOp { tag, .. }) =>
427 "not granting access to tag {tag:?} because that would remove {item:?} which is {protected} because it is an argument of call {call_id:?}",
430 tag.and_then(|tag| self.get_logs_relevant_to(tag, Some(item.tag()))),
435 #[inline(never)] // This is only called on fatal code paths
436 pub fn dealloc_error(&self, stack: &Stack) -> InterpError<'tcx> {
437 let Operation::Dealloc(op) = &self.operation else {
438 unreachable!("dealloc_error should only be called during a deallocation")
442 "attempting deallocation using {tag:?} at {alloc_id:?}{cause}",
444 alloc_id = self.history.id,
445 cause = error_cause(stack, op.tag),
448 op.tag.and_then(|tag| self.get_logs_relevant_to(tag, None)),
453 pub fn check_tracked_tag_popped(&self, item: &Item, global: &GlobalStateInner) {
454 if !global.tracked_pointer_tags.contains(&item.tag()) {
457 let summary = match self.operation {
458 Operation::Dealloc(_) => None,
459 Operation::Access(AccessOp { kind, tag, .. }) => Some((tag, kind)),
460 Operation::Retag(RetagOp { orig_tag, permission, .. }) => {
461 let kind = match permission
462 .expect("start_grant should set the current permission before popping a tag")
464 Permission::SharedReadOnly => AccessKind::Read,
465 Permission::Unique => AccessKind::Write,
466 Permission::SharedReadWrite | Permission::Disabled => {
467 panic!("Only SharedReadOnly and Unique retags can pop tags");
470 Some((orig_tag, kind))
473 self.machine.emit_diagnostic(NonHaltingDiagnostic::PoppedPointerTag(*item, summary));
477 fn operation_summary(operation: &str, alloc_id: AllocId, alloc_range: AllocRange) -> String {
478 format!("this error occurs as part of {operation} at {alloc_id:?}{alloc_range:?}")
481 fn error_cause(stack: &Stack, prov_extra: ProvenanceExtra) -> &'static str {
482 if let ProvenanceExtra::Concrete(tag) = prov_extra {
484 .map(|i| stack.get(i).unwrap())
485 .any(|item| item.tag() == tag && item.perm() != Permission::Disabled)
487 ", but that tag only grants SharedReadOnly permission for this location"
489 ", but that tag does not exist in the borrow stack for this location"
492 ", but no exposed tags have suitable permission in the borrow stack for this location"
497 fn summary(&self) -> String {
499 RetagCause::Normal => "retag",
500 RetagCause::FnEntry => "FnEntry retag",
501 RetagCause::FnReturn => "FnReturn retag",
502 RetagCause::TwoPhase => "two-phase retag",