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 = "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 items encountered when possible. Generic items found in the crate are
85    /// skipped. To only translate a particular call graph, use `--start-from`. Note: this doesn't
86    /// currently support `dyn Trait`.
87    #[clap(long, visible_alias = "mono")]
88    #[serde(default)]
89    pub monomorphize: bool,
90    /// Partially monomorphize items to make it so that no item is ever monomorphized with a
91    /// mutable reference (or type containing one); said differently, so that the presence of
92    /// mutable references in a type is independent of its generics. This is used by Aeneas.
93    #[clap(
94        long,
95        value_name("INCLUDE_TYPES"),
96        num_args(0..=1),
97        require_equals(true),
98        default_missing_value("all"),
99    )]
100    #[serde(default)]
101    pub monomorphize_mut: Option<MonomorphizeMut>,
102    /// Usually we skip the bodies of foreign methods and structs with private fields. When this
103    /// flag is on, we don't.
104    #[clap(long = "extract-opaque-bodies")]
105    #[serde(default)]
106    pub extract_opaque_bodies: bool,
107    /// Usually we skip the provided methods that aren't used. When this flag is on, we translate
108    /// them all.
109    #[clap(long = "translate-all-methods")]
110    #[serde(default)]
111    pub translate_all_methods: bool,
112    /// Whitelist of items to translate. These use the name-matcher syntax.
113    #[clap(
114        long = "include",
115        help = indoc!("
116            Whitelist of items to translate. These use the name-matcher syntax (note: this differs
117            a bit from the ocaml NameMatcher).
118
119            Note: This is very rough at the moment. E.g. this parses `u64` as a path instead of the
120            built-in type. It is also not possible to filter a trait impl (this will only filter
121            its methods). Please report bugs or missing features.
122
123            Examples:
124              - `crate::module1::module2::item`: refers to this item and all its subitems (e.g.
125                  submodules or trait methods);
126              - `crate::module1::module2::item::_`: refers only to the subitems of this item;
127              - `core::convert::{impl core::convert::Into<_> for _}`: retrieve the body of this
128                  very useful impl;
129
130            When multiple patterns in the `--include` and `--opaque` options match the same item,
131            the most precise pattern wins. E.g.: `charon --opaque crate::module --include
132            crate::module::_` makes the `module` opaque (we won't explore its contents), but the
133            items in it transparent (we will translate them if we encounter them.)
134    "))]
135    #[serde(default)]
136    #[charon::rename("included")]
137    pub include: Vec<String>,
138    /// Blacklist of items to keep opaque. These use the name-matcher syntax.
139    #[clap(
140        long = "opaque",
141        help = "Blacklist of items to keep opaque. Works just like `--include`, see the doc there."
142    )]
143    #[serde(default)]
144    pub opaque: Vec<String>,
145    /// Blacklist of items to not translate at all. These use the name-matcher syntax.
146    #[clap(
147        long = "exclude",
148        help = "Blacklist of items to not translate at all. Works just like `--include`, see the doc there."
149    )]
150    #[serde(default)]
151    pub exclude: Vec<String>,
152    /// List of traits for which we transform associated types to type parameters.
153    #[clap(
154        long = "remove-associated-types",
155        help = "List of traits for which we transform associated types to type parameters. \
156        The syntax is like `--include`, see the doc there."
157    )]
158    #[serde(default)]
159    pub remove_associated_types: Vec<String>,
160    /// Whether to hide various marker traits such as `Sized`, `Sync`, `Send` and `Destruct`
161    /// anywhere they show up.
162    #[clap(long = "hide-marker-traits")]
163    #[serde(default)]
164    pub hide_marker_traits: bool,
165    /// Remove trait clauses from type declarations. Must be combined with
166    /// `--remove-associated-types` for type declarations that use trait associated types in their
167    /// fields, otherwise this will result in errors.
168    #[clap(long)]
169    #[serde(default)]
170    pub remove_adt_clauses: bool,
171    /// Hide the `A` type parameter on standard library containers (`Box`, `Vec`, etc).
172    #[clap(long)]
173    #[serde(default)]
174    pub hide_allocator: bool,
175    /// Trait method declarations take a `Self: Trait` clause as parameter, so that they can be
176    /// reused by multiple trait impls. This however causes trait definitions to be mutually
177    /// recursive with their method declarations. This flag removes `Self` clauses that aren't used
178    /// to break this mutual recursion.
179    #[clap(long)]
180    #[serde(default)]
181    pub remove_unused_self_clauses: bool,
182    /// Whether to add `Destruct` bounds everywhere to enable proper tracking of what code runs on
183    /// a given `drop` call.
184    #[clap(long)]
185    #[serde(default)]
186    pub add_drop_bounds: bool,
187    /// A list of item paths to use as starting points for the translation. We will translate these
188    /// items and any items they refer to, according to the opacity rules. When absent, we start
189    /// from the path `crate` (which translates the whole crate).
190    #[clap(long = "start-from", value_delimiter = ',')]
191    #[serde(default)]
192    pub start_from: Vec<String>,
193    /// Do not run cargo; instead, run the driver directly.
194    #[clap(long = "no-cargo")]
195    #[serde(default)]
196    pub no_cargo: bool,
197    /// Extra flags to pass to rustc.
198    #[clap(long = "rustc-flag", alias = "rustc-arg")]
199    #[serde(default)]
200    pub rustc_args: Vec<String>,
201    /// Extra flags to pass to cargo. Incompatible with `--no-cargo`.
202    #[clap(long = "cargo-arg")]
203    #[serde(default)]
204    pub cargo_args: Vec<String>,
205    /// Panic on the first error. This is useful for debugging.
206    #[clap(long = "abort-on-error")]
207    #[serde(default)]
208    pub abort_on_error: bool,
209    /// Print the errors as warnings
210    #[clap(long = "error-on-warnings", help = "Consider any warnings as errors")]
211    #[serde(default)]
212    pub error_on_warnings: bool,
213    #[clap(
214        long = "no-serialize",
215        help = "Don't serialize the final (U)LLBC to a file."
216    )]
217    #[serde(default)]
218    pub no_serialize: bool,
219    #[clap(
220        long = "print-original-ullbc",
221        help = "Print the ULLBC immediately after extraction from MIR."
222    )]
223    #[serde(default)]
224    pub print_original_ullbc: bool,
225    #[clap(
226        long = "print-ullbc",
227        help = "Print the ULLBC after applying the micro-passes (before serialization/control-flow reconstruction)."
228    )]
229    #[serde(default)]
230    pub print_ullbc: bool,
231    #[clap(
232        long = "print-built-llbc",
233        help = "Print the LLBC just after we built it (i.e., immediately after loop reconstruction)."
234    )]
235    #[serde(default)]
236    pub print_built_llbc: bool,
237    #[clap(
238        long = "print-llbc",
239        help = "Print the final LLBC (after all the cleaning micro-passes)."
240    )]
241    #[serde(default)]
242    pub print_llbc: bool,
243    #[clap(
244        long = "no-merge-goto-chains",
245        help = "Do not merge the chains of gotos in the ULLBC control-flow graph."
246    )]
247    #[serde(default)]
248    pub no_merge_goto_chains: bool,
249
250    #[clap(
251        long = "no-ops-to-function-calls",
252        help = "Do not transform ArrayToSlice, Repeat, and RawPtr aggregates to builtin function calls for ULLBC"
253    )]
254    #[serde(default)]
255    pub no_ops_to_function_calls: bool,
256
257    #[clap(
258        long = "raw-boxes",
259        help = "Do not special-case the translation of `Box<T>` into a builtin ADT."
260    )]
261    #[serde(default)]
262    pub raw_boxes: bool,
263
264    /// Named builtin sets of options. Currently used only for dependent projects, eveentually
265    /// should be replaced with semantically-meaningful presets.
266    #[clap(long = "preset")]
267    #[arg(value_enum)]
268    pub preset: Option<Preset>,
269
270    #[clap(
271        long = "desugar-drops",
272        help = "If activated, transform `Drop(p)` to `Call drop_in_place(&raw mut p)`"
273    )]
274    #[serde(default)]
275    pub desugar_drops: bool,
276}
277
278/// The MIR stage to use. This is only relevant for the current crate: for dependencies, only mir
279/// optimized is available (or mir elaborated for consts).
280#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, ValueEnum, Serialize, Deserialize)]
281pub enum MirLevel {
282    /// The MIR just after MIR lowering.
283    Built,
284    /// The MIR after const promotion. This is the MIR used by the borrow-checker.
285    Promoted,
286    /// The MIR after drop elaboration. This is the first MIR to include all the runtime
287    /// information.
288    Elaborated,
289    /// The MIR after optimizations. Charon disables all the optimizations it can, so this is
290    /// sensibly the same MIR as the elaborated MIR.
291    Optimized,
292}
293
294/// Presets to make it easier to tweak options without breaking dependent projects. Eventually we
295/// should define semantically-meaningful presets instead of project-specific ones.
296#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, ValueEnum, Serialize, Deserialize)]
297#[non_exhaustive]
298pub enum Preset {
299    /// The default translation used before May 2025. After that, many passes were made optional
300    /// and disabled by default.
301    OldDefaults,
302    Aeneas,
303    Eurydice,
304    Soteria,
305    Tests,
306}
307
308#[derive(
309    Debug, Default, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, ValueEnum, Serialize, Deserialize,
310)]
311pub enum MonomorphizeMut {
312    /// Monomorphize any item instantiated with `&mut`.
313    #[default]
314    All,
315    /// Monomorphize all non-typedecl items instantiated with `&mut`.
316    ExceptTypes,
317}
318
319impl CliOpts {
320    pub fn apply_preset(&mut self) {
321        if let Some(preset) = self.preset {
322            match preset {
323                Preset::OldDefaults => {
324                    self.hide_allocator = !self.raw_boxes;
325                }
326                Preset::Aeneas => {
327                    self.remove_associated_types.push("*".to_owned());
328                    self.hide_marker_traits = true;
329                    self.hide_allocator = true;
330                    self.remove_unused_self_clauses = true;
331                    // Hide drop impls because they often involve nested borrows. which aeneas
332                    // doesn't handle yet.
333                    self.exclude.push("core::ops::drop::Drop".to_owned());
334                    self.exclude
335                        .push("{impl core::ops::drop::Drop for _}".to_owned());
336                }
337                Preset::Eurydice => {
338                    self.hide_allocator = true;
339                    self.remove_associated_types.push("*".to_owned());
340                    // Eurydice doesn't support opaque vtables it seems?
341                    self.include.push("core::marker::MetaSized".to_owned());
342                }
343                Preset::Soteria => {
344                    self.extract_opaque_bodies = true;
345                    self.monomorphize = true;
346                    self.raw_boxes = true;
347                    self.mir = Some(MirLevel::Elaborated);
348                    self.ullbc = true;
349                }
350                Preset::Tests => {
351                    self.hide_allocator = !self.raw_boxes;
352                    self.rustc_args.push("--edition=2021".to_owned());
353                    self.rustc_args
354                        .push("-Zcrate-attr=feature(register_tool)".to_owned());
355                    self.rustc_args
356                        .push("-Zcrate-attr=register_tool(charon)".to_owned());
357                    self.exclude.push("core::fmt::Formatter".to_owned());
358                }
359            }
360        }
361    }
362
363    /// Check that the options are meaningful
364    pub fn validate(&self) -> anyhow::Result<()> {
365        assert!(
366            !self.lib || self.bin.is_none(),
367            "Can't use --lib and --bin at the same time"
368        );
369
370        assert!(
371            !self.mir_promoted || !self.mir_optimized,
372            "Can't use --mir_promoted and --mir_optimized at the same time"
373        );
374
375        if self.input_file.is_some() {
376            display_unspanned_error(
377                Level::WARNING,
378                "`--input` is deprecated, use `charon rustc [charon options] -- [rustc options] <input file>` instead",
379            )
380        }
381        if self.no_cargo {
382            display_unspanned_error(
383                Level::WARNING,
384                "`--no-cargo` is deprecated, use `charon rustc [charon options] -- [rustc options] <input file>` instead",
385            )
386        }
387        if self.read_llbc.is_some() {
388            display_unspanned_error(
389                Level::WARNING,
390                "`--read_llbc` is deprecated, use `charon pretty-print <input file>` instead",
391            )
392        }
393        if self.use_polonius {
394            display_unspanned_error(
395                Level::WARNING,
396                "`--polonius` is deprecated, use `--rustc-arg=-Zpolonius` instead",
397            )
398        }
399        if self.mir_optimized {
400            display_unspanned_error(
401                Level::WARNING,
402                "`--mir_optimized` is deprecated, use `--mir optimized` instead",
403            )
404        }
405        if self.mir_promoted {
406            display_unspanned_error(
407                Level::WARNING,
408                "`--mir_promoted` is deprecated, use `--mir promoted` instead",
409            )
410        }
411        if self.lib {
412            display_unspanned_error(
413                Level::WARNING,
414                "`--lib` is deprecated, use `charon cargo -- --lib` instead",
415            )
416        }
417        if self.bin.is_some() {
418            display_unspanned_error(
419                Level::WARNING,
420                "`--bin` is deprecated, use `charon cargo -- --bin <name>` instead",
421            )
422        }
423        if self.dest_dir.is_some() {
424            display_unspanned_error(
425                Level::WARNING,
426                "`--dest` is deprecated, use `--dest-file` instead",
427            )
428        }
429
430        if self.remove_adt_clauses && self.remove_associated_types.is_empty() {
431            anyhow::bail!(
432                "`--remove-adt-clauses` should be used with `--remove-associated-types='*'` \
433                to avoid missing clause errors",
434            )
435        }
436        if matches!(self.monomorphize_mut, Some(MonomorphizeMut::ExceptTypes))
437            && !self.remove_adt_clauses
438        {
439            anyhow::bail!(
440                "`--monomorphize-mut=except-types` should be used with `--remove-adt-clauses` \
441                to avoid generics mismatches"
442            )
443        }
444        Ok(())
445    }
446}
447
448/// The options that control translation and transformation.
449pub struct TranslateOptions {
450    /// The level at which to extract the MIR
451    pub mir_level: MirLevel,
452    /// Usually we skip the provided methods that aren't used. When this flag is on, we translate
453    /// them all.
454    pub translate_all_methods: bool,
455    /// If `Some(_)`, run the partial mutability monomorphization pass. The contained enum
456    /// indicates whether to partially monomorphize types.
457    pub monomorphize_mut: Option<MonomorphizeMut>,
458    /// Whether to hide various marker traits such as `Sized`, `Sync`, `Send` and `Destruct`
459    /// anywhere they show up.
460    pub hide_marker_traits: bool,
461    /// Remove trait clauses attached to type declarations.
462    pub remove_adt_clauses: bool,
463    /// Hide the `A` type parameter on standard library containers (`Box`, `Vec`, etc).
464    pub hide_allocator: bool,
465    /// Remove unused `Self: Trait` clauses on method declarations.
466    pub remove_unused_self_clauses: bool,
467    /// Monomorphize code using hax's instantiation mechanism.
468    pub monomorphize_with_hax: bool,
469    /// Transforms ArrayToSlice, Repeat, and RawPtr aggregates to builtin function calls.
470    pub no_ops_to_function_calls: bool,
471    /// Do not merge the chains of gotos.
472    pub no_merge_goto_chains: bool,
473    /// Print the llbc just after control-flow reconstruction.
474    pub print_built_llbc: bool,
475    /// Don't special-case the translation of `Box<T>`
476    pub raw_boxes: bool,
477    /// List of patterns to assign a given opacity to. Same as the corresponding `TranslateOptions`
478    /// field.
479    pub item_opacities: Vec<(NamePattern, ItemOpacity)>,
480    /// List of traits for which we transform associated types to type parameters.
481    pub remove_associated_types: Vec<NamePattern>,
482    /// Transform Drop to Call drop_in_place
483    pub desugar_drops: bool,
484}
485
486impl TranslateOptions {
487    pub fn new(error_ctx: &mut ErrorCtx, options: &CliOpts) -> Self {
488        let mut parse_pattern = |s: &str| match NamePattern::parse(s) {
489            Ok(p) => Ok(p),
490            Err(e) => {
491                raise_error!(
492                    error_ctx,
493                    crate(&TranslatedCrate::default()),
494                    Span::dummy(),
495                    "failed to parse pattern `{s}` ({e})"
496                )
497            }
498        };
499
500        let mir_level = if options.mir_optimized {
501            MirLevel::Optimized
502        } else {
503            options.mir.unwrap_or(MirLevel::Promoted)
504        };
505
506        let item_opacities = {
507            use ItemOpacity::*;
508            let mut opacities = vec![];
509
510            // This is how to treat items that don't match any other pattern.
511            if options.extract_opaque_bodies {
512                opacities.push(("_".to_string(), Transparent));
513            } else {
514                opacities.push(("_".to_string(), Foreign));
515            }
516
517            // We always include the items from the crate.
518            opacities.push(("crate".to_owned(), Transparent));
519
520            for pat in options.include.iter() {
521                opacities.push((pat.to_string(), Transparent));
522            }
523            for pat in options.opaque.iter() {
524                opacities.push((pat.to_string(), Opaque));
525            }
526            for pat in options.exclude.iter() {
527                opacities.push((pat.to_string(), Invisible));
528            }
529
530            if options.hide_allocator {
531                opacities.push((format!("core::alloc::Allocator"), Invisible));
532                opacities.push((
533                    format!("alloc::alloc::{{impl core::alloc::Allocator for _}}"),
534                    Invisible,
535                ));
536            }
537
538            opacities
539                .into_iter()
540                .filter_map(|(s, opacity)| parse_pattern(&s).ok().map(|pat| (pat, opacity)))
541                .collect()
542        };
543
544        let remove_associated_types = options
545            .remove_associated_types
546            .iter()
547            .filter_map(|s| parse_pattern(&s).ok())
548            .collect();
549
550        TranslateOptions {
551            mir_level,
552            monomorphize_mut: options.monomorphize_mut,
553            hide_marker_traits: options.hide_marker_traits,
554            remove_adt_clauses: options.remove_adt_clauses,
555            hide_allocator: options.hide_allocator,
556            remove_unused_self_clauses: options.remove_unused_self_clauses,
557            monomorphize_with_hax: options.monomorphize,
558            no_merge_goto_chains: options.no_merge_goto_chains,
559            no_ops_to_function_calls: options.no_ops_to_function_calls,
560            print_built_llbc: options.print_built_llbc,
561            item_opacities,
562            raw_boxes: options.raw_boxes,
563            remove_associated_types,
564            translate_all_methods: options.translate_all_methods,
565            desugar_drops: options.desugar_drops,
566        }
567    }
568
569    /// Find the opacity requested for the given name. This does not take into account
570    /// `#[charon::opaque]` annotations, only cli parameters.
571    #[tracing::instrument(skip(self, krate), ret)]
572    pub fn opacity_for_name(&self, krate: &TranslatedCrate, name: &Name) -> ItemOpacity {
573        // Find the most precise pattern that matches this name. There is always one since
574        // the list contains the `_` pattern. If there are conflicting settings for this item, we
575        // err on the side of being more opaque.
576        let (_, opacity) = self
577            .item_opacities
578            .iter()
579            .filter(|(pat, _)| pat.matches(krate, name))
580            .max()
581            .unwrap();
582        *opacity
583    }
584}