1use 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
15pub const CHARON_ARGS: &str = "CHARON_ARGS";
18
19#[derive(Debug, Default, Clone, clap::Args, Serialize, Deserialize)]
28#[clap(name = "Charon")]
29#[charon::rename("cli_options")]
30pub struct CliOpts {
31 #[clap(long = "ullbc")]
33 #[serde(default)]
34 pub ullbc: bool,
35 #[clap(long = "lib")]
37 #[serde(default)]
38 pub lib: bool,
39 #[clap(long = "bin")]
41 #[serde(default)]
42 pub bin: Option<String>,
43 #[clap(long = "mir_promoted")]
45 #[serde(default)]
46 pub mir_promoted: bool,
47 #[clap(long = "mir_optimized")]
49 #[serde(default)]
50 pub mir_optimized: bool,
51 #[arg(long)]
54 pub mir: Option<MirLevel>,
55 #[clap(long = "input", value_parser)]
59 #[serde(default)]
60 pub input_file: Option<PathBuf>,
61 #[clap(long = "read-llbc", value_parser)]
63 #[serde(default)]
64 pub read_llbc: Option<PathBuf>,
65 #[clap(long = "dest", value_parser)]
68 #[serde(default)]
69 pub dest_dir: Option<PathBuf>,
70 #[clap(long = "dest-file", value_parser)]
73 #[serde(default)]
74 pub dest_file: Option<PathBuf>,
75 #[clap(long = "polonius")]
78 #[serde(default)]
79 pub use_polonius: bool,
80 #[clap(long = "skip-borrowck")]
82 #[serde(default)]
83 pub skip_borrowck: bool,
84 #[clap(long = "monomorphize")]
86 #[serde(default)]
87 pub monomorphize: bool,
88 #[clap(long = "extract-opaque-bodies")]
91 #[serde(default)]
92 pub extract_opaque_bodies: bool,
93 #[clap(long = "translate-all-methods")]
96 #[serde(default)]
97 pub translate_all_methods: bool,
98 #[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 #[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 #[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 #[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 #[clap(long = "hide-marker-traits")]
149 #[serde(default)]
150 pub hide_marker_traits: bool,
151 #[clap(long)]
153 #[serde(default)]
154 pub hide_allocator: bool,
155 #[clap(long)]
160 #[serde(default)]
161 pub remove_unused_self_clauses: bool,
162 #[clap(long)]
165 #[serde(default)]
166 pub add_drop_bounds: bool,
167 #[clap(long = "start-from")]
171 #[serde(default)]
172 pub start_from: Vec<String>,
173 #[clap(long = "no-cargo")]
175 #[serde(default)]
176 pub no_cargo: bool,
177 #[clap(long = "rustc-flag", alias = "rustc-arg")]
179 #[serde(default)]
180 pub rustc_args: Vec<String>,
181 #[clap(long = "cargo-arg")]
183 #[serde(default)]
184 pub cargo_args: Vec<String>,
185 #[clap(long = "abort-on-error")]
187 #[serde(default)]
188 pub abort_on_error: bool,
189 #[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 #[clap(long = "preset")]
247 #[arg(value_enum)]
248 pub preset: Option<Preset>,
249}
250
251#[derive(
254 Debug, Default, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, ValueEnum, Serialize, Deserialize,
255)]
256pub enum MirLevel {
257 #[default]
259 Built,
260 Promoted,
262 Elaborated,
265 Optimized,
268}
269
270#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, ValueEnum, Serialize, Deserialize)]
273#[non_exhaustive]
274pub enum Preset {
275 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 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 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
383pub struct TranslateOptions {
385 pub mir_level: MirLevel,
387 pub translate_all_methods: bool,
390 pub hide_marker_traits: bool,
393 pub hide_allocator: bool,
395 pub remove_unused_self_clauses: bool,
397 pub monomorphize: bool,
399 pub no_ops_to_function_calls: bool,
401 pub no_merge_goto_chains: bool,
403 pub print_built_llbc: bool,
405 pub raw_boxes: bool,
407 pub item_opacities: Vec<(NamePattern, ItemOpacity)>,
410 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 if options.extract_opaque_bodies {
442 opacities.push(("_".to_string(), Transparent));
443 } else {
444 opacities.push(("_".to_string(), Foreign));
445 }
446
447 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 #[tracing::instrument(skip(self, krate), ret)]
499 pub fn opacity_for_name(&self, krate: &TranslatedCrate, name: &Name) -> ItemOpacity {
500 let (_, opacity) = self
504 .item_opacities
505 .iter()
506 .filter(|(pat, _)| pat.matches(krate, name))
507 .max()
508 .unwrap();
509 *opacity
510 }
511}