Skip to main content

flux_driver/
callbacks.rs

1use flux_common::{bug, cache::QueryCache, iter::IterExt, result::ResultExt};
2use flux_config::{self as config};
3use flux_errors::FluxSession;
4use flux_infer::{
5    fixpoint_encoding::{FixQueryCache, LeanStatus, lean_task_key},
6    lean_encoding,
7};
8use flux_metadata::CStore;
9use flux_middle::{
10    Specs,
11    def_id::MaybeExternId,
12    fhir::{self},
13    global_env::GlobalEnv,
14    metrics::{self, Metric, TimingKind},
15    queries::{Providers, QueryResult},
16    rty::StaticInfo,
17};
18use flux_refineck as refineck;
19use rustc_borrowck::consumers::ConsumerOptions;
20use rustc_driver::{Callbacks, Compilation};
21use rustc_errors::ErrorGuaranteed;
22use rustc_hir::{
23    def::{CtorKind, DefKind},
24    def_id::{LOCAL_CRATE, LocalDefId},
25};
26use rustc_interface::interface::Compiler;
27use rustc_middle::{queries, ty::TyCtxt, util};
28use rustc_session::config::OutputType;
29
30use crate::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.queries.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        if flux_config::std_extern_specs() {
48            inject_std_extern_specs(config);
49        }
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(&tcx.sess.opts, tcx.sess.psess.clone_source_map());
65
66        let mut providers = Providers::default();
67        flux_opt::provide(&mut providers);
68        flux_desugar::provide(&mut providers);
69        flux_fhir_analysis::provide(&mut providers);
70        providers.collect_specs = collect_specs;
71
72        let cstore = CStore::load(tcx, &sess);
73        let arena = fhir::Arena::new();
74        GlobalEnv::enter(tcx, &sess, Box::new(cstore), &arena, providers, |genv| {
75            let result = metrics::time_it(TimingKind::Total, || check_crate(genv));
76            if result.is_ok() {
77                encode_and_save_metadata(genv);
78            }
79            lean_encoding::finalize(genv).unwrap_or(());
80        });
81        let _ = metrics::print_and_dump_timings(tcx);
82        sess.finish_diagnostics();
83    }
84}
85
86fn load_extern_specs() -> Vec<(String, std::path::PathBuf)> {
87    use flux_sysroot::SysrootManifest;
88    let Some(sysroot) = config::sysroot() else { return vec![] };
89    SysrootManifest::extern_specs(&sysroot)
90}
91
92fn inject_std_extern_specs(config: &mut rustc_interface::interface::Config) {
93    use std::collections::{BTreeMap, BTreeSet};
94
95    use rustc_session::{
96        config::{ExternEntry, ExternLocation, Externs},
97        utils::CanonicalizedPath,
98    };
99
100    let specs = load_extern_specs();
101    if specs.is_empty() {
102        return;
103    }
104
105    let mut map: BTreeMap<String, ExternEntry> = config
106        .opts
107        .externs
108        .iter()
109        .map(|(k, v)| (k.clone(), v.clone()))
110        .collect();
111
112    for (crate_name, rmeta_path) in specs {
113        let entry = ExternEntry {
114            location: ExternLocation::ExactPaths(BTreeSet::from([CanonicalizedPath::new(
115                rmeta_path,
116            )])),
117            is_private_dep: false,
118            add_prelude: true,
119            nounused_dep: true,
120            force: true,
121        };
122        map.insert(crate_name, entry);
123    }
124
125    config.opts.externs = Externs::new(map);
126}
127
128fn check_crate(genv: GlobalEnv) -> Result<(), ErrorGuaranteed> {
129    tracing::info_span!("check_crate").in_scope(move || {
130        tracing::info!("Callbacks::check_wf");
131        // Query qualifiers and spec funcs to report wf errors
132        let _ = genv.qualifiers().emit(&genv)?;
133        let _ = genv.normalized_defns(LOCAL_CRATE);
134
135        let mut ck = CrateChecker::new(genv);
136
137        // Iterate over all def ids including dummy items for extern specs
138        let result = genv
139            .tcx()
140            .iter_local_def_id()
141            .try_for_each_exhaust(|def_id| ck.check_def_catching_bugs(def_id));
142
143        if config::lean().is_check() || config::lean().is_emit() {
144            lean_encoding::finalize(genv)
145                .unwrap_or_else(|err| bug!("error running lean-check {err:?}"));
146        }
147
148        let lean_result = if config::lean().is_check() {
149            // A cached proof is only valid if the lean project has not changed since it was checked
150            let digest = if config::is_cache_enabled() {
151                lean_encoding::project_digest(genv).ok()
152            } else {
153                None
154            };
155            let pending = genv
156                .iter_local_def_id()
157                .filter(|def_id| {
158                    // Skip proof check if not included or not proven externally
159                    genv.included(genv.maybe_extern_id(*def_id))
160                        && genv.proven_externally(*def_id).is_some()
161                })
162                .map(|def_id| def_id.to_def_id())
163                .filter(|def_id| {
164                    // Skip proof check if previously verified successfully.
165                    let key = lean_task_key(genv.tcx(), *def_id);
166                    let cached = ck.cache.lookup_by_key(&key).map(|r| &r.lean_status);
167                    !matches!((cached, digest), (Some(LeanStatus::Valid(d1)), Some(d2)) if *d1 == d2)
168                })
169                .collect::<Vec<_>>();
170            // Check all the pending proofs with a single `lake build`
171            let results = lean_encoding::check_proofs(genv, &pending);
172            pending
173                .iter()
174                .zip(results)
175                .map(|(def_id, result)| {
176                    if result.is_ok()
177                        && let Some(digest) = digest
178                    {
179                        // Mark as valid in cache so future runs skip re-verification.
180                        let key = lean_task_key(genv.tcx(), *def_id);
181                        ck.cache.update_result_by_key(&key, |r| {
182                            r.lean_status = LeanStatus::Valid(digest);
183                        });
184                    }
185                    result
186                })
187                .try_for_each_exhaust(|result| result)
188        } else {
189            Ok(())
190        };
191
192        ck.cache.save().unwrap_or(());
193
194        tracing::info!("Callbacks::check_crate");
195
196        result.and(lean_result)
197    })
198}
199
200fn collect_specs(genv: GlobalEnv) -> Specs {
201    match SpecCollector::collect(genv.tcx(), genv.sess()) {
202        Ok(specs) => specs,
203        Err(err) => {
204            genv.sess().abort(err);
205        }
206    }
207}
208
209fn encode_and_save_metadata(genv: GlobalEnv) {
210    // We only save metadata when `--emit=metadata` is passed as an argument. In this case, we save
211    // the `.fluxmeta` file alongside the `.rmeta` file. This setup works for `cargo flux`, which
212    // wraps `cargo check` and always passes `--emit=metadata`. Tests also explicitly pass this flag.
213    let tcx = genv.tcx();
214    if tcx
215        .output_filenames(())
216        .outputs
217        .contains_key(&OutputType::Metadata)
218    {
219        let path = flux_metadata::filename_for_metadata(tcx);
220        flux_metadata::encode_metadata(genv, path.as_path());
221    }
222}
223
224struct CrateChecker<'genv, 'tcx> {
225    genv: GlobalEnv<'genv, 'tcx>,
226    cache: FixQueryCache,
227}
228
229impl<'genv, 'tcx> CrateChecker<'genv, 'tcx> {
230    fn new(genv: GlobalEnv<'genv, 'tcx>) -> Self {
231        Self { genv, cache: QueryCache::load() }
232    }
233
234    fn check_def_catching_bugs(&mut self, def_id: LocalDefId) -> Result<(), ErrorGuaranteed> {
235        let mut this = std::panic::AssertUnwindSafe(self);
236        let msg = format!("def_id: {:?}, span: {:?}", def_id, this.genv.tcx().def_span(def_id));
237        flux_common::bug::catch_bugs(&msg, move || this.check_def(def_id))?
238    }
239
240    fn check_def(&mut self, def_id: LocalDefId) -> Result<(), ErrorGuaranteed> {
241        let genv = self.genv;
242        let def_id = genv.maybe_extern_id(def_id);
243
244        // Dummy items generated for extern specs are excluded from metrics
245        if genv.is_dummy(def_id.local_id()) {
246            return Ok(());
247        }
248
249        let kind = genv.def_kind(def_id);
250
251        // For the purpose of metrics, we consider to be a *function* an item that
252        // 1. It's local, i.e., it's not an extern spec.
253        // 2. It's a free function (`DefKind::Fn`) or associated item (`DefKind::AssocFn`), and
254        // 3. It has a mir body
255        // In particular, this excludes closures (because they dont have the right `DefKind`) and
256        // trait methods without a default body.
257        let is_fn_with_body = def_id
258            .as_local()
259            .map(|local_id| {
260                matches!(kind, DefKind::Fn | DefKind::AssocFn)
261                    && genv.tcx().is_mir_available(local_id)
262            })
263            .unwrap_or(false);
264
265        metrics::incr_metric_if(is_fn_with_body, Metric::FnTotal);
266
267        if genv.ignored(def_id.local_id()) {
268            metrics::incr_metric_if(is_fn_with_body, Metric::FnIgnored);
269            return Ok(());
270        }
271        if !self.genv.included(def_id) {
272            metrics::incr_metric_if(is_fn_with_body, Metric::FnTrusted);
273            return Ok(());
274        }
275
276        trigger_queries(genv, def_id).emit(&genv)?;
277
278        match kind {
279            DefKind::Fn | DefKind::AssocFn => {
280                let Some(local_id) = def_id.as_local() else { return Ok(()) };
281                if is_fn_with_body {
282                    refineck::check_fn(genv, &mut self.cache, local_id)?;
283                }
284            }
285            DefKind::Enum => {
286                let adt_def = genv.adt_def(def_id).emit(&genv)?;
287                let enum_def = genv
288                    .fhir_expect_item(def_id.local_id())
289                    .emit(&genv)?
290                    .expect_enum();
291                refineck::invariants::check_invariants(
292                    genv,
293                    &mut self.cache,
294                    def_id,
295                    enum_def.invariants,
296                    &adt_def,
297                )?;
298            }
299            DefKind::Struct => {
300                // We check invariants for `struct` in `check_constructor` (i.e. when the struct is built),
301                // so nothing to do here.
302            }
303            DefKind::Impl { of_trait } => {
304                if of_trait {
305                    refineck::compare_impl_item::check_impl_against_trait(genv, def_id)
306                        .emit(&genv)?;
307                }
308            }
309            DefKind::TyAlias => {}
310            DefKind::Trait => {}
311            DefKind::Static { .. } => {
312                if let StaticInfo::Known(ty) = genv.static_info(def_id).emit(&genv)?
313                    && let Some(local_id) = def_id.as_local()
314                {
315                    refineck::check_static(genv, &mut self.cache, local_id, ty)?;
316                }
317            }
318            _ => (),
319        }
320        Ok(())
321    }
322}
323
324/// Triggers queries for the given `def_id` to mark it as "reached" for metadata encoding.
325///
326/// This function ensures that all relevant queries for a definition are triggered upfront,
327/// so the item and its associated data will be included in the encoded metadata. Without this,
328/// items might be missing from the metadata (extern specs in particular which are not otherwise "checked"),
329/// causing errors when dependent crates try to use them.
330fn trigger_queries(genv: GlobalEnv, def_id: MaybeExternId) -> QueryResult {
331    match genv.def_kind(def_id) {
332        DefKind::Trait => {
333            genv.generics_of(def_id)?;
334            genv.predicates_of(def_id)?;
335            genv.refinement_generics_of(def_id)?;
336        }
337        DefKind::Impl { .. } => {
338            genv.generics_of(def_id)?;
339            genv.predicates_of(def_id)?;
340            genv.refinement_generics_of(def_id)?;
341        }
342        DefKind::Fn | DefKind::AssocFn => {
343            genv.generics_of(def_id)?;
344            genv.refinement_generics_of(def_id)?;
345            genv.predicates_of(def_id)?;
346            genv.fn_sig(def_id)?;
347        }
348        DefKind::Ctor(_, CtorKind::Fn) => {
349            genv.generics_of(def_id)?;
350            genv.refinement_generics_of(def_id)?;
351            // We don't report the error because it can raise a `QueryErr::OpaqueStruct`,  which
352            // should be reported at the use site.
353            let _ = genv.fn_sig(def_id);
354        }
355        DefKind::Enum | DefKind::Struct => {
356            genv.generics_of(def_id)?;
357            genv.predicates_of(def_id)?;
358            genv.refinement_generics_of(def_id)?;
359            genv.adt_def(def_id)?;
360            genv.adt_sort_def_of(def_id)?;
361            genv.variants_of(def_id)?;
362            genv.type_of(def_id)?;
363        }
364        DefKind::TyAlias => {
365            genv.generics_of(def_id)?;
366            genv.predicates_of(def_id)?;
367            genv.refinement_generics_of(def_id)?;
368            genv.type_of(def_id)?;
369        }
370        DefKind::OpaqueTy => {
371            genv.generics_of(def_id)?;
372            genv.predicates_of(def_id)?;
373            genv.item_bounds(def_id)?;
374            genv.refinement_generics_of(def_id)?;
375        }
376        _ => {}
377    }
378    Ok(())
379}
380
381fn mir_borrowck<'tcx>(
382    tcx: TyCtxt<'tcx>,
383    def_id: LocalDefId,
384) -> queries::mir_borrowck::ProvidedValue<'tcx> {
385    let bodies_with_facts = rustc_borrowck::consumers::get_bodies_with_borrowck_facts(
386        tcx,
387        def_id,
388        ConsumerOptions::RegionInferenceContext,
389    );
390    for (def_id, body_with_facts) in bodies_with_facts {
391        // SAFETY: This is safe because we are feeding in the same `tcx` that is
392        // going to be used as a witness when pulling out the data.
393        unsafe {
394            flux_common::mir_storage::store_mir_body(tcx, def_id, body_with_facts);
395        }
396    }
397    let mut providers = util::Providers::default();
398    rustc_borrowck::provide(&mut providers.queries);
399    let original_mir_borrowck = providers.queries.mir_borrowck;
400    original_mir_borrowck(tcx, def_id)
401}