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};
16use flux_refineck as refineck;
17use itertools::Itertools;
18use rustc_borrowck::consumers::ConsumerOptions;
19use rustc_driver::{Callbacks, Compilation};
20use rustc_errors::ErrorGuaranteed;
21use rustc_hir::{
22    def::DefKind,
23    def_id::{DefId, 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        let crate_items = genv.tcx().hir_crate_items(());
95
96        let dups = crate_items.definitions().duplicates().collect_vec();
97        if !dups.is_empty() {
98            bug!("TODO: {dups:#?}");
99        }
100        let result = crate_items
101            .definitions()
102            .try_for_each_exhaust(|def_id| ck.check_def_catching_bugs(def_id));
103
104        if config::lean().is_emit() {
105            lean_encoding::finalize(genv)
106                .unwrap_or_else(|err| bug!("error running lean-check {err:?}"));
107        }
108
109        let lean_result = if config::lean().is_check() {
110            crate_items.definitions().try_for_each_exhaust(|def_id| {
111                if genv.proven_externally(def_id).is_some() {
112                    lean_encoding::check_proof(genv, def_id.to_def_id())
113                } else {
114                    Ok(())
115                }
116            })
117        } else {
118            Ok(())
119        };
120
121        ck.cache.save().unwrap_or(());
122
123        tracing::info!("Callbacks::check_crate");
124
125        result.and(lean_result)
126    })
127}
128
129fn collect_specs(genv: GlobalEnv) -> Specs {
130    match SpecCollector::collect(genv.tcx(), genv.sess()) {
131        Ok(specs) => specs,
132        Err(err) => {
133            genv.sess().abort(err);
134        }
135    }
136}
137
138fn encode_and_save_metadata(genv: GlobalEnv) {
139    // HACK(nilehmann) do not encode metadata for `core`, this is so verify-rust-std works even
140    // if it has unsupported items. We report errors lazily so partially analyzing the crate should
141    // skip the error, except that encoding the metadata for the crate will trigger conversion for
142    // all items which can trigger the error even if not included for analysis. To fix this properly
143    // we should consider how to handle metadata encoding if only part of the crate is included for
144    // analysis.
145    if genv.tcx().crate_name(LOCAL_CRATE) == flux_syntax::symbols::sym::core {
146        return;
147    }
148
149    // We only save metadata when `--emit=metadata` is passed as an argument. In this case, we save
150    // the `.fluxmeta` file alongside the `.rmeta` file. This setup works for `cargo flux`, which
151    // wraps `cargo check` and always passes `--emit=metadata`. Tests also explicitly pass this flag.
152    let tcx = genv.tcx();
153    if tcx
154        .output_filenames(())
155        .outputs
156        .contains_key(&OutputType::Metadata)
157    {
158        let path = flux_metadata::filename_for_metadata(tcx);
159        flux_metadata::encode_metadata(genv, path.as_path());
160    }
161}
162
163struct CrateChecker<'genv, 'tcx> {
164    genv: GlobalEnv<'genv, 'tcx>,
165    cache: FixQueryCache,
166}
167
168impl<'genv, 'tcx> CrateChecker<'genv, 'tcx> {
169    fn new(genv: GlobalEnv<'genv, 'tcx>) -> Self {
170        Self { genv, cache: QueryCache::load() }
171    }
172
173    fn matches_def(&self, def_id: MaybeExternId, def: &str) -> bool {
174        // Does this def_id's name contain `fn_name`?
175        let def_path = self.genv.tcx().def_path_str(def_id.local_id());
176        def_path.contains(def)
177    }
178
179    fn matches_file_path<F>(&self, def_id: MaybeExternId, matcher: F) -> bool
180    where
181        F: Fn(&Path) -> bool,
182    {
183        let def_id = def_id.local_id();
184        let tcx = self.genv.tcx();
185        let span = tcx.def_span(def_id);
186        let sm = tcx.sess.source_map();
187        let FileName::Real(file_name) = sm.span_to_filename(span) else { return true };
188        let mut file_path = file_name.local_path_if_available();
189
190        // If the path is absolute try to normalize it to be relative to the working_dir
191        if file_path.is_absolute() {
192            let working_dir = tcx.sess.opts.working_dir.local_path_if_available();
193            let Ok(p) = file_path.strip_prefix(working_dir) else { return true };
194            file_path = p;
195        }
196
197        matcher(file_path)
198    }
199
200    fn matches_pos(&self, def_id: MaybeExternId, line: usize, col: usize) -> bool {
201        let def_id = def_id.local_id();
202        let tcx = self.genv.tcx();
203        let hir_id = tcx.local_def_id_to_hir_id(def_id);
204        let body_span = tcx.hir_span_with_body(hir_id);
205        let source_map = tcx.sess.source_map();
206        let lo_pos = source_map.lookup_char_pos(body_span.lo());
207        let start_line = lo_pos.line;
208        let start_col = lo_pos.col_display;
209        let hi_pos = source_map.lookup_char_pos(body_span.hi());
210        let end_line = hi_pos.line;
211        let end_col = hi_pos.col_display;
212
213        // is the line in the range of the body?
214        if start_line < end_line {
215            // multiple lines: check if the line is in the range
216            start_line <= line && line <= end_line
217        } else {
218            // single line: check if the line is the same and the column is in range
219            start_line == line && start_col <= col && col <= end_col
220        }
221    }
222
223    /// Check whether the `def_id` (or the file where `def_id` is defined)
224    /// is in the `include` pattern, and conservatively return `true` if
225    /// anything unexpected happens.
226    fn is_included(&self, def_id: MaybeExternId) -> bool {
227        let Some(pattern) = config::include_pattern() else { return true };
228        if self.matches_file_path(def_id, |path| pattern.glob.is_match(path)) {
229            return true;
230        }
231        if pattern.defs.iter().any(|def| self.matches_def(def_id, def)) {
232            return true;
233        }
234        if pattern.spans.iter().any(|pos| {
235            self.matches_file_path(def_id, |path| path.ends_with(&pos.file))
236                && self.matches_pos(def_id, pos.line, pos.column)
237        }) {
238            return true;
239        }
240        false
241    }
242
243    fn check_def_catching_bugs(&mut self, def_id: LocalDefId) -> Result<(), ErrorGuaranteed> {
244        let mut this = std::panic::AssertUnwindSafe(self);
245        let msg = format!("def_id: {:?}, span: {:?}", def_id, this.genv.tcx().def_span(def_id));
246        flux_common::bug::catch_bugs(&msg, move || this.check_def(def_id))?
247    }
248
249    fn check_def(&mut self, def_id: LocalDefId) -> Result<(), ErrorGuaranteed> {
250        let def_id = self.genv.maybe_extern_id(def_id);
251
252        // Dummy items generated for extern specs are excluded from metrics
253        if self.genv.is_dummy(def_id.local_id()) {
254            return Ok(());
255        }
256
257        let kind = self.genv.def_kind(def_id);
258
259        // For the purpose of metrics, we consider to be a *function* an item that
260        // 1. It's local, i.e., it's not an extern spec.
261        // 2. It's a free function (`DefKind::Fn`) or associated item (`DefKind::AssocFn`), and
262        // 3. It has a mir body
263        // In particular, this excludes closures (because they dont have the right `DefKind`) and
264        // trait methods without a default body.
265        let is_fn_with_body = def_id
266            .as_local()
267            .map(|local_id| {
268                matches!(kind, DefKind::Fn | DefKind::AssocFn)
269                    && self.genv.tcx().is_mir_available(local_id)
270            })
271            .unwrap_or(false);
272
273        metrics::incr_metric_if(is_fn_with_body, Metric::FnTotal);
274
275        if self.genv.ignored(def_id.local_id()) {
276            metrics::incr_metric_if(is_fn_with_body, Metric::FnIgnored);
277            return Ok(());
278        }
279        if !self.is_included(def_id) {
280            metrics::incr_metric_if(is_fn_with_body, Metric::FnTrusted);
281            return Ok(());
282        }
283
284        match kind {
285            DefKind::Fn | DefKind::AssocFn => {
286                // Make sure we run conversion and report errors even if we skip the function
287                force_conv(self.genv, def_id.resolved_id()).emit(&self.genv)?;
288                let Some(local_id) = def_id.as_local() else { return Ok(()) };
289                if is_fn_with_body {
290                    refineck::check_fn(self.genv, &mut self.cache, local_id)?;
291                }
292                Ok(())
293            }
294            DefKind::Enum => {
295                self.genv.check_wf(def_id.local_id()).emit(&self.genv)?;
296                self.genv.variants_of(def_id).emit(&self.genv)?;
297                let adt_def = self.genv.adt_def(def_id).emit(&self.genv)?;
298                let enum_def = self
299                    .genv
300                    .fhir_expect_item(def_id.local_id())
301                    .emit(&self.genv)?
302                    .expect_enum();
303                refineck::invariants::check_invariants(
304                    self.genv,
305                    &mut self.cache,
306                    def_id,
307                    enum_def.invariants,
308                    &adt_def,
309                )
310            }
311            DefKind::Struct => {
312                // We check invariants for `struct` in `check_constructor` (i.e. when the struct is built).
313                // However, we leave the below code in to force the queries that do the conversions that check
314                // for ill-formed annotations e.g. see tests/tests/neg/error_messages/annot_check/struct_error.rs
315                self.genv.check_wf(def_id.local_id()).emit(&self.genv)?;
316                self.genv.adt_def(def_id).emit(&self.genv)?;
317                self.genv.variants_of(def_id).emit(&self.genv)?;
318                let _struct_def = self
319                    .genv
320                    .fhir_expect_item(def_id.local_id())
321                    .emit(&self.genv)?
322                    .expect_struct();
323                Ok(())
324            }
325            DefKind::Impl { of_trait } => {
326                self.genv.check_wf(def_id.local_id()).emit(&self.genv)?;
327                if of_trait {
328                    refineck::compare_impl_item::check_impl_against_trait(self.genv, def_id)
329                        .emit(&self.genv)?;
330                }
331                Ok(())
332            }
333            DefKind::TyAlias => {
334                self.genv.check_wf(def_id.local_id()).emit(&self.genv)?;
335                self.genv.type_of(def_id).emit(&self.genv)?;
336                Ok(())
337            }
338            DefKind::Trait => {
339                self.genv.check_wf(def_id.local_id()).emit(&self.genv)?;
340                Ok(())
341            }
342            _ => Ok(()),
343        }
344    }
345}
346
347fn force_conv(genv: GlobalEnv, def_id: DefId) -> QueryResult {
348    genv.generics_of(def_id)?;
349    genv.refinement_generics_of(def_id)?;
350    genv.predicates_of(def_id)?;
351    genv.fn_sig(def_id)?;
352    Ok(())
353}
354
355fn mir_borrowck<'tcx>(
356    tcx: TyCtxt<'tcx>,
357    def_id: LocalDefId,
358) -> query::queries::mir_borrowck::ProvidedValue<'tcx> {
359    let bodies_with_facts = rustc_borrowck::consumers::get_bodies_with_borrowck_facts(
360        tcx,
361        def_id,
362        ConsumerOptions::RegionInferenceContext,
363    );
364    for (def_id, body_with_facts) in bodies_with_facts {
365        // SAFETY: This is safe because we are feeding in the same `tcx` that is
366        // going to be used as a witness when pulling out the data.
367        unsafe {
368            flux_common::mir_storage::store_mir_body(tcx, def_id, body_with_facts);
369        }
370    }
371    let mut providers = query::Providers::default();
372    rustc_borrowck::provide(&mut providers);
373    let original_mir_borrowck = providers.mir_borrowck;
374    original_mir_borrowck(tcx, def_id)
375}