charon_lib/options.rs
1//! The options that control charon behavior.
2use annotate_snippets::Level;
3use clap::ValueEnum;
4use indoc::indoc;
5use serde::{Deserialize, Serialize};
6use std::path::PathBuf;
7
8use crate::{
9 ast::*,
10 errors::{ErrorCtx, display_unspanned_error},
11 name_matcher::NamePattern,
12 raise_error, register_error,
13};
14
15/// The name of the environment variable we use to save the serialized Cli options
16/// when calling charon-driver from cargo-charon.
17pub const CHARON_ARGS: &str = "CHARON_ARGS";
18
19// This structure is used to store the command-line instructions.
20// We automatically derive a command-line parser based on this structure.
21// Note that the doc comments are used to generate the help message when using
22// `--help`.
23//
24// Note that because we need to transmit the options to the charon driver,
25// we store them in a file before calling this driver (hence the `Serialize`,
26// `Deserialize` options).
27#[derive(Debug, Default, Clone, clap::Args, PartialEq, Eq, Serialize, Deserialize)]
28#[clap(name = "Charon")]
29#[charon::rename("cli_options")]
30pub struct CliOpts {
31 /// Extract the unstructured LLBC (i.e., don't reconstruct the control-flow)
32 #[clap(long)]
33 #[serde(default)]
34 pub ullbc: bool,
35 /// Whether to precisely translate drops and drop-related code. For this, we add explicit
36 /// `Destruct` bounds to all generic parameters, set the MIR level to at least `elaborated`,
37 /// and attempt to retrieve drop glue for all types.
38 ///
39 /// This option is known to cause panics inside rustc, because their drop handling is not
40 /// design to work on polymorphic types. To silence the warning, pass appropriate `--opaque
41 /// '{impl core::marker::Destruct for some::Type}'` options.
42 ///
43 /// Without this option, drops may be "conditional" and we may lack information about what code
44 /// is run on drop in a given polymorphic function body.
45 #[clap(long)]
46 #[serde(default)]
47 pub precise_drops: bool,
48 /// If activated, this skips borrow-checking of the crate.
49 #[clap(long)]
50 #[serde(default)]
51 pub skip_borrowck: bool,
52 /// The MIR stage to extract. This is only relevant for the current crate; for dpendencies only
53 /// MIR optimized is available.
54 #[arg(long)]
55 pub mir: Option<MirLevel>,
56 /// Extra flags to pass to rustc.
57 #[clap(long = "rustc-arg")]
58 #[serde(default)]
59 pub rustc_args: Vec<String>,
60 /// Monomorphize the items encountered when possible. Generic items found in the crate are
61 /// skipped. To only translate a particular call graph, use `--start-from`. Note: this doesn't
62 /// currently support `dyn Trait`.
63 #[clap(long, visible_alias = "mono")]
64 #[serde(default)]
65 pub monomorphize: bool,
66 /// Partially monomorphize items to make it so that no item is ever monomorphized with a
67 /// mutable reference (or type containing one); said differently, so that the presence of
68 /// mutable references in a type is independent of its generics. This is used by Aeneas.
69 #[clap(
70 long,
71 value_name("INCLUDE_TYPES"),
72 num_args(0..=1),
73 require_equals(true),
74 default_missing_value("all"),
75 )]
76 #[serde(default)]
77 pub monomorphize_mut: Option<MonomorphizeMut>,
78
79 /// A list of item paths to use as starting points for the translation. We will translate these
80 /// items and any items they refer to, according to the opacity rules. When absent, we start
81 /// from the path `crate` (which translates the whole crate).
82 #[clap(long, value_delimiter = ',')]
83 #[serde(default)]
84 pub start_from: Vec<String>,
85 /// Whitelist of items to translate. These use the name-matcher syntax.
86 #[clap(
87 long,
88 help = indoc!("
89 Whitelist of items to translate. These use the name-matcher syntax (note: this differs
90 a bit from the ocaml NameMatcher).
91
92 Note: This is very rough at the moment. E.g. this parses `u64` as a path instead of the
93 built-in type. It is also not possible to filter a trait impl (this will only filter
94 its methods). Please report bugs or missing features.
95
96 Examples:
97 - `crate::module1::module2::item`: refers to this item and all its subitems (e.g.
98 submodules or trait methods);
99 - `crate::module1::module2::item::_`: refers only to the subitems of this item;
100 - `core::convert::{impl core::convert::Into<_> for _}`: retrieve the body of this
101 very useful impl;
102
103 When multiple patterns in the `--include` and `--opaque` options match the same item,
104 the most precise pattern wins. E.g.: `charon --opaque crate::module --include
105 crate::module::_` makes the `module` opaque (we won't explore its contents), but the
106 items in it transparent (we will translate them if we encounter them.)
107 "))]
108 #[serde(default)]
109 #[charon::rename("included")]
110 pub include: Vec<String>,
111 /// Blacklist of items to keep opaque. Works just like `--include`, see the doc there.
112 #[clap(long)]
113 #[serde(default)]
114 pub opaque: Vec<String>,
115 /// Blacklist of items to not translate at all. Works just like `--include`, see the doc there.
116 #[clap(long)]
117 #[serde(default)]
118 pub exclude: Vec<String>,
119 /// Usually we skip the bodies of foreign methods and structs with private fields. When this
120 /// flag is on, we don't.
121 #[clap(long)]
122 #[serde(default)]
123 pub extract_opaque_bodies: bool,
124 /// Usually we skip the provided methods that aren't used. When this flag is on, we translate
125 /// them all.
126 #[clap(long)]
127 #[serde(default)]
128 pub translate_all_methods: bool,
129
130 /// Transforma the associate types of traits to be type parameters instead. This takes a list
131 /// of name patterns of the traits to transform, using the same syntax as `--include`.
132 #[clap(long)]
133 #[serde(default)]
134 pub remove_associated_types: Vec<String>,
135 /// Whether to hide various marker traits such as `Sized`, `Sync`, `Send` and `Destruct`
136 /// anywhere they show up. This can considerably speed up translation.
137 #[clap(long)]
138 #[serde(default)]
139 pub hide_marker_traits: bool,
140 /// Remove trait clauses from type declarations. Must be combined with
141 /// `--remove-associated-types` for type declarations that use trait associated types in their
142 /// fields, otherwise this will result in errors.
143 #[clap(long)]
144 #[serde(default)]
145 pub remove_adt_clauses: bool,
146 /// Hide the `A` type parameter on standard library containers (`Box`, `Vec`, etc).
147 #[clap(long)]
148 #[serde(default)]
149 pub hide_allocator: bool,
150 /// Trait method declarations take a `Self: Trait` clause as parameter, so that they can be
151 /// reused by multiple trait impls. This however causes trait definitions to be mutually
152 /// recursive with their method declarations. This flag removes `Self` clauses that aren't used
153 /// to break this mutual recursion when possible.
154 #[clap(long)]
155 #[serde(default)]
156 pub remove_unused_self_clauses: bool,
157
158 /// Transform precise drops to the equivalent `drop_in_place(&raw mut p)` call.
159 #[clap(long)]
160 #[serde(default)]
161 pub desugar_drops: bool,
162 /// Transform array-to-slice unsizing, repeat expressions, and raw pointer construction into
163 /// builtin functions in ULLBC.
164 #[clap(long)]
165 #[serde(default)]
166 pub ops_to_function_calls: bool,
167 /// Transform array/slice indexing into builtin functions in ULLBC. Note that this may
168 /// introduce UB since it creates references that were not normally created, including when
169 /// indexing behind a raw pointer.
170 #[clap(long)]
171 #[serde(default)]
172 pub index_to_function_calls: bool,
173 /// Treat `Box<T>` as if it was a built-in type.
174 #[clap(long)]
175 #[serde(default)]
176 pub treat_box_as_builtin: bool,
177 /// Do not inline or evaluate constants.
178 #[clap(long)]
179 #[serde(default)]
180 pub raw_consts: bool,
181 /// Replace "bound checks followed by UB-on-overflow operation" with the corresponding
182 /// panic-on-overflow operation. This loses unwinding information.
183 #[clap(long)]
184 #[serde(default)]
185 pub reconstruct_fallible_operations: bool,
186 /// Replace "if x { panic() }" with "assert(x)".
187 #[clap(long)]
188 #[serde(default)]
189 pub reconstruct_asserts: bool,
190 /// Use `DeBruijnVar::Free` for the variables bound in item signatures, instead of
191 /// `DeBruijnVar::Bound` everywhere. This simplifies the management of generics for projects
192 /// that don't intend to manipulate them too much.
193 #[clap(long)]
194 #[serde(default)]
195 pub unbind_item_vars: bool,
196 /// Disable the aeneas-only erasure of `Body` regions. Temporary flag to help migration.
197 #[clap(long)]
198 #[serde(default)]
199 pub no_erase_body_regions: bool,
200
201 /// Pretty-print the ULLBC immediately after extraction from MIR.
202 #[clap(long)]
203 #[serde(default)]
204 pub print_original_ullbc: bool,
205 /// Pretty-print the ULLBC after applying the micro-passes (before serialization/control-flow reconstruction).
206 #[clap(long)]
207 #[serde(default)]
208 pub print_ullbc: bool,
209 /// Pretty-print the LLBC just after we built it (i.e., immediately after loop reconstruction).
210 #[clap(long)]
211 #[serde(default)]
212 pub print_built_llbc: bool,
213 /// Pretty-print the final LLBC (after all the cleaning micro-passes).
214 #[clap(long)]
215 #[serde(default)]
216 pub print_llbc: bool,
217
218 /// The destination directory. Files will be generated as `<dest_dir>/<crate_name>.{u}llbc`,
219 /// unless `dest_file` is set. `dest_dir` defaults to the current directory.
220 #[clap(long = "dest", value_parser)]
221 #[serde(default)]
222 pub dest_dir: Option<PathBuf>,
223 /// The destination file. By default `<dest_dir>/<crate_name>.llbc`. If this is set we ignore
224 /// `dest_dir`.
225 #[clap(long, value_parser)]
226 #[serde(default)]
227 pub dest_file: Option<PathBuf>,
228 /// Don't deduplicate values (types, trait refs) in the .(u)llbc file. This makes the file easier to inspect.
229 #[clap(long)]
230 #[serde(default)]
231 pub no_dedup_serialized_ast: bool,
232 /// Don't serialize the final (U)LLBC to a file.
233 #[clap(long)]
234 #[serde(default)]
235 pub no_serialize: bool,
236 /// Panic on the first error. This is useful for debugging.
237 #[clap(long)]
238 #[serde(default)]
239 pub abort_on_error: bool,
240 /// Consider any warnings to be errors.
241 #[clap(long)]
242 #[serde(default)]
243 pub error_on_warnings: bool,
244
245 /// Named builtin sets of options.
246 #[clap(long)]
247 #[arg(value_enum)]
248 pub preset: Option<Preset>,
249}
250
251/// The MIR stage to use. This is only relevant for the current crate: for dependencies, only mir
252/// optimized is available (or mir elaborated for consts).
253#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, ValueEnum, Serialize, Deserialize)]
254pub enum MirLevel {
255 /// The MIR just after MIR lowering.
256 Built,
257 /// The MIR after const promotion. This is the MIR used by the borrow-checker.
258 Promoted,
259 /// The MIR after drop elaboration. This is the first MIR to include all the runtime
260 /// information.
261 Elaborated,
262 /// The MIR after optimizations. Charon disables all the optimizations it can, so this is
263 /// sensibly the same MIR as the elaborated MIR.
264 Optimized,
265}
266
267/// Presets to make it easier to tweak options without breaking dependent projects. Eventually we
268/// should define semantically-meaningful presets instead of project-specific ones.
269#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, ValueEnum, Serialize, Deserialize)]
270#[non_exhaustive]
271pub enum Preset {
272 /// The default translation used before May 2025. After that, many passes were made optional
273 /// and disabled by default.
274 OldDefaults,
275 /// Emit the MIR as unmodified as possible. This is very imperfect for now, we should make more
276 /// passes optional.
277 RawMir,
278 Aeneas,
279 Eurydice,
280 Soteria,
281 Tests,
282}
283
284#[derive(
285 Debug, Default, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, ValueEnum, Serialize, Deserialize,
286)]
287pub enum MonomorphizeMut {
288 /// Monomorphize any item instantiated with `&mut`.
289 #[default]
290 All,
291 /// Monomorphize all non-typedecl items instantiated with `&mut`.
292 ExceptTypes,
293}
294
295impl CliOpts {
296 pub fn apply_preset(&mut self) {
297 if let Some(preset) = self.preset {
298 match preset {
299 Preset::OldDefaults => {
300 self.treat_box_as_builtin = true;
301 self.hide_allocator = true;
302 self.ops_to_function_calls = true;
303 self.index_to_function_calls = true;
304 self.reconstruct_fallible_operations = true;
305 self.reconstruct_asserts = true;
306 self.unbind_item_vars = true;
307 }
308 Preset::RawMir => {
309 self.extract_opaque_bodies = true;
310 self.raw_consts = true;
311 self.ullbc = true;
312 }
313 Preset::Aeneas => {
314 self.remove_associated_types.push("*".to_owned());
315 self.treat_box_as_builtin = true;
316 self.ops_to_function_calls = true;
317 self.index_to_function_calls = true;
318 self.reconstruct_fallible_operations = true;
319 self.reconstruct_asserts = true;
320 self.hide_marker_traits = true;
321 self.hide_allocator = true;
322 self.remove_unused_self_clauses = true;
323 self.unbind_item_vars = true;
324 // Hide drop impls because they often involve nested borrows. which aeneas
325 // doesn't handle yet.
326 self.exclude.push("core::ops::drop::Drop".to_owned());
327 self.exclude
328 .push("{impl core::ops::drop::Drop for _}".to_owned());
329 }
330 Preset::Eurydice => {
331 self.hide_allocator = true;
332 self.treat_box_as_builtin = true;
333 self.ops_to_function_calls = true;
334 self.index_to_function_calls = true;
335 self.reconstruct_fallible_operations = true;
336 self.reconstruct_asserts = true;
337 self.remove_associated_types.push("*".to_owned());
338 self.unbind_item_vars = true;
339 // Eurydice doesn't support opaque vtables it seems?
340 self.include.push("core::marker::MetaSized".to_owned());
341 }
342 Preset::Soteria => {
343 self.extract_opaque_bodies = true;
344 self.monomorphize = true;
345 self.mir = Some(MirLevel::Elaborated);
346 self.ullbc = true;
347 }
348 Preset::Tests => {
349 self.no_dedup_serialized_ast = true; // Helps debug
350 self.treat_box_as_builtin = true;
351 self.hide_allocator = true;
352 self.reconstruct_fallible_operations = true;
353 self.reconstruct_asserts = true;
354 self.ops_to_function_calls = true;
355 self.index_to_function_calls = true;
356 self.rustc_args.push("--edition=2021".to_owned());
357 self.rustc_args
358 .push("-Zcrate-attr=feature(register_tool)".to_owned());
359 self.rustc_args
360 .push("-Zcrate-attr=register_tool(charon)".to_owned());
361 self.exclude.push("core::fmt::Formatter".to_owned());
362 }
363 }
364 }
365 }
366
367 /// Check that the options are meaningful
368 pub fn validate(&self) -> anyhow::Result<()> {
369 if self.dest_dir.is_some() {
370 display_unspanned_error(
371 Level::WARNING,
372 "`--dest` is deprecated, use `--dest-file` instead",
373 )
374 }
375
376 if self.remove_adt_clauses && self.remove_associated_types.is_empty() {
377 anyhow::bail!(
378 "`--remove-adt-clauses` should be used with `--remove-associated-types='*'` \
379 to avoid missing clause errors",
380 )
381 }
382 if matches!(self.monomorphize_mut, Some(MonomorphizeMut::ExceptTypes))
383 && !self.remove_adt_clauses
384 {
385 anyhow::bail!(
386 "`--monomorphize-mut=except-types` should be used with `--remove-adt-clauses` \
387 to avoid generics mismatches"
388 )
389 }
390 Ok(())
391 }
392}
393
394/// The options that control translation and transformation.
395pub struct TranslateOptions {
396 /// The level at which to extract the MIR
397 pub mir_level: MirLevel,
398 /// Usually we skip the provided methods that aren't used. When this flag is on, we translate
399 /// them all.
400 pub translate_all_methods: bool,
401 /// If `Some(_)`, run the partial mutability monomorphization pass. The contained enum
402 /// indicates whether to partially monomorphize types.
403 pub monomorphize_mut: Option<MonomorphizeMut>,
404 /// Whether to hide various marker traits such as `Sized`, `Sync`, `Send` and `Destruct`
405 /// anywhere they show up.
406 pub hide_marker_traits: bool,
407 /// Remove trait clauses attached to type declarations.
408 pub remove_adt_clauses: bool,
409 /// Hide the `A` type parameter on standard library containers (`Box`, `Vec`, etc).
410 pub hide_allocator: bool,
411 /// Remove unused `Self: Trait` clauses on method declarations.
412 pub remove_unused_self_clauses: bool,
413 /// Monomorphize code using hax's instantiation mechanism.
414 pub monomorphize_with_hax: bool,
415 /// Transform array-to-slice unsizing, repeat expressions, and raw pointer construction into
416 /// builtin functions in ULLBC.
417 pub ops_to_function_calls: bool,
418 /// Transform array/slice indexing into builtin functions in ULLBC.
419 pub index_to_function_calls: bool,
420 /// Print the llbc just after control-flow reconstruction.
421 pub print_built_llbc: bool,
422 /// Treat `Box<T>` as if it was a built-in type.
423 pub treat_box_as_builtin: bool,
424 /// Don't inline or evaluate constants.
425 pub raw_consts: bool,
426 /// Replace "bound checks followed by UB-on-overflow operation" with the corresponding
427 /// panic-on-overflow operation. This loses unwinding information.
428 pub reconstruct_fallible_operations: bool,
429 /// Replace "if x { panic() }" with "assert(x)".
430 pub reconstruct_asserts: bool,
431 // Use `DeBruijnVar::Free` for the variables bound in item signatures.
432 pub unbind_item_vars: bool,
433 /// List of patterns to assign a given opacity to. Same as the corresponding `TranslateOptions`
434 /// field.
435 pub item_opacities: Vec<(NamePattern, ItemOpacity)>,
436 /// List of traits for which we transform associated types to type parameters.
437 pub remove_associated_types: Vec<NamePattern>,
438 /// Transform Drop to Call drop_in_place
439 pub desugar_drops: bool,
440 /// Add `Destruct` bounds to all generic params.
441 pub add_destruct_bounds: bool,
442 /// Translate drop glue for poly types, knowing that this may cause ICEs.
443 pub translate_poly_drop_glue: bool,
444 /// Whether to erase all `Body` lifetimes at the end of translation.
445 pub erase_body_lifetimes: bool,
446}
447
448impl TranslateOptions {
449 pub fn new(error_ctx: &mut ErrorCtx, options: &CliOpts) -> Self {
450 let mut parse_pattern = |s: &str| match NamePattern::parse(s) {
451 Ok(p) => Ok(p),
452 Err(e) => {
453 raise_error!(
454 error_ctx,
455 crate(&TranslatedCrate::default()),
456 Span::dummy(),
457 "failed to parse pattern `{s}` ({e})"
458 )
459 }
460 };
461
462 let mut mir_level = options.mir.unwrap_or(MirLevel::Promoted);
463 if options.precise_drops {
464 mir_level = std::cmp::max(mir_level, MirLevel::Elaborated);
465 }
466
467 let item_opacities = {
468 use ItemOpacity::*;
469 let mut opacities = vec![];
470
471 // This is how to treat items that don't match any other pattern.
472 if options.extract_opaque_bodies {
473 opacities.push(("_".to_string(), Transparent));
474 } else {
475 opacities.push(("_".to_string(), Foreign));
476 }
477
478 // We always include the items from the crate.
479 opacities.push(("crate".to_owned(), Transparent));
480
481 for pat in options.include.iter() {
482 opacities.push((pat.to_string(), Transparent));
483 }
484 for pat in options.opaque.iter() {
485 opacities.push((pat.to_string(), Opaque));
486 }
487 for pat in options.exclude.iter() {
488 opacities.push((pat.to_string(), Invisible));
489 }
490
491 if options.hide_allocator {
492 opacities.push((format!("core::alloc::Allocator"), Invisible));
493 opacities.push((
494 format!("alloc::alloc::{{impl core::alloc::Allocator for _}}"),
495 Invisible,
496 ));
497 }
498
499 opacities
500 .into_iter()
501 .filter_map(|(s, opacity)| parse_pattern(&s).ok().map(|pat| (pat, opacity)))
502 .collect()
503 };
504
505 let remove_associated_types = options
506 .remove_associated_types
507 .iter()
508 .filter_map(|s| parse_pattern(&s).ok())
509 .collect();
510
511 TranslateOptions {
512 mir_level,
513 monomorphize_mut: options.monomorphize_mut,
514 hide_marker_traits: options.hide_marker_traits,
515 remove_adt_clauses: options.remove_adt_clauses,
516 hide_allocator: options.hide_allocator,
517 remove_unused_self_clauses: options.remove_unused_self_clauses,
518 monomorphize_with_hax: options.monomorphize,
519 ops_to_function_calls: options.ops_to_function_calls,
520 index_to_function_calls: options.index_to_function_calls,
521 print_built_llbc: options.print_built_llbc,
522 item_opacities,
523 treat_box_as_builtin: options.treat_box_as_builtin,
524 raw_consts: options.raw_consts,
525 reconstruct_fallible_operations: options.reconstruct_fallible_operations,
526 reconstruct_asserts: options.reconstruct_asserts,
527 remove_associated_types,
528 unbind_item_vars: options.unbind_item_vars,
529 translate_all_methods: options.translate_all_methods,
530 desugar_drops: options.desugar_drops,
531 add_destruct_bounds: options.precise_drops,
532 translate_poly_drop_glue: options.precise_drops,
533 erase_body_lifetimes: matches!(options.preset, Some(Preset::Aeneas))
534 && !options.no_erase_body_regions,
535 }
536 }
537
538 /// Find the opacity requested for the given name. This does not take into account
539 /// `#[charon::opaque]` annotations, only cli parameters.
540 #[tracing::instrument(skip(self, krate), ret)]
541 pub fn opacity_for_name(&self, krate: &TranslatedCrate, name: &Name) -> ItemOpacity {
542 // Find the most precise pattern that matches this name. There is always one since
543 // the list contains the `_` pattern. If there are conflicting settings for this item, we
544 // err on the side of being more opaque.
545 let (_, opacity) = self
546 .item_opacities
547 .iter()
548 .filter(|(pat, _)| pat.matches(krate, name))
549 .max()
550 .unwrap();
551 *opacity
552 }
553}