flux_driver/
callbacks.rs

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