diff --git a/.gitignore b/.gitignore index 9d1792b8..86450e18 100644 --- a/.gitignore +++ b/.gitignore @@ -20,5 +20,8 @@ Cargo.lock tests/apps/*.wat output/ +# Ignore the wasm playground files +wasm_playground/*.wasm + # Ignore mac files .DS_Store diff --git a/Cargo.toml b/Cargo.toml index b2500262..cd040def 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,12 +7,13 @@ edition = "2021" doc = false name = "whamm" path = "src/main.rs" -required-features = ["exe"] +#required-features = ["exe"] [dependencies] failure = "0.1.5" glob = "0.3.1" lazy_static = "1.4.0" +convert_case = "0.6.0" regex = "1.10.4" walrus = "0.20.3" @@ -30,11 +31,12 @@ graphviz-rust = "0.9.0" project-root = "0.2.2" opener = { version = "0.7.0", default-features = false } -[dependencies.clap] -optional = true -version = "3.2.23" -features = ["derive"] +# CLI +clap = { version = "4.5.4", features = ["derive", "cargo", "env"] } +clap_complete = "4.5.2" -[features] -default = ["exe"] -exe = ["clap"] +[build-dependencies] +clap = { version = "4.5.4", features = ["derive", "cargo", "env"] } +clap_complete = "4.5.2" +clap_mangen = "0.2.20" +project-root = "0.2.2" diff --git a/build.rs b/build.rs new file mode 100644 index 00000000..ed87a004 --- /dev/null +++ b/build.rs @@ -0,0 +1,57 @@ +use std::fs::File; +use std::io::Error; +use std::path::{Path, PathBuf}; +use std::process::exit; +use clap::CommandFactory; +use clap_mangen::Man; +use project_root::get_project_root; + +include!("src/cli.rs"); + +fn build_man(out_dir: &Path) -> Result<(), Error> { + let app = WhammCli::command(); + + let file = Path::new(&out_dir).join("example.1"); + let mut file = File::create(&file)?; + + Man::new(app).render(&mut file)?; + + Ok(()) +} + +fn main() -> Result<(), Box> { + println!("cargo:rerun-if-changed=src/cli.rs"); + println!("cargo:rerun-if-changed=man"); + + // Create `target/assets/` folder. + let mut path = match get_pb(&PathBuf::from("target")) { + Ok(pb) => { + pb + } + Err(_) => { + exit(1) + } + }; + path.push("assets"); + std::fs::create_dir_all(&path).unwrap(); + + // build_shell_completion(&path)?; + build_man(&path)?; + + Ok(()) +} + +fn get_pb(file_pb: &PathBuf) -> Result { + if file_pb.is_relative() { + match get_project_root() { + Ok(r) => { + let mut full_path = r.clone(); + full_path.push(file_pb); + Ok(full_path) + } + Err(e) => Err(format!("the root folder does not exist: {:?}", e)), + } + } else { + Ok(file_pb.clone()) + } +} \ No newline at end of file diff --git a/src/behavior/builder_visitor.rs b/src/behavior/builder_visitor.rs index 85547c66..7a345b8d 100644 --- a/src/behavior/builder_visitor.rs +++ b/src/behavior/builder_visitor.rs @@ -9,7 +9,7 @@ use regex::Regex; use crate::behavior::tree::ParamActionType; use crate::behavior::tree::DecoratorType::{HasAltCall, PredIs}; use crate::common::error::ErrorGen; -use crate::parser::types::Global; +use crate::parser::types::{Global, ProvidedFunctionality}; pub type SimpleAST = HashMap>>>>; @@ -114,6 +114,23 @@ impl BehaviorTreeBuilder<'_> { } } + fn visit_provided_globals(&mut self, globals: &HashMap) { + if globals.len() > 0 { + self.tree.sequence(self.err); + + // visit globals + for (_name, (.., global)) in globals.iter() { + if global.is_comp_provided { + if let Expr::VarId { name, ..} = &global.var_name { + self.tree.define(self.context_name.clone(), + name.clone(), self.err); + } + } + } + self.tree.exit_sequence(self.err); + } + } + fn is_in_context(&self, pattern: &str) -> bool { let regex = Regex::new(pattern).unwrap(); if let Some(_caps) = regex.captures(self.context_name.as_str()) { @@ -282,7 +299,7 @@ impl WhammVisitor<()> for BehaviorTreeBuilder<'_> { // .enter_scope(self.context_name.clone()); // visit globals - self.visit_globals(&whamm.globals); + self.visit_provided_globals(&whamm.globals); // visit whammys whamm.whammys.iter().for_each(| whammy | self.visit_whammy(whammy)); @@ -323,7 +340,7 @@ impl WhammVisitor<()> for BehaviorTreeBuilder<'_> { self.tree.enter_scope(self.context_name.clone(), provider.name.clone(), self.err); // visit globals - self.visit_globals(&provider.globals); + self.visit_provided_globals(&provider.globals); provider.packages.iter().for_each(| (_name, package) | { self.visit_package(package) diff --git a/src/cli.rs b/src/cli.rs new file mode 100644 index 00000000..e31a37cc --- /dev/null +++ b/src/cli.rs @@ -0,0 +1,96 @@ +use clap::{Args, Parser, Subcommand}; + +/// `whamm` instruments a Wasm application with the Probes defined in the specified Whammy. +#[derive(Debug, Parser)] +#[clap(author, version, about, long_about = None)] +pub struct WhammCli { + // #[clap(flatten)] + // global_opts: GlobalOpts, + + #[command(subcommand)] + pub(crate) command: Cmd +} + +#[derive(Debug, Subcommand)] +pub(crate) enum Cmd { + // /// Generate shell completion + // Completion { + // /// Shell to generate completion for + // #[arg(arg_enum)] + // shell: Shell, + // }, + /// To provide the globals and functions available for the given probe specification. + /// To use this option, simply follow the command with a full or partial specification + /// (use pattern matching to see what would be triggered). + Info { + #[arg(short, long, value_parser)] + spec: String, + + /// Show the globals in-scope when using the probe specification. + #[arg(long, short, action, default_value = "false")] + globals: bool, + + /// Show the functions in-scope when using the probe specification. + #[arg(long, short, action, default_value = "false")] + functions: bool, + }, + + /// To instrument a Wasm application. + Instr(InstrArgs), + + /// To visualize the relationship between various structures in the module and its instructions + VisWasm { + /// The path to the Wasm module we want to visualize. + #[clap(short, long, value_parser)] + wasm: String, + + /// The path to output the visualization to. + #[clap(short, long, value_parser, default_value = "output/wasm.dot")] + output_path: String, + }, + + /// To visualize the generated behavior tree from the specified `whammy` + VisWhammy { + /// The path to the `whammy` file we want to visualize. + #[clap(short, long, value_parser)] + whammy: String, + + /// Whether to run the verifier on the specified whammy + #[clap(long, short, action, default_value = "false")] // TODO -- change this default value to true when I have this implemented + run_verifier: bool, + + /// The path to output the visualization to. + #[clap(short, long, value_parser, default_value = "output/vis.svg")] + output_path: String, + } +} + +// #[derive(Debug, Args)] +// struct GlobalOpts { +// // (not needed yet) +// } + +#[derive(Debug, Args)] +pub struct InstrArgs { + /// The path to the application's Wasm module we want to instrument. + #[arg(short, long, value_parser)] + pub app: String, + /// The path to the Whammy containing the instrumentation Probe definitions. + #[arg(short, long, value_parser)] + pub whammy: String, + /// The path that the instrumented version of the Wasm app should be output to. + #[arg(short, long, value_parser, default_value = "./output/output.wasm")] + pub output_path: String, + + /// Whether to emit Virgil code as the instrumentation code + #[arg(short, long, action, default_value = "false")] + pub virgil: bool, + + /// Whether to run the verifier on the specified whammy + #[arg(long, short, action, default_value = "false")] // TODO -- change this default value to true when I have this implemented + pub run_verifier: bool +} + +// pub fn print_completion(gen: G, app: &mut App) { +// generate(gen, app, app.get_name().to_string(), &mut io::stdout()); +// } \ No newline at end of file diff --git a/src/common.rs b/src/common.rs index a281f3e6..525f50a0 100644 --- a/src/common.rs +++ b/src/common.rs @@ -1 +1,2 @@ -pub mod error; \ No newline at end of file +pub mod error; +pub mod terminal; \ No newline at end of file diff --git a/src/common/error.rs b/src/common/error.rs index 97abb475..fb64b8ce 100644 --- a/src/common/error.rs +++ b/src/common/error.rs @@ -1,11 +1,11 @@ use std::{cmp, mem}; use std::borrow::Cow; -use std::io::Write; +use termcolor::{Buffer, BufferWriter, ColorChoice, WriteColor}; use std::process::exit; +use crate::common::terminal::{blue, red, white}; use log::error; use pest::error::{Error, LineColLocation}; use pest::error::ErrorVariant::ParsingError; -use termcolor::{Buffer, BufferWriter, Color, ColorChoice, ColorSpec, WriteColor}; use crate::parser::types::Rule; const ERR_UNDERLINE_CHAR: char = '^'; @@ -34,7 +34,7 @@ impl ErrorGen { } pub fn add_error(&mut self, error: WhammError) { - let fatal = error.fatal; + let fatal = error.fatal.clone(); self.errors.push(error); self.inc_errors(); @@ -64,7 +64,7 @@ impl ErrorGen { } pub fn fatal_report(&mut self, context: &str) { - if !self.has_errors { + if !&self.has_errors { return; } self.report(); @@ -90,27 +90,40 @@ impl ErrorGen { // == Error Generators == // ====================== - pub fn get_parse_error(fatal: bool, message: Option, line_col: LineColLocation, + pub fn get_parse_error(fatal: bool, message: Option, line_col: Option, positives: Vec, negatives: Vec) -> WhammError { - WhammError { - fatal, - ty: ErrorType::ParsingError { - positives, - negatives, - message: message.clone() - }, - err_loc: Some(CodeLocation { - is_err: true, - message, - line_col, - line_str: None, - line2_str: None - }), - info_loc: None + if let Some(line_col) = line_col { + WhammError { + fatal, + ty: ErrorType::ParsingError { + positives, + negatives, + message: message.clone() + }, + err_loc: Some(CodeLocation { + is_err: true, + message, + line_col, + line_str: None, + line2_str: None + }), + info_loc: None + } + } else { + WhammError { + fatal, + ty: ErrorType::ParsingError { + positives, + negatives, + message: message.clone() + }, + err_loc: None, + info_loc: None + } } } - pub fn parse_error(&mut self, fatal: bool, message: Option, line_col: LineColLocation, + pub fn parse_error(&mut self, fatal: bool, message: Option, line_col: Option, positives: Vec, negatives: Vec) { let err = Self::get_parse_error(fatal, message, line_col, positives, negatives); self.add_error(err); @@ -297,7 +310,7 @@ impl CodeLocation { "".to_string() }; if let (LineColLocation::Span(_, (le, _)), Some(ref line2)) = (&self.line_col, &self.line2_str) { - let has_line_gap = *le - *ls > 1; + let has_line_gap = le - ls > 1; if has_line_gap { self.print_numbered_line(ls, line, spacing, buffer); @@ -323,16 +336,16 @@ impl CodeLocation { fn define_lines(&mut self, script: &String) { match &self.line_col { LineColLocation::Pos((line_no, ..)) => { - if let Some(script_line) = script.lines().nth(*line_no - 1) { + if let Some(script_line) = script.lines().nth(line_no - 1) { self.line_str = Some(script_line.to_string()); } } LineColLocation::Span((s0_line, ..), (s1_line, ..)) => { - if let Some(script_line) = script.lines().nth(*s0_line - 1) { + if let Some(script_line) = script.lines().nth(s0_line - 1) { self.line_str = Some(script_line.to_string()); } if s0_line != s1_line { - if let Some(script_line) = script.lines().nth(*s1_line - 1) { + if let Some(script_line) = script.lines().nth(s1_line - 1) { self.line2_str = Some(script_line.to_string()); } } @@ -342,34 +355,34 @@ impl CodeLocation { fn print_numbered_line(&self, l: &usize, line: &String, s: &String, buffer: &mut Buffer) { let w = s.len(); - blue(format!("{l:w$} | "), buffer); - white(format!("{line}\n"), buffer); + blue(false, format!("{l:w$} | "), buffer); + white(false, format!("{line}\n"), buffer); } fn print_line_start(&self, s: &String, buffer: &mut Buffer) { - blue(format!("{s} | "), buffer); + blue(false, format!("{s} | "), buffer); } fn print_err(&self, line: &str, s: &String, buffer: &mut Buffer) { self.print_line_start(s, buffer); - red(format!("{line}\n"), buffer); + red(false, format!("{line}\n"), buffer); } fn print_info(&self, line: &str, s: &String, buffer: &mut Buffer) { self.print_line_start(s, buffer); - blue(format!("{line}\n"), buffer); + blue(false, format!("{line}\n"), buffer); } fn print_norm(&self, line: &str, s: &String, buffer: &mut Buffer) { self.print_line_start(s, buffer); - white(format!("{line}\n"), buffer); + white(false, format!("{line}\n"), buffer); } fn underline(&self, start_col: &usize) -> String { let mut underline = String::new(); let mut start_col = start_col.clone(); - let end = match self.line_col { + let end = match &self.line_col { LineColLocation::Span(_, (_, mut end)) => { let inverted_cols = start_col > end; if inverted_cols { @@ -402,10 +415,10 @@ impl CodeLocation { INFO_UNDERLINE_CHAR }; - underline.push(u_char); - if end - start_col > 1 { - for _ in 2..(end - start_col) { - underline.push(u_char); + underline.push(u_char.clone()); + if end - &start_col > 1 { + for _ in 2..(&end - &start_col) { + underline.push(u_char.clone()); } underline.push(u_char); } @@ -434,7 +447,7 @@ pub struct WhammError { } impl WhammError { pub fn is_fatal(&self) -> bool { - self.fatal + self.fatal.clone() } /// report this error to the console, including color highlighting @@ -445,10 +458,8 @@ impl WhammError { let writer = BufferWriter::stderr(ColorChoice::Always); let mut buffer = writer.buffer(); - set_bold(true, &mut buffer); - red(format!("error[{}]", self.ty.name()), &mut buffer); - white(format!(": {}\n", message), &mut buffer); - set_bold(false, &mut buffer); + red(true, format!("error[{}]", self.ty.name()), &mut buffer); + white(true, format!(": {}\n", message), &mut buffer); if let Some(err_loc) = &mut self.err_loc { if err_loc.message.is_none() { @@ -457,14 +468,14 @@ impl WhammError { print_preamble(&err_loc.line_col, whammy_path, &spacing, &mut buffer); print_empty(&spacing, &mut buffer); - let err_start = match err_loc.line_col { + let err_start = match &err_loc.line_col { LineColLocation::Pos((line, _)) => line, LineColLocation::Span((start_line, _), ..) => { start_line } }; if let Some(info_loc) = &mut self.info_loc { - let info_start = match info_loc.line_col { + let info_start = match &info_loc.line_col { LineColLocation::Pos((line, _)) => line, LineColLocation::Span((start_line, _), ..) => { start_line @@ -487,8 +498,8 @@ impl WhammError { print_empty(&spacing, &mut buffer); } else { // This error isn't tied to a specific code location - blue(format!(" --> "), &mut buffer); - blue(format!("{whammy_path}\n\n"), &mut buffer); + blue(false, format!(" --> "), &mut buffer); + blue(false, format!("{whammy_path}\n\n"), &mut buffer); } writer.print(&buffer).expect("Uh oh, something went wrong while printing to terminal"); buffer.reset().expect("Uh oh, something went wrong while printing to terminal"); @@ -626,7 +637,7 @@ impl ErrorType { let non_separated = f(&rules[l - 1]); let separated = rules .iter() - .take(l - 1) + .take(l.clone() - 1) .map(f) .collect::>() .join(", "); @@ -636,46 +647,6 @@ impl ErrorType { } } -// =========================== -// = Terminal Printing Logic = -// =========================== - -fn set_bold(yes: bool, buffer: &mut Buffer) { - let write_err = "Uh oh, something went wrong while printing to terminal"; - buffer.set_color(ColorSpec::new().set_bold(yes)).expect(write_err); -} - -fn color(s: String, buffer: &mut Buffer, c: Color) { - let write_err = "Uh oh, something went wrong while printing to terminal"; - buffer.set_color(ColorSpec::new().set_fg(Some(c))).expect(write_err); - write!(buffer, "{}", s.as_str()).expect(write_err); -} - -// fn black(s: String, buffer: &mut Buffer) { -// color(s, buffer, Color::Black) -// } -fn blue(s: String, buffer: &mut Buffer) { - color(s, buffer, Color::Blue) -} -// fn cyan(s: String, buffer: &mut Buffer) { -// color(s, buffer, Color::Cyan) -// } -// fn green(s: String, buffer: &mut Buffer) { -// color(s, buffer, Color::Green) -// } -// fn magenta(s: String, buffer: &mut Buffer) { -// color(s, buffer, Color::Magenta) -// } -fn red(s: String, buffer: &mut Buffer) { - color(s, buffer, Color::Red) -} -fn white(s: String, buffer: &mut Buffer) { - color(s, buffer, Color::Rgb(193,193,193)) -} -// fn yellow(s: String, buffer: &mut Buffer) { -// color(s, buffer, Color::Yellow) -// } - fn print_preamble(line_col: &LineColLocation, whammy_path: &String, s: &String, buffer: &mut Buffer) { let (ls, c) = match line_col { @@ -683,17 +654,17 @@ fn print_preamble(line_col: &LineColLocation, whammy_path: &String, s: &String, LineColLocation::Span(start_line_col, _) => start_line_col }; - blue(format!("{s}--> "), buffer); - blue(format!("{whammy_path}:"), buffer); - blue(format!("{ls}:{c}\n"), buffer); + blue(false, format!("{s}--> "), buffer); + blue(false, format!("{whammy_path}:"), buffer); + blue(false, format!("{ls}:{c}\n"), buffer); } fn print_line(line: &str, is_err: bool, s: &String, buffer: &mut Buffer) { - blue(format!("{s} | "), buffer); + blue(false, format!("{s} | "), buffer); if is_err { - red(format!("{line}\n"), buffer); + red(false, format!("{line}\n"), buffer); } else { - white(format!("{line}\n"), buffer); + white(false, format!("{line}\n"), buffer); } } diff --git a/src/common/terminal.rs b/src/common/terminal.rs new file mode 100644 index 00000000..3dbc48eb --- /dev/null +++ b/src/common/terminal.rs @@ -0,0 +1,48 @@ +use std::io::Write; +use termcolor::{Buffer, Color, ColorSpec, WriteColor}; + +// =========================== +// = Terminal Printing Logic = +// =========================== + +const WRITE_ERR: &str = "Uh oh, something went wrong while printing to terminal"; + +pub fn color(s: String, buffer: &mut Buffer, bold: bool, italics: bool, c: Color) { + buffer.set_color(ColorSpec::new().set_bold(bold).set_italic(italics).set_fg(Some(c))).expect(WRITE_ERR); + write!(buffer, "{}", s.as_str()).expect(WRITE_ERR); +} + +pub fn black(bold: bool, s: String, buffer: &mut Buffer) { + color(s, buffer, bold, false, Color::Black) +} +pub fn blue(bold: bool, s: String, buffer: &mut Buffer) { + color(s, buffer, bold, false, Color::Blue) +} +pub fn cyan(bold: bool, s: String, buffer: &mut Buffer) { + color(s, buffer, bold, false, Color::Cyan) +} +pub fn green(bold: bool, s: String, buffer: &mut Buffer) { + color(s, buffer, bold, false, Color::Green) +} +pub fn magenta(bold: bool, s: String, buffer: &mut Buffer) { + color(s, buffer, bold, false, Color::Magenta) +} +pub fn magenta_italics(bold: bool, s: String, buffer: &mut Buffer) { + color(s, buffer, bold, true, Color::Magenta) +} +pub fn red(bold: bool, s: String, buffer: &mut Buffer) { + color(s, buffer, bold, false, Color::Red) +} +pub fn white(bold: bool, s: String, buffer: &mut Buffer) { + color(s, buffer, bold, false, Color::Rgb(193,193,193)) +} +pub fn white_italics(bold: bool, s: String, buffer: &mut Buffer) { + color(s, buffer, bold, true, Color::White) +} +pub fn yellow(bold: bool, s: String, buffer: &mut Buffer) { + color(s, buffer, bold, false, Color::Yellow) +} +pub fn long_line(buffer: &mut Buffer) { + let s = "--".repeat(50); + color(s, buffer, false, false, Color::White); +} \ No newline at end of file diff --git a/src/generator/init_generator.rs b/src/generator/init_generator.rs index 2f81d0cd..6e1bc4c1 100644 --- a/src/generator/init_generator.rs +++ b/src/generator/init_generator.rs @@ -6,7 +6,7 @@ use std::collections::HashMap; use log::{trace, warn}; use crate::common::error::ErrorGen; use crate::generator::emitters::Emitter; -use crate::parser::types::{DataType, Whammy, Whamm, WhammVisitorMut, Expr, Event, Package, Op, Probe, Provider, Statement, Value, Global}; +use crate::parser::types::{DataType, Whammy, Whamm, WhammVisitorMut, Expr, Event, Package, Op, Probe, Provider, Statement, Value, Global, ProvidedFunctionality}; /// Serves as the first phase of instrumenting a module by setting up /// the groundwork. @@ -31,7 +31,23 @@ impl InitGenerator<'_> { let mut is_success = true; for (name, global) in globals.iter() { // do not inject globals into Wasm that are used/defined by the compiler - if !global.is_comp_provided { + if !&global.is_comp_provided { + match self.emitter.emit_global(name.clone(), global.ty.clone(), &global.value) { + Err(e) => self.err.add_error(e), + Ok(res) => { + is_success &= res + } + } + } + } + + is_success + } + fn visit_provided_globals(&mut self, globals: &HashMap) -> bool { + let mut is_success = true; + for (name, (.., global)) in globals.iter() { + // do not inject globals into Wasm that are used/defined by the compiler + if !&global.is_comp_provided { match self.emitter.emit_global(name.clone(), global.ty.clone(), &global.value) { Err(e) => self.err.add_error(e), Ok(res) => { @@ -51,11 +67,11 @@ impl WhammVisitorMut for InitGenerator<'_> { let mut is_success = true; // visit fns - whamm.fns.iter_mut().for_each(| f | { + whamm.fns.iter_mut().for_each(| (.., f) | { is_success &= self.visit_fn(f); }); // inject globals - is_success &= self.visit_globals(&whamm.globals); + is_success &= self.visit_provided_globals(&whamm.globals); // visit whammys whamm.whammys.iter_mut().for_each(|whammy| { is_success &= self.visit_whammy(whammy); @@ -107,11 +123,11 @@ impl WhammVisitorMut for InitGenerator<'_> { let mut is_success = true; // visit fns - provider.fns.iter_mut().for_each(| f | { + provider.fns.iter_mut().for_each(| (.., f) | { is_success &= self.visit_fn(f); }); // inject globals - is_success &= self.visit_globals(&provider.globals); + is_success &= self.visit_provided_globals(&provider.globals); // visit the packages provider.packages.iter_mut().for_each(|(_name, package)| { is_success &= self.visit_package(package); @@ -137,11 +153,11 @@ impl WhammVisitorMut for InitGenerator<'_> { self.context_name += &format!(":{}", package.name.clone()); // visit fns - package.fns.iter_mut().for_each(| f | { + package.fns.iter_mut().for_each(| (.., f) | { is_success &= self.visit_fn(f); }); // inject globals - is_success &= self.visit_globals(&package.globals); + is_success &= self.visit_provided_globals(&package.globals); // visit the events package.events.iter_mut().for_each(|(_name, event)| { is_success &= self.visit_event(event); @@ -168,11 +184,11 @@ impl WhammVisitorMut for InitGenerator<'_> { let mut is_success = true; // visit fns - event.fns.iter_mut().for_each(| f | { + event.fns.iter_mut().for_each(| (.., f) | { is_success &= self.visit_fn(f); }); // inject globals - is_success &= self.visit_globals(&event.globals); + is_success &= self.visit_provided_globals(&event.globals); // 1. visit the BEFORE probes if let Some(probes) = event.probe_map.get_mut(&"before".to_string()) { @@ -219,11 +235,11 @@ impl WhammVisitorMut for InitGenerator<'_> { let mut is_success = true; // visit fns - probe.fns.iter_mut().for_each(| f | { + probe.fns.iter_mut().for_each(| (.., f) | { is_success &= self.visit_fn(f); }); // inject globals - is_success &= self.visit_globals(&probe.globals); + is_success &= self.visit_provided_globals(&probe.globals); trace!("Exiting: CodeGenerator::visit_probe"); match self.emitter.exit_scope() { diff --git a/src/generator/instr_generator.rs b/src/generator/instr_generator.rs index 60c3633c..c5f965a9 100644 --- a/src/generator/instr_generator.rs +++ b/src/generator/instr_generator.rs @@ -1,3 +1,4 @@ +use convert_case::{Case, Casing}; use log::warn; use crate::behavior::builder_visitor::SimpleAST; use crate::behavior::tree::{ActionType, ActionWithChildType, ArgActionType, BehaviorVisitor, DecoratorType, ParamActionType}; @@ -122,7 +123,7 @@ impl BehaviorVisitor for InstrGenerator<'_, '_> { if let Some(node) = self.tree.get_node(child.clone()) { child_is_success &= self.visit_node(node); } - if !child_is_success { + if !&child_is_success { // If the child was unsuccessful, don't execute the following children // and return `false` (failure) return child_is_success; @@ -297,11 +298,18 @@ impl BehaviorVisitor for InstrGenerator<'_, '_> { let mut first_instr = true; while first_instr || self.emitter.has_next_instr() { - if !first_instr { + if !&first_instr { self.emitter.next_instr(); } - let instr_ty = self.emitter.curr_instr_type(); + let instr_ty = match self.emitter.curr_instr_type().as_str() { + // Handle some special-cases + "V128Bitselect" => "v128_bitselect".to_string(), + "I8x16Swizzle" => "i8x16_swizzle".to_string(), + "I8x16Shuffle" => "i8x16_shuffle".to_string(), + other => other.to_case(Case::Snake) + }; + // is this an instruction of-interest? if let Some(globals) = events.get(&instr_ty) { // enter this event's scope diff --git a/src/main.rs b/src/main.rs index b68cf7a8..19630016 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,6 +1,7 @@ extern crate core; -use std::path::PathBuf; +use cli::{Cmd, WhammCli}; + use crate::parser::whamm_parser::*; use crate::behavior::builder_visitor::*; use crate::generator::emitters::{Emitter, WasmRewritingEmitter}; @@ -8,16 +9,18 @@ use crate::generator::init_generator::{InitGenerator}; use crate::generator::instr_generator::{InstrGenerator}; use crate::common::error::ErrorGen; +mod cli; pub mod parser; pub mod behavior; pub mod verifier; pub mod generator; pub mod common; -use clap::{Args, Parser, Subcommand}; +use clap::Parser; use graphviz_rust::exec_dot; use log::{info, error}; use std::process::exit; +use std::path::PathBuf; use graphviz_rust::cmd::{CommandArg, Format}; use project_root::get_project_root; use walrus::Module; @@ -34,75 +37,6 @@ fn setup_logger() { env_logger::init(); } -/// `whamm` instruments a Wasm application with the Probes defined in the specified Whammy. -#[derive(Debug, Parser)] -#[clap(author, version, about, long_about = None)] -pub struct WhammCli { - // #[clap(flatten)] - // global_opts: GlobalOpts, - - #[clap(subcommand)] - command: Command -} - -#[derive(Debug, Subcommand)] -enum Command { - /// To instrument a Wasm application. - Instr(InstrArgs), - - /// To visualize the relationship between various structures in the module and its instructions - VisWasm { - /// The path to the Wasm module we want to visualize. - #[clap(short, long, value_parser)] - wasm: String, - - /// The path to output the visualization to. - #[clap(short, long, value_parser, default_value = "output/wasm.dot")] - output_path: String, - }, - - /// To visualize the generated behavior tree from the specified `whammy` - VisWhammy { - /// The path to the `whammy` file we want to visualize. - #[clap(short, long, value_parser)] - whammy: String, - - /// Whether to run the verifier on the specified whammy - #[clap(long, short, action, default_value = "false")] // TODO -- change this default value to true when I have this implemented - run_verifier: bool, - - /// The path to output the visualization to. - #[clap(short, long, value_parser, default_value = "output/vis.svg")] - output_path: String, - } -} - -// #[derive(Debug, Args)] -// struct GlobalOpts { -// // (not needed yet) -// } - -#[derive(Debug, Args)] -struct InstrArgs { - /// The path to the application's Wasm module we want to instrument. - #[clap(short, long, value_parser)] - app: String, - /// The path to the Whammy containing the instrumentation Probe definitions. - #[clap(short, long, value_parser)] - whammy: String, - /// The path that the instrumented version of the Wasm app should be output to. - #[clap(short, long, value_parser, default_value = "./output/output.wasm")] - output_path: String, - - /// Whether to emit Virgil code as the instrumentation code - #[clap(short, long, action, default_value = "false")] - virgil: bool, - - /// Whether to run the verifier on the specified whammy - #[clap(long, short, action, default_value = "false")] // TODO -- change this default value to true when I have this implemented - run_verifier: bool -} - fn main() { if let Err(e) = try_main() { eprintln!("error: {}", e); @@ -121,13 +55,16 @@ fn try_main() -> Result<(), failure::Error> { let cli = WhammCli::parse(); match cli.command { - Command::Instr(args) => { + Cmd::Info {spec, globals, functions} => { + run_info(spec, globals, functions); + } + Cmd::Instr(args) => { run_instr(args.app, args.whammy, args.output_path, args.virgil, args.run_verifier); } - Command::VisWasm {wasm, output_path} => { + Cmd::VisWasm {wasm, output_path} => { run_vis_wasm(wasm, output_path); } - Command::VisWhammy {whammy, run_verifier, output_path} => { + Cmd::VisWhammy {whammy, run_verifier, output_path} => { run_vis_whammy(whammy, run_verifier, output_path); } } @@ -135,6 +72,14 @@ fn try_main() -> Result<(), failure::Error> { Ok(()) } +fn run_info(spec: String, print_globals: bool, print_functions: bool) { + // Parse the script and generate the information + let mut err = ErrorGen::new("".to_string(), spec.clone(), MAX_ERRORS); + print_info(spec, print_globals, print_functions, &mut err); + + err.fatal_report("PrintInfo"); +} + fn run_instr(app_wasm_path: String, whammy_path: String, output_wasm_path: String, emit_virgil: bool, run_verifier: bool) { // Set up error reporting mechanism let mut err = ErrorGen::new(whammy_path.clone(), "".to_string(), MAX_ERRORS); diff --git a/src/parser/print_visitor.rs b/src/parser/print_visitor.rs index 3e18bcc9..70b39278 100644 --- a/src/parser/print_visitor.rs +++ b/src/parser/print_visitor.rs @@ -3,7 +3,7 @@ use parser_types::{WhammVisitor}; use std::cmp; use std::collections::HashMap; -use crate::parser::types::{DataType, Whammy, Whamm, Expr, Event, Package, Op, Probe, Provider, Statement, Value, Global}; +use crate::parser::types::{DataType, Whammy, Whamm, Expr, Event, Package, Op, Probe, Provider, Statement, Value, Global, ProvidedFunctionality}; const NL: &str = "\n"; @@ -20,7 +20,7 @@ impl AsStrVisitor { } fn get_indent(&self) -> String { - "--".repeat(cmp::max(0, self.indent as usize)) + "--".repeat(cmp::max(0, self.indent.clone() as usize)) } fn visit_globals(&mut self, globals: &HashMap) -> String { @@ -34,6 +34,18 @@ impl AsStrVisitor { } s } + + fn visit_provided_globals(&mut self, globals: &HashMap) -> String { + let mut s = "".to_string(); + for (name, (.., global)) in globals.iter() { + s += &format!("{}{} := ", self.get_indent(), name); + match &global.value { + Some(v) => s += &format!("{}{}", self.visit_value(v), NL), + None => s += &format!("None{}", NL) + } + } + s + } } impl WhammVisitor for AsStrVisitor { @@ -44,7 +56,7 @@ impl WhammVisitor for AsStrVisitor { if whamm.fns.len() > 0 { s += &format!("Whamm events:{}", NL); self.increase_indent(); - for f in whamm.fns.iter() { + for (.., f) in whamm.fns.iter() { s += &format!("{}{}", self.visit_fn(f), NL); } self.decrease_indent(); @@ -54,7 +66,7 @@ impl WhammVisitor for AsStrVisitor { if whamm.globals.len() > 0 { s += &format!("Whamm globals:{}", NL); self.increase_indent(); - for (name, global) in whamm.globals.iter() { + for (name, (.., global)) in whamm.globals.iter() { s += &format!("{}{} := ", self.get_indent(), name); match &global.value { Some(v) => s += &format!("{}{}", self.visit_value(v), NL), @@ -122,7 +134,7 @@ impl WhammVisitor for AsStrVisitor { if provider.fns.len() > 0 { s += &format!("{} events:{}", self.get_indent(), NL); self.increase_indent(); - for f in provider.fns.iter() { + for (.., f) in provider.fns.iter() { s += &format!("{}{}{}", self.get_indent(), self.visit_fn(f), NL); } self.decrease_indent(); @@ -132,7 +144,7 @@ impl WhammVisitor for AsStrVisitor { if provider.globals.len() > 0 { s += &format!("{} globals:{}", self.get_indent(), NL); self.increase_indent(); - self.visit_globals(&provider.globals); + self.visit_provided_globals(&provider.globals); self.decrease_indent(); } @@ -162,7 +174,7 @@ impl WhammVisitor for AsStrVisitor { if package.fns.len() > 0 { s += &format!("{} package fns:{}", self.get_indent(), NL); self.increase_indent(); - for f in package.fns.iter() { + for (.., f) in package.fns.iter() { s += &format!("{}{}{}", self.get_indent(), self.visit_fn(f), NL); } self.decrease_indent(); @@ -172,7 +184,7 @@ impl WhammVisitor for AsStrVisitor { if package.globals.len() > 0 { s += &format!("{} package globals:{}", self.get_indent(), NL); self.increase_indent(); - self.visit_globals(&package.globals); + self.visit_provided_globals(&package.globals); self.decrease_indent(); } @@ -200,7 +212,7 @@ impl WhammVisitor for AsStrVisitor { if event.fns.len() > 0 { s += &format!("{} event fns:{}", self.get_indent(), NL); self.increase_indent(); - for f in event.fns.iter() { + for (.., f) in event.fns.iter() { s += &format!("{}{}{}", self.get_indent(), self.visit_fn(f), NL); } self.decrease_indent(); @@ -210,7 +222,7 @@ impl WhammVisitor for AsStrVisitor { if event.globals.len() > 0 { s += &format!("{} event globals:{}", self.get_indent(), NL); self.increase_indent(); - self.visit_globals(&event.globals); + self.visit_provided_globals(&event.globals); self.decrease_indent(); } @@ -243,7 +255,7 @@ impl WhammVisitor for AsStrVisitor { if probe.fns.len() > 0 { s += &format!("{} probe fns:{}", self.get_indent(), NL); self.increase_indent(); - for f in probe.fns.iter() { + for (.., f) in probe.fns.iter() { s += &format!("{}{}{}", self.get_indent(), self.visit_fn(f), NL); } self.decrease_indent(); @@ -253,7 +265,7 @@ impl WhammVisitor for AsStrVisitor { if probe.globals.len() > 0 { s += &format!("{} probe globals:{}", self.get_indent(), NL); self.increase_indent(); - self.visit_globals(&probe.globals); + self.visit_provided_globals(&probe.globals); self.decrease_indent(); } diff --git a/src/parser/tests.rs b/src/parser/tests.rs index 1aceede8..c575464d 100644 --- a/src/parser/tests.rs +++ b/src/parser/tests.rs @@ -175,7 +175,7 @@ pub fn run_test_on_valid_list(scripts: Vec, err: &mut ErrorGen) { if err.has_errors { err.report(); } - assert!(!err.has_errors); + assert!(!&err.has_errors); assert!(res); } } @@ -202,7 +202,7 @@ pub fn test_parse_invalid_scripts() { error!("string = '{}' is recognized as valid, but it should not", script) } assert!(err.has_errors); - assert!(!res); + assert!(!&res); } } diff --git a/src/parser/types.rs b/src/parser/types.rs index 6f9403a0..6736128d 100644 --- a/src/parser/types.rs +++ b/src/parser/types.rs @@ -1,10 +1,13 @@ use std::collections::HashMap; +use termcolor::{Buffer, ColorChoice, WriteColor}; use glob::Pattern; use pest::error::LineColLocation; use pest_derive::Parser; use pest::pratt_parser::PrattParser; +use termcolor::BufferWriter; use walrus::DataId; +use crate::common::terminal::{green, long_line, magenta, magenta_italics, white, white_italics, yellow}; use crate::common::error::{ErrorGen, WhammError}; #[derive(Parser)] @@ -61,14 +64,14 @@ impl Location { } pub fn span_between(loc0: &Location, loc1: &Location) -> LineColLocation { - let pos0 = match loc0.line_col { + let pos0 = match &loc0.line_col { LineColLocation::Pos(pos0) | - LineColLocation::Span(pos0, ..) => pos0 + LineColLocation::Span(pos0, ..) => pos0.clone() }; - let pos1 = match loc1.line_col { + let pos1 = match &loc1.line_col { LineColLocation::Pos(end1) | - LineColLocation::Span(.., end1) => end1 + LineColLocation::Span(.., end1) => end1.clone() }; return LineColLocation::Span(pos0, pos1); @@ -85,6 +88,38 @@ pub enum DataType { ty_info: Option>> } } +impl DataType { + pub fn print(&self, buffer: &mut Buffer) { + match self { + DataType::Integer => { + yellow(true, "int".to_string(), buffer); + }, + DataType::Boolean => { + yellow(true, "bool".to_string(), buffer); + }, + DataType::Null => { + yellow(true, "null".to_string(), buffer); + }, + DataType::Str => { + yellow(true, "str".to_string(), buffer); + }, + DataType::Tuple {ty_info} => { + white(true, "(".to_string(), buffer); + let mut is_first = true; + if let Some(types) = ty_info { + for ty in types { + if !is_first { + white(true, ", ".to_string(), buffer); + } + ty.print(buffer); + is_first = false; + } + } + white(true, ")".to_string(), buffer); + } + } + } +} // Values #[derive(Clone, Debug, Eq, Hash, PartialEq)] @@ -214,6 +249,30 @@ pub struct Fn { pub(crate) return_ty: Option, pub(crate) body: Option> } +impl Fn { + pub fn print(&self, buffer: &mut Buffer) { + green(true, format!("{}", self.name.name), buffer); + white(true, "(".to_string(), buffer); + let mut is_first = true; + for (param_name, param_ty) in self.params.iter() { + if !is_first { + white(true, ", ".to_string(), buffer); + } + if let Expr::VarId {name, ..} = param_name { + green(true, format!("{name}"), buffer); + white(true, ": ".to_string(), buffer); + param_ty.print(buffer); + } + is_first = false; + } + white(true, ")".to_string(), buffer); + + if let Some(return_ty) = &self.return_ty { + white(true, " -> ".to_string(), buffer); + return_ty.print(buffer); + } + } +} #[derive(Clone, Debug)] pub struct Global { @@ -223,11 +282,65 @@ pub struct Global { pub var_name: Expr, // Should be VarId pub value: Option } +impl Global { + pub fn print(&self, buffer: &mut Buffer) { + if let Expr::VarId {name, ..} = &self.var_name { + green(true, format!("{name}"), buffer); + } + white(true, ": ".to_string(), buffer); + self.ty.print(buffer); + } +} + +fn print_global_vars(tabs: &mut usize, globals: &HashMap, buffer: &mut Buffer) { + if !globals.is_empty() { + white(true, format!("{}GLOBALS:\n", " ".repeat(*tabs * 4)), buffer); + *tabs += 1; + for (.., (info, global)) in globals.iter() { + white(false, format!("{}", " ".repeat(*tabs * 4)), buffer); + global.print(buffer); + + *tabs += 1; + white(false, format!("\n{}{}\n", " ".repeat(*tabs * 4), info.docs), buffer); + *tabs -= 1; + } + *tabs -= 1; + white(false, format!("\n"), buffer); + } +} + +fn print_fns(tabs: &mut usize, functions: &Vec<(ProvidedFunctionality, Fn)>, buffer: &mut Buffer) { + if !functions.is_empty() { + white(true, format!("{}FUNCTIONS:\n", " ".repeat(*tabs * 4)), buffer); + *tabs += 1; + for (info, f) in functions.iter() { + green(true, format!("{}", " ".repeat(*tabs * 4)), buffer); + f.print(buffer); + green(true, format!("\n"), buffer); + *tabs += 1; + white(false, format!("{}{}\n", " ".repeat(*tabs * 4), info.docs), buffer); + *tabs -= 1; + } + *tabs -= 1; + white(false, format!("\n"), buffer); + } +} + +pub type ProvidedProbes = HashMap + )> + )> +)>; pub struct Whamm { - pub provided_probes: HashMap>>>, - pub(crate) fns: Vec, // Comp-provided - pub globals: HashMap, // Comp-provided + pub provided_probes: ProvidedProbes, + pub fns: Vec<(ProvidedFunctionality, Fn)>, // Comp-provided + pub globals: HashMap, // Comp-provided pub whammys: Vec } @@ -244,7 +357,7 @@ impl Whamm { whamm } - fn get_provided_fns() -> Vec { + fn get_provided_fns() -> Vec<(ProvidedFunctionality, Fn)> { let params = vec![ ( Expr::VarId { @@ -276,10 +389,15 @@ impl Whamm { return_ty: Some(DataType::Boolean), body: None }; - vec![ strcmp_fn ] + let docs = ProvidedFunctionality { + name: "strcmp".to_string(), + docs: "Compare two wasm strings and return whether they are equivalent.".to_string() + }; + + vec![ (docs, strcmp_fn) ] } - fn get_provided_globals() -> HashMap { + fn get_provided_globals() -> HashMap { HashMap::new() } @@ -291,84 +409,418 @@ impl Whamm { fn init_core_probes(&mut self) { // Not really any packages or events for a core probe...just two types! - self.provided_probes.insert("core".to_string(), HashMap::from([ - ("".to_string(), HashMap::from([ - ("".to_string(), vec![ - "begin".to_string(), - "end".to_string() - ]) - ])) - ])); + self.provided_probes.insert("begin".to_string(), ( + ProvidedFunctionality { + name: "begin".to_string(), + docs: "Run this logic on application startup.".to_string(), + }, + HashMap::new() + )); + self.provided_probes.insert("end".to_string(), ( + ProvidedFunctionality { + name: "end".to_string(), + docs: "Run this logic when the application exits.".to_string(), + }, + HashMap::new() + )); } fn init_wasm_probes(&mut self) { // This list of events matches up with bytecodes supported by Walrus. // See: https://docs.rs/walrus/latest/walrus/ir/ let wasm_bytecode_events = vec![ - "Block".to_string(), - "Loop".to_string(), - "Call".to_string(), - "CallIndirect".to_string(), - "LocalGet".to_string(), - "LocalSet".to_string(), - "LocalTee".to_string(), - "GlobalGet".to_string(), - "GlobalSet".to_string(), - "Const".to_string(), - "Binop".to_string(), - "Unop".to_string(), - "Select".to_string(), - "Unreachable".to_string(), - "Br".to_string(), - "BrIf".to_string(), - "IfElse".to_string(), - "BrTable".to_string(), - "Drop".to_string(), - "Return".to_string(), - "MemorySize".to_string(), - "MemoryGrow".to_string(), - "MemoryInit".to_string(), - "DataDrop".to_string(), - "MemoryCopy".to_string(), - "MemoryFill".to_string(), - "Load".to_string(), - "Store".to_string(), - "AtomicRmw".to_string(), - "Cmpxchg".to_string(), - "AtomicNotify".to_string(), - "AtomicWait".to_string(), - "AtomicFence".to_string(), - "TableGet".to_string(), - "TableSet".to_string(), - "TableGrow".to_string(), - "TableSize".to_string(), - "TableFill".to_string(), - "RefNull".to_string(), - "RefIsNull".to_string(), - "RefFunc".to_string(), - "V128Bitselect".to_string(), - "I8x16Swizzle".to_string(), - "I8x16Shuffle".to_string(), - "LoadSimd".to_string(), - "TableInit".to_string(), - "ElemDrop".to_string(), - "TableCopy".to_string() + ( + ProvidedFunctionality { + name: "block".to_string(), + docs: "https://developer.mozilla.org/en-US/docs/WebAssembly/Reference/Control_flow/block".to_string(), + }, + "block".to_string() + ), + ( + ProvidedFunctionality { + name: "loop".to_string(), + docs: "https://developer.mozilla.org/en-US/docs/WebAssembly/Reference/Control_flow/loop".to_string(), + }, + "loop".to_string() + ), + ( + ProvidedFunctionality { + name: "call".to_string(), + docs: "https://developer.mozilla.org/en-US/docs/WebAssembly/Reference/Control_flow/call".to_string(), + }, + "call".to_string() + ), + ( + ProvidedFunctionality { + name: "call_indirect".to_string(), + docs: "https://developer.mozilla.org/en-US/docs/WebAssembly/Reference/Control_flow/call".to_string(), + }, + "call_indirect".to_string() + ), + ( + ProvidedFunctionality { + name: "local_get".to_string(), + docs: "https://developer.mozilla.org/en-US/docs/WebAssembly/Reference/Variables/Local_get".to_string(), + }, + "local_get".to_string() + ), + ( + ProvidedFunctionality { + name: "local_set".to_string(), + docs: "https://developer.mozilla.org/en-US/docs/WebAssembly/Reference/Variables/Local_set".to_string(), + }, + "local_set".to_string() + ), + ( + ProvidedFunctionality { + name: "local_tee".to_string(), + docs: "https://developer.mozilla.org/en-US/docs/WebAssembly/Reference/Variables/Local_tee".to_string(), + }, + "local_tee".to_string() + ), + ( + ProvidedFunctionality { + name: "global_get".to_string(), + docs: "https://developer.mozilla.org/en-US/docs/WebAssembly/Reference/Variables/Global_get".to_string(), + }, + "global_get".to_string() + ), + ( + ProvidedFunctionality { + name: "global_set".to_string(), + docs: "https://developer.mozilla.org/en-US/docs/WebAssembly/Reference/Variables/Global_set".to_string(), + }, + "global_set".to_string() + ), + ( + ProvidedFunctionality { + name: "const".to_string(), + docs: "https://developer.mozilla.org/en-US/docs/WebAssembly/Reference/Numeric/Const".to_string(), + }, + "const".to_string() + ), + ( + ProvidedFunctionality { + name: "binop".to_string(), + docs: "Consume two operands and produce one result of the respective type. \ + The types of binary operations available to instrument depend on the operands \ + of the respective instruction. \ + A list of such operations is available here: \ + https://developer.mozilla.org/en-US/docs/WebAssembly/Reference/Numeric".to_string(), + }, + "binop".to_string() + ), + ( + ProvidedFunctionality { + name: "unop".to_string(), + docs: "Consume one operand and produce one result of the respective type. \ + The types of unary operations available to instrument depend on the operands \ + of the respective instruction. \ + A list of such operations is available here: \ + https://developer.mozilla.org/en-US/docs/WebAssembly/Reference/Numeric".to_string(), + }, + "unop".to_string() + ), + ( + ProvidedFunctionality { + name: "select".to_string(), + docs: "https://developer.mozilla.org/en-US/docs/WebAssembly/Reference/Control_flow/Select".to_string(), + }, + "select".to_string() + ), + ( + ProvidedFunctionality { + name: "unreachable".to_string(), + docs: "https://developer.mozilla.org/en-US/docs/WebAssembly/Reference/Control_flow/unreachable".to_string(), + }, + "unreachable".to_string() + ), + ( + ProvidedFunctionality { + name: "br".to_string(), + docs: "https://developer.mozilla.org/en-US/docs/WebAssembly/Reference/Control_flow/br".to_string(), + }, + "br".to_string() + ), + ( + ProvidedFunctionality { + name: "br_if".to_string(), + docs: "https://developer.mozilla.org/en-US/docs/WebAssembly/Reference/Control_flow/br".to_string(), + }, + "br_if".to_string() + ), + ( + ProvidedFunctionality { + name: "if_else".to_string(), + docs: "https://developer.mozilla.org/en-US/docs/WebAssembly/Reference/Control_flow/if...else".to_string(), + }, + "if_else".to_string() + ), + ( + ProvidedFunctionality { + name: "br_table".to_string(), + docs: "https://developer.mozilla.org/en-US/docs/WebAssembly/Reference/Control_flow/br".to_string(), + }, + "br_table".to_string() + ), + ( + ProvidedFunctionality { + name: "drop".to_string(), + docs: "https://developer.mozilla.org/en-US/docs/WebAssembly/Reference/Control_flow/Drop".to_string(), + }, + "drop".to_string() + ), + ( + ProvidedFunctionality { + name: "return".to_string(), + docs: "https://developer.mozilla.org/en-US/docs/WebAssembly/Reference/Control_flow/return".to_string(), + }, + "return".to_string() + ), + ( + ProvidedFunctionality { + name: "memory_size".to_string(), + docs: "https://developer.mozilla.org/en-US/docs/WebAssembly/Reference/Memory/Size".to_string(), + }, + "memory_size".to_string() + ), + ( + ProvidedFunctionality { + name: "memory_grow".to_string(), + docs: "https://developer.mozilla.org/en-US/docs/WebAssembly/Reference/Memory/Grow".to_string(), + }, + "memory_grow".to_string() + ), + ( + ProvidedFunctionality { + name: "memory_init".to_string(), + docs: "https://www.w3.org/TR/wasm-core-2/#syntax-instr-memory".to_string(), + }, + "memory_init".to_string() + ), + ( + ProvidedFunctionality { + name: "data_drop".to_string(), + docs: "https://www.w3.org/TR/wasm-core-2/#syntax-instr-memory".to_string(), + }, + "data_drop".to_string() + ), + ( + ProvidedFunctionality { + name: "memory_copy".to_string(), + docs: "https://developer.mozilla.org/en-US/docs/WebAssembly/Reference/Memory/Copy".to_string(), + }, + "memory_copy".to_string() + ), + ( + ProvidedFunctionality { + name: "memory_fill".to_string(), + docs: "https://developer.mozilla.org/en-US/docs/WebAssembly/Reference/Memory/Fill".to_string(), + }, + "memory_fill".to_string() + ), + ( + ProvidedFunctionality { + name: "load".to_string(), + docs: "https://developer.mozilla.org/en-US/docs/WebAssembly/Reference/Memory/Load".to_string(), + }, + "load".to_string() + ), + ( + ProvidedFunctionality { + name: "store".to_string(), + docs: "https://developer.mozilla.org/en-US/docs/WebAssembly/Reference/Memory/Store".to_string(), + }, + "store".to_string() + ), + ( + ProvidedFunctionality { + name: "atomic_rmw".to_string(), + docs: "https://github.com/WebAssembly/threads/blob/main/proposals/threads/Overview.md#read-modify-write".to_string(), + }, + "atomic_rmw".to_string() + ), + ( + ProvidedFunctionality { + name: "cmpxchg".to_string(), + docs: "https://github.com/WebAssembly/threads/blob/main/proposals/threads/Overview.md#compare-exchange".to_string(), + }, + "cmpxchg".to_string() + ), + ( + ProvidedFunctionality { + name: "atomic_notify".to_string(), + docs: "https://github.com/WebAssembly/threads/blob/main/proposals/threads/Overview.md#wait-and-notify-operators".to_string(), + }, + "atomic_notify".to_string() + ), + ( + ProvidedFunctionality { + name: "atomic_wait".to_string(), + docs: "https://github.com/WebAssembly/threads/blob/main/proposals/threads/Overview.md#wait-and-notify-operators".to_string(), + }, + "atomic_wait".to_string() + ), + ( + ProvidedFunctionality { + name: "atomic_fence".to_string(), + docs: "https://github.com/WebAssembly/threads/blob/main/proposals/threads/Overview.md#fence-operator".to_string(), + }, + "atomic_fence".to_string() + ), + ( + ProvidedFunctionality { + name: "table_get".to_string(), + docs: "https://www.w3.org/TR/wasm-core-2/#syntax-instr-table".to_string(), + }, + "table_get".to_string() + ), + ( + ProvidedFunctionality { + name: "table_set".to_string(), + docs: "https://www.w3.org/TR/wasm-core-2/#syntax-instr-table".to_string(), + }, + "table_set".to_string() + ), + ( + ProvidedFunctionality { + name: "table_grow".to_string(), + docs: "https://www.w3.org/TR/wasm-core-2/#syntax-instr-table".to_string(), + }, + "table_grow".to_string() + ), + ( + ProvidedFunctionality { + name: "table_size".to_string(), + docs: "https://www.w3.org/TR/wasm-core-2/#syntax-instr-table".to_string(), + }, + "table_size".to_string() + ), + ( + ProvidedFunctionality { + name: "table_fill".to_string(), + docs: "https://www.w3.org/TR/wasm-core-2/#syntax-instr-table".to_string(), + }, + "table_fill".to_string() + ), + ( + ProvidedFunctionality { + name: "ref_null".to_string(), + docs: "https://www.w3.org/TR/wasm-core-2/#syntax-instr-ref".to_string(), + }, + "ref_null".to_string() + ), + ( + ProvidedFunctionality { + name: "ref_is_null".to_string(), + docs: "https://www.w3.org/TR/wasm-core-2/#syntax-instr-ref".to_string(), + }, + "ref_is_null".to_string() + ), + ( + ProvidedFunctionality { + name: "ref_func".to_string(), + docs: "https://www.w3.org/TR/wasm-core-2/#syntax-instr-ref".to_string(), + }, + "ref_func".to_string() + ), + ( + ProvidedFunctionality { + name: "v128_bitselect".to_string(), + docs: "https://www.w3.org/TR/wasm-core-2/#syntax-instr-vec".to_string(), + }, + "v128_bitselect".to_string() + ), + ( + ProvidedFunctionality { + name: "i8x16_swizzle".to_string(), + docs: "https://www.w3.org/TR/wasm-core-2/#syntax-instr-vec".to_string(), + }, + "i8x16_swizzle".to_string() + ), + ( + ProvidedFunctionality { + name: "i8x16_shuffle".to_string(), + docs: "https://www.w3.org/TR/wasm-core-2/#syntax-instr-vec".to_string(), + }, + "i8x16_shuffle".to_string() + ), + ( + ProvidedFunctionality { + name: "load_simd".to_string(), + docs: "https://www.w3.org/TR/wasm-core-2/#syntax-instr-vec".to_string(), + }, + "load_simd".to_string() + ), + ( + ProvidedFunctionality { + name: "table_init".to_string(), + docs: "https://www.w3.org/TR/wasm-core-2/#syntax-instr-table".to_string(), + }, + "table_init".to_string() + ), + ( + ProvidedFunctionality { + name: "elem_drop".to_string(), + docs: "https://www.w3.org/TR/wasm-core-2/#syntax-instr-table".to_string(), + }, + "elem_drop".to_string() + ), + ( + ProvidedFunctionality { + name: "table_copy".to_string(), + docs: "https://www.w3.org/TR/wasm-core-2/#syntax-instr-table".to_string(), + }, + "table_copy".to_string() + ), ]; let wasm_bytecode_probe_types = vec![ - "before".to_string(), - "after".to_string(), - "alt".to_string() + ( + ProvidedFunctionality { + name: "before".to_string(), + docs: "This mode will cause the instrumentation logic to run *before* the \ + probed event (if the predicate evaluates to `true`).".to_string(), + }, + "before".to_string() + ), + ( + ProvidedFunctionality { + name: "after".to_string(), + docs: "This mode will cause the instrumentation logic to run *after* the \ + probed event (if the predicate evaluates to `true`).".to_string(), + }, + "after".to_string() + ), + ( + ProvidedFunctionality { + name: "alt".to_string(), + docs: "This mode will cause the instrumentation logic to run *instead of* the \ + probed event (if the predicate evaluates to `true`).".to_string(), + }, + "alt".to_string() + ) ]; let mut wasm_bytecode_map = HashMap::new(); // Build out the wasm_bytecode_map - for event in wasm_bytecode_events { - wasm_bytecode_map.insert(event, wasm_bytecode_probe_types.clone()); + for (info, name) in wasm_bytecode_events { + wasm_bytecode_map.insert(name, (info.clone(), wasm_bytecode_probe_types.clone())); } - self.provided_probes.insert("wasm".to_string(), HashMap::from([ - ("bytecode".to_string(), wasm_bytecode_map) - ])); + self.provided_probes.insert("wasm".to_string(), ( + ProvidedFunctionality { + name: "wasm".to_string(), + docs: "This provides various events to instrument that are specific \ + to WebAssembly.".to_string(), + }, + HashMap::from([("bytecode".to_string(), ( + ProvidedFunctionality { + name: "bytecode".to_string(), + docs: "This package within the wasm provider contains enables the \ + instrumentation of WebAssembly bytecode instructions.".to_string(), + }, + wasm_bytecode_map + ))]))); } pub fn add_whammy(&mut self, mut whammy: Whammy) -> usize { let id = self.whammys.len(); @@ -436,9 +888,338 @@ impl Whammy { } } + fn get_provider_info(provided_probes: &ProvidedProbes, probe_spec: &ProbeSpec) -> Result, WhammError> { + let (prov_matches, prov_loc) = if let Some(prov_patt) = &probe_spec.provider { + (Provider::get_matches(provided_probes, &prov_patt.name), prov_patt.loc.clone()) + } else { + (vec![], None) + }; + + if prov_matches.is_empty() { + let loc = if let Some(loc) = &prov_loc { + Some(loc.line_col.clone()) + } else { + None + }; + return Err(ErrorGen::get_parse_error(true, + Some(format!("Could not find any matches for the provider pattern")), + loc, vec![], vec![])); + } + + Ok(prov_matches) + } + + fn get_package_info(provided_probes: &ProvidedProbes, provider_matches: &Vec<(ProvidedFunctionality, String)>, probe_spec: &ProbeSpec) -> Result>, WhammError> { + let (package_matches, package_loc) = if let Some(package_patt) = &probe_spec.package { + let mut matches = HashMap::new(); + for (.., provider) in provider_matches.iter() { + let next = Package::get_matches(provided_probes, provider, &package_patt.name); + matches.insert(provider.clone(),next); + } + + (matches, package_patt.loc.clone()) + } else { + (HashMap::new(), None) + }; + + if package_matches.is_empty() { + let loc = if let Some(loc) = &package_loc { + Some(loc.line_col.clone()) + } else { + None + }; + return Err(ErrorGen::get_parse_error(true, + Some(format!("Could not find any matches for the package pattern")), + loc, vec![], vec![])); + } + Ok(package_matches) + } + + fn get_event_info(provided_probes: &ProvidedProbes, package_matches: &HashMap>, probe_spec: &ProbeSpec) -> Result>>, WhammError> { + let (event_matches, event_loc) = if let Some(event_patt) = &probe_spec.event { + let mut event_matches = HashMap::new(); + for (provider_name, packages) in package_matches.iter() { + let mut package = HashMap::new(); + for (.., package_name) in packages.iter() { + let next = Event::get_matches(provided_probes, provider_name, package_name, &event_patt.name); + package.insert(package_name.clone(), next); + } + event_matches.insert(provider_name.clone(), package); + } + + (event_matches, event_patt.loc.clone()) + } else { + (HashMap::new(), None) + }; + + if package_matches.is_empty() { + let loc = if let Some(loc) = &event_loc { + Some(loc.line_col.clone()) + } else { + None + }; + return Err(ErrorGen::get_parse_error(true, + Some(format!("Could not find any matches for the event pattern")), + loc, vec![], vec![])); + } + Ok(event_matches) + } + + fn get_mode_info(provided_probes: &ProvidedProbes, matches: &HashMap>>, probe_spec: &ProbeSpec) -> Result>>>, WhammError> { + let (mode_matches, mode_loc) = if let Some(mode_patt) = &probe_spec.mode { + let mut mode_matches = HashMap::new(); + for (provider_name, package_matches) in matches.iter() { + let mut package = HashMap::new(); + for (package_name, event_matches) in package_matches.iter() { + let mut modes = HashMap::new(); + for (.., event_name) in event_matches.iter() { + let next = Probe::get_matches(provided_probes, provider_name, package_name, event_name, &mode_patt.name); + modes.insert(package_name.clone(), next); + } + package.insert(package_name.clone(), modes); + } + mode_matches.insert(provider_name.clone(), package); + } + + (mode_matches, mode_patt.loc.clone()) + } else { + (HashMap::new(), None) + }; + + if mode_matches.is_empty() { + let loc = if let Some(loc) = &mode_loc { + Some(loc.line_col.clone()) + } else { + None + }; + return Err(ErrorGen::get_parse_error(true, + Some(format!("Could not find any matches for the mode pattern")), + loc, vec![], vec![])); + } + Ok(mode_matches) + } + + pub fn print_info(&mut self, provided_probes: &ProvidedProbes, probe_spec: &ProbeSpec, + print_globals: bool, print_functions: bool) -> Result<(), WhammError> { + let writer = BufferWriter::stderr(ColorChoice::Always); + let mut buffer = writer.buffer(); + + // Print `whamm` info + let mut tabs = 0; + if print_globals || print_functions { + white(true, "\nCORE ".to_string(), &mut buffer); + magenta(true, "`whamm`".to_string(), &mut buffer); + white(true, " FUNCTIONALITY\n\n".to_string(), &mut buffer); + + // Print the globals + if print_globals { + let globals = Whamm::get_provided_globals(); + print_global_vars(&mut tabs, &globals, &mut buffer); + } + + // Print the functions + if print_functions { + let functions = Whamm::get_provided_fns(); + print_fns(&mut tabs, &functions, &mut buffer); + } + } + + long_line(&mut buffer); + white(true, "\n\n".to_string(), &mut buffer); + + let prov_info = if probe_spec.provider.is_some() { + Self::get_provider_info(provided_probes, probe_spec)? + } else { + vec![] + }; + let pkg_info = if probe_spec.package.is_some() { + Self::get_package_info(provided_probes, &prov_info, probe_spec)? + } else { + HashMap::new() + }; + let event_info = if probe_spec.event.is_some() { + Self::get_event_info(provided_probes, &pkg_info, probe_spec)? + } else { + HashMap::new() + }; + let mode_info = if probe_spec.mode.is_some() { + Self::get_mode_info(provided_probes, &event_info, probe_spec)? + } else { + HashMap::new() + }; + + // Print matched provider introduction + if !prov_info.is_empty() { + magenta(true, format!("{}", &probe_spec.provider.as_ref().unwrap().name), &mut buffer); + if let Some(package_patt) = &probe_spec.package { + white(true, format!(":{}", &package_patt.name), &mut buffer); + if let Some(event_patt) = &probe_spec.event { + white(true, format!(":{}", &event_patt.name), &mut buffer); + if let Some(mode_patt) = &probe_spec.mode { + white(true, format!(":{}", &mode_patt.name), &mut buffer); + } + } + } + white(true, "\n".to_string(), &mut buffer); + white_italics(true, "matches the following providers:\n\n".to_string(), &mut buffer); + } + + // Print the matched provider information + for (provider_info, provider_str) in prov_info.iter() { + magenta_italics(true, provider_str.clone(), &mut buffer); + white(true, format!(" provider\n"), &mut buffer); + + // Print the provider description + tabs += 1; + white(false, format!("{}{}\n\n", " ".repeat(tabs * 4), provider_info.docs), &mut buffer); + + // Print the globals + if print_globals { + let globals = Provider::get_provided_globals(&provider_str); + print_global_vars(&mut tabs, &globals, &mut buffer); + } + + // Print the functions + if print_functions { + let functions = Provider::get_provided_fns(&provider_str); + print_fns(&mut tabs, &functions, &mut buffer); + } + tabs -= 1; + } + long_line(&mut buffer); + white(true, "\n\n".to_string(), &mut buffer); + + // Print matched package introduction + if !pkg_info.is_empty() { + white(true, format!("{}:", &probe_spec.provider.as_ref().unwrap().name), &mut buffer); + magenta(true, format!("{}", &probe_spec.package.as_ref().unwrap().name), &mut buffer); + if let Some(event_patt) = &probe_spec.event { + white(true, format!(":{}", &event_patt.name), &mut buffer); + if let Some(mode_patt) = &probe_spec.mode { + white(true, format!(":{}", &mode_patt.name), &mut buffer); + } + } + white(true, "\n".to_string(), &mut buffer); + white_italics(true, "matches the following packages:\n\n".to_string(), &mut buffer); + } + + // Print the matched package information + let mut tabs = 0; + for (_prov_str, package_list) in pkg_info.iter() { + for (package_info, package_str) in package_list { + magenta_italics(true, package_str.clone(), &mut buffer); + white(true, format!(" package\n"), &mut buffer); + + // Print the package description + tabs += 1; + white(false, format!("{}{}\n\n", " ".repeat(tabs * 4), package_info.docs), &mut buffer); + + // Print the globals + if print_globals { + let globals = Package::get_provided_globals(&package_str); + print_global_vars(&mut tabs, &globals, &mut buffer); + } + + // Print the functions + if print_functions { + let functions = Package::get_provided_fns(&package_str); + print_fns(&mut tabs, &functions, &mut buffer); + } + tabs -= 1; + } + } + long_line(&mut buffer); + white(true, "\n\n".to_string(), &mut buffer); + + // Print matched event introduction + if !pkg_info.is_empty() { + white(true, format!("{}:{}:", &probe_spec.provider.as_ref().unwrap().name, &probe_spec.package.as_ref().unwrap().name), &mut buffer); + magenta(true, format!("{}", &probe_spec.event.as_ref().unwrap().name), &mut buffer); + if let Some(mode_patt) = &probe_spec.mode { + white(true, format!(":{}", &mode_patt.name), &mut buffer); + } + white(true, "\n".to_string(), &mut buffer); + white_italics(true, "matches the following events:\n\n".to_string(), &mut buffer); + } + + // Print the matched event information + let mut tabs = 0; + for (_prov_str, package_map) in event_info.iter() { + for (_package_str, event_list) in package_map { + for (event_info, event_str) in event_list { + magenta_italics(true, event_str.clone(), &mut buffer); + white(true, format!(" event\n"), &mut buffer); + + // Print the event description + tabs += 1; + white(false, format!("{}{}\n\n", " ".repeat(tabs * 4), event_info.docs), &mut buffer); + + // Print the globals + if print_globals { + let globals = Event::get_provided_globals(&event_str); + print_global_vars(&mut tabs, &globals, &mut buffer); + } + + // Print the functions + if print_functions { + let functions = Event::get_provided_fns(&event_str); + print_fns(&mut tabs, &functions, &mut buffer); + } + tabs -= 1; + } + } + } + long_line(&mut buffer); + white(true, "\n\n".to_string(), &mut buffer); + + // Print matched mode introduction + if !mode_info.is_empty() { + white(true, format!("{}:{}:{}:", &probe_spec.provider.as_ref().unwrap().name, + &probe_spec.package.as_ref().unwrap().name, + &probe_spec.event.as_ref().unwrap().name), &mut buffer); + magenta(true, format!("{}\n", &probe_spec.mode.as_ref().unwrap().name), &mut buffer); + white_italics(true, "matches the following modes:\n\n".to_string(), &mut buffer); + } + + // Print the matched mode information + let mut tabs = 0; + for (_prov_str, package_map) in mode_info.iter() { + for (_package_str, event_list) in package_map { + for (_event_str, mode_list) in event_list { + for (mode_info, mode_str) in mode_list { + magenta_italics(true, mode_str.clone(), &mut buffer); + white(true, format!(" mode\n"), &mut buffer); + + // Print the mode description + tabs += 1; + white(false, format!("{}{}\n\n", " ".repeat(tabs * 4), mode_info.docs), &mut buffer); + + // Print the globals + if print_globals { + let globals = Probe::get_provided_globals(&mode_str); + print_global_vars(&mut tabs, &globals, &mut buffer); + } + + // Print the functions + if print_functions { + let functions = Probe::get_provided_fns(&mode_str); + print_fns(&mut tabs, &functions, &mut buffer); + } + tabs -= 1; + } + } + } + } + + writer.print(&buffer).expect("Uh oh, something went wrong while printing to terminal"); + buffer.reset().expect("Uh oh, something went wrong while printing to terminal"); + + return Ok(()); + } + /// Iterates over all of the matched providers, packages, events, and probe names /// to add a copy of the user-defined Probe for each of them. - pub fn add_probe(&mut self, provided_probes: &HashMap>>>, + pub fn add_probe(&mut self, provided_probes: &ProvidedProbes, probe_spec: &ProbeSpec, predicate: Option, body: Option>) -> Result<(), WhammError> { let mut reason = &probe_spec.provider; if let Some(prov_patt) = &probe_spec.provider { @@ -447,10 +1228,10 @@ impl Whammy { if matches.is_empty() { return Err(ErrorGen::get_parse_error(true, Some(format!("Could not find any matches for the specified provider pattern: {}", prov_patt.name)), - prov_patt.loc.as_ref().unwrap().line_col.clone(), vec![], vec![])); + Some(prov_patt.loc.as_ref().unwrap().line_col.clone()), vec![], vec![])); } - for provider_str in matches.iter() { + for (.., provider_str) in matches.iter() { let mut is_empty = true; // Does provider exist yet? let provider = match self.providers.get_mut(provider_str) { @@ -462,12 +1243,18 @@ impl Whammy { self.providers.get_mut(&provider_str.to_lowercase()).unwrap() } }; + + if provider_str.to_uppercase() == "BEGIN" || provider_str.to_uppercase() == "END" { + // special case, just stop here + return Ok(()); + } + if let Some(package_patt) = &probe_spec.package { let matches = Package::get_matches(provided_probes, provider_str, &package_patt.name); if matches.is_empty() { reason = &probe_spec.package; } - for package_str in matches.iter() { + for (.., package_str) in matches.iter() { // Does package exist yet? let package = match provider.packages.get_mut(package_str) { Some(m) => m, @@ -483,7 +1270,7 @@ impl Whammy { if matches.is_empty() { reason = &probe_spec.event; } - for event_str in matches.iter() { + for (.., event_str) in matches.iter() { // Does event exist yet? let event = match package.events.get_mut(event_str) { Some(f) => f, @@ -500,7 +1287,7 @@ impl Whammy { reason = &probe_spec.mode; } - for name_str in matches.iter() { + for (.., name_str) in matches.iter() { event.insert_probe(name_str.to_string(), Probe::new(mode_patt.name.to_string(), mode_patt.loc.clone(), predicate.clone(), body.clone())); is_empty = false; } @@ -526,7 +1313,7 @@ impl Whammy { if let Some(mode_loc) = &r.loc { return Err(ErrorGen::get_parse_error(true, Some(format!("Could not find any matches for this pattern")), - mode_loc.line_col.clone(), vec![], vec![])); + Some(mode_loc.line_col.clone()), vec![], vec![])); } } } @@ -534,10 +1321,16 @@ impl Whammy { } } +#[derive(Clone, Debug, Eq, Hash, PartialEq)] +pub struct ProvidedFunctionality { + pub name: String, + pub docs: String +} + pub struct Provider { pub name: String, - pub fns: Vec, // Comp-provided - pub globals: HashMap, // Comp-provided + pub fns: Vec<(ProvidedFunctionality, Fn)>, // Comp-provided + pub globals: HashMap, // Comp-provided /// The packages of the probes that have been used in the Whammy. /// These will be sub-packages of this Provider. @@ -557,22 +1350,22 @@ impl Provider { } } - fn get_provided_fns(_name: &String) -> Vec { + fn get_provided_fns(_name: &String) -> Vec<(ProvidedFunctionality, Fn)> { vec![] } - fn get_provided_globals(_name: &String) -> HashMap { + fn get_provided_globals(_name: &String) -> HashMap { HashMap::new() } /// Get the provider names that match the passed glob pattern - pub fn get_matches(provided_probes: &HashMap>>>, prov_patt: &str) -> Vec { + pub fn get_matches(provided_probes: &ProvidedProbes, prov_patt: &str) -> Vec<(ProvidedFunctionality, String)> { let glob = Pattern::new(&prov_patt.to_lowercase()).unwrap(); let mut matches = vec![]; - for (provider_name, _provider) in provided_probes.into_iter() { + for (provider_name, (info, _provider)) in provided_probes.into_iter() { if glob.matches(&provider_name.to_lowercase()) { - matches.push(provider_name.clone()); + matches.push((info.clone(), provider_name.clone())); } } @@ -582,8 +1375,8 @@ impl Provider { pub struct Package { pub name: String, - pub fns: Vec, // Comp-provided - pub globals: HashMap, // Comp-provided + pub fns: Vec<(ProvidedFunctionality, Fn)>, // Comp-provided + pub globals: HashMap, // Comp-provided /// The events of the probes that have been used in the Whammy. /// These will be sub-events of this Package. @@ -603,23 +1396,23 @@ impl Package { } } - fn get_provided_fns(_name: &String) -> Vec { + fn get_provided_fns(_name: &String) -> Vec<(ProvidedFunctionality, Fn)> { vec![] } - fn get_provided_globals(_name: &String) -> HashMap { + fn get_provided_globals(_name: &String) -> HashMap { HashMap::new() } /// Get the Package names that match the passed glob pattern - pub fn get_matches(provided_probes: &HashMap>>>, provider: &str, mod_patt: &str) -> Vec { + pub fn get_matches(provided_probes: &ProvidedProbes, provider: &str, mod_patt: &str) -> Vec<(ProvidedFunctionality, String)> { let glob = Pattern::new(&mod_patt.to_lowercase()).unwrap(); let mut matches = vec![]; - for (mod_name, _package) in provided_probes.get(provider).unwrap().into_iter() { + for (mod_name, (info, _package)) in provided_probes.get(provider).unwrap().1.iter() { if glob.matches(&mod_name.to_lowercase()) { - matches.push(mod_name.clone()); + matches.push((info.clone(), mod_name.clone())); } } @@ -629,8 +1422,8 @@ impl Package { pub struct Event { pub name: String, - pub fns: Vec, // Comp-provided - pub globals: HashMap, // Comp-provided + pub fns: Vec<(ProvidedFunctionality, Fn)>, // Comp-provided + pub globals: HashMap, // Comp-provided pub probe_map: HashMap>, pub loc: Option } @@ -647,64 +1440,89 @@ impl Event { } } - fn get_provided_fns(_name: &String) -> Vec { + fn get_provided_fns(_name: &String) -> Vec<(ProvidedFunctionality, Fn)> { vec![] } - fn get_provided_globals(name: &String) -> HashMap { + fn get_provided_globals(name: &String) -> HashMap { let mut globals = HashMap::new(); if name.to_lowercase() == "call" { // Add in provided globals for the "call" event - globals.insert("target_fn_type".to_string(),Global { - is_comp_provided: true, - ty: DataType::Str, - var_name: Expr::VarId { + globals.insert("target_fn_type".to_string(),( + ProvidedFunctionality { name: "target_fn_type".to_string(), - loc: None + docs: "The type of function being called at this call site. This constant will \ + evaluate to either `local` or `import`.".to_string() }, - value: None - }); - globals.insert("target_imp_module".to_string(),Global { - is_comp_provided: true, - ty: DataType::Str, - var_name: Expr::VarId { + Global { + is_comp_provided: true, + ty: DataType::Str, + var_name: Expr::VarId { + name: "target_fn_type".to_string(), + loc: None + }, + value: None + })); + globals.insert("target_imp_module".to_string(),( + ProvidedFunctionality { name: "target_imp_module".to_string(), - loc: None + docs: "The name of the module that the imported function comes from. \ + To improve performance, pair with `target_fn_type == \"import\"` \ + for faster short-circuiting.".to_string() }, - value: None - }); - globals.insert("target_imp_name".to_string(),Global { - is_comp_provided: true, - ty: DataType::Str, - var_name: Expr::VarId { + Global { + is_comp_provided: true, + ty: DataType::Str, + var_name: Expr::VarId { + name: "target_imp_module".to_string(), + loc: None + }, + value: None + })); + globals.insert("target_imp_name".to_string(),( + ProvidedFunctionality { name: "target_imp_name".to_string(), - loc: None + docs: "The name of the imported function. \ + To improve performance, pair with `target_fn_type == \"import\"` \ + for faster short-circuiting.".to_string() }, - value: None - }); - globals.insert("new_target_fn_name".to_string(),Global { - is_comp_provided: true, - ty: DataType::Str, - var_name: Expr::VarId { + Global { + is_comp_provided: true, + ty: DataType::Str, + var_name: Expr::VarId { + name: "target_imp_name".to_string(), + loc: None + }, + value: None + })); + globals.insert("new_target_fn_name".to_string(),( + ProvidedFunctionality { name: "new_target_fn_name".to_string(), - loc: None + docs: "(DEPRECATED) The name of the target function to call instead of the original.".to_string() }, - value: None - }); + Global { + is_comp_provided: true, + ty: DataType::Str, + var_name: Expr::VarId { + name: "new_target_fn_name".to_string(), + loc: None + }, + value: None + })); } globals } /// Get the Event names that match the passed glob pattern - pub fn get_matches(provided_probes: &HashMap>>>, provider: &str, package: &str, func_patt: &str) -> Vec { + pub fn get_matches(provided_probes: &ProvidedProbes, provider: &str, package: &str, func_patt: &str) -> Vec<(ProvidedFunctionality, String)> { let glob = Pattern::new(&func_patt.to_lowercase()).unwrap(); let mut matches = vec![]; - for (fn_name, _package) in provided_probes.get(provider).unwrap().get(package).unwrap().into_iter() { + for (fn_name, (info, _package)) in provided_probes.get(provider).unwrap().1.get(package).unwrap().1.iter() { if glob.matches(&fn_name.to_lowercase()) { - matches.push(fn_name.clone()); + matches.push((info.clone(), fn_name.clone())); } } @@ -729,8 +1547,8 @@ impl Event { pub struct Probe { pub name: String, pub loc: Option, - pub fns: Vec, // Comp-provided - pub globals: HashMap, // Comp-provided + pub fns: Vec<(ProvidedFunctionality, Fn)>, // Comp-provided + pub globals: HashMap, // Comp-provided pub predicate: Option, pub body: Option> @@ -750,23 +1568,23 @@ impl Probe { } } - fn get_provided_fns(_name: &String) -> Vec { + fn get_provided_fns(_name: &String) -> Vec<(ProvidedFunctionality, Fn)> { vec![] } - fn get_provided_globals(_name: &String) -> HashMap { + fn get_provided_globals(_name: &String) -> HashMap { HashMap::new() } /// Get the Probe names that match the passed glob pattern - pub fn get_matches(provided_probes: &HashMap>>>, provider: &str, package: &str, event: &str, probe_patt: &str) -> Vec { + pub fn get_matches(provided_probes: &ProvidedProbes, provider: &str, package: &str, event: &str, probe_patt: &str) -> Vec<(ProvidedFunctionality, String)> { let glob = Pattern::new(&probe_patt.to_lowercase()).unwrap(); let mut matches = vec![]; - for p_name in provided_probes.get(provider).unwrap().get(package).unwrap().get(event).unwrap().iter() { + for (info, p_name) in provided_probes.get(provider).unwrap().1.get(package).unwrap().1.get(event).unwrap().1.iter() { if glob.matches(&p_name.to_lowercase()) { - matches.push(p_name.clone()); + matches.push((info.clone(), p_name.clone())); } } diff --git a/src/parser/whamm_parser.rs b/src/parser/whamm_parser.rs index 1f3b3a34..d811c25b 100644 --- a/src/parser/whamm_parser.rs +++ b/src/parser/whamm_parser.rs @@ -12,6 +12,37 @@ use crate::parser::types::{DataType, Whammy, Whamm, Expr, Statement, Value, Loca const UNEXPECTED_ERR_MSG: &str = "WhammParser: Looks like you've found a bug...please report this behavior! Exiting now..."; +pub fn print_info(spec: String, print_globals: bool, print_functions: bool, err: &mut ErrorGen) { + trace!("Entered print_info"); + err.set_script_text(spec.to_owned()); + + let res = WhammParser::parse(Rule::PROBE_SPEC, &*spec); + match res { + Ok(mut pairs) => { + // Create the probe specification from the input string + let probe_spec = probe_spec_from_rule( + // inner of script + pairs.next().unwrap(), + err + ); + + // Print the information for the passed probe specification + let mut whamm = Whamm::new(); + let id = whamm.add_whammy(Whammy::new()); + let whammy: &mut Whammy = whamm.whammys.get_mut(id).unwrap(); + match whammy.print_info(&whamm.provided_probes, &probe_spec, print_globals, print_functions) { + Err(e) => { + err.add_error(e); + }, + _ => {} + } + }, + Err(e) => { + err.pest_err(e); + }, + } +} + pub fn parse_script(script: &String, err: &mut ErrorGen) -> Option { trace!("Entered parse_script"); err.set_script_text(script.to_owned()); @@ -60,7 +91,7 @@ pub fn to_ast(pair: Pair, err: &mut ErrorGen) -> Result rule => { err.parse_error(true, Some(UNEXPECTED_ERR_MSG.to_string()), - LineColLocation::from(pair.as_span()), + Some(LineColLocation::from(pair.as_span())), vec![Rule::whammy], vec![rule]); // should have exited above (since it's a fatal error) unreachable!() @@ -153,7 +184,7 @@ pub fn process_pair(whamm: &mut Whamm, whammy_count: usize, pair: Pair, er rule => { err.parse_error(true, Some(UNEXPECTED_ERR_MSG.to_string()), - LineColLocation::from(pair.as_span()), + Some(LineColLocation::from(pair.as_span())), vec![Rule::whammy, Rule::probe_def, Rule::EOI], vec![rule]); // should have exited above (since it's a fatal error) unreachable!() @@ -308,7 +339,7 @@ fn stmt_from_rule(pair: Pair, err: &mut ErrorGen) -> Statement { rule => { err.parse_error(true, Some(UNEXPECTED_ERR_MSG.to_string()), - LineColLocation::from(pair.as_span()), + Some(LineColLocation::from(pair.as_span())), vec![Rule::statement, Rule::assignment, Rule::fn_call], vec![rule]); // should have exited above (since it's a fatal error) unreachable!(); @@ -339,7 +370,7 @@ fn probe_spec_part_from_rule(pair: Pair, err: &mut ErrorGen) -> SpecPart rule => { err.parse_error(true, Some(UNEXPECTED_ERR_MSG.to_string()), - LineColLocation::from(pair.as_span()), + Some(LineColLocation::from(pair.as_span())), vec![Rule::PROBE_ID, Rule::PROBE_ID], vec![rule]); // should have exited above (since it's a fatal error) unreachable!(); @@ -369,21 +400,12 @@ fn probe_spec_from_rule(pair: Pair, err: &mut ErrorGen) -> ProbeSpec { return ProbeSpec { provider: Some(SpecPart { - name: "core".to_string(), + name: spec_as_str.to_uppercase(), loc: loc.clone() }), - package: Some(SpecPart { - name: "*".to_string(), - loc: loc.clone() - }), - event: Some(SpecPart { - name: "*".to_string(), - loc: loc.clone() - }), - mode: Some(SpecPart { - name: "BEGIN".to_string(), - loc - }), + package: None, + event: None, + mode: None, } } @@ -431,7 +453,7 @@ fn probe_spec_from_rule(pair: Pair, err: &mut ErrorGen) -> ProbeSpec { rule => { err.parse_error(true, Some(UNEXPECTED_ERR_MSG.to_string()), - LineColLocation::from(pair.as_span()), + Some(LineColLocation::from(pair.as_span())), vec![Rule::PROBE_SPEC], vec![rule]); // should have exited above (since it's a fatal error) unreachable!(); @@ -573,7 +595,7 @@ fn expr_from_pairs(pairs: Pairs) -> Result> { return Err(vec![ErrorGen::get_parse_error( true, Some(UNEXPECTED_ERR_MSG.to_string()), - LineColLocation::from(op.as_span()), + Some(LineColLocation::from(op.as_span())), vec![Rule::and, Rule::or, Rule::eq, Rule::ne, Rule::ge, Rule::gt, Rule::le, Rule::lt, Rule::add, Rule::subtract, Rule::multiply, Rule::divide, Rule::modulo], vec![rule])]); diff --git a/src/verifier/builder_visitor.rs b/src/verifier/builder_visitor.rs index a67f7363..2d6fee8d 100644 --- a/src/verifier/builder_visitor.rs +++ b/src/verifier/builder_visitor.rs @@ -5,7 +5,7 @@ use crate::verifier::types::{Record, ScopeType, SymbolTable}; use log::trace; use crate::common::error::ErrorGen; -use crate::parser::types::Global; +use crate::parser::types::{Global, ProvidedFunctionality}; const UNEXPECTED_ERR_MSG: &str = "SymbolTableBuilder: Looks like you've found a bug...please report this behavior! Exiting now..."; @@ -328,6 +328,12 @@ impl SymbolTableBuilder<'_> { self.add_fn_id_to_curr_rec(id); } + fn visit_provided_globals(&mut self, globals: &HashMap) { + for (name, (.., global)) in globals.iter() { + self.add_global(global.ty.clone(), name.clone()); + } + } + fn visit_globals(&mut self, globals: &HashMap) { for (name, global) in globals.iter() { self.add_global(global.ty.clone(), name.clone()); @@ -355,10 +361,10 @@ impl WhammVisitor<()> for SymbolTableBuilder<'_> { self.curr_whamm = Some(id); // visit fns - whamm.fns.iter().for_each(| f | self.visit_fn(f) ); + whamm.fns.iter().for_each(| (.., f) | self.visit_fn(f) ); // visit globals - self.visit_globals(&whamm.globals); + self.visit_provided_globals(&whamm.globals); // visit whammys whamm.whammys.iter().for_each(| whammy | self.visit_whammy(whammy)); @@ -389,8 +395,8 @@ impl WhammVisitor<()> for SymbolTableBuilder<'_> { trace!("Entering: visit_provider"); self.add_provider(provider); - provider.fns.iter().for_each(| f | self.visit_fn(f) ); - self.visit_globals(&provider.globals); + provider.fns.iter().for_each(| (.., f) | self.visit_fn(f) ); + self.visit_provided_globals(&provider.globals); provider.packages.iter().for_each(| (_name, package) | { self.visit_package(package) }); @@ -407,8 +413,8 @@ impl WhammVisitor<()> for SymbolTableBuilder<'_> { trace!("Entering: visit_package"); self.add_package(package); - package.fns.iter().for_each(| f | self.visit_fn(f) ); - self.visit_globals(&package.globals); + package.fns.iter().for_each(| (.., f) | self.visit_fn(f) ); + self.visit_provided_globals(&package.globals); package.events.iter().for_each(| (_name, event) | { self.visit_event(event) }); @@ -425,8 +431,8 @@ impl WhammVisitor<()> for SymbolTableBuilder<'_> { trace!("Entering: visit_event"); self.add_event(event); - event.fns.iter().for_each(| f | self.visit_fn(f) ); - self.visit_globals(&event.globals); + event.fns.iter().for_each(| (.., f) | self.visit_fn(f) ); + self.visit_provided_globals(&event.globals); // visit probe_map event.probe_map.iter().for_each(| probes | { @@ -447,8 +453,8 @@ impl WhammVisitor<()> for SymbolTableBuilder<'_> { trace!("Entering: visit_probe"); self.add_probe(probe); - probe.fns.iter().for_each(| f | self.visit_fn(f) ); - self.visit_globals(&probe.globals); + probe.fns.iter().for_each(| (.., f) | self.visit_fn(f) ); + self.visit_provided_globals(&probe.globals); // Will not visit predicate/body at this stage