flux_driver/
callbacks.rs

1use std::path::Path;
2
3use flux_common::{bug, cache::QueryCache, iter::IterExt, result::ResultExt};
4use flux_config::{self as config};
5use flux_errors::FluxSession;
6use flux_infer::{fixpoint_encoding::FixQueryCache, lean_encoding};
7use flux_metadata::CStore;
8use flux_middle::{
9    Specs,
10    def_id::MaybeExternId,
11    fhir::{self},
12    global_env::GlobalEnv,
13    metrics::{self, Metric, TimingKind},
14    queries::{Providers, QueryResult},
15    rty::StaticInfo,
16};
17use flux_refineck as refineck;
18use rustc_borrowck::consumers::ConsumerOptions;
19use rustc_driver::{Callbacks, Compilation};
20use rustc_errors::ErrorGuaranteed;
21use rustc_hir::{
22    def::{CtorKind, DefKind},
23    def_id::{LOCAL_CRATE, LocalDefId},
24};
25use rustc_interface::interface::Compiler;
26use rustc_middle::{query, ty::TyCtxt};
27use rustc_session::config::OutputType;
28use rustc_span::FileName;
29
30use crate::{DEFAULT_LOCALE_RESOURCES, collector::SpecCollector};
31
32#[derive(Default)]
33pub struct FluxCallbacks;
34
35impl Callbacks for FluxCallbacks {
36    fn config(&mut self, config: &mut rustc_interface::interface::Config) {
37        assert!(config.override_queries.is_none());
38
39        config.override_queries = Some(|_, local| {
40            local.mir_borrowck = mir_borrowck;
41        });
42        // this should always be empty otherwise something changed in rustc and all our assumptions
43        // about symbol interning are wrong.
44        assert!(config.extra_symbols.is_empty());
45        config.extra_symbols = flux_syntax::symbols::PREDEFINED_FLUX_SYMBOLS.to_vec();
46    }
47
48    fn after_analysis(&mut self, compiler: &Compiler, tcx: TyCtxt<'_>) -> Compilation {
49        self.verify(compiler, tcx);
50        if config::full_compilation() { Compilation::Continue } else { Compilation::Stop }
51    }
52}
53
54impl FluxCallbacks {
55    fn verify(&self, compiler: &Compiler, tcx: TyCtxt<'_>) {
56        if compiler.sess.dcx().has_errors().is_some() {
57            return;
58        }
59
60        let sess = FluxSession::new(
61            &tcx.sess.opts,
62            tcx.sess.psess.clone_source_map(),
63            rustc_errors::fallback_fluent_bundle(DEFAULT_LOCALE_RESOURCES.to_vec(), false),
64        );
65
66        let mut providers = Providers::default();
67        flux_desugar::provide(&mut providers);
68        flux_fhir_analysis::provide(&mut providers);
69        providers.collect_specs = collect_specs;
70
71        let cstore = CStore::load(tcx, &sess);
72        let arena = fhir::Arena::new();
73        GlobalEnv::enter(tcx, &sess, Box::new(cstore), &arena, providers, |genv| {
74            let result = metrics::time_it(TimingKind::Total, || check_crate(genv));
75            if result.is_ok() {
76                encode_and_save_metadata(genv);
77            }
78            lean_encoding::finalize(genv).unwrap_or(());
79        });
80        let _ = metrics::print_and_dump_timings(tcx);
81        sess.finish_diagnostics();
82    }
83}
84
85fn check_crate(genv: GlobalEnv) -> Result<(), ErrorGuaranteed> {
86    tracing::info_span!("check_crate").in_scope(move || {
87        tracing::info!("Callbacks::check_wf");
88        // Query qualifiers and spec funcs to report wf errors
89        let _ = genv.qualifiers().emit(&genv)?;
90        let _ = genv.normalized_defns(LOCAL_CRATE);
91
92        let mut ck = CrateChecker::new(genv);
93
94        // Iterate over all def ids including dummy items for extern specs
95        let result = genv
96            .tcx()
97            .iter_local_def_id()
98            .try_for_each_exhaust(|def_id| ck.check_def_catching_bugs(def_id));
99
100        if config::lean().is_check() || config::lean().is_emit() {
101            lean_encoding::finalize(genv)
102                .unwrap_or_else(|err| bug!("error running lean-check {err:?}"));
103        }
104
105        let lean_result = if config::lean().is_check() {
106            genv.iter_local_def_id().try_for_each_exhaust(|def_id| {
107                if genv.proven_externally(def_id).is_some() {
108                    lean_encoding::check_proof(genv, def_id.to_def_id())
109                } else {
110                    Ok(())
111                }
112            })
113        } else {
114            Ok(())
115        };
116
117        ck.cache.save().unwrap_or(());
118
119        tracing::info!("Callbacks::check_crate");
120
121        result.and(lean_result)
122    })
123}
124
125fn collect_specs(genv: GlobalEnv) -> Specs {
126    match SpecCollector::collect(genv.tcx(), genv.sess()) {
127        Ok(specs) => specs,
128        Err(err) => {
129            genv.sess().abort(err);
130        }
131    }
132}
133
134fn encode_and_save_metadata(genv: GlobalEnv) {
135    // We only save metadata when `--emit=metadata` is passed as an argument. In this case, we save
136    // the `.fluxmeta` file alongside the `.rmeta` file. This setup works for `cargo flux`, which
137    // wraps `cargo check` and always passes `--emit=metadata`. Tests also explicitly pass this flag.
138    let tcx = genv.tcx();
139    if tcx
140        .output_filenames(())
141        .outputs
142        .contains_key(&OutputType::Metadata)
143    {
144        let path = flux_metadata::filename_for_metadata(tcx);
145        flux_metadata::encode_metadata(genv, path.as_path());
146    }
147}
148
149struct CrateChecker<'genv, 'tcx> {
150    genv: GlobalEnv<'genv, 'tcx>,
151    cache: FixQueryCache,
152}
153
154impl<'genv, 'tcx> CrateChecker<'genv, 'tcx> {
155    fn new(genv: GlobalEnv<'genv, 'tcx>) -> Self {
156        Self { genv, cache: QueryCache::load() }
157    }
158
159    fn matches_def(&self, def_id: MaybeExternId, def: &str) -> bool {
160        // Does this def_id's name contain `fn_name`?
161        let def_path = self.genv.tcx().def_path_str(def_id.local_id());
162        def_path.contains(def)
163    }
164
165    fn matches_file_path<F>(&self, def_id: MaybeExternId, matcher: F) -> bool
166    where
167        F: Fn(&Path) -> bool,
168    {
169        let def_id = def_id.local_id();
170        let tcx = self.genv.tcx();
171        let span = tcx.def_span(def_id);
172        let sm = tcx.sess.source_map();
173        let FileName::Real(file_name) = sm.span_to_filename(span) else { return true };
174        let mut file_path = file_name.local_path_if_available();
175
176        // If the path is absolute try to normalize it to be relative to the working_dir
177        if file_path.is_absolute() {
178            let working_dir = tcx.sess.opts.working_dir.local_path_if_available();
179            let Ok(p) = file_path.strip_prefix(working_dir) else { return true };
180            file_path = p;
181        }
182
183        matcher(file_path)
184    }
185
186    fn matches_pos(&self, def_id: MaybeExternId, line: usize, col: usize) -> bool {
187        let def_id = def_id.local_id();
188        let tcx = self.genv.tcx();
189        let hir_id = tcx.local_def_id_to_hir_id(def_id);
190        let body_span = tcx.hir_span_with_body(hir_id);
191        let source_map = tcx.sess.source_map();
192        let lo_pos = source_map.lookup_char_pos(body_span.lo());
193        let start_line = lo_pos.line;
194        let start_col = lo_pos.col_display;
195        let hi_pos = source_map.lookup_char_pos(body_span.hi());
196        let end_line = hi_pos.line;
197        let end_col = hi_pos.col_display;
198
199        // is the line in the range of the body?
200        if start_line < end_line {
201            // multiple lines: check if the line is in the range
202            start_line <= line && line <= end_line
203        } else {
204            // single line: check if the line is the same and the column is in range
205            start_line == line && start_col <= col && col <= end_col
206        }
207    }
208
209    /// Check whether the `def_id` (or the file where `def_id` is defined)
210    /// is in the `include` pattern, and conservatively return `true` if
211    /// anything unexpected happens.
212    fn is_included(&self, def_id: MaybeExternId) -> bool {
213        let Some(pattern) = config::include_pattern() else { return true };
214        if self.matches_file_path(def_id, |path| pattern.glob.is_match(path)) {
215            return true;
216        }
217        if pattern.defs.iter().any(|def| self.matches_def(def_id, def)) {
218            return true;
219        }
220        if pattern.spans.iter().any(|pos| {
221            self.matches_file_path(def_id, |path| path.ends_with(&pos.file))
222                && self.matches_pos(def_id, pos.line, pos.column)
223        }) {
224            return true;
225        }
226        false
227    }
228
229    fn check_def_catching_bugs(&mut self, def_id: LocalDefId) -> Result<(), ErrorGuaranteed> {
230        let mut this = std::panic::AssertUnwindSafe(self);
231        let msg = format!("def_id: {:?}, span: {:?}", def_id, this.genv.tcx().def_span(def_id));
232        flux_common::bug::catch_bugs(&msg, move || this.check_def(def_id))?
233    }
234
235    fn check_def(&mut self, def_id: LocalDefId) -> Result<(), ErrorGuaranteed> {
236        let genv = self.genv;
237        let def_id = genv.maybe_extern_id(def_id);
238
239        // Dummy items generated for extern specs are excluded from metrics
240        if genv.is_dummy(def_id.local_id()) {
241            return Ok(());
242        }
243
244        let kind = genv.def_kind(def_id);
245
246        // For the purpose of metrics, we consider to be a *function* an item that
247        // 1. It's local, i.e., it's not an extern spec.
248        // 2. It's a free function (`DefKind::Fn`) or associated item (`DefKind::AssocFn`), and
249        // 3. It has a mir body
250        // In particular, this excludes closures (because they dont have the right `DefKind`) and
251        // trait methods without a default body.
252        let is_fn_with_body = def_id
253            .as_local()
254            .map(|local_id| {
255                matches!(kind, DefKind::Fn | DefKind::AssocFn)
256                    && genv.tcx().is_mir_available(local_id)
257            })
258            .unwrap_or(false);
259
260        metrics::incr_metric_if(is_fn_with_body, Metric::FnTotal);
261
262        if genv.ignored(def_id.local_id()) {
263            metrics::incr_metric_if(is_fn_with_body, Metric::FnIgnored);
264            return Ok(());
265        }
266        if !self.is_included(def_id) {
267            metrics::incr_metric_if(is_fn_with_body, Metric::FnTrusted);
268            return Ok(());
269        }
270
271        trigger_queries(genv, def_id).emit(&genv)?;
272
273        match kind {
274            DefKind::Fn | DefKind::AssocFn => {
275                let Some(local_id) = def_id.as_local() else { return Ok(()) };
276                if is_fn_with_body {
277                    refineck::check_fn(genv, &mut self.cache, local_id)?;
278                }
279            }
280            DefKind::Enum => {
281                let adt_def = genv.adt_def(def_id).emit(&genv)?;
282                let enum_def = genv
283                    .fhir_expect_item(def_id.local_id())
284                    .emit(&genv)?
285                    .expect_enum();
286                refineck::invariants::check_invariants(
287                    genv,
288                    &mut self.cache,
289                    def_id,
290                    enum_def.invariants,
291                    &adt_def,
292                )?;
293            }
294            DefKind::Struct => {
295                // We check invariants for `struct` in `check_constructor` (i.e. when the struct is built),
296                // so nothing to do here.
297            }
298            DefKind::Impl { of_trait } => {
299                if of_trait {
300                    refineck::compare_impl_item::check_impl_against_trait(genv, def_id)
301                        .emit(&genv)?;
302                }
303            }
304            DefKind::TyAlias => {}
305            DefKind::Trait => {}
306            DefKind::Static { .. } => {
307                if let StaticInfo::Known(ty) = genv.static_info(def_id).emit(&genv)?
308                    && let Some(local_id) = def_id.as_local()
309                {
310                    refineck::check_static(genv, &mut self.cache, local_id, ty)?;
311                }
312            }
313            _ => (),
314        }
315        Ok(())
316    }
317}
318
319/// Triggers queries for the given `def_id` to mark it as "reached" for metadata encoding.
320///
321/// This function ensures that all relevant queries for a definition are triggered upfront,
322/// so the item and its associated data will be included in the encoded metadata. Without this,
323/// items might be missing from the metadata (extern specs in particular which are not otherwise "checked"),
324/// causing errors when dependent crates try to use them.
325fn trigger_queries(genv: GlobalEnv, def_id: MaybeExternId) -> QueryResult {
326    match genv.def_kind(def_id) {
327        DefKind::Trait => {
328            genv.generics_of(def_id)?;
329            genv.predicates_of(def_id)?;
330            genv.refinement_generics_of(def_id)?;
331        }
332        DefKind::Impl { .. } => {
333            genv.generics_of(def_id)?;
334            genv.predicates_of(def_id)?;
335            genv.refinement_generics_of(def_id)?;
336        }
337        DefKind::Fn | DefKind::AssocFn => {
338            genv.generics_of(def_id)?;
339            genv.refinement_generics_of(def_id)?;
340            genv.predicates_of(def_id)?;
341            genv.fn_sig(def_id)?;
342        }
343        DefKind::Ctor(_, CtorKind::Fn) => {
344            genv.generics_of(def_id)?;
345            genv.refinement_generics_of(def_id)?;
346            // We don't report the error because it can raise a `QueryErr::OpaqueStruct`,  which
347            // should be reported at the use site.
348            let _ = genv.fn_sig(def_id);
349        }
350        DefKind::Enum | DefKind::Struct => {
351            genv.generics_of(def_id)?;
352            genv.predicates_of(def_id)?;
353            genv.refinement_generics_of(def_id)?;
354            genv.adt_def(def_id)?;
355            genv.adt_sort_def_of(def_id)?;
356            genv.variants_of(def_id)?;
357            genv.type_of(def_id)?;
358        }
359        DefKind::TyAlias => {
360            genv.generics_of(def_id)?;
361            genv.predicates_of(def_id)?;
362            genv.refinement_generics_of(def_id)?;
363            genv.type_of(def_id)?;
364        }
365        DefKind::OpaqueTy => {
366            genv.generics_of(def_id)?;
367            genv.predicates_of(def_id)?;
368            genv.item_bounds(def_id)?;
369            genv.refinement_generics_of(def_id)?;
370        }
371        _ => {}
372    }
373    Ok(())
374}
375
376fn mir_borrowck<'tcx>(
377    tcx: TyCtxt<'tcx>,
378    def_id: LocalDefId,
379) -> query::queries::mir_borrowck::ProvidedValue<'tcx> {
380    let bodies_with_facts = rustc_borrowck::consumers::get_bodies_with_borrowck_facts(
381        tcx,
382        def_id,
383        ConsumerOptions::RegionInferenceContext,
384    );
385    for (def_id, body_with_facts) in bodies_with_facts {
386        // SAFETY: This is safe because we are feeding in the same `tcx` that is
387        // going to be used as a witness when pulling out the data.
388        unsafe {
389            flux_common::mir_storage::store_mir_body(tcx, def_id, body_with_facts);
390        }
391    }
392    let mut providers = query::Providers::default();
393    rustc_borrowck::provide(&mut providers);
394    let original_mir_borrowck = providers.mir_borrowck;
395    original_mir_borrowck(tcx, def_id)
396}