|
| 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 borrow_check::nll::region_infer::{Cause, ConstraintIndex, RegionInferenceContext}; |
| 12 | +use borrow_check::nll::region_infer::values::ToElementIndex; |
| 13 | +use borrow_check::nll::type_check::Locations; |
| 14 | +use rustc::hir::def_id::DefId; |
| 15 | +use rustc::infer::InferCtxt; |
| 16 | +use rustc::infer::error_reporting::nice_region_error::NiceRegionError; |
| 17 | +use rustc::mir::{Location, Mir, StatementKind, TerminatorKind, Rvalue}; |
| 18 | +use rustc::ty::RegionVid; |
| 19 | +use rustc_data_structures::fx::{FxHashMap, FxHashSet}; |
| 20 | +use rustc_data_structures::indexed_vec::IndexVec; |
| 21 | +use syntax_pos::Span; |
| 22 | + |
| 23 | +/// Constraints that are considered interesting can be categorized to |
| 24 | +/// determine why they are interesting. |
| 25 | +#[derive(Debug, Eq, PartialEq, PartialOrd, Ord)] |
| 26 | +enum ConstraintCategory { |
| 27 | + Assignment, |
| 28 | + Cast, |
| 29 | + CallArgument, |
| 30 | + Other, |
| 31 | +} |
| 32 | + |
| 33 | +impl<'tcx> RegionInferenceContext<'tcx> { |
| 34 | + /// When reporting an error, it is useful to be able to determine which constraints influenced |
| 35 | + /// the region being reported as an error. This function finds all of the paths from the |
| 36 | + /// constraint. |
| 37 | + fn find_constraint_paths_from_region( |
| 38 | + &self, |
| 39 | + r0: RegionVid |
| 40 | + ) -> Vec<Vec<ConstraintIndex>> { |
| 41 | + let constraints = self.constraints.clone(); |
| 42 | + |
| 43 | + // Mapping of regions to the previous region and constraint index that led to it. |
| 44 | + let mut previous = FxHashMap(); |
| 45 | + // Regions yet to be visited. |
| 46 | + let mut next = vec! [ r0 ]; |
| 47 | + // Regions that have been visited. |
| 48 | + let mut visited = FxHashSet(); |
| 49 | + // Ends of paths. |
| 50 | + let mut end_regions: Vec<RegionVid> = Vec::new(); |
| 51 | + |
| 52 | + // When we've still got points to visit... |
| 53 | + while let Some(current) = next.pop() { |
| 54 | + // ...take the next point... |
| 55 | + debug!("find_constraint_paths_from_region: current={:?} next={:?}", current, next); |
| 56 | + |
| 57 | + // ...find the edges containing it... |
| 58 | + let mut upcoming = Vec::new(); |
| 59 | + for (index, constraint) in constraints.iter_enumerated() { |
| 60 | + if constraint.sub == current { |
| 61 | + // ...add the regions that join us with to the path we've taken... |
| 62 | + debug!("find_constraint_paths_from_region: index={:?} constraint={:?}", |
| 63 | + index, constraint); |
| 64 | + let next_region = constraint.sup.clone(); |
| 65 | + |
| 66 | + // ...unless we've visited it since this was added... |
| 67 | + if visited.contains(&next_region) { |
| 68 | + debug!("find_constraint_paths_from_region: skipping as visited"); |
| 69 | + continue; |
| 70 | + } |
| 71 | + |
| 72 | + previous.insert(next_region, (index, Some(current))); |
| 73 | + upcoming.push(next_region); |
| 74 | + } |
| 75 | + } |
| 76 | + |
| 77 | + if upcoming.is_empty() { |
| 78 | + // If we didn't find any edges then this is the end of a path... |
| 79 | + debug!("find_constraint_paths_from_region: new end region current={:?}", current); |
| 80 | + end_regions.push(current); |
| 81 | + } else { |
| 82 | + // ...but, if we did find edges, then add these to the regions yet to visit... |
| 83 | + debug!("find_constraint_paths_from_region: extend next upcoming={:?}", upcoming); |
| 84 | + next.extend(upcoming); |
| 85 | + } |
| 86 | + |
| 87 | + // ...and don't visit it again. |
| 88 | + visited.insert(current.clone()); |
| 89 | + debug!("find_constraint_paths_from_region: next={:?} visited={:?}", next, visited); |
| 90 | + } |
| 91 | + |
| 92 | + // Now we've visited each point, compute the final paths. |
| 93 | + let mut paths: Vec<Vec<ConstraintIndex>> = Vec::new(); |
| 94 | + debug!("find_constraint_paths_from_region: end_regions={:?}", end_regions); |
| 95 | + for end_region in end_regions { |
| 96 | + debug!("find_constraint_paths_from_region: end_region={:?}", end_region); |
| 97 | + |
| 98 | + // Get the constraint and region that led to this end point. |
| 99 | + // We can unwrap as we know if end_point was in the vector that it |
| 100 | + // must also be in our previous map. |
| 101 | + let (mut index, mut region) = previous.get(&end_region).unwrap(); |
| 102 | + debug!("find_constraint_paths_from_region: index={:?} region={:?}", index, region); |
| 103 | + |
| 104 | + // Keep track of the indices. |
| 105 | + let mut path: Vec<ConstraintIndex> = vec![index]; |
| 106 | + |
| 107 | + while region.is_some() && region != Some(r0) { |
| 108 | + let p = previous.get(®ion.unwrap()).unwrap(); |
| 109 | + index = p.0; |
| 110 | + region = p.1; |
| 111 | + |
| 112 | + debug!("find_constraint_paths_from_region: index={:?} region={:?}", index, region); |
| 113 | + path.push(index); |
| 114 | + } |
| 115 | + |
| 116 | + // Add to our paths. |
| 117 | + paths.push(path); |
| 118 | + } |
| 119 | + |
| 120 | + debug!("find_constraint_paths_from_region: paths={:?}", paths); |
| 121 | + paths |
| 122 | + } |
| 123 | + |
| 124 | + /// This function will return true if a constraint is interesting and false if a constraint |
| 125 | + /// is not. It is useful in filtering constraint paths to only interesting points. |
| 126 | + fn constraint_is_interesting(&self, index: &ConstraintIndex) -> bool { |
| 127 | + self.constraints.get(*index).filter(|constraint| { |
| 128 | + debug!("constraint_is_interesting: locations={:?} constraint={:?}", |
| 129 | + constraint.locations, constraint); |
| 130 | + if let Locations::Interesting(_) = constraint.locations { true } else { false } |
| 131 | + }).is_some() |
| 132 | + } |
| 133 | + |
| 134 | + /// This function classifies a constraint from a location. |
| 135 | + fn classify_constraint(&self, location: Location, mir: &Mir<'tcx>) -> ConstraintCategory { |
| 136 | + let data = &mir[location.block]; |
| 137 | + if location.statement_index == data.statements.len() { |
| 138 | + if let Some(ref terminator) = data.terminator { |
| 139 | + match terminator.kind { |
| 140 | + TerminatorKind::DropAndReplace { .. } => ConstraintCategory::Assignment, |
| 141 | + TerminatorKind::Call { .. } => ConstraintCategory::CallArgument, |
| 142 | + _ => ConstraintCategory::Other, |
| 143 | + } |
| 144 | + } else { |
| 145 | + ConstraintCategory::Other |
| 146 | + } |
| 147 | + } else { |
| 148 | + let statement = &data.statements[location.statement_index]; |
| 149 | + match statement.kind { |
| 150 | + StatementKind::Assign(_, ref rvalue) => match rvalue { |
| 151 | + Rvalue::Cast(..) => ConstraintCategory::Cast, |
| 152 | + Rvalue::Use(..) => ConstraintCategory::Assignment, |
| 153 | + _ => ConstraintCategory::Other, |
| 154 | + }, |
| 155 | + _ => ConstraintCategory::Other, |
| 156 | + } |
| 157 | + } |
| 158 | + } |
| 159 | + |
| 160 | + /// Report an error because the universal region `fr` was required to outlive |
| 161 | + /// `outlived_fr` but it is not known to do so. For example: |
| 162 | + /// |
| 163 | + /// ``` |
| 164 | + /// fn foo<'a, 'b>(x: &'a u32) -> &'b u32 { x } |
| 165 | + /// ``` |
| 166 | + /// |
| 167 | + /// Here we would be invoked with `fr = 'a` and `outlived_fr = `'b`. |
| 168 | + pub(super) fn report_error( |
| 169 | + &self, |
| 170 | + mir: &Mir<'tcx>, |
| 171 | + infcx: &InferCtxt<'_, '_, 'tcx>, |
| 172 | + mir_def_id: DefId, |
| 173 | + fr: RegionVid, |
| 174 | + outlived_fr: RegionVid, |
| 175 | + blame_span: Span, |
| 176 | + ) { |
| 177 | + // Obviously uncool error reporting. |
| 178 | + |
| 179 | + let fr_name = self.to_error_region(fr); |
| 180 | + let outlived_fr_name = self.to_error_region(outlived_fr); |
| 181 | + |
| 182 | + if let (Some(f), Some(o)) = (fr_name, outlived_fr_name) { |
| 183 | + let tables = infcx.tcx.typeck_tables_of(mir_def_id); |
| 184 | + let nice = NiceRegionError::new_from_span(infcx.tcx, blame_span, o, f, Some(tables)); |
| 185 | + if let Some(_error_reported) = nice.try_report() { |
| 186 | + return; |
| 187 | + } |
| 188 | + } |
| 189 | + |
| 190 | + let constraints = self.find_constraint_paths_from_region(fr.clone()); |
| 191 | + let path = constraints.iter().min_by_key(|p| p.len()).unwrap(); |
| 192 | + debug!("report_error: path={:?}", path); |
| 193 | + |
| 194 | + let path = path.iter() |
| 195 | + .filter(|index| self.constraint_is_interesting(index)) |
| 196 | + .collect::<Vec<&ConstraintIndex>>(); |
| 197 | + debug!("report_error: path={:?}", path); |
| 198 | + |
| 199 | + let mut categorized_path = path.iter().filter_map(|index| { |
| 200 | + self.constraints.get(**index).iter().filter_map(|constraint| { |
| 201 | + let span = constraint.locations.span(mir); |
| 202 | + constraint.locations.from_location().iter().filter_map(|location| { |
| 203 | + let classification = self.classify_constraint(*location, mir); |
| 204 | + Some((classification, span)) |
| 205 | + }).next() |
| 206 | + }).next() |
| 207 | + }).collect::<Vec<(ConstraintCategory, Span)>>(); |
| 208 | + debug!("report_error: categorized_path={:?}", categorized_path); |
| 209 | + |
| 210 | + categorized_path.sort_by(|p0, p1| p0.0.cmp(&p1.0)); |
| 211 | + debug!("report_error: sorted_path={:?}", categorized_path); |
| 212 | + |
| 213 | + if categorized_path.len() > 0 { |
| 214 | + let blame_constraint = &categorized_path[0]; |
| 215 | + |
| 216 | + let mut diag = infcx.tcx.sess.struct_span_err( |
| 217 | + blame_constraint.1, |
| 218 | + &format!("{:?}", blame_constraint.0), |
| 219 | + ); |
| 220 | + |
| 221 | + for secondary in categorized_path.iter().skip(1) { |
| 222 | + diag.span_label(secondary.1, format!("{:?}", secondary.0)); |
| 223 | + } |
| 224 | + |
| 225 | + diag.emit(); |
| 226 | + } else { |
| 227 | + let fr_string = match fr_name { |
| 228 | + Some(r) => format!("free region `{}`", r), |
| 229 | + None => format!("free region `{:?}`", fr), |
| 230 | + }; |
| 231 | + |
| 232 | + let outlived_fr_string = match outlived_fr_name { |
| 233 | + Some(r) => format!("free region `{}`", r), |
| 234 | + None => format!("free region `{:?}`", outlived_fr), |
| 235 | + }; |
| 236 | + |
| 237 | + let mut diag = infcx.tcx.sess.struct_span_err( |
| 238 | + blame_span, |
| 239 | + &format!("{} does not outlive {}", fr_string, outlived_fr_string,), |
| 240 | + ); |
| 241 | + |
| 242 | + diag.emit(); |
| 243 | + } |
| 244 | + } |
| 245 | + |
| 246 | + crate fn why_region_contains_point(&self, fr1: RegionVid, elem: Location) -> Option<Cause> { |
| 247 | + // Find some constraint `X: Y` where: |
| 248 | + // - `fr1: X` transitively |
| 249 | + // - and `Y` is live at `elem` |
| 250 | + let index = self.blame_constraint(fr1, elem); |
| 251 | + let region_sub = self.constraints[index].sub; |
| 252 | + |
| 253 | + // then return why `Y` was live at `elem` |
| 254 | + self.liveness_constraints.cause(region_sub, elem) |
| 255 | + } |
| 256 | + |
| 257 | + /// Tries to finds a good span to blame for the fact that `fr1` |
| 258 | + /// contains `fr2`. |
| 259 | + pub(super) fn blame_constraint(&self, fr1: RegionVid, |
| 260 | + elem: impl ToElementIndex) -> ConstraintIndex { |
| 261 | + // Find everything that influenced final value of `fr`. |
| 262 | + let influenced_fr1 = self.dependencies(fr1); |
| 263 | + |
| 264 | + // Try to find some outlives constraint `'X: fr2` where `'X` |
| 265 | + // influenced `fr1`. Blame that. |
| 266 | + // |
| 267 | + // NB, this is a pretty bad choice most of the time. In |
| 268 | + // particular, the connection between `'X` and `fr1` may not |
| 269 | + // be obvious to the user -- not to mention the naive notion |
| 270 | + // of dependencies, which doesn't account for the locations of |
| 271 | + // contraints at all. But it will do for now. |
| 272 | + let relevant_constraint = self.constraints |
| 273 | + .iter_enumerated() |
| 274 | + .filter_map(|(i, constraint)| { |
| 275 | + if !self.liveness_constraints.contains(constraint.sub, elem) { |
| 276 | + None |
| 277 | + } else { |
| 278 | + influenced_fr1[constraint.sup] |
| 279 | + .map(|distance| (distance, i)) |
| 280 | + } |
| 281 | + }) |
| 282 | + .min() // constraining fr1 with fewer hops *ought* to be more obvious |
| 283 | + .map(|(_dist, i)| i); |
| 284 | + |
| 285 | + relevant_constraint.unwrap_or_else(|| { |
| 286 | + bug!( |
| 287 | + "could not find any constraint to blame for {:?}: {:?}", |
| 288 | + fr1, |
| 289 | + elem, |
| 290 | + ); |
| 291 | + }) |
| 292 | + } |
| 293 | + |
| 294 | + /// Finds all regions whose values `'a` may depend on in some way. |
| 295 | + /// For each region, returns either `None` (does not influence |
| 296 | + /// `'a`) or `Some(d)` which indicates that it influences `'a` |
| 297 | + /// with distinct `d` (minimum number of edges that must be |
| 298 | + /// traversed). |
| 299 | + /// |
| 300 | + /// Used during error reporting, extremely naive and inefficient. |
| 301 | + fn dependencies(&self, r0: RegionVid) -> IndexVec<RegionVid, Option<usize>> { |
| 302 | + let mut result_set = IndexVec::from_elem(None, &self.definitions); |
| 303 | + let mut changed = true; |
| 304 | + result_set[r0] = Some(0); // distance 0 from `r0` |
| 305 | + |
| 306 | + while changed { |
| 307 | + changed = false; |
| 308 | + for constraint in &*self.constraints { |
| 309 | + if let Some(n) = result_set[constraint.sup] { |
| 310 | + let m = n + 1; |
| 311 | + if result_set[constraint.sub] |
| 312 | + .map(|distance| m < distance) |
| 313 | + .unwrap_or(true) |
| 314 | + { |
| 315 | + result_set[constraint.sub] = Some(m); |
| 316 | + changed = true; |
| 317 | + } |
| 318 | + } |
| 319 | + } |
| 320 | + } |
| 321 | + |
| 322 | + result_set |
| 323 | + } |
| 324 | +} |
0 commit comments