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};
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::{queries, ty::TyCtxt, util};
27use rustc_session::config::OutputType;
28
29use crate::collector::SpecCollector;
30
31#[derive(Default)]
32pub struct FluxCallbacks;
33
34impl Callbacks for FluxCallbacks {
35    fn config(&mut self, config: &mut rustc_interface::interface::Config) {
36        assert!(config.override_queries.is_none());
37
38        config.override_queries = Some(|_, local| {
39            local.queries.mir_borrowck = mir_borrowck;
40        });
41        // this should always be empty otherwise something changed in rustc and all our assumptions
42        // about symbol interning are wrong.
43        assert!(config.extra_symbols.is_empty());
44        config.extra_symbols = flux_syntax::symbols::PREDEFINED_FLUX_SYMBOLS.to_vec();
45
46        if flux_config::std_extern_specs() {
47            inject_std_extern_specs(config);
48        }
49    }
50
51    fn after_analysis(&mut self, compiler: &Compiler, tcx: TyCtxt<'_>) -> Compilation {
52        self.verify(compiler, tcx);
53        if config::full_compilation() { Compilation::Continue } else { Compilation::Stop }
54    }
55}
56
57impl FluxCallbacks {
58    fn verify(&self, compiler: &Compiler, tcx: TyCtxt<'_>) {
59        if compiler.sess.dcx().has_errors().is_some() {
60            return;
61        }
62
63        let sess = FluxSession::new(&tcx.sess.opts, tcx.sess.psess.clone_source_map());
64
65        let mut providers = Providers::default();
66        flux_opt::provide(&mut providers);
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 load_extern_specs() -> Vec<(String, std::path::PathBuf)> {
86    use flux_sysroot::SysrootManifest;
87    let Some(sysroot) = config::sysroot() else { return vec![] };
88    SysrootManifest::extern_specs(&sysroot)
89}
90
91fn inject_std_extern_specs(config: &mut rustc_interface::interface::Config) {
92    use std::collections::{BTreeMap, BTreeSet};
93
94    use rustc_session::{
95        config::{ExternEntry, ExternLocation, Externs},
96        utils::CanonicalizedPath,
97    };
98
99    let specs = load_extern_specs();
100    if specs.is_empty() {
101        return;
102    }
103
104    let mut map: BTreeMap<String, ExternEntry> = config
105        .opts
106        .externs
107        .iter()
108        .map(|(k, v)| (k.clone(), v.clone()))
109        .collect();
110
111    for (crate_name, rmeta_path) in specs {
112        let entry = ExternEntry {
113            location: ExternLocation::ExactPaths(BTreeSet::from([CanonicalizedPath::new(
114                rmeta_path,
115            )])),
116            is_private_dep: false,
117            add_prelude: true,
118            nounused_dep: true,
119            force: true,
120        };
121        map.insert(crate_name, entry);
122    }
123
124    config.opts.externs = Externs::new(map);
125}
126
127fn check_crate(genv: GlobalEnv) -> Result<(), ErrorGuaranteed> {
128    tracing::info_span!("check_crate").in_scope(move || {
129        tracing::info!("Callbacks::check_wf");
130        // Query qualifiers and spec funcs to report wf errors
131        let _ = genv.qualifiers().emit(&genv)?;
132        let _ = genv.spec_funcs(LOCAL_CRATE);
133
134        let mut ck = CrateChecker::new(genv);
135
136        // Iterate over all def ids including dummy items for extern specs
137        let result = genv
138            .tcx()
139            .iter_local_def_id()
140            .try_for_each_exhaust(|def_id| ck.check_def_catching_bugs(def_id));
141
142        if config::lean().is_check() || config::lean().is_emit() {
143            lean_encoding::finalize(genv)
144                .unwrap_or_else(|err| bug!("error running lean-check {err:?}"));
145        }
146
147        let lean_result = if config::lean().is_check() {
148            // A cached proof is only valid if the lean project has not changed since it was checked
149            let digest = if config::is_cache_enabled() {
150                lean_encoding::project_digest(genv).ok()
151            } else {
152                None
153            };
154            let pending = genv
155                .iter_local_def_id()
156                .filter(|def_id| {
157                    // Skip proof check if not included or not proven externally
158                    genv.included(genv.maybe_extern_id(*def_id))
159                        && genv.proven_externally(*def_id).is_some()
160                })
161                .map(|def_id| def_id.to_def_id())
162                .filter(|def_id| {
163                    // Skip proof check if previously verified successfully.
164                    let key = lean_task_key(genv.tcx(), *def_id);
165                    let cached = ck.cache.lookup_by_key(&key).map(|r| &r.lean_status);
166                    !matches!((cached, digest), (Some(LeanStatus::Valid(d1)), Some(d2)) if *d1 == d2)
167                })
168                .collect::<Vec<_>>();
169            // Check all the pending proofs with a single `lake build`
170            let results = lean_encoding::check_proofs(genv, &pending);
171            pending
172                .iter()
173                .zip(results)
174                .map(|(def_id, result)| {
175                    if result.is_ok()
176                        && let Some(digest) = digest
177                    {
178                        // Mark as valid in cache so future runs skip re-verification.
179                        let key = lean_task_key(genv.tcx(), *def_id);
180                        ck.cache.update_result_by_key(&key, |r| {
181                            r.lean_status = LeanStatus::Valid(digest);
182                        });
183                    }
184                    result
185                })
186                .try_for_each_exhaust(|result| result)
187        } else {
188            Ok(())
189        };
190
191        ck.cache.save().unwrap_or(());
192
193        tracing::info!("Callbacks::check_crate");
194
195        result.and(lean_result)
196    })
197}
198
199fn collect_specs(genv: GlobalEnv) -> Specs {
200    match SpecCollector::collect(genv.tcx(), genv.sess()) {
201        Ok(specs) => specs,
202        Err(err) => {
203            genv.sess().abort(err);
204        }
205    }
206}
207
208fn encode_and_save_metadata(genv: GlobalEnv) {
209    // We only save metadata when `--emit=metadata` is passed as an argument. In this case, we save
210    // the `.fluxmeta` file alongside the `.rmeta` file. This setup works for `cargo flux`, which
211    // wraps `cargo check` and always passes `--emit=metadata`. Tests also explicitly pass this flag.
212    let tcx = genv.tcx();
213    if tcx
214        .output_filenames(())
215        .outputs
216        .contains_key(&OutputType::Metadata)
217    {
218        let path = flux_metadata::filename_for_metadata(tcx);
219        flux_metadata::encode_metadata(genv, path.as_path());
220    }
221}
222
223struct CrateChecker<'genv, 'tcx> {
224    genv: GlobalEnv<'genv, 'tcx>,
225    cache: FixQueryCache,
226}
227
228impl<'genv, 'tcx> CrateChecker<'genv, 'tcx> {
229    fn new(genv: GlobalEnv<'genv, 'tcx>) -> Self {
230        Self { genv, cache: QueryCache::load() }
231    }
232
233    fn check_def_catching_bugs(&mut self, def_id: LocalDefId) -> Result<(), ErrorGuaranteed> {
234        let mut this = std::panic::AssertUnwindSafe(self);
235        let msg = format!("def_id: {:?}, span: {:?}", def_id, this.genv.tcx().def_span(def_id));
236        flux_common::bug::catch_bugs(&msg, move || this.check_def(def_id))?
237    }
238
239    fn check_def(&mut self, def_id: LocalDefId) -> Result<(), ErrorGuaranteed> {
240        let genv = self.genv;
241        let def_id = genv.maybe_extern_id(def_id);
242
243        // Dummy items generated for extern specs are excluded from metrics
244        if genv.is_dummy(def_id.local_id()) {
245            return Ok(());
246        }
247
248        let kind = genv.def_kind(def_id);
249
250        // For the purpose of metrics, we consider to be a *function* an item that
251        // 1. It's local, i.e., it's not an extern spec.
252        // 2. It's a free function (`DefKind::Fn`) or associated item (`DefKind::AssocFn`), and
253        // 3. It has a mir body
254        // In particular, this excludes closures (because they dont have the right `DefKind`) and
255        // trait methods without a default body.
256        let is_fn_with_body = def_id
257            .as_local()
258            .map(|local_id| {
259                matches!(kind, DefKind::Fn | DefKind::AssocFn)
260                    && genv.tcx().is_mir_available(local_id)
261            })
262            .unwrap_or(false);
263
264        metrics::incr_metric_if(is_fn_with_body, Metric::FnTotal);
265
266        if genv.ignored(def_id.local_id()) {
267            metrics::incr_metric_if(is_fn_with_body, Metric::FnIgnored);
268            return Ok(());
269        }
270        if !self.genv.included(def_id) {
271            metrics::incr_metric_if(is_fn_with_body, Metric::FnTrusted);
272            return Ok(());
273        }
274
275        trigger_queries(genv, def_id).emit(&genv)?;
276
277        match kind {
278            DefKind::Fn | DefKind::AssocFn => {
279                let Some(local_id) = def_id.as_local() else { return Ok(()) };
280                if is_fn_with_body {
281                    refineck::check_fn(genv, &mut self.cache, local_id)?;
282                }
283            }
284            DefKind::Enum => {
285                let adt_def = genv.adt_def(def_id).emit(&genv)?;
286                let enum_def = genv
287                    .fhir_expect_item(def_id.local_id())
288                    .emit(&genv)?
289                    .expect_enum();
290                refineck::invariants::check_invariants(
291                    genv,
292                    &mut self.cache,
293                    def_id,
294                    enum_def.invariants,
295                    &adt_def,
296                )?;
297            }
298            DefKind::Struct => {
299                // We check invariants for `struct` in `check_constructor` (i.e. when the struct is built),
300                // so nothing to do here.
301            }
302            DefKind::Impl { of_trait } => {
303                if of_trait {
304                    refineck::compare_impl_item::check_impl_against_trait(genv, def_id)
305                        .emit(&genv)?;
306                }
307            }
308            DefKind::TyAlias => {}
309            DefKind::Trait => {}
310            DefKind::Static { .. } => {
311                // Extern (i.e. foreign) statics have no body (initializer) to check
312                if let Some(local_id) = def_id.as_local()
313                    && genv.tcx().hir_node_by_def_id(local_id).body_id().is_some()
314                {
315                    // A static without a spec is checked against the default refinement of its
316                    // type, e.g., to check the field specs of a struct in the initializer.
317                    let ty = genv.static_info(def_id).emit(&genv)?;
318                    refineck::check_static(genv, &mut self.cache, local_id, ty)?;
319                }
320            }
321            _ => (),
322        }
323        Ok(())
324    }
325}
326
327/// Triggers queries for the given `def_id` to mark it as "reached" for metadata encoding.
328///
329/// This function ensures that all relevant queries for a definition are triggered upfront,
330/// so the item and its associated data will be included in the encoded metadata. Without this,
331/// items might be missing from the metadata (extern specs in particular which are not otherwise "checked"),
332/// causing errors when dependent crates try to use them.
333fn trigger_queries(genv: GlobalEnv, def_id: MaybeExternId) -> QueryResult {
334    match genv.def_kind(def_id) {
335        DefKind::Trait => {
336            genv.generics_of(def_id)?;
337            genv.predicates_of(def_id)?;
338            genv.refinement_generics_of(def_id)?;
339        }
340        DefKind::Impl { .. } => {
341            genv.generics_of(def_id)?;
342            genv.predicates_of(def_id)?;
343            genv.refinement_generics_of(def_id)?;
344        }
345        DefKind::Fn | DefKind::AssocFn => {
346            genv.generics_of(def_id)?;
347            genv.refinement_generics_of(def_id)?;
348            genv.predicates_of(def_id)?;
349            genv.fn_sig(def_id)?;
350        }
351        DefKind::Ctor(_, CtorKind::Fn) => {
352            genv.generics_of(def_id)?;
353            genv.refinement_generics_of(def_id)?;
354            // We don't report the error because it can raise a `QueryErr::OpaqueStruct`,  which
355            // should be reported at the use site.
356            let _ = genv.fn_sig(def_id);
357        }
358        DefKind::Enum | DefKind::Struct => {
359            genv.generics_of(def_id)?;
360            genv.predicates_of(def_id)?;
361            genv.refinement_generics_of(def_id)?;
362            genv.adt_def(def_id)?;
363            genv.invariants_of(def_id);
364            genv.adt_sort_def_of(def_id)?;
365            genv.variants_of(def_id)?;
366            genv.type_of(def_id)?;
367        }
368        DefKind::TyAlias => {
369            genv.generics_of(def_id)?;
370            genv.predicates_of(def_id)?;
371            genv.refinement_generics_of(def_id)?;
372            genv.type_of(def_id)?;
373        }
374        DefKind::OpaqueTy => {
375            genv.generics_of(def_id)?;
376            genv.predicates_of(def_id)?;
377            genv.item_bounds(def_id)?;
378            genv.refinement_generics_of(def_id)?;
379        }
380        _ => {}
381    }
382    Ok(())
383}
384
385fn mir_borrowck<'tcx>(
386    tcx: TyCtxt<'tcx>,
387    def_id: LocalDefId,
388) -> 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 = util::Providers::default();
402    rustc_borrowck::provide(&mut providers.queries);
403    let original_mir_borrowck = providers.queries.mir_borrowck;
404    original_mir_borrowck(tcx, def_id)
405}