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 pub in_field: bool,
195}
196
197#[derive(Debug, Clone, Copy, PartialEq)]
198pub enum RetagCause {
199 Normal,
200 InPlaceFnPassing,
201 FnEntry,
202 TwoPhase,
203}
204
205#[derive(Debug, Clone)]
206struct AccessOp {
207 kind: AccessKind,
208 tag: ProvenanceExtra,
209 range: AllocRange,
210}
211
212#[derive(Debug, Clone)]
213struct DeallocOp {
214 tag: ProvenanceExtra,
215}
216
217impl AllocHistory {
218 pub fn new(id: AllocId, item: Item, machine: &MiriMachine<'_>) -> Self {
219 Self {
220 id,
221 root: (item, machine.current_user_relevant_span()),
222 creations: SmallVec::new(),
223 invalidations: SmallVec::new(),
224 protectors: SmallVec::new(),
225 }
226 }
227
228 pub fn retain(&mut self, live_tags: &FxHashSet<BorTag>) {
229 self.invalidations.retain(|event| live_tags.contains(&event.tag));
230 self.creations.retain(|event| live_tags.contains(&event.retag.new_tag));
231 self.protectors.retain(|event| live_tags.contains(&event.tag));
232 }
233}
234
235impl<'history, 'ecx, 'tcx> DiagnosticCx<'history, 'ecx, 'tcx> {
236 pub fn start_grant(&mut self, perm: Permission) {
237 let Operation::Retag(op) = &mut self.operation else {
238 unreachable!(
239 "start_grant must only be called during a retag, this is: {:?}",
240 self.operation
241 )
242 };
243 op.permission = Some(perm);
244
245 let last_creation = &mut self.history.creations.last_mut().unwrap();
246 match last_creation.retag.permission {
247 None => {
248 last_creation.retag.permission = Some(perm);
249 }
250 Some(previous) =>
251 if previous != perm {
252 let previous_range = last_creation.retag.range;
254 last_creation.retag.range = alloc_range(previous_range.start, self.offset);
255 let mut new_event = last_creation.clone();
256 new_event.retag.range = alloc_range(self.offset, previous_range.end());
257 new_event.retag.permission = Some(perm);
258 self.history.creations.push(new_event);
259 },
260 }
261 }
262
263 pub fn log_creation(&mut self) {
264 let Operation::Retag(op) = &self.operation else {
265 unreachable!("log_creation must only be called during a retag")
266 };
267 self.history
268 .creations
269 .push(Creation { retag: op.clone(), span: self.machine.current_user_relevant_span() });
270 }
271
272 pub fn log_invalidation(&mut self, tag: BorTag) {
273 let mut span = self.machine.current_user_relevant_span();
274 let (range, cause) = match &self.operation {
275 Operation::Retag(RetagOp { info, range, permission, .. }) => {
276 if info.cause == RetagCause::FnEntry {
277 span = self.machine.caller_span();
278 }
279 (*range, InvalidationCause::Retag(permission.unwrap(), *info))
280 }
281 Operation::Access(AccessOp { kind, range, .. }) =>
282 (*range, InvalidationCause::Access(*kind)),
283 Operation::Dealloc(_) => {
284 return;
287 }
288 };
289 self.history.invalidations.push(Invalidation { tag, range, span, cause });
290 }
291
292 pub fn log_protector(&mut self) {
293 let Operation::Retag(op) = &self.operation else {
294 unreachable!("Protectors can only be created during a retag")
295 };
296 self.history
297 .protectors
298 .push(Protection { tag: op.new_tag, span: self.machine.current_user_relevant_span() });
299 }
300
301 pub fn get_logs_relevant_to(
302 &self,
303 tag: BorTag,
304 protector_tag: Option<BorTag>,
305 ) -> Option<TagHistory> {
306 let Some(created) = self
307 .history
308 .creations
309 .iter()
310 .rev()
311 .find_map(|event| {
312 let range = event.retag.range;
316 if event.retag.new_tag == tag
317 && self.offset >= range.start
318 && self.offset < (range.start + range.size)
319 {
320 Some(event.generate_diagnostic())
321 } else {
322 None
323 }
324 })
325 .or_else(|| {
326 self.history.creations.iter().rev().find_map(|event| {
330 if event.retag.new_tag == tag {
331 Some(event.generate_diagnostic())
332 } else {
333 None
334 }
335 })
336 })
337 .or_else(|| {
338 if self.history.root.0.tag() == tag {
341 Some((
342 format!(
343 "{tag:?} was created here, as the root tag for {}",
344 self.history.id
345 ),
346 self.history.root.1.data(),
347 ))
348 } else {
349 None
350 }
351 })
352 else {
353 return None;
356 };
357
358 let invalidated = self.history.invalidations.iter().rev().find_map(|event| {
359 if event.tag == tag { Some(event.generate_diagnostic()) } else { None }
360 });
361
362 let protected = protector_tag
363 .and_then(|protector| {
364 self.history.protectors.iter().find(|protection| protection.tag == protector)
365 })
366 .map(|protection| {
367 let protected_tag = protection.tag;
368 (format!("{protected_tag:?} is this argument"), protection.span.data())
369 });
370
371 Some(TagHistory { created, invalidated, protected })
372 }
373
374 #[inline(never)] pub(super) fn grant_error(&self, stack: &Stack) -> InterpErrorKind<'tcx> {
377 let Operation::Retag(op) = &self.operation else {
378 unreachable!("grant_error should only be called during a retag")
379 };
380 let perm =
381 op.permission.expect("`start_grant` must be called before calling `grant_error`");
382 let action = format!(
383 "trying to retag from {:?} for {:?} permission at {}[{:#x}]",
384 op.orig_tag,
385 perm,
386 self.history.id,
387 self.offset.bytes(),
388 );
389 let mut helps = vec![operation_summary(&op.info.summary(), self.history.id, op.range)];
390 if op.info.in_field {
391 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"));
392 }
393 err_sb_ub(
394 format!("{action}{}", error_cause(stack, op.orig_tag)),
395 helps,
396 op.orig_tag.and_then(|orig_tag| self.get_logs_relevant_to(orig_tag, None)),
397 )
398 }
399
400 #[inline(never)] pub(super) fn access_error(&self, stack: &Stack) -> InterpErrorKind<'tcx> {
403 let op = match &self.operation {
405 Operation::Access(op) => op,
406 Operation::Retag(_) => return self.grant_error(stack),
407 Operation::Dealloc(_) => return self.dealloc_error(stack),
408 };
409 let action = format!(
410 "attempting a {access} using {tag:?} at {alloc_id}[{offset:#x}]",
411 access = op.kind,
412 tag = op.tag,
413 alloc_id = self.history.id,
414 offset = self.offset.bytes(),
415 );
416 err_sb_ub(
417 format!("{action}{}", error_cause(stack, op.tag)),
418 vec![operation_summary("an access", self.history.id, op.range)],
419 op.tag.and_then(|tag| self.get_logs_relevant_to(tag, None)),
420 )
421 }
422
423 #[inline(never)] pub(super) fn protector_error(
425 &self,
426 item: &Item,
427 kind: ProtectorKind,
428 ) -> InterpErrorKind<'tcx> {
429 let protected = match kind {
430 ProtectorKind::WeakProtector => "weakly protected",
431 ProtectorKind::StrongProtector => "strongly protected",
432 };
433 match self.operation {
434 Operation::Dealloc(_) =>
435 err_sb_ub(format!("deallocating while item {item:?} is {protected}",), vec![], None),
436 Operation::Retag(RetagOp { orig_tag: tag, .. })
437 | Operation::Access(AccessOp { tag, .. }) =>
438 err_sb_ub(
439 format!(
440 "not granting access to tag {tag:?} because that would remove {item:?} which is {protected}",
441 ),
442 vec![],
443 tag.and_then(|tag| self.get_logs_relevant_to(tag, Some(item.tag()))),
444 ),
445 }
446 }
447
448 #[inline(never)] pub fn dealloc_error(&self, stack: &Stack) -> InterpErrorKind<'tcx> {
450 let Operation::Dealloc(op) = &self.operation else {
451 unreachable!("dealloc_error should only be called during a deallocation")
452 };
453 err_sb_ub(
454 format!(
455 "attempting deallocation using {tag:?} at {alloc_id}{cause}",
456 tag = op.tag,
457 alloc_id = self.history.id,
458 cause = error_cause(stack, op.tag),
459 ),
460 vec![],
461 op.tag.and_then(|tag| self.get_logs_relevant_to(tag, None)),
462 )
463 }
464
465 #[inline(never)]
466 pub fn check_tracked_tag_popped(&self, item: &Item, global: &GlobalStateInner) {
467 if !global.tracked_pointer_tags.contains(&item.tag()) {
468 return;
469 }
470 let cause = match self.operation {
471 Operation::Dealloc(_) => format!(" due to deallocation"),
472 Operation::Access(AccessOp { kind, tag, .. }) =>
473 format!(" due to {kind:?} access for {tag:?}"),
474 Operation::Retag(RetagOp { orig_tag, permission, new_tag, .. }) => {
475 let permission = permission
476 .expect("start_grant should set the current permission before popping a tag");
477 format!(
478 " due to {permission:?} retag from {orig_tag:?} (that retag created {new_tag:?})"
479 )
480 }
481 };
482
483 self.machine.emit_diagnostic(NonHaltingDiagnostic::PoppedPointerTag(*item, cause));
484 }
485}
486
487fn 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}")
489}
490
491fn error_cause(stack: &Stack, prov_extra: ProvenanceExtra) -> &'static str {
492 if let ProvenanceExtra::Concrete(tag) = prov_extra {
493 if (0..stack.len())
494 .map(|i| stack.get(i).unwrap())
495 .any(|item| item.tag() == tag && item.perm() != Permission::Disabled)
496 {
497 ", but that tag only grants SharedReadOnly permission for this location"
498 } else {
499 ", but that tag does not exist in the borrow stack for this location"
500 }
501 } else {
502 ", but no exposed tags have suitable permission in the borrow stack for this location"
503 }
504}
505
506impl RetagInfo {
507 fn summary(&self) -> String {
508 let mut s = match self.cause {
509 RetagCause::Normal => "retag",
510 RetagCause::FnEntry => "function-entry retag",
511 RetagCause::InPlaceFnPassing => "in-place function argument/return passing protection",
512 RetagCause::TwoPhase => "two-phase retag",
513 }
514 .to_string();
515 if self.in_field {
516 s.push_str(" (of a reference/box inside this compound value)");
517 }
518 s
519 }
520}