Skip to main content

flux_driver/
callbacks.rs

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