1use annotate_snippets::Level;
3use clap::ValueEnum;
4use indoc::indoc;
5use itertools::Itertools;
6use macros::EnumAsGetters;
7use serde::{Deserialize, Serialize};
8use std::path::PathBuf;
9
10use crate::{
11 ast::*,
12 errors::{ErrorCtx, display_unspanned_error},
13 name_matcher::NamePattern,
14 raise_error, register_error,
15};
16
17pub const CHARON_ARGS: &str = "CHARON_ARGS";
20
21#[derive(Debug, Default, Clone, clap::Args, PartialEq, Eq, Serialize, Deserialize)]
30#[clap(name = "Charon")]
31#[charon::rename("cli_options")]
32pub struct CliOpts {
33 #[clap(long)]
35 #[serde(default)]
36 pub ullbc: bool,
37 #[clap(long)]
48 #[serde(default)]
49 pub precise_drops: bool,
50 #[clap(long)]
52 #[serde(default)]
53 pub skip_borrowck: bool,
54 #[arg(long)]
57 pub mir: Option<MirLevel>,
58 #[clap(long = "rustc-arg")]
60 #[serde(default)]
61 pub rustc_args: Vec<String>,
62 #[clap(long, value_delimiter = ',')]
67 #[serde(default)]
68 pub targets: Vec<String>,
69
70 #[clap(long, visible_alias = "mono")]
74 #[serde(default)]
75 pub monomorphize: bool,
76 #[clap(
80 long,
81 value_name("INCLUDE_TYPES"),
82 num_args(0..=1),
83 require_equals(true),
84 default_missing_value("all"),
85 )]
86 #[serde(default)]
87 pub monomorphize_mut: Option<MonomorphizeMut>,
88
89 #[clap(long, value_delimiter = ',')]
93 #[serde(default)]
94 pub start_from: Vec<String>,
95 #[clap(long, value_delimiter = ',')]
98 #[serde(default)]
99 pub start_from_if_exists: Vec<String>,
100 #[clap(
104 long,
105 value_name("ATTRIBUTE"),
106 num_args(0..=1),
107 require_equals(true),
108 default_missing_value("verify::start_from"),
109 )]
110 #[serde(default)]
111 pub start_from_attribute: Option<String>,
112 #[clap(long)]
114 #[serde(default)]
115 pub start_from_pub: bool,
116
117 #[clap(
119 long,
120 help = indoc!("
121 Whitelist of items to translate. These use the name-matcher syntax (note: this differs
122 a bit from the ocaml NameMatcher).
123
124 Note: This is very rough at the moment. E.g. this parses `u64` as a path instead of the
125 built-in type. It is also not possible to filter a trait impl (this will only filter
126 its methods). Please report bugs or missing features.
127
128 Examples:
129 - `crate::module1::module2::item`: refers to this item and all its subitems (e.g.
130 submodules or trait methods);
131 - `crate::module1::module2::item::_`: refers only to the subitems of this item;
132 - `core::convert::{impl core::convert::Into<_> for _}`: retrieve the body of this
133 very useful impl;
134
135 When multiple patterns in the `--include` and `--opaque` options match the same item,
136 the most precise pattern wins. E.g.: `charon --opaque crate::module --include
137 crate::module::_` makes the `module` opaque (we won't explore its contents), but the
138 items in it transparent (we will translate them if we encounter them.)
139 "))]
140 #[serde(default)]
141 #[charon::rename("included")]
142 pub include: Vec<String>,
143 #[clap(long)]
145 #[serde(default)]
146 pub opaque: Vec<String>,
147 #[clap(long)]
149 #[serde(default)]
150 pub exclude: Vec<String>,
151 #[clap(long)]
154 #[serde(default)]
155 pub extract_opaque_bodies: bool,
156 #[clap(long)]
159 #[serde(default)]
160 pub translate_all_methods: bool,
161
162 #[clap(long, alias = "remove-associated-types")]
165 #[serde(default)]
166 pub lift_associated_types: Vec<String>,
167 #[clap(long)]
170 #[serde(default)]
171 pub hide_marker_traits: bool,
172 #[clap(long)]
176 #[serde(default)]
177 pub remove_adt_clauses: bool,
178 #[clap(long)]
180 #[serde(default)]
181 pub hide_allocator: bool,
182 #[clap(long)]
187 #[serde(default)]
188 pub remove_unused_self_clauses: bool,
189
190 #[clap(long)]
192 #[serde(default)]
193 pub desugar_drops: bool,
194 #[clap(long)]
197 #[serde(default)]
198 pub ops_to_function_calls: bool,
199 #[clap(long)]
203 #[serde(default)]
204 pub index_to_function_calls: bool,
205 #[clap(long)]
207 #[serde(default)]
208 pub treat_box_as_builtin: bool,
209 #[clap(long)]
211 #[serde(default)]
212 pub raw_consts: bool,
213 #[clap(long)]
216 #[serde(default)]
217 pub unsized_strings: bool,
218 #[clap(long)]
221 #[serde(default)]
222 pub reconstruct_fallible_operations: bool,
223 #[clap(long)]
225 #[serde(default)]
226 pub reconstruct_asserts: bool,
227 #[clap(long)]
231 #[serde(default)]
232 pub unbind_item_vars: bool,
233
234 #[clap(long)]
236 #[serde(default)]
237 pub print_original_ullbc: bool,
238 #[clap(long)]
240 #[serde(default)]
241 pub print_ullbc: bool,
242 #[clap(long)]
244 #[serde(default)]
245 pub print_built_llbc: bool,
246 #[clap(long)]
248 #[serde(default)]
249 pub print_llbc: bool,
250 #[clap(long = "dest", value_parser)]
254 #[serde(default)]
255 pub dest_dir: Option<PathBuf>,
256 #[clap(long, value_parser)]
260 #[serde(default)]
261 pub dest_file: Option<PathBuf>,
262 #[clap(long)]
264 #[serde(default)]
265 pub no_dedup_serialized_ast: bool,
266 #[clap(long, value_enum)]
268 #[serde(default)]
269 pub format: Option<SerializationFormatArg>,
270 #[clap(long)]
272 #[serde(default)]
273 pub no_serialize: bool,
274 #[clap(long)]
276 #[serde(default)]
277 pub no_typecheck: bool,
278 #[clap(long)]
280 #[serde(default)]
281 pub no_normalize: bool,
282 #[clap(long)]
284 #[serde(default)]
285 pub abort_on_error: bool,
286 #[clap(long)]
288 #[serde(default)]
289 pub error_on_warnings: bool,
290
291 #[clap(long)]
293 #[arg(value_enum)]
294 pub preset: Option<Preset>,
295}
296
297#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, ValueEnum, Serialize, Deserialize)]
300pub enum MirLevel {
301 Built,
303 Promoted,
305 Elaborated,
308 Optimized,
311}
312
313#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, ValueEnum, Serialize, Deserialize)]
316#[non_exhaustive]
317pub enum Preset {
318 OldDefaults,
321 RawMir,
324 Fast,
326 Aeneas,
327 Eurydice,
328 Soteria,
329 Tests,
330}
331
332#[derive(
333 Debug, Default, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, ValueEnum, Serialize, Deserialize,
334)]
335pub enum MonomorphizeMut {
336 #[default]
338 All,
339 ExceptTypes,
341}
342
343#[derive(Debug, Copy, Clone, PartialEq, Eq, ValueEnum, Serialize, Deserialize)]
344pub enum SerializationFormatArg {
345 Json,
346 Postcard,
347 #[charon::rename("AllFormats")]
348 All,
349}
350
351#[derive(
352 Debug, Default, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, ValueEnum, Serialize, Deserialize,
353)]
354pub enum SerializationFormat {
355 #[default]
356 Json,
357 Postcard,
358}
359
360impl SerializationFormatArg {
361 pub fn as_format(self) -> Option<SerializationFormat> {
362 match self {
363 SerializationFormatArg::Json => Some(SerializationFormat::Json),
364 SerializationFormatArg::Postcard => Some(SerializationFormat::Postcard),
365 SerializationFormatArg::All => None,
366 }
367 }
368}
369
370impl From<SerializationFormat> for SerializationFormatArg {
371 fn from(format: SerializationFormat) -> SerializationFormatArg {
372 match format {
373 SerializationFormat::Json => SerializationFormatArg::Json,
374 SerializationFormat::Postcard => SerializationFormatArg::Postcard,
375 }
376 }
377}
378
379impl SerializationFormat {
380 pub fn output_extension(self, ullbc: bool) -> &'static str {
381 match (ullbc, self) {
382 (true, SerializationFormat::Json) => "ullbc",
383 (false, SerializationFormat::Json) => "llbc",
384 (true, SerializationFormat::Postcard) => "ullbc.postcard",
385 (false, SerializationFormat::Postcard) => "llbc.postcard",
386 }
387 }
388}
389
390impl CliOpts {
391 pub fn apply_preset(&mut self) {
392 if let Some(preset) = self.preset {
393 match preset {
394 Preset::OldDefaults => {
395 self.treat_box_as_builtin = true;
396 self.hide_allocator = true;
397 self.ops_to_function_calls = true;
398 self.index_to_function_calls = true;
399 self.reconstruct_fallible_operations = true;
400 self.reconstruct_asserts = true;
401 self.unbind_item_vars = true;
402 }
403 Preset::RawMir => {
404 self.extract_opaque_bodies = true;
405 self.raw_consts = true;
406 self.ullbc = true;
407 }
408 Preset::Fast => {
409 self.ullbc = true;
410 self.no_typecheck = true;
411 self.no_normalize = true;
412 self.hide_marker_traits = true;
413 self.raw_consts = true;
414 }
415 Preset::Aeneas => {
416 self.lift_associated_types.push("*".to_owned());
417 self.treat_box_as_builtin = true;
418 self.ops_to_function_calls = true;
419 self.index_to_function_calls = true;
420 self.reconstruct_fallible_operations = true;
421 self.reconstruct_asserts = true;
422 self.hide_marker_traits = true;
423 self.hide_allocator = true;
424 self.remove_unused_self_clauses = true;
425 self.remove_adt_clauses = true;
426 self.unbind_item_vars = true;
427 self.exclude.push("core::ops::drop::Drop".to_owned());
430 self.exclude
431 .push("{impl core::ops::drop::Drop for _}".to_owned());
432 }
433 Preset::Eurydice => {
434 self.hide_allocator = true;
435 self.treat_box_as_builtin = true;
436 self.ops_to_function_calls = true;
437 self.index_to_function_calls = true;
438 self.reconstruct_fallible_operations = true;
439 self.reconstruct_asserts = true;
440 self.lift_associated_types.push("*".to_owned());
441 self.unbind_item_vars = true;
442 self.include.push("core::marker::MetaSized".to_owned());
444 }
445 Preset::Soteria => {
446 self.extract_opaque_bodies = true;
447 self.monomorphize = true;
448 self.mir = Some(MirLevel::Elaborated);
449 self.ullbc = true;
450 }
451 Preset::Tests => {
452 self.no_dedup_serialized_ast = true; self.treat_box_as_builtin = true;
454 self.hide_allocator = true;
455 self.reconstruct_fallible_operations = true;
456 self.reconstruct_asserts = true;
457 self.ops_to_function_calls = true;
458 self.index_to_function_calls = true;
459 self.rustc_args.push("--edition=2021".to_owned());
460 self.rustc_args
461 .push("-Zcrate-attr=feature(register_tool)".to_owned());
462 self.rustc_args
463 .push("-Zcrate-attr=register_tool(charon)".to_owned());
464 self.exclude.push("core::fmt::Formatter".to_owned());
465 }
466 }
467 }
468 }
469
470 pub fn validate(&self) -> anyhow::Result<()> {
472 if self.dest_dir.is_some() {
473 display_unspanned_error(
474 Level::WARNING,
475 "`--dest` is deprecated, use `--dest-file` instead",
476 )
477 }
478
479 if self.remove_adt_clauses && self.lift_associated_types.is_empty() {
480 anyhow::bail!(
481 "`--remove-adt-clauses` should be used with `--remove-associated-types='*'` \
482 to avoid missing clause errors",
483 )
484 }
485 if matches!(self.monomorphize_mut, Some(MonomorphizeMut::ExceptTypes))
486 && !self.remove_adt_clauses
487 {
488 anyhow::bail!(
489 "`--monomorphize-mut=except-types` should be used with `--remove-adt-clauses` \
490 to avoid generics mismatches"
491 )
492 }
493 if self.no_serialize && self.format.is_some() {
494 anyhow::bail!(
495 "`--no-serialize` is not compatible with `--format`, the format is only relevant if we serialize"
496 );
497 }
498 Ok(())
499 }
500
501 fn target_filename(
502 &self,
503 path_base: PathBuf,
504 format: SerializationFormat,
505 ) -> (PathBuf, SerializationFormat) {
506 let extension = format.output_extension(self.ullbc);
507 let target_filename = path_base.with_added_extension(extension);
508 (target_filename, format)
509 }
510
511 pub fn targets(&self, crate_name: &str) -> Vec<(PathBuf, SerializationFormat)> {
512 if self.no_serialize {
513 return vec![];
514 }
515
516 let format = self.format.unwrap_or(SerializationFormatArg::Json);
517 let mut path_base = self.dest_dir.clone().unwrap_or_default();
518 path_base.push(crate_name);
519
520 match format.as_format() {
521 Some(format) => match self.dest_file.clone() {
522 Some(dest) => vec![(dest, format)],
523 None => vec![self.target_filename(path_base, format)],
524 },
525 None => {
526 let path_base = self.dest_file.clone().unwrap_or(path_base);
527 vec![
528 self.target_filename(path_base.clone(), SerializationFormat::Json),
529 self.target_filename(path_base, SerializationFormat::Postcard),
530 ]
531 }
532 }
533 }
534}
535
536#[derive(Debug, Clone, EnumAsGetters)]
538pub enum StartFrom {
539 Pattern { pattern: NamePattern, strict: bool },
542 Attribute(String),
544 Pub,
547}
548
549impl StartFrom {
550 pub fn matches(&self, ctx: &TranslatedCrate, item_meta: &ItemMeta) -> bool {
551 match self {
552 StartFrom::Pattern { pattern, .. } => pattern.matches(ctx, &item_meta.name),
553 StartFrom::Attribute(attr) => item_meta
554 .attr_info
555 .attributes
556 .iter()
557 .filter_map(|a| a.as_unknown())
558 .any(|raw_attr| raw_attr.path == *attr),
559 StartFrom::Pub => item_meta.attr_info.public,
560 }
561 }
562}
563
564pub struct TranslateOptions {
566 pub start_from: Vec<StartFrom>,
568 pub mir_level: MirLevel,
570 pub translate_all_methods: bool,
573 pub monomorphize_mut: Option<MonomorphizeMut>,
576 pub remove_adt_clauses: bool,
578 pub hide_marker_traits: bool,
581 pub hide_allocator: bool,
583 pub hide_traits: Vec<NamePattern>,
586 pub remove_unused_self_clauses: bool,
588 pub monomorphize_with_hax: bool,
590 pub ops_to_function_calls: bool,
593 pub index_to_function_calls: bool,
595 pub print_built_llbc: bool,
597 pub treat_box_as_builtin: bool,
599 pub raw_consts: bool,
601 pub unsized_strings: bool,
604 pub reconstruct_fallible_operations: bool,
607 pub reconstruct_asserts: bool,
609 pub unbind_item_vars: bool,
611 pub item_opacities: Vec<(NamePattern, ItemOpacity)>,
614 pub lift_associated_types: Vec<NamePattern>,
616 pub no_typecheck: bool,
618 pub no_normalize: bool,
620 pub desugar_drops: bool,
622 pub add_destruct_bounds: bool,
624 pub translate_poly_drop_glue: bool,
626}
627
628impl TranslateOptions {
629 pub fn new(error_ctx: &mut ErrorCtx, options: &CliOpts) -> Self {
630 let mut parse_pattern = |s: &str| -> Result<_, Error> {
631 match NamePattern::parse(s) {
632 Ok(p) => Ok(p),
633 Err(e) => raise_error!(error_ctx, no_crate, "failed to parse pattern `{s}` ({e})"),
634 }
635 };
636
637 let mut mir_level = options.mir.unwrap_or(MirLevel::Promoted);
638 if options.precise_drops {
639 mir_level = std::cmp::max(mir_level, MirLevel::Elaborated);
640 }
641
642 let mut start_from = options
643 .start_from
644 .iter()
645 .filter_map(|path| parse_pattern(path).ok())
646 .map(|p| StartFrom::Pattern {
647 pattern: p,
648 strict: true,
649 })
650 .collect_vec();
651 start_from.extend(
652 options
653 .start_from_if_exists
654 .iter()
655 .filter_map(|path| parse_pattern(path).ok())
656 .map(|p| StartFrom::Pattern {
657 pattern: p,
658 strict: false,
659 }),
660 );
661 if let Some(attr) = options.start_from_attribute.clone() {
662 start_from.push(StartFrom::Attribute(attr));
663 }
664 if options.start_from_pub {
665 start_from.push(StartFrom::Pub);
666 }
667 if start_from.is_empty() {
668 start_from.push(StartFrom::Pattern {
669 pattern: parse_pattern("crate").unwrap(),
670 strict: true,
671 });
672 }
673
674 let hide_traits = options
675 .hide_marker_traits
676 .then_some([
677 "core::marker::Sized",
678 "core::marker::MetaSized",
679 "core::marker::PointeeSized",
680 "core::marker::Tuple",
681 "core::clone::TrivialClone",
682 ])
683 .into_iter()
684 .flatten()
685 .chain(
686 (options.hide_marker_traits && !options.precise_drops)
687 .then_some("core::marker::Destruct"),
688 )
689 .chain(options.hide_allocator.then_some("core::alloc::Allocator"))
690 .filter_map(|s| parse_pattern(s).ok())
691 .collect_vec();
692
693 let item_opacities = {
694 use ItemOpacity::*;
695 let mut opacities = vec![];
696
697 if options.extract_opaque_bodies {
699 opacities.push(("_".to_string(), Transparent));
700 } else {
701 opacities.push(("_".to_string(), Foreign));
702 }
703
704 opacities.push(("crate".to_owned(), Transparent));
706
707 for pat in options.include.iter() {
708 opacities.push((pat.to_string(), Transparent));
709 }
710 for pat in options.opaque.iter() {
711 opacities.push((pat.to_string(), Opaque));
712 }
713 for pat in options.exclude.iter() {
714 opacities.push((pat.to_string(), Invisible));
715 }
716
717 for trait_name in &hide_traits {
718 opacities.push((trait_name.to_string(), Invisible));
719 }
720
721 let hide_traits = hide_traits
723 .iter()
724 .cloned()
725 .flat_map(|pat| [pat.clone(), NamePattern::impl_for(pat)])
726 .map(|pat| (pat, Invisible));
727 opacities
728 .into_iter()
729 .filter_map(|(s, opacity)| parse_pattern(&s).ok().map(|pat| (pat, opacity)))
730 .chain(hide_traits)
731 .collect()
732 };
733
734 let lift_associated_types = options
735 .lift_associated_types
736 .iter()
737 .filter_map(|s| parse_pattern(s).ok())
738 .collect();
739
740 TranslateOptions {
741 start_from,
742 mir_level,
743 monomorphize_mut: options.monomorphize_mut,
744 remove_adt_clauses: options.remove_adt_clauses,
745 hide_marker_traits: options.hide_marker_traits,
746 hide_allocator: options.hide_allocator,
747 hide_traits,
748 remove_unused_self_clauses: options.remove_unused_self_clauses,
749 monomorphize_with_hax: options.monomorphize,
750 ops_to_function_calls: options.ops_to_function_calls,
751 index_to_function_calls: options.index_to_function_calls,
752 print_built_llbc: options.print_built_llbc,
753 item_opacities,
754 treat_box_as_builtin: options.treat_box_as_builtin,
755 raw_consts: options.raw_consts,
756 unsized_strings: options.unsized_strings,
757 reconstruct_fallible_operations: options.reconstruct_fallible_operations,
758 reconstruct_asserts: options.reconstruct_asserts,
759 lift_associated_types,
760 unbind_item_vars: options.unbind_item_vars,
761 translate_all_methods: options.translate_all_methods,
762 no_typecheck: options.no_typecheck,
763 no_normalize: options.no_normalize,
764 desugar_drops: options.desugar_drops,
765 add_destruct_bounds: options.precise_drops,
766 translate_poly_drop_glue: options.precise_drops,
767 }
768 }
769
770 #[tracing::instrument(skip(self, krate), ret)]
773 pub fn opacity_for_name(&self, krate: &TranslatedCrate, name: &Name) -> ItemOpacity {
774 let (_, opacity) = self
778 .item_opacities
779 .iter()
780 .filter(|(pat, _)| pat.matches(krate, name))
781 .max()
782 .unwrap();
783 *opacity
784 }
785}