| //! The entry point of the NLL borrow checker. |
| |
| use std::path::PathBuf; |
| use std::rc::Rc; |
| use std::str::FromStr; |
| use std::{env, io}; |
| |
| use polonius_engine::{Algorithm, Output}; |
| use rustc_data_structures::fx::FxIndexMap; |
| use rustc_hir::def_id::LocalDefId; |
| use rustc_index::IndexSlice; |
| use rustc_middle::mir::pretty::{PrettyPrintMirOptions, dump_mir_with_options}; |
| use rustc_middle::mir::{ |
| Body, ClosureOutlivesSubject, ClosureRegionRequirements, PassWhere, Promoted, create_dump_file, |
| dump_enabled, dump_mir, |
| }; |
| use rustc_middle::ty::print::with_no_trimmed_paths; |
| use rustc_middle::ty::{self, OpaqueHiddenType, TyCtxt}; |
| use rustc_mir_dataflow::ResultsCursor; |
| use rustc_mir_dataflow::impls::MaybeInitializedPlaces; |
| use rustc_mir_dataflow::move_paths::MoveData; |
| use rustc_mir_dataflow::points::DenseLocationMap; |
| use rustc_session::config::MirIncludeSpans; |
| use rustc_span::symbol::sym; |
| use tracing::{debug, instrument}; |
| |
| use crate::borrow_set::BorrowSet; |
| use crate::consumers::ConsumerOptions; |
| use crate::diagnostics::RegionErrors; |
| use crate::facts::{AllFacts, AllFactsExt, RustcFacts}; |
| use crate::location::LocationTable; |
| use crate::region_infer::RegionInferenceContext; |
| use crate::type_check::{self, MirTypeckRegionConstraints, MirTypeckResults}; |
| use crate::universal_regions::UniversalRegions; |
| use crate::{BorrowckInferCtxt, polonius, renumber}; |
| |
| pub type PoloniusOutput = Output<RustcFacts>; |
| |
| /// The output of `nll::compute_regions`. This includes the computed `RegionInferenceContext`, any |
| /// closure requirements to propagate, and any generated errors. |
| pub(crate) struct NllOutput<'tcx> { |
| pub regioncx: RegionInferenceContext<'tcx>, |
| pub opaque_type_values: FxIndexMap<LocalDefId, OpaqueHiddenType<'tcx>>, |
| pub polonius_input: Option<Box<AllFacts>>, |
| pub polonius_output: Option<Box<PoloniusOutput>>, |
| pub opt_closure_req: Option<ClosureRegionRequirements<'tcx>>, |
| pub nll_errors: RegionErrors<'tcx>, |
| } |
| |
| /// Rewrites the regions in the MIR to use NLL variables, also scraping out the set of universal |
| /// regions (e.g., region parameters) declared on the function. That set will need to be given to |
| /// `compute_regions`. |
| #[instrument(skip(infcx, param_env, body, promoted), level = "debug")] |
| pub(crate) fn replace_regions_in_mir<'tcx>( |
| infcx: &BorrowckInferCtxt<'tcx>, |
| param_env: ty::ParamEnv<'tcx>, |
| body: &mut Body<'tcx>, |
| promoted: &mut IndexSlice<Promoted, Body<'tcx>>, |
| ) -> UniversalRegions<'tcx> { |
| let def = body.source.def_id().expect_local(); |
| |
| debug!(?def); |
| |
| // Compute named region information. This also renumbers the inputs/outputs. |
| let universal_regions = UniversalRegions::new(infcx, def, param_env); |
| |
| // Replace all remaining regions with fresh inference variables. |
| renumber::renumber_mir(infcx, body, promoted); |
| |
| dump_mir(infcx.tcx, false, "renumber", &0, body, |_, _| Ok(())); |
| |
| universal_regions |
| } |
| |
| /// Computes the (non-lexical) regions from the input MIR. |
| /// |
| /// This may result in errors being reported. |
| pub(crate) fn compute_regions<'a, 'tcx>( |
| infcx: &BorrowckInferCtxt<'tcx>, |
| universal_regions: UniversalRegions<'tcx>, |
| body: &Body<'tcx>, |
| promoted: &IndexSlice<Promoted, Body<'tcx>>, |
| location_table: &LocationTable, |
| param_env: ty::ParamEnv<'tcx>, |
| flow_inits: &mut ResultsCursor<'a, 'tcx, MaybeInitializedPlaces<'a, 'tcx>>, |
| move_data: &MoveData<'tcx>, |
| borrow_set: &BorrowSet<'tcx>, |
| upvars: &[&ty::CapturedPlace<'tcx>], |
| consumer_options: Option<ConsumerOptions>, |
| ) -> NllOutput<'tcx> { |
| let is_polonius_legacy_enabled = infcx.tcx.sess.opts.unstable_opts.polonius.is_legacy_enabled(); |
| let polonius_input = consumer_options.map(|c| c.polonius_input()).unwrap_or_default() |
| || is_polonius_legacy_enabled; |
| let polonius_output = consumer_options.map(|c| c.polonius_output()).unwrap_or_default() |
| || is_polonius_legacy_enabled; |
| let mut all_facts = |
| (polonius_input || AllFacts::enabled(infcx.tcx)).then_some(AllFacts::default()); |
| |
| let universal_regions = Rc::new(universal_regions); |
| |
| let elements = Rc::new(DenseLocationMap::new(body)); |
| |
| // Run the MIR type-checker. |
| let MirTypeckResults { constraints, universal_region_relations, opaque_type_values } = |
| type_check::type_check( |
| infcx, |
| param_env, |
| body, |
| promoted, |
| universal_regions.clone(), |
| location_table, |
| borrow_set, |
| &mut all_facts, |
| flow_inits, |
| move_data, |
| elements.clone(), |
| upvars, |
| ); |
| |
| // Create the region inference context, taking ownership of the |
| // region inference data that was contained in `infcx`, and the |
| // base constraints generated by the type-check. |
| let var_origins = infcx.get_region_var_origins(); |
| let MirTypeckRegionConstraints { |
| placeholder_indices, |
| placeholder_index_to_region: _, |
| liveness_constraints, |
| mut outlives_constraints, |
| mut member_constraints, |
| universe_causes, |
| type_tests, |
| } = constraints; |
| let placeholder_indices = Rc::new(placeholder_indices); |
| |
| // If requested, emit legacy polonius facts. |
| polonius::emit_facts( |
| &mut all_facts, |
| infcx.tcx, |
| location_table, |
| body, |
| borrow_set, |
| move_data, |
| &universal_regions, |
| &universal_region_relations, |
| ); |
| |
| if let Some(guar) = universal_regions.tainted_by_errors() { |
| // Suppress unhelpful extra errors in `infer_opaque_types` by clearing out all |
| // outlives bounds that we may end up checking. |
| outlives_constraints = Default::default(); |
| member_constraints = Default::default(); |
| |
| // Also taint the entire scope. |
| infcx.set_tainted_by_errors(guar); |
| } |
| |
| let mut regioncx = RegionInferenceContext::new( |
| infcx, |
| var_origins, |
| universal_regions, |
| placeholder_indices, |
| universal_region_relations, |
| outlives_constraints, |
| member_constraints, |
| universe_causes, |
| type_tests, |
| liveness_constraints, |
| elements, |
| ); |
| |
| // If requested: dump NLL facts, and run legacy polonius analysis. |
| let polonius_output = all_facts.as_ref().and_then(|all_facts| { |
| if infcx.tcx.sess.opts.unstable_opts.nll_facts { |
| let def_id = body.source.def_id(); |
| let def_path = infcx.tcx.def_path(def_id); |
| let dir_path = PathBuf::from(&infcx.tcx.sess.opts.unstable_opts.nll_facts_dir) |
| .join(def_path.to_filename_friendly_no_crate()); |
| all_facts.write_to_dir(dir_path, location_table).unwrap(); |
| } |
| |
| if polonius_output { |
| let algorithm = |
| env::var("POLONIUS_ALGORITHM").unwrap_or_else(|_| String::from("Hybrid")); |
| let algorithm = Algorithm::from_str(&algorithm).unwrap(); |
| debug!("compute_regions: using polonius algorithm {:?}", algorithm); |
| let _prof_timer = infcx.tcx.prof.generic_activity("polonius_analysis"); |
| Some(Box::new(Output::compute(all_facts, algorithm, false))) |
| } else { |
| None |
| } |
| }); |
| |
| // Solve the region constraints. |
| let (closure_region_requirements, nll_errors) = |
| regioncx.solve(infcx, body, polonius_output.clone()); |
| |
| if let Some(guar) = nll_errors.has_errors() { |
| // Suppress unhelpful extra errors in `infer_opaque_types`. |
| infcx.set_tainted_by_errors(guar); |
| } |
| |
| let remapped_opaque_tys = regioncx.infer_opaque_types(infcx, opaque_type_values); |
| |
| NllOutput { |
| regioncx, |
| opaque_type_values: remapped_opaque_tys, |
| polonius_input: all_facts.map(Box::new), |
| polonius_output, |
| opt_closure_req: closure_region_requirements, |
| nll_errors, |
| } |
| } |
| |
| /// `-Zdump-mir=nll` dumps MIR annotated with NLL specific information: |
| /// - free regions |
| /// - inferred region values |
| /// - region liveness |
| /// - inference constraints and their causes |
| /// |
| /// As well as graphviz `.dot` visualizations of: |
| /// - the region constraints graph |
| /// - the region SCC graph |
| pub(super) fn dump_nll_mir<'tcx>( |
| infcx: &BorrowckInferCtxt<'tcx>, |
| body: &Body<'tcx>, |
| regioncx: &RegionInferenceContext<'tcx>, |
| closure_region_requirements: &Option<ClosureRegionRequirements<'tcx>>, |
| borrow_set: &BorrowSet<'tcx>, |
| ) { |
| let tcx = infcx.tcx; |
| if !dump_enabled(tcx, "nll", body.source.def_id()) { |
| return; |
| } |
| |
| // We want the NLL extra comments printed by default in NLL MIR dumps (they were removed in |
| // #112346). Specifying `-Z mir-include-spans` on the CLI still has priority: for example, |
| // they're always disabled in mir-opt tests to make working with blessed dumps easier. |
| let options = PrettyPrintMirOptions { |
| include_extra_comments: matches!( |
| infcx.tcx.sess.opts.unstable_opts.mir_include_spans, |
| MirIncludeSpans::On | MirIncludeSpans::Nll |
| ), |
| }; |
| dump_mir_with_options( |
| tcx, |
| false, |
| "nll", |
| &0, |
| body, |
| |pass_where, out| { |
| match pass_where { |
| // Before the CFG, dump out the values for each region variable. |
| PassWhere::BeforeCFG => { |
| regioncx.dump_mir(tcx, out)?; |
| writeln!(out, "|")?; |
| |
| if let Some(closure_region_requirements) = closure_region_requirements { |
| writeln!(out, "| Free Region Constraints")?; |
| for_each_region_constraint(tcx, closure_region_requirements, &mut |msg| { |
| writeln!(out, "| {msg}") |
| })?; |
| writeln!(out, "|")?; |
| } |
| |
| if borrow_set.len() > 0 { |
| writeln!(out, "| Borrows")?; |
| for (borrow_idx, borrow_data) in borrow_set.iter_enumerated() { |
| writeln!( |
| out, |
| "| {:?}: issued at {:?} in {:?}", |
| borrow_idx, borrow_data.reserve_location, borrow_data.region |
| )?; |
| } |
| writeln!(out, "|")?; |
| } |
| } |
| |
| PassWhere::BeforeLocation(_) => {} |
| |
| PassWhere::AfterTerminator(_) => {} |
| |
| PassWhere::BeforeBlock(_) | PassWhere::AfterLocation(_) | PassWhere::AfterCFG => {} |
| } |
| Ok(()) |
| }, |
| options, |
| ); |
| |
| // Also dump the region constraint graph as a graphviz file. |
| let _: io::Result<()> = try { |
| let mut file = create_dump_file(tcx, "regioncx.all.dot", false, "nll", &0, body)?; |
| regioncx.dump_graphviz_raw_constraints(&mut file)?; |
| }; |
| |
| // Also dump the region constraint SCC graph as a graphviz file. |
| let _: io::Result<()> = try { |
| let mut file = create_dump_file(tcx, "regioncx.scc.dot", false, "nll", &0, body)?; |
| regioncx.dump_graphviz_scc_constraints(&mut file)?; |
| }; |
| } |
| |
| #[allow(rustc::diagnostic_outside_of_impl)] |
| #[allow(rustc::untranslatable_diagnostic)] |
| pub(super) fn dump_annotation<'tcx, 'infcx>( |
| infcx: &'infcx BorrowckInferCtxt<'tcx>, |
| body: &Body<'tcx>, |
| regioncx: &RegionInferenceContext<'tcx>, |
| closure_region_requirements: &Option<ClosureRegionRequirements<'tcx>>, |
| opaque_type_values: &FxIndexMap<LocalDefId, OpaqueHiddenType<'tcx>>, |
| diags: &mut crate::diags::BorrowckDiags<'infcx, 'tcx>, |
| ) { |
| let tcx = infcx.tcx; |
| let base_def_id = tcx.typeck_root_def_id(body.source.def_id()); |
| if !tcx.has_attr(base_def_id, sym::rustc_regions) { |
| return; |
| } |
| |
| // When the enclosing function is tagged with `#[rustc_regions]`, |
| // we dump out various bits of state as warnings. This is useful |
| // for verifying that the compiler is behaving as expected. These |
| // warnings focus on the closure region requirements -- for |
| // viewing the intraprocedural state, the -Zdump-mir output is |
| // better. |
| |
| let def_span = tcx.def_span(body.source.def_id()); |
| let mut err = if let Some(closure_region_requirements) = closure_region_requirements { |
| let mut err = infcx.dcx().struct_span_note(def_span, "external requirements"); |
| |
| regioncx.annotate(tcx, &mut err); |
| |
| err.note(format!( |
| "number of external vids: {}", |
| closure_region_requirements.num_external_vids |
| )); |
| |
| // Dump the region constraints we are imposing *between* those |
| // newly created variables. |
| for_each_region_constraint(tcx, closure_region_requirements, &mut |msg| { |
| err.note(msg); |
| Ok(()) |
| }) |
| .unwrap(); |
| |
| err |
| } else { |
| let mut err = infcx.dcx().struct_span_note(def_span, "no external requirements"); |
| regioncx.annotate(tcx, &mut err); |
| |
| err |
| }; |
| |
| if !opaque_type_values.is_empty() { |
| err.note(format!("Inferred opaque type values:\n{opaque_type_values:#?}")); |
| } |
| |
| diags.buffer_non_error(err); |
| } |
| |
| fn for_each_region_constraint<'tcx>( |
| tcx: TyCtxt<'tcx>, |
| closure_region_requirements: &ClosureRegionRequirements<'tcx>, |
| with_msg: &mut dyn FnMut(String) -> io::Result<()>, |
| ) -> io::Result<()> { |
| for req in &closure_region_requirements.outlives_requirements { |
| let subject = match req.subject { |
| ClosureOutlivesSubject::Region(subject) => format!("{subject:?}"), |
| ClosureOutlivesSubject::Ty(ty) => { |
| with_no_trimmed_paths!(format!( |
| "{}", |
| ty.instantiate(tcx, |vid| ty::Region::new_var(tcx, vid)) |
| )) |
| } |
| }; |
| with_msg(format!("where {}: {:?}", subject, req.outlived_free_region,))?; |
| } |
| Ok(()) |
| } |
| |
| pub(crate) trait ConstraintDescription { |
| fn description(&self) -> &'static str; |
| } |