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::helpers::CurrentSpan;
9 use crate::stacked_borrows::{err_sb_ub, AccessKind, GlobalStateInner, Permission};
12 use rustc_middle::mir::interpret::InterpError;
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<'span, 'ecx, 'mir, 'tcx> {
114 operation: Operation,
115 // 'span cannot be merged with any other lifetime since they appear invariantly, under the
117 current_span: &'span mut CurrentSpan<'ecx, 'mir, 'tcx>,
118 threads: &'ecx ThreadManager<'mir, 'tcx>,
121 pub struct DiagnosticCx<'span, 'history, 'ecx, 'mir, 'tcx> {
122 operation: Operation,
123 // 'span and 'history cannot be merged, since when we call `unbuild` we need
124 // to return the exact 'span that was used when calling `build`.
125 current_span: &'span mut CurrentSpan<'ecx, 'mir, 'tcx>,
126 threads: &'ecx ThreadManager<'mir, 'tcx>,
127 history: &'history mut AllocHistory,
131 impl<'span, 'ecx, 'mir, 'tcx> DiagnosticCxBuilder<'span, 'ecx, 'mir, 'tcx> {
132 pub fn build<'history>(
134 history: &'history mut AllocHistory,
136 ) -> DiagnosticCx<'span, 'history, 'ecx, 'mir, 'tcx> {
138 operation: self.operation,
139 current_span: self.current_span,
140 threads: self.threads,
147 current_span: &'span mut CurrentSpan<'ecx, 'mir, 'tcx>,
148 threads: &'ecx ThreadManager<'mir, 'tcx>,
151 orig_tag: ProvenanceExtra,
155 Operation::Retag(RetagOp { cause, new_tag, orig_tag, range, permission: None });
157 DiagnosticCxBuilder { current_span, threads, operation }
161 current_span: &'span mut CurrentSpan<'ecx, 'mir, 'tcx>,
162 threads: &'ecx ThreadManager<'mir, 'tcx>,
163 tag: ProvenanceExtra,
166 let operation = Operation::Access(AccessOp { kind: AccessKind::Read, tag, range });
167 DiagnosticCxBuilder { current_span, threads, operation }
171 current_span: &'span mut CurrentSpan<'ecx, 'mir, 'tcx>,
172 threads: &'ecx ThreadManager<'mir, 'tcx>,
173 tag: ProvenanceExtra,
176 let operation = Operation::Access(AccessOp { kind: AccessKind::Write, tag, range });
177 DiagnosticCxBuilder { current_span, threads, operation }
181 current_span: &'span mut CurrentSpan<'ecx, 'mir, 'tcx>,
182 threads: &'ecx ThreadManager<'mir, 'tcx>,
183 tag: ProvenanceExtra,
185 let operation = Operation::Dealloc(DeallocOp { tag });
186 DiagnosticCxBuilder { current_span, threads, operation }
190 impl<'span, 'history, 'ecx, 'mir, 'tcx> DiagnosticCx<'span, 'history, 'ecx, 'mir, 'tcx> {
191 pub fn unbuild(self) -> DiagnosticCxBuilder<'span, 'ecx, 'mir, 'tcx> {
192 DiagnosticCxBuilder {
193 operation: self.operation,
194 current_span: self.current_span,
195 threads: self.threads,
200 #[derive(Debug, Clone)]
207 #[derive(Debug, Clone)]
211 orig_tag: ProvenanceExtra,
213 permission: Option<Permission>,
216 #[derive(Debug, Clone, Copy, PartialEq)]
217 pub enum RetagCause {
224 #[derive(Debug, Clone)]
227 tag: ProvenanceExtra,
231 #[derive(Debug, Clone)]
233 tag: ProvenanceExtra,
237 pub fn new(id: AllocId, item: Item, current_span: &mut CurrentSpan<'_, '_, '_>) -> Self {
240 base: (item, current_span.get()),
241 creations: SmallVec::new(),
242 invalidations: SmallVec::new(),
243 protectors: SmallVec::new(),
248 impl<'span, 'history, 'ecx, 'mir, 'tcx> DiagnosticCx<'span, 'history, 'ecx, 'mir, 'tcx> {
249 pub fn start_grant(&mut self, perm: Permission) {
250 let Operation::Retag(op) = &mut self.operation else {
251 unreachable!("start_grant must only be called during a retag, this is: {:?}", self.operation)
253 op.permission = Some(perm);
255 let last_creation = &mut self.history.creations.last_mut().unwrap();
256 match last_creation.retag.permission {
258 last_creation.retag.permission = Some(perm);
261 if previous != perm {
262 // 'Split up' the creation event.
263 let previous_range = last_creation.retag.range;
264 last_creation.retag.range = alloc_range(previous_range.start, self.offset);
265 let mut new_event = last_creation.clone();
266 new_event.retag.range = alloc_range(self.offset, previous_range.end());
267 new_event.retag.permission = Some(perm);
268 self.history.creations.push(new_event);
273 pub fn log_creation(&mut self) {
274 let Operation::Retag(op) = &self.operation else {
275 unreachable!("log_creation must only be called during a retag")
277 self.history.creations.push(Creation { retag: op.clone(), span: self.current_span.get() });
280 pub fn log_invalidation(&mut self, tag: SbTag) {
281 let mut span = self.current_span.get();
282 let (range, cause) = match &self.operation {
283 Operation::Retag(RetagOp { cause, range, permission, .. }) => {
284 if *cause == RetagCause::FnEntry {
285 span = self.current_span.get_caller();
287 (*range, InvalidationCause::Retag(permission.unwrap(), *cause))
289 Operation::Access(AccessOp { kind, range, .. }) =>
290 (*range, InvalidationCause::Access(*kind)),
291 _ => unreachable!("Tags can only be invalidated during a retag or access"),
293 self.history.invalidations.push(Invalidation { tag, range, span, cause });
296 pub fn log_protector(&mut self) {
297 let Operation::Retag(op) = &self.operation else {
298 unreachable!("Protectors can only be created during a retag")
300 self.history.protectors.push(Protection { tag: op.new_tag, span: self.current_span.get() });
303 pub fn get_logs_relevant_to(
306 protector_tag: Option<SbTag>,
307 ) -> Option<TagHistory> {
308 let Some(created) = self.history
313 // First, look for a Creation event where the tag and the offset matches. This
314 // ensrues that we pick the right Creation event when a retag isn't uniform due to
316 let range = event.retag.range;
317 if event.retag.new_tag == tag
318 && self.offset >= range.start
319 && self.offset < (range.start + range.size)
321 Some(event.generate_diagnostic())
327 // If we didn't find anything with a matching offset, just return the event where
328 // the tag was created. This branch is hit when we use a tag at an offset that
329 // doesn't have the tag.
330 self.history.creations.iter().rev().find_map(|event| {
331 if event.retag.new_tag == tag {
332 Some(event.generate_diagnostic())
338 // If we didn't find a retag that created this tag, it might be the base tag of
340 if self.history.base.0.tag() == tag {
342 format!("{tag:?} was created here, as the base tag for {:?}", self.history.id),
343 self.history.base.1.data()
349 // But if we don't have a creation event, this is related to a wildcard, and there
350 // is really nothing we can do to help.
354 let invalidated = self.history.invalidations.iter().rev().find_map(|event| {
355 if event.tag == tag { Some(event.generate_diagnostic()) } else { None }
358 let protected = protector_tag
359 .and_then(|protector| {
360 self.history.protectors.iter().find(|protection| protection.tag == protector)
363 let protected_tag = protection.tag;
364 (format!("{protected_tag:?} is this argument"), protection.span.data())
367 Some(TagHistory { created, invalidated, protected })
370 /// Report a descriptive error when `new` could not be granted from `derived_from`.
371 #[inline(never)] // This is only called on fatal code paths
372 pub fn grant_error(&self, perm: Permission, stack: &Stack) -> InterpError<'tcx> {
373 let Operation::Retag(op) = &self.operation else {
374 unreachable!("grant_error should only be called during a retag")
376 let action = format!(
377 "trying to retag from {:?} for {:?} permission at {:?}[{:#x}]",
384 format!("{action}{}", error_cause(stack, op.orig_tag)),
385 Some(operation_summary(&op.cause.summary(), self.history.id, op.range)),
386 op.orig_tag.and_then(|orig_tag| self.get_logs_relevant_to(orig_tag, None)),
390 /// Report a descriptive error when `access` is not permitted based on `tag`.
391 #[inline(never)] // This is only called on fatal code paths
392 pub fn access_error(&self, stack: &Stack) -> InterpError<'tcx> {
393 let Operation::Access(op) = &self.operation else {
394 unreachable!("access_error should only be called during an access")
396 let action = format!(
397 "attempting a {access} using {tag:?} at {alloc_id:?}[{offset:#x}]",
400 alloc_id = self.history.id,
401 offset = self.offset.bytes(),
404 format!("{action}{}", error_cause(stack, op.tag)),
405 Some(operation_summary("an access", self.history.id, op.range)),
406 op.tag.and_then(|tag| self.get_logs_relevant_to(tag, None)),
410 #[inline(never)] // This is only called on fatal code paths
411 pub fn protector_error(&self, item: &Item) -> InterpError<'tcx> {
417 frame.extra.stacked_borrows.as_ref().expect("we should have Stacked Borrows data")
419 .find(|frame| frame.protected_tags.contains(&item.tag()))
420 .map(|frame| frame.call_id)
421 .unwrap(); // FIXME: Surely we should find something, but a panic seems wrong here?
422 match self.operation {
423 Operation::Dealloc(_) =>
426 "deallocating while item {:?} is protected by call {:?}",
432 Operation::Retag(RetagOp { orig_tag: tag, .. })
433 | Operation::Access(AccessOp { tag, .. }) =>
436 "not granting access to tag {:?} because that would remove {:?} which is protected because it is an argument of call {:?}",
440 tag.and_then(|tag| self.get_logs_relevant_to(tag, Some(item.tag()))),
445 #[inline(never)] // This is only called on fatal code paths
446 pub fn dealloc_error(&self) -> InterpError<'tcx> {
447 let Operation::Dealloc(op) = &self.operation else {
448 unreachable!("dealloc_error should only be called during a deallocation")
452 "no item granting write access for deallocation to tag {:?} at {:?} found in borrow stack",
453 op.tag, self.history.id,
456 op.tag.and_then(|tag| self.get_logs_relevant_to(tag, None)),
461 pub fn check_tracked_tag_popped(&self, item: &Item, global: &GlobalStateInner) {
462 if !global.tracked_pointer_tags.contains(&item.tag()) {
465 let summary = match self.operation {
466 Operation::Dealloc(_) => None,
467 Operation::Access(AccessOp { kind, tag, .. }) => Some((tag, kind)),
468 Operation::Retag(RetagOp { orig_tag, permission, .. }) => {
469 let kind = match permission
470 .expect("start_grant should set the current permission before popping a tag")
472 Permission::SharedReadOnly => AccessKind::Read,
473 Permission::Unique => AccessKind::Write,
474 Permission::SharedReadWrite | Permission::Disabled => {
475 panic!("Only SharedReadOnly and Unique retags can pop tags");
478 Some((orig_tag, kind))
483 .emit_diagnostic(NonHaltingDiagnostic::PoppedPointerTag(*item, summary));
487 fn operation_summary(operation: &str, alloc_id: AllocId, alloc_range: AllocRange) -> String {
488 format!("this error occurs as part of {operation} at {alloc_id:?}{alloc_range:?}")
491 fn error_cause(stack: &Stack, prov_extra: ProvenanceExtra) -> &'static str {
492 if let ProvenanceExtra::Concrete(tag) = prov_extra {
494 .map(|i| stack.get(i).unwrap())
495 .any(|item| item.tag() == tag && item.perm() != Permission::Disabled)
497 ", but that tag only grants SharedReadOnly permission for this location"
499 ", but that tag does not exist in the borrow stack for this location"
502 ", but no exposed tags have suitable permission in the borrow stack for this location"
507 fn summary(&self) -> String {
509 RetagCause::Normal => "retag",
510 RetagCause::FnEntry => "FnEntry retag",
511 RetagCause::FnReturn => "FnReturn retag",
512 RetagCause::TwoPhase => "two-phase retag",