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 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 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 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 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 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 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 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 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 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
226fn 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 if genv.is_dummy(def_id.local_id()) {
262 return Ok(());
263 }
264
265 let kind = genv.def_kind(def_id);
266
267 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 }
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 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 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
344fn 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 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 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}