1use std::fmt;
2
3use rustc_abi::Size;
4use rustc_data_structures::fx::FxHashSet;
5use rustc_span::{Span, SpanData};
6use smallvec::SmallVec;
7
8use crate::borrow_tracker::{AccessKind, GlobalStateInner, ProtectorKind};
9use crate::*;
10
11fn err_sb_ub<'tcx>(
13 msg: String,
14 help: Vec<String>,
15 history: Option<TagHistory>,
16) -> InterpErrorKind<'tcx> {
17 err_machine_stop!(TerminationInfo::StackedBorrowsUb { msg, help, history })
18}
19
20#[derive(Clone, Debug)]
21pub struct AllocHistory {
22 id: AllocId,
23 root: (Item, Span),
24 creations: smallvec::SmallVec<[Creation; 1]>,
25 invalidations: smallvec::SmallVec<[Invalidation; 1]>,
26 protectors: smallvec::SmallVec<[Protection; 1]>,
27}
28
29#[derive(Clone, Debug)]
30struct Creation {
31 retag: RetagOp,
32 span: Span,
33}
34
35impl Creation {
36 fn generate_diagnostic(&self) -> (String, SpanData) {
37 let tag = self.retag.new_tag;
38 if let Some(perm) = self.retag.permission {
39 (
40 format!("{tag:?} was created by a {perm:?} retag at offsets {}", self.retag.range),
41 self.span.data(),
42 )
43 } else {
44 assert!(self.retag.range.size == Size::ZERO);
45 (
46 format!(
47 "{tag:?} would have been created here, but this is a zero-size retag ({}) so the tag in question does not exist anywhere",
48 self.retag.range,
49 ),
50 self.span.data(),
51 )
52 }
53 }
54}
55
56#[derive(Clone, Debug)]
57struct Invalidation {
58 tag: BorTag,
59 range: AllocRange,
60 span: Span,
61 cause: InvalidationCause,
62}
63
64#[derive(Clone, Debug)]
65enum InvalidationCause {
66 Access(AccessKind),
67 Retag(Permission, RetagInfo),
68}
69
70impl Invalidation {
71 fn generate_diagnostic(&self) -> (String, SpanData) {
72 let message = if matches!(
73 self.cause,
74 InvalidationCause::Retag(_, RetagInfo { cause: RetagCause::FnEntry, .. })
75 ) {
76 format!(
79 "{:?} was later invalidated at offsets {} by a {} inside this call",
80 self.tag, self.range, self.cause
81 )
82 } else {
83 format!(
84 "{:?} was later invalidated at offsets {} by a {}",
85 self.tag, self.range, self.cause
86 )
87 };
88 (message, self.span.data())
89 }
90}
91
92impl fmt::Display for InvalidationCause {
93 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
94 match self {
95 InvalidationCause::Access(kind) => write!(f, "{kind}"),
96 InvalidationCause::Retag(perm, info) =>
97 write!(f, "{perm:?} {retag}", retag = info.summary()),
98 }
99 }
100}
101
102#[derive(Clone, Debug)]
103struct Protection {
104 tag: BorTag,
105 span: Span,
106}
107
108#[derive(Clone)]
109pub struct TagHistory {
110 pub created: (String, SpanData),
111 pub invalidated: Option<(String, SpanData)>,
112 pub protected: Option<(String, SpanData)>,
113}
114
115pub struct DiagnosticCxBuilder<'ecx, 'tcx> {
116 operation: Operation,
117 machine: &'ecx MiriMachine<'tcx>,
118}
119
120pub struct DiagnosticCx<'history, 'ecx, 'tcx> {
121 operation: Operation,
122 machine: &'ecx MiriMachine<'tcx>,
123 history: &'history mut AllocHistory,
124 offset: Size,
125}
126
127impl<'ecx, 'tcx> DiagnosticCxBuilder<'ecx, 'tcx> {
128 pub fn build<'history>(
129 self,
130 history: &'history mut AllocHistory,
131 offset: Size,
132 ) -> DiagnosticCx<'history, 'ecx, 'tcx> {
133 DiagnosticCx { operation: self.operation, machine: self.machine, history, offset }
134 }
135
136 pub fn retag(
137 machine: &'ecx MiriMachine<'tcx>,
138 info: RetagInfo,
139 new_tag: BorTag,
140 orig_tag: ProvenanceExtra,
141 range: AllocRange,
142 ) -> Self {
143 let operation =
144 Operation::Retag(RetagOp { info, new_tag, orig_tag, range, permission: None });
145
146 DiagnosticCxBuilder { machine, operation }
147 }
148
149 pub fn read(machine: &'ecx MiriMachine<'tcx>, tag: ProvenanceExtra, range: AllocRange) -> Self {
150 let operation = Operation::Access(AccessOp { kind: AccessKind::Read, tag, range });
151 DiagnosticCxBuilder { machine, operation }
152 }
153
154 pub fn write(
155 machine: &'ecx MiriMachine<'tcx>,
156 tag: ProvenanceExtra,
157 range: AllocRange,
158 ) -> Self {
159 let operation = Operation::Access(AccessOp { kind: AccessKind::Write, tag, range });
160 DiagnosticCxBuilder { machine, operation }
161 }
162
163 pub fn dealloc(machine: &'ecx MiriMachine<'tcx>, tag: ProvenanceExtra) -> Self {
164 let operation = Operation::Dealloc(DeallocOp { tag });
165 DiagnosticCxBuilder { machine, operation }
166 }
167}
168
169impl<'history, 'ecx, 'tcx> DiagnosticCx<'history, 'ecx, 'tcx> {
170 pub fn unbuild(self) -> DiagnosticCxBuilder<'ecx, 'tcx> {
171 DiagnosticCxBuilder { machine: self.machine, operation: self.operation }
172 }
173}
174
175#[derive(Debug, Clone)]
176enum Operation {
177 Retag(RetagOp),
178 Access(AccessOp),
179 Dealloc(DeallocOp),
180}
181
182#[derive(Debug, Clone)]
183struct RetagOp {
184 info: RetagInfo,
185 new_tag: BorTag,
186 orig_tag: ProvenanceExtra,
187 range: AllocRange,
188 permission: Option<Permission>,
189}
190
191#[derive(Debug, Clone, Copy, PartialEq)]
192pub struct RetagInfo {
193 pub cause: RetagCause,
194}
195
196#[derive(Debug, Clone, Copy, PartialEq)]
197pub enum RetagCause {
198 Normal,
199 InPlaceFnPassing,
200 FnEntry,
201 TwoPhase,
202}
203
204#[derive(Debug, Clone)]
205struct AccessOp {
206 kind: AccessKind,
207 tag: ProvenanceExtra,
208 range: AllocRange,
209}
210
211#[derive(Debug, Clone)]
212struct DeallocOp {
213 tag: ProvenanceExtra,
214}
215
216impl AllocHistory {
217 pub fn new(id: AllocId, item: Item, machine: &MiriMachine<'_>) -> Self {
218 Self {
219 id,
220 root: (item, machine.current_user_relevant_span()),
221 creations: SmallVec::new(),
222 invalidations: SmallVec::new(),
223 protectors: SmallVec::new(),
224 }
225 }
226
227 pub fn retain(&mut self, live_tags: &FxHashSet<BorTag>) {
228 self.invalidations.retain(|event| live_tags.contains(&event.tag));
229 self.creations.retain(|event| live_tags.contains(&event.retag.new_tag));
230 self.protectors.retain(|event| live_tags.contains(&event.tag));
231 }
232}
233
234impl<'history, 'ecx, 'tcx> DiagnosticCx<'history, 'ecx, 'tcx> {
235 pub fn start_grant(&mut self, perm: Permission) {
236 let Operation::Retag(op) = &mut self.operation else {
237 unreachable!(
238 "start_grant must only be called during a retag, this is: {:?}",
239 self.operation
240 )
241 };
242 op.permission = Some(perm);
243
244 let last_creation = &mut self.history.creations.last_mut().unwrap();
245 match last_creation.retag.permission {
246 None => {
247 last_creation.retag.permission = Some(perm);
248 }
249 Some(previous) =>
250 if previous != perm {
251 let previous_range = last_creation.retag.range;
253 last_creation.retag.range = alloc_range(previous_range.start, self.offset);
254 let mut new_event = last_creation.clone();
255 new_event.retag.range = alloc_range(self.offset, previous_range.end());
256 new_event.retag.permission = Some(perm);
257 self.history.creations.push(new_event);
258 },
259 }
260 }
261
262 pub fn log_creation(&mut self) {
263 let Operation::Retag(op) = &self.operation else {
264 unreachable!("log_creation must only be called during a retag")
265 };
266 self.history
267 .creations
268 .push(Creation { retag: op.clone(), span: self.machine.current_user_relevant_span() });
269 }
270
271 pub fn log_invalidation(&mut self, tag: BorTag) {
272 let mut span = self.machine.current_user_relevant_span();
273 let (range, cause) = match &self.operation {
274 Operation::Retag(RetagOp { info, range, permission, .. }) => {
275 if info.cause == RetagCause::FnEntry {
276 span = self.machine.caller_span();
277 }
278 (*range, InvalidationCause::Retag(permission.unwrap(), *info))
279 }
280 Operation::Access(AccessOp { kind, range, .. }) =>
281 (*range, InvalidationCause::Access(*kind)),
282 Operation::Dealloc(_) => {
283 return;
286 }
287 };
288 self.history.invalidations.push(Invalidation { tag, range, span, cause });
289 }
290
291 pub fn log_protector(&mut self) {
292 let Operation::Retag(op) = &self.operation else {
293 unreachable!("Protectors can only be created during a retag")
294 };
295 self.history
296 .protectors
297 .push(Protection { tag: op.new_tag, span: self.machine.current_user_relevant_span() });
298 }
299
300 pub fn get_logs_relevant_to(
301 &self,
302 tag: BorTag,
303 protector_tag: Option<BorTag>,
304 ) -> Option<TagHistory> {
305 let Some(created) = self
306 .history
307 .creations
308 .iter()
309 .rev()
310 .find_map(|event| {
311 let range = event.retag.range;
315 if event.retag.new_tag == tag
316 && self.offset >= range.start
317 && self.offset < (range.start + range.size)
318 {
319 Some(event.generate_diagnostic())
320 } else {
321 None
322 }
323 })
324 .or_else(|| {
325 self.history.creations.iter().rev().find_map(|event| {
329 if event.retag.new_tag == tag {
330 Some(event.generate_diagnostic())
331 } else {
332 None
333 }
334 })
335 })
336 .or_else(|| {
337 if self.history.root.0.tag() == tag {
340 Some((
341 format!(
342 "{tag:?} was created here, as the root tag for {}",
343 self.history.id
344 ),
345 self.history.root.1.data(),
346 ))
347 } else {
348 None
349 }
350 })
351 else {
352 return None;
355 };
356
357 let invalidated = self.history.invalidations.iter().rev().find_map(|event| {
358 if event.tag == tag { Some(event.generate_diagnostic()) } else { None }
359 });
360
361 let protected = protector_tag
362 .and_then(|protector| {
363 self.history.protectors.iter().find(|protection| protection.tag == protector)
364 })
365 .map(|protection| {
366 let protected_tag = protection.tag;
367 (format!("{protected_tag:?} is this argument"), protection.span.data())
368 });
369
370 Some(TagHistory { created, invalidated, protected })
371 }
372
373 #[inline(never)] pub(super) fn grant_error(&self, stack: &Stack) -> InterpErrorKind<'tcx> {
376 let Operation::Retag(op) = &self.operation else {
377 unreachable!("grant_error should only be called during a retag")
378 };
379 let perm =
380 op.permission.expect("`start_grant` must be called before calling `grant_error`");
381 let action = format!(
382 "trying to retag from {:?} for {:?} permission at {}[{:#x}]",
383 op.orig_tag,
384 perm,
385 self.history.id,
386 self.offset.bytes(),
387 );
388 let helps = vec![operation_summary(&op.info.summary(), self.history.id, op.range)];
389 err_sb_ub(
390 format!("{action}{}", error_cause(stack, op.orig_tag)),
391 helps,
392 op.orig_tag.and_then(|orig_tag| self.get_logs_relevant_to(orig_tag, None)),
393 )
394 }
395
396 #[inline(never)] pub(super) fn access_error(&self, stack: &Stack) -> InterpErrorKind<'tcx> {
399 let op = match &self.operation {
401 Operation::Access(op) => op,
402 Operation::Retag(_) => return self.grant_error(stack),
403 Operation::Dealloc(_) => return self.dealloc_error(stack),
404 };
405 let action = format!(
406 "attempting a {access} using {tag:?} at {alloc_id}[{offset:#x}]",
407 access = op.kind,
408 tag = op.tag,
409 alloc_id = self.history.id,
410 offset = self.offset.bytes(),
411 );
412 err_sb_ub(
413 format!("{action}{}", error_cause(stack, op.tag)),
414 vec![operation_summary("an access", self.history.id, op.range)],
415 op.tag.and_then(|tag| self.get_logs_relevant_to(tag, None)),
416 )
417 }
418
419 #[inline(never)] pub(super) fn protector_error(
421 &self,
422 item: &Item,
423 kind: ProtectorKind,
424 ) -> InterpErrorKind<'tcx> {
425 let protected = match kind {
426 ProtectorKind::WeakProtector => "weakly protected",
427 ProtectorKind::StrongProtector => "strongly protected",
428 };
429 match self.operation {
430 Operation::Dealloc(_) =>
431 err_sb_ub(format!("deallocating while item {item:?} is {protected}",), vec![], None),
432 Operation::Retag(RetagOp { orig_tag: tag, .. })
433 | Operation::Access(AccessOp { tag, .. }) =>
434 err_sb_ub(
435 format!(
436 "not granting access to tag {tag:?} because that would remove {item:?} which is {protected}",
437 ),
438 vec![],
439 tag.and_then(|tag| self.get_logs_relevant_to(tag, Some(item.tag()))),
440 ),
441 }
442 }
443
444 #[inline(never)] pub fn dealloc_error(&self, stack: &Stack) -> InterpErrorKind<'tcx> {
446 let Operation::Dealloc(op) = &self.operation else {
447 unreachable!("dealloc_error should only be called during a deallocation")
448 };
449 err_sb_ub(
450 format!(
451 "attempting deallocation using {tag:?} at {alloc_id}{cause}",
452 tag = op.tag,
453 alloc_id = self.history.id,
454 cause = error_cause(stack, op.tag),
455 ),
456 vec![],
457 op.tag.and_then(|tag| self.get_logs_relevant_to(tag, None)),
458 )
459 }
460
461 #[inline(never)]
462 pub fn check_tracked_tag_popped(&self, item: &Item, global: &GlobalStateInner) {
463 if !global.tracked_pointer_tags.contains(&item.tag()) {
464 return;
465 }
466 let cause = match self.operation {
467 Operation::Dealloc(_) => format!(" due to deallocation"),
468 Operation::Access(AccessOp { kind, tag, .. }) =>
469 format!(" due to {kind:?} access for {tag:?}"),
470 Operation::Retag(RetagOp { orig_tag, permission, new_tag, .. }) => {
471 let permission = permission
472 .expect("start_grant should set the current permission before popping a tag");
473 format!(
474 " due to {permission:?} retag from {orig_tag:?} (that retag created {new_tag:?})"
475 )
476 }
477 };
478
479 self.machine.emit_diagnostic(NonHaltingDiagnostic::PoppedPointerTag(*item, cause));
480 }
481}
482
483fn operation_summary(operation: &str, alloc_id: AllocId, alloc_range: AllocRange) -> String {
484 format!("this error occurs as part of {operation} at {alloc_id}{alloc_range}")
485}
486
487fn error_cause(stack: &Stack, prov_extra: ProvenanceExtra) -> &'static str {
488 if let ProvenanceExtra::Concrete(tag) = prov_extra {
489 if (0..stack.len())
490 .map(|i| stack.get(i).unwrap())
491 .any(|item| item.tag() == tag && item.perm() != Permission::Disabled)
492 {
493 ", but that tag only grants SharedReadOnly permission for this location"
494 } else {
495 ", but that tag does not exist in the borrow stack for this location"
496 }
497 } else {
498 ", but no exposed tags have suitable permission in the borrow stack for this location"
499 }
500}
501
502impl RetagInfo {
503 fn summary(&self) -> String {
504 match self.cause {
505 RetagCause::Normal => "retag",
506 RetagCause::FnEntry => "function-entry retag",
507 RetagCause::InPlaceFnPassing => "in-place function argument/return passing protection",
508 RetagCause::TwoPhase => "two-phase retag",
509 }
510 .to_string()
511 }
512}