]> git.lizzy.rs Git - rust.git/blob - src/tools/miri/src/borrow_tracker/stacked_borrows/diagnostics.rs
Auto merge of #107843 - bjorn3:sync_cg_clif-2023-02-09, r=bjorn3
[rust.git] / src / tools / miri / src / borrow_tracker / stacked_borrows / diagnostics.rs
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 {
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 {
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                 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> {
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
130     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
143     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
152     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
161     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> {
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 {
210     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> {
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
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
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
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
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                 // ensrues 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
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
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
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
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)]
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
471 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
475 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 {
491     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 }