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 string literal constants with a constant u8 array that gets unsized,
182    /// expliciting the fact a string constant has a hidden reference.
183    #[clap(long)]
184    #[serde(default)]
185    pub unsized_strings: bool,
186    /// Replace "bound checks followed by UB-on-overflow operation" with the corresponding
187    /// panic-on-overflow operation. This loses unwinding information.
188    #[clap(long)]
189    #[serde(default)]
190    pub reconstruct_fallible_operations: bool,
191    /// Replace "if x { panic() }" with "assert(x)".
192    #[clap(long)]
193    #[serde(default)]
194    pub reconstruct_asserts: bool,
195    /// Use `DeBruijnVar::Free` for the variables bound in item signatures, instead of
196    /// `DeBruijnVar::Bound` everywhere. This simplifies the management of generics for projects
197    /// that don't intend to manipulate them too much.
198    #[clap(long)]
199    #[serde(default)]
200    pub unbind_item_vars: bool,
201
202    /// Pretty-print the ULLBC immediately after extraction from MIR.
203    #[clap(long)]
204    #[serde(default)]
205    pub print_original_ullbc: bool,
206    /// Pretty-print the ULLBC after applying the micro-passes (before serialization/control-flow reconstruction).
207    #[clap(long)]
208    #[serde(default)]
209    pub print_ullbc: bool,
210    /// Pretty-print the LLBC just after we built it (i.e., immediately after loop reconstruction).
211    #[clap(long)]
212    #[serde(default)]
213    pub print_built_llbc: bool,
214    /// Pretty-print the final LLBC (after all the cleaning micro-passes).
215    #[clap(long)]
216    #[serde(default)]
217    pub print_llbc: bool,
218
219    /// The destination directory. Files will be generated as `<dest_dir>/<crate_name>.{u}llbc`,
220    /// unless `dest_file` is set. `dest_dir` defaults to the current directory.
221    #[clap(long = "dest", value_parser)]
222    #[serde(default)]
223    pub dest_dir: Option<PathBuf>,
224    /// The destination file. By default `<dest_dir>/<crate_name>.llbc`. If this is set we ignore
225    /// `dest_dir`.
226    #[clap(long, value_parser)]
227    #[serde(default)]
228    pub dest_file: Option<PathBuf>,
229    /// Don't deduplicate values (types, trait refs) in the .(u)llbc file. This makes the file easier to inspect.
230    #[clap(long)]
231    #[serde(default)]
232    pub no_dedup_serialized_ast: bool,
233    /// Don't serialize the final (U)LLBC to a file.
234    #[clap(long)]
235    #[serde(default)]
236    pub no_serialize: bool,
237    /// Panic on the first error. This is useful for debugging.
238    #[clap(long)]
239    #[serde(default)]
240    pub abort_on_error: bool,
241    /// Consider any warnings to be errors.
242    #[clap(long)]
243    #[serde(default)]
244    pub error_on_warnings: bool,
245
246    /// Named builtin sets of options.
247    #[clap(long)]
248    #[arg(value_enum)]
249    pub preset: Option<Preset>,
250}
251
252/// The MIR stage to use. This is only relevant for the current crate: for dependencies, only mir
253/// optimized is available (or mir elaborated for consts).
254#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, ValueEnum, Serialize, Deserialize)]
255pub enum MirLevel {
256    /// The MIR just after MIR lowering.
257    Built,
258    /// The MIR after const promotion. This is the MIR used by the borrow-checker.
259    Promoted,
260    /// The MIR after drop elaboration. This is the first MIR to include all the runtime
261    /// information.
262    Elaborated,
263    /// The MIR after optimizations. Charon disables all the optimizations it can, so this is
264    /// sensibly the same MIR as the elaborated MIR.
265    Optimized,
266}
267
268/// Presets to make it easier to tweak options without breaking dependent projects. Eventually we
269/// should define semantically-meaningful presets instead of project-specific ones.
270#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, ValueEnum, Serialize, Deserialize)]
271#[non_exhaustive]
272pub enum Preset {
273    /// The default translation used before May 2025. After that, many passes were made optional
274    /// and disabled by default.
275    OldDefaults,
276    /// Emit the MIR as unmodified as possible. This is very imperfect for now, we should make more
277    /// passes optional.
278    RawMir,
279    Aeneas,
280    Eurydice,
281    Soteria,
282    Tests,
283}
284
285#[derive(
286    Debug, Default, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, ValueEnum, Serialize, Deserialize,
287)]
288pub enum MonomorphizeMut {
289    /// Monomorphize any item instantiated with `&mut`.
290    #[default]
291    All,
292    /// Monomorphize all non-typedecl items instantiated with `&mut`.
293    ExceptTypes,
294}
295
296impl CliOpts {
297    pub fn apply_preset(&mut self) {
298        if let Some(preset) = self.preset {
299            match preset {
300                Preset::OldDefaults => {
301                    self.treat_box_as_builtin = true;
302                    self.hide_allocator = true;
303                    self.ops_to_function_calls = true;
304                    self.index_to_function_calls = true;
305                    self.reconstruct_fallible_operations = true;
306                    self.reconstruct_asserts = true;
307                    self.unbind_item_vars = true;
308                }
309                Preset::RawMir => {
310                    self.extract_opaque_bodies = true;
311                    self.raw_consts = true;
312                    self.ullbc = true;
313                }
314                Preset::Aeneas => {
315                    self.remove_associated_types.push("*".to_owned());
316                    self.treat_box_as_builtin = true;
317                    self.ops_to_function_calls = true;
318                    self.index_to_function_calls = true;
319                    self.reconstruct_fallible_operations = true;
320                    self.reconstruct_asserts = true;
321                    self.hide_marker_traits = true;
322                    self.hide_allocator = true;
323                    self.remove_unused_self_clauses = true;
324                    self.unbind_item_vars = true;
325                    // Hide drop impls because they often involve nested borrows. which aeneas
326                    // doesn't handle yet.
327                    self.exclude.push("core::ops::drop::Drop".to_owned());
328                    self.exclude
329                        .push("{impl core::ops::drop::Drop for _}".to_owned());
330                }
331                Preset::Eurydice => {
332                    self.hide_allocator = true;
333                    self.treat_box_as_builtin = true;
334                    self.ops_to_function_calls = true;
335                    self.index_to_function_calls = true;
336                    self.reconstruct_fallible_operations = true;
337                    self.reconstruct_asserts = true;
338                    self.remove_associated_types.push("*".to_owned());
339                    self.unbind_item_vars = true;
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.mir = Some(MirLevel::Elaborated);
347                    self.ullbc = true;
348                }
349                Preset::Tests => {
350                    self.no_dedup_serialized_ast = true; // Helps debug
351                    self.treat_box_as_builtin = true;
352                    self.hide_allocator = true;
353                    self.reconstruct_fallible_operations = true;
354                    self.reconstruct_asserts = true;
355                    self.ops_to_function_calls = true;
356                    self.index_to_function_calls = true;
357                    self.rustc_args.push("--edition=2021".to_owned());
358                    self.rustc_args
359                        .push("-Zcrate-attr=feature(register_tool)".to_owned());
360                    self.rustc_args
361                        .push("-Zcrate-attr=register_tool(charon)".to_owned());
362                    self.exclude.push("core::fmt::Formatter".to_owned());
363                }
364            }
365        }
366    }
367
368    /// Check that the options are meaningful
369    pub fn validate(&self) -> anyhow::Result<()> {
370        if self.dest_dir.is_some() {
371            display_unspanned_error(
372                Level::WARNING,
373                "`--dest` is deprecated, use `--dest-file` instead",
374            )
375        }
376
377        if self.remove_adt_clauses && self.remove_associated_types.is_empty() {
378            anyhow::bail!(
379                "`--remove-adt-clauses` should be used with `--remove-associated-types='*'` \
380                to avoid missing clause errors",
381            )
382        }
383        if matches!(self.monomorphize_mut, Some(MonomorphizeMut::ExceptTypes))
384            && !self.remove_adt_clauses
385        {
386            anyhow::bail!(
387                "`--monomorphize-mut=except-types` should be used with `--remove-adt-clauses` \
388                to avoid generics mismatches"
389            )
390        }
391        Ok(())
392    }
393}
394
395/// The options that control translation and transformation.
396pub struct TranslateOptions {
397    /// Items from which to start translation.
398    pub start_from: Vec<NamePattern>,
399    /// The level at which to extract the MIR
400    pub mir_level: MirLevel,
401    /// Usually we skip the provided methods that aren't used. When this flag is on, we translate
402    /// them all.
403    pub translate_all_methods: bool,
404    /// If `Some(_)`, run the partial mutability monomorphization pass. The contained enum
405    /// indicates whether to partially monomorphize types.
406    pub monomorphize_mut: Option<MonomorphizeMut>,
407    /// Whether to hide various marker traits such as `Sized`, `Sync`, `Send` and `Destruct`
408    /// anywhere they show up.
409    pub hide_marker_traits: bool,
410    /// Remove trait clauses attached to type declarations.
411    pub remove_adt_clauses: bool,
412    /// Hide the `A` type parameter on standard library containers (`Box`, `Vec`, etc).
413    pub hide_allocator: bool,
414    /// Remove unused `Self: Trait` clauses on method declarations.
415    pub remove_unused_self_clauses: bool,
416    /// Monomorphize code using hax's instantiation mechanism.
417    pub monomorphize_with_hax: bool,
418    /// Transform array-to-slice unsizing, repeat expressions, and raw pointer construction into
419    /// builtin functions in ULLBC.
420    pub ops_to_function_calls: bool,
421    /// Transform array/slice indexing into builtin functions in ULLBC.
422    pub index_to_function_calls: bool,
423    /// Print the llbc just after control-flow reconstruction.
424    pub print_built_llbc: bool,
425    /// Treat `Box<T>` as if it was a built-in type.
426    pub treat_box_as_builtin: bool,
427    /// Don't inline or evaluate constants.
428    pub raw_consts: bool,
429    /// Replace string literal constants with a constant u8 array that gets unsized,
430    /// expliciting the fact a string constant has a hidden reference.
431    pub unsized_strings: bool,
432    /// Replace "bound checks followed by UB-on-overflow operation" with the corresponding
433    /// panic-on-overflow operation. This loses unwinding information.
434    pub reconstruct_fallible_operations: bool,
435    /// Replace "if x { panic() }" with "assert(x)".
436    pub reconstruct_asserts: bool,
437    // Use `DeBruijnVar::Free` for the variables bound in item signatures.
438    pub unbind_item_vars: bool,
439    /// List of patterns to assign a given opacity to. Same as the corresponding `TranslateOptions`
440    /// field.
441    pub item_opacities: Vec<(NamePattern, ItemOpacity)>,
442    /// List of traits for which we transform associated types to type parameters.
443    pub remove_associated_types: Vec<NamePattern>,
444    /// Transform Drop to Call drop_in_place
445    pub desugar_drops: bool,
446    /// Add `Destruct` bounds to all generic params.
447    pub add_destruct_bounds: bool,
448    /// Translate drop glue for poly types, knowing that this may cause ICEs.
449    pub translate_poly_drop_glue: bool,
450}
451
452impl TranslateOptions {
453    pub fn new(error_ctx: &mut ErrorCtx, options: &CliOpts) -> Self {
454        let mut parse_pattern = |s: &str| match NamePattern::parse(s) {
455            Ok(p) => Ok(p),
456            Err(e) => {
457                raise_error!(
458                    error_ctx,
459                    crate(&TranslatedCrate::default()),
460                    Span::dummy(),
461                    "failed to parse pattern `{s}` ({e})"
462                )
463            }
464        };
465
466        let mut mir_level = options.mir.unwrap_or(MirLevel::Promoted);
467        if options.precise_drops {
468            mir_level = std::cmp::max(mir_level, MirLevel::Elaborated);
469        }
470
471        let start_from = if options.start_from.is_empty() {
472            vec![parse_pattern("crate").unwrap()]
473        } else {
474            options
475                .start_from
476                .iter()
477                .filter_map(|path| parse_pattern(&path).ok())
478                .collect()
479        };
480
481        let item_opacities = {
482            use ItemOpacity::*;
483            let mut opacities = vec![];
484
485            // This is how to treat items that don't match any other pattern.
486            if options.extract_opaque_bodies {
487                opacities.push(("_".to_string(), Transparent));
488            } else {
489                opacities.push(("_".to_string(), Foreign));
490            }
491
492            // We always include the items from the crate.
493            opacities.push(("crate".to_owned(), Transparent));
494
495            for pat in options.include.iter() {
496                opacities.push((pat.to_string(), Transparent));
497            }
498            for pat in options.opaque.iter() {
499                opacities.push((pat.to_string(), Opaque));
500            }
501            for pat in options.exclude.iter() {
502                opacities.push((pat.to_string(), Invisible));
503            }
504
505            if options.hide_allocator {
506                opacities.push((format!("core::alloc::Allocator"), Invisible));
507                opacities.push((
508                    format!("alloc::alloc::{{impl core::alloc::Allocator for _}}"),
509                    Invisible,
510                ));
511            }
512
513            opacities
514                .into_iter()
515                .filter_map(|(s, opacity)| parse_pattern(&s).ok().map(|pat| (pat, opacity)))
516                .collect()
517        };
518
519        let remove_associated_types = options
520            .remove_associated_types
521            .iter()
522            .filter_map(|s| parse_pattern(&s).ok())
523            .collect();
524
525        TranslateOptions {
526            start_from,
527            mir_level,
528            monomorphize_mut: options.monomorphize_mut,
529            hide_marker_traits: options.hide_marker_traits,
530            remove_adt_clauses: options.remove_adt_clauses,
531            hide_allocator: options.hide_allocator,
532            remove_unused_self_clauses: options.remove_unused_self_clauses,
533            monomorphize_with_hax: options.monomorphize,
534            ops_to_function_calls: options.ops_to_function_calls,
535            index_to_function_calls: options.index_to_function_calls,
536            print_built_llbc: options.print_built_llbc,
537            item_opacities,
538            treat_box_as_builtin: options.treat_box_as_builtin,
539            raw_consts: options.raw_consts,
540            unsized_strings: options.unsized_strings,
541            reconstruct_fallible_operations: options.reconstruct_fallible_operations,
542            reconstruct_asserts: options.reconstruct_asserts,
543            remove_associated_types,
544            unbind_item_vars: options.unbind_item_vars,
545            translate_all_methods: options.translate_all_methods,
546            desugar_drops: options.desugar_drops,
547            add_destruct_bounds: options.precise_drops,
548            translate_poly_drop_glue: options.precise_drops,
549        }
550    }
551
552    /// Find the opacity requested for the given name. This does not take into account
553    /// `#[charon::opaque]` annotations, only cli parameters.
554    #[tracing::instrument(skip(self, krate), ret)]
555    pub fn opacity_for_name(&self, krate: &TranslatedCrate, name: &Name) -> ItemOpacity {
556        // Find the most precise pattern that matches this name. There is always one since
557        // the list contains the `_` pattern. If there are conflicting settings for this item, we
558        // err on the side of being more opaque.
559        let (_, opacity) = self
560            .item_opacities
561            .iter()
562            .filter(|(pat, _)| pat.matches(krate, name))
563            .max()
564            .unwrap();
565        *opacity
566    }
567}