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, 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 = "ullbc")]
33    #[serde(default)]
34    pub ullbc: bool,
35    /// Compile the package's library
36    #[clap(long = "lib")]
37    #[serde(default)]
38    pub lib: bool,
39    /// Compile the specified binary
40    #[clap(long = "bin")]
41    #[serde(default)]
42    pub bin: Option<String>,
43    /// Deprecated: use `--mir promoted` instead.
44    #[clap(long = "mir_promoted")]
45    #[serde(default)]
46    pub mir_promoted: bool,
47    /// Deprecated: use `--mir optimized` instead.
48    #[clap(long = "mir_optimized")]
49    #[serde(default)]
50    pub mir_optimized: bool,
51    /// The MIR stage to extract. This is only relevant for the current crate; for dpendencies only
52    /// MIR optimized is available.
53    #[arg(long)]
54    pub mir: Option<MirLevel>,
55    /// The input file (the entry point of the crate to extract).
56    /// This is needed if you want to define a custom entry point (to only
57    /// extract part of a crate for instance).
58    #[clap(long = "input", value_parser)]
59    #[serde(default)]
60    pub input_file: Option<PathBuf>,
61    /// Read an llbc file and pretty-print it. This is a terrible API, we should use subcommands.
62    #[clap(long = "read-llbc", value_parser)]
63    #[serde(default)]
64    pub read_llbc: Option<PathBuf>,
65    /// The destination directory. Files will be generated as `<dest_dir>/<crate_name>.{u}llbc`,
66    /// unless `dest_file` is set. `dest_dir` defaults to the current directory.
67    #[clap(long = "dest", value_parser)]
68    #[serde(default)]
69    pub dest_dir: Option<PathBuf>,
70    /// The destination file. By default `<dest_dir>/<crate_name>.llbc`. If this is set we ignore
71    /// `dest_dir`.
72    #[clap(long = "dest-file", value_parser)]
73    #[serde(default)]
74    pub dest_file: Option<PathBuf>,
75    /// If activated, use Polonius' non-lexical lifetimes (NLL) analysis.
76    /// Otherwise, use the standard borrow checker.
77    #[clap(long = "polonius")]
78    #[serde(default)]
79    pub use_polonius: bool,
80    /// If activated, this skips borrow-checking of the crate.
81    #[clap(long = "skip-borrowck")]
82    #[serde(default)]
83    pub skip_borrowck: bool,
84    /// Monomorphize the code, replacing generics with their concrete types.
85    #[clap(long = "monomorphize")]
86    #[serde(default)]
87    pub monomorphize: bool,
88    /// Usually we skip the bodies of foreign methods and structs with private fields. When this
89    /// flag is on, we don't.
90    #[clap(long = "extract-opaque-bodies")]
91    #[serde(default)]
92    pub extract_opaque_bodies: bool,
93    /// Usually we skip the provided methods that aren't used. When this flag is on, we translate
94    /// them all.
95    #[clap(long = "translate-all-methods")]
96    #[serde(default)]
97    pub translate_all_methods: bool,
98    /// Whitelist of items to translate. These use the name-matcher syntax.
99    #[clap(
100        long = "include",
101        help = indoc!("
102            Whitelist of items to translate. These use the name-matcher syntax (note: this differs
103            a bit from the ocaml NameMatcher).
104
105            Note: This is very rough at the moment. E.g. this parses `u64` as a path instead of the
106            built-in type. It is also not possible to filter a trait impl (this will only filter
107            its methods). Please report bugs or missing features.
108
109            Examples:
110              - `crate::module1::module2::item`: refers to this item and all its subitems (e.g.
111                  submodules or trait methods);
112              - `crate::module1::module2::item::_`: refers only to the subitems of this item;
113              - `core::convert::{impl core::convert::Into<_> for _}`: retrieve the body of this
114                  very useful impl;
115
116            When multiple patterns in the `--include` and `--opaque` options match the same item,
117            the most precise pattern wins. E.g.: `charon --opaque crate::module --include
118            crate::module::_` makes the `module` opaque (we won't explore its contents), but the
119            items in it transparent (we will translate them if we encounter them.)
120    "))]
121    #[serde(default)]
122    #[charon::rename("included")]
123    pub include: Vec<String>,
124    /// Blacklist of items to keep opaque. These use the name-matcher syntax.
125    #[clap(
126        long = "opaque",
127        help = "Blacklist of items to keep opaque. Works just like `--include`, see the doc there."
128    )]
129    #[serde(default)]
130    pub opaque: Vec<String>,
131    /// Blacklist of items to not translate at all. These use the name-matcher syntax.
132    #[clap(
133        long = "exclude",
134        help = "Blacklist of items to not translate at all. Works just like `--include`, see the doc there."
135    )]
136    #[serde(default)]
137    pub exclude: Vec<String>,
138    /// List of traits for which we transform associated types to type parameters.
139    #[clap(
140        long = "remove-associated-types",
141        help = "List of traits for which we transform associated types to type parameters. \
142        The syntax is like `--include`, see the doc there."
143    )]
144    #[serde(default)]
145    pub remove_associated_types: Vec<String>,
146    /// Whether to hide the `Sized`, `Sync`, `Send` and `Unpin` marker traits anywhere they show
147    /// up.
148    #[clap(long = "hide-marker-traits")]
149    #[serde(default)]
150    pub hide_marker_traits: bool,
151    /// Hide the `A` type parameter on standard library containers (`Box`, `Vec`, etc).
152    #[clap(long)]
153    #[serde(default)]
154    pub hide_allocator: bool,
155    /// Trait method declarations take a `Self: Trait` clause as parameter, so that they can be
156    /// reused by multiple trait impls. This however causes trait definitions to be mutually
157    /// recursive with their method declarations. This flag removes `Self` clauses that aren't used
158    /// to break this mutual recursion.
159    #[clap(long)]
160    #[serde(default)]
161    pub remove_unused_self_clauses: bool,
162    /// Whether to add `Drop` bounds everywhere to enable proper tracking of what code runs on a
163    /// given `drop` call.
164    #[clap(long)]
165    #[serde(default)]
166    pub add_drop_bounds: bool,
167    /// A list of item paths to use as starting points for the translation. We will translate these
168    /// items and any items they refer to, according to the opacity rules. When absent, we start
169    /// from the path `crate` (which translates the whole crate).
170    #[clap(long = "start-from")]
171    #[serde(default)]
172    pub start_from: Vec<String>,
173    /// Do not run cargo; instead, run the driver directly.
174    #[clap(long = "no-cargo")]
175    #[serde(default)]
176    pub no_cargo: bool,
177    /// Extra flags to pass to rustc.
178    #[clap(long = "rustc-flag", alias = "rustc-arg")]
179    #[serde(default)]
180    pub rustc_args: Vec<String>,
181    /// Extra flags to pass to cargo. Incompatible with `--no-cargo`.
182    #[clap(long = "cargo-arg")]
183    #[serde(default)]
184    pub cargo_args: Vec<String>,
185    /// Panic on the first error. This is useful for debugging.
186    #[clap(long = "abort-on-error")]
187    #[serde(default)]
188    pub abort_on_error: bool,
189    /// Print the errors as warnings
190    #[clap(long = "error-on-warnings", help = "Consider any warnings as errors")]
191    #[serde(default)]
192    pub error_on_warnings: bool,
193    #[clap(
194        long = "no-serialize",
195        help = "Don't serialize the final (U)LLBC to a file."
196    )]
197    #[serde(default)]
198    pub no_serialize: bool,
199    #[clap(
200        long = "print-original-ullbc",
201        help = "Print the ULLBC immediately after extraction from MIR."
202    )]
203    #[serde(default)]
204    pub print_original_ullbc: bool,
205    #[clap(
206        long = "print-ullbc",
207        help = "Print the ULLBC after applying the micro-passes (before serialization/control-flow reconstruction)."
208    )]
209    #[serde(default)]
210    pub print_ullbc: bool,
211    #[clap(
212        long = "print-built-llbc",
213        help = "Print the LLBC just after we built it (i.e., immediately after loop reconstruction)."
214    )]
215    #[serde(default)]
216    pub print_built_llbc: bool,
217    #[clap(
218        long = "print-llbc",
219        help = "Print the final LLBC (after all the cleaning micro-passes)."
220    )]
221    #[serde(default)]
222    pub print_llbc: bool,
223    #[clap(
224        long = "no-merge-goto-chains",
225        help = "Do not merge the chains of gotos in the ULLBC control-flow graph."
226    )]
227    #[serde(default)]
228    pub no_merge_goto_chains: bool,
229
230    #[clap(
231        long = "no-ops-to-function-calls",
232        help = "Do not transform ArrayToSlice, Repeat, and RawPtr aggregates to builtin function calls for ULLBC"
233    )]
234    #[serde(default)]
235    pub no_ops_to_function_calls: bool,
236
237    #[clap(
238        long = "raw-boxes",
239        help = "Do not special-case the translation of `Box<T>` into a builtin ADT."
240    )]
241    #[serde(default)]
242    pub raw_boxes: bool,
243
244    /// Named builtin sets of options. Currently used only for dependent projects, eveentually
245    /// should be replaced with semantically-meaningful presets.
246    #[clap(long = "preset")]
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(
254    Debug, Default, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, ValueEnum, Serialize, Deserialize,
255)]
256pub enum MirLevel {
257    /// The MIR just after MIR lowering.
258    #[default]
259    Built,
260    /// The MIR after const promotion. This is the MIR used by the borrow-checker.
261    Promoted,
262    /// The MIR after drop elaboration. This is the first MIR to include all the runtime
263    /// information.
264    Elaborated,
265    /// The MIR after optimizations. Charon disables all the optimizations it can, so this is
266    /// sensibly the same MIR as the elaborated MIR.
267    Optimized,
268}
269
270/// Presets to make it easier to tweak options without breaking dependent projects. Eventually we
271/// should define semantically-meaningful presets instead of project-specific ones.
272#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, ValueEnum, Serialize, Deserialize)]
273#[non_exhaustive]
274pub enum Preset {
275    /// The default translation used before May 2025. After that, many passes were made optional
276    /// and disabled by default.
277    OldDefaults,
278    Aeneas,
279    Eurydice,
280    Tests,
281}
282
283impl CliOpts {
284    pub fn apply_preset(&mut self) {
285        if let Some(preset) = self.preset {
286            match preset {
287                Preset::OldDefaults => {
288                    self.hide_allocator = !self.raw_boxes;
289                }
290                Preset::Aeneas => {
291                    self.remove_associated_types.push("*".to_owned());
292                    self.hide_marker_traits = true;
293                    self.hide_allocator = true;
294                    self.remove_unused_self_clauses = true;
295                    // Hide drop impls because they often involve nested borrows. which aeneas
296                    // doesn't handle yet.
297                    self.exclude.push("core::ops::drop::Drop".to_owned());
298                    self.exclude
299                        .push("{impl core::ops::drop::Drop for _}".to_owned());
300                }
301                Preset::Eurydice => {
302                    self.hide_allocator = true;
303                    self.remove_associated_types.push("*".to_owned());
304                }
305                Preset::Tests => {
306                    self.hide_allocator = !self.raw_boxes;
307                    self.rustc_args.push("--edition=2021".to_owned());
308                    self.exclude.push("core::fmt::Formatter".to_owned());
309                }
310            }
311        }
312    }
313
314    /// Check that the options are meaningful
315    pub fn validate(&self) {
316        assert!(
317            !self.lib || self.bin.is_none(),
318            "Can't use --lib and --bin at the same time"
319        );
320
321        assert!(
322            !self.mir_promoted || !self.mir_optimized,
323            "Can't use --mir_promoted and --mir_optimized at the same time"
324        );
325
326        if self.input_file.is_some() {
327            display_unspanned_error(
328                Level::WARNING,
329                "`--input` is deprecated, use `charon rustc [charon options] -- [rustc options] <input file>` instead",
330            )
331        }
332        if self.no_cargo {
333            display_unspanned_error(
334                Level::WARNING,
335                "`--no-cargo` is deprecated, use `charon rustc [charon options] -- [rustc options] <input file>` instead",
336            )
337        }
338        if self.read_llbc.is_some() {
339            display_unspanned_error(
340                Level::WARNING,
341                "`--read_llbc` is deprecated, use `charon pretty-print <input file>` instead",
342            )
343        }
344        if self.use_polonius {
345            display_unspanned_error(
346                Level::WARNING,
347                "`--polonius` is deprecated, use `--rustc-arg=-Zpolonius` instead",
348            )
349        }
350        if self.mir_optimized {
351            display_unspanned_error(
352                Level::WARNING,
353                "`--mir_optimized` is deprecated, use `--mir optimized` instead",
354            )
355        }
356        if self.mir_promoted {
357            display_unspanned_error(
358                Level::WARNING,
359                "`--mir_promoted` is deprecated, use `--mir promoted` instead",
360            )
361        }
362        if self.lib {
363            display_unspanned_error(
364                Level::WARNING,
365                "`--lib` is deprecated, use `charon cargo -- --lib` instead",
366            )
367        }
368        if self.bin.is_some() {
369            display_unspanned_error(
370                Level::WARNING,
371                "`--bin` is deprecated, use `charon cargo -- --bin <name>` instead",
372            )
373        }
374        if self.dest_dir.is_some() {
375            display_unspanned_error(
376                Level::WARNING,
377                "`--dest` is deprecated, use `--dest-file` instead",
378            )
379        }
380    }
381}
382
383/// The options that control translation and transformation.
384pub struct TranslateOptions {
385    /// The level at which to extract the MIR
386    pub mir_level: MirLevel,
387    /// Usually we skip the provided methods that aren't used. When this flag is on, we translate
388    /// them all.
389    pub translate_all_methods: bool,
390    /// Whether to hide the `Sized`, `Sync`, `Send` and `Unpin` marker traits anywhere they show
391    /// up.
392    pub hide_marker_traits: bool,
393    /// Hide the `A` type parameter on standard library containers (`Box`, `Vec`, etc).
394    pub hide_allocator: bool,
395    /// Remove unused `Self: Trait` clauses on method declarations.
396    pub remove_unused_self_clauses: bool,
397    /// Monomorphize functions.
398    pub monomorphize: bool,
399    /// Transforms ArrayToSlice, Repeat, and RawPtr aggregates to builtin function calls.
400    pub no_ops_to_function_calls: bool,
401    /// Do not merge the chains of gotos.
402    pub no_merge_goto_chains: bool,
403    /// Print the llbc just after control-flow reconstruction.
404    pub print_built_llbc: bool,
405    /// Don't special-case the translation of `Box<T>`
406    pub raw_boxes: bool,
407    /// List of patterns to assign a given opacity to. Same as the corresponding `TranslateOptions`
408    /// field.
409    pub item_opacities: Vec<(NamePattern, ItemOpacity)>,
410    /// List of traits for which we transform associated types to type parameters.
411    pub remove_associated_types: Vec<NamePattern>,
412}
413
414impl TranslateOptions {
415    pub fn new(error_ctx: &mut ErrorCtx, options: &CliOpts) -> Self {
416        let mut parse_pattern = |s: &str| match NamePattern::parse(s) {
417            Ok(p) => Ok(p),
418            Err(e) => {
419                raise_error!(
420                    error_ctx,
421                    crate(&TranslatedCrate::default()),
422                    Span::dummy(),
423                    "failed to parse pattern `{s}` ({e})"
424                )
425            }
426        };
427
428        let mir_level = if options.mir_optimized {
429            MirLevel::Optimized
430        } else if options.mir_promoted {
431            MirLevel::Promoted
432        } else {
433            options.mir.unwrap_or_default()
434        };
435
436        let item_opacities = {
437            use ItemOpacity::*;
438            let mut opacities = vec![];
439
440            // This is how to treat items that don't match any other pattern.
441            if options.extract_opaque_bodies {
442                opacities.push(("_".to_string(), Transparent));
443            } else {
444                opacities.push(("_".to_string(), Foreign));
445            }
446
447            // We always include the items from the crate.
448            opacities.push(("crate".to_owned(), Transparent));
449
450            for pat in options.include.iter() {
451                opacities.push((pat.to_string(), Transparent));
452            }
453            for pat in options.opaque.iter() {
454                opacities.push((pat.to_string(), Opaque));
455            }
456            for pat in options.exclude.iter() {
457                opacities.push((pat.to_string(), Invisible));
458            }
459
460            if options.hide_allocator {
461                opacities.push((format!("core::alloc::Allocator"), Invisible));
462                opacities.push((
463                    format!("alloc::alloc::{{impl core::alloc::Allocator for _}}"),
464                    Invisible,
465                ));
466            }
467
468            opacities
469                .into_iter()
470                .filter_map(|(s, opacity)| parse_pattern(&s).ok().map(|pat| (pat, opacity)))
471                .collect()
472        };
473
474        let remove_associated_types = options
475            .remove_associated_types
476            .iter()
477            .filter_map(|s| parse_pattern(&s).ok())
478            .collect();
479
480        TranslateOptions {
481            mir_level,
482            hide_marker_traits: options.hide_marker_traits,
483            hide_allocator: options.hide_allocator,
484            remove_unused_self_clauses: options.remove_unused_self_clauses,
485            monomorphize: options.monomorphize,
486            no_merge_goto_chains: options.no_merge_goto_chains,
487            no_ops_to_function_calls: options.no_ops_to_function_calls,
488            print_built_llbc: options.print_built_llbc,
489            item_opacities,
490            raw_boxes: options.raw_boxes,
491            remove_associated_types,
492            translate_all_methods: options.translate_all_methods,
493        }
494    }
495
496    /// Find the opacity requested for the given name. This does not take into account
497    /// `#[charon::opaque]` annotations, only cli parameters.
498    #[tracing::instrument(skip(self, krate), ret)]
499    pub fn opacity_for_name(&self, krate: &TranslatedCrate, name: &Name) -> ItemOpacity {
500        // Find the most precise pattern that matches this name. There is always one since
501        // the list contains the `_` pattern. If there are conflicting settings for this item, we
502        // err on the side of being more opaque.
503        let (_, opacity) = self
504            .item_opacities
505            .iter()
506            .filter(|(pat, _)| pat.matches(krate, name))
507            .max()
508            .unwrap();
509        *opacity
510    }
511}