1 use smallvec::SmallVec;
2 use std::fmt;
3
4 use rustc_middle::mir::interpret::{alloc_range, AllocId, AllocRange, InterpError};
5 use rustc_span::{Span, SpanData};
6 use rustc_target::abi::Size;
7
8 use crate::borrow_tracker::{
9 stacked_borrows::{err_sb_ub, Permission},
10 AccessKind, GlobalStateInner, ProtectorKind,
11 };
12 use crate::*;
13
14 #[derive(Clone, Debug)]
15 pub struct AllocHistory {
16 id: AllocId,
17 base: (Item, Span),
18 creations: smallvec::SmallVec<[Creation; 1]>,
19 invalidations: smallvec::SmallVec<[Invalidation; 1]>,
20 protectors: smallvec::SmallVec<[Protection; 1]>,
21 }
22
23 #[derive(Clone, Debug)]
24 struct Creation {
25 retag: RetagOp,
26 span: Span,
27 }
28
29 impl Creation {
generate_diagnostic(&self) -> (String, SpanData)30 fn generate_diagnostic(&self) -> (String, SpanData) {
31 let tag = self.retag.new_tag;
32 if let Some(perm) = self.retag.permission {
33 (
34 format!(
35 "{tag:?} was created by a {:?} retag at offsets {:?}",
36 perm, self.retag.range,
37 ),
38 self.span.data(),
39 )
40 } else {
41 assert!(self.retag.range.size == Size::ZERO);
42 (
43 format!(
44 "{tag:?} would have been created here, but this is a zero-size retag ({:?}) so the tag in question does not exist anywhere",
45 self.retag.range,
46 ),
47 self.span.data(),
48 )
49 }
50 }
51 }
52
53 #[derive(Clone, Debug)]
54 struct Invalidation {
55 tag: BorTag,
56 range: AllocRange,
57 span: Span,
58 cause: InvalidationCause,
59 }
60
61 #[derive(Clone, Debug)]
62 enum InvalidationCause {
63 Access(AccessKind),
64 Retag(Permission, RetagCause),
65 }
66
67 impl Invalidation {
generate_diagnostic(&self) -> (String, SpanData)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`.
72 format!(
73 "{:?} was later invalidated at offsets {:?} by a {} inside this call",
74 self.tag, self.range, self.cause
75 )
76 } else {
77 format!(
78 "{:?} was later invalidated at offsets {:?} by a {}",
79 self.tag, self.range, self.cause
80 )
81 };
82 (message, self.span.data())
83 }
84 }
85
86 impl fmt::Display for InvalidationCause {
fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result87 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
88 match self {
89 InvalidationCause::Access(kind) => write!(f, "{kind}"),
90 InvalidationCause::Retag(perm, kind) =>
91 write!(f, "{perm:?} {retag}", retag = kind.summary()),
92 }
93 }
94 }
95
96 #[derive(Clone, Debug)]
97 struct Protection {
98 tag: BorTag,
99 span: Span,
100 }
101
102 #[derive(Clone)]
103 pub struct TagHistory {
104 pub created: (String, SpanData),
105 pub invalidated: Option<(String, SpanData)>,
106 pub protected: Option<(String, SpanData)>,
107 }
108
109 pub struct DiagnosticCxBuilder<'ecx, 'mir, 'tcx> {
110 operation: Operation,
111 machine: &'ecx MiriMachine<'mir, 'tcx>,
112 }
113
114 pub struct DiagnosticCx<'history, 'ecx, 'mir, 'tcx> {
115 operation: Operation,
116 machine: &'ecx MiriMachine<'mir, 'tcx>,
117 history: &'history mut AllocHistory,
118 offset: Size,
119 }
120
121 impl<'ecx, 'mir, 'tcx> DiagnosticCxBuilder<'ecx, 'mir, 'tcx> {
build<'history>( self, history: &'history mut AllocHistory, offset: Size, ) -> DiagnosticCx<'history, 'ecx, 'mir, 'tcx>122 pub fn build<'history>(
123 self,
124 history: &'history mut AllocHistory,
125 offset: Size,
126 ) -> DiagnosticCx<'history, 'ecx, 'mir, 'tcx> {
127 DiagnosticCx { operation: self.operation, machine: self.machine, history, offset }
128 }
129
retag( machine: &'ecx MiriMachine<'mir, 'tcx>, cause: RetagCause, new_tag: BorTag, orig_tag: ProvenanceExtra, range: AllocRange, ) -> Self130 pub fn retag(
131 machine: &'ecx MiriMachine<'mir, 'tcx>,
132 cause: RetagCause,
133 new_tag: BorTag,
134 orig_tag: ProvenanceExtra,
135 range: AllocRange,
136 ) -> Self {
137 let operation =
138 Operation::Retag(RetagOp { cause, new_tag, orig_tag, range, permission: None });
139
140 DiagnosticCxBuilder { machine, operation }
141 }
142
read( machine: &'ecx MiriMachine<'mir, 'tcx>, tag: ProvenanceExtra, range: AllocRange, ) -> Self143 pub fn read(
144 machine: &'ecx MiriMachine<'mir, 'tcx>,
145 tag: ProvenanceExtra,
146 range: AllocRange,
147 ) -> Self {
148 let operation = Operation::Access(AccessOp { kind: AccessKind::Read, tag, range });
149 DiagnosticCxBuilder { machine, operation }
150 }
151
write( machine: &'ecx MiriMachine<'mir, 'tcx>, tag: ProvenanceExtra, range: AllocRange, ) -> Self152 pub fn write(
153 machine: &'ecx MiriMachine<'mir, 'tcx>,
154 tag: ProvenanceExtra,
155 range: AllocRange,
156 ) -> Self {
157 let operation = Operation::Access(AccessOp { kind: AccessKind::Write, tag, range });
158 DiagnosticCxBuilder { machine, operation }
159 }
160
dealloc(machine: &'ecx MiriMachine<'mir, 'tcx>, tag: ProvenanceExtra) -> Self161 pub fn dealloc(machine: &'ecx MiriMachine<'mir, 'tcx>, tag: ProvenanceExtra) -> Self {
162 let operation = Operation::Dealloc(DeallocOp { tag });
163 DiagnosticCxBuilder { machine, operation }
164 }
165 }
166
167 impl<'history, 'ecx, 'mir, 'tcx> DiagnosticCx<'history, 'ecx, 'mir, 'tcx> {
unbuild(self) -> DiagnosticCxBuilder<'ecx, 'mir, 'tcx>168 pub fn unbuild(self) -> DiagnosticCxBuilder<'ecx, 'mir, 'tcx> {
169 DiagnosticCxBuilder { machine: self.machine, operation: self.operation }
170 }
171 }
172
173 #[derive(Debug, Clone)]
174 enum Operation {
175 Retag(RetagOp),
176 Access(AccessOp),
177 Dealloc(DeallocOp),
178 }
179
180 #[derive(Debug, Clone)]
181 struct RetagOp {
182 cause: RetagCause,
183 new_tag: BorTag,
184 orig_tag: ProvenanceExtra,
185 range: AllocRange,
186 permission: Option<Permission>,
187 }
188
189 #[derive(Debug, Clone, Copy, PartialEq)]
190 pub enum RetagCause {
191 Normal,
192 FnReturnPlace,
193 FnEntry,
194 TwoPhase,
195 }
196
197 #[derive(Debug, Clone)]
198 struct AccessOp {
199 kind: AccessKind,
200 tag: ProvenanceExtra,
201 range: AllocRange,
202 }
203
204 #[derive(Debug, Clone)]
205 struct DeallocOp {
206 tag: ProvenanceExtra,
207 }
208
209 impl AllocHistory {
new(id: AllocId, item: Item, machine: &MiriMachine<'_, '_>) -> Self210 pub fn new(id: AllocId, item: Item, machine: &MiriMachine<'_, '_>) -> Self {
211 Self {
212 id,
213 base: (item, machine.current_span()),
214 creations: SmallVec::new(),
215 invalidations: SmallVec::new(),
216 protectors: SmallVec::new(),
217 }
218 }
219 }
220
221 impl<'history, 'ecx, 'mir, 'tcx> DiagnosticCx<'history, 'ecx, 'mir, 'tcx> {
start_grant(&mut self, perm: Permission)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)
225 };
226 op.permission = Some(perm);
227
228 let last_creation = &mut self.history.creations.last_mut().unwrap();
229 match last_creation.retag.permission {
230 None => {
231 last_creation.retag.permission = Some(perm);
232 }
233 Some(previous) =>
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);
242 },
243 }
244 }
245
log_creation(&mut self)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")
249 };
250 self.history
251 .creations
252 .push(Creation { retag: op.clone(), span: self.machine.current_span() });
253 }
254
log_invalidation(&mut self, tag: BorTag)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();
261 }
262 (*range, InvalidationCause::Retag(permission.unwrap(), *cause))
263 }
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
268 // gone now.
269 return;
270 }
271 };
272 self.history.invalidations.push(Invalidation { tag, range, span, cause });
273 }
274
log_protector(&mut self)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")
278 };
279 self.history
280 .protectors
281 .push(Protection { tag: op.new_tag, span: self.machine.current_span() });
282 }
283
get_logs_relevant_to( &self, tag: BorTag, protector_tag: Option<BorTag>, ) -> Option<TagHistory>284 pub fn get_logs_relevant_to(
285 &self,
286 tag: BorTag,
287 protector_tag: Option<BorTag>,
288 ) -> Option<TagHistory> {
289 let Some(created) = self.history
290 .creations
291 .iter()
292 .rev()
293 .find_map(|event| {
294 // First, look for a Creation event where the tag and the offset matches. This
295 // ensures that we pick the right Creation event when a retag isn't uniform due to
296 // Freeze.
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)
301 {
302 Some(event.generate_diagnostic())
303 } else {
304 None
305 }
306 })
307 .or_else(|| {
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())
314 } else {
315 None
316 }
317 })
318 }).or_else(|| {
319 // If we didn't find a retag that created this tag, it might be the base tag of
320 // this allocation.
321 if self.history.base.0.tag() == tag {
322 Some((
323 format!("{tag:?} was created here, as the base tag for {:?}", self.history.id),
324 self.history.base.1.data()
325 ))
326 } else {
327 None
328 }
329 }) else {
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.
332 return None;
333 };
334
335 let invalidated = self.history.invalidations.iter().rev().find_map(|event| {
336 if event.tag == tag { Some(event.generate_diagnostic()) } else { None }
337 });
338
339 let protected = protector_tag
340 .and_then(|protector| {
341 self.history.protectors.iter().find(|protection| protection.tag == protector)
342 })
343 .map(|protection| {
344 let protected_tag = protection.tag;
345 (format!("{protected_tag:?} is this argument"), protection.span.data())
346 });
347
348 Some(TagHistory { created, invalidated, protected })
349 }
350
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
grant_error(&self, stack: &Stack) -> InterpError<'tcx>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")
356 };
357 let perm =
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}]",
361 op.orig_tag,
362 perm,
363 self.history.id,
364 self.offset.bytes(),
365 );
366 err_sb_ub(
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)),
370 )
371 }
372
373 /// Report a descriptive error when `access` is not permitted based on `tag`.
374 #[inline(never)] // This is only called on fatal code paths
access_error(&self, stack: &Stack) -> InterpError<'tcx>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),
381 };
382 let action = format!(
383 "attempting a {access} using {tag:?} at {alloc_id:?}[{offset:#x}]",
384 access = op.kind,
385 tag = op.tag,
386 alloc_id = self.history.id,
387 offset = self.offset.bytes(),
388 );
389 err_sb_ub(
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)),
393 )
394 }
395
396 #[inline(never)] // This is only called on fatal code paths
protector_error(&self, item: &Item, kind: ProtectorKind) -> InterpError<'tcx>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",
401 };
402 let call_id = self
403 .machine
404 .threads
405 .all_stacks()
406 .flatten()
407 .map(|frame| {
408 frame.extra.borrow_tracker.as_ref().expect("we should have borrow tracking data")
409 })
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(_) =>
415 err_sb_ub(
416 format!("deallocating while item {item:?} is {protected} by call {call_id:?}",),
417 None,
418 None,
419 ),
420 Operation::Retag(RetagOp { orig_tag: tag, .. })
421 | Operation::Access(AccessOp { tag, .. }) =>
422 err_sb_ub(
423 format!(
424 "not granting access to tag {tag:?} because that would remove {item:?} which is {protected} because it is an argument of call {call_id:?}",
425 ),
426 None,
427 tag.and_then(|tag| self.get_logs_relevant_to(tag, Some(item.tag()))),
428 ),
429 }
430 }
431
432 #[inline(never)] // This is only called on fatal code paths
dealloc_error(&self, stack: &Stack) -> InterpError<'tcx>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")
436 };
437 err_sb_ub(
438 format!(
439 "attempting deallocation using {tag:?} at {alloc_id:?}{cause}",
440 tag = op.tag,
441 alloc_id = self.history.id,
442 cause = error_cause(stack, op.tag),
443 ),
444 None,
445 op.tag.and_then(|tag| self.get_logs_relevant_to(tag, None)),
446 )
447 }
448
449 #[inline(never)]
check_tracked_tag_popped(&self, item: &Item, global: &GlobalStateInner)450 pub fn check_tracked_tag_popped(&self, item: &Item, global: &GlobalStateInner) {
451 if !global.tracked_pointer_tags.contains(&item.tag()) {
452 return;
453 }
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");
461 format!(
462 " due to {permission:?} retag from {orig_tag:?} (that retag created {new_tag:?})"
463 )
464 }
465 };
466
467 self.machine.emit_diagnostic(NonHaltingDiagnostic::PoppedPointerTag(*item, cause));
468 }
469 }
470
operation_summary(operation: &str, alloc_id: AllocId, alloc_range: AllocRange) -> String471 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:?}")
473 }
474
error_cause(stack: &Stack, prov_extra: ProvenanceExtra) -> &'static str475 fn error_cause(stack: &Stack, prov_extra: ProvenanceExtra) -> &'static str {
476 if let ProvenanceExtra::Concrete(tag) = prov_extra {
477 if (0..stack.len())
478 .map(|i| stack.get(i).unwrap())
479 .any(|item| item.tag() == tag && item.perm() != Permission::Disabled)
480 {
481 ", but that tag only grants SharedReadOnly permission for this location"
482 } else {
483 ", but that tag does not exist in the borrow stack for this location"
484 }
485 } else {
486 ", but no exposed tags have suitable permission in the borrow stack for this location"
487 }
488 }
489
490 impl RetagCause {
summary(&self) -> String491 fn summary(&self) -> String {
492 match self {
493 RetagCause::Normal => "retag",
494 RetagCause::FnEntry => "function-entry retag",
495 RetagCause::FnReturnPlace => "return-place retag",
496 RetagCause::TwoPhase => "two-phase retag",
497 }
498 .to_string()
499 }
500 }
501