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