]> git.proxmox.com Git - rustc.git/blame - vendor/polonius-engine/src/output/mod.rs
New upstream version 1.44.1+dfsg1
[rustc.git] / vendor / polonius-engine / src / output / mod.rs
CommitLineData
94b46f34
XL
1// Copyright 2017 The Rust Project Developers. See the COPYRIGHT
2// file at the top-level directory of this distribution and at
3// http://rust-lang.org/COPYRIGHT.
4//
5// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
6// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
7// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
8// option. This file may not be copied, modified, or distributed
9// except according to those terms.
10
60c5eb7d
XL
11use datafrog::Relation;
12use rustc_hash::{FxHashMap, FxHashSet};
94b46f34
XL
13use std::borrow::Cow;
14use std::collections::{BTreeMap, BTreeSet};
15
60c5eb7d
XL
16use crate::facts::{AllFacts, Atom, FactTypes};
17
94b46f34 18mod datafrog_opt;
e1599b0c 19mod initialization;
416331ca 20mod liveness;
94b46f34
XL
21mod location_insensitive;
22mod naive;
94b46f34
XL
23
24#[derive(Debug, Clone, Copy)]
25pub enum Algorithm {
60c5eb7d 26 /// Simple rules, but slower to execute
94b46f34 27 Naive,
60c5eb7d
XL
28
29 /// Optimized variant of the rules
94b46f34 30 DatafrogOpt,
60c5eb7d
XL
31
32 /// Fast to compute, but imprecise: there can be false-positives
33 /// but no false-negatives. Tailored for quick "early return" situations.
94b46f34 34 LocationInsensitive,
60c5eb7d
XL
35
36 /// Compares the `Naive` and `DatafrogOpt` variants to ensure they indeed
37 /// compute the same errors.
94b46f34 38 Compare,
60c5eb7d
XL
39
40 /// Combination of the fast `LocationInsensitive` pre-pass, followed by
41 /// the more expensive `DatafrogOpt` variant.
48663c56 42 Hybrid,
94b46f34
XL
43}
44
45impl Algorithm {
0731742a
XL
46 /// Optimized variants that ought to be equivalent to "naive"
47 pub const OPTIMIZED: &'static [Algorithm] = &[Algorithm::DatafrogOpt];
48
48663c56
XL
49 pub fn variants() -> [&'static str; 5] {
50 [
51 "Naive",
52 "DatafrogOpt",
53 "LocationInsensitive",
54 "Compare",
55 "Hybrid",
56 ]
94b46f34
XL
57 }
58}
59
60impl ::std::str::FromStr for Algorithm {
61 type Err = String;
62 fn from_str(s: &str) -> Result<Self, Self::Err> {
63 match s.to_lowercase().as_ref() {
64 "naive" => Ok(Algorithm::Naive),
65 "datafrogopt" => Ok(Algorithm::DatafrogOpt),
66 "locationinsensitive" => Ok(Algorithm::LocationInsensitive),
67 "compare" => Ok(Algorithm::Compare),
48663c56 68 "hybrid" => Ok(Algorithm::Hybrid),
94b46f34 69 _ => Err(String::from(
48663c56 70 "valid values: Naive, DatafrogOpt, LocationInsensitive, Compare, Hybrid",
94b46f34
XL
71 )),
72 }
73 }
74}
75
76#[derive(Clone, Debug)]
60c5eb7d
XL
77pub struct Output<T: FactTypes> {
78 pub errors: FxHashMap<T::Point, Vec<T::Loan>>,
79 pub subset_errors: FxHashMap<T::Point, BTreeSet<(T::Origin, T::Origin)>>,
74b04a01 80 pub move_errors: FxHashMap<T::Point, Vec<T::Path>>,
94b46f34
XL
81
82 pub dump_enabled: bool,
83
84 // these are just for debugging
60c5eb7d
XL
85 pub borrow_live_at: FxHashMap<T::Point, Vec<T::Loan>>,
86 pub restricts: FxHashMap<T::Point, BTreeMap<T::Origin, BTreeSet<T::Loan>>>,
87 pub restricts_anywhere: FxHashMap<T::Origin, BTreeSet<T::Loan>>,
74b04a01 88 pub origin_live_on_entry: FxHashMap<T::Point, Vec<T::Origin>>,
60c5eb7d
XL
89 pub invalidates: FxHashMap<T::Point, Vec<T::Loan>>,
90 pub subset: FxHashMap<T::Point, BTreeMap<T::Origin, BTreeSet<T::Origin>>>,
91 pub subset_anywhere: FxHashMap<T::Origin, BTreeSet<T::Origin>>,
74b04a01
XL
92 pub var_live_on_entry: FxHashMap<T::Point, Vec<T::Variable>>,
93 pub var_drop_live_on_entry: FxHashMap<T::Point, Vec<T::Variable>>,
94 pub path_maybe_initialized_on_exit: FxHashMap<T::Point, Vec<T::Path>>,
60c5eb7d 95 pub known_contains: FxHashMap<T::Origin, BTreeSet<T::Loan>>,
74b04a01 96 pub var_maybe_partly_initialized_on_exit: FxHashMap<T::Point, Vec<T::Variable>>,
94b46f34
XL
97}
98
60c5eb7d
XL
99/// Subset of `AllFacts` dedicated to initialization
100struct InitializationContext<T: FactTypes> {
74b04a01
XL
101 child_path: Vec<(T::Path, T::Path)>,
102 path_is_var: Vec<(T::Path, T::Variable)>,
103 path_assigned_at_base: Vec<(T::Path, T::Point)>,
104 path_moved_at_base: Vec<(T::Path, T::Point)>,
105 path_accessed_at_base: Vec<(T::Path, T::Point)>,
60c5eb7d 106}
0731742a 107
60c5eb7d
XL
108/// Subset of `AllFacts` dedicated to liveness
109struct LivenessContext<T: FactTypes> {
74b04a01
XL
110 var_used_at: Vec<(T::Variable, T::Point)>,
111 var_defined_at: Vec<(T::Variable, T::Point)>,
112 var_dropped_at: Vec<(T::Variable, T::Point)>,
113 use_of_var_derefs_origin: Vec<(T::Variable, T::Origin)>,
114 drop_of_var_derefs_origin: Vec<(T::Variable, T::Origin)>,
94b46f34
XL
115}
116
60c5eb7d
XL
117/// Subset of `AllFacts` dedicated to borrow checking, and data ready to use by the variants
118struct Context<'ctx, T: FactTypes> {
119 // `Relation`s used as static inputs, by all variants
74b04a01 120 origin_live_on_entry: Relation<(T::Origin, T::Point)>,
60c5eb7d
XL
121 invalidates: Relation<(T::Loan, T::Point)>,
122
123 // static inputs used via `Variable`s, by all variants
124 outlives: &'ctx Vec<(T::Origin, T::Origin, T::Point)>,
125 borrow_region: &'ctx Vec<(T::Origin, T::Loan, T::Point)>,
126
127 // static inputs used by variants other than `LocationInsensitive`
128 cfg_node: &'ctx BTreeSet<T::Point>,
129 killed: Relation<(T::Loan, T::Point)>,
130 known_contains: Relation<(T::Origin, T::Loan)>,
131 placeholder_origin: Relation<(T::Origin, ())>,
132 placeholder_loan: Relation<(T::Loan, T::Origin)>,
133
134 // while this static input is unused by `LocationInsensitive`, it's depended on by initialization
135 // and liveness, so already computed by the time we get to borrowcking.
136 cfg_edge: Relation<(T::Point, T::Point)>,
137
138 // Partial results possibly used by other variants as input
139 potential_errors: Option<FxHashSet<T::Loan>>,
140}
141
142impl<T: FactTypes> Output<T> {
143 /// All variants require the same initial preparations, done in multiple
144 /// successive steps:
145 /// - compute initialization data
146 /// - compute liveness
147 /// - prepare static inputs as shared `Relation`s
148 /// - in cases where `LocationInsensitive` variant is ran as a filtering pre-pass,
149 /// partial results can also be stored in the context, so that the following
150 /// variant can use it to prune its own input data
151 pub fn compute(all_facts: &AllFacts<T>, algorithm: Algorithm, dump_enabled: bool) -> Self {
152 let mut result = Output::new(dump_enabled);
153
154 // TODO: remove all the cloning thereafter, but that needs to be done in concert with rustc
155
156 let cfg_edge = all_facts.cfg_edge.clone().into();
157
158 // 1) Initialization
159 let initialization_ctx = InitializationContext {
74b04a01
XL
160 child_path: all_facts.child_path.clone(),
161 path_is_var: all_facts.path_is_var.clone(),
162 path_assigned_at_base: all_facts.path_assigned_at_base.clone(),
163 path_moved_at_base: all_facts.path_moved_at_base.clone(),
164 path_accessed_at_base: all_facts.path_accessed_at_base.clone(),
60c5eb7d
XL
165 };
166
74b04a01
XL
167 let initialization::InitializationResult::<T>(
168 var_maybe_partly_initialized_on_exit,
169 move_errors,
170 ) = initialization::compute(initialization_ctx, &cfg_edge, &mut result);
171
ba9703b0
XL
172 // FIXME: move errors should prevent the computation from continuing: we can't compute
173 // liveness and analyze loans accurately when there are move errors, and should early
174 // return here.
74b04a01
XL
175 for &(path, location) in move_errors.iter() {
176 result.move_errors.entry(location).or_default().push(path);
177 }
60c5eb7d
XL
178
179 // 2) Liveness
180 let liveness_ctx = LivenessContext {
74b04a01
XL
181 var_used_at: all_facts.var_used_at.clone(),
182 var_defined_at: all_facts.var_defined_at.clone(),
183 var_dropped_at: all_facts.var_dropped_at.clone(),
184 use_of_var_derefs_origin: all_facts.use_of_var_derefs_origin.clone(),
185 drop_of_var_derefs_origin: all_facts.drop_of_var_derefs_origin.clone(),
60c5eb7d
XL
186 };
187
74b04a01 188 let mut origin_live_on_entry = liveness::compute_live_origins(
60c5eb7d
XL
189 liveness_ctx,
190 &cfg_edge,
74b04a01 191 var_maybe_partly_initialized_on_exit,
60c5eb7d
XL
192 &mut result,
193 );
194
195 let cfg_node = cfg_edge
196 .iter()
197 .map(|&(point1, _)| point1)
198 .chain(cfg_edge.iter().map(|&(_, point2)| point2))
199 .collect();
200
201 liveness::make_universal_regions_live::<T>(
74b04a01 202 &mut origin_live_on_entry,
60c5eb7d
XL
203 &cfg_node,
204 &all_facts.universal_region,
205 );
206
207 // 3) Borrow checking
208
209 // Prepare data as datafrog relations, ready to join.
210 //
211 // Note: if rustc and polonius had more interaction, we could also delay or avoid
212 // generating some of the facts that are now always present here. For example,
213 // the `LocationInsensitive` variant doesn't use the `killed` or `invalidates`
214 // relations, so we could technically delay passing them from rustc, when
215 // using this or the `Hybrid` variant, to after the pre-pass has made sure
216 // we actually need to compute the full analysis. If these facts happened to
217 // be recorded in separate MIR walks, we might also avoid generating those facts.
218
74b04a01 219 let origin_live_on_entry = origin_live_on_entry.into();
60c5eb7d
XL
220
221 // TODO: also flip the order of this relation's arguments in rustc
222 // from `invalidates(point, loan)` to `invalidates(loan, point)`.
223 // to avoid this allocation.
224 let invalidates = Relation::from_iter(
225 all_facts
226 .invalidates
227 .iter()
228 .map(|&(point, loan)| (loan, point)),
229 );
230
231 let killed = all_facts.killed.clone().into();
232
233 // `known_subset` is a list of all the `'a: 'b` subset relations the user gave:
234 // it's not required to be transitive. `known_contains` is its transitive closure: a list
235 // of all the known placeholder loans that each of these placeholder origins contains.
236 // Given the `known_subset`s `'a: 'b` and `'b: 'c`: in the `known_contains` relation, `'a`
237 // will also contain `'c`'s placeholder loan.
238 let known_subset = all_facts.known_subset.clone().into();
239 let known_contains =
240 Output::<T>::compute_known_contains(&known_subset, &all_facts.placeholder);
241
242 let placeholder_origin: Relation<_> = Relation::from_iter(
243 all_facts
244 .universal_region
245 .iter()
246 .map(|&origin| (origin, ())),
247 );
248
249 let placeholder_loan = Relation::from_iter(
250 all_facts
251 .placeholder
252 .iter()
253 .map(|&(origin, loan)| (loan, origin)),
254 );
255
256 // Ask the variants to compute errors in their own way
257 let mut ctx = Context {
74b04a01 258 origin_live_on_entry,
60c5eb7d
XL
259 invalidates,
260 cfg_edge,
261 cfg_node: &cfg_node,
262 outlives: &all_facts.outlives,
263 borrow_region: &all_facts.borrow_region,
264 killed,
265 known_contains,
266 placeholder_origin,
267 placeholder_loan,
268 potential_errors: None,
269 };
270
271 let errors = match algorithm {
272 Algorithm::LocationInsensitive => location_insensitive::compute(&ctx, &mut result),
273 Algorithm::Naive => {
274 let (errors, subset_errors) = naive::compute(&ctx, &mut result);
275
276 // Record illegal subset errors
277 for &(origin1, origin2, location) in subset_errors.iter() {
278 result
279 .subset_errors
280 .entry(location)
281 .or_default()
282 .insert((origin1, origin2));
283 }
284
285 errors
286 }
287 Algorithm::DatafrogOpt => datafrog_opt::compute(&ctx, &mut result),
288 Algorithm::Hybrid => {
289 // WIP: the `LocationInsensitive` variant doesn't compute any illegal subset
290 // relation errors. So using it as a quick pre-filter for illegal accesses
291 // errors will also end up skipping checking for subset errors.
292
293 // Execute the fast `LocationInsensitive` computation as a pre-pass:
294 // if it finds no possible errors, we don't need to do the more complex
295 // computations as they won't find errors either, and we can return early.
296 let potential_errors = location_insensitive::compute(&ctx, &mut result);
297 if potential_errors.is_empty() {
298 potential_errors
299 } else {
300 // Record these potential errors as they can be used to limit the next
301 // variant's work to only these loans.
302 ctx.potential_errors =
303 Some(potential_errors.iter().map(|&(loan, _)| loan).collect());
304
305 datafrog_opt::compute(&ctx, &mut result)
306 }
94b46f34
XL
307 }
308 Algorithm::Compare => {
60c5eb7d
XL
309 // Ensure the `Naive` and `DatafrogOpt` errors are the same
310 let (naive_errors, _) = naive::compute(&ctx, &mut result);
311 let opt_errors = datafrog_opt::compute(&ctx, &mut result);
312
313 // TODO: compare illegal subset relations errors as well here ?
314
315 let mut naive_errors_by_point = FxHashMap::default();
316 for &(loan, point) in naive_errors.iter() {
317 naive_errors_by_point
318 .entry(point)
74b04a01 319 .or_insert_with(Vec::new)
60c5eb7d
XL
320 .push(loan);
321 }
322
323 let mut opt_errors_by_point = FxHashMap::default();
324 for &(loan, point) in opt_errors.iter() {
325 opt_errors_by_point
326 .entry(point)
74b04a01 327 .or_insert_with(Vec::new)
60c5eb7d
XL
328 .push(loan);
329 }
330
331 if compare_errors(&naive_errors_by_point, &opt_errors_by_point) {
94b46f34
XL
332 panic!(concat!(
333 "The errors reported by the naive algorithm differ from ",
334 "the errors reported by the optimized algorithm. ",
335 "See the error log for details."
336 ));
337 } else {
338 debug!("Naive and optimized algorithms reported the same errors.");
339 }
60c5eb7d
XL
340
341 naive_errors
342 }
343 };
344
345 // Record illegal access errors
346 for &(loan, location) in errors.iter() {
347 result.errors.entry(location).or_default().push(loan);
348 }
349
350 // Record more debugging info when asked to do so
351 if dump_enabled {
74b04a01 352 for &(origin, location) in ctx.origin_live_on_entry.iter() {
60c5eb7d 353 result
74b04a01 354 .origin_live_on_entry
60c5eb7d
XL
355 .entry(location)
356 .or_default()
357 .push(origin);
358 }
359
360 for &(origin, loan) in ctx.known_contains.iter() {
361 result
362 .known_contains
363 .entry(origin)
364 .or_default()
365 .insert(loan);
94b46f34
XL
366 }
367 }
60c5eb7d
XL
368
369 result
370 }
371
372 /// Computes the transitive closure of the `known_subset` relation, so that we have
373 /// the full list of placeholder loans contained by the placeholder origins.
374 fn compute_known_contains(
375 known_subset: &Relation<(T::Origin, T::Origin)>,
74b04a01 376 placeholder: &[(T::Origin, T::Loan)],
60c5eb7d
XL
377 ) -> Relation<(T::Origin, T::Loan)> {
378 let mut iteration = datafrog::Iteration::new();
379 let known_contains = iteration.variable("known_contains");
380
381 // known_contains(Origin1, Loan1) :-
382 // placeholder(Origin1, Loan1).
383 known_contains.extend(placeholder.iter());
384
385 while iteration.changed() {
386 // known_contains(Origin2, Loan1) :-
387 // known_contains(Origin1, Loan1),
388 // known_subset(Origin1, Origin2).
389 known_contains.from_join(
390 &known_contains,
391 known_subset,
392 |&_origin1, &loan1, &origin2| (origin2, loan1),
393 );
394 }
395
396 known_contains.complete()
94b46f34
XL
397 }
398
399 fn new(dump_enabled: bool) -> Self {
400 Output {
60c5eb7d
XL
401 errors: FxHashMap::default(),
402 subset_errors: FxHashMap::default(),
403 dump_enabled,
94b46f34
XL
404 borrow_live_at: FxHashMap::default(),
405 restricts: FxHashMap::default(),
406 restricts_anywhere: FxHashMap::default(),
74b04a01 407 origin_live_on_entry: FxHashMap::default(),
94b46f34 408 invalidates: FxHashMap::default(),
74b04a01 409 move_errors: FxHashMap::default(),
94b46f34
XL
410 subset: FxHashMap::default(),
411 subset_anywhere: FxHashMap::default(),
74b04a01
XL
412 var_live_on_entry: FxHashMap::default(),
413 var_drop_live_on_entry: FxHashMap::default(),
414 path_maybe_initialized_on_exit: FxHashMap::default(),
415 var_maybe_partly_initialized_on_exit: FxHashMap::default(),
60c5eb7d 416 known_contains: FxHashMap::default(),
94b46f34
XL
417 }
418 }
419
60c5eb7d 420 pub fn errors_at(&self, location: T::Point) -> &[T::Loan] {
94b46f34
XL
421 match self.errors.get(&location) {
422 Some(v) => v,
423 None => &[],
424 }
425 }
426
60c5eb7d 427 pub fn borrows_in_scope_at(&self, location: T::Point) -> &[T::Loan] {
94b46f34
XL
428 match self.borrow_live_at.get(&location) {
429 Some(p) => p,
430 None => &[],
431 }
432 }
433
60c5eb7d
XL
434 pub fn restricts_at(
435 &self,
436 location: T::Point,
437 ) -> Cow<'_, BTreeMap<T::Origin, BTreeSet<T::Loan>>> {
94b46f34
XL
438 assert!(self.dump_enabled);
439 match self.restricts.get(&location) {
440 Some(map) => Cow::Borrowed(map),
441 None => Cow::Owned(BTreeMap::default()),
442 }
443 }
444
60c5eb7d 445 pub fn regions_live_at(&self, location: T::Point) -> &[T::Origin] {
94b46f34 446 assert!(self.dump_enabled);
74b04a01 447 match self.origin_live_on_entry.get(&location) {
94b46f34
XL
448 Some(v) => v,
449 None => &[],
450 }
451 }
452
60c5eb7d
XL
453 pub fn subsets_at(
454 &self,
455 location: T::Point,
456 ) -> Cow<'_, BTreeMap<T::Origin, BTreeSet<T::Origin>>> {
94b46f34
XL
457 assert!(self.dump_enabled);
458 match self.subset.get(&location) {
459 Some(v) => Cow::Borrowed(v),
460 None => Cow::Owned(BTreeMap::default()),
461 }
462 }
463}
464
60c5eb7d
XL
465/// Compares errors reported by Naive implementation with the errors
466/// reported by the optimized implementation.
467fn compare_errors<Loan: Atom, Point: Atom>(
468 all_naive_errors: &FxHashMap<Point, Vec<Loan>>,
469 all_opt_errors: &FxHashMap<Point, Vec<Loan>>,
470) -> bool {
471 let points = all_naive_errors.keys().chain(all_opt_errors.keys());
472
473 let mut differ = false;
474 for point in points {
475 let mut naive_errors = all_naive_errors.get(&point).cloned().unwrap_or_default();
476 naive_errors.sort();
477
478 let mut opt_errors = all_opt_errors.get(&point).cloned().unwrap_or_default();
479 opt_errors.sort();
480
481 for err in naive_errors.iter() {
482 if !opt_errors.contains(err) {
483 error!(
484 "Error {0:?} at {1:?} reported by naive, but not opt.",
485 err, point
486 );
487 differ = true;
488 }
489 }
490
491 for err in opt_errors.iter() {
492 if !naive_errors.contains(err) {
493 error!(
494 "Error {0:?} at {1:?} reported by opt, but not naive.",
495 err, point
496 );
497 differ = true;
498 }
499 }
500 }
501
502 differ
503}
504
94b46f34
XL
505#[cfg(test)]
506mod tests {
507 use super::*;
508
509 impl Atom for usize {
510 fn index(self) -> usize {
511 self
512 }
513 }
514
515 fn compare(
516 errors1: &FxHashMap<usize, Vec<usize>>,
517 errors2: &FxHashMap<usize, Vec<usize>>,
518 ) -> bool {
519 let diff1 = compare_errors(errors1, errors2);
520 let diff2 = compare_errors(errors2, errors1);
521 assert_eq!(diff1, diff2);
522 diff1
523 }
524
525 #[test]
526 fn test_compare_errors() {
527 let empty = FxHashMap::default();
528 assert_eq!(false, compare(&empty, &empty));
529 let mut empty_vec = FxHashMap::default();
530 empty_vec.insert(1, vec![]);
531 empty_vec.insert(2, vec![]);
532 assert_eq!(false, compare(&empty, &empty_vec));
533
534 let mut singleton1 = FxHashMap::default();
535 singleton1.insert(1, vec![10]);
536 assert_eq!(false, compare(&singleton1, &singleton1));
537 let mut singleton2 = FxHashMap::default();
538 singleton2.insert(1, vec![11]);
539 assert_eq!(false, compare(&singleton2, &singleton2));
540 let mut singleton3 = FxHashMap::default();
541 singleton3.insert(2, vec![10]);
542 assert_eq!(false, compare(&singleton3, &singleton3));
543
544 assert_eq!(true, compare(&singleton1, &singleton2));
545 assert_eq!(true, compare(&singleton2, &singleton3));
546 assert_eq!(true, compare(&singleton1, &singleton3));
547
548 assert_eq!(true, compare(&empty, &singleton1));
549 assert_eq!(true, compare(&empty, &singleton2));
550 assert_eq!(true, compare(&empty, &singleton3));
551
552 let mut errors1 = FxHashMap::default();
553 errors1.insert(1, vec![11]);
554 errors1.insert(2, vec![10]);
555 assert_eq!(false, compare(&errors1, &errors1));
556 assert_eq!(true, compare(&errors1, &singleton1));
557 assert_eq!(true, compare(&errors1, &singleton2));
558 assert_eq!(true, compare(&errors1, &singleton3));
559 }
560}