rustc_borrowck/
nll.rs

1//! The entry point of the NLL borrow checker.
2
3use std::path::PathBuf;
4use std::rc::Rc;
5use std::str::FromStr;
6use std::{env, io};
7
8use polonius_engine::{Algorithm, Output};
9use rustc_data_structures::fx::FxIndexMap;
10use rustc_hir::def_id::LocalDefId;
11use rustc_index::IndexSlice;
12use rustc_middle::mir::pretty::{PrettyPrintMirOptions, dump_mir_with_options};
13use rustc_middle::mir::{
14    Body, ClosureOutlivesSubject, ClosureRegionRequirements, PassWhere, Promoted, create_dump_file,
15    dump_enabled, dump_mir,
16};
17use rustc_middle::ty::print::with_no_trimmed_paths;
18use rustc_middle::ty::{self, OpaqueHiddenType, TyCtxt};
19use rustc_mir_dataflow::ResultsCursor;
20use rustc_mir_dataflow::impls::MaybeInitializedPlaces;
21use rustc_mir_dataflow::move_paths::MoveData;
22use rustc_mir_dataflow::points::DenseLocationMap;
23use rustc_session::config::MirIncludeSpans;
24use rustc_span::sym;
25use tracing::{debug, instrument};
26
27use crate::borrow_set::BorrowSet;
28use crate::consumers::ConsumerOptions;
29use crate::diagnostics::{BorrowckDiagnosticsBuffer, RegionErrors};
30use crate::polonius::PoloniusDiagnosticsContext;
31use crate::polonius::legacy::{
32    PoloniusFacts, PoloniusFactsExt, PoloniusLocationTable, PoloniusOutput,
33};
34use crate::region_infer::RegionInferenceContext;
35use crate::type_check::{self, MirTypeckResults};
36use crate::universal_regions::UniversalRegions;
37use crate::{BorrowckInferCtxt, polonius, renumber};
38
39/// The output of `nll::compute_regions`. This includes the computed `RegionInferenceContext`, any
40/// closure requirements to propagate, and any generated errors.
41pub(crate) struct NllOutput<'tcx> {
42    pub regioncx: RegionInferenceContext<'tcx>,
43    pub opaque_type_values: FxIndexMap<LocalDefId, OpaqueHiddenType<'tcx>>,
44    pub polonius_input: Option<Box<PoloniusFacts>>,
45    pub polonius_output: Option<Box<PoloniusOutput>>,
46    pub opt_closure_req: Option<ClosureRegionRequirements<'tcx>>,
47    pub nll_errors: RegionErrors<'tcx>,
48
49    /// When using `-Zpolonius=next`: the data used to compute errors and diagnostics, e.g.
50    /// localized typeck and liveness constraints.
51    pub polonius_diagnostics: Option<PoloniusDiagnosticsContext>,
52}
53
54/// Rewrites the regions in the MIR to use NLL variables, also scraping out the set of universal
55/// regions (e.g., region parameters) declared on the function. That set will need to be given to
56/// `compute_regions`.
57#[instrument(skip(infcx, body, promoted), level = "debug")]
58pub(crate) fn replace_regions_in_mir<'tcx>(
59    infcx: &BorrowckInferCtxt<'tcx>,
60    body: &mut Body<'tcx>,
61    promoted: &mut IndexSlice<Promoted, Body<'tcx>>,
62) -> UniversalRegions<'tcx> {
63    let def = body.source.def_id().expect_local();
64
65    debug!(?def);
66
67    // Compute named region information. This also renumbers the inputs/outputs.
68    let universal_regions = UniversalRegions::new(infcx, def);
69
70    // Replace all remaining regions with fresh inference variables.
71    renumber::renumber_mir(infcx, body, promoted);
72
73    dump_mir(infcx.tcx, false, "renumber", &0, body, |_, _| Ok(()));
74
75    universal_regions
76}
77
78/// Computes the (non-lexical) regions from the input MIR.
79///
80/// This may result in errors being reported.
81pub(crate) fn compute_regions<'a, 'tcx>(
82    infcx: &BorrowckInferCtxt<'tcx>,
83    universal_regions: UniversalRegions<'tcx>,
84    body: &Body<'tcx>,
85    promoted: &IndexSlice<Promoted, Body<'tcx>>,
86    location_table: &PoloniusLocationTable,
87    flow_inits: ResultsCursor<'a, 'tcx, MaybeInitializedPlaces<'a, 'tcx>>,
88    move_data: &MoveData<'tcx>,
89    borrow_set: &BorrowSet<'tcx>,
90    consumer_options: Option<ConsumerOptions>,
91) -> NllOutput<'tcx> {
92    let is_polonius_legacy_enabled = infcx.tcx.sess.opts.unstable_opts.polonius.is_legacy_enabled();
93    let polonius_input = consumer_options.map(|c| c.polonius_input()).unwrap_or_default()
94        || is_polonius_legacy_enabled;
95    let polonius_output = consumer_options.map(|c| c.polonius_output()).unwrap_or_default()
96        || is_polonius_legacy_enabled;
97    let mut polonius_facts =
98        (polonius_input || PoloniusFacts::enabled(infcx.tcx)).then_some(PoloniusFacts::default());
99
100    let location_map = Rc::new(DenseLocationMap::new(body));
101
102    // Run the MIR type-checker.
103    let MirTypeckResults {
104        constraints,
105        universal_region_relations,
106        opaque_type_values,
107        polonius_context,
108    } = type_check::type_check(
109        infcx,
110        body,
111        promoted,
112        universal_regions,
113        location_table,
114        borrow_set,
115        &mut polonius_facts,
116        flow_inits,
117        move_data,
118        Rc::clone(&location_map),
119    );
120
121    // Create the region inference context, taking ownership of the
122    // region inference data that was contained in `infcx`, and the
123    // base constraints generated by the type-check.
124    let var_infos = infcx.get_region_var_infos();
125
126    // If requested, emit legacy polonius facts.
127    polonius::legacy::emit_facts(
128        &mut polonius_facts,
129        infcx.tcx,
130        location_table,
131        body,
132        borrow_set,
133        move_data,
134        &universal_region_relations,
135        &constraints,
136    );
137
138    let mut regioncx = RegionInferenceContext::new(
139        infcx,
140        var_infos,
141        constraints,
142        universal_region_relations,
143        location_map,
144    );
145
146    // If requested for `-Zpolonius=next`, convert NLL constraints to localized outlives constraints
147    // and use them to compute loan liveness.
148    let polonius_diagnostics = polonius_context.map(|polonius_context| {
149        polonius_context.compute_loan_liveness(infcx.tcx, &mut regioncx, body, borrow_set)
150    });
151
152    // If requested: dump NLL facts, and run legacy polonius analysis.
153    let polonius_output = polonius_facts.as_ref().and_then(|polonius_facts| {
154        if infcx.tcx.sess.opts.unstable_opts.nll_facts {
155            let def_id = body.source.def_id();
156            let def_path = infcx.tcx.def_path(def_id);
157            let dir_path = PathBuf::from(&infcx.tcx.sess.opts.unstable_opts.nll_facts_dir)
158                .join(def_path.to_filename_friendly_no_crate());
159            polonius_facts.write_to_dir(dir_path, location_table).unwrap();
160        }
161
162        if polonius_output {
163            let algorithm =
164                env::var("POLONIUS_ALGORITHM").unwrap_or_else(|_| String::from("Hybrid"));
165            let algorithm = Algorithm::from_str(&algorithm).unwrap();
166            debug!("compute_regions: using polonius algorithm {:?}", algorithm);
167            let _prof_timer = infcx.tcx.prof.generic_activity("polonius_analysis");
168            Some(Box::new(Output::compute(polonius_facts, algorithm, false)))
169        } else {
170            None
171        }
172    });
173
174    // Solve the region constraints.
175    let (closure_region_requirements, nll_errors) =
176        regioncx.solve(infcx, body, polonius_output.clone());
177
178    if let Some(guar) = nll_errors.has_errors() {
179        // Suppress unhelpful extra errors in `infer_opaque_types`.
180        infcx.set_tainted_by_errors(guar);
181    }
182
183    let remapped_opaque_tys = regioncx.infer_opaque_types(infcx, opaque_type_values);
184
185    NllOutput {
186        regioncx,
187        opaque_type_values: remapped_opaque_tys,
188        polonius_input: polonius_facts.map(Box::new),
189        polonius_output,
190        opt_closure_req: closure_region_requirements,
191        nll_errors,
192        polonius_diagnostics,
193    }
194}
195
196/// `-Zdump-mir=nll` dumps MIR annotated with NLL specific information:
197/// - free regions
198/// - inferred region values
199/// - region liveness
200/// - inference constraints and their causes
201///
202/// As well as graphviz `.dot` visualizations of:
203/// - the region constraints graph
204/// - the region SCC graph
205pub(super) fn dump_nll_mir<'tcx>(
206    infcx: &BorrowckInferCtxt<'tcx>,
207    body: &Body<'tcx>,
208    regioncx: &RegionInferenceContext<'tcx>,
209    closure_region_requirements: &Option<ClosureRegionRequirements<'tcx>>,
210    borrow_set: &BorrowSet<'tcx>,
211) {
212    let tcx = infcx.tcx;
213    if !dump_enabled(tcx, "nll", body.source.def_id()) {
214        return;
215    }
216
217    // We want the NLL extra comments printed by default in NLL MIR dumps (they were removed in
218    // #112346). Specifying `-Z mir-include-spans` on the CLI still has priority: for example,
219    // they're always disabled in mir-opt tests to make working with blessed dumps easier.
220    let options = PrettyPrintMirOptions {
221        include_extra_comments: matches!(
222            infcx.tcx.sess.opts.unstable_opts.mir_include_spans,
223            MirIncludeSpans::On | MirIncludeSpans::Nll
224        ),
225    };
226    dump_mir_with_options(
227        tcx,
228        false,
229        "nll",
230        &0,
231        body,
232        |pass_where, out| {
233            emit_nll_mir(tcx, regioncx, closure_region_requirements, borrow_set, pass_where, out)
234        },
235        options,
236    );
237
238    // Also dump the region constraint graph as a graphviz file.
239    let _: io::Result<()> = try {
240        let mut file = create_dump_file(tcx, "regioncx.all.dot", false, "nll", &0, body)?;
241        regioncx.dump_graphviz_raw_constraints(&mut file)?;
242    };
243
244    // Also dump the region constraint SCC graph as a graphviz file.
245    let _: io::Result<()> = try {
246        let mut file = create_dump_file(tcx, "regioncx.scc.dot", false, "nll", &0, body)?;
247        regioncx.dump_graphviz_scc_constraints(&mut file)?;
248    };
249}
250
251/// Produces the actual NLL MIR sections to emit during the dumping process.
252pub(crate) fn emit_nll_mir<'tcx>(
253    tcx: TyCtxt<'tcx>,
254    regioncx: &RegionInferenceContext<'tcx>,
255    closure_region_requirements: &Option<ClosureRegionRequirements<'tcx>>,
256    borrow_set: &BorrowSet<'tcx>,
257    pass_where: PassWhere,
258    out: &mut dyn io::Write,
259) -> io::Result<()> {
260    match pass_where {
261        // Before the CFG, dump out the values for each region variable.
262        PassWhere::BeforeCFG => {
263            regioncx.dump_mir(tcx, out)?;
264            writeln!(out, "|")?;
265
266            if let Some(closure_region_requirements) = closure_region_requirements {
267                writeln!(out, "| Free Region Constraints")?;
268                for_each_region_constraint(tcx, closure_region_requirements, &mut |msg| {
269                    writeln!(out, "| {msg}")
270                })?;
271                writeln!(out, "|")?;
272            }
273
274            if borrow_set.len() > 0 {
275                writeln!(out, "| Borrows")?;
276                for (borrow_idx, borrow_data) in borrow_set.iter_enumerated() {
277                    writeln!(
278                        out,
279                        "| {:?}: issued at {:?} in {:?}",
280                        borrow_idx, borrow_data.reserve_location, borrow_data.region
281                    )?;
282                }
283                writeln!(out, "|")?;
284            }
285        }
286
287        PassWhere::BeforeLocation(_) => {}
288
289        PassWhere::AfterTerminator(_) => {}
290
291        PassWhere::BeforeBlock(_) | PassWhere::AfterLocation(_) | PassWhere::AfterCFG => {}
292    }
293    Ok(())
294}
295
296#[allow(rustc::diagnostic_outside_of_impl)]
297#[allow(rustc::untranslatable_diagnostic)]
298pub(super) fn dump_annotation<'tcx, 'infcx>(
299    infcx: &'infcx BorrowckInferCtxt<'tcx>,
300    body: &Body<'tcx>,
301    regioncx: &RegionInferenceContext<'tcx>,
302    closure_region_requirements: &Option<ClosureRegionRequirements<'tcx>>,
303    opaque_type_values: &FxIndexMap<LocalDefId, OpaqueHiddenType<'tcx>>,
304    diagnostics_buffer: &mut BorrowckDiagnosticsBuffer<'infcx, 'tcx>,
305) {
306    let tcx = infcx.tcx;
307    let base_def_id = tcx.typeck_root_def_id(body.source.def_id());
308    if !tcx.has_attr(base_def_id, sym::rustc_regions) {
309        return;
310    }
311
312    // When the enclosing function is tagged with `#[rustc_regions]`,
313    // we dump out various bits of state as warnings. This is useful
314    // for verifying that the compiler is behaving as expected. These
315    // warnings focus on the closure region requirements -- for
316    // viewing the intraprocedural state, the -Zdump-mir output is
317    // better.
318
319    let def_span = tcx.def_span(body.source.def_id());
320    let mut err = if let Some(closure_region_requirements) = closure_region_requirements {
321        let mut err = infcx.dcx().struct_span_note(def_span, "external requirements");
322
323        regioncx.annotate(tcx, &mut err);
324
325        err.note(format!(
326            "number of external vids: {}",
327            closure_region_requirements.num_external_vids
328        ));
329
330        // Dump the region constraints we are imposing *between* those
331        // newly created variables.
332        for_each_region_constraint(tcx, closure_region_requirements, &mut |msg| {
333            err.note(msg);
334            Ok(())
335        })
336        .unwrap();
337
338        err
339    } else {
340        let mut err = infcx.dcx().struct_span_note(def_span, "no external requirements");
341        regioncx.annotate(tcx, &mut err);
342
343        err
344    };
345
346    if !opaque_type_values.is_empty() {
347        err.note(format!("Inferred opaque type values:\n{opaque_type_values:#?}"));
348    }
349
350    diagnostics_buffer.buffer_non_error(err);
351}
352
353fn for_each_region_constraint<'tcx>(
354    tcx: TyCtxt<'tcx>,
355    closure_region_requirements: &ClosureRegionRequirements<'tcx>,
356    with_msg: &mut dyn FnMut(String) -> io::Result<()>,
357) -> io::Result<()> {
358    for req in &closure_region_requirements.outlives_requirements {
359        let subject = match req.subject {
360            ClosureOutlivesSubject::Region(subject) => format!("{subject:?}"),
361            ClosureOutlivesSubject::Ty(ty) => {
362                with_no_trimmed_paths!(format!(
363                    "{}",
364                    ty.instantiate(tcx, |vid| ty::Region::new_var(tcx, vid))
365                ))
366            }
367        };
368        with_msg(format!("where {}: {:?}", subject, req.outlived_free_region,))?;
369    }
370    Ok(())
371}
372
373pub(crate) trait ConstraintDescription {
374    fn description(&self) -> &'static str;
375}