rem_utils/
lib.rs

1#![feature(box_patterns)]
2#![feature(never_type)]
3#![feature(iter_intersperse)]
4
5// extern crate rustc_driver;
6// pub extern crate rustc_lint;
7// pub extern crate rustc_span;
8pub 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
58////////////////////////////////////////////////////////////////////////////////////////////////////
59////////////////////////////////        COMPILE        /////////////////////////////////////////////
60////////////////////////////////////////////////////////////////////////////////////////////////////
61pub 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
296////////////////////////////////////////////////////////////////////////////////////////////////////
297////////////////////////////////          MISC          ////////////////////////////////////////////
298////////////////////////////////////////////////////////////////////////////////////////////////////
299
300/// Resolves the path to the Charon Binary.
301/// - If the user has provided a path via the CLI, use that.
302/// - Otherwise, use the path speficied in the environment variable `CHARON_PATH`.
303/// - If neither of the above are provided, use the path specified in the
304///   `config.toml` file.
305/// - If none of the above are provided, use the default path, `./charon`
306pub fn resolve_charon_path(cli_charon_path: &Option<PathBuf>) -> Result<PathBuf, Box<dyn Error>> {
307    // 1. Check if the user has provided a path via the CLI
308    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    // 2. Check if the user has provided a path via the environment variable
314    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    // 3. Check if the user has provided a path via the config file
320    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    // 4. Use the default path (assumed the binary is in the current directory)
326    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
339/// Resolves the path to the Aeneas Binary.
340/// - If the user has provided a path via the CLI, use that.
341/// - Otherwise, use the path speficied in the environment variable `AENEAS_PATH`.
342/// - If neither of the above are provided, use the path specified in the
343///  `config.toml` file.
344/// - If none of the above are provided, use the default path, `./aeneas`
345pub fn resolve_aeneas_path(cli_aneas_path: &Option<PathBuf>) -> Result<PathBuf, Box<dyn Error>> {
346    // 1. Check if the user has provided a path via the CLI
347    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    // 2. Check if the user has provided a path via the environment variable
353    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    // 3. Check if the user has provided a path via the config file
359    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    // 4. Use the default path (assumed the binary is in the current directory)
365    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
428/// Strips ANSI color codes from a string using a regex
429/// This is useful for comparing strings with ANSI color codes to strings without
430pub 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
435/// Parses two strings into ASTs and compares them for equality
436pub 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    // Convert both ASTs back into token stres for comparison
441    // FIXME this is sometimes buggy and is convinced that the two files are
442    // different when they are infact the same
443    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
449/// Prints the differences between two files to stdout
450pub 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()), // Expected but not in output
459                diff::Result::Right(r) => println!("{}", format!("+ {}", r).green()), // In output but not in expected
460                diff::Result::Both(b, _) => println!("{}", format!("  {}", b)), // Same in both
461            }
462        }
463    } else {
464        println!("{}", "No differences found.".green());
465    }
466
467    Ok(())
468}