diff --git a/README.md b/README.md index 2eb3915..0310346 100644 --- a/README.md +++ b/README.md @@ -25,7 +25,19 @@ Navigate to your Rust project folder containing a `Cargo.toml` file. Then run `c [toolchain override shorthand syntax]: https://rust-lang.github.io/rustup/overrides.html#toolchain-override-shorthand ```shell -cargo +nightly-2024-06-30 rap # ... rest of options of cargo-rap +cargo rap [rap options] -- [cargo check options] + +where `-- [cargo check options]` is optional, and if specified, they are passed to cargo check. +``` + +Alternatively, you can switch to the pinned toolchain ahead of time: + +```rust +# set up rap's toolchain as default +rustup default nightly-2024-06-30 + +# run cargo rap without +toolchain syntax any more +cargo rap [rap options] -- [cargo check options] ``` Check out supported options with `-help`: diff --git a/rap/Cargo.lock b/rap/Cargo.lock index 72e1ab2..9c7311f 100644 --- a/rap/Cargo.lock +++ b/rap/Cargo.lock @@ -94,37 +94,6 @@ version = "3.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f30e7476521f6f8af1a1c4c0b8cc94f0bee37d91763d0ca2665f299b6cd8aec" -[[package]] -name = "camino" -version = "1.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c59e92b5a388f549b863a7bea62612c09f24c8393560709a54558a9abdfb3b9c" -dependencies = [ - "serde", -] - -[[package]] -name = "cargo-platform" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2cfa25e60aea747ec7e1124f238816749faa93759c6ff5b31f1ccdda137f4479" -dependencies = [ - "serde", -] - -[[package]] -name = "cargo_metadata" -version = "0.14.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4acbb09d9ee8e23699b9634375c72795d095bf268439da88562cf9b501f181fa" -dependencies = [ - "camino", - "cargo-platform", - "semver", - "serde", - "serde_json", -] - [[package]] name = "cc" version = "1.0.83" @@ -403,13 +372,11 @@ dependencies = [ name = "rap" version = "1.0.0" dependencies = [ - "cargo_metadata", "chrono", "colorful", "fern", "log", "rustc-demangle", - "rustc_version", "serde_json", "snafu", "wait-timeout", @@ -457,30 +424,12 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" -[[package]] -name = "rustc_version" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" -dependencies = [ - "semver", -] - [[package]] name = "ryu" version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741" -[[package]] -name = "semver" -version = "1.0.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad977052201c6de01a8ef2aa3378c4bd23217a056337d1d6da40468d267a4fb0" -dependencies = [ - "serde", -] - [[package]] name = "serde" version = "1.0.188" diff --git a/rap/Cargo.toml b/rap/Cargo.toml index 57dd154..9c541c3 100644 --- a/rap/Cargo.toml +++ b/rap/Cargo.toml @@ -17,9 +17,6 @@ name = "cargo-rap" name = "rap" [dependencies] -#lazy_static = "1.4" -rustc_version = "0.4.0" -cargo_metadata = "0.14.1" snafu = "0.7.0" chrono = "0.4.19" serde_json = "1.0.72" diff --git a/rap/src/bin/cargo-rap.rs b/rap/src/bin/cargo-rap.rs deleted file mode 100644 index 57f0153..0000000 --- a/rap/src/bin/cargo-rap.rs +++ /dev/null @@ -1,389 +0,0 @@ -/* - This is a cargo program to start RAP. - The file references the cargo file for Miri: https://github.com/rust-lang/miri/blob/master/cargo-miri/src/main.rs -*/ -use cargo_metadata::{Metadata, MetadataCommand}; -use rap::utils::log::{init_log, rap_error_and_exit}; -use rap::{rap_debug, rap_error, rap_info}; -use rustc_version::VersionMeta; -use std::env; -use std::fmt::{Display, Formatter}; -use std::iter::TakeWhile; -use std::path::{Path, PathBuf}; -use std::process::{self, Command}; -use std::time::Duration; -use wait_timeout::ChildExt; - -const RAP_HELP: &str = r#" -Usage: - cargo rap [options...] - -Use-After-Free/double free detection. - -F or -uaf command: "cargo rap -uaf" - -Memory leakage detection. - -M or -mleak command: "cargo rap -mleak" - -Unsafe code tracing - -UI or -uig generate unsafe code isolation graphs - -Dataflow tracing - -dataflow generate dataflow graphs - -General command: - -H or -help: show help information - -V or -version: show the version of RAP - -Debugging options: - -mir print the MIR of each function -"#; - -const RAP_VERSION: &str = r#" -rap version 0.1 -released at 2024-07-23 -developped by artisan-lab @ Fudan university -"#; - -/// Yields all values of command line flag `name`. -struct ArgFlagValueIter<'a> { - args: TakeWhile bool>, - name: &'a str, -} - -impl<'a> ArgFlagValueIter<'a> { - fn new(name: &'a str) -> Self { - Self { - args: env::args().take_while(|val| val != "--"), - name, - } - } -} - -impl Iterator for ArgFlagValueIter<'_> { - type Item = String; - - fn next(&mut self) -> Option { - loop { - let arg = self.args.next()?; - if !arg.starts_with(self.name) { - continue; - } - // Strip leading `name`. - let suffix = &arg[self.name.len()..]; - if suffix.is_empty() { - // This argument is exactly `name`; the next one is the value. - return self.args.next(); - } else if suffix.starts_with('=') { - // This argument is `name=value`; get the value. - // Strip leading `=`. - return Some(suffix[1..].to_owned()); - } - } - } -} - -fn get_arg_flag_value(name: &str) -> Option { - ArgFlagValueIter::new(name).next() -} - -fn find_rap() -> PathBuf { - let mut path = env::current_exe().expect("Current executable path invalid."); - path.set_file_name("rap"); - path -} - -fn version_info() -> VersionMeta { - let rap = Command::new(find_rap()); - VersionMeta::for_command(rap).expect("Failed to determine underlying rustc version of rap.") -} - -/* - The function finds a package under the current directory. -*/ -fn find_targets(metadata: &mut Metadata) -> Vec { - rap_info!("Search local targets for analysis."); - let current_dir = env::current_dir(); - let current_dir = current_dir.as_ref().expect("Cannot read current dir."); - let mut pkg_iter = metadata.packages.iter().filter(|package| { - let package_dir = Path::new(&package.manifest_path) - .parent() - .expect("Failed to find parent directory."); - rap_debug!("Package_dir: {:?}.", package_dir); - //FIXME: do we need to handle sub directories? - package_dir == current_dir || package_dir.starts_with(¤t_dir.to_str().unwrap()) - }); - let mut targets = Vec::new(); - while let Some(pkg) = pkg_iter.next() { - rap_info!("Find a new pakage: {:?}.", pkg.name); - let mut pkg_targets: Vec<_> = pkg.targets.clone().into_iter().collect(); - // Ensure `lib` is compiled before `bin` - pkg_targets.sort_by_key(|target| TargetKind::from(target) as u8); - targets.extend(pkg_targets); - } - targets -} - -fn is_identified_target(target: &cargo_metadata::Target, cmd: &mut Command) -> bool { - match TargetKind::from(target) { - TargetKind::Library => { - cmd.arg("--lib"); - true - } - TargetKind::Bin => { - cmd.arg("--bin").arg(&target.name); - true - } - TargetKind::Unspecified => false, - } -} - -fn run_cmd(mut cmd: Command) { - rap_debug!("Command is: {:?}.", cmd); - match cmd.status() { - Ok(status) => { - if !status.success() { - process::exit(status.code().unwrap()); - } - } - Err(err) => panic!("Error in running {:?} {}.", cmd, err), - } -} - -fn cleanup() { - let mut cmd = Command::new("cargo"); - cmd.arg("clean"); - run_cmd(cmd); - rap_info!("Execute cargo clean."); -} - -fn phase_cargo_rap() { - rap_info!("Start cargo-rap"); - let mut args = env::args().skip(2); // here we skip two tokens: cargo rap - let Some(arg) = args.next() else { - rap_error!("Expect command: e.g., `cargo rap -help`."); - return; - }; - match arg.as_str() { - "-V" | "-version" => { - rap_info!("{}", RAP_VERSION); - return; - } - "-H" | "-help" | "--help" => { - rap_info!("{}", RAP_HELP); - return; - } - _ => {} - } - cleanup(); // clean up the directory before building. - - let cmd = MetadataCommand::new(); - rap_debug!("Please run `cargo metadata` if this step takes too long"); - let mut metadata = match cmd.exec() { - // execute command: `cargo metadata' - Ok(metadata) => metadata, - Err(e) => rap_error_and_exit(format!("Cannot obtain cargo metadata: {}.", e)), - }; - - let targets = find_targets(&mut metadata); - for target in targets { - /*Here we prepare the cargo command as cargo check, which is similar to build, but much faster*/ - let mut cmd = Command::new("cargo"); - cmd.arg("check"); - - /* We only process bin and lib targets, and ignore others */ - if !is_identified_target(&target, &mut cmd) { - rap_debug!("Ignore the target because it is neither bin or lib."); - continue; - } - - /* set the target as a filter for phase_rustc_rap */ - let host = version_info().host; - if get_arg_flag_value("--target").is_none() { - cmd.arg("--target"); - cmd.arg(&host); - } - - // Serialize the remaining args into a special environment variable. - // This will be read by `phase_rustc_rap` when we go to invoke - // our actual target crate (the binary or the test we are running). - - let args = env::args().skip(2); - let args_vec: Vec = args.collect(); - cmd.env( - "RAP_ARGS", - serde_json::to_string(&args_vec).expect("Failed to serialize args."), - ); - - // Invoke actual cargo for the job, but with different flags. - let cargo_rap_path = env::current_exe().expect("Current executable path is invalid."); - cmd.env("RUSTC_WRAPPER", &cargo_rap_path); - - rap_debug!("Command is: {:?}.", cmd); - rap_info!( - "Running rap for target {}:{}", - TargetKind::from(&target), - &target.name - ); - - let mut child = cmd.spawn().expect("Could not run cargo check."); - match child - .wait_timeout(Duration::from_secs(60 * 60)) // 1 hour timeout - .expect("Failed to wait for subprocess.") - { - Some(status) => { - if !status.success() { - rap_error_and_exit("Finished with non-zero exit code."); - } - } - None => { - child.kill().expect("Failed to kill subprocess."); - child.wait().expect("Failed to wait for subprocess."); - rap_error_and_exit("Process killed due to timeout."); - } - }; - } -} - -fn phase_rustc_wrapper() { - rap_debug!("Launch cargo-rap again triggered by cargo check."); - fn is_target_crate() -> bool { - get_arg_flag_value("--target").is_some() - } - - // Determines if we are being invoked to build crate for local crate. - // Cargo passes the file name as a relative address when building the local crate, - fn is_current_compile_crate() -> bool { - fn find_arg_with_rs_suffix() -> Option { - let mut args = env::args().take_while(|s| s != "--"); - args.find(|s| s.ends_with(".rs")) - } - let arg_path = match find_arg_with_rs_suffix() { - Some(path) => path, - None => return false, - }; - let entry_path: &Path = arg_path.as_ref(); - entry_path.is_relative() - } - - fn is_crate_type_lib() -> bool { - fn any_arg_flag(name: &str, mut check: F) -> bool - where - F: FnMut(&str) -> bool, - { - // Stop searching at `--`. - let mut args = std::env::args().take_while(|val| val != "--"); - loop { - let arg = match args.next() { - Some(arg) => arg, - None => return false, - }; - if !arg.starts_with(name) { - continue; - } - - // Strip leading `name`. - let suffix = &arg[name.len()..]; - let value = if suffix.is_empty() { - // This argument is exactly `name`; the next one is the value. - match args.next() { - Some(arg) => arg, - None => return false, - } - } else if suffix.starts_with('=') { - // This argument is `name=value`; get the value. - // Strip leading `=`. - suffix[1..].to_owned() - } else { - return false; - }; - - if check(&value) { - return true; - } - } - } - - any_arg_flag("--crate--type", TargetKind::is_lib_str) - } - - let is_direct = is_current_compile_crate() && is_target_crate(); - if is_direct { - let mut cmd = Command::new(find_rap()); - cmd.args(env::args().skip(2)); - let magic = env::var("RAP_ARGS").expect("Missing RAP_ARGS."); - let rap_args: Vec = - serde_json::from_str(&magic).expect("Failed to deserialize RAP_ARGS."); - cmd.args(rap_args); - run_cmd(cmd); - } - if !is_direct || is_crate_type_lib() { - let mut cmd = Command::new("rustc"); - cmd.args(env::args().skip(2)); - run_cmd(cmd); - }; -} - -#[repr(u8)] -enum TargetKind { - Library, - Bin, - Unspecified, -} - -impl Display for TargetKind { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - write!( - f, - "{}", - match self { - TargetKind::Library => "lib", - TargetKind::Bin => "bin", - TargetKind::Unspecified => "unspecified", - } - ) - } -} - -impl From<&cargo_metadata::Target> for TargetKind { - fn from(target: &cargo_metadata::Target) -> Self { - if target - .kind - .iter() - .any(|s| s == "lib" || s == "rlib" || s == "staticlib") - { - TargetKind::Library - } else if target.kind.iter().any(|s| s == "bin") { - TargetKind::Bin - } else { - TargetKind::Unspecified - } - } -} - -impl TargetKind { - fn is_lib_str(s: &str) -> bool { - s == "lib" || s == "rlib" || s == "staticlib" - } -} - -fn main() { - /* This function will be enteredd twice: - 1. When we run `cargo rap ...`, cargo dispatches the execution to cargo-rap. - In this step, we set RUSTC_WRAPPER to cargo-rap, and execute `cargo check ...` command; - 2. Cargo check actually triggers `path/cargo-rap path/rustc` according to RUSTC_WRAPPER. - Because RUSTC_WRAPPER is defined, Cargo calls the command: `$RUSTC_WRAPPER path/rustc ...` - */ - - // Init the log_system - init_log().expect("Failed to init log."); - rap_debug!("Enter cargo-rap; Received args: {:?}", env::args()); - - let first_arg = env::args().nth(1); - match first_arg.unwrap() { - s if s.ends_with("rap") => phase_cargo_rap(), - s if s.ends_with("rustc") => phase_rustc_wrapper(), - _ => { - rap_error_and_exit("rap must be called with either `rap` or `rustc` as first argument.") - } - } -} diff --git a/rap/src/bin/cargo-rap/args.rs b/rap/src/bin/cargo-rap/args.rs new file mode 100644 index 0000000..c319255 --- /dev/null +++ b/rap/src/bin/cargo-rap/args.rs @@ -0,0 +1,76 @@ +use std::{ + env, + path::{Path, PathBuf}, + sync::LazyLock, +}; + +struct Arguments { + /// a collection of `std::env::args()` + args: Vec, + /// options as first half before -- in args + args_group1: Vec, + /// options as second half after -- in args + args_group2: Vec, + current_exe_path: PathBuf, +} + +impl Arguments { + fn new() -> Self { + let args: Vec<_> = env::args().collect(); + let path = env::current_exe().expect("Current executable path invalid."); + rap_debug!("Current exe: {path:?}\tReceived args: {args:?}"); + let [args_group1, args_group2] = split_args_by_double_dash(&args); + Arguments { + args, + args_group1, + args_group2, + current_exe_path: path, + } + } + + // In rustc phase: + // Determines if we are being invoked to build crate for local crate. + // Cargo passes the file name as a relative address when building the local crate, + fn is_current_compile_crate(&self) -> bool { + let mut args = self.args_group1.iter(); + let entry_path = match args.find(|s| s.ends_with(".rs")) { + Some(path) => Path::new(path), + None => return false, + }; + entry_path.is_relative() + } +} + +fn split_args_by_double_dash(args: &[String]) -> [Vec; 2] { + let mut args = args.iter().skip(2).map(|arg| arg.to_owned()); + let rap_args = args.by_ref().take_while(|arg| *arg != "--").collect(); + let cargo_args = args.collect(); + [rap_args, cargo_args] +} + +static ARGS: LazyLock = LazyLock::new(Arguments::new); + +/// `cargo rap [rap options] -- [cargo check options]` +/// +/// Options before the first `--` are arguments forwarding to rap. +/// Stuff all after the first `--` are arguments forwarding to cargo check. +pub fn rap_and_cargo_args() -> [&'static [String]; 2] { + [&ARGS.args_group1, &ARGS.args_group2] +} + +/// If a crate being compiled is local in rustc phase. +pub fn is_current_compile_crate() -> bool { + ARGS.is_current_compile_crate() +} + +pub fn get_arg(pos: usize) -> Option<&'static str> { + ARGS.args.get(pos).map(|x| x.as_str()) +} + +pub fn skip2() -> &'static [String] { + ARGS.args.get(2..).unwrap_or(&[]) +} + +pub fn current_exe_path() -> &'static Path { + &ARGS.current_exe_path +} diff --git a/rap/src/bin/cargo-rap/help.rs b/rap/src/bin/cargo-rap/help.rs new file mode 100644 index 0000000..27418c6 --- /dev/null +++ b/rap/src/bin/cargo-rap/help.rs @@ -0,0 +1,43 @@ +pub const RAP_HELP: &str = r#" +Usage: + cargo rap [rap options] -- [cargo check options] + +Rap Options: + +Use-After-Free/double free detection. + -F or -uaf command: "cargo rap -uaf" + +Memory leakage detection. + -M or -mleak command: "cargo rap -mleak" + +Unsafe code tracing + -UI or -uig generate unsafe code isolation graphs + +Dataflow tracing + -dataflow generate dataflow graphs + +General command: + -H or -help: show help information + -V or -version: show the version of RAP + +Debugging options: + -mir print the MIR of each function + +NOTE: multiple detections can be processed in single run by +appending the options to the arguments. Like `cargo rap -F -M` +will perform two kinds of detection in a row. + +e.g. +1. detect use-after-free and memory leak for a riscv target: + cargo rap -F -M -- --target riscv64gc-unknown-none-elf +2. detect use-after-free and memory leak for tests: + cargo rap -F -M -- --tests +3. detect use-after-free and memory leak for all members: + cargo rap -F -M -- --workspace +"#; + +pub const RAP_VERSION: &str = r#" +rap version 0.1 +released at 2024-07-23 +developped by artisan-lab @ Fudan university +"#; diff --git a/rap/src/bin/cargo-rap/main.rs b/rap/src/bin/cargo-rap/main.rs new file mode 100644 index 0000000..1c82f88 --- /dev/null +++ b/rap/src/bin/cargo-rap/main.rs @@ -0,0 +1,75 @@ +/* + This is a cargo program to start RAP. + The file references the cargo file for Miri: https://github.com/rust-lang/miri/blob/master/cargo-miri/src/main.rs +*/ + +#[macro_use] +extern crate rap; + +use rap::utils::log::{init_log, rap_error_and_exit}; + +mod args; +mod help; + +mod utils; +use crate::utils::*; + +mod target; +use target::*; + +fn phase_cargo_rap() { + rap_info!("Start cargo-rap"); + + // here we skip two args: cargo rap + let Some(arg) = args::get_arg(2) else { + rap_error!("Expect command: e.g., `cargo rap -help`."); + return; + }; + match arg { + "-V" | "-version" => { + rap_info!("{}", help::RAP_VERSION); + return; + } + "-H" | "-help" | "--help" => { + rap_info!("{}", help::RAP_HELP); + return; + } + _ => {} + } + + run_cargo_check(); +} + +fn phase_rustc_wrapper() { + rap_debug!("Launch cargo-rap again triggered by cargo check."); + + let is_direct = args::is_current_compile_crate(); + // rap only checks local crates + if is_direct { + run_rap(); + return; + } + + // for dependencies, run rustc as usual + run_rustc(); +} + +fn main() { + /* This function will be enteredd twice: + 1. When we run `cargo rap ...`, cargo dispatches the execution to cargo-rap. + In this step, we set RUSTC_WRAPPER to cargo-rap, and execute `cargo check ...` command; + 2. Cargo check actually triggers `path/cargo-rap path/rustc` according to RUSTC_WRAPPER. + Because RUSTC_WRAPPER is defined, Cargo calls the command: `$RUSTC_WRAPPER path/rustc ...` + */ + + // Init the log_system + init_log().expect("Failed to init log."); + + match args::get_arg(1).unwrap() { + s if s.ends_with("rap") => phase_cargo_rap(), + s if s.ends_with("rustc") => phase_rustc_wrapper(), + _ => { + rap_error_and_exit("rap must be called with either `rap` or `rustc` as first argument.") + } + } +} diff --git a/rap/src/bin/cargo-rap/target.rs b/rap/src/bin/cargo-rap/target.rs new file mode 100644 index 0000000..1d7100e --- /dev/null +++ b/rap/src/bin/cargo-rap/target.rs @@ -0,0 +1,48 @@ +use crate::args; +use rap::utils::log::rap_error_and_exit; +use std::{process::Command, time::Duration}; +use wait_timeout::ChildExt; + +pub fn run_cargo_check() { + let [rap_args, cargo_args] = crate::args::rap_and_cargo_args(); + rap_debug!("rap_args={rap_args:?}\tcargo_args={cargo_args:?}"); + + /*Here we prepare the cargo command as cargo check, which is similar to build, but much faster*/ + let mut cmd = Command::new("cargo"); + cmd.arg("check"); + + /* set the target as a filter for phase_rustc_rap */ + cmd.args(cargo_args); + + // Serialize the remaining args into a special environment variable. + // This will be read by `phase_rustc_rap` when we go to invoke + // our actual target crate (the binary or the test we are running). + + cmd.env( + "RAP_ARGS", + serde_json::to_string(rap_args).expect("Failed to serialize args."), + ); + + // Invoke actual cargo for the job, but with different flags. + let cargo_rap_path = args::current_exe_path(); + cmd.env("RUSTC_WRAPPER", cargo_rap_path); + + rap_debug!("Command is: {:?}.", cmd); + + let mut child = cmd.spawn().expect("Could not run cargo check."); + match child + .wait_timeout(Duration::from_secs(60 * 60)) // 1 hour timeout + .expect("Failed to wait for subprocess.") + { + Some(status) => { + if !status.success() { + rap_error_and_exit("Finished with non-zero exit code."); + } + } + None => { + child.kill().expect("Failed to kill subprocess."); + child.wait().expect("Failed to wait for subprocess."); + rap_error_and_exit("Process killed due to timeout."); + } + }; +} diff --git a/rap/src/bin/cargo-rap/utils.rs b/rap/src/bin/cargo-rap/utils.rs new file mode 100644 index 0000000..f6b1897 --- /dev/null +++ b/rap/src/bin/cargo-rap/utils.rs @@ -0,0 +1,40 @@ +use crate::args; +use std::{ + env, + path::PathBuf, + process::{self, Command}, +}; + +fn find_rap() -> PathBuf { + let mut path = args::current_exe_path().to_owned(); + path.set_file_name("rap"); + path +} + +pub fn run_cmd(mut cmd: Command) { + rap_debug!("Command is: {:?}.", cmd); + match cmd.status() { + Ok(status) => { + if !status.success() { + process::exit(status.code().unwrap()); + } + } + Err(err) => panic!("Error in running {:?} {}.", cmd, err), + } +} + +pub fn run_rustc() { + let mut cmd = Command::new("rustc"); + cmd.args(args::skip2()); + run_cmd(cmd); +} + +pub fn run_rap() { + let mut cmd = Command::new(find_rap()); + cmd.args(args::skip2()); + let magic = env::var("RAP_ARGS").expect("Missing RAP_ARGS."); + let rap_args: Vec = + serde_json::from_str(&magic).expect("Failed to deserialize RAP_ARGS."); + cmd.args(rap_args); + run_cmd(cmd); +} diff --git a/rap/src/lib.rs b/rap/src/lib.rs index b02113b..4c37a32 100644 --- a/rap/src/lib.rs +++ b/rap/src/lib.rs @@ -2,9 +2,11 @@ #![feature(control_flow_enum)] #![feature(box_patterns)] -pub mod analysis; +#[macro_use] pub mod utils; +pub mod analysis; + extern crate rustc_data_structures; extern crate rustc_driver; extern crate rustc_errors;