1#![feature(box_patterns)]
2#![feature(never_type)]
3#![feature(iter_intersperse)]
4
5pub extern crate string_cache;
9
10pub mod annotation;
11pub mod error;
12pub mod filesystem;
13pub mod formatter;
14pub mod labelling;
15pub mod location;
16pub mod macros;
17pub mod parser;
18pub mod typ;
19pub mod wrappers;
20pub mod local_config;
21
22use config::{
23 Config,
24 File as CfgFile,
25};
26
27use local_config::Settings;
28
29use log::{
30 debug, info, error,
31};
32use quote::ToTokens;
33
34use std::{
35 fs,
36 io::Write,
37 process::{Command, Stdio},
38 path::PathBuf,
39 env,
40 error::Error,
41};
42
43use syn::{
44 visit_mut::VisitMut,
45 ExprCall,
46 ExprMethodCall,
47 File,
48 ImplItemMethod,
49 ItemFn,
50 TraitItemMethod,
51 parse_file
52};
53
54use home::cargo_home;
55use regex::Regex;
56use colored::*;
57
58pub fn compile_file(file_name: &str, args: &Vec<&str>) -> Command {
62 let mut compile = Command::new("rustc");
63 for arg in args {
64 compile.arg(arg);
65 }
66 compile.arg(file_name);
67 compile
68}
69
70pub fn check_project(manifest_path: &str, cargo_args: &Vec<&str>) -> Command {
71 let mut check = Command::new("cargo");
72 check.arg("check");
73 for arg in cargo_args {
74 check.arg(arg);
75 }
76 let toml = format!("--manifest-path={}", manifest_path);
77 check.arg(toml);
78 check.arg("--message-format=json");
79 check
80}
81
82pub fn build_project(manifest_path: &str, cargo_args: &Vec<&str>) -> Command {
83 let mut check = Command::new("cargo");
84 check.arg("build");
85 for arg in cargo_args {
86 check.arg(arg);
87 }
88 let toml = format!("--manifest-path={}", manifest_path);
89 check.arg(toml);
90 check.arg("--message-format=json");
91 check
92}
93
94pub struct FindCallee<'a> {
95 pub found: bool,
96 pub callee_fn_name: &'a str,
97}
98
99impl VisitMut for FindCallee<'_> {
100 fn visit_expr_call_mut(&mut self, i: &mut ExprCall) {
101 let callee = i.func.as_ref().into_token_stream().to_string();
102 debug!("looking at callee: {}", callee);
103 match callee.contains(self.callee_fn_name) {
104 true => self.found = true,
105 false => syn::visit_mut::visit_expr_call_mut(self, i),
106 }
107 }
108
109 fn visit_expr_method_call_mut(&mut self, i: &mut ExprMethodCall) {
110 let callee = i.method.clone().into_token_stream().to_string();
111 debug!("looking at callee: {}", callee);
112 match callee.contains(self.callee_fn_name) {
113 true => self.found = true,
114 false => syn::visit_mut::visit_expr_method_call_mut(self, i),
115 }
116 }
117}
118
119pub struct FindCaller<'a> {
120 caller_fn_name: &'a str,
121 callee_finder: &'a mut FindCallee<'a>,
122 found: bool,
123 caller: String,
124}
125
126impl VisitMut for FindCaller<'_> {
127 fn visit_impl_item_method_mut(&mut self, i: &mut ImplItemMethod) {
128 if self.found {
129 return;
130 }
131 debug!("{:?}", i);
132 let id = i.sig.ident.to_string();
133 match id == self.caller_fn_name {
134 true => {
135 self.callee_finder.visit_impl_item_method_mut(i);
136 if !self.callee_finder.found {
137 return;
138 }
139 self.found = true;
140 self.caller = i.into_token_stream().to_string();
141 }
142 false => {}
143 }
144 syn::visit_mut::visit_impl_item_method_mut(self, i);
145 }
146
147 fn visit_trait_item_method_mut(&mut self, i: &mut TraitItemMethod) {
148 if self.found {
149 return;
150 }
151 debug!("{:?}", i);
152 let id = i.sig.ident.to_string();
153 match id == self.caller_fn_name {
154 true => {
155 self.callee_finder.visit_trait_item_method_mut(i);
156 if !self.callee_finder.found {
157 return;
158 }
159 self.found = true;
160 self.caller = i.into_token_stream().to_string();
161 }
162 false => {}
163 }
164 syn::visit_mut::visit_trait_item_method_mut(self, i);
165 }
166
167 fn visit_item_fn_mut(&mut self, i: &mut ItemFn) {
168 if self.found {
169 return;
170 }
171 debug!("{:?}", i);
172 let id = i.sig.ident.to_string();
173 match id == self.caller_fn_name {
174 true => {
175 self.callee_finder.visit_item_fn_mut(i);
176 if !self.callee_finder.found {
177 return;
178 }
179 self.found = true;
180 self.caller = i.into_token_stream().to_string();
181 }
182 false => (),
183 }
184 }
185}
186
187pub struct FindFn<'a> {
188 fn_name: &'a str,
189 found: bool,
190 fn_txt: String,
191 body_only: bool,
192}
193
194impl VisitMut for FindFn<'_> {
195 fn visit_impl_item_method_mut(&mut self, i: &mut ImplItemMethod) {
196 if self.found {
197 return;
198 }
199 debug!("{:?}", i);
200 let id = i.sig.ident.to_string();
201 match id == self.fn_name {
202 true => {
203 self.found = true;
204 i.attrs = vec![];
205 if self.body_only {
206 self.fn_txt = i.block.clone().into_token_stream().to_string();
207 } else {
208 self.fn_txt = i.into_token_stream().to_string();
209 }
210 }
211 false => {}
212 }
213 syn::visit_mut::visit_impl_item_method_mut(self, i);
214 }
215
216 fn visit_item_fn_mut(&mut self, i: &mut ItemFn) {
217 if self.found {
218 return;
219 }
220 debug!("{:?}", i);
221 let id = i.sig.ident.to_string();
222 match id == self.fn_name {
223 true => {
224 self.found = true;
225 i.attrs = vec![];
226 if self.body_only {
227 self.fn_txt = i.block.clone().into_token_stream().to_string();
228 } else {
229 self.fn_txt = i.into_token_stream().to_string();
230 }
231 }
232 false => (),
233 }
234 }
235
236 fn visit_trait_item_method_mut(&mut self, i: &mut TraitItemMethod) {
237 if self.found {
238 return;
239 }
240 debug!("{:?}", i);
241 let id = i.sig.ident.to_string();
242 match id == self.fn_name {
243 true => {
244 self.found = true;
245 i.attrs = vec![];
246 if self.body_only {
247 self.fn_txt = "{}".to_string();
248 } else {
249 self.fn_txt = i.into_token_stream().to_string();
250 }
251 }
252 false => {}
253 }
254 syn::visit_mut::visit_trait_item_method_mut(self, i);
255 }
256}
257
258pub fn find_caller(
259 file_name: &str,
260 caller_name: &str,
261 callee_name: &str,
262 callee_body_only: bool,
263) -> (bool, String, String) {
264 let file_content: String = fs::read_to_string(&file_name).unwrap().parse().unwrap();
265 let mut file = syn::parse_str::<File>(file_content.as_str())
266 .map_err(|e| format!("{:?}", e))
267 .unwrap();
268
269 let mut visit = FindCaller {
270 caller_fn_name: caller_name,
271 callee_finder: &mut FindCallee {
272 found: false,
273 callee_fn_name: callee_name,
274 },
275 found: false,
276 caller: String::new(),
277 };
278 visit.visit_file_mut(&mut file);
279
280 let mut callee = FindFn {
281 fn_name: callee_name,
282 found: false,
283 fn_txt: String::new(),
284 body_only: callee_body_only,
285 };
286
287 callee.visit_file_mut(&mut file);
288
289 (
290 visit.found && callee.found,
291 format_source(visit.caller.as_str()),
292 format_source(callee.fn_txt.as_str()),
293 )
294}
295
296pub fn resolve_charon_path(cli_charon_path: &Option<PathBuf>) -> Result<PathBuf, Box<dyn Error>> {
307 if let Some(path) = cli_charon_path {
309 info!("Using Charon path provided via CLI: {:?}", path.clone());
310 return Ok(path.clone());
311 }
312
313 if let Ok(path) = env::var("CHARON_PATH") {
315 info!("Using Charon path provided via environment variable: {:?}", path);
316 return Ok(PathBuf::from(path));
317 }
318
319 if let Ok(settings) = get_config() {
321 info!("Using Charon path provided via config file: {:?}", get_charon_path(&settings));
322 return Ok(get_charon_path(&settings));
323 }
324
325 info!("Using default Charon path: ./charon");
327 if let Ok(path) = env::current_dir() {
328 let charon_path = path.join("charon");
329 if charon_path.exists() {
330 return Ok(charon_path);
331 }
332 }
333
334 error!("Could not find Charon binary. Please provide the path to the binary using the `--charon-path` flag or the `CHARON_PATH` environment variable.");
335 Err("Could not find Charon binary. Please provide the path to the binary using the `--charon-path` flag or the `CHARON_PATH` environment variable.".into())
336}
337
338
339pub fn resolve_aeneas_path(cli_aneas_path: &Option<PathBuf>) -> Result<PathBuf, Box<dyn Error>> {
346 if let Some(path) = cli_aneas_path {
348 info!("Using Aeneas path provided via CLI: {:?}", path.clone());
349 return Ok(path.clone());
350 }
351
352 if let Ok(path) = env::var("AENEAS_PATH") {
354 info!("Using Aeneas path provided via environment variable: {:?}", path);
355 return Ok(PathBuf::from(path));
356 }
357
358 if let Ok(settings) = get_config() {
360 info!("Using Aeneas path provided via config file: {:?}", get_aeneas_path(&settings));
361 return Ok(get_aeneas_path(&settings));
362 }
363
364 if let Ok(path) = env::current_dir() {
366 let aeneas_path = path.join("aeneas");
367 if aeneas_path.exists() {
368 return Ok(aeneas_path);
369 }
370 }
371
372 error!("Could not find Aeneas binary. Please provide the path to the binary using the `--aeneas-path` flag or the `AENEAS_PATH` environment variable.");
373 Err("Could not find Aeneas binary. Please provide the path to the binary using the `--aeneas-path` flag or the `AENEAS_PATH` environment variable.".into())
374
375}
376
377fn get_config() -> Result<Settings, Box<dyn std::error::Error>> {
378 let config: Config = Config::builder()
379 .add_source(CfgFile::with_name("Config")
380 .required(true))
381 .build()?;
382 let s: Settings = config.try_deserialize()?;
383 Ok(s)
384}
385
386fn get_aeneas_path(settings: &Settings) -> PathBuf {
387 let aeneas_str:&String = &settings.programs.aeneas;
388 PathBuf::from(aeneas_str)
389}
390
391fn get_charon_path(settings: &Settings) -> PathBuf {
392 let charon_str:&String = &settings.programs.charon;
393 PathBuf::from(charon_str)
394}
395
396pub fn format_source(src: &str) -> String {
397 let rustfmt = {
398 let rustfmt_path = format!("{}/bin/rustfmt", cargo_home().unwrap().to_string_lossy());
399 println!("{}", &rustfmt_path);
400 let mut proc = Command::new(&rustfmt_path)
401 .arg("--edition=2021")
402 .stdin(Stdio::piped())
403 .stdout(Stdio::piped())
404 .spawn()
405 .unwrap();
406 let mut stdin = proc.stdin.take().unwrap();
407 stdin.write_all(src.as_bytes()).unwrap();
408 proc
409 };
410
411 let stdout = rustfmt.wait_with_output().unwrap();
412
413 String::from_utf8(stdout.stdout).unwrap()
414}
415
416pub fn remove_all_files(dir: &PathBuf) -> () {
417 info!("Removing all files in directory: {:?}", dir);
418 for entry in fs::read_dir(dir).unwrap() {
419 let entry = entry.unwrap();
420 let path = entry.path();
421 if path.is_file() {
422 info!("Removing file: {:?}", path);
423 fs::remove_file(path).unwrap();
424 }
425 }
426}
427
428pub fn strip_ansi_codes(s: &str) -> String {
431 let ansi_regex = Regex::new(r"\x1b\[([0-9]{1,2}(;[0-9]{0,2})*)m").unwrap();
432 ansi_regex.replace_all(s, "").to_string()
433}
434
435pub fn parse_and_compare_ast(first: &String, second: &String) -> Result<bool, syn::Error> {
437 let first_ast: File = parse_file(&first)?;
438 let second_ast: File = parse_file(&second)?;
439
440 let first_tokens: String = first_ast.into_token_stream().to_string();
444 let second_tokens: String = second_ast.into_token_stream().to_string();
445
446 Ok(first_tokens == second_tokens)
447}
448
449pub fn print_file_diff(expected_file_path: &str, output_file_path: &str) -> Result<(), std::io::Error> {
451 let expected_content = fs::read_to_string(expected_file_path)?;
452 let output_content = fs::read_to_string(output_file_path)?;
453
454 if expected_content != output_content {
455 println!("Differences found between expected and output:");
456 for diff in diff::lines(&expected_content, &output_content) {
457 match diff {
458 diff::Result::Left(l) => println!("{}", format!("- {}", l).red()), diff::Result::Right(r) => println!("{}", format!("+ {}", r).green()), diff::Result::Both(b, _) => println!("{}", format!(" {}", b)), }
462 }
463 } else {
464 println!("{}", "No differences found.".green());
465 }
466
467 Ok(())
468}