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