]> git.lizzy.rs Git - rust.git/blob - src/tools/miri/src/stacked_borrows/diagnostics.rs
Rollup merge of #103588 - weihanglo:rustdoc/url-redirect, r=notriddle
[rust.git] / src / tools / miri / src / stacked_borrows / diagnostics.rs
1 use smallvec::SmallVec;
2 use std::fmt;
3
4 use rustc_middle::mir::interpret::{alloc_range, AllocId, AllocRange};
5 use rustc_span::{Span, SpanData};
6 use rustc_target::abi::Size;
7
8 use crate::helpers::CurrentSpan;
9 use crate::stacked_borrows::{err_sb_ub, AccessKind, GlobalStateInner, Permission};
10 use crate::*;
11
12 use rustc_middle::mir::interpret::InterpError;
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 {
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: SbTag,
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 {
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 {
87     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                 if *kind == RetagCause::FnEntry {
92                     write!(f, "{perm:?} FnEntry retag")
93                 } else {
94                     write!(f, "{perm:?} retag")
95                 },
96         }
97     }
98 }
99
100 #[derive(Clone, Debug)]
101 struct Protection {
102     tag: SbTag,
103     span: Span,
104 }
105
106 #[derive(Clone)]
107 pub struct TagHistory {
108     pub created: (String, SpanData),
109     pub invalidated: Option<(String, SpanData)>,
110     pub protected: Option<(String, SpanData)>,
111 }
112
113 pub struct DiagnosticCxBuilder<'span, 'ecx, 'mir, 'tcx> {
114     operation: Operation,
115     // 'span cannot be merged with any other lifetime since they appear invariantly, under the
116     // mutable ref.
117     current_span: &'span mut CurrentSpan<'ecx, 'mir, 'tcx>,
118     threads: &'ecx ThreadManager<'mir, 'tcx>,
119 }
120
121 pub struct DiagnosticCx<'span, 'history, 'ecx, 'mir, 'tcx> {
122     operation: Operation,
123     // 'span and 'history cannot be merged, since when we call `unbuild` we need
124     // to return the exact 'span that was used when calling `build`.
125     current_span: &'span mut CurrentSpan<'ecx, 'mir, 'tcx>,
126     threads: &'ecx ThreadManager<'mir, 'tcx>,
127     history: &'history mut AllocHistory,
128     offset: Size,
129 }
130
131 impl<'span, 'ecx, 'mir, 'tcx> DiagnosticCxBuilder<'span, 'ecx, 'mir, 'tcx> {
132     pub fn build<'history>(
133         self,
134         history: &'history mut AllocHistory,
135         offset: Size,
136     ) -> DiagnosticCx<'span, 'history, 'ecx, 'mir, 'tcx> {
137         DiagnosticCx {
138             operation: self.operation,
139             current_span: self.current_span,
140             threads: self.threads,
141             history,
142             offset,
143         }
144     }
145
146     pub fn retag(
147         current_span: &'span mut CurrentSpan<'ecx, 'mir, 'tcx>,
148         threads: &'ecx ThreadManager<'mir, 'tcx>,
149         cause: RetagCause,
150         new_tag: SbTag,
151         orig_tag: ProvenanceExtra,
152         range: AllocRange,
153     ) -> Self {
154         let operation =
155             Operation::Retag(RetagOp { cause, new_tag, orig_tag, range, permission: None });
156
157         DiagnosticCxBuilder { current_span, threads, operation }
158     }
159
160     pub fn read(
161         current_span: &'span mut CurrentSpan<'ecx, 'mir, 'tcx>,
162         threads: &'ecx ThreadManager<'mir, 'tcx>,
163         tag: ProvenanceExtra,
164         range: AllocRange,
165     ) -> Self {
166         let operation = Operation::Access(AccessOp { kind: AccessKind::Read, tag, range });
167         DiagnosticCxBuilder { current_span, threads, operation }
168     }
169
170     pub fn write(
171         current_span: &'span mut CurrentSpan<'ecx, 'mir, 'tcx>,
172         threads: &'ecx ThreadManager<'mir, 'tcx>,
173         tag: ProvenanceExtra,
174         range: AllocRange,
175     ) -> Self {
176         let operation = Operation::Access(AccessOp { kind: AccessKind::Write, tag, range });
177         DiagnosticCxBuilder { current_span, threads, operation }
178     }
179
180     pub fn dealloc(
181         current_span: &'span mut CurrentSpan<'ecx, 'mir, 'tcx>,
182         threads: &'ecx ThreadManager<'mir, 'tcx>,
183         tag: ProvenanceExtra,
184     ) -> Self {
185         let operation = Operation::Dealloc(DeallocOp { tag });
186         DiagnosticCxBuilder { current_span, threads, operation }
187     }
188 }
189
190 impl<'span, 'history, 'ecx, 'mir, 'tcx> DiagnosticCx<'span, 'history, 'ecx, 'mir, 'tcx> {
191     pub fn unbuild(self) -> DiagnosticCxBuilder<'span, 'ecx, 'mir, 'tcx> {
192         DiagnosticCxBuilder {
193             operation: self.operation,
194             current_span: self.current_span,
195             threads: self.threads,
196         }
197     }
198 }
199
200 #[derive(Debug, Clone)]
201 enum Operation {
202     Retag(RetagOp),
203     Access(AccessOp),
204     Dealloc(DeallocOp),
205 }
206
207 #[derive(Debug, Clone)]
208 struct RetagOp {
209     cause: RetagCause,
210     new_tag: SbTag,
211     orig_tag: ProvenanceExtra,
212     range: AllocRange,
213     permission: Option<Permission>,
214 }
215
216 #[derive(Debug, Clone, Copy, PartialEq)]
217 pub enum RetagCause {
218     Normal,
219     FnReturn,
220     FnEntry,
221     TwoPhase,
222 }
223
224 #[derive(Debug, Clone)]
225 struct AccessOp {
226     kind: AccessKind,
227     tag: ProvenanceExtra,
228     range: AllocRange,
229 }
230
231 #[derive(Debug, Clone)]
232 struct DeallocOp {
233     tag: ProvenanceExtra,
234 }
235
236 impl AllocHistory {
237     pub fn new(id: AllocId, item: Item, current_span: &mut CurrentSpan<'_, '_, '_>) -> Self {
238         Self {
239             id,
240             base: (item, current_span.get()),
241             creations: SmallVec::new(),
242             invalidations: SmallVec::new(),
243             protectors: SmallVec::new(),
244         }
245     }
246 }
247
248 impl<'span, 'history, 'ecx, 'mir, 'tcx> DiagnosticCx<'span, 'history, 'ecx, 'mir, 'tcx> {
249     pub fn start_grant(&mut self, perm: Permission) {
250         let Operation::Retag(op) = &mut self.operation else {
251             unreachable!("start_grant must only be called during a retag, this is: {:?}", self.operation)
252         };
253         op.permission = Some(perm);
254
255         let last_creation = &mut self.history.creations.last_mut().unwrap();
256         match last_creation.retag.permission {
257             None => {
258                 last_creation.retag.permission = Some(perm);
259             }
260             Some(previous) =>
261                 if previous != perm {
262                     // 'Split up' the creation event.
263                     let previous_range = last_creation.retag.range;
264                     last_creation.retag.range = alloc_range(previous_range.start, self.offset);
265                     let mut new_event = last_creation.clone();
266                     new_event.retag.range = alloc_range(self.offset, previous_range.end());
267                     new_event.retag.permission = Some(perm);
268                     self.history.creations.push(new_event);
269                 },
270         }
271     }
272
273     pub fn log_creation(&mut self) {
274         let Operation::Retag(op) = &self.operation else {
275             unreachable!("log_creation must only be called during a retag")
276         };
277         self.history.creations.push(Creation { retag: op.clone(), span: self.current_span.get() });
278     }
279
280     pub fn log_invalidation(&mut self, tag: SbTag) {
281         let mut span = self.current_span.get();
282         let (range, cause) = match &self.operation {
283             Operation::Retag(RetagOp { cause, range, permission, .. }) => {
284                 if *cause == RetagCause::FnEntry {
285                     span = self.current_span.get_caller();
286                 }
287                 (*range, InvalidationCause::Retag(permission.unwrap(), *cause))
288             }
289             Operation::Access(AccessOp { kind, range, .. }) =>
290                 (*range, InvalidationCause::Access(*kind)),
291             _ => unreachable!("Tags can only be invalidated during a retag or access"),
292         };
293         self.history.invalidations.push(Invalidation { tag, range, span, cause });
294     }
295
296     pub fn log_protector(&mut self) {
297         let Operation::Retag(op) = &self.operation else {
298             unreachable!("Protectors can only be created during a retag")
299         };
300         self.history.protectors.push(Protection { tag: op.new_tag, span: self.current_span.get() });
301     }
302
303     pub fn get_logs_relevant_to(
304         &self,
305         tag: SbTag,
306         protector_tag: Option<SbTag>,
307     ) -> Option<TagHistory> {
308         let Some(created) = self.history
309             .creations
310             .iter()
311             .rev()
312             .find_map(|event| {
313                 // First, look for a Creation event where the tag and the offset matches. This
314                 // ensrues that we pick the right Creation event when a retag isn't uniform due to
315                 // Freeze.
316                 let range = event.retag.range;
317                 if event.retag.new_tag == tag
318                     && self.offset >= range.start
319                     && self.offset < (range.start + range.size)
320                 {
321                     Some(event.generate_diagnostic())
322                 } else {
323                     None
324                 }
325             })
326             .or_else(|| {
327                 // If we didn't find anything with a matching offset, just return the event where
328                 // the tag was created. This branch is hit when we use a tag at an offset that
329                 // doesn't have the tag.
330                 self.history.creations.iter().rev().find_map(|event| {
331                     if event.retag.new_tag == tag {
332                         Some(event.generate_diagnostic())
333                     } else {
334                         None
335                     }
336                 })
337             }).or_else(|| {
338                 // If we didn't find a retag that created this tag, it might be the base tag of
339                 // this allocation.
340                 if self.history.base.0.tag() == tag {
341                     Some((
342                         format!("{tag:?} was created here, as the base tag for {:?}", self.history.id),
343                         self.history.base.1.data()
344                     ))
345                 } else {
346                     None
347                 }
348             }) else {
349                 // But if we don't have a creation event, this is related to a wildcard, and there
350                 // is really nothing we can do to help.
351                 return None;
352             };
353
354         let invalidated = self.history.invalidations.iter().rev().find_map(|event| {
355             if event.tag == tag { Some(event.generate_diagnostic()) } else { None }
356         });
357
358         let protected = protector_tag
359             .and_then(|protector| {
360                 self.history.protectors.iter().find(|protection| protection.tag == protector)
361             })
362             .map(|protection| {
363                 let protected_tag = protection.tag;
364                 (format!("{protected_tag:?} is this argument"), protection.span.data())
365             });
366
367         Some(TagHistory { created, invalidated, protected })
368     }
369
370     /// Report a descriptive error when `new` could not be granted from `derived_from`.
371     #[inline(never)] // This is only called on fatal code paths
372     pub fn grant_error(&self, perm: Permission, stack: &Stack) -> InterpError<'tcx> {
373         let Operation::Retag(op) = &self.operation else {
374             unreachable!("grant_error should only be called during a retag")
375         };
376         let action = format!(
377             "trying to retag from {:?} for {:?} permission at {:?}[{:#x}]",
378             op.orig_tag,
379             perm,
380             self.history.id,
381             self.offset.bytes(),
382         );
383         err_sb_ub(
384             format!("{action}{}", error_cause(stack, op.orig_tag)),
385             Some(operation_summary(&op.cause.summary(), self.history.id, op.range)),
386             op.orig_tag.and_then(|orig_tag| self.get_logs_relevant_to(orig_tag, None)),
387         )
388     }
389
390     /// Report a descriptive error when `access` is not permitted based on `tag`.
391     #[inline(never)] // This is only called on fatal code paths
392     pub fn access_error(&self, stack: &Stack) -> InterpError<'tcx> {
393         let Operation::Access(op) = &self.operation  else {
394             unreachable!("access_error should only be called during an access")
395         };
396         let action = format!(
397             "attempting a {access} using {tag:?} at {alloc_id:?}[{offset:#x}]",
398             access = op.kind,
399             tag = op.tag,
400             alloc_id = self.history.id,
401             offset = self.offset.bytes(),
402         );
403         err_sb_ub(
404             format!("{action}{}", error_cause(stack, op.tag)),
405             Some(operation_summary("an access", self.history.id, op.range)),
406             op.tag.and_then(|tag| self.get_logs_relevant_to(tag, None)),
407         )
408     }
409
410     #[inline(never)] // This is only called on fatal code paths
411     pub fn protector_error(&self, item: &Item) -> InterpError<'tcx> {
412         let call_id = self
413             .threads
414             .all_stacks()
415             .flatten()
416             .map(|frame| {
417                 frame.extra.stacked_borrows.as_ref().expect("we should have Stacked Borrows data")
418             })
419             .find(|frame| frame.protected_tags.contains(&item.tag()))
420             .map(|frame| frame.call_id)
421             .unwrap(); // FIXME: Surely we should find something, but a panic seems wrong here?
422         match self.operation {
423             Operation::Dealloc(_) =>
424                 err_sb_ub(
425                     format!(
426                         "deallocating while item {:?} is protected by call {:?}",
427                         item, call_id
428                     ),
429                     None,
430                     None,
431                 ),
432             Operation::Retag(RetagOp { orig_tag: tag, .. })
433             | Operation::Access(AccessOp { tag, .. }) =>
434                 err_sb_ub(
435                     format!(
436                         "not granting access to tag {:?} because that would remove {:?} which is protected because it is an argument of call {:?}",
437                         tag, item, call_id
438                     ),
439                     None,
440                     tag.and_then(|tag| self.get_logs_relevant_to(tag, Some(item.tag()))),
441                 ),
442         }
443     }
444
445     #[inline(never)] // This is only called on fatal code paths
446     pub fn dealloc_error(&self) -> InterpError<'tcx> {
447         let Operation::Dealloc(op) = &self.operation else {
448             unreachable!("dealloc_error should only be called during a deallocation")
449         };
450         err_sb_ub(
451             format!(
452                 "no item granting write access for deallocation to tag {:?} at {:?} found in borrow stack",
453                 op.tag, self.history.id,
454             ),
455             None,
456             op.tag.and_then(|tag| self.get_logs_relevant_to(tag, None)),
457         )
458     }
459
460     #[inline(never)]
461     pub fn check_tracked_tag_popped(&self, item: &Item, global: &GlobalStateInner) {
462         if !global.tracked_pointer_tags.contains(&item.tag()) {
463             return;
464         }
465         let summary = match self.operation {
466             Operation::Dealloc(_) => None,
467             Operation::Access(AccessOp { kind, tag, .. }) => Some((tag, kind)),
468             Operation::Retag(RetagOp { orig_tag, permission, .. }) => {
469                 let kind = match permission
470                     .expect("start_grant should set the current permission before popping a tag")
471                 {
472                     Permission::SharedReadOnly => AccessKind::Read,
473                     Permission::Unique => AccessKind::Write,
474                     Permission::SharedReadWrite | Permission::Disabled => {
475                         panic!("Only SharedReadOnly and Unique retags can pop tags");
476                     }
477                 };
478                 Some((orig_tag, kind))
479             }
480         };
481         self.current_span
482             .machine()
483             .emit_diagnostic(NonHaltingDiagnostic::PoppedPointerTag(*item, summary));
484     }
485 }
486
487 fn 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
491 fn 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
506 impl RetagCause {
507     fn summary(&self) -> String {
508         match self {
509             RetagCause::Normal => "retag",
510             RetagCause::FnEntry => "FnEntry retag",
511             RetagCause::FnReturn => "FnReturn retag",
512             RetagCause::TwoPhase => "two-phase retag",
513         }
514         .to_string()
515     }
516 }