1 use smallvec::SmallVec;
4 use rustc_middle::mir::interpret::{alloc_range, AllocId, AllocRange};
5 use rustc_span::{Span, SpanData, DUMMY_SP};
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 {
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) {
70 "{:?} was later invalidated at offsets {:?} by a {}",
71 self.tag, self.range, self.cause
78 impl fmt::Display for InvalidationCause {
79 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
81 InvalidationCause::Access(kind) => write!(f, "{}", kind),
82 InvalidationCause::Retag(perm, kind) =>
83 if *kind == RetagCause::FnEntry {
84 write!(f, "{:?} FnEntry retag", perm)
86 write!(f, "{:?} retag", perm)
92 #[derive(Clone, Debug)]
94 /// The parent tag from which this protected tag was derived.
95 orig_tag: ProvenanceExtra,
101 pub struct TagHistory {
102 pub created: (String, SpanData),
103 pub invalidated: Option<(String, SpanData)>,
104 pub protected: Option<([(String, SpanData); 2])>,
107 pub struct DiagnosticCxBuilder<'span, 'ecx, 'mir, 'tcx> {
108 operation: Operation,
109 // 'span cannot be merged with any other lifetime since they appear invariantly, under the
111 current_span: &'span mut CurrentSpan<'ecx, 'mir, 'tcx>,
112 threads: &'ecx ThreadManager<'mir, 'tcx>,
115 pub struct DiagnosticCx<'span, 'history, 'ecx, 'mir, 'tcx> {
116 operation: Operation,
117 // 'span and 'history cannot be merged, since when we call `unbuild` we need
118 // to return the exact 'span that was used when calling `build`.
119 current_span: &'span mut CurrentSpan<'ecx, 'mir, 'tcx>,
120 threads: &'ecx ThreadManager<'mir, 'tcx>,
121 history: &'history mut AllocHistory,
125 impl<'span, 'ecx, 'mir, 'tcx> DiagnosticCxBuilder<'span, 'ecx, 'mir, 'tcx> {
126 pub fn build<'history>(
128 history: &'history mut AllocHistory,
130 ) -> DiagnosticCx<'span, 'history, 'ecx, 'mir, 'tcx> {
132 operation: self.operation,
133 current_span: self.current_span,
134 threads: self.threads,
141 current_span: &'span mut CurrentSpan<'ecx, 'mir, 'tcx>,
142 threads: &'ecx ThreadManager<'mir, 'tcx>,
145 orig_tag: ProvenanceExtra,
149 Operation::Retag(RetagOp { cause, new_tag, orig_tag, range, permission: None });
151 DiagnosticCxBuilder { current_span, threads, operation }
155 current_span: &'span mut CurrentSpan<'ecx, 'mir, 'tcx>,
156 threads: &'ecx ThreadManager<'mir, 'tcx>,
157 tag: ProvenanceExtra,
160 let operation = Operation::Access(AccessOp { kind: AccessKind::Read, tag, range });
161 DiagnosticCxBuilder { current_span, threads, operation }
165 current_span: &'span mut CurrentSpan<'ecx, 'mir, 'tcx>,
166 threads: &'ecx ThreadManager<'mir, 'tcx>,
167 tag: ProvenanceExtra,
170 let operation = Operation::Access(AccessOp { kind: AccessKind::Write, tag, range });
171 DiagnosticCxBuilder { current_span, threads, operation }
175 current_span: &'span mut CurrentSpan<'ecx, 'mir, 'tcx>,
176 threads: &'ecx ThreadManager<'mir, 'tcx>,
177 tag: ProvenanceExtra,
179 let operation = Operation::Dealloc(DeallocOp { tag });
180 DiagnosticCxBuilder { current_span, threads, operation }
184 impl<'span, 'history, 'ecx, 'mir, 'tcx> DiagnosticCx<'span, 'history, 'ecx, 'mir, 'tcx> {
185 pub fn unbuild(self) -> DiagnosticCxBuilder<'span, 'ecx, 'mir, 'tcx> {
186 DiagnosticCxBuilder {
187 operation: self.operation,
188 current_span: self.current_span,
189 threads: self.threads,
194 #[derive(Debug, Clone)]
201 #[derive(Debug, Clone)]
205 orig_tag: ProvenanceExtra,
207 permission: Option<Permission>,
210 #[derive(Debug, Clone, Copy, PartialEq)]
211 pub enum RetagCause {
218 #[derive(Debug, Clone)]
221 tag: ProvenanceExtra,
225 #[derive(Debug, Clone)]
227 tag: ProvenanceExtra,
231 pub fn new(id: AllocId) -> Self {
234 creations: SmallVec::new(),
235 invalidations: SmallVec::new(),
236 protectors: SmallVec::new(),
241 impl<'span, 'history, 'ecx, 'mir, 'tcx> DiagnosticCx<'span, 'history, 'ecx, 'mir, 'tcx> {
242 pub fn start_grant(&mut self, perm: Permission) {
243 let Operation::Retag(op) = &mut self.operation else {
244 unreachable!("start_grant must only be called during a retag, this is: {:?}", self.operation)
246 op.permission = Some(perm);
248 let last_creation = &mut self.history.creations.last_mut().unwrap();
249 match last_creation.retag.permission {
251 last_creation.retag.permission = Some(perm);
254 if previous != perm {
255 // 'Split up' the creation event.
256 let previous_range = last_creation.retag.range;
257 last_creation.retag.range = alloc_range(previous_range.start, self.offset);
258 let mut new_event = last_creation.clone();
259 new_event.retag.range = alloc_range(self.offset, previous_range.end());
260 new_event.retag.permission = Some(perm);
261 self.history.creations.push(new_event);
266 pub fn log_creation(&mut self) {
267 let Operation::Retag(op) = &self.operation else {
268 unreachable!("log_creation must only be called during a retag")
270 self.history.creations.push(Creation { retag: op.clone(), span: self.current_span.get() });
273 pub fn log_invalidation(&mut self, tag: SbTag) {
274 let mut span = self.current_span.get();
275 let (range, cause) = match &self.operation {
276 Operation::Retag(RetagOp { cause, range, permission, .. }) => {
277 if *cause == RetagCause::FnEntry {
278 span = self.current_span.get_parent();
280 (*range, InvalidationCause::Retag(permission.unwrap(), *cause))
282 Operation::Access(AccessOp { kind, range, .. }) =>
283 (*range, InvalidationCause::Access(*kind)),
284 _ => unreachable!("Tags can only be invalidated during a retag or access"),
286 self.history.invalidations.push(Invalidation { tag, range, span, cause });
289 pub fn log_protector(&mut self) {
290 let Operation::Retag(op) = &self.operation else {
291 unreachable!("Protectors can only be created during a retag")
293 self.history.protectors.push(Protection {
294 orig_tag: op.orig_tag,
296 span: self.current_span.get(),
300 pub fn get_logs_relevant_to(
303 protector_tag: Option<SbTag>,
304 ) -> Option<TagHistory> {
305 let Some(created) = self.history
310 // First, look for a Creation event where the tag and the offset matches. This
311 // ensrues that we pick the right Creation event when a retag isn't uniform due to
313 let range = event.retag.range;
314 if event.retag.new_tag == tag
315 && self.offset >= range.start
316 && self.offset < (range.start + range.size)
318 Some(event.generate_diagnostic())
324 // If we didn't find anything with a matching offset, just return the event where
325 // the tag was created. This branch is hit when we use a tag at an offset that
326 // doesn't have the tag.
327 self.history.creations.iter().rev().find_map(|event| {
328 if event.retag.new_tag == tag {
329 Some(event.generate_diagnostic())
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| {
347 protection.tag == protector
350 .and_then(|protection| {
351 self.history.creations.iter().rev().find_map(|event| {
352 if ProvenanceExtra::Concrete(event.retag.new_tag) == protection.orig_tag {
353 Some((protection, event))
359 .map(|(protection, protection_parent)| {
360 let protected_tag = protection.tag;
364 "{tag:?} cannot be used for memory access because that would remove protected tag {protected_tag:?}, protected by this function call",
366 protection.span.data(),
368 if protection_parent.retag.new_tag == tag {
369 (format!("{protected_tag:?} was derived from {tag:?}, the tag used for this memory access"), DUMMY_SP.data())
373 "{protected_tag:?} was derived from {protected_parent_tag:?}, which in turn was created here",
374 protected_parent_tag = protection_parent.retag.new_tag,
376 protection_parent.span.data()
382 Some(TagHistory { created, invalidated, protected })
385 /// Report a descriptive error when `new` could not be granted from `derived_from`.
386 #[inline(never)] // This is only called on fatal code paths
387 pub fn grant_error(&self, perm: Permission, stack: &Stack) -> InterpError<'tcx> {
388 let Operation::Retag(op) = &self.operation else {
389 unreachable!("grant_error should only be called during a retag")
391 let action = format!(
392 "trying to retag from {:?} for {:?} permission at {:?}[{:#x}]",
399 format!("{}{}", action, error_cause(stack, op.orig_tag)),
400 Some(operation_summary(&op.cause.summary(), self.history.id, op.range)),
401 op.orig_tag.and_then(|orig_tag| self.get_logs_relevant_to(orig_tag, None)),
405 /// Report a descriptive error when `access` is not permitted based on `tag`.
406 #[inline(never)] // This is only called on fatal code paths
407 pub fn access_error(&self, stack: &Stack) -> InterpError<'tcx> {
408 let Operation::Access(op) = &self.operation else {
409 unreachable!("access_error should only be called during an access")
411 let action = format!(
412 "attempting a {access} using {tag:?} at {alloc_id:?}[{offset:#x}]",
415 alloc_id = self.history.id,
416 offset = self.offset.bytes(),
419 format!("{}{}", action, error_cause(stack, op.tag)),
420 Some(operation_summary("an access", self.history.id, op.range)),
421 op.tag.and_then(|tag| self.get_logs_relevant_to(tag, None)),
425 #[inline(never)] // This is only called on fatal code paths
426 pub fn protector_error(&self, item: &Item) -> InterpError<'tcx> {
432 frame.extra.stacked_borrows.as_ref().expect("we should have Stacked Borrows data")
434 .find(|frame| frame.protected_tags.contains(&item.tag()))
435 .map(|frame| frame.call_id)
436 .unwrap(); // FIXME: Surely we should find something, but a panic seems wrong here?
437 match self.operation {
438 Operation::Dealloc(_) =>
441 "deallocating while item {:?} is protected by call {:?}",
447 Operation::Retag(RetagOp { orig_tag: tag, .. })
448 | Operation::Access(AccessOp { tag, .. }) =>
451 "not granting access to tag {:?} because incompatible item {:?} is protected by call {:?}",
455 tag.and_then(|tag| self.get_logs_relevant_to(tag, Some(item.tag()))),
460 #[inline(never)] // This is only called on fatal code paths
461 pub fn dealloc_error(&self) -> InterpError<'tcx> {
462 let Operation::Dealloc(op) = &self.operation else {
463 unreachable!("dealloc_error should only be called during a deallocation")
467 "no item granting write access for deallocation to tag {:?} at {:?} found in borrow stack",
468 op.tag, self.history.id,
471 op.tag.and_then(|tag| self.get_logs_relevant_to(tag, None)),
476 pub fn check_tracked_tag_popped(&self, item: &Item, global: &GlobalStateInner) {
477 if !global.tracked_pointer_tags.contains(&item.tag()) {
480 let summary = match self.operation {
481 Operation::Dealloc(_) => None,
482 Operation::Access(AccessOp { kind, tag, .. }) => Some((tag, kind)),
483 Operation::Retag(RetagOp { orig_tag, permission, .. }) => {
484 let kind = match permission
485 .expect("start_grant should set the current permission before popping a tag")
487 Permission::SharedReadOnly => AccessKind::Read,
488 Permission::Unique => AccessKind::Write,
489 Permission::SharedReadWrite | Permission::Disabled => {
490 panic!("Only SharedReadOnly and Unique retags can pop tags");
493 Some((orig_tag, kind))
496 register_diagnostic(NonHaltingDiagnostic::PoppedPointerTag(*item, summary));
500 fn operation_summary(operation: &str, alloc_id: AllocId, alloc_range: AllocRange) -> String {
501 format!("this error occurs as part of {operation} at {alloc_id:?}{alloc_range:?}")
504 fn error_cause(stack: &Stack, prov_extra: ProvenanceExtra) -> &'static str {
505 if let ProvenanceExtra::Concrete(tag) = prov_extra {
507 .map(|i| stack.get(i).unwrap())
508 .any(|item| item.tag() == tag && item.perm() != Permission::Disabled)
510 ", but that tag only grants SharedReadOnly permission for this location"
512 ", but that tag does not exist in the borrow stack for this location"
515 ", but no exposed tags have suitable permission in the borrow stack for this location"
520 fn summary(&self) -> String {
522 RetagCause::Normal => "retag",
523 RetagCause::FnEntry => "FnEntry retag",
524 RetagCause::FnReturn => "FnReturn retag",
525 RetagCause::TwoPhase => "two-phase retag",