From 0fb495f18db00b63b8367905edc1e372e608631c Mon Sep 17 00:00:00 2001 From: Joshua Wong Date: Thu, 10 Nov 2022 18:45:12 -0600 Subject: [PATCH 001/517] fix: remove executable perm --- crates/forge_analyzer/src/analyzer.rs | 0 crates/forge_analyzer/src/lib.rs | 0 2 files changed, 0 insertions(+), 0 deletions(-) mode change 100755 => 100644 crates/forge_analyzer/src/analyzer.rs mode change 100755 => 100644 crates/forge_analyzer/src/lib.rs diff --git a/crates/forge_analyzer/src/analyzer.rs b/crates/forge_analyzer/src/analyzer.rs old mode 100755 new mode 100644 diff --git a/crates/forge_analyzer/src/lib.rs b/crates/forge_analyzer/src/lib.rs old mode 100755 new mode 100644 From d3a0bc72b66509274ef10adbb189e6ddeccc8cf1 Mon Sep 17 00:00:00 2001 From: Joshua Wong Date: Thu, 10 Nov 2022 18:47:49 -0600 Subject: [PATCH 002/517] fix: import fxhash from forge_utils --- crates/forge_analyzer/src/engine.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/forge_analyzer/src/engine.rs b/crates/forge_analyzer/src/engine.rs index 91defbd2..84fd919c 100644 --- a/crates/forge_analyzer/src/engine.rs +++ b/crates/forge_analyzer/src/engine.rs @@ -1,6 +1,6 @@ use std::collections::VecDeque; -use rustc_hash::FxHashSet; +use forge_utils::FxHashSet; use swc_core::ecma::ast::Id; use tracing::{debug, info, instrument}; From 07f2fb50445f0cca1b13812d028bc67275075b4f Mon Sep 17 00:00:00 2001 From: Joshua Wong Date: Fri, 11 Nov 2022 11:17:17 -0600 Subject: [PATCH 003/517] feat: add options for dumping graphviz-formatted cfgs --- crates/fsrt/src/main.rs | 40 +++++++++++++++++++++++++++++++++++----- 1 file changed, 35 insertions(+), 5 deletions(-) diff --git a/crates/fsrt/src/main.rs b/crates/fsrt/src/main.rs index fe422291..25f3e90a 100644 --- a/crates/fsrt/src/main.rs +++ b/crates/fsrt/src/main.rs @@ -41,19 +41,37 @@ struct Args { #[arg(short, long)] debug: bool, + /// Dump a graphviz formatted callgraph + #[arg(long)] + callgraph: bool, + + /// Dump a graphviz formatted control flow graph of the function specified in `--function` + #[arg(long)] + cfg: bool, + + /// A specific function to scan. Must be an entrypoint specified in `manifest.yml` #[arg(short, long)] function: Option, + /// The directory to scan. Assumes there is a `manifest.ya?ml` file in the top level + /// directory, and that the source code is located in `src/` #[arg(name = "DIRS", value_hint = ValueHint::DirPath)] dirs: Vec, } +#[derive(Debug, Clone, Copy, Default)] +struct Opts { + dump_cfg: bool, + dump_callgraph: bool, +} + #[derive(Default)] struct ForgeProject { #[allow(dead_code)] sm: Arc, ctx: AppCtx, funcs: Vec<(ModId, Id)>, + opts: Opts, } impl ForgeProject { @@ -63,6 +81,7 @@ impl ForgeProject { sm: Default::default(), ctx: Default::default(), funcs: vec![], + opts: Default::default(), } } @@ -73,7 +92,9 @@ impl ForgeProject { let func_item = ModItem::new(modid, func.clone()); let mut machine = Machine::new(modid, func.clone(), &mut self.ctx); let res = (modid, func, machine.run()); - let _ = dump_cfg_dot(&self.ctx, &func_item, stdout()); + if self.opts.dump_callgraph { + let _ = dump_cfg_dot(&self.ctx, &func_item, stdout()); + } res }) } @@ -87,8 +108,12 @@ impl ForgeProject { let funcitem = ModItem::new(modid, ident.clone()); let mut machine = Machine::new(modid, ident.clone(), &mut self.ctx); let res = machine.run(); - let _ = dump_cfg_dot(&self.ctx, &funcitem, stdout()); - let _ = dump_callgraph_dot(&self.ctx, &funcitem, stdout()); + if self.opts.dump_cfg { + let _ = dump_cfg_dot(&self.ctx, &funcitem, stdout()); + } + if self.opts.dump_callgraph { + let _ = dump_callgraph_dot(&self.ctx, &funcitem, stdout()); + } (funcitem, res) } @@ -164,7 +189,7 @@ fn collect_sourcefiles>(root: P) -> impl Iterator } #[tracing::instrument(level = "debug")] -fn scan_directory(dir: PathBuf, function: Option<&str>) -> Result { +fn scan_directory(dir: PathBuf, function: Option<&str>, opts: Opts) -> Result { let mut manifest_file = dir.clone(); manifest_file.push("manifest.yaml"); if !manifest_file.exists() { @@ -188,6 +213,7 @@ fn scan_directory(dir: PathBuf, function: Option<&str>) -> Result }) .flatten(); let mut proj = ForgeProject::with_files(paths.clone()); + proj.opts = opts; proj.add_funcs(funcrefs); resolve_calls(&mut proj.ctx); if let Some(func) = function { @@ -207,9 +233,13 @@ fn main() -> Result<()> { .with(EnvFilter::from_env("FORGE_LOG")) .init(); let function = args.function.as_deref(); + let opts = Opts { + dump_callgraph: args.callgraph, + dump_cfg: args.cfg, + }; for dir in args.dirs { debug!(?dir); - scan_directory(dir, function)?; + scan_directory(dir, function, opts)?; } Ok(()) } From b6630e5c0db337ff2fcc4150fe671776f343273e Mon Sep 17 00:00:00 2001 From: Joshua Wong Date: Fri, 11 Nov 2022 15:06:55 -0600 Subject: [PATCH 004/517] feat: display functions in the result --- crates/forge_analyzer/src/engine.rs | 3 +- crates/forge_loader/src/manifest.rs | 37 ++++++++++++++++++++++++- crates/fsrt/Cargo.toml | 1 - crates/fsrt/src/main.rs | 43 ++++++++++++++--------------- 4 files changed, 59 insertions(+), 25 deletions(-) diff --git a/crates/forge_analyzer/src/engine.rs b/crates/forge_analyzer/src/engine.rs index 84fd919c..c1a715ab 100644 --- a/crates/forge_analyzer/src/engine.rs +++ b/crates/forge_analyzer/src/engine.rs @@ -132,7 +132,8 @@ impl<'ctx> Machine<'ctx> { } let result = self.app.func_res(&orig_func); info!(?result, "analysis complete"); - println!("Result: {result}"); + let fname: &str = &orig_func.ident.0; + println!("Result of analyzing {fname}: {result}"); result } } diff --git a/crates/forge_loader/src/manifest.rs b/crates/forge_loader/src/manifest.rs index f3aae3e8..8f8bdf49 100644 --- a/crates/forge_loader/src/manifest.rs +++ b/crates/forge_loader/src/manifest.rs @@ -6,7 +6,7 @@ use std::{ }; use crate::Error; -use itertools::Itertools; +use itertools::{Either, Itertools}; use serde::Deserialize; use tracing::trace; @@ -151,6 +151,41 @@ pub enum FunctionTy { WebTrigger(T), } +impl FunctionTy { + pub fn map(self, f: impl FnOnce(T) -> O) -> FunctionTy { + match self { + Self::Invokable(t) => FunctionTy::Invokable(f(t)), + Self::WebTrigger(t) => FunctionTy::WebTrigger(f(t)), + } + } + + #[inline] + pub fn into_inner(self) -> T { + match self { + FunctionTy::Invokable(t) | FunctionTy::WebTrigger(t) => t, + } + } + + pub fn sequence( + self, + f: impl FnOnce(T) -> I, + ) -> impl Iterator> { + match self { + Self::Invokable(t) => Either::Left(f(t).into_iter().map(FunctionTy::Invokable)), + Self::WebTrigger(t) => Either::Right(f(t).into_iter().map(FunctionTy::WebTrigger)), + } + } +} + +impl AsRef for FunctionTy { + #[inline] + fn as_ref(&self) -> &T { + match self { + FunctionTy::Invokable(t) | FunctionTy::WebTrigger(t) => t, + } + } +} + impl<'a> ForgeModules<'a> { pub fn into_analyzable_functions( mut self, diff --git a/crates/fsrt/Cargo.toml b/crates/fsrt/Cargo.toml index 6982953b..1554c7e9 100644 --- a/crates/fsrt/Cargo.toml +++ b/crates/fsrt/Cargo.toml @@ -20,4 +20,3 @@ tracing.workspace = true tracing-subscriber.workspace = true tracing-tree.workspace = true walkdir.workspace = true - diff --git a/crates/fsrt/src/main.rs b/crates/fsrt/src/main.rs index 25f3e90a..e8842c86 100644 --- a/crates/fsrt/src/main.rs +++ b/crates/fsrt/src/main.rs @@ -32,7 +32,7 @@ use forge_analyzer::{ engine::Machine, resolver::{dump_callgraph_dot, dump_cfg_dot, resolve_calls}, }; -use forge_loader::manifest::{ForgeManifest, FunctionRef}; +use forge_loader::manifest::{ForgeManifest, FunctionRef, FunctionTy}; use walkdir::WalkDir; #[derive(Parser, Debug)] @@ -70,7 +70,7 @@ struct ForgeProject { #[allow(dead_code)] sm: Arc, ctx: AppCtx, - funcs: Vec<(ModId, Id)>, + funcs: Vec>, opts: Opts, } @@ -87,7 +87,8 @@ impl ForgeProject { #[instrument(level = "debug", skip(self))] fn verify_funs(&mut self) -> impl Iterator + '_ { - self.funcs.iter().cloned().map(|(modid, func)| { + self.funcs.iter().cloned().map(|fty| { + let (modid, func) = fty.into_inner(); // TODO(perf): reuse the same `Machine` between iterations let func_item = ModItem::new(modid, func.clone()); let mut machine = Machine::new(modid, func.clone(), &mut self.ctx); @@ -100,11 +101,15 @@ impl ForgeProject { } fn verify_fun(&mut self, func: &str) -> (ModItem, AuthZVal) { - let &(modid, ref ident) = self + let (modid, ref ident) = *self .funcs .iter() - .find(|(_, ident)| *ident.0 == *func) - .unwrap(); + .find(|&ty| { + let (_, ref ident) = *ty.as_ref(); + *ident.0 == *func + }) + .unwrap() + .as_ref(); let funcitem = ModItem::new(modid, ident.clone()); let mut machine = Machine::new(modid, ident.clone(), &mut self.ctx); let res = machine.run(); @@ -153,13 +158,14 @@ impl ForgeProject { } } - fn add_funcs<'a, I: IntoIterator>(&mut self, iter: I) { - self.funcs - .extend(iter.into_iter().filter_map(|(func, path)| { + fn add_funcs<'a, I: IntoIterator>>(&mut self, iter: I) { + self.funcs.extend(iter.into_iter().flat_map(|ftype| { + ftype.sequence(|(func, path)| { let modid = self.ctx.modid_from_path(&path)?; let func = self.ctx.export(modid, func)?; Some((modid, func)) - })) + }) + })); } } @@ -199,19 +205,12 @@ fn scan_directory(dir: PathBuf, function: Option<&str>, opts: Opts) -> Result>(); - let funcrefs = manifest - .modules - .functions - .into_iter() - .map(|f| { - let resolved_func = FunctionRef::try_from(f)?.try_resolve(&paths, &dir)?; - Ok(resolved_func.into_func_path()) - }) - .inspect(|res: &Result<_, forge_loader::Error>| match res { - Ok((func, path)) => debug!(?func, ?path), - Err(err) => warn!(?err), + let funcrefs = manifest.modules.into_analyzable_functions().flat_map(|f| { + f.sequence(|fmod| { + let resolved_func = FunctionRef::try_from(fmod)?.try_resolve(&paths, &dir)?; + Ok::<_, forge_loader::Error>(resolved_func.into_func_path()) }) - .flatten(); + }); let mut proj = ForgeProject::with_files(paths.clone()); proj.opts = opts; proj.add_funcs(funcrefs); From 5417c956778692a94be432db5bf83680066e58a6 Mon Sep 17 00:00:00 2001 From: Joshua Wong Date: Fri, 11 Nov 2022 15:09:51 -0600 Subject: [PATCH 005/517] docs: update example help --- README.md | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index 7cab55f4..74353737 100644 --- a/README.md +++ b/README.md @@ -12,13 +12,15 @@ A static analysis tool for finding common [Forge][1] vulnerabilities. Usage: fsrt [OPTIONS] [DIRS]... Arguments: - [DIRS]... - -Options: - -d, --debug - -f, --function - -h, --help Print help information - -V, --version Print version information + [DIRS]... The directory to scan. Assumes there is a `manifest.ya?ml` file in the top level directory, and that the source code is located in `src/` + + Options: + -d, --debug + --callgraph Dump a graphviz formatted callgraph + --cfg Dump a graphviz formatted control flow graph of the function specified in `--function` + -f, --function A specific function to scan. Must be an entrypoint specified in `manifest.yml` + -h, --help Print help information + -V, --version Print version information ``` ## Installation @@ -26,7 +28,7 @@ Options: Installing from source: ```sh -cargo install --path . +cargo install --path crates/fsrt ``` ## Tests @@ -46,7 +48,7 @@ fsrt ./test-apps/jira-damn-vulnerable-forge-app ## Contributions -Contributions to FSRT are welcome! Please see [CONTRIBUTING.md](CONTRIBUTING.md) for details. +Contributions to FSRT are welcome! Please see [CONTRIBUTING.md](CONTRIBUTING.md) for details. ## License @@ -57,4 +59,3 @@ FSRT is dual licensed under the MIT and Apache 2.0 licenses. See [LICENSE-APACHE](LICENSE-APACHE) and [LICENSE-MIT](LICENSE-MIT) for details. [![With ❤️ from Atlassian](https://raw.githubusercontent.com/atlassian-internal/oss-assets/master/banner-cheers.png)](https://www.atlassian.com) - From 22ba818b26fe73dcaf1a7f70cf970a859ee1fed9 Mon Sep 17 00:00:00 2001 From: Joshua Wong Date: Wed, 16 Nov 2022 17:15:22 -0600 Subject: [PATCH 006/517] feat: try more resolution strategies --- Cargo.lock | 353 ++++++++++++++++---------- Cargo.toml | 9 +- crates/forge_file_resolver/Cargo.toml | 10 + crates/forge_file_resolver/src/lib.rs | 164 ++++++++++++ 4 files changed, 399 insertions(+), 137 deletions(-) create mode 100644 crates/forge_file_resolver/Cargo.toml create mode 100644 crates/forge_file_resolver/src/lib.rs diff --git a/Cargo.lock b/Cargo.lock index be82a40a..e26804fe 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -57,15 +57,6 @@ dependencies = [ "libc", ] -[[package]] -name = "ansi_term" -version = "0.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2" -dependencies = [ - "winapi", -] - [[package]] name = "anyhow" version = "1.0.66" @@ -198,9 +189,9 @@ checksum = "c1ad822118d20d2c234f427000d5acc36eabe1e29a348c89b63dd60b13f28e5d" [[package]] name = "cc" -version = "1.0.73" +version = "1.0.76" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2fff2a6927b3bb87f9595d67196a70493f627687a71d87a0d692242c33f58c11" +checksum = "76a284da2e6fe2092f2353e51713435363112dfd60030e22add80be333fb928f" [[package]] name = "cfg-if" @@ -225,9 +216,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.0.22" +version = "4.0.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91b9970d7505127a162fdaa9b96428d28a479ba78c9ec7550a63a5d9863db682" +checksum = "60494cedb60cb47462c0ff7be53de32c0e42a6fc2c772184554fa12bd9489c03" dependencies = [ "atty", "bitflags", @@ -236,7 +227,7 @@ dependencies = [ "once_cell", "strsim", "termcolor", - "terminal_size 0.2.1", + "terminal_size 0.2.2", ] [[package]] @@ -261,6 +252,16 @@ dependencies = [ "os_str_bytes", ] +[[package]] +name = "codespan-reporting" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3538270d33cc669650c4b093848450d380def10c331d38c768e34cac80576e6e" +dependencies = [ + "termcolor", + "unicode-width", +] + [[package]] name = "core-foundation-sys" version = "0.8.3" @@ -299,26 +300,24 @@ dependencies = [ [[package]] name = "crossbeam-epoch" -version = "0.9.10" +version = "0.9.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "045ebe27666471bb549370b4b0b3e51b07f56325befa4284db65fc89c02511b1" +checksum = "f916dfc5d356b0ed9dae65f1db9fc9770aa2851d2662b988ccf4fe3516e86348" dependencies = [ "autocfg", "cfg-if", "crossbeam-utils", "memoffset", - "once_cell", "scopeguard", ] [[package]] name = "crossbeam-utils" -version = "0.8.11" +version = "0.8.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51887d4adc7b564537b15adcfb307936f8075dfcd5f00dde9a9f1d29383682bc" +checksum = "edbafec5fa1f196ca66527c1b12c2ec4745ca14b50f1ad8f9f6f720b55d11fac" dependencies = [ "cfg-if", - "once_cell", ] [[package]] @@ -341,6 +340,50 @@ dependencies = [ "syn", ] +[[package]] +name = "cxx" +version = "1.0.81" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97abf9f0eca9e52b7f81b945524e76710e6cb2366aead23b7d4fbf72e281f888" +dependencies = [ + "cc", + "cxxbridge-flags", + "cxxbridge-macro", + "link-cplusplus", +] + +[[package]] +name = "cxx-build" +version = "1.0.81" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cc32cc5fea1d894b77d269ddb9f192110069a8a9c1f1d441195fba90553dea3" +dependencies = [ + "cc", + "codespan-reporting", + "once_cell", + "proc-macro2", + "quote", + "scratch", + "syn", +] + +[[package]] +name = "cxxbridge-flags" +version = "1.0.81" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ca220e4794c934dc6b1207c3b42856ad4c302f2df1712e9f8d2eec5afaacf1f" + +[[package]] +name = "cxxbridge-macro" +version = "1.0.81" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b846f081361125bfc8dc9d3940c84e1fd83ba54bbca7b17cd29483c828be0704" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "darling" version = "0.13.4" @@ -480,11 +523,12 @@ checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" name = "forge_analyzer" version = "0.1.0" dependencies = [ - "anyhow", "clap", "fixedbitset", + "forge_file_resolver", "forge_utils", "itertools", + "num-bigint", "once_cell", "petgraph", "pretty_assertions", @@ -498,9 +542,12 @@ dependencies = [ "tracing-subscriber", "typed-arena", "typed-index-collections", - "walkdir", ] +[[package]] +name = "forge_file_resolver" +version = "0.1.0" + [[package]] name = "forge_loader" version = "0.1.0" @@ -551,6 +598,7 @@ version = "0.1.0" dependencies = [ "clap", "forge_analyzer", + "forge_file_resolver", "forge_loader", "miette 5.4.1", "rustc-hash", @@ -575,9 +623,9 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.7" +version = "0.2.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4eb1a864a501629691edf6c15a593b7a51eebaa1e8468e9ddc623de7c9b58ec6" +checksum = "c05aeb6a22b8f62540c194aac980f2115af067bfe15a0734d7277a768d396b31" dependencies = [ "cfg-if", "libc", @@ -628,18 +676,28 @@ dependencies = [ [[package]] name = "iana-time-zone" -version = "0.1.48" +version = "0.1.51" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "237a0714f28b1ee39ccec0770ccb544eb02c9ef2c82bb096230eefcffa6468b0" +checksum = "f5a6ef98976b22b3b7f2f3a806f858cb862044cfa66805aa3ad84cb3d3b785ed" dependencies = [ "android_system_properties", "core-foundation-sys", + "iana-time-zone-haiku", "js-sys", - "once_cell", "wasm-bindgen", "winapi", ] +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0703ae284fc167426161c2e3f1da3ea71d94b21bedbcc9494e92b28e334e3dca" +dependencies = [ + "cxx", + "cxx-build", +] + [[package]] name = "ident_case" version = "1.0.1" @@ -675,9 +733,9 @@ dependencies = [ [[package]] name = "io-lifetimes" -version = "0.7.3" +version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ea37f355c05dde75b84bba2d767906ad522e97cd9e2eef2be7a4ab7fb442c06" +checksum = "59ce5ef949d49ee85593fc4d3f3f95ad61657076395cbbce23e2121fc5542074" [[package]] name = "is-macro" @@ -709,9 +767,9 @@ dependencies = [ [[package]] name = "itoa" -version = "1.0.3" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c8af84674fe1f223a982c933a0ee1086ac4d4052aa0fb8060c12c6ad838e754" +checksum = "4217ad341ebadf8d8e724e264f13e593e0648f5b3e94b3896a5df283be015ecc" [[package]] name = "js-sys" @@ -812,9 +870,18 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.132" +version = "0.2.137" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8371e4e5341c3a96db127eb2465ac681ced4c433e01dd0e938adbef26ba93ba5" +checksum = "fc7fcc620a3bff7cdd7a365be3376c97191aeaccc2a603e600951e452615bf89" + +[[package]] +name = "link-cplusplus" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9272ab7b96c9046fbc5bc56c06c117cb639fe2d509df0c421cad82d2915cf369" +dependencies = [ + "cc", +] [[package]] name = "linux-raw-sys" @@ -824,9 +891,9 @@ checksum = "d4d2456c373231a208ad294c33dc5bff30051eafd954cd4caae83a712b12854d" [[package]] name = "lock_api" -version = "0.4.8" +version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f80bf5aacaf25cbfc8210d1cfb718f2bf3b11c4c54e5afe36c236853a8ec390" +checksum = "435011366fe56583b16cf956f9df0095b405b82d76425bc8981c0e22e60ec4df" dependencies = [ "autocfg", "scopeguard", @@ -1019,9 +1086,9 @@ dependencies = [ [[package]] name = "num_cpus" -version = "1.13.1" +version = "1.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19e64526ebdee182341572e50e9ad03965aa510cd94427a4549448f285e957a1" +checksum = "f6058e64324c71e02bc2b150e4f3bc8286db6c83092132ffa3f6b1eab0f9def5" dependencies = [ "hermit-abi", "libc", @@ -1053,9 +1120,9 @@ checksum = "86f0b0d4bf799edbc74508c1e8bf170ff5f41238e5f8225603ca7caaae2b7860" [[package]] name = "os_str_bytes" -version = "6.3.0" +version = "6.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ff7415e9ae3fff1225851df9e0d9e4e5479f947619774677a63572e55e80eff" +checksum = "3baf96e39c5359d2eb0dd6ccb42c62b91d9678aa68160d261b9e0ccbf9e9dea9" [[package]] name = "output_vt100" @@ -1090,9 +1157,9 @@ dependencies = [ [[package]] name = "parking_lot_core" -version = "0.9.3" +version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09a279cbf25cb0757810394fbc1e359949b59e348145c643a939a525692e6929" +checksum = "4dc9e0dc2adc1c69d09143aff38d3d30c5c3f0df0dad82e6d25547af174ebec0" dependencies = [ "cfg-if", "libc", @@ -1192,9 +1259,9 @@ dependencies = [ [[package]] name = "ppv-lite86" -version = "0.2.16" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb9f9e6e233e5c4a35559a617bf40a4ec447db2e84c20b55a6f83167b7e57872" +checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" [[package]] name = "precomputed-hash" @@ -1264,9 +1331,9 @@ checksum = "dbf0c48bc1d91375ae5c3cd81e3722dff1abcf81a30960240640d223f59fe0e5" [[package]] name = "proc-macro2" -version = "1.0.43" +version = "1.0.47" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a2ca2c61bc9f3d74d2886294ab7b9853abd9c1ad903a3ac7815c58989bb7bab" +checksum = "5ea3d908b0e36316caf9e9e2c4625cdde190a7e6f440d794667ed17a1855e725" dependencies = [ "unicode-ident", ] @@ -1371,9 +1438,9 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.6.27" +version = "0.6.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3f87b73ce11b1619a3c6332f45341e0047173771e8b8b73f87bfeefb7b56244" +checksum = "456c603be3e8d448b072f410900c09faf164fbce2d480456f50eea6e25f9c848" [[package]] name = "retain_mut" @@ -1404,9 +1471,9 @@ dependencies = [ [[package]] name = "rustix" -version = "0.35.9" +version = "0.35.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72c825b8aa8010eb9ee99b75f05e10180b9278d161583034d7574c9d617aeada" +checksum = "727a1a6d65f786ec22df8a81ca3121107f235970dc1705ed681d3e6e8b9cd5f9" dependencies = [ "bitflags", "errno", @@ -1449,6 +1516,12 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" +[[package]] +name = "scratch" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8132065adcfd6e02db789d9285a0deb2f3fcb04002865ab67d5fb103533898" + [[package]] name = "semver" version = "0.9.0" @@ -1568,9 +1641,9 @@ checksum = "f67ad224767faa3c7d8b6d91985b78e70a1324408abcb1cfcc2be4c06bc06043" [[package]] name = "sourcemap" -version = "6.1.0" +version = "6.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "58ad6f449ac2dc2eaa01e766408b76b55fc0a20c842b63aa11a8448caa72f50b" +checksum = "c46fdc1838ff49cf692226f5c2b0f5b7538f556863d0eca602984714667ac6e7" dependencies = [ "base64", "if_chain", @@ -1663,9 +1736,9 @@ checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" [[package]] name = "supports-color" -version = "1.3.0" +version = "1.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4872ced36b91d47bae8a214a683fe54e7078875b399dfa251df346c9b547d1f9" +checksum = "8ba6faf2ca7ee42fdd458f4347ae0a9bd6bcc445ad7cb57ad82b383f18870d6f" dependencies = [ "atty", "is_ci", @@ -1691,9 +1764,9 @@ dependencies = [ [[package]] name = "swc" -version = "0.232.84" +version = "0.232.93" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef79dbe33da3870888d0de10f38c03b4882e712919de128855330e5cfa531fd4" +checksum = "88a9a25f0200ec6617563fd39bbfb2a15ec14151496bea0134a4fb6c28831226" dependencies = [ "ahash", "anyhow", @@ -1766,9 +1839,9 @@ dependencies = [ [[package]] name = "swc_common" -version = "0.29.13" +version = "0.29.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "953e1f014688eadbbd3e9131a525e8922c552540bb02b0bb6d9fdcb1375bccc4" +checksum = "4bde01c52376971bc6839c42e1a71dec9526ac7acfbfcf1eb3e606e5aa1b2de0" dependencies = [ "ahash", "ast_node", @@ -1820,9 +1893,9 @@ dependencies = [ [[package]] name = "swc_core" -version = "0.41.3" +version = "0.43.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5cb83df2a0c4c47853139cd08235218c3762a6057d7733a7c971da0b392a4b11" +checksum = "85c23a92f00c6f5fe3dd2c8c3ba6f550e4d8f984b9178714955a68ec8882ecbd" dependencies = [ "swc", "swc_atoms", @@ -1839,9 +1912,9 @@ dependencies = [ [[package]] name = "swc_ecma_ast" -version = "0.94.18" +version = "0.94.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6fbace94cfac9a0767fb513bac02327ad074da83b7964b7c6c83cdb38beb88f" +checksum = "f54bd55f94f02afe98be444e1808e068fa3dca0a113d0c38748d3fdd7a380c2b" dependencies = [ "bitflags", "is-macro", @@ -1856,9 +1929,9 @@ dependencies = [ [[package]] name = "swc_ecma_codegen" -version = "0.127.30" +version = "0.127.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df8d8be2c7cdcc43cb6270d08a97002d1a31c2f7eb4cd67161b681668d7b68db" +checksum = "e807c7271cc05ce3853ce7937776b89730463a39a98729f83bef76bfb6a99048" dependencies = [ "memchr", "num-bigint", @@ -1888,9 +1961,9 @@ dependencies = [ [[package]] name = "swc_ecma_ext_transforms" -version = "0.91.30" +version = "0.91.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "de9de4e6963694232392d0c5c2b8ca379fb082d3c34bd503b7b922f7d1784714" +checksum = "16ce0f7a23ce2dcb70e1dcc6e848968f192001e12583c4c915f069b60f00051c" dependencies = [ "phf", "swc_atoms", @@ -1902,9 +1975,9 @@ dependencies = [ [[package]] name = "swc_ecma_lints" -version = "0.66.45" +version = "0.66.48" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb55176416bad562da01963fd384cd16d84d0e6722bd08252d5614375121eb1d" +checksum = "835a1bb73b00494b6e58c9a7094a90f893effb2ed39d3564b21687c872153952" dependencies = [ "ahash", "auto_impl", @@ -1923,9 +1996,9 @@ dependencies = [ [[package]] name = "swc_ecma_loader" -version = "0.41.14" +version = "0.41.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f6ac7ce0c7a4c9f5badfd603d6c1caa969a3ee519da22a2511ee8da96e542cd" +checksum = "78b475a49f4c6cc848fe0084c89d202f35691035601ad1ff34e8d72f673c8759" dependencies = [ "ahash", "anyhow", @@ -1945,9 +2018,9 @@ dependencies = [ [[package]] name = "swc_ecma_minifier" -version = "0.159.73" +version = "0.159.81" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a448d933db0cab7a6be5c759fa5612f0839e787c21e6f1596f96a6289a1d275" +checksum = "08bd43e585789982187df760482587e5c9aceb775570b223141e0846cc0ff9d3" dependencies = [ "ahash", "arrayvec", @@ -1979,9 +2052,9 @@ dependencies = [ [[package]] name = "swc_ecma_parser" -version = "0.122.25" +version = "0.122.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "80e5a791ca360a54bdadd8905aa8b5c511243b06e0400593975971558ca10161" +checksum = "0bac20cd9f38112de7572150bc3ef24d99eed7c64d03f73f9c87df3bb497ca94" dependencies = [ "either", "enum_kind", @@ -1998,9 +2071,9 @@ dependencies = [ [[package]] name = "swc_ecma_preset_env" -version = "0.174.43" +version = "0.174.49" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "729bfe3ad894e9125028b32f04894e39881314757ff38e9c86f41594ae77798c" +checksum = "8d25f8d2d4a2e9d7cb1b4286931b404405c862258b7767ea722278c54724f694" dependencies = [ "ahash", "anyhow", @@ -2023,9 +2096,9 @@ dependencies = [ [[package]] name = "swc_ecma_transforms" -version = "0.198.43" +version = "0.198.49" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f26ce48f89d2cecdc9c5d1bd3abc9cef0a138f85a352ce3f303a1239556f9b7" +checksum = "962b799fba3e74bc113cafdba27c7d747ba2861a46b49036525f2e155c49275e" dependencies = [ "swc_atoms", "swc_common", @@ -2043,9 +2116,9 @@ dependencies = [ [[package]] name = "swc_ecma_transforms_base" -version = "0.111.44" +version = "0.111.48" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75ba90804fabf97bac6139647db85aa00a958f32d68f80f00a54b5ec2072e81a" +checksum = "7853d181f5eb8a620c0189eab19161cd68234f07df00d302acb8596fff88147f" dependencies = [ "better_scoped_tls", "bitflags", @@ -2065,9 +2138,9 @@ dependencies = [ [[package]] name = "swc_ecma_transforms_classes" -version = "0.100.44" +version = "0.100.47" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7ba1eb6d2b09ec7a90a33ae9d2dd535a9d979904a1a331f2da2b94bd7b42d56" +checksum = "21e8426a2a48d675828b60e6e2cdd43a8eac8f9d901590cabc7833b053ba931b" dependencies = [ "swc_atoms", "swc_common", @@ -2079,9 +2152,9 @@ dependencies = [ [[package]] name = "swc_ecma_transforms_compat" -version = "0.136.36" +version = "0.136.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b30316ece5ec0ea82c2259acdfcb6ac877f01d8db16570cee09a402b3208cc3c" +checksum = "98fd1bb023214b0846b2a0083702868b18c9b26c50de6c1f4438c6f45d24b1ef" dependencies = [ "ahash", "arrayvec", @@ -2118,9 +2191,9 @@ dependencies = [ [[package]] name = "swc_ecma_transforms_module" -version = "0.153.37" +version = "0.153.43" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4734808d8b80ac3652488f5ac4360859cfafaa4374a7fefbc132e5ca16655ab1" +checksum = "d875fe3535ee6a718e83d09f33ae06a02f570a3b312a28815bb6cb8668c20a27" dependencies = [ "Inflector", "ahash", @@ -2146,9 +2219,9 @@ dependencies = [ [[package]] name = "swc_ecma_transforms_optimization" -version = "0.167.43" +version = "0.167.49" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3476ffcb0b9504395b0ceddc17295ef81face7b48920f57f4b576555d26517e4" +checksum = "a4d408666841c399c256e6ddf9851fc11628f2759dc49a4e23fbf1671e698c85" dependencies = [ "ahash", "dashmap", @@ -2171,9 +2244,9 @@ dependencies = [ [[package]] name = "swc_ecma_transforms_proposal" -version = "0.144.36" +version = "0.144.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "45fffefd88bd73af47cfd77a1181acc2969a39ec7755bedb25ae2c9bf90bd135" +checksum = "0e211e3f7fb99d2a67d9b702d694f151afb3ccec58b0c16738c989ffab250b88" dependencies = [ "either", "serde", @@ -2190,9 +2263,9 @@ dependencies = [ [[package]] name = "swc_ecma_transforms_react" -version = "0.155.37" +version = "0.155.43" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3429eff1dace0c8e2a1eec8b042b92269d0d42802eba655f885bbb0671573e7" +checksum = "6620cccf5fc0e44ab6115df325d984e27a798836788ce14f1e3f48f0c1876a9d" dependencies = [ "ahash", "base64", @@ -2216,9 +2289,9 @@ dependencies = [ [[package]] name = "swc_ecma_transforms_typescript" -version = "0.159.39" +version = "0.159.45" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8234258ff22f112ab0d73a2a1fea5b87714393763c19d236edfded1fb68f250" +checksum = "ce69ec6f0ad1819b02653bce0abc3165c7afa11429103eb530bf64cd6aab74e8" dependencies = [ "serde", "swc_atoms", @@ -2232,9 +2305,9 @@ dependencies = [ [[package]] name = "swc_ecma_utils" -version = "0.105.30" +version = "0.105.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1742c68a38edd07a621b7a9a540c496e4921758709bffd30b9b955b214b2c5e9" +checksum = "baaee0747f8c8d32a7d55f6054e915c7a0eae13fc20127dd9ab52bc1e2f2c785" dependencies = [ "indexmap", "num_cpus", @@ -2249,9 +2322,9 @@ dependencies = [ [[package]] name = "swc_ecma_visit" -version = "0.80.18" +version = "0.80.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ff3dfcd70ad5ab6d06e5da0f5ca249a3264fbfd42e72b5155dff7a8fb2f24ac" +checksum = "d7b42489b19f3451b65c01ed4a7926e44fab294ed9bfa8489634e58ecc96df88" dependencies = [ "num-bigint", "swc_atoms", @@ -2275,9 +2348,9 @@ dependencies = [ [[package]] name = "swc_error_reporters" -version = "0.13.13" +version = "0.13.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1996acb4fd0656d77769764e93ca486810d6baa724a8ed877a7ae3cc7f7c6b5" +checksum = "cfdfda46250b8d5ff325c4f9e7e50497125e8f357f3a2daa655ba0b4ad8d964a" dependencies = [ "anyhow", "miette 4.7.1", @@ -2288,9 +2361,9 @@ dependencies = [ [[package]] name = "swc_fast_graph" -version = "0.17.14" +version = "0.17.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5448dee060201d38e4019496d56bce897ef69cfa91cae294ac8d8b132c0cc2e" +checksum = "fd95667b47445a6aec7994c6701ade4e250632d38a1a8676c633b99e09897d78" dependencies = [ "ahash", "indexmap", @@ -2312,9 +2385,9 @@ dependencies = [ [[package]] name = "swc_node_comments" -version = "0.16.13" +version = "0.16.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d0dba1a80d962b65b6df0cf07f6a242543ff536539021f6534b05a29ce36316" +checksum = "eed7b4e6db6bd936ce05e368cdcafa81dcd1f1fe8ae21b7b7af1bbf0e8b55869" dependencies = [ "ahash", "dashmap", @@ -2324,9 +2397,9 @@ dependencies = [ [[package]] name = "swc_timer" -version = "0.17.13" +version = "0.17.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb051167c8568445a1917104b55c2ec4db36f458b1864022a066a9b99f14f0dc" +checksum = "34005d58739d4c115eaa8a4b3f5e82eba67dd9b84b55b1f3a8486b6575c83d76" dependencies = [ "tracing", ] @@ -2368,9 +2441,9 @@ dependencies = [ [[package]] name = "syn" -version = "1.0.99" +version = "1.0.103" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "58dbef6ec655055e20b86b15a8cc6d439cca19b667537ac6a1369572d151ab13" +checksum = "a864042229133ada95abf3b54fdc62ef5ccabe9515b64717bcb9a1919e59445d" dependencies = [ "proc-macro2", "quote", @@ -2398,9 +2471,9 @@ dependencies = [ [[package]] name = "terminal_size" -version = "0.2.1" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8440c860cf79def6164e4a0a983bcc2305d82419177a0e0c71930d049e3ac5a1" +checksum = "40ca90c434fd12083d1a6bdcbe9f92a14f96c8a1ba600ba451734ac334521f7a" dependencies = [ "rustix", "windows-sys", @@ -2408,9 +2481,9 @@ dependencies = [ [[package]] name = "textwrap" -version = "0.15.1" +version = "0.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "949517c0cf1bf4ee812e2e07e08ab448e3ae0d23472aee8a06c985f0c8815b16" +checksum = "b1141d4d61095b28419e22cb0bbf02755f5e54e0526f97f1e3d1d160e60885fb" dependencies = [ "smawk", "unicode-linebreak", @@ -2459,9 +2532,9 @@ dependencies = [ [[package]] name = "time" -version = "0.3.14" +version = "0.3.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c3f9a28b618c3a6b9251b6908e9c99e04b9e5c02e6581ccbb67d59c34ef7f9b" +checksum = "d634a985c4d4238ec39cacaed2e7ae552fbd3c476b552c1deac3021b7d7eaf0c" dependencies = [ "itoa", "libc", @@ -2547,12 +2620,12 @@ dependencies = [ [[package]] name = "tracing-tree" -version = "0.2.1" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d07e90b329c621ade432823988574e820212648aa40e7a2497777d58de0fb453" +checksum = "758e983ab7c54fee18403994507e7f212b9005e957ce7984996fac8d11facedb" dependencies = [ - "ansi_term", "atty", + "nu-ansi-term", "tracing-core", "tracing-log", "tracing-subscriber", @@ -2600,15 +2673,15 @@ checksum = "d70b6494226b36008c8366c288d77190b3fad2eb4c10533139c1c1f461127f1a" [[package]] name = "unicode-ident" -version = "1.0.4" +version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dcc811dc4066ac62f84f11307873c4850cb653bfa9b1719cee2bd2204a4bc5dd" +checksum = "6ceab39d59e4c9499d4e5a8ee0e2735b891bb7308ac83dfb4e80cad195c9f6f3" [[package]] name = "unicode-linebreak" -version = "0.1.3" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba853b89cad55680dd3cf06f2798cb1ad8d483f0e2dfa14138d7e789ecee5c4e" +checksum = "c5faade31a542b8b35855fff6e8def199853b2da8da256da52f52f1316ee3137" dependencies = [ "hashbrown", "regex", @@ -2664,7 +2737,7 @@ dependencies = [ "getset", "rustversion", "thiserror", - "time 0.3.14", + "time 0.3.15", ] [[package]] @@ -2785,46 +2858,60 @@ checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] name = "windows-sys" -version = "0.36.1" +version = "0.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea04155a16a59f9eab786fe12a4a450e75cdb175f9e0d80da1e17db09f55b8d2" +checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7" dependencies = [ + "windows_aarch64_gnullvm", "windows_aarch64_msvc", "windows_i686_gnu", "windows_i686_msvc", "windows_x86_64_gnu", + "windows_x86_64_gnullvm", "windows_x86_64_msvc", ] +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d2aa71f6f0cbe00ae5167d90ef3cfe66527d6f613ca78ac8024c3ccab9a19e" + [[package]] name = "windows_aarch64_msvc" -version = "0.36.1" +version = "0.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9bb8c3fd39ade2d67e9874ac4f3db21f0d710bee00fe7cab16949ec184eeaa47" +checksum = "dd0f252f5a35cac83d6311b2e795981f5ee6e67eb1f9a7f64eb4500fbc4dcdb4" [[package]] name = "windows_i686_gnu" -version = "0.36.1" +version = "0.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "180e6ccf01daf4c426b846dfc66db1fc518f074baa793aa7d9b9aaeffad6a3b6" +checksum = "fbeae19f6716841636c28d695375df17562ca208b2b7d0dc47635a50ae6c5de7" [[package]] name = "windows_i686_msvc" -version = "0.36.1" +version = "0.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2e7917148b2812d1eeafaeb22a97e4813dfa60a3f8f78ebe204bcc88f12f024" +checksum = "84c12f65daa39dd2babe6e442988fc329d6243fdce47d7d2d155b8d874862246" [[package]] name = "windows_x86_64_gnu" -version = "0.36.1" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf7b1b21b5362cbc318f686150e5bcea75ecedc74dd157d874d754a2ca44b0ed" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4dcd171b8776c41b97521e5da127a2d86ad280114807d0b2ab1e462bc764d9e1" +checksum = "09d525d2ba30eeb3297665bd434a54297e4170c7f1a44cad4ef58095b4cd2028" [[package]] name = "windows_x86_64_msvc" -version = "0.36.1" +version = "0.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c811ca4a8c853ef420abd8592ba53ddbbac90410fab6903b3e79972a631f7680" +checksum = "f40009d85759725a34da6d89a94e63d7bdc50a862acf0dbc7c8e488f1edcb6f5" [[package]] name = "yansi" diff --git a/Cargo.toml b/Cargo.toml index 948e6038..516415e0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,11 +8,11 @@ edition = "2021" license = "MIT OR Apache-2.0" [workspace.dependencies] -anyhow = "1.0.66" -clap = { version = "4.0.22", features = ["derive", "wrap_help"] } +clap = { version = "4.0.24", features = ["derive", "wrap_help"] } fixedbitset = "0.4.2" itertools = "0.10.5" miette = { version = "5.4.1", features = ["fancy"] } +num-bigint = { version = "0.4.3" } serde = { version = "1.0.147", features = ["derive"] } serde_json = "1.0.87" serde_yaml = "0.9.14" @@ -23,16 +23,17 @@ once_cell = "1.16.0" regex = "1.7.0" rustc-hash = "1.1.0" smallvec = { version = "1.10.0", features = ["union"] } -swc_core = { version = "0.41.3", features = ["common_perf", "common", "common_sourcemap", "ecma_visit_path", "ecma_utils", "ecma_ast", "swc", "ecma_visit", "ecma_transforms", "ecma_transforms_module", "ecma_transforms_typescript", "ecma_parser_typescript"] } +swc_core = { version = "0.43.11", features = ["common_perf", "common", "common_sourcemap", "ecma_visit_path", "ecma_utils", "ecma_ast", "swc", "ecma_visit", "ecma_transforms", "ecma_transforms_module", "ecma_transforms_typescript", "ecma_parser_typescript"] } thiserror = "1.0.37" tracing = "0.1.37" tracing-subscriber = { version = "0.3.16", features = ["env-filter"] } -tracing-tree = "0.2.1" +tracing-tree = "0.2.2" typed-arena = "2.0.1" typed-index-collections = "3.1.0" walkdir = "2.3.2" forge_loader = { path = "crates/forge_loader" } forge_analyzer = { path = "crates/forge_analyzer" } +forge_file_resolver = { path = "crates/forge_file_resolver" } forge_utils = { path = "crates/forge_utils" } [profile.release] diff --git a/crates/forge_file_resolver/Cargo.toml b/crates/forge_file_resolver/Cargo.toml new file mode 100644 index 00000000..d8eca233 --- /dev/null +++ b/crates/forge_file_resolver/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "forge_file_resolver" +version.workspace = true +authors.workspace = true +edition.workspace = true +license.workspace = true + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] diff --git a/crates/forge_file_resolver/src/lib.rs b/crates/forge_file_resolver/src/lib.rs new file mode 100644 index 00000000..47230266 --- /dev/null +++ b/crates/forge_file_resolver/src/lib.rs @@ -0,0 +1,164 @@ +use std::{ + error, fmt, + hash::Hash, + path::{Component, Path, PathBuf}, + result::Result, +}; + +pub trait FileResolver: Sized { + type Id: Copy + Eq + Into + Hash; + type Error; + + fn with_sourceroot>(src: P) -> Self; + + fn add_module>(&mut self, path: P) -> Self::Id; + + fn resolve_import>( + &self, + module: Self::Id, + import: P, + ) -> Result; +} + +#[derive(Debug)] +pub enum Error { + InvalidId(usize), + UnknownModule { + base: usize, + path: PathBuf, + attempts: Vec, + }, +} + +pub struct ForgeResolver { + modules: Vec, + no_ext: Vec, + src: PathBuf, +} + +impl FileResolver for ForgeResolver { + type Id = usize; + type Error = Error; + + fn with_sourceroot>(src: P) -> Self { + let src = src.as_ref(); + assert!(src.ends_with("src")); + Self { + src: normalize_path(src), + no_ext: Default::default(), + modules: Default::default(), + } + } + + #[inline] + fn add_module>(&mut self, path: P) -> usize { + let path = path.as_ref(); + self.add_module_inner(path) + } + + #[inline] + fn resolve_import>(&self, module: Self::Id, import: P) -> Result { + let import = import.as_ref(); + self.resolve_import_path(module, import) + } +} + +fn normalize_path(p: &Path) -> PathBuf { + let mut comps = p.components().peekable(); + let mut normalized = match comps.peek() { + Some(c @ Component::Prefix(_)) => { + let path = PathBuf::from(c.as_os_str()); + comps.next(); + path + } + _ => PathBuf::new(), + }; + + for comp in comps { + match comp { + Component::Prefix(_) => unreachable!("prefix path should be stripped"), + Component::RootDir => normalized.push(comp.as_os_str()), + Component::CurDir => {} + Component::ParentDir => { + normalized.pop(); + } + Component::Normal(c) => normalized.push(c), + } + } + normalized +} + +impl ForgeResolver { + fn add_module_inner(&mut self, path: &Path) -> usize { + let path = normalize_path(path); + self.no_ext.push(path.with_extension("")); + self.modules.push(path); + self.modules.len() + } + + fn search_normalized(&self, path: &Path) -> Option { + let path = normalize_path(path); + let path_no_extension = path.with_extension(""); + self.modules + .iter() + .enumerate() + .zip(&self.no_ext) + .find_map(|((modid, modpath), no_ext)| { + (*modpath == path || *no_ext == path_no_extension).then_some(modid) + }) + } + + fn resolve_import_path(&self, module: usize, import: &Path) -> Result { + let current_file = self.modules.get(module).ok_or(Error::InvalidId(module))?; + // see if the file exists relative to the path of `module` + let relative_path = 'rel: { + let Some(file_dir) = current_file.parent() else { + break 'rel current_file.clone(); + }; + let relative_path = file_dir.join(import); + match self.search_normalized(&relative_path) { + Some(modid) => return Ok(modid), + None => relative_path, + } + }; + + // if not, try to search relative to the base of the project + let project_relative = self.src.join(import); + self.search_normalized(&project_relative) + .ok_or_else(|| Error::UnknownModule { + base: module, + path: import.to_owned(), + attempts: vec![project_relative, relative_path], + }) + } +} + +impl fmt::Display for Error { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Error::InvalidId(modid) => write!(f, "module id {modid} does not exist"), + Error::UnknownModule { + base, + path, + attempts, + } => write!( + f, + "could not find import {path:?} from {base}, tried searching: {attempts:?}" + ), + } + } +} + +impl error::Error for Error {} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_push() { + let mut file_resolver = ForgeResolver::with_sourceroot("test/src"); + let id = file_resolver.add_module("index.js"); + assert_eq!(id, 0); + } +} From 2fbef9e9d9939b6cd508d89e89176456ee5cd59c Mon Sep 17 00:00:00 2001 From: Joshua Wong Date: Wed, 16 Nov 2022 17:36:15 -0600 Subject: [PATCH 007/517] docs: add alternative install instructions --- README.md | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/README.md b/README.md index 74353737..8802676f 100644 --- a/README.md +++ b/README.md @@ -25,12 +25,30 @@ Arguments: ## Installation +You will need to install [Rust] to compile `FSRT`. You can install `Rust` through [Rustup] or through your distro's package manager. You will also +need [Cargo], which comes by default with most `Rust toolchains`.[^1] +latest stable release, and adding the toolchain + +[^1]: Cargo is technically not required if you want to download every dependency, invoke `rustc`, and link everything manually. However, I wouldn't recommend doing this unless you're extremely bored. + +[Rust]: +[Rustup]: "Rustup" +[Cargo]: + Installing from source: ```sh +git clone https://github.com/atlassian-labs/FSRT.git +cd FSRT cargo install --path crates/fsrt ``` +or alternatively: + +```text +cargo install --git https://github.com/atlassian-labs/FSRT +``` + ## Tests To run the test suite: From f72a154567f3bb2ad9fe4e4c8aa5f2e0f4fe44dc Mon Sep 17 00:00:00 2001 From: Joshua Wong Date: Wed, 16 Nov 2022 17:40:35 -0600 Subject: [PATCH 008/517] feat: print the unauthorized case only. --- crates/forge_analyzer/src/engine.rs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/crates/forge_analyzer/src/engine.rs b/crates/forge_analyzer/src/engine.rs index c1a715ab..43204fba 100644 --- a/crates/forge_analyzer/src/engine.rs +++ b/crates/forge_analyzer/src/engine.rs @@ -133,7 +133,13 @@ impl<'ctx> Machine<'ctx> { let result = self.app.func_res(&orig_func); info!(?result, "analysis complete"); let fname: &str = &orig_func.ident.0; - println!("Result of analyzing {fname}: {result}"); + println!("Result of analyzing {fname}:"); + match result { + AuthZVal::Unauthorized => { + println!("FAIL: Unauthorized call detected from handler: {fname}") + } + _ => println!("PASS"), + } result } } From b2ee521b97e17b99561e65014cec56b5a6cb4fb2 Mon Sep 17 00:00:00 2001 From: Joshua Wong Date: Wed, 16 Nov 2022 17:43:29 -0600 Subject: [PATCH 009/517] fix: remove nonexistent dependencies --- crates/forge_analyzer/Cargo.toml | 1 - 1 file changed, 1 deletion(-) diff --git a/crates/forge_analyzer/Cargo.toml b/crates/forge_analyzer/Cargo.toml index 5af77b91..f1b11619 100644 --- a/crates/forge_analyzer/Cargo.toml +++ b/crates/forge_analyzer/Cargo.toml @@ -8,7 +8,6 @@ license.workspace = true # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -anyhow.workspace = true clap.workspace = true itertools.workspace = true fixedbitset.workspace = true From d2b4375bea945c4bfe37af19f6f3f98a895f7d48 Mon Sep 17 00:00:00 2001 From: Joshua Wong Date: Wed, 16 Nov 2022 17:52:48 -0600 Subject: [PATCH 010/517] fix(resolver): return the previous index --- crates/forge_file_resolver/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/forge_file_resolver/src/lib.rs b/crates/forge_file_resolver/src/lib.rs index 47230266..1a40fba6 100644 --- a/crates/forge_file_resolver/src/lib.rs +++ b/crates/forge_file_resolver/src/lib.rs @@ -93,7 +93,7 @@ impl ForgeResolver { let path = normalize_path(path); self.no_ext.push(path.with_extension("")); self.modules.push(path); - self.modules.len() + self.modules.len() - 1 } fn search_normalized(&self, path: &Path) -> Option { From 98ece6635b681713090425a3c0f7f83c430c77fe Mon Sep 17 00:00:00 2001 From: Steffen Opel Date: Thu, 17 Nov 2022 17:52:34 +0100 Subject: [PATCH 011/517] fix: remove trailing whitespace in filename --- .../jira-damn-vulnerable-forge-app/{.eslintrc => .eslintrc} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename test-apps/jira-damn-vulnerable-forge-app/{.eslintrc => .eslintrc} (100%) mode change 100755 => 100644 diff --git a/test-apps/jira-damn-vulnerable-forge-app/.eslintrc b/test-apps/jira-damn-vulnerable-forge-app/.eslintrc old mode 100755 new mode 100644 similarity index 100% rename from test-apps/jira-damn-vulnerable-forge-app/.eslintrc rename to test-apps/jira-damn-vulnerable-forge-app/.eslintrc From 38a8530bb6cbb09944b5a4ab5549cd2118df9dcf Mon Sep 17 00:00:00 2001 From: Joshua Wong Date: Fri, 18 Nov 2022 13:24:40 -0600 Subject: [PATCH 012/517] WIP --- crates/forge_analyzer/Cargo.toml | 13 +- crates/forge_analyzer/src/ctx.rs | 6 + crates/forge_analyzer/src/definitions.rs | 991 +++++++++++++++++++++++ crates/forge_analyzer/src/ir.rs | 89 +- crates/forge_analyzer/src/lib.rs | 1 + crates/forge_utils/src/lib.rs | 4 +- crates/fsrt/Cargo.toml | 5 +- crates/fsrt/src/main.rs | 14 + 8 files changed, 1103 insertions(+), 20 deletions(-) create mode 100644 crates/forge_analyzer/src/definitions.rs diff --git a/crates/forge_analyzer/Cargo.toml b/crates/forge_analyzer/Cargo.toml index f1b11619..3260c535 100644 --- a/crates/forge_analyzer/Cargo.toml +++ b/crates/forge_analyzer/Cargo.toml @@ -9,22 +9,23 @@ license.workspace = true [dependencies] clap.workspace = true -itertools.workspace = true fixedbitset.workspace = true +forge_file_resolver.workspace = true forge_utils.workspace = true +itertools.workspace = true +num-bigint.workspace = true +once_cell.workspace = true petgraph.workspace = true -serde.workspace = true +regex.workspace = true serde_json.workspace = true +serde.workspace = true serde_yaml.workspace = true -once_cell.workspace = true -regex.workspace = true smallvec.workspace = true swc_core.workspace = true -tracing.workspace = true tracing-subscriber.workspace = true +tracing.workspace = true typed-arena.workspace = true typed-index-collections.workspace = true -walkdir.workspace = true [dev-dependencies] pretty_assertions.workspace = true diff --git a/crates/forge_analyzer/src/ctx.rs b/crates/forge_analyzer/src/ctx.rs index 61276097..afe220fc 100644 --- a/crates/forge_analyzer/src/ctx.rs +++ b/crates/forge_analyzer/src/ctx.rs @@ -289,9 +289,15 @@ impl AppCtx { self.modules.keys() } + #[inline] pub fn import_ids(&self) -> &FxHashMap { &self.import_ids } + + #[inline] + pub fn path_ids(&self) -> &FxHashMap { + &self.path_ids + } } impl FunctionMeta { diff --git a/crates/forge_analyzer/src/definitions.rs b/crates/forge_analyzer/src/definitions.rs new file mode 100644 index 00000000..0d404040 --- /dev/null +++ b/crates/forge_analyzer/src/definitions.rs @@ -0,0 +1,991 @@ +#![allow(dead_code, unused)] + +use std::{borrow::Borrow, fmt, mem}; + +use forge_file_resolver::{FileResolver, ForgeResolver}; +use forge_utils::{create_newtype, FxHashMap}; + +use swc_core::{ + common::SyntaxContext, + ecma::{ + ast::{ + AssignProp, BindingIdent, CallExpr, Callee, ClassDecl, ClassExpr, ComputedPropName, + Decl, DefaultDecl, ExportAll, ExportDecl, ExportDefaultDecl, ExportDefaultExpr, + ExportNamedSpecifier, Expr, ExprOrSpread, FnDecl, FnExpr, Id, Ident, ImportDecl, + ImportDefaultSpecifier, ImportNamedSpecifier, ImportStarAsSpecifier, KeyValueProp, Lit, + MemberExpr, MemberProp, MethodProp, Module, ModuleDecl, ModuleExportName, ModuleItem, + NewExpr, ObjectLit, Pat, PrivateName, Prop, PropName, PropOrSpread, Str, VarDecl, + VarDeclarator, + }, + atoms::JsWord, + visit::{noop_visit_type, Visit, VisitWith}, + }, +}; +use tracing::warn; +use typed_index_collections::{TiSlice, TiVec}; + +use crate::{ctx::ModId, ir::Body}; + +create_newtype! { + pub struct GlobalId(u32); +} + +create_newtype! { + pub struct FuncId(u32); +} + +create_newtype! { + pub struct ObjId(u32); +} + +create_newtype! { + pub struct DefId(u32); +} + +const INVALID_FUNC: FuncId = FuncId(u32::MAX); + +const INVALID_CLASS: ObjId = ObjId(u32::MAX); + +const INVALID_GLOBAL: GlobalId = GlobalId(u32::MAX); + +trait DefinitionDb { + fn possible_funcalls(&self, proj: &[Option]) -> &[DefId]; +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +enum DefKind { + Function(FuncId), + Global(GlobalId), + Class(ObjId), + ExportAlias(DefId), + /// exported(usuall) handler to the actual resolver definitions + /// + /// [`DefId`] should point to [`DefKind::Resolver`] + /// + /// # Example: + /// + /// ```javascript + /// import Resolver from '@forge/resolver'; + /// + /// const resolver = new Resolver(); + /// + /// resolver.define('handlerFunc' ({ payload, context }) => { + /// console.log(`payload: ${payload}, ctx: ${context}`); + /// }); + /// // the `handler` symbol resolves to a DefId for [`DefKind::ResolverHandler`] + /// export const handler = resolver.getDefinitions(); + /// ``` + ResolverHandler(DefId), + Resolver(ObjId), + // to account for the common pattern of object literals being used to organize + // functions + ObjLit(ObjId), + // Ex: `module` in import * as 'foo' from 'module' + ModuleNs(ModId), + Foreign(ForeignId), + // should only be set by the initial exporter + Undefined, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +enum ObjKind { + Class(ObjId), + Lit(ObjId), + Resolver(ObjId), +} + +struct NormalizedExport { + module: ModId, + def: DefId, +} + +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +struct Symbol { + module: ModId, + id: Id, +} + +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +struct SymbolExport { + module: ModId, + name: JsWord, +} + +#[derive(Debug, Clone, Default)] +struct ResolverTable { + defs: TiVec, + names: TiVec, + symbol_to_id: FxHashMap, + parent: FxHashMap, + owning_module: TiVec, +} + +struct ModuleDefs { + symbols: FxHashMap, + functions: Box<[DefId]>, + globals: Box<[DefId]>, + classes: Box<[DefId]>, + exports: Box<[DefId]>, +} + +pub fn run_resolver( + modules: &TiSlice, + file_resolver: &ForgeResolver, +) -> (Resolver, TiVec) { + let mut resolver = Resolver::new(); + for (curr_mod, module) in modules.iter_enumerated() { + let mut export_collector = ExportCollector { + res_table: &mut resolver.res_table, + curr_mod, + exports: vec![], + default: None, + }; + module.visit_children_with(&mut export_collector); + let mod_id = resolver.exports.push_and_get_key(export_collector.exports); + debug_assert_eq!(curr_mod, mod_id); + if let Some(default) = export_collector.default { + let def_id = resolver.default_exports.insert(curr_mod, default); + debug_assert_eq!(def_id, None, "def_id shouldn't be set"); + } + } + + let mut foreign_defs = TiVec::default(); + for (curr_mod, module) in modules.iter_enumerated() { + let mut import_collector = ImportCollector { + resolver: &mut resolver, + file_resolver, + foreign_defs: &mut foreign_defs, + curr_mod, + current_import: Default::default(), + in_foreign_import: false, + }; + module.visit_with(&mut import_collector); + } + (resolver, foreign_defs) +} + +/// this struct is a bit of a hack, because we also use it for +/// the definition of "global" scoped object literals, i.e. +/// +/// ```javascript +/// const glbl = { +/// foo: () => { ... }, +/// ... +/// } +/// +/// export default { +/// f1, f2, f3 +/// } +/// ``` +#[derive(Debug, Clone)] +struct Class { + def: DefId, + pub_members: Vec<(JsWord, DefId)>, + constructor: Option, +} + +impl Class { + fn new(def: DefId) -> Self { + Self { + def, + pub_members: vec![], + constructor: None, + } + } +} + +create_newtype! { + pub struct ForeignId(u32); +} + +#[derive(Debug, Clone, PartialEq, Eq)] +enum ImportKind { + Star, + Default, + Named(JsWord), +} + +#[derive(Debug, Clone)] +pub struct ForeignItem { + kind: ImportKind, + module_name: JsWord, +} + +#[derive(Debug, Clone)] +struct Definitions { + funcs: TiVec, + globals: TiVec, + classes: TiVec, + foreign: TiVec, +} + +#[derive(Debug, Clone, Default)] +pub struct Resolver { + exports: TiVec>, + default_exports: FxHashMap, + res_table: ResolverTable, +} + +struct ImportCollector<'cx> { + resolver: &'cx mut Resolver, + file_resolver: &'cx ForgeResolver, + foreign_defs: &'cx mut TiVec, + curr_mod: ModId, + current_import: JsWord, + in_foreign_import: bool, +} + +struct ExportCollector<'cx> { + res_table: &'cx mut ResolverTable, + curr_mod: ModId, + exports: Vec<(JsWord, DefId)>, + default: Option, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +enum LowerStage { + Collect, + Create, +} + +struct Lowerer<'cx> { + res_table: &'cx mut ResolverTable, + defs: &'cx mut Definitions, + curr_mod: ModId, + stage: LowerStage, + parents: Vec, + curr_def: Option, +} + +enum PropPath { + Def(DefId), + Str(JsWord), + Unknown(Id), + Expr, + This, + Super, + Private(Id), + Computed(Id), +} + +fn normalize_callee_expr(callee: &Callee, res_table: &ResolverTable, curr_mod: ModId) { + struct CalleeNormalizer<'cx> { + res_table: &'cx ResolverTable, + curr_mod: ModId, + path: Vec, + in_prop: bool, + } + + impl<'cx> CalleeNormalizer<'cx> { + fn check_prop(&mut self, n: &MemberProp) { + let old_prop = mem::replace(&mut self.in_prop, true); + n.visit_with(self); + self.in_prop = old_prop; + } + } + + impl Visit for CalleeNormalizer<'_> { + fn visit_member_prop(&mut self, n: &MemberProp) { + match n { + MemberProp::Ident(n) => n.visit_with(self), + MemberProp::PrivateName(PrivateName { id, .. }) => { + self.path.push(PropPath::Private(id.to_id())) + } + MemberProp::Computed(ComputedPropName { expr, .. }) => { + let old_prop = mem::replace(&mut self.in_prop, true); + expr.visit_with(self); + self.in_prop = old_prop; + } + } + } + + fn visit_ident(&mut self, n: &Ident) { + let id = n.to_id(); + if self.in_prop { + self.path.push(PropPath::Computed(id)); + } else { + self.path.push(PropPath::Unknown(id)); + } + } + + fn visit_str(&mut self, n: &Str) { + let str = n.value.clone(); + self.path.push(PropPath::Str(str)); + } + + fn visit_lit(&mut self, n: &Lit) { + match n { + Lit::Str(n) => n.visit_with(self), + Lit::Bool(_) + | Lit::Null(_) + | Lit::Num(_) + | Lit::BigInt(_) + | Lit::Regex(_) + | Lit::JSXText(_) => self.path.push(PropPath::Expr), + } + } + + fn visit_expr(&mut self, n: &Expr) { + match n { + Expr::Member(MemberExpr { obj, prop, .. }) if !self.in_prop => { + obj.visit_with(self); + prop.visit_with(self); + } + Expr::This(_) => { + if !self.path.is_empty() { + warn!("thisexpr in prop index: {}", self.path.len()); + } else { + self.path.push(PropPath::This); + } + } + expr @ (Expr::Lit(_) | Expr::Paren(_) | Expr::Ident(_)) => { + expr.visit_children_with(self) + } + + _ => self.path.push(PropPath::Expr), + } + } + } + if let Some(expr) = callee.as_expr() { + let mut normalizer = CalleeNormalizer { + res_table, + curr_mod, + path: vec![], + in_prop: false, + }; + } +} + +impl ResolverTable { + #[inline] + fn sym_id(&self, id: Id, module: ModId) -> Option { + self.symbol_to_id.get(&Symbol { module, id }).copied() + } + + #[inline] + fn sym_kind(&self, id: Id, module: ModId) -> Option { + let def = self.sym_id(id, module)?; + self.defs.get(def).copied() + } + + #[inline] + fn reserve_symbol(&mut self, id: Id, module: ModId) -> DefId { + self.add_sym(DefKind::Undefined, id, module) + } + + #[inline] + fn get_or_insert_sym(&mut self, id: Id, module: ModId) -> DefId { + self.sym_id(id.clone(), module) + .unwrap_or_else(|| self.reserve_symbol(id, module)) + } + + fn reserve_def(&mut self, name: JsWord, module: ModId) -> DefId { + self.defs.push_and_get_key(DefKind::Undefined); + self.names.push_and_get_key(name); + self.owning_module.push_and_get_key(module) + } + + fn add_prop(&mut self, def: DefKind, prop: JsWord, module: ModId) -> DefId { + let defid = self.defs.push_and_get_key(def); + let defid2 = self.owning_module.push_and_get_key(module); + debug_assert_eq!( + defid, defid2, + "inconsistent state while inserting {}", + &*prop + ); + let defid3 = self.names.push_and_get_key(prop); + debug_assert_eq!( + defid, + defid3, + "inconsistent state while inserting {}", + self.names.last().unwrap() + ); + defid + } + + fn add_sym(&mut self, def: DefKind, id: Id, module: ModId) -> DefId { + let defid = self.defs.push_and_get_key(def); + let defid2 = self.owning_module.push_and_get_key(module); + debug_assert_eq!(defid, defid2, "inconsistent state while inserting {}", id.0); + let sym = id.0.clone(); + self.symbol_to_id.insert(Symbol { id, module }, defid2); + let defid3 = self.names.push_and_get_key(sym); + debug_assert_eq!( + defid, + defid3, + "inconsistent state while inserting {}", + self.names.last().unwrap() + ); + defid + } +} + +struct FunctionCollector<'cx> { + res: &'cx mut ResolverTable, + defs: &'cx mut Definitions, + module: ModId, + parent: Option, +} + +impl Lowerer<'_> { + fn defid_from_ident(&self, id: Id) -> Option { + let sym = Symbol { + module: self.curr_mod, + id, + }; + self.res_table.symbol_to_id.get(&sym).copied() + } + + #[inline] + fn reserve_symbol(&mut self, id: Id) -> DefId { + self.res_table.get_or_insert_sym(id, self.curr_mod) + } + + fn defkind_from_ident(&self, id: Id) -> Option { + let cid = id.clone(); + let defid = self.defid_from_ident(id)?; + let defkind = self.res_table.defs.get(defid).copied(); + if defkind.is_none() { + warn!( + module = ?self.curr_mod, + "resolver table has unknown defid: {defid:?} for id: {cid:?}" + ); + } + defkind + } + + fn as_foreign_import(&self, imported_sym: Id, module: &str) -> Option<&ImportKind> { + match self.defkind_from_ident(imported_sym) { + Some(DefKind::Foreign(fid)) => self + .defs + .foreign + .get(fid) + .filter(|&item| item.module_name == *module) + .map(|item| &item.kind), + _ => None, + } + } + + #[inline] + fn next_key(&self) -> DefId { + self.res_table.defs.next_key() + } + + fn def_objlike(&mut self, id: Id, f: impl FnOnce(ObjId) -> ObjKind) -> (DefId, ObjId) { + let idlookup = id.clone(); + match self.defid_from_ident(idlookup) { + Some(def) => { + let defkind = self.res_table.defs[def]; + (def, defkind.as_objkind().unwrap().into_inner()) + } + None => { + let next_key = self.next_key(); + let objid = self.defs.classes.push_and_get_key(Class::new(next_key)); + let objkind = f(objid); + self.res_table + .add_sym(objkind.as_defkind(), id, self.curr_mod); + (next_key, objkind.into_inner()) + } + } + } + + fn add_sym_or_else(&mut self, id: Id, f: impl FnOnce(&mut Self, DefId) -> DefKind) -> DefId { + self.defid_from_ident(id.clone()).unwrap_or_else(|| { + let next_key = self.next_key(); + let defkind = f(self, next_key); + self.res_table.add_sym(defkind, id, self.curr_mod) + }) + } + + fn def_method(&mut self, sym: JsWord) -> DefId { + let func_id = self.defs.funcs.push_and_get_key(Body::default()); + self.res_table + .add_prop(DefKind::Function(func_id), sym, self.curr_mod) + } + + fn def_function(&mut self, id: Id) -> DefId { + self.defid_from_ident(id.clone()).unwrap_or_else(|| { + let funcid = self.defs.funcs.push_and_get_key(Body::default()); + self.res_table + .add_sym(DefKind::Function(funcid), id, self.curr_mod) + }) + } +} + +enum ResolverDef { + FnDef, + Handler, +} + +fn as_resolver( + expr: &Expr, + res_table: &ResolverTable, + module: ModId, +) -> Option<(ObjId, ResolverDef)> { + if let Expr::Member(MemberExpr { + obj, + prop: MemberProp::Ident(prop), + .. + }) = expr + { + let id = obj.as_ident()?; + let def = res_table.sym_kind(id.to_id(), module)?; + if let DefKind::Resolver(obj) = def { + match &*prop.sym { + "getDefinitions" => return Some((obj, ResolverDef::Handler)), + "define" => return Some((obj, ResolverDef::FnDef)), + unknown => { + warn!("unknown prop: {unknown} on resolver: {}", &*id.sym); + } + } + } + } + None +} + +impl Visit for Lowerer<'_> { + noop_visit_type!(); + + fn visit_var_declarator(&mut self, n: &VarDeclarator) { + if let VarDeclarator { + name: Pat::Ident(BindingIdent { id, .. }), + init: Some(expr), + .. + } = n + { + let id = id.to_id(); + match &**expr { + Expr::Arrow(expr) => { + let def_id = self.def_function(id); + let old_def = self.curr_def.replace(def_id); + expr.visit_children_with(self); + self.curr_def = old_def; + } + Expr::Fn(expr) => { + let def_id = self.def_function(id); + let old_def = self.curr_def.replace(def_id); + expr.visit_children_with(self); + self.curr_def = old_def; + } + Expr::Call(CallExpr { + callee: Callee::Expr(expr), + args, + .. + }) => { + if let Some((objid, kind)) = as_resolver(expr, self.res_table, self.curr_mod) { + match kind { + ResolverDef::FnDef => { + if let [ExprOrSpread { expr: name, .. }, ExprOrSpread { expr: args, .. }] = + &**args + { + if let Expr::Lit(Lit::Str(Str { value, .. })) = &**expr { + let fname = value.clone(); + let class = &mut self.defs.classes[objid]; + class.pub_members.push((fname, self.curr_def.unwrap())); + } + } + } + ResolverDef::Handler => todo!(), + } + } + expr.visit_children_with(self); + } + Expr::Object(ObjectLit { props, .. }) => { + let (def_id, obj_id) = self.def_objlike(id, ObjKind::Lit); + let old_def = self.curr_def.replace(def_id); + for prop in props { + match prop { + // TODO: track 'spreaded' objects + PropOrSpread::Spread(_) => {} + PropOrSpread::Prop(prop) => match &**prop { + Prop::Shorthand(id) => { + let id = id.to_id(); + let sym = id.0.clone(); + let def_id = self.reserve_symbol(id); + self.defs.classes[obj_id].pub_members.push((sym, def_id)); + } + Prop::KeyValue(KeyValueProp { key, value }) => { + let cls = &mut self.defs.classes[obj_id]; + if let sym @ Some(_) = key.as_symbol() { + let defid = value.as_ident().map(|id| { + self.res_table + .get_or_insert_sym(id.to_id(), self.curr_mod) + }); + cls.pub_members.extend(sym.zip(defid)); + } + } + Prop::Assign(AssignProp { key, .. }) => { + let obj_sym = &self.res_table.names[def_id]; + warn!("object {obj_sym:?} invalid assign prop {:?}", &key.sym); + } + /// TODO: track these + Prop::Getter(_) | Prop::Setter(_) => {} + Prop::Method(MethodProp { key, function }) => { + function.body.visit_with(self); + if let Some(sym) = key.as_symbol() { + let def_id = self.def_method(sym.clone()); + let cls = &mut self.defs.classes[obj_id]; + cls.pub_members.push((sym, def_id)); + } + } + }, + } + } + } + Expr::New(NewExpr { callee, .. }) => { + let Some(id) = callee.as_ident() else { + expr.visit_children_with(self); + return; + }; + let id = id.to_id(); + if Some(&ImportKind::Default) + == self.as_foreign_import(id.clone(), "@forge/resolver") + { + self.add_sym_or_else(id, |this, to_insert| { + let obj_id = this.defs.classes.push_and_get_key(Class::new(to_insert)); + DefKind::Resolver(obj_id) + }); + } + expr.visit_children_with(self); + } + _ => {} + } + } + } + + fn visit_fn_decl(&mut self, n: &FnDecl) { + let id = n.ident.to_id(); + let _defid = self.def_function(id); + } +} + +trait AsSymbol { + fn expect_symbol(&self) -> JsWord { + self.as_symbol().unwrap() + } + fn as_symbol(&self) -> Option; +} + +impl AsSymbol for PropName { + fn as_symbol(&self) -> Option { + match self { + PropName::Str(Str { value: sym, .. }) | PropName::Ident(Ident { sym, .. }) => { + Some(sym.clone()) + } + PropName::Num(_) | PropName::Computed(_) | PropName::BigInt(_) => None, + } + } +} + +impl ExportCollector<'_> { + fn add_export(&mut self, def: DefKind, id: Id) -> DefId { + let exported_sym = id.0.clone(); + let defid = self.res_table.add_sym(def, id, self.curr_mod); + self.exports.push((exported_sym, defid)); + defid + } + + fn add_default(&mut self, def: DefKind, id: Option) { + let defid = match id { + Some(id) => self.res_table.add_sym(def, id, self.curr_mod), + None => { + self.res_table.names.push("default".into()); + self.res_table.defs.push_and_get_key(def) + } + }; + self.default = Some(defid); + } +} + +impl Visit for ImportCollector<'_> { + noop_visit_type!(); + + fn visit_import_decl(&mut self, n: &ImportDecl) { + let Str { value, .. } = &*n.src; + let old_import = mem::replace(&mut self.current_import, value.clone()); + n.visit_children_with(self); + self.current_import = old_import; + } + + fn visit_import_named_specifier(&mut self, n: &ImportNamedSpecifier) { + if n.is_type_only { + return; + } + let ImportNamedSpecifier { + local, imported, .. + } = n; + let local = local.to_id(); + let import_name = imported + .as_ref() + .map_or_else(|| local.0.clone(), export_name_to_jsword); + + match self + .file_resolver + .resolve_import(self.curr_mod.into(), &*self.current_import) + { + Ok(id) => { + // TODO: find exported symbols + if let Some(def_id) = self + .resolver + .resolve_local_export(ModId::from(id), &import_name) + { + self.resolver.res_table.symbol_to_id.insert( + Symbol { + module: self.curr_mod, + id: local, + }, + def_id, + ); + } + } + Err(_) => { + let foreign_id = self.foreign_defs.push_and_get_key(ForeignItem { + kind: ImportKind::Named(import_name), + module_name: self.current_import.clone(), + }); + self.resolver + .res_table + .add_sym(DefKind::Foreign(foreign_id), local, self.curr_mod); + } + } + } + + fn visit_import_default_specifier(&mut self, n: &ImportDefaultSpecifier) { + let local = n.local.to_id(); + match self + .file_resolver + .resolve_import(self.curr_mod.into(), &*self.current_import) + { + Ok(id) => { + let mod_id = ModId::from(id); + debug_assert_ne!(self.curr_mod, mod_id); + match self.resolver.default_export(mod_id) { + Some(def) => { + self.resolver.res_table.symbol_to_id.insert( + Symbol { + module: self.curr_mod, + id: local, + }, + def, + ); + } + None => warn!("unable to find default import for {}", &self.current_import), + } + } + Err(_) => { + let foreign_id = self.foreign_defs.push_and_get_key(ForeignItem { + kind: ImportKind::Default, + module_name: self.current_import.clone(), + }); + + self.resolver + .res_table + .add_sym(DefKind::Foreign(foreign_id), local, self.curr_mod); + } + }; + } + + fn visit_import_star_as_specifier(&mut self, n: &ImportStarAsSpecifier) { + let local = n.local.to_id(); + let defkind = match self + .file_resolver + .resolve_import(self.curr_mod.into(), &*self.current_import) + { + Ok(id) => { + let mod_id = ModId::from(id); + debug_assert_ne!(self.curr_mod, mod_id); + DefKind::ModuleNs(mod_id) + } + Err(_) => { + let foreign_id = self.foreign_defs.push_and_get_key(ForeignItem { + kind: ImportKind::Star, + module_name: self.current_import.clone(), + }); + DefKind::Foreign(foreign_id) + } + }; + self.resolver + .res_table + .add_sym(defkind, local, self.curr_mod); + } + + fn visit_module_item(&mut self, n: &ModuleItem) { + if let ModuleItem::ModuleDecl(ModuleDecl::Import(n)) = n { + n.visit_with(self); + } + } +} + +impl<'cx> Visit for ExportCollector<'cx> { + noop_visit_type!(); + fn visit_export_decl(&mut self, n: &ExportDecl) { + match &n.decl { + Decl::Class(ClassDecl { ident, .. }) => { + let ident = ident.to_id(); + self.add_export(DefKind::Class(INVALID_CLASS), ident); + } + Decl::Fn(FnDecl { ident, .. }) => { + let ident = ident.to_id(); + self.add_export(DefKind::Function(INVALID_FUNC), ident); + } + Decl::Var(vardecls) => { + let VarDecl { decls, .. } = &**vardecls; + decls.iter().for_each(|var| self.visit_var_declarator(var)); + } + Decl::TsInterface(_) => {} + Decl::TsTypeAlias(_) => {} + Decl::TsEnum(_) => {} + Decl::TsModule(_) => {} + }; + } + + fn visit_var_declarator(&mut self, n: &VarDeclarator) { + // TODO: handle other kinds of destructuring patterns + if let Pat::Ident(BindingIdent { id, .. }) = &n.name { + let id = id.to_id(); + self.add_export(DefKind::Undefined, id); + } + } + + fn visit_module_item(&mut self, n: &ModuleItem) { + match n { + ModuleItem::ModuleDecl(decl) + if matches!( + decl, + ModuleDecl::ExportDecl(_) + | ModuleDecl::ExportDefaultDecl(_) + | ModuleDecl::ExportDefaultExpr(_) + | ModuleDecl::ExportAll(_) + | ModuleDecl::ExportNamed(_) + ) => + { + decl.visit_children_with(self) + } + _ => {} + } + } + + fn visit_export_all(&mut self, _: &ExportAll) {} + + fn visit_export_default_decl(&mut self, n: &ExportDefaultDecl) { + match &n.decl { + DefaultDecl::Class(ClassExpr { ident, .. }) => self.add_default( + DefKind::Class(INVALID_CLASS), + ident.as_ref().map(Ident::to_id), + ), + DefaultDecl::Fn(FnExpr { ident, .. }) => self.add_default( + DefKind::Function(INVALID_FUNC), + ident.as_ref().map(Ident::to_id), + ), + DefaultDecl::TsInterfaceDecl(_) => {} + } + } + + fn visit_export_named_specifier(&mut self, n: &ExportNamedSpecifier) { + let orig_id = n.orig.as_id(); + let orig = self.add_export(DefKind::Undefined, orig_id); + if let Some(id) = &n.exported { + let exported_id = id.as_id(); + self.add_export(DefKind::ExportAlias(orig), exported_id); + } + } + + fn visit_export_default_expr(&mut self, _: &ExportDefaultExpr) { + self.add_default(DefKind::Undefined, None); + } +} + +impl Resolver { + fn new() -> Self { + Self { + exports: Default::default(), + default_exports: Default::default(), + res_table: Default::default(), + } + } + + #[inline] + fn default_export(&self, module: ModId) -> Option { + self.default_exports.get(&module).copied() + } + + fn resolve_local_export(&self, module: ModId, name: &JsWord) -> Option { + match &**name { + "default" => self.default_exports.get(&module).copied(), + _ => self.exports.get(module).and_then(|exports| { + exports + .iter() + .find_map(|&(ref export, def_id)| (*export == *name).then_some(def_id)) + }), + } + } +} + +trait AsId { + fn as_id(&self) -> Id; +} + +fn export_name_to_jsword(expname: &ModuleExportName) -> JsWord { + match expname { + ModuleExportName::Ident(ident) => ident.to_id().0, + ModuleExportName::Str(str) => str.value.clone(), + } +} + +impl ObjKind { + #[inline] + fn into_inner(self) -> ObjId { + match self { + ObjKind::Lit(id) | ObjKind::Resolver(id) | ObjKind::Class(id) => id, + } + } + + #[inline] + fn as_defkind(&self) -> DefKind { + match *self { + ObjKind::Class(id) => DefKind::Class(id), + ObjKind::Lit(id) => DefKind::ObjLit(id), + ObjKind::Resolver(id) => DefKind::Resolver(id), + } + } +} + +impl DefKind { + #[inline] + fn as_objkind(&self) -> Option { + match *self { + DefKind::Class(id) => Some(ObjKind::Class(id)), + DefKind::Resolver(id) => Some(ObjKind::Resolver(id)), + DefKind::ObjLit(id) => Some(ObjKind::Lit(id)), + DefKind::Function(_) + | DefKind::Global(_) + | DefKind::ExportAlias(_) + | DefKind::ResolverHandler(_) + | DefKind::ModuleNs(_) + | DefKind::Foreign(_) + | DefKind::Undefined => None, + } + } +} + +impl From for DefKind { + #[inline] + fn from(value: ObjKind) -> Self { + value.as_defkind() + } +} + +impl AsId for ModuleExportName { + fn as_id(&self) -> Id { + match self { + ModuleExportName::Ident(ident) => ident.to_id(), + ModuleExportName::Str(str) => (str.value.clone(), SyntaxContext::empty()), + } + } +} + +impl fmt::Display for GlobalId { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "@G{}", self.0) + } +} diff --git a/crates/forge_analyzer/src/ir.rs b/crates/forge_analyzer/src/ir.rs index 9b1b8e12..5bf77be1 100644 --- a/crates/forge_analyzer/src/ir.rs +++ b/crates/forge_analyzer/src/ir.rs @@ -3,7 +3,11 @@ // TODO: Use [`SSA`] instead // [`SSA`]: https://pfalcon.github.io/ssabook/latest/book-full.pdf +use core::fmt; use std::collections::BTreeSet; +use std::hash; +use std::hash::Hash; +use std::mem; use forge_utils::create_newtype; use forge_utils::FxHashMap; @@ -65,10 +69,10 @@ struct Location { } #[derive(Clone, Default, Debug)] -struct Function { +pub(crate) struct Body { blocks: TiVec, - vars: TiVec, - id_to_var: FxHashMap, + local_vars: TiVec, + id_to_local: FxHashMap, predecessors: BTreeSet<(BasicBlockId, BasicBlockId)>, successors: BTreeSet<(BasicBlockId, BasicBlockId)>, } @@ -87,14 +91,15 @@ enum Stmt { Assign(Variable, Rvalue), } -#[derive(Clone, Debug, PartialEq)] +#[derive(Clone, Debug, Default)] enum Literal { Str(Id), Bool(bool), Null, + #[default] Undef, // what a bunk language Number(f64), - BigInt, + BigInt(num_bigint::BigInt), // regexp, flags RegExp(Atom, Atom), } @@ -136,7 +141,7 @@ enum UnOp { Delete, } -#[derive(Clone, Debug, PartialEq)] +#[derive(Clone, Debug, PartialEq, Eq, Hash)] enum Operand { Var(Variable), Lit(Literal), @@ -151,20 +156,84 @@ create_newtype! { pub struct VarId(u32); } -#[derive(Clone, Debug, PartialEq)] +#[derive(Clone, Debug, PartialEq, Eq, Hash)] struct Variable { var: VarId, projections: SmallVec<[Projection; 1]>, } -#[derive(Clone, Debug, PartialEq)] +#[derive(Clone, Debug, PartialEq, Eq, Hash)] enum Projection { Lit(Literal), Var(VarId), } -impl Function { +impl fmt::Display for VarId { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "%{}", self.0) + } +} + +impl fmt::Display for Variable { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", self.var)?; + for proj in &self.projections { + match proj { + Projection::Lit(Literal::Str((s, _))) => write!(f, ".{s}")?, + Projection::Lit(lit) => write!(f, "[{lit}]")?, + Projection::Var(id) => write!(f, "[{id}]")?, + } + } + Ok(()) + } +} + +impl Body { fn new() -> Self { - Function::default() + Body::default() + } +} + +impl PartialEq for Literal { + fn eq(&self, other: &Self) -> bool { + match (self, other) { + (Self::Str(l0), Self::Str(r0)) => l0 == r0, + (Self::Bool(l0), Self::Bool(r0)) => l0 == r0, + (Self::Number(l0), Self::Number(r0)) => l0 == r0, + (Self::RegExp(l0, l1), Self::RegExp(r0, r1)) => l0 == r0 && l1 == r1, + (Self::BigInt(l0), Self::BigInt(r0)) => l0 == r0, + (l0, r0) => mem::discriminant(l0) == mem::discriminant(r0), + } + } +} + +// I don't like it either, but in JS NaNs are compared bitwise +impl Eq for Literal {} + +impl Hash for Literal { + fn hash(&self, state: &mut H) { + mem::discriminant(self).hash(state); + match self { + Literal::Str(s) => s.hash(state), + Literal::Bool(b) => b.hash(state), + Literal::Null | Literal::Undef => {} + Literal::Number(n) => n.to_bits().hash(state), + Literal::BigInt(bn) => bn.hash(state), + Literal::RegExp(s, t) => (s, t).hash(state), + } + } +} + +impl fmt::Display for Literal { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match *self { + Literal::Str((ref s, _)) => write!(f, "\"{s}\""), + Literal::Bool(b) => write!(f, "{b}"), + Literal::Null => write!(f, "[null]"), + Literal::Undef => write!(f, "[undefined]"), + Literal::Number(n) => write!(f, "{n}"), + Literal::BigInt(ref n) => write!(f, "{n}"), + Literal::RegExp(ref regex, ref flags) => write!(f, "/{regex}/{flags}"), + } } } diff --git a/crates/forge_analyzer/src/lib.rs b/crates/forge_analyzer/src/lib.rs index 0f02d8d6..da6a47b0 100644 --- a/crates/forge_analyzer/src/lib.rs +++ b/crates/forge_analyzer/src/lib.rs @@ -1,5 +1,6 @@ pub mod analyzer; pub mod ctx; +pub mod definitions; pub mod engine; pub mod exports; pub mod ir; diff --git a/crates/forge_utils/src/lib.rs b/crates/forge_utils/src/lib.rs index ba261877..df2bc0f4 100644 --- a/crates/forge_utils/src/lib.rs +++ b/crates/forge_utils/src/lib.rs @@ -13,9 +13,9 @@ pub use rustc_hash::{FxHashMap, FxHashSet}; #[macro_export] macro_rules! create_newtype { - ($vis:vis struct $ident:ident ( $ty:ty );) => { + ($vis:vis struct $ident:ident ( $tyvis:vis $ty:ty );) => { #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)] - $vis struct $ident($ty); + $vis struct $ident($tyvis $ty); impl ::core::convert::From for $ident { fn from(n: usize) -> $ident { diff --git a/crates/fsrt/Cargo.toml b/crates/fsrt/Cargo.toml index 1554c7e9..55e1ec06 100644 --- a/crates/fsrt/Cargo.toml +++ b/crates/fsrt/Cargo.toml @@ -9,14 +9,15 @@ license.workspace = true [dependencies] clap.workspace = true -forge_loader.workspace = true forge_analyzer.workspace = true +forge_file_resolver.workspace = true +forge_loader.workspace = true miette.workspace = true rustc-hash.workspace = true serde.workspace = true serde_yaml.workspace = true swc_core.workspace = true -tracing.workspace = true tracing-subscriber.workspace = true tracing-tree.workspace = true +tracing.workspace = true walkdir.workspace = true diff --git a/crates/fsrt/src/main.rs b/crates/fsrt/src/main.rs index e8842c86..1a5aaedf 100644 --- a/crates/fsrt/src/main.rs +++ b/crates/fsrt/src/main.rs @@ -32,6 +32,7 @@ use forge_analyzer::{ engine::Machine, resolver::{dump_callgraph_dot, dump_cfg_dot, resolve_calls}, }; +use forge_file_resolver::{FileResolver, ForgeResolver}; use forge_loader::manifest::{ForgeManifest, FunctionRef, FunctionTy}; use walkdir::WalkDir; @@ -215,6 +216,19 @@ fn scan_directory(dir: PathBuf, function: Option<&str>, opts: Opts) -> Result Date: Thu, 24 Nov 2022 23:29:58 -0600 Subject: [PATCH 013/517] feat: add new resolver --- crates/forge_analyzer/src/ctx.rs | 19 +++++- crates/forge_analyzer/src/definitions.rs | 73 +++++++++++++++++++-- crates/forge_file_resolver/src/lib.rs | 10 +++ crates/fsrt/src/main.rs | 82 +++++++++++++----------- 4 files changed, 139 insertions(+), 45 deletions(-) diff --git a/crates/forge_analyzer/src/ctx.rs b/crates/forge_analyzer/src/ctx.rs index afe220fc..7b8beccc 100644 --- a/crates/forge_analyzer/src/ctx.rs +++ b/crates/forge_analyzer/src/ctx.rs @@ -1,6 +1,8 @@ use std::iter::repeat; +use std::path::Path; use std::{borrow::Borrow, hash::Hash, path::PathBuf}; +use forge_file_resolver::{FileResolver, ForgeResolver}; use forge_utils::create_newtype; use forge_utils::FxHashMap; use once_cell::unsync::OnceCell; @@ -110,10 +112,11 @@ pub struct ModuleCtx { pub(crate) functions: FxHashMap, } -#[derive(Default, Clone, Debug)] +#[derive(Clone, Debug)] pub struct AppCtx { // Map from import Id -> module name pub(crate) import_ids: FxHashMap, + pub(crate) file_resolver: ForgeResolver, pub(crate) path_ids: FxHashMap, pub(crate) modules: TiVec, @@ -122,9 +125,10 @@ pub struct AppCtx { impl AppCtx { #[inline] - pub fn new() -> Self { + pub fn new>(src_root: P) -> Self { Self { import_ids: FxHashMap::default(), + file_resolver: ForgeResolver::with_sourceroot(src_root), path_ids: FxHashMap::default(), modules: TiVec::default(), modctx: TiVec::default(), @@ -135,6 +139,7 @@ impl AppCtx { pub fn load_module(&mut self, path: PathBuf, module: Module) -> ModId { let modctx = lower_module(&module); let mod_id = self.modules.push_and_get_key(module); + self.file_resolver.add_module(path.clone()); self.modctx.insert(mod_id, modctx); self.path_ids.insert(path, mod_id); mod_id @@ -298,6 +303,16 @@ impl AppCtx { pub fn path_ids(&self) -> &FxHashMap { &self.path_ids } + + #[inline] + pub fn file_resolver(&self) -> &ForgeResolver { + &self.file_resolver + } + + #[inline] + pub fn modules(&self) -> &TiVec { + &self.modules + } } impl FunctionMeta { diff --git a/crates/forge_analyzer/src/definitions.rs b/crates/forge_analyzer/src/definitions.rs index 0d404040..0cda2645 100644 --- a/crates/forge_analyzer/src/definitions.rs +++ b/crates/forge_analyzer/src/definitions.rs @@ -53,7 +53,7 @@ trait DefinitionDb { } #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] -enum DefKind { +pub enum DefKind { Function(FuncId), Global(GlobalId), Class(ObjId), @@ -184,6 +184,20 @@ struct Class { constructor: Option, } +enum ObjTy { + Class { constructor: DefId }, + Lit, + LitProp { parent: DefId }, + ResolverProp { parent: DefId }, + Resolver, +} + +struct Object { + def: DefId, + pub_members: Vec<(JsWord, DefId)>, + ty: ObjTy, +} + impl Class { fn new(def: DefId) -> Self { Self { @@ -690,6 +704,7 @@ impl ExportCollector<'_> { Some(id) => self.res_table.add_sym(def, id, self.curr_mod), None => { self.res_table.names.push("default".into()); + self.res_table.owning_module.push(self.curr_mod); self.res_table.defs.push_and_get_key(def) } }; @@ -897,18 +912,29 @@ impl<'cx> Visit for ExportCollector<'cx> { impl Resolver { fn new() -> Self { - Self { - exports: Default::default(), - default_exports: Default::default(), - res_table: Default::default(), - } + Self::default() } #[inline] - fn default_export(&self, module: ModId) -> Option { + pub fn default_export(&self, module: ModId) -> Option { self.default_exports.get(&module).copied() } + #[inline] + pub fn def_name(&self, def: DefId) -> &str { + &self.res_table.names[def] + } + + #[inline] + pub fn module_exports(&self, module: ModId) -> impl Iterator + '_ { + self.exports[module].iter().map(|(k, v)| (&**k, *v)) + } + + #[inline] + pub fn def_kind(&self, def: DefId) -> DefKind { + self.res_table.defs[def] + } + fn resolve_local_export(&self, module: ModId, name: &JsWord) -> Option { match &**name { "default" => self.default_exports.get(&module).copied(), @@ -968,6 +994,39 @@ impl DefKind { } } +impl fmt::Display for DefKind { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + DefKind::Class(_) => write!(f, "class"), + DefKind::Resolver(_) => write!(f, "resolver"), + DefKind::ObjLit(_) => write!(f, "object literal"), + DefKind::Function(_) => write!(f, "function"), + DefKind::Global(_) => write!(f, "global"), + DefKind::ExportAlias(_) => write!(f, "export alias"), + DefKind::ResolverHandler(_) => write!(f, "resolver handler"), + DefKind::ModuleNs(_) => write!(f, "module namespace"), + DefKind::Foreign(_) => write!(f, "foreign"), + DefKind::Undefined => write!(f, "undefined"), + } + } +} + +impl fmt::Display for ImportKind { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + ImportKind::Star => write!(f, "*"), + ImportKind::Default => write!(f, "default"), + ImportKind::Named(sym) => write!(f, "{}", &**sym), + } + } +} + +impl fmt::Display for ForeignItem { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "import {} from {}", self.kind, &*self.module_name) + } +} + impl From for DefKind { #[inline] fn from(value: ObjKind) -> Self { diff --git a/crates/forge_file_resolver/src/lib.rs b/crates/forge_file_resolver/src/lib.rs index 1a40fba6..11e87255 100644 --- a/crates/forge_file_resolver/src/lib.rs +++ b/crates/forge_file_resolver/src/lib.rs @@ -11,6 +11,8 @@ pub trait FileResolver: Sized { fn with_sourceroot>(src: P) -> Self; + fn get_module_path(&self, id: Self::Id) -> Result<&Path, Self::Error>; + fn add_module>(&mut self, path: P) -> Self::Id; fn resolve_import>( @@ -30,6 +32,7 @@ pub enum Error { }, } +#[derive(Debug, Clone)] pub struct ForgeResolver { modules: Vec, no_ext: Vec, @@ -50,6 +53,13 @@ impl FileResolver for ForgeResolver { } } + fn get_module_path(&self, id: Self::Id) -> Result<&Path, Self::Error> { + self.modules + .get(id) + .map(PathBuf::as_path) + .ok_or(Error::InvalidId(id)) + } + #[inline] fn add_module>(&mut self, path: P) -> usize { let path = path.as_ref(); diff --git a/crates/fsrt/src/main.rs b/crates/fsrt/src/main.rs index 1a5aaedf..49136fe3 100644 --- a/crates/fsrt/src/main.rs +++ b/crates/fsrt/src/main.rs @@ -4,7 +4,6 @@ use std::{ convert::TryFrom, fs, io::stdout, - iter::FromIterator, os::unix::prelude::OsStrExt, path::{Path, PathBuf}, sync::Arc, @@ -29,10 +28,11 @@ use tracing_tree::HierarchicalLayer; use forge_analyzer::{ analyzer::AuthZVal, ctx::{AppCtx, ModId, ModItem}, + definitions::run_resolver, engine::Machine, resolver::{dump_callgraph_dot, dump_cfg_dot, resolve_calls}, }; -use forge_file_resolver::{FileResolver, ForgeResolver}; +use forge_file_resolver::FileResolver; use forge_loader::manifest::{ForgeManifest, FunctionRef, FunctionTy}; use walkdir::WalkDir; @@ -66,7 +66,6 @@ struct Opts { dump_callgraph: bool, } -#[derive(Default)] struct ForgeProject { #[allow(dead_code)] sm: Arc, @@ -76,16 +75,6 @@ struct ForgeProject { } impl ForgeProject { - #[inline] - fn new() -> Self { - Self { - sm: Default::default(), - ctx: Default::default(), - funcs: vec![], - opts: Default::default(), - } - } - #[instrument(level = "debug", skip(self))] fn verify_funs(&mut self) -> impl Iterator + '_ { self.funcs.iter().cloned().map(|fty| { @@ -123,11 +112,14 @@ impl ForgeProject { (funcitem, res) } - fn with_files>(iter: I) -> Self { + fn with_files_and_sourceroot, I: IntoIterator>( + src: P, + iter: I, + ) -> Self { let sm = Arc::::default(); let target = EsVersion::latest(); let globals = Globals::new(); - let ctx = AppCtx::new(); + let ctx = AppCtx::new(src); let ctx = iter.into_iter().fold(ctx, |mut ctx, p| { let sourcemap = Arc::clone(&sm); GLOBALS.set(&globals, || { @@ -155,7 +147,8 @@ impl ForgeProject { Self { sm, ctx, - ..ForgeProject::new() + funcs: vec![], + opts: Opts::default(), } } @@ -170,13 +163,6 @@ impl ForgeProject { } } -impl FromIterator for ForgeProject { - #[inline] - fn from_iter>(iter: T) -> Self { - Self::with_files(iter) - } -} - fn is_js_file>(path: P) -> bool { matches!( path.as_ref().extension().map(|s| s.as_bytes()), @@ -212,23 +198,47 @@ fn scan_directory(dir: PathBuf, function: Option<&str>, opts: Opts) -> Result(resolved_func.into_func_path()) }) }); - let mut proj = ForgeProject::with_files(paths.clone()); + let src_root = dir.join("src"); + let mut proj = ForgeProject::with_files_and_sourceroot(src_root, paths.clone()); proj.opts = opts; proj.add_funcs(funcrefs); resolve_calls(&mut proj.ctx); - for path in proj.ctx.path_ids().keys() { - let path = path.strip_prefix(&dir).unwrap(); - let path = path - .parent() - .unwrap() - .join("../src/auth.jsx") - .canonicalize() - .unwrap(); - println!("stripped path: {path:?}"); - let path = path.to_string_lossy(); - let (base, src) = path.split_once("src/").unwrap(); - println!("resolved path = {base} {src}"); + let (res, foreign) = run_resolver(proj.ctx.modules(), proj.ctx.file_resolver()); + proj.ctx + .modules() + .iter_enumerated() + .map(|(id, _)| id) + .for_each(|id| { + println!( + "path: {:?}", + proj.ctx.file_resolver().get_module_path(id.into()).unwrap() + ); + for (sym, def) in res.module_exports(id) { + let kind = res.def_kind(def); + println!("{sym}: {def:?} kind: {kind}"); + } + if let Some(def) = res.default_export(id) { + let kind = res.def_kind(def); + println!("default: {def:?} defkind: {kind}"); + } + }); + for item in &foreign { + println!("foreign: {item}"); } + + // for path in proj.ctx.path_ids().keys() { + // let path = path.strip_prefix(&dir).unwrap(); + // let path = path + // .parent() + // .unwrap() + // .join("../src/auth.jsx") + // .canonicalize() + // .unwrap(); + // println!("stripped path: {path:?}"); + // let path = path.to_string_lossy(); + // let (base, src) = path.split_once("src/").unwrap(); + // println!("resolved path = {base} {src}"); + // } if let Some(func) = function { proj.verify_fun(func); } else { From 9e59bf4a72fa8f230d7d141f0c7e72bd7756d7eb Mon Sep 17 00:00:00 2001 From: Joshua Wong Date: Fri, 25 Nov 2022 00:46:18 -0600 Subject: [PATCH 014/517] chore: bump deps --- Cargo.lock | 289 ++++++++++++++++++++++++++++++++--------------------- Cargo.toml | 10 +- 2 files changed, 178 insertions(+), 121 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e26804fe..44145cf2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -41,9 +41,9 @@ dependencies = [ [[package]] name = "aho-corasick" -version = "0.7.19" +version = "0.7.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4f55bd91a0978cbfd91c457a164bab8b4001c833b7f323132c0a4e1922dd44e" +checksum = "cc936419f96fa211c1b9166887b38e5e40b19958e5b895be7c1f93adec7071ac" dependencies = [ "memchr", ] @@ -89,7 +89,7 @@ version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" dependencies = [ - "hermit-abi", + "hermit-abi 0.1.19", "libc", "winapi", ] @@ -129,9 +129,9 @@ dependencies = [ [[package]] name = "base64" -version = "0.13.0" +version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd" +checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" [[package]] name = "better_scoped_tls" @@ -189,9 +189,9 @@ checksum = "c1ad822118d20d2c234f427000d5acc36eabe1e29a348c89b63dd60b13f28e5d" [[package]] name = "cc" -version = "1.0.76" +version = "1.0.77" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76a284da2e6fe2092f2353e51713435363112dfd60030e22add80be333fb928f" +checksum = "e9f73505338f7d905b19d18738976aae232eb46b8efc15554ffc56deb5d9ebe4" [[package]] name = "cfg-if" @@ -201,9 +201,9 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "chrono" -version = "0.4.22" +version = "0.4.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfd4d1b31faaa3a89d7934dbded3111da0d2ef28e3ebccdb4f0179f5929d1ef1" +checksum = "16b0a3d9ed01224b22057780a37bb8c5dbfe1be8ba48678e7bf57ec4b385411f" dependencies = [ "iana-time-zone", "js-sys", @@ -216,14 +216,14 @@ dependencies = [ [[package]] name = "clap" -version = "4.0.24" +version = "4.0.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60494cedb60cb47462c0ff7be53de32c0e42a6fc2c772184554fa12bd9489c03" +checksum = "0acbd8d28a0a60d7108d7ae850af6ba34cf2d1257fc646980e5f97ce14275966" dependencies = [ - "atty", "bitflags", "clap_derive", "clap_lex", + "is-terminal", "once_cell", "strsim", "termcolor", @@ -300,9 +300,9 @@ dependencies = [ [[package]] name = "crossbeam-epoch" -version = "0.9.11" +version = "0.9.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f916dfc5d356b0ed9dae65f1db9fc9770aa2851d2662b988ccf4fe3516e86348" +checksum = "01a9af1f4c2ef74bb8aa1f7e19706bc72d03598c8a570bb5de72243c7a9d9d5a" dependencies = [ "autocfg", "cfg-if", @@ -313,9 +313,9 @@ dependencies = [ [[package]] name = "crossbeam-utils" -version = "0.8.12" +version = "0.8.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "edbafec5fa1f196ca66527c1b12c2ec4745ca14b50f1ad8f9f6f720b55d11fac" +checksum = "4fb766fa798726286dbbb842f174001dab8abc7b627a1dd86e0b7222a95d929f" dependencies = [ "cfg-if", ] @@ -440,9 +440,9 @@ checksum = "56254986775e3233ffa9c4d7d3faaf6d36a2c09d30b20687e9f88bc8bafc16c8" [[package]] name = "digest" -version = "0.10.5" +version = "0.10.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "adfbc57365a37acbd2ebf2b64d7e69bb766e2fea813521ed536f5d0520dcf86c" +checksum = "8168378f4e5023e7218c89c891c0fd8ecdb5e5e4f18cb78f38cf245dd021e76f" dependencies = [ "block-buffer", "crypto-common", @@ -554,7 +554,7 @@ version = "0.1.0" dependencies = [ "clap", "itertools", - "miette 5.4.1", + "miette 5.5.0", "pretty_assertions", "serde", "serde_json", @@ -600,7 +600,7 @@ dependencies = [ "forge_analyzer", "forge_file_resolver", "forge_loader", - "miette 5.4.1", + "miette 5.5.0", "rustc-hash", "serde", "serde_yaml", @@ -674,11 +674,20 @@ dependencies = [ "libc", ] +[[package]] +name = "hermit-abi" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee512640fe35acbfb4bb779db6f0d80704c2cacfa2e39b601ef3e3f47d1ae4c7" +dependencies = [ + "libc", +] + [[package]] name = "iana-time-zone" -version = "0.1.51" +version = "0.1.53" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f5a6ef98976b22b3b7f2f3a806f858cb862044cfa66805aa3ad84cb3d3b785ed" +checksum = "64c122667b287044802d6ce17ee2ddf13207ed924c712de9a66a5814d5b64765" dependencies = [ "android_system_properties", "core-foundation-sys", @@ -722,9 +731,9 @@ checksum = "cb56e1aa765b4b4f3aadfab769793b7087bb03a4ea4920644a6d238e2df5b9ed" [[package]] name = "indexmap" -version = "1.9.1" +version = "1.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10a35a97730320ffe8e2d410b5d3b69279b98d2c14bdb8b70ea89ecf7888d41e" +checksum = "1885e79c1fc4b10f0e172c475f458b7f7b93061064d98c3293e98c5ba0c8b399" dependencies = [ "autocfg", "hashbrown", @@ -737,6 +746,16 @@ version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "59ce5ef949d49ee85593fc4d3f3f95ad61657076395cbbce23e2121fc5542074" +[[package]] +name = "io-lifetimes" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7d367024b3f3414d8e01f437f704f41a9f64ab36f9067fa73e526ad4c763c87" +dependencies = [ + "libc", + "windows-sys", +] + [[package]] name = "is-macro" version = "0.2.1" @@ -750,6 +769,18 @@ dependencies = [ "syn", ] +[[package]] +name = "is-terminal" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aae5bc6e2eb41c9def29a3e0f1306382807764b9b53112030eff57435667352d" +dependencies = [ + "hermit-abi 0.2.6", + "io-lifetimes 1.0.1", + "rustix 0.36.3", + "windows-sys", +] + [[package]] name = "is_ci" version = "1.1.1" @@ -889,6 +920,12 @@ version = "0.0.46" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d4d2456c373231a208ad294c33dc5bff30051eafd954cd4caae83a712b12854d" +[[package]] +name = "linux-raw-sys" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f9f08d8963a6c613f4b1a78f4f4a4dbfadf8e6545b2d72861731e4858b8b47f" + [[package]] name = "lock_api" version = "0.4.9" @@ -934,9 +971,9 @@ checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" [[package]] name = "memoffset" -version = "0.6.5" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5aa361d4faea93603064a027415f07bd8e1d5c88c9fbf68bf56a285428fd79ce" +checksum = "5de893c32cde5f383baa4c04c5d6dbdd735cfd4a794b0debdb2bb1b421da5ff4" dependencies = [ "autocfg", ] @@ -963,13 +1000,13 @@ dependencies = [ [[package]] name = "miette" -version = "5.4.1" +version = "5.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a24c4b4063c21e037dffb4de388ee85e400bff299803aba9513d9c52de8116b" +checksum = "4afd9b301defa984bbdbe112b4763e093ed191750a0d914a78c1106b2d0fe703" dependencies = [ "atty", "backtrace", - "miette-derive 5.4.1", + "miette-derive 5.5.0", "once_cell", "owo-colors", "supports-color", @@ -994,9 +1031,9 @@ dependencies = [ [[package]] name = "miette-derive" -version = "5.4.1" +version = "5.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "827d18edee5d43dc309eb0ac565f2b8e2fdc89b986b2d929e924a0f6e7f23835" +checksum = "97c2401ab7ac5282ca5c8b518a87635b1a93762b0b90b9990c509888eeccba29" dependencies = [ "proc-macro2", "quote", @@ -1090,16 +1127,7 @@ version = "1.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f6058e64324c71e02bc2b150e4f3bc8286db6c83092132ffa3f6b1eab0f9def5" dependencies = [ - "hermit-abi", - "libc", -] - -[[package]] -name = "num_threads" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2819ce041d2ee131036f4fc9d6ae7ae125a3a40e97ba64d04fe799ad9dabbb44" -dependencies = [ + "hermit-abi 0.1.19", "libc", ] @@ -1120,9 +1148,9 @@ checksum = "86f0b0d4bf799edbc74508c1e8bf170ff5f41238e5f8225603ca7caaae2b7860" [[package]] name = "os_str_bytes" -version = "6.3.1" +version = "6.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3baf96e39c5359d2eb0dd6ccb42c62b91d9678aa68160d261b9e0ccbf9e9dea9" +checksum = "9b7820b9daea5457c9f21c69448905d723fbd21136ccf521748f23fd49e723ee" [[package]] name = "output_vt100" @@ -1385,11 +1413,10 @@ dependencies = [ [[package]] name = "rayon" -version = "1.5.3" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd99e5772ead8baa5215278c9b15bf92087709e9c1b2d1f97cdb5a183c933a7d" +checksum = "1e060280438193c554f654141c9ea9417886713b7acd75974c85b18a69a88e0b" dependencies = [ - "autocfg", "crossbeam-deque", "either", "rayon-core", @@ -1397,9 +1424,9 @@ dependencies = [ [[package]] name = "rayon-core" -version = "1.9.3" +version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "258bcdb5ac6dad48491bb2992db6b7cf74878b0384908af124823d118c99683f" +checksum = "cac410af5d00ab6884528b4ab69d1e8e146e8d471201800fa1b4524126de6ad3" dependencies = [ "crossbeam-channel", "crossbeam-deque", @@ -1477,9 +1504,23 @@ checksum = "727a1a6d65f786ec22df8a81ca3121107f235970dc1705ed681d3e6e8b9cd5f9" dependencies = [ "bitflags", "errno", - "io-lifetimes", + "io-lifetimes 0.7.5", + "libc", + "linux-raw-sys 0.0.46", + "windows-sys", +] + +[[package]] +name = "rustix" +version = "0.36.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b1fbb4dfc4eb1d390c02df47760bb19a84bb80b301ecc947ab5406394d8223e" +dependencies = [ + "bitflags", + "errno", + "io-lifetimes 1.0.1", "libc", - "linux-raw-sys", + "linux-raw-sys 0.1.3", "windows-sys", ] @@ -1506,9 +1547,9 @@ dependencies = [ [[package]] name = "scoped-tls" -version = "1.0.0" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea6a9290e3c9cf0f18145ef7ffa62d68ee0bf5fcd651017e586dc7fd5da448c2" +checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294" [[package]] name = "scopeguard" @@ -1579,9 +1620,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.87" +version = "1.0.89" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ce777b7b150d76b9cf60d28b55f5847135a003f7d7350c6be7a773508ce7d45" +checksum = "020ff22c755c2ed3f8cf162dbb41a7268d934702f3ed3631656ea597e08fc3db" dependencies = [ "itoa", "ryu", @@ -1764,9 +1805,9 @@ dependencies = [ [[package]] name = "swc" -version = "0.232.93" +version = "0.232.110" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88a9a25f0200ec6617563fd39bbfb2a15ec14151496bea0134a4fb6c28831226" +checksum = "06e34fa65054df498b6e8baeec55171b9adcfa68dfe1be06b7e5be3731d095e1" dependencies = [ "ahash", "anyhow", @@ -1811,9 +1852,9 @@ dependencies = [ [[package]] name = "swc_atoms" -version = "0.4.24" +version = "0.4.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79642938ff437f2217718abf30a3450b014f600847c8f4bd60fa44f88a5210ea" +checksum = "63b8033a868fbebf5829797ac0c543499622b657e2d33a08ca6ab12547b8bafc" dependencies = [ "once_cell", "rustc-hash", @@ -1839,9 +1880,9 @@ dependencies = [ [[package]] name = "swc_common" -version = "0.29.14" +version = "0.29.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4bde01c52376971bc6839c42e1a71dec9526ac7acfbfcf1eb3e606e5aa1b2de0" +checksum = "270c8551babaeafa4fc33ab06548ae454532af88a7c650860909dd574042b921" dependencies = [ "ahash", "ast_node", @@ -1893,9 +1934,9 @@ dependencies = [ [[package]] name = "swc_core" -version = "0.43.11" +version = "0.43.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85c23a92f00c6f5fe3dd2c8c3ba6f550e4d8f984b9178714955a68ec8882ecbd" +checksum = "9916f3083d831d9af37d5dfd0cf0c61de979894d3dff73b5a5bbc2139a92864f" dependencies = [ "swc", "swc_atoms", @@ -1912,9 +1953,9 @@ dependencies = [ [[package]] name = "swc_ecma_ast" -version = "0.94.19" +version = "0.94.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f54bd55f94f02afe98be444e1808e068fa3dca0a113d0c38748d3fdd7a380c2b" +checksum = "f2e2786dccb1e68c26b3b67efa124d37055dae27fbe42a25bb541ce00ab9eaa5" dependencies = [ "bitflags", "is-macro", @@ -1929,9 +1970,9 @@ dependencies = [ [[package]] name = "swc_ecma_codegen" -version = "0.127.31" +version = "0.127.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e807c7271cc05ce3853ce7937776b89730463a39a98729f83bef76bfb6a99048" +checksum = "76601c9cfcb1a63ef0fd83dde314ddda9d6fd2d0583cd4149990550d87bb555b" dependencies = [ "memchr", "num-bigint", @@ -1961,9 +2002,9 @@ dependencies = [ [[package]] name = "swc_ecma_ext_transforms" -version = "0.91.33" +version = "0.91.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16ce0f7a23ce2dcb70e1dcc6e848968f192001e12583c4c915f069b60f00051c" +checksum = "174b61dbb706cb1abc9eafa90faef3b2978c9a3e695bea944bd00c1fc192807e" dependencies = [ "phf", "swc_atoms", @@ -1975,9 +2016,9 @@ dependencies = [ [[package]] name = "swc_ecma_lints" -version = "0.66.48" +version = "0.66.56" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "835a1bb73b00494b6e58c9a7094a90f893effb2ed39d3564b21687c872153952" +checksum = "e03462a3077c1cbe58a6fb4bdaede607bd9ebfee9b8100ee3f8cef6cdb34b992" dependencies = [ "ahash", "auto_impl", @@ -1996,9 +2037,9 @@ dependencies = [ [[package]] name = "swc_ecma_loader" -version = "0.41.15" +version = "0.41.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78b475a49f4c6cc848fe0084c89d202f35691035601ad1ff34e8d72f673c8759" +checksum = "70b019b735b4aea596baf98d22878579478e1aeb4a2fe41acc93a6b049735b93" dependencies = [ "ahash", "anyhow", @@ -2018,9 +2059,9 @@ dependencies = [ [[package]] name = "swc_ecma_minifier" -version = "0.159.81" +version = "0.159.96" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08bd43e585789982187df760482587e5c9aceb775570b223141e0846cc0ff9d3" +checksum = "098a9f93e2e200f1b87732f9a8c0bd214694e815d1b2cbd580be3f6e3480b011" dependencies = [ "ahash", "arrayvec", @@ -2052,9 +2093,9 @@ dependencies = [ [[package]] name = "swc_ecma_parser" -version = "0.122.26" +version = "0.122.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0bac20cd9f38112de7572150bc3ef24d99eed7c64d03f73f9c87df3bb497ca94" +checksum = "9ea0ad1938122f69686aaa60828e56e5da30e2ce356bee4e9710ae3a1c699dea" dependencies = [ "either", "enum_kind", @@ -2071,9 +2112,9 @@ dependencies = [ [[package]] name = "swc_ecma_preset_env" -version = "0.174.49" +version = "0.174.60" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d25f8d2d4a2e9d7cb1b4286931b404405c862258b7767ea722278c54724f694" +checksum = "5b84bd0ee54fde06c39403825ad0740877d879acadab449821e8d64a76c289eb" dependencies = [ "ahash", "anyhow", @@ -2096,9 +2137,9 @@ dependencies = [ [[package]] name = "swc_ecma_transforms" -version = "0.198.49" +version = "0.198.60" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "962b799fba3e74bc113cafdba27c7d747ba2861a46b49036525f2e155c49275e" +checksum = "2f9351c9eea401f1e4183fa2de2f6bfe55521c4689901924123c5aaccdd6fd07" dependencies = [ "swc_atoms", "swc_common", @@ -2116,9 +2157,9 @@ dependencies = [ [[package]] name = "swc_ecma_transforms_base" -version = "0.111.48" +version = "0.111.55" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7853d181f5eb8a620c0189eab19161cd68234f07df00d302acb8596fff88147f" +checksum = "530e30b116e59cdc6f1e05594f7953111436a1e79543795a3f13097d0d75f453" dependencies = [ "better_scoped_tls", "bitflags", @@ -2138,9 +2179,9 @@ dependencies = [ [[package]] name = "swc_ecma_transforms_classes" -version = "0.100.47" +version = "0.100.54" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21e8426a2a48d675828b60e6e2cdd43a8eac8f9d901590cabc7833b053ba931b" +checksum = "c887fc91a84d5ab8e306bf75aff9b46f775b907a45f769dd9bbcd37f6f672c9d" dependencies = [ "swc_atoms", "swc_common", @@ -2152,9 +2193,9 @@ dependencies = [ [[package]] name = "swc_ecma_transforms_compat" -version = "0.136.41" +version = "0.136.48" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "98fd1bb023214b0846b2a0083702868b18c9b26c50de6c1f4438c6f45d24b1ef" +checksum = "9b49461279fe4f029041b9a6aa947c1337d25ae87b6135e3b12cf579c8584e90" dependencies = [ "ahash", "arrayvec", @@ -2191,9 +2232,9 @@ dependencies = [ [[package]] name = "swc_ecma_transforms_module" -version = "0.153.43" +version = "0.153.52" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d875fe3535ee6a718e83d09f33ae06a02f570a3b312a28815bb6cb8668c20a27" +checksum = "7ea58451864586eb40a920c8347bcca9183db9b655da199caade95be1748fdcc" dependencies = [ "Inflector", "ahash", @@ -2219,9 +2260,9 @@ dependencies = [ [[package]] name = "swc_ecma_transforms_optimization" -version = "0.167.49" +version = "0.167.60" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4d408666841c399c256e6ddf9851fc11628f2759dc49a4e23fbf1671e698c85" +checksum = "cdc5fef67528f28d2cb5218d4afed05551fa87a8eb9bf3190b86e4b7e32f99bc" dependencies = [ "ahash", "dashmap", @@ -2244,9 +2285,9 @@ dependencies = [ [[package]] name = "swc_ecma_transforms_proposal" -version = "0.144.41" +version = "0.144.48" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e211e3f7fb99d2a67d9b702d694f151afb3ccec58b0c16738c989ffab250b88" +checksum = "7a7345ea66975671dd8a68310d10536430dfce7415ae9d657edfbacf2d5b2ca2" dependencies = [ "either", "serde", @@ -2263,9 +2304,9 @@ dependencies = [ [[package]] name = "swc_ecma_transforms_react" -version = "0.155.43" +version = "0.155.53" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6620cccf5fc0e44ab6115df325d984e27a798836788ce14f1e3f48f0c1876a9d" +checksum = "d3d8d9b050792194de8183ff2cbfc8bd63393f61138a5660df1ec346178535c2" dependencies = [ "ahash", "base64", @@ -2289,9 +2330,9 @@ dependencies = [ [[package]] name = "swc_ecma_transforms_typescript" -version = "0.159.45" +version = "0.159.55" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce69ec6f0ad1819b02653bce0abc3165c7afa11429103eb530bf64cd6aab74e8" +checksum = "dcb800ad09b8fe03c56fa88ca8f9d975d44a7f014d2a355946cd7f77ea8219d2" dependencies = [ "serde", "swc_atoms", @@ -2305,9 +2346,9 @@ dependencies = [ [[package]] name = "swc_ecma_utils" -version = "0.105.33" +version = "0.105.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "baaee0747f8c8d32a7d55f6054e915c7a0eae13fc20127dd9ab52bc1e2f2c785" +checksum = "ac9a42eb8a8d4d0ce9d623e3b64691deee7690a1d3a725b2d0088658679ae09b" dependencies = [ "indexmap", "num_cpus", @@ -2322,9 +2363,9 @@ dependencies = [ [[package]] name = "swc_ecma_visit" -version = "0.80.19" +version = "0.80.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7b42489b19f3451b65c01ed4a7926e44fab294ed9bfa8489634e58ecc96df88" +checksum = "e6ec255eeefb6a5146800047d1ee86c44326b9198f98c54988b2740fb40ec3f9" dependencies = [ "num-bigint", "swc_atoms", @@ -2348,9 +2389,9 @@ dependencies = [ [[package]] name = "swc_error_reporters" -version = "0.13.14" +version = "0.13.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cfdfda46250b8d5ff325c4f9e7e50497125e8f357f3a2daa655ba0b4ad8d964a" +checksum = "c500999360900f1f36838549e1a4faf1a3499a2d70cc5c24edd9ba3134448b9f" dependencies = [ "anyhow", "miette 4.7.1", @@ -2361,9 +2402,9 @@ dependencies = [ [[package]] name = "swc_fast_graph" -version = "0.17.15" +version = "0.17.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd95667b47445a6aec7994c6701ade4e250632d38a1a8676c633b99e09897d78" +checksum = "13830b02764bf532d347118283393fe6f2b6b5a93c6948af443d2891958705f9" dependencies = [ "ahash", "indexmap", @@ -2385,9 +2426,9 @@ dependencies = [ [[package]] name = "swc_node_comments" -version = "0.16.14" +version = "0.16.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eed7b4e6db6bd936ce05e368cdcafa81dcd1f1fe8ae21b7b7af1bbf0e8b55869" +checksum = "3ad0ec8c52bc0ed6225805117688abe890313d491c36de5665dcc2e80a643f08" dependencies = [ "ahash", "dashmap", @@ -2397,9 +2438,9 @@ dependencies = [ [[package]] name = "swc_timer" -version = "0.17.14" +version = "0.17.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34005d58739d4c115eaa8a4b3f5e82eba67dd9b84b55b1f3a8486b6575c83d76" +checksum = "b5d2783e7023fcf40687ac2700ec4ad98e31851f4c5efc55291ebd13ae374e6a" dependencies = [ "tracing", ] @@ -2475,15 +2516,15 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "40ca90c434fd12083d1a6bdcbe9f92a14f96c8a1ba600ba451734ac334521f7a" dependencies = [ - "rustix", + "rustix 0.35.13", "windows-sys", ] [[package]] name = "textwrap" -version = "0.15.0" +version = "0.15.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1141d4d61095b28419e22cb0bbf02755f5e54e0526f97f1e3d1d160e60885fb" +checksum = "b7b3e525a49ec206798b40326a44121291b530c963cfb01018f63e135bac543d" dependencies = [ "smawk", "unicode-linebreak", @@ -2532,13 +2573,29 @@ dependencies = [ [[package]] name = "time" -version = "0.3.15" +version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d634a985c4d4238ec39cacaed2e7ae552fbd3c476b552c1deac3021b7d7eaf0c" +checksum = "a561bf4617eebd33bca6434b988f39ed798e527f51a1e797d0ee4f61c0a38376" dependencies = [ "itoa", - "libc", - "num_threads", + "serde", + "time-core", + "time-macros", +] + +[[package]] +name = "time-core" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e153e1f1acaef8acc537e68b44906d2db6436e2b35ac2c6b42640fff91f00fd" + +[[package]] +name = "time-macros" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d967f99f534ca7e495c575c62638eebc2898a8c84c119b89e250477bc4ba16b2" +dependencies = [ + "time-core", ] [[package]] @@ -2737,7 +2794,7 @@ dependencies = [ "getset", "rustversion", "thiserror", - "time 0.3.15", + "time 0.3.17", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 516415e0..167f351c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,22 +8,22 @@ edition = "2021" license = "MIT OR Apache-2.0" [workspace.dependencies] -clap = { version = "4.0.24", features = ["derive", "wrap_help"] } +clap = { version = "4.0.27", features = ["derive", "wrap_help"] } fixedbitset = "0.4.2" itertools = "0.10.5" -miette = { version = "5.4.1", features = ["fancy"] } +miette = { version = "5.5.0", features = ["fancy"] } num-bigint = { version = "0.4.3" } serde = { version = "1.0.147", features = ["derive"] } -serde_json = "1.0.87" +serde_json = "1.0.89" serde_yaml = "0.9.14" petgraph = "0.6.2" pretty_assertions = "1.3.0" -indexmap = { version = "1.9.1", features = ["std"] } +indexmap = { version = "1.9.2", features = ["std"] } once_cell = "1.16.0" regex = "1.7.0" rustc-hash = "1.1.0" smallvec = { version = "1.10.0", features = ["union"] } -swc_core = { version = "0.43.11", features = ["common_perf", "common", "common_sourcemap", "ecma_visit_path", "ecma_utils", "ecma_ast", "swc", "ecma_visit", "ecma_transforms", "ecma_transforms_module", "ecma_transforms_typescript", "ecma_parser_typescript"] } +swc_core = { version = "0.43.30", features = ["common_perf", "common", "common_sourcemap", "ecma_visit_path", "ecma_utils", "ecma_ast", "swc", "ecma_visit", "ecma_transforms", "ecma_transforms_module", "ecma_transforms_typescript", "ecma_parser_typescript"] } thiserror = "1.0.37" tracing = "0.1.37" tracing-subscriber = { version = "0.3.16", features = ["env-filter"] } From 63e4bb19f06f1026042ef9274bb5170d6681b413 Mon Sep 17 00:00:00 2001 From: Joshua Wong Date: Sat, 26 Nov 2022 19:23:15 -0600 Subject: [PATCH 015/517] refactor(ctx): remove unused functions --- crates/forge_analyzer/src/ctx.rs | 17 ----------------- 1 file changed, 17 deletions(-) diff --git a/crates/forge_analyzer/src/ctx.rs b/crates/forge_analyzer/src/ctx.rs index 7b8beccc..aba5080f 100644 --- a/crates/forge_analyzer/src/ctx.rs +++ b/crates/forge_analyzer/src/ctx.rs @@ -1,4 +1,3 @@ -use std::iter::repeat; use std::path::Path; use std::{borrow::Borrow, hash::Hash, path::PathBuf}; @@ -343,11 +342,6 @@ impl FunctionMeta { self.blocks.push_and_get_key(BasicBlock::default()) } - #[inline] - pub(crate) fn add_terminator_to_last(&mut self, term: TerminatorKind) { - self.blocks.last_mut().unwrap().terminator = term; - } - #[inline] pub(crate) fn add_edge(&mut self, from: BasicBlockId, to: BasicBlockId) { self.succ.entry(from).or_default().push(to); @@ -375,17 +369,6 @@ impl FunctionMeta { pub(crate) fn iter_stmts_mut(&mut self) -> impl Iterator + '_ { self.blocks.iter_mut().flat_map(|bb| &mut bb.stmts) } - - #[inline] - pub(crate) fn iter_stmts_enumerated_mut( - &mut self, - ) -> impl Iterator + '_ { - self.blocks.iter_mut_enumerated().flat_map(|(id, bb)| { - repeat(id) - .zip(bb.stmts.iter_mut_enumerated()) - .map(|(id, (idx, stmt))| (id, idx, stmt)) - }) - } } impl ModuleCtx { From ab94d5cdf07177806d779fef705e5945fbe68474 Mon Sep 17 00:00:00 2001 From: Joshua Wong Date: Sat, 26 Nov 2022 19:24:15 -0600 Subject: [PATCH 016/517] wip: add more descriptive debug output --- crates/fsrt/src/main.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/fsrt/src/main.rs b/crates/fsrt/src/main.rs index 49136fe3..34c99ea2 100644 --- a/crates/fsrt/src/main.rs +++ b/crates/fsrt/src/main.rs @@ -215,11 +215,11 @@ fn scan_directory(dir: PathBuf, function: Option<&str>, opts: Opts) -> Result Date: Sat, 26 Nov 2022 19:24:42 -0600 Subject: [PATCH 017/517] chore: remove more dead code --- crates/forge_analyzer/src/lib.rs | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/crates/forge_analyzer/src/lib.rs b/crates/forge_analyzer/src/lib.rs index da6a47b0..fd20cd06 100644 --- a/crates/forge_analyzer/src/lib.rs +++ b/crates/forge_analyzer/src/lib.rs @@ -43,7 +43,6 @@ pub(crate) struct ForgeImports { #[derive(Debug, Clone)] enum Exports { - Default(Id), Named(FxHashMap), } @@ -126,14 +125,6 @@ impl ForgeImports { debug!(import = ?self.as_app, ?id, "checking if asApp import"); Some(id) == self.as_app.as_ref() } - - pub(crate) fn contains_forge_imports(&self) -> bool { - self.api - .as_ref() - .or(self.as_app.as_ref()) - .or(self.authorize.as_ref()) - .is_some() - } } impl Visit for ImportCollector { @@ -239,13 +230,11 @@ impl Exports { fn add_named(&mut self, orig: Id, exported: Id) { match self { Exports::Named(map) => map.insert(orig, exported), - Exports::Default(_) => panic!("expected named exports"), }; } pub(crate) fn orig_from_str(&self, lookup: &str) -> Option { match self { - Exports::Default(_) => None, Exports::Named(exports) => exports .iter() .find_map(|(orig, exported)| (&exported.0 == lookup).then(|| orig.clone())), @@ -254,7 +243,6 @@ impl Exports { pub(crate) fn find_orig(&self, lookup: &Id) -> Option { match self { - Exports::Default(_) => None, Exports::Named(exports) => exports .iter() .find_map(|(orig, exported)| (exported.0 == lookup.0).then(|| orig.clone())), From c9156d81b15c2b898666e1d2c2fda8a540169816 Mon Sep 17 00:00:00 2001 From: Joshua Wong Date: Sat, 26 Nov 2022 19:31:05 -0600 Subject: [PATCH 018/517] wip --- crates/forge_analyzer/src/definitions.rs | 302 ++++++++++++++++++----- crates/forge_analyzer/src/ir.rs | 53 ++-- 2 files changed, 274 insertions(+), 81 deletions(-) diff --git a/crates/forge_analyzer/src/definitions.rs b/crates/forge_analyzer/src/definitions.rs index 0cda2645..65c9e330 100644 --- a/crates/forge_analyzer/src/definitions.rs +++ b/crates/forge_analyzer/src/definitions.rs @@ -14,21 +14,20 @@ use swc_core::{ ExportNamedSpecifier, Expr, ExprOrSpread, FnDecl, FnExpr, Id, Ident, ImportDecl, ImportDefaultSpecifier, ImportNamedSpecifier, ImportStarAsSpecifier, KeyValueProp, Lit, MemberExpr, MemberProp, MethodProp, Module, ModuleDecl, ModuleExportName, ModuleItem, - NewExpr, ObjectLit, Pat, PrivateName, Prop, PropName, PropOrSpread, Str, VarDecl, + NewExpr, ObjectLit, Pat, PrivateName, Prop, PropName, PropOrSpread, Stmt, Str, VarDecl, VarDeclarator, }, atoms::JsWord, visit::{noop_visit_type, Visit, VisitWith}, }, }; -use tracing::warn; +use tracing::{debug, warn}; use typed_index_collections::{TiSlice, TiVec}; -use crate::{ctx::ModId, ir::Body}; - -create_newtype! { - pub struct GlobalId(u32); -} +use crate::{ + ctx::ModId, + ir::{BasicBlockId, Body, Inst, Terminator}, +}; create_newtype! { pub struct FuncId(u32); @@ -46,16 +45,9 @@ const INVALID_FUNC: FuncId = FuncId(u32::MAX); const INVALID_CLASS: ObjId = ObjId(u32::MAX); -const INVALID_GLOBAL: GlobalId = GlobalId(u32::MAX); - -trait DefinitionDb { - fn possible_funcalls(&self, proj: &[Option]) -> &[DefId]; -} - #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub enum DefKind { Function(FuncId), - Global(GlobalId), Class(ObjId), ExportAlias(DefId), /// exported(usuall) handler to the actual resolver definitions @@ -149,19 +141,36 @@ pub fn run_resolver( } } - let mut foreign_defs = TiVec::default(); + let mut foreign = TiVec::default(); for (curr_mod, module) in modules.iter_enumerated() { let mut import_collector = ImportCollector { resolver: &mut resolver, file_resolver, - foreign_defs: &mut foreign_defs, + foreign_defs: &mut foreign, curr_mod, current_import: Default::default(), in_foreign_import: false, }; module.visit_with(&mut import_collector); } - (resolver, foreign_defs) + + //TODO: return defs instead of cloning + let mut defs = Definitions { + foreign: foreign.clone(), + ..Default::default() + }; + for (curr_mod, module) in modules.iter_enumerated() { + let mut lowerer = Lowerer { + res: &mut resolver, + defs: &mut defs, + curr_mod, + parents: vec![], + curr_def: None, + }; + module.visit_with(&mut lowerer); + } + + (resolver, foreign) } /// this struct is a bit of a hack, because we also use it for @@ -225,10 +234,9 @@ pub struct ForeignItem { module_name: JsWord, } -#[derive(Debug, Clone)] +#[derive(Debug, Clone, Default)] struct Definitions { funcs: TiVec, - globals: TiVec, classes: TiVec, foreign: TiVec, } @@ -263,10 +271,9 @@ enum LowerStage { } struct Lowerer<'cx> { - res_table: &'cx mut ResolverTable, + res: &'cx mut Resolver, defs: &'cx mut Definitions, curr_mod: ModId, - stage: LowerStage, parents: Vec, curr_def: Option, } @@ -435,37 +442,133 @@ impl ResolverTable { } struct FunctionCollector<'cx> { - res: &'cx mut ResolverTable, + res: &'cx mut Resolver, defs: &'cx mut Definitions, module: ModId, parent: Option, } +struct FunctionAnalyzer<'cx> { + res: &'cx mut Resolver, + defs: &'cx mut Definitions, + module: ModId, + current_def: DefId, + body: Body, + block: BasicBlockId, +} + +impl<'cx> FunctionAnalyzer<'cx> { + #[inline] + fn set_curr_terminator(&mut self, term: Terminator) { + self.body.set_terminator(self.block, term); + } + + #[inline] + fn push_curr_inst(&mut self, inst: Inst) { + self.body.push_inst(self.block, inst); + } +} + +impl Visit for FunctionAnalyzer<'_> { + fn visit_member_expr(&mut self, n: &MemberExpr) {} + + fn visit_expr(&mut self, n: &Expr) { + match n { + Expr::This(_) => todo!(), + Expr::Array(_) => todo!(), + Expr::Object(_) => todo!(), + Expr::Fn(_) => todo!(), + Expr::Unary(_) => todo!(), + Expr::Update(_) => todo!(), + Expr::Bin(_) => todo!(), + Expr::Assign(_) => todo!(), + Expr::Member(_) => todo!(), + Expr::SuperProp(_) => todo!(), + Expr::Cond(_) => todo!(), + Expr::Call(_) => todo!(), + Expr::New(_) => todo!(), + Expr::Seq(_) => todo!(), + Expr::Ident(_) => todo!(), + Expr::Lit(_) => todo!(), + Expr::Tpl(_) => todo!(), + Expr::TaggedTpl(_) => todo!(), + Expr::Arrow(_) => todo!(), + Expr::Class(_) => todo!(), + Expr::Yield(_) => todo!(), + Expr::MetaProp(_) => todo!(), + Expr::Await(_) => todo!(), + Expr::Paren(_) => todo!(), + Expr::JSXMember(_) => todo!(), + Expr::JSXNamespacedName(_) => todo!(), + Expr::JSXEmpty(_) => todo!(), + Expr::JSXElement(_) => todo!(), + Expr::JSXFragment(_) => todo!(), + Expr::TsTypeAssertion(_) => todo!(), + Expr::TsConstAssertion(_) => todo!(), + Expr::TsNonNull(_) => todo!(), + Expr::TsAs(_) => todo!(), + Expr::TsInstantiation(_) => todo!(), + Expr::TsSatisfies(_) => todo!(), + Expr::PrivateName(_) => todo!(), + Expr::OptChain(_) => todo!(), + Expr::Invalid(_) => todo!(), + } + } + fn visit_stmt(&mut self, n: &Stmt) { + match n { + Stmt::Block(_) => todo!(), + Stmt::Empty(_) => todo!(), + Stmt::Debugger(_) => todo!(), + Stmt::With(_) => todo!(), + Stmt::Return(_) => todo!(), + Stmt::Labeled(_) => todo!(), + Stmt::Break(_) => todo!(), + Stmt::Continue(_) => todo!(), + Stmt::If(_) => todo!(), + Stmt::Switch(_) => todo!(), + Stmt::Throw(_) => todo!(), + Stmt::Try(_) => todo!(), + Stmt::While(_) => todo!(), + Stmt::DoWhile(_) => todo!(), + Stmt::For(_) => todo!(), + Stmt::ForIn(_) => todo!(), + Stmt::ForOf(_) => todo!(), + Stmt::Decl(_) => todo!(), + Stmt::Expr(_) => todo!(), + } + } +} + +impl Visit for FunctionCollector<'_> { + fn visit_fn_decl(&mut self, n: &FnDecl) { + let id = n.ident.to_id(); + let def = self.res.get_or_insert_sym(id, self.module); + self.parent = Some(def); + n.function.visit_children_with(self); + self.parent = None; + } +} + impl Lowerer<'_> { + #[inline] fn defid_from_ident(&self, id: Id) -> Option { - let sym = Symbol { - module: self.curr_mod, - id, - }; - self.res_table.symbol_to_id.get(&sym).copied() + self.res.sym_to_id(id, self.curr_mod) + } + + #[inline] + fn get_or_insert_sym(&mut self, id: Id) -> DefId { + self.res.get_or_insert_sym(id, self.curr_mod) } #[inline] fn reserve_symbol(&mut self, id: Id) -> DefId { - self.res_table.get_or_insert_sym(id, self.curr_mod) + self.res.reserve_sym(id, self.curr_mod) } fn defkind_from_ident(&self, id: Id) -> Option { let cid = id.clone(); let defid = self.defid_from_ident(id)?; - let defkind = self.res_table.defs.get(defid).copied(); - if defkind.is_none() { - warn!( - module = ?self.curr_mod, - "resolver table has unknown defid: {defid:?} for id: {cid:?}" - ); - } - defkind + Some(self.res.def_kind(defid)) } fn as_foreign_import(&self, imported_sym: Id, module: &str) -> Option<&ImportKind> { @@ -482,22 +585,21 @@ impl Lowerer<'_> { #[inline] fn next_key(&self) -> DefId { - self.res_table.defs.next_key() + self.res.next_key() } fn def_objlike(&mut self, id: Id, f: impl FnOnce(ObjId) -> ObjKind) -> (DefId, ObjId) { let idlookup = id.clone(); match self.defid_from_ident(idlookup) { Some(def) => { - let defkind = self.res_table.defs[def]; + let defkind = self.res.def_kind(def); (def, defkind.as_objkind().unwrap().into_inner()) } None => { let next_key = self.next_key(); let objid = self.defs.classes.push_and_get_key(Class::new(next_key)); let objkind = f(objid); - self.res_table - .add_sym(objkind.as_defkind(), id, self.curr_mod); + self.res.add_sym(objkind.as_defkind(), id, self.curr_mod); (next_key, objkind.into_inner()) } } @@ -507,35 +609,32 @@ impl Lowerer<'_> { self.defid_from_ident(id.clone()).unwrap_or_else(|| { let next_key = self.next_key(); let defkind = f(self, next_key); - self.res_table.add_sym(defkind, id, self.curr_mod) + self.res.add_sym(defkind, id, self.curr_mod) }) } fn def_method(&mut self, sym: JsWord) -> DefId { let func_id = self.defs.funcs.push_and_get_key(Body::default()); - self.res_table + self.res .add_prop(DefKind::Function(func_id), sym, self.curr_mod) } fn def_function(&mut self, id: Id) -> DefId { self.defid_from_ident(id.clone()).unwrap_or_else(|| { let funcid = self.defs.funcs.push_and_get_key(Body::default()); - self.res_table + self.res .add_sym(DefKind::Function(funcid), id, self.curr_mod) }) } } +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] enum ResolverDef { FnDef, Handler, } -fn as_resolver( - expr: &Expr, - res_table: &ResolverTable, - module: ModId, -) -> Option<(ObjId, ResolverDef)> { +fn as_resolver(expr: &Expr, res_table: &Resolver, module: ModId) -> Option<(ObjId, ResolverDef)> { if let Expr::Member(MemberExpr { obj, prop: MemberProp::Ident(prop), @@ -543,7 +642,7 @@ fn as_resolver( }) = expr { let id = obj.as_ident()?; - let def = res_table.sym_kind(id.to_id(), module)?; + let def = res_table.sym_to_kind(id.to_id(), module)?; if let DefKind::Resolver(obj) = def { match &*prop.sym { "getDefinitions" => return Some((obj, ResolverDef::Handler)), @@ -560,6 +659,21 @@ fn as_resolver( impl Visit for Lowerer<'_> { noop_visit_type!(); + fn visit_call_expr(&mut self, n: &CallExpr) { + if let Some(expr) = n.callee.as_expr() { + if let Some((objid, ResolverDef::FnDef)) = as_resolver(expr, self.res, self.curr_mod) { + if let [ExprOrSpread { expr: name, .. }, ExprOrSpread { expr: args, .. }] = &*n.args + { + if let Expr::Lit(Lit::Str(Str { value, .. })) = &**name { + let fname = value.clone(); + let class = &mut self.defs.classes[objid]; + class.pub_members.push((fname, self.curr_def.unwrap())); + } + } + } + } + } + fn visit_var_declarator(&mut self, n: &VarDeclarator) { if let VarDeclarator { name: Pat::Ident(BindingIdent { id, .. }), @@ -586,7 +700,7 @@ impl Visit for Lowerer<'_> { args, .. }) => { - if let Some((objid, kind)) = as_resolver(expr, self.res_table, self.curr_mod) { + if let Some((objid, kind)) = as_resolver(expr, self.res, self.curr_mod) { match kind { ResolverDef::FnDef => { if let [ExprOrSpread { expr: name, .. }, ExprOrSpread { expr: args, .. }] = @@ -594,12 +708,18 @@ impl Visit for Lowerer<'_> { { if let Expr::Lit(Lit::Str(Str { value, .. })) = &**expr { let fname = value.clone(); + println!("defining function: {}", &*fname); let class = &mut self.defs.classes[objid]; class.pub_members.push((fname, self.curr_def.unwrap())); } } } - ResolverDef::Handler => todo!(), + ResolverDef::Handler => { + let def_id = self.get_or_insert_sym(id); + let res_def_id = self.defs.classes[objid].def; + *self.res.def_kind_mut(def_id) = + DefKind::ResolverHandler(res_def_id); + } } } expr.visit_children_with(self); @@ -622,15 +742,14 @@ impl Visit for Lowerer<'_> { let cls = &mut self.defs.classes[obj_id]; if let sym @ Some(_) = key.as_symbol() { let defid = value.as_ident().map(|id| { - self.res_table - .get_or_insert_sym(id.to_id(), self.curr_mod) + self.res.get_or_insert_sym(id.to_id(), self.curr_mod) }); cls.pub_members.extend(sym.zip(defid)); } } Prop::Assign(AssignProp { key, .. }) => { - let obj_sym = &self.res_table.names[def_id]; - warn!("object {obj_sym:?} invalid assign prop {:?}", &key.sym); + let obj_sym = self.res.def_name(def_id); + warn!("object {obj_sym} invalid assign prop {:?}", &key.sym); } /// TODO: track these Prop::Getter(_) | Prop::Setter(_) => {} @@ -647,13 +766,13 @@ impl Visit for Lowerer<'_> { } } Expr::New(NewExpr { callee, .. }) => { - let Some(id) = callee.as_ident() else { + let Some(callee_id) = callee.as_ident() else { expr.visit_children_with(self); return; }; - let id = id.to_id(); + let callee_id = callee_id.to_id(); if Some(&ImportKind::Default) - == self.as_foreign_import(id.clone(), "@forge/resolver") + == self.as_foreign_import(callee_id, "@forge/resolver") { self.add_sym_or_else(id, |this, to_insert| { let obj_id = this.defs.classes.push_and_get_key(Class::new(to_insert)); @@ -831,7 +950,7 @@ impl Visit for ImportCollector<'_> { } } -impl<'cx> Visit for ExportCollector<'cx> { +impl Visit for ExportCollector<'_> { noop_visit_type!(); fn visit_export_decl(&mut self, n: &ExportDecl) { match &n.decl { @@ -915,6 +1034,25 @@ impl Resolver { Self::default() } + fn next_key(&self) -> DefId { + self.res_table.defs.next_key() + } + + #[inline] + fn add_prop(&mut self, kind: DefKind, sym: JsWord, module: ModId) -> DefId { + self.res_table.add_prop(kind, sym, module) + } + + #[inline] + fn add_sym(&mut self, kind: DefKind, id: Id, module: ModId) -> DefId { + self.res_table.add_sym(kind, id, module) + } + + #[inline] + fn get_or_insert_sym(&mut self, id: Id, module: ModId) -> DefId { + self.res_table.get_or_insert_sym(id, module) + } + #[inline] pub fn default_export(&self, module: ModId) -> Option { self.default_exports.get(&module).copied() @@ -935,6 +1073,27 @@ impl Resolver { self.res_table.defs[def] } + #[inline] + fn reserve_sym(&mut self, id: Id, module: ModId) -> DefId { + self.res_table.reserve_symbol(id, module) + } + + #[inline] + fn sym_to_id(&self, id: Id, module: ModId) -> Option { + let sym = Symbol { module, id }; + self.res_table.symbol_to_id.get(&sym).copied() + } + + #[inline] + fn sym_to_kind(&self, id: Id, module: ModId) -> Option { + self.res_table.sym_kind(id, module) + } + + #[inline] + fn def_kind_mut(&mut self, def: DefId) -> &mut DefKind { + &mut self.res_table.defs[def] + } + fn resolve_local_export(&self, module: ModId, name: &JsWord) -> Option { match &**name { "default" => self.default_exports.get(&module).copied(), @@ -947,6 +1106,23 @@ impl Resolver { } } +trait Database { + type Value; + fn get(&self, key: K) -> Option<&Self::Value>; + fn get_mut(&mut self, key: K) -> Option<&mut Self::Value>; +} + +impl Database for Definitions { + type Value = ForeignItem; + fn get(&self, key: ForeignId) -> Option<&Self::Value> { + self.foreign.get(key) + } + + fn get_mut(&mut self, key: ForeignId) -> Option<&mut Self::Value> { + self.foreign.get_mut(key) + } +} + trait AsId { fn as_id(&self) -> Id; } @@ -984,7 +1160,6 @@ impl DefKind { DefKind::Resolver(id) => Some(ObjKind::Resolver(id)), DefKind::ObjLit(id) => Some(ObjKind::Lit(id)), DefKind::Function(_) - | DefKind::Global(_) | DefKind::ExportAlias(_) | DefKind::ResolverHandler(_) | DefKind::ModuleNs(_) @@ -1001,7 +1176,6 @@ impl fmt::Display for DefKind { DefKind::Resolver(_) => write!(f, "resolver"), DefKind::ObjLit(_) => write!(f, "object literal"), DefKind::Function(_) => write!(f, "function"), - DefKind::Global(_) => write!(f, "global"), DefKind::ExportAlias(_) => write!(f, "export alias"), DefKind::ResolverHandler(_) => write!(f, "resolver handler"), DefKind::ModuleNs(_) => write!(f, "module namespace"), @@ -1042,9 +1216,3 @@ impl AsId for ModuleExportName { } } } - -impl fmt::Display for GlobalId { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "@G{}", self.0) - } -} diff --git a/crates/forge_analyzer/src/ir.rs b/crates/forge_analyzer/src/ir.rs index 5bf77be1..82781412 100644 --- a/crates/forge_analyzer/src/ir.rs +++ b/crates/forge_analyzer/src/ir.rs @@ -22,13 +22,14 @@ create_newtype! { } #[derive(Clone, Debug)] -struct BranchTargets { +pub(crate) struct BranchTargets { compare: SmallVec<[Operand; 1]>, branch: SmallVec<[BasicBlockId; 2]>, } -#[derive(Clone, Debug)] -enum Terminator { +#[derive(Clone, Debug, Default)] +pub(crate) enum Terminator { + #[default] Ret, Goto(BasicBlockId), Throw, @@ -44,7 +45,7 @@ enum Terminator { } #[derive(Clone, Debug)] -enum Rvalue { +pub(crate) enum Rvalue { Unary(UnOp, Operand), Bin(BinOp, Operand, Operand), Read(Variable), @@ -56,9 +57,9 @@ enum Rvalue { }, } -#[derive(Clone, Debug)] -struct BasicBlock { - stmts: Vec, +#[derive(Clone, Debug, Default)] +pub(crate) struct BasicBlock { + insts: Vec, term: Terminator, } @@ -85,14 +86,14 @@ enum ApiCall { } #[derive(Clone, Debug)] -enum Stmt { +pub(crate) enum Inst { // maybe just use assign with a dummy VARIABLE for these? Expr(Rvalue), Assign(Variable, Rvalue), } #[derive(Clone, Debug, Default)] -enum Literal { +pub(crate) enum Literal { Str(Id), Bool(bool), Null, @@ -105,7 +106,7 @@ enum Literal { } #[derive(Clone, Copy, Debug, PartialEq, Eq)] -enum BinOp { +pub(crate) enum BinOp { Lt, Gt, EqEq, @@ -132,7 +133,7 @@ enum BinOp { } #[derive(Clone, Copy, Debug, PartialEq, Eq)] -enum UnOp { +pub(crate) enum UnOp { Neg, Not, BitNot, @@ -142,7 +143,7 @@ enum UnOp { } #[derive(Clone, Debug, PartialEq, Eq, Hash)] -enum Operand { +pub(crate) enum Operand { Var(Variable), Lit(Literal), Global(ModId, Id), @@ -157,13 +158,13 @@ create_newtype! { } #[derive(Clone, Debug, PartialEq, Eq, Hash)] -struct Variable { +pub(crate) struct Variable { var: VarId, projections: SmallVec<[Projection; 1]>, } #[derive(Clone, Debug, PartialEq, Eq, Hash)] -enum Projection { +pub(crate) enum Projection { Lit(Literal), Var(VarId), } @@ -189,9 +190,33 @@ impl fmt::Display for Variable { } impl Body { + #[inline] fn new() -> Self { Body::default() } + + #[inline] + pub(crate) fn new_block(&mut self) -> BasicBlockId { + self.blocks.push_and_get_key(BasicBlock::default()) + } + + #[inline] + pub(crate) fn new_block_with_terminator(&mut self, term: Terminator) -> BasicBlockId { + self.blocks.push_and_get_key(BasicBlock { + term, + ..Default::default() + }) + } + + #[inline] + pub(crate) fn set_terminator(&mut self, bb: BasicBlockId, term: Terminator) -> Terminator { + mem::replace(&mut self.blocks[bb].term, term) + } + + #[inline] + pub(crate) fn push_inst(&mut self, bb: BasicBlockId, inst: Inst) { + self.blocks[bb].insts.push(inst); + } } impl PartialEq for Literal { From 0a52e362ee611c0d2c8988238f5a1b8d2c79e7e9 Mon Sep 17 00:00:00 2001 From: Joshua Wong Date: Mon, 28 Nov 2022 10:58:39 -0600 Subject: [PATCH 019/517] chore: bump deps --- Cargo.lock | 122 ++++++++++++++++++++++++++--------------------------- Cargo.toml | 4 +- 2 files changed, 63 insertions(+), 63 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 44145cf2..3833bee1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -209,7 +209,7 @@ dependencies = [ "js-sys", "num-integer", "num-traits", - "time 0.1.44", + "time 0.1.45", "wasm-bindgen", "winapi", ] @@ -1589,9 +1589,9 @@ checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" [[package]] name = "serde" -version = "1.0.147" +version = "1.0.148" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d193d69bae983fc11a79df82342761dfbf28a99fc8d203dca4c3c1b590948965" +checksum = "e53f64bb4ba0191d6d0676e1b141ca55047d83b74f5607e6d8eb88126c52c2dc" dependencies = [ "serde_derive", ] @@ -1609,9 +1609,9 @@ dependencies = [ [[package]] name = "serde_derive" -version = "1.0.147" +version = "1.0.148" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f1d362ca8fc9c3e3a7484440752472d68a6caa98f1ab81d99b5dfe517cec852" +checksum = "a55492425aa53521babf6137309e7d34c20bbfbbfcfe2c7f3a047fd1f6b92c0c" dependencies = [ "proc-macro2", "quote", @@ -1805,9 +1805,9 @@ dependencies = [ [[package]] name = "swc" -version = "0.232.110" +version = "0.232.113" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06e34fa65054df498b6e8baeec55171b9adcfa68dfe1be06b7e5be3731d095e1" +checksum = "2673b6c50a317d1ee557d0a6fd93870a0b41bbcd809388f9daa0be882830bfd8" dependencies = [ "ahash", "anyhow", @@ -1880,9 +1880,9 @@ dependencies = [ [[package]] name = "swc_common" -version = "0.29.15" +version = "0.29.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "270c8551babaeafa4fc33ab06548ae454532af88a7c650860909dd574042b921" +checksum = "876ef0da27185c6b263ad6353a0314f10a3c077f9197d3e3dd71f8e07d2f0fae" dependencies = [ "ahash", "ast_node", @@ -1934,9 +1934,9 @@ dependencies = [ [[package]] name = "swc_core" -version = "0.43.30" +version = "0.43.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9916f3083d831d9af37d5dfd0cf0c61de979894d3dff73b5a5bbc2139a92864f" +checksum = "e3cd78577b4cbfa2f43f65f5ab720248b771d4107f10d474ac8bd838cd506b39" dependencies = [ "swc", "swc_atoms", @@ -1953,9 +1953,9 @@ dependencies = [ [[package]] name = "swc_ecma_ast" -version = "0.94.20" +version = "0.94.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2e2786dccb1e68c26b3b67efa124d37055dae27fbe42a25bb541ce00ab9eaa5" +checksum = "b7823e863bebbcbcabba5d5781dd8cedcf1acb35a770a871c914855b43783e17" dependencies = [ "bitflags", "is-macro", @@ -1970,9 +1970,9 @@ dependencies = [ [[package]] name = "swc_ecma_codegen" -version = "0.127.36" +version = "0.127.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76601c9cfcb1a63ef0fd83dde314ddda9d6fd2d0583cd4149990550d87bb555b" +checksum = "6f6fb2b7f7f0017d80b7b5ae025c7070a0845b774e6543f79415b8c398f45af7" dependencies = [ "memchr", "num-bigint", @@ -2002,9 +2002,9 @@ dependencies = [ [[package]] name = "swc_ecma_ext_transforms" -version = "0.91.37" +version = "0.91.39" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "174b61dbb706cb1abc9eafa90faef3b2978c9a3e695bea944bd00c1fc192807e" +checksum = "66e6bf70ae6092e31212325bb1228d7cb57be283074c5ce5cb9bc4f01053db25" dependencies = [ "phf", "swc_atoms", @@ -2016,9 +2016,9 @@ dependencies = [ [[package]] name = "swc_ecma_lints" -version = "0.66.56" +version = "0.66.59" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e03462a3077c1cbe58a6fb4bdaede607bd9ebfee9b8100ee3f8cef6cdb34b992" +checksum = "6d5d40f1f5c378a359f16126a8448c422110d294f5c18790b4497bf3d47c631c" dependencies = [ "ahash", "auto_impl", @@ -2037,9 +2037,9 @@ dependencies = [ [[package]] name = "swc_ecma_loader" -version = "0.41.16" +version = "0.41.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70b019b735b4aea596baf98d22878579478e1aeb4a2fe41acc93a6b049735b93" +checksum = "d1bd9051cbd8d792f7bed99f1dbe4ad550bcf885cce2359f298bde8508a24c31" dependencies = [ "ahash", "anyhow", @@ -2059,9 +2059,9 @@ dependencies = [ [[package]] name = "swc_ecma_minifier" -version = "0.159.96" +version = "0.159.99" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "098a9f93e2e200f1b87732f9a8c0bd214694e815d1b2cbd580be3f6e3480b011" +checksum = "d136af52a9d97ff4e2a92f02644b04238e686c25e1d42891acacc8339c15d3d1" dependencies = [ "ahash", "arrayvec", @@ -2093,9 +2093,9 @@ dependencies = [ [[package]] name = "swc_ecma_parser" -version = "0.122.29" +version = "0.122.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ea0ad1938122f69686aaa60828e56e5da30e2ce356bee4e9710ae3a1c699dea" +checksum = "f2676e6a964ea14e900024db687aabe9c2162a756338e3ba9d331e2ef44534f4" dependencies = [ "either", "enum_kind", @@ -2112,9 +2112,9 @@ dependencies = [ [[package]] name = "swc_ecma_preset_env" -version = "0.174.60" +version = "0.174.63" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b84bd0ee54fde06c39403825ad0740877d879acadab449821e8d64a76c289eb" +checksum = "55847ec7ea485d2fbc80863a4073c2f91401637a0eba057106726b7a84760fc3" dependencies = [ "ahash", "anyhow", @@ -2137,9 +2137,9 @@ dependencies = [ [[package]] name = "swc_ecma_transforms" -version = "0.198.60" +version = "0.198.63" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f9351c9eea401f1e4183fa2de2f6bfe55521c4689901924123c5aaccdd6fd07" +checksum = "912527d1d2af88f3c3a8010a196890930ee9dc2295eeeb0f61ee885fdac23591" dependencies = [ "swc_atoms", "swc_common", @@ -2157,9 +2157,9 @@ dependencies = [ [[package]] name = "swc_ecma_transforms_base" -version = "0.111.55" +version = "0.111.58" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "530e30b116e59cdc6f1e05594f7953111436a1e79543795a3f13097d0d75f453" +checksum = "2c0ad15396b65635844021483cb6c83e0a8a253e959ec2f546eeddc6faee4fab" dependencies = [ "better_scoped_tls", "bitflags", @@ -2179,9 +2179,9 @@ dependencies = [ [[package]] name = "swc_ecma_transforms_classes" -version = "0.100.54" +version = "0.100.57" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c887fc91a84d5ab8e306bf75aff9b46f775b907a45f769dd9bbcd37f6f672c9d" +checksum = "c4b1d890c82fd59cb2cfcc0ca501563103fd77ed6e04349b65432534d018b970" dependencies = [ "swc_atoms", "swc_common", @@ -2193,9 +2193,9 @@ dependencies = [ [[package]] name = "swc_ecma_transforms_compat" -version = "0.136.48" +version = "0.136.51" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b49461279fe4f029041b9a6aa947c1337d25ae87b6135e3b12cf579c8584e90" +checksum = "b6a90bf33231b48cfc42690c6131374f422fdb89e4dd347742a90e55971bed69" dependencies = [ "ahash", "arrayvec", @@ -2232,9 +2232,9 @@ dependencies = [ [[package]] name = "swc_ecma_transforms_module" -version = "0.153.52" +version = "0.153.55" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ea58451864586eb40a920c8347bcca9183db9b655da199caade95be1748fdcc" +checksum = "f27675cfa7c9bd4a679bc02045125a23269f81ded0549bb65806dc6e53d0eee4" dependencies = [ "Inflector", "ahash", @@ -2260,9 +2260,9 @@ dependencies = [ [[package]] name = "swc_ecma_transforms_optimization" -version = "0.167.60" +version = "0.167.63" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cdc5fef67528f28d2cb5218d4afed05551fa87a8eb9bf3190b86e4b7e32f99bc" +checksum = "f67c3adf40cd1a5f85108759181a36cdb0e4b5422279ace33dd4f4e49e9c4cf1" dependencies = [ "ahash", "dashmap", @@ -2285,9 +2285,9 @@ dependencies = [ [[package]] name = "swc_ecma_transforms_proposal" -version = "0.144.48" +version = "0.144.51" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a7345ea66975671dd8a68310d10536430dfce7415ae9d657edfbacf2d5b2ca2" +checksum = "71f12817cfcb25433aed1f02194dd6bb6a49c91528eddca3039e88af87da086c" dependencies = [ "either", "serde", @@ -2304,9 +2304,9 @@ dependencies = [ [[package]] name = "swc_ecma_transforms_react" -version = "0.155.53" +version = "0.155.56" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3d8d9b050792194de8183ff2cbfc8bd63393f61138a5660df1ec346178535c2" +checksum = "74e008af280dd3f9dc5580c3219619fe9dba46e3373db00787ba40d60f93eee5" dependencies = [ "ahash", "base64", @@ -2330,9 +2330,9 @@ dependencies = [ [[package]] name = "swc_ecma_transforms_typescript" -version = "0.159.55" +version = "0.159.58" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dcb800ad09b8fe03c56fa88ca8f9d975d44a7f014d2a355946cd7f77ea8219d2" +checksum = "55d13d1239328e3a66f243e2d51d039af704bb890d1bee059623a2dec02c147a" dependencies = [ "serde", "swc_atoms", @@ -2346,9 +2346,9 @@ dependencies = [ [[package]] name = "swc_ecma_utils" -version = "0.105.37" +version = "0.105.39" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac9a42eb8a8d4d0ce9d623e3b64691deee7690a1d3a725b2d0088658679ae09b" +checksum = "3972d422059ab3009a2ed78be32d86abb6ebf9d2a1025de0fd9c6743e4b8afaa" dependencies = [ "indexmap", "num_cpus", @@ -2363,9 +2363,9 @@ dependencies = [ [[package]] name = "swc_ecma_visit" -version = "0.80.20" +version = "0.80.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6ec255eeefb6a5146800047d1ee86c44326b9198f98c54988b2740fb40ec3f9" +checksum = "07c3c5ca0a76f58b8b40e3733e4c67c47a6875c318e40ad11d535a26e44609b6" dependencies = [ "num-bigint", "swc_atoms", @@ -2389,9 +2389,9 @@ dependencies = [ [[package]] name = "swc_error_reporters" -version = "0.13.15" +version = "0.13.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c500999360900f1f36838549e1a4faf1a3499a2d70cc5c24edd9ba3134448b9f" +checksum = "f7f7b896139b023c3b07522f5c2264ab433941d7277cc979acd331b1d472932c" dependencies = [ "anyhow", "miette 4.7.1", @@ -2402,9 +2402,9 @@ dependencies = [ [[package]] name = "swc_fast_graph" -version = "0.17.16" +version = "0.17.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13830b02764bf532d347118283393fe6f2b6b5a93c6948af443d2891958705f9" +checksum = "dcb71b75f0b74f3d00500930d3cb0a43729abf3f5799fe2d90b2815738e0935f" dependencies = [ "ahash", "indexmap", @@ -2426,9 +2426,9 @@ dependencies = [ [[package]] name = "swc_node_comments" -version = "0.16.15" +version = "0.16.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ad0ec8c52bc0ed6225805117688abe890313d491c36de5665dcc2e80a643f08" +checksum = "3b14ee59e2f0adcaa7dc3e4e1e97bac26e03b31700be82eb9b4b9be469a38c54" dependencies = [ "ahash", "dashmap", @@ -2438,9 +2438,9 @@ dependencies = [ [[package]] name = "swc_timer" -version = "0.17.15" +version = "0.17.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5d2783e7023fcf40687ac2700ec4ad98e31851f4c5efc55291ebd13ae374e6a" +checksum = "f191978a63b86e2b0e93283efb1fe1a44d49a1fd1bdb9c2c8911f40215dbf172" dependencies = [ "tracing", ] @@ -2482,9 +2482,9 @@ dependencies = [ [[package]] name = "syn" -version = "1.0.103" +version = "1.0.104" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a864042229133ada95abf3b54fdc62ef5ccabe9515b64717bcb9a1919e59445d" +checksum = "4ae548ec36cf198c0ef7710d3c230987c2d6d7bd98ad6edc0274462724c585ce" dependencies = [ "proc-macro2", "quote", @@ -2562,9 +2562,9 @@ dependencies = [ [[package]] name = "time" -version = "0.1.44" +version = "0.1.45" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6db9e6914ab8b1ae1c260a4ae7a49b6c5611b40328a735b21862567685e73255" +checksum = "1b797afad3f312d1c66a56d11d0316f916356d11bd158fbc6ca6389ff6bf805a" dependencies = [ "libc", "wasi 0.10.0+wasi-snapshot-preview1", diff --git a/Cargo.toml b/Cargo.toml index 167f351c..f147dd33 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,7 +13,7 @@ fixedbitset = "0.4.2" itertools = "0.10.5" miette = { version = "5.5.0", features = ["fancy"] } num-bigint = { version = "0.4.3" } -serde = { version = "1.0.147", features = ["derive"] } +serde = { version = "1.0.148", features = ["derive"] } serde_json = "1.0.89" serde_yaml = "0.9.14" petgraph = "0.6.2" @@ -23,7 +23,7 @@ once_cell = "1.16.0" regex = "1.7.0" rustc-hash = "1.1.0" smallvec = { version = "1.10.0", features = ["union"] } -swc_core = { version = "0.43.30", features = ["common_perf", "common", "common_sourcemap", "ecma_visit_path", "ecma_utils", "ecma_ast", "swc", "ecma_visit", "ecma_transforms", "ecma_transforms_module", "ecma_transforms_typescript", "ecma_parser_typescript"] } +swc_core = { version = "0.43.33", features = ["common_perf", "common", "common_sourcemap", "ecma_visit_path", "ecma_utils", "ecma_ast", "swc", "ecma_visit", "ecma_transforms", "ecma_transforms_module", "ecma_transforms_typescript", "ecma_parser_typescript"] } thiserror = "1.0.37" tracing = "0.1.37" tracing-subscriber = { version = "0.3.16", features = ["env-filter"] } From 6c8a3ae0228b33f1af7f8cd95bc51a456089bf86 Mon Sep 17 00:00:00 2001 From: Joshua Wong Date: Mon, 28 Nov 2022 19:56:19 -0600 Subject: [PATCH 020/517] feat: detect resolver definitions --- crates/forge_analyzer/src/definitions.rs | 1018 ++++++++++++++++------ crates/forge_analyzer/src/ir.rs | 118 ++- crates/forge_utils/src/lib.rs | 2 +- crates/fsrt/src/main.rs | 12 +- 4 files changed, 852 insertions(+), 298 deletions(-) diff --git a/crates/forge_analyzer/src/definitions.rs b/crates/forge_analyzer/src/definitions.rs index 65c9e330..5780e133 100644 --- a/crates/forge_analyzer/src/definitions.rs +++ b/crates/forge_analyzer/src/definitions.rs @@ -9,24 +9,25 @@ use swc_core::{ common::SyntaxContext, ecma::{ ast::{ - AssignProp, BindingIdent, CallExpr, Callee, ClassDecl, ClassExpr, ComputedPropName, - Decl, DefaultDecl, ExportAll, ExportDecl, ExportDefaultDecl, ExportDefaultExpr, - ExportNamedSpecifier, Expr, ExprOrSpread, FnDecl, FnExpr, Id, Ident, ImportDecl, - ImportDefaultSpecifier, ImportNamedSpecifier, ImportStarAsSpecifier, KeyValueProp, Lit, - MemberExpr, MemberProp, MethodProp, Module, ModuleDecl, ModuleExportName, ModuleItem, - NewExpr, ObjectLit, Pat, PrivateName, Prop, PropName, PropOrSpread, Stmt, Str, VarDecl, - VarDeclarator, + ArrowExpr, AssignPat, AssignPatProp, AssignProp, BindingIdent, CallExpr, Callee, + ClassDecl, ClassExpr, ComputedPropName, Decl, DefaultDecl, ExportAll, ExportDecl, + ExportDefaultDecl, ExportDefaultExpr, ExportNamedSpecifier, Expr, ExprOrSpread, FnDecl, + FnExpr, Function, Id, Ident, ImportDecl, ImportDefaultSpecifier, ImportNamedSpecifier, + ImportStarAsSpecifier, KeyValuePatProp, KeyValueProp, Lit, MemberExpr, MemberProp, + MethodProp, Module, ModuleDecl, ModuleExportName, ModuleItem, NewExpr, ObjectLit, + ObjectPat, ObjectPatProp, Pat, PrivateName, Prop, PropName, PropOrSpread, Stmt, Str, + VarDecl, VarDeclarator, }, atoms::JsWord, visit::{noop_visit_type, Visit, VisitWith}, }, }; -use tracing::{debug, warn}; +use tracing::{debug, info, instrument, warn}; use typed_index_collections::{TiSlice, TiVec}; use crate::{ ctx::ModId, - ir::{BasicBlockId, Body, Inst, Terminator}, + ir::{BasicBlockId, Body, Inst, Operand, Terminator}, }; create_newtype! { @@ -41,44 +42,6 @@ create_newtype! { pub struct DefId(u32); } -const INVALID_FUNC: FuncId = FuncId(u32::MAX); - -const INVALID_CLASS: ObjId = ObjId(u32::MAX); - -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] -pub enum DefKind { - Function(FuncId), - Class(ObjId), - ExportAlias(DefId), - /// exported(usuall) handler to the actual resolver definitions - /// - /// [`DefId`] should point to [`DefKind::Resolver`] - /// - /// # Example: - /// - /// ```javascript - /// import Resolver from '@forge/resolver'; - /// - /// const resolver = new Resolver(); - /// - /// resolver.define('handlerFunc' ({ payload, context }) => { - /// console.log(`payload: ${payload}, ctx: ${context}`); - /// }); - /// // the `handler` symbol resolves to a DefId for [`DefKind::ResolverHandler`] - /// export const handler = resolver.getDefinitions(); - /// ``` - ResolverHandler(DefId), - Resolver(ObjId), - // to account for the common pattern of object literals being used to organize - // functions - ObjLit(ObjId), - // Ex: `module` in import * as 'foo' from 'module' - ModuleNs(ModId), - Foreign(ForeignId), - // should only be set by the initial exporter - Undefined, -} - #[derive(Debug, Clone, Copy, PartialEq, Eq)] enum ObjKind { Class(ObjId), @@ -105,13 +68,20 @@ struct SymbolExport { #[derive(Debug, Clone, Default)] struct ResolverTable { - defs: TiVec, + defs: TiVec, names: TiVec, symbol_to_id: FxHashMap, parent: FxHashMap, owning_module: TiVec, } +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +enum AnonType { + Obj, + Closure, + Unknown, +} + struct ModuleDefs { symbols: FxHashMap, functions: Box<[DefId]>, @@ -120,14 +90,15 @@ struct ModuleDefs { exports: Box<[DefId]>, } +#[instrument(skip(modules, file_resolver))] pub fn run_resolver( modules: &TiSlice, file_resolver: &ForgeResolver, -) -> (Resolver, TiVec) { - let mut resolver = Resolver::new(); +) -> Environment { + let mut resolver = Environment::new(); for (curr_mod, module) in modules.iter_enumerated() { let mut export_collector = ExportCollector { - res_table: &mut resolver.res_table, + res_table: &mut resolver.resolver, curr_mod, exports: vec![], default: None, @@ -154,15 +125,18 @@ pub fn run_resolver( module.visit_with(&mut import_collector); } - //TODO: return defs instead of cloning - let mut defs = Definitions { - foreign: foreign.clone(), - ..Default::default() - }; + let defs = Definitions::new( + resolver + .resolver + .defs + .iter_enumerated() + .map(|(id, &d)| (id, d)), + foreign, + ); + resolver.defs = defs; for (curr_mod, module) in modules.iter_enumerated() { let mut lowerer = Lowerer { res: &mut resolver, - defs: &mut defs, curr_mod, parents: vec![], curr_def: None, @@ -170,7 +144,16 @@ pub fn run_resolver( module.visit_with(&mut lowerer); } - (resolver, foreign) + for (curr_mod, module) in modules.iter_enumerated() { + let mut collector = FunctionCollector { + res: &mut resolver, + module: curr_mod, + parent: None, + }; + module.visit_with(&mut collector); + } + + resolver } /// this struct is a bit of a hack, because we also use it for @@ -187,12 +170,18 @@ pub fn run_resolver( /// } /// ``` #[derive(Debug, Clone)] -struct Class { +pub struct Class { def: DefId, pub_members: Vec<(JsWord, DefId)>, constructor: Option, } +enum PropKind { + Alias(DefId), + Unknown, + Setter(DefId), +} + enum ObjTy { Class { constructor: DefId }, Lit, @@ -215,6 +204,13 @@ impl Class { constructor: None, } } + + fn find_member(&self, name: &JsWord) -> Option { + self.pub_members + .iter() + .find(|(n, _)| n == name) + .map(|(_, d)| *d) + } } create_newtype! { @@ -234,22 +230,125 @@ pub struct ForeignItem { module_name: JsWord, } +type DefKey = DefKind; +type DefRef<'a> = DefKind<&'a Body, &'a Class, &'a ForeignItem>; +type DefMut<'a> = DefKind<&'a mut Body, &'a mut Class, &'a mut ForeignItem>; +type DefRes = DefKind<(), (), I>; + +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum DefKind { + Arg, + Function(F), + ExportAlias(DefId), + GlobalObj(O), + Class(O), + Foreign(I), + /// exported(usual) handler to the actual resolver definitions + /// + /// [`DefId`] should point to [`DefKind::Resolver`] + /// + /// # Example: + /// + /// ```javascript + /// import Resolver from '@forge/resolver'; + /// + /// const resolver = new Resolver(); + /// + /// resolver.define('handlerFunc' ({ payload, context }) => { + /// console.log(`payload: ${payload}, ctx: ${context}`); + /// }); + /// // the `handler` symbol resolves to a DefId for [`DefKind::ResolverHandler`] + /// export const handler = resolver.getDefinitions(); + /// ``` + ResolverHandler(DefId), + Resolver(O), + ResolverDef(DefId), + Closure(F), + // Ex: `module` in import * as 'foo' from 'module' + ModuleNs(ModId), + Undefined, +} + +impl Default for DefKind { + fn default() -> Self { + Self::Undefined + } +} + +impl DefKind { + fn expect_body(self) -> F { + match self { + Self::Function(f) | Self::Closure(f) => f, + _ => panic!("expected function"), + } + } + + fn expect_class(self) -> O { + match self { + Self::Class(c) | Self::GlobalObj(c) | Self::Resolver(c) => c, + _ => panic!("expected class"), + } + } +} + +impl PartialEq for DefRes { + fn eq(&self, other: &DefKey) -> bool { + match (*self, *other) { + (DefKind::Function(()), DefKind::Function(_)) => true, + (DefKind::ExportAlias(l0), DefKind::ExportAlias(r0)) => l0 == r0, + (DefKind::GlobalObj(()), DefKind::GlobalObj(_)) => true, + (DefKind::Class(()), DefKind::Class(_)) => true, + (DefKind::Foreign(l0), DefKind::Foreign(r0)) => l0 == r0, + (DefKind::ResolverHandler(l0), DefKind::ResolverHandler(r0)) => l0 == r0, + (DefKind::Resolver(()), DefKind::Resolver(_)) => true, + (DefKind::ResolverDef(l0), DefKind::ResolverDef(r0)) => l0 == r0, + (DefKind::Closure(()), DefKind::Closure(_)) => true, + (DefKind::ModuleNs(l0), DefKind::ModuleNs(r0)) => l0 == r0, + _ => false, + } + } +} + +impl PartialEq for DefKey { + fn eq(&self, other: &DefRes) -> bool { + match (*self, *other) { + (DefKind::Function(_), DefKind::Function(())) => true, + (DefKind::ExportAlias(l0), DefKind::ExportAlias(r0)) => l0 == r0, + (DefKind::GlobalObj(_), DefKind::GlobalObj(())) => true, + (DefKind::Class(_), DefKind::Class(())) => true, + (DefKind::Foreign(l0), DefKind::Foreign(r0)) => l0 == r0, + (DefKind::ResolverHandler(l0), DefKind::ResolverHandler(r0)) => l0 == r0, + (DefKind::Resolver(_), DefKind::Resolver(())) => true, + (DefKind::ResolverDef(l0), DefKind::ResolverDef(r0)) => l0 == r0, + (DefKind::Closure(_), DefKind::Closure(())) => true, + (DefKind::ModuleNs(l0), DefKind::ModuleNs(r0)) => l0 == r0, + _ => false, + } + } +} + +impl Copy for DefKey {} +impl Copy for DefRes {} +impl Copy for DefRef<'_> {} + #[derive(Debug, Clone, Default)] struct Definitions { + defs: TiVec, funcs: TiVec, classes: TiVec, foreign: TiVec, } #[derive(Debug, Clone, Default)] -pub struct Resolver { +pub struct Environment { exports: TiVec>, + defs: Definitions, default_exports: FxHashMap, - res_table: ResolverTable, + resolver: ResolverTable, } struct ImportCollector<'cx> { - resolver: &'cx mut Resolver, + resolver: &'cx mut Environment, file_resolver: &'cx ForgeResolver, foreign_defs: &'cx mut TiVec, curr_mod: ModId, @@ -271,13 +370,13 @@ enum LowerStage { } struct Lowerer<'cx> { - res: &'cx mut Resolver, - defs: &'cx mut Definitions, + res: &'cx mut Environment, curr_mod: ModId, parents: Vec, curr_def: Option, } +#[derive(Debug, Clone, PartialEq, Eq, Hash)] enum PropPath { Def(DefId), Str(JsWord), @@ -289,9 +388,13 @@ enum PropPath { Computed(Id), } -fn normalize_callee_expr(callee: &Callee, res_table: &ResolverTable, curr_mod: ModId) { +fn normalize_callee_expr( + callee: &Callee, + res_table: &Environment, + curr_mod: ModId, +) -> Vec { struct CalleeNormalizer<'cx> { - res_table: &'cx ResolverTable, + res_table: &'cx Environment, curr_mod: ModId, path: Vec, in_prop: bool, @@ -325,7 +428,11 @@ fn normalize_callee_expr(callee: &Callee, res_table: &ResolverTable, curr_mod: M if self.in_prop { self.path.push(PropPath::Computed(id)); } else { - self.path.push(PropPath::Unknown(id)); + let prop = self + .res_table + .sym_to_id(id.clone(), self.curr_mod) + .map_or(PropPath::Unknown(id), PropPath::Def); + self.path.push(prop); } } @@ -374,6 +481,10 @@ fn normalize_callee_expr(callee: &Callee, res_table: &ResolverTable, curr_mod: M path: vec![], in_prop: false, }; + normalizer.visit_expr(expr); + normalizer.path + } else { + vec![] } } @@ -384,14 +495,14 @@ impl ResolverTable { } #[inline] - fn sym_kind(&self, id: Id, module: ModId) -> Option { + fn sym_kind(&self, id: Id, module: ModId) -> Option { let def = self.sym_id(id, module)?; self.defs.get(def).copied() } #[inline] fn reserve_symbol(&mut self, id: Id, module: ModId) -> DefId { - self.add_sym(DefKind::Undefined, id, module) + self.add_sym(DefRes::default(), id, module) } #[inline] @@ -401,20 +512,20 @@ impl ResolverTable { } fn reserve_def(&mut self, name: JsWord, module: ModId) -> DefId { - self.defs.push_and_get_key(DefKind::Undefined); + self.defs.push_and_get_key(DefRes::default()); self.names.push_and_get_key(name); self.owning_module.push_and_get_key(module) } - fn add_prop(&mut self, def: DefKind, prop: JsWord, module: ModId) -> DefId { + fn add_anon(&mut self, def: DefRes, name: JsWord, module: ModId) -> DefId { let defid = self.defs.push_and_get_key(def); let defid2 = self.owning_module.push_and_get_key(module); debug_assert_eq!( defid, defid2, "inconsistent state while inserting {}", - &*prop + &*name ); - let defid3 = self.names.push_and_get_key(prop); + let defid3 = self.names.push_and_get_key(name); debug_assert_eq!( defid, defid3, @@ -424,7 +535,7 @@ impl ResolverTable { defid } - fn add_sym(&mut self, def: DefKind, id: Id, module: ModId) -> DefId { + fn add_sym(&mut self, def: DefRes, id: Id, module: ModId) -> DefId { let defid = self.defs.push_and_get_key(def); let defid2 = self.owning_module.push_and_get_key(module); debug_assert_eq!(defid, defid2, "inconsistent state while inserting {}", id.0); @@ -442,19 +553,19 @@ impl ResolverTable { } struct FunctionCollector<'cx> { - res: &'cx mut Resolver, - defs: &'cx mut Definitions, + res: &'cx mut Environment, module: ModId, parent: Option, } struct FunctionAnalyzer<'cx> { - res: &'cx mut Resolver, - defs: &'cx mut Definitions, + res: &'cx mut Environment, module: ModId, current_def: DefId, body: Body, block: BasicBlockId, + operand_stack: Vec, + in_rhs: bool, } impl<'cx> FunctionAnalyzer<'cx> { @@ -470,81 +581,191 @@ impl<'cx> FunctionAnalyzer<'cx> { } impl Visit for FunctionAnalyzer<'_> { - fn visit_member_expr(&mut self, n: &MemberExpr) {} + fn visit_call_expr(&mut self, n: &CallExpr) { + n.visit_children_with(self); + let _props = normalize_callee_expr(&n.callee, self.res, self.module); + match _props.first() { + Some(&PropPath::Def(id)) => { + debug!("call from: {}", self.res.def_name(id)); + debug!("call expr: {:?}", _props); + } + Some(PropPath::Unknown(id)) => { + debug!("call from: {}", id.0); + debug!("call expr: {:?}", _props); + } + _ => (), + } + } +} + +struct ArgDefiner<'cx> { + res: &'cx mut Environment, + module: ModId, + func: DefId, + body: Body, +} - fn visit_expr(&mut self, n: &Expr) { +impl Visit for ArgDefiner<'_> { + fn visit_ident(&mut self, n: &Ident) { + let id = n.to_id(); + let defid = self + .res + .get_or_overwrite_sym(id.clone(), self.module, DefRes::Arg); + self.res.add_parent(defid, self.func); + self.body.add_arg(defid, id); + } + + fn visit_object_pat_prop(&mut self, n: &ObjectPatProp) { match n { - Expr::This(_) => todo!(), - Expr::Array(_) => todo!(), - Expr::Object(_) => todo!(), - Expr::Fn(_) => todo!(), - Expr::Unary(_) => todo!(), - Expr::Update(_) => todo!(), - Expr::Bin(_) => todo!(), - Expr::Assign(_) => todo!(), - Expr::Member(_) => todo!(), - Expr::SuperProp(_) => todo!(), - Expr::Cond(_) => todo!(), - Expr::Call(_) => todo!(), - Expr::New(_) => todo!(), - Expr::Seq(_) => todo!(), - Expr::Ident(_) => todo!(), - Expr::Lit(_) => todo!(), - Expr::Tpl(_) => todo!(), - Expr::TaggedTpl(_) => todo!(), - Expr::Arrow(_) => todo!(), - Expr::Class(_) => todo!(), - Expr::Yield(_) => todo!(), - Expr::MetaProp(_) => todo!(), - Expr::Await(_) => todo!(), - Expr::Paren(_) => todo!(), - Expr::JSXMember(_) => todo!(), - Expr::JSXNamespacedName(_) => todo!(), - Expr::JSXEmpty(_) => todo!(), - Expr::JSXElement(_) => todo!(), - Expr::JSXFragment(_) => todo!(), - Expr::TsTypeAssertion(_) => todo!(), - Expr::TsConstAssertion(_) => todo!(), - Expr::TsNonNull(_) => todo!(), - Expr::TsAs(_) => todo!(), - Expr::TsInstantiation(_) => todo!(), - Expr::TsSatisfies(_) => todo!(), - Expr::PrivateName(_) => todo!(), - Expr::OptChain(_) => todo!(), - Expr::Invalid(_) => todo!(), + ObjectPatProp::KeyValue(KeyValuePatProp { key, .. }) => key.visit_with(self), + ObjectPatProp::Assign(AssignPatProp { key, .. }) => self.visit_ident(key), + ObjectPatProp::Rest(_) => {} } } - fn visit_stmt(&mut self, n: &Stmt) { + + fn visit_pat(&mut self, n: &Pat) { match n { - Stmt::Block(_) => todo!(), - Stmt::Empty(_) => todo!(), - Stmt::Debugger(_) => todo!(), - Stmt::With(_) => todo!(), - Stmt::Return(_) => todo!(), - Stmt::Labeled(_) => todo!(), - Stmt::Break(_) => todo!(), - Stmt::Continue(_) => todo!(), - Stmt::If(_) => todo!(), - Stmt::Switch(_) => todo!(), - Stmt::Throw(_) => todo!(), - Stmt::Try(_) => todo!(), - Stmt::While(_) => todo!(), - Stmt::DoWhile(_) => todo!(), - Stmt::For(_) => todo!(), - Stmt::ForIn(_) => todo!(), - Stmt::ForOf(_) => todo!(), - Stmt::Decl(_) => todo!(), - Stmt::Expr(_) => todo!(), + Pat::Ident(_) | Pat::Array(_) => n.visit_children_with(self), + Pat::Object(ObjectPat { props, .. }) => props.visit_children_with(self), + Pat::Assign(AssignPat { left, .. }) => left.visit_with(self), + Pat::Expr(id) => { + if let Expr::Ident(id) = &**id { + id.visit_with(self); + } + } + Pat::Invalid(_) => {} + Pat::Rest(_) => {} + Pat::Invalid(_) => {} } } } +struct LocalDefiner<'cx> { + res: &'cx mut Environment, + module: ModId, + func: DefId, + body: Body, +} + +impl Visit for LocalDefiner<'_> { + fn visit_ident(&mut self, n: &Ident) { + let id = n.to_id(); + let defid = self.res.get_or_insert_sym(id.clone(), self.module); + self.res.try_add_parent(defid, self.func); + self.body.add_local_def(defid, id); + } + + fn visit_var_declarator(&mut self, n: &VarDeclarator) { + n.name.visit_with(self); + } + + fn visit_decl(&mut self, n: &Decl) { + match n { + Decl::Class(_) => {} + Decl::Fn(FnDecl { ident, .. }) => { + ident.visit_with(self); + } + Decl::Var(vars) => vars.visit_children_with(self), + Decl::TsInterface(_) | Decl::TsTypeAlias(_) | Decl::TsEnum(_) | Decl::TsModule(_) => {} + } + } + + fn visit_arrow_expr(&mut self, _: &ArrowExpr) {} + fn visit_fn_decl(&mut self, _: &FnDecl) {} +} + impl Visit for FunctionCollector<'_> { + fn visit_function(&mut self, n: &Function) { + n.visit_children_with(self); + let owner = self.parent.unwrap_or_else(|| { + self.res + .add_anonymous("__UNKNOWN", AnonType::Closure, self.module) + }); + let mut argdef = ArgDefiner { + res: self.res, + module: self.module, + func: owner, + body: Body::with_owner(owner), + }; + n.params.visit_children_with(&mut argdef); + let body = argdef.body; + let mut localdef = LocalDefiner { + res: self.res, + module: self.module, + func: owner, + body, + }; + n.body.visit_children_with(&mut localdef); + let body = localdef.body; + let mut analyzer = FunctionAnalyzer { + res: self.res, + module: self.module, + current_def: owner, + body, + block: BasicBlockId::default(), + operand_stack: vec![], + in_rhs: false, + }; + n.body.visit_with(&mut analyzer); + } + + fn visit_var_declarator(&mut self, n: &VarDeclarator) { + n.visit_children_with(self); + let Some(BindingIdent{id, ..}) = n.name.as_ident() else { + return; + }; + let id = id.to_id(); + match n.init.as_deref() { + Some(Expr::Fn(f)) => { + let defid = self + .res + .get_or_overwrite_sym(id, self.module, DefKind::Function(())); + let old_parent = self.parent.replace(defid); + f.visit_with(self); + self.parent = old_parent; + } + Some(Expr::Arrow(f)) => { + let defid = self + .res + .get_or_overwrite_sym(id, self.module, DefKind::Function(())); + let old_parent = self.parent.replace(defid); + f.visit_with(self); + self.parent = old_parent; + } + _ => {} + } + } + + fn visit_call_expr(&mut self, n: &CallExpr) { + n.visit_children_with(self); + if let Some((def_id, propname, expr)) = as_resolver_def(n, self.res, self.module) { + info!("found possible resolver: {propname}"); + match self.res.lookup_prop(def_id, propname) { + Some(def) => { + info!("analyzing resolver def: {def:?}"); + let mut analyzer = FunctionAnalyzer { + res: self.res, + module: self.module, + current_def: def, + body: Body::with_owner(def), + block: BasicBlockId::default(), + operand_stack: vec![], + in_rhs: false, + }; + expr.visit_with(&mut analyzer); + } + None => { + warn!("resolver def not found"); + } + } + } + } + fn visit_fn_decl(&mut self, n: &FnDecl) { let id = n.ident.to_id(); let def = self.res.get_or_insert_sym(id, self.module); self.parent = Some(def); - n.function.visit_children_with(self); + n.function.visit_with(self); self.parent = None; } } @@ -560,71 +781,20 @@ impl Lowerer<'_> { self.res.get_or_insert_sym(id, self.curr_mod) } - #[inline] - fn reserve_symbol(&mut self, id: Id) -> DefId { - self.res.reserve_sym(id, self.curr_mod) - } - - fn defkind_from_ident(&self, id: Id) -> Option { - let cid = id.clone(); - let defid = self.defid_from_ident(id)?; - Some(self.res.def_kind(defid)) + fn res_from_ident(&self, id: Id) -> Option> { + self.res.sym_to_def(id, self.curr_mod) } fn as_foreign_import(&self, imported_sym: Id, module: &str) -> Option<&ImportKind> { - match self.defkind_from_ident(imported_sym) { - Some(DefKind::Foreign(fid)) => self - .defs - .foreign - .get(fid) - .filter(|&item| item.module_name == *module) - .map(|item| &item.kind), + match self.res_from_ident(imported_sym) { + Some(DefRef::Foreign(item)) if item.module_name == *module => Some(&item.kind), _ => None, } } - #[inline] - fn next_key(&self) -> DefId { - self.res.next_key() - } - - fn def_objlike(&mut self, id: Id, f: impl FnOnce(ObjId) -> ObjKind) -> (DefId, ObjId) { - let idlookup = id.clone(); - match self.defid_from_ident(idlookup) { - Some(def) => { - let defkind = self.res.def_kind(def); - (def, defkind.as_objkind().unwrap().into_inner()) - } - None => { - let next_key = self.next_key(); - let objid = self.defs.classes.push_and_get_key(Class::new(next_key)); - let objkind = f(objid); - self.res.add_sym(objkind.as_defkind(), id, self.curr_mod); - (next_key, objkind.into_inner()) - } - } - } - - fn add_sym_or_else(&mut self, id: Id, f: impl FnOnce(&mut Self, DefId) -> DefKind) -> DefId { - self.defid_from_ident(id.clone()).unwrap_or_else(|| { - let next_key = self.next_key(); - let defkind = f(self, next_key); - self.res.add_sym(defkind, id, self.curr_mod) - }) - } - - fn def_method(&mut self, sym: JsWord) -> DefId { - let func_id = self.defs.funcs.push_and_get_key(Body::default()); - self.res - .add_prop(DefKind::Function(func_id), sym, self.curr_mod) - } - fn def_function(&mut self, id: Id) -> DefId { - self.defid_from_ident(id.clone()).unwrap_or_else(|| { - let funcid = self.defs.funcs.push_and_get_key(Body::default()); - self.res - .add_sym(DefKind::Function(funcid), id, self.curr_mod) - }) + self.res + .get_or_overwrite_sym(id, self.curr_mod, DefRes::Function(())) } } @@ -634,7 +804,11 @@ enum ResolverDef { Handler, } -fn as_resolver(expr: &Expr, res_table: &Resolver, module: ModId) -> Option<(ObjId, ResolverDef)> { +fn as_resolver( + expr: &Expr, + res_table: &Environment, + module: ModId, +) -> Option<(DefId, ResolverDef)> { if let Expr::Member(MemberExpr { obj, prop: MemberProp::Ident(prop), @@ -642,11 +816,12 @@ fn as_resolver(expr: &Expr, res_table: &Resolver, module: ModId) -> Option<(ObjI }) = expr { let id = obj.as_ident()?; - let def = res_table.sym_to_kind(id.to_id(), module)?; + let def_id = res_table.sym_to_id(id.to_id(), module)?; + let def = res_table.def_ref(def_id); if let DefKind::Resolver(obj) = def { match &*prop.sym { - "getDefinitions" => return Some((obj, ResolverDef::Handler)), - "define" => return Some((obj, ResolverDef::FnDef)), + "getDefinitions" => return Some((def_id, ResolverDef::Handler)), + "define" => return Some((def_id, ResolverDef::FnDef)), unknown => { warn!("unknown prop: {unknown} on resolver: {}", &*id.sym); } @@ -656,6 +831,27 @@ fn as_resolver(expr: &Expr, res_table: &Resolver, module: ModId) -> Option<(ObjI None } +fn as_resolver_def<'a>( + call: &'a CallExpr, + res: &Environment, + module: ModId, +) -> Option<(DefId, &'a JsWord, &'a Expr)> { + let Some((objid, ResolverDef::FnDef)) = call + .callee + .as_expr() + .and_then(|expr| as_resolver(expr, res, module)) else + { + return None; + }; + let [ExprOrSpread { expr: name, .. }, ExprOrSpread { expr: args, .. }] = &*call.args else { + return None; + }; + match &**name { + Expr::Lit(Lit::Str(Str { value, .. })) => Some((objid, value, args)), + _ => None, + } +} + impl Visit for Lowerer<'_> { noop_visit_type!(); @@ -666,7 +862,7 @@ impl Visit for Lowerer<'_> { { if let Expr::Lit(Lit::Str(Str { value, .. })) = &**name { let fname = value.clone(); - let class = &mut self.defs.classes[objid]; + let class = self.res.def_mut(objid).expect_class(); class.pub_members.push((fname, self.curr_def.unwrap())); } } @@ -709,24 +905,44 @@ impl Visit for Lowerer<'_> { if let Expr::Lit(Lit::Str(Str { value, .. })) = &**expr { let fname = value.clone(); println!("defining function: {}", &*fname); - let class = &mut self.defs.classes[objid]; - class.pub_members.push((fname, self.curr_def.unwrap())); + let member_def = match &**args { + Expr::Fn(_) | Expr::Arrow(_) => self.res.add_anonymous( + fname.clone(), + AnonType::Closure, + self.curr_mod, + ), + Expr::Ident(id) => self.get_or_insert_sym(id.to_id()), + _ => { + warn!("unknown function def: {:?}", args); + self.res.add_anonymous( + fname.clone(), + AnonType::Unknown, + self.curr_mod, + ) + } + }; + let class = self.res.def_mut(objid).expect_class(); + class.pub_members.push((fname, member_def)); } } } ResolverDef::Handler => { - let def_id = self.get_or_insert_sym(id); - let res_def_id = self.defs.classes[objid].def; - *self.res.def_kind_mut(def_id) = - DefKind::ResolverHandler(res_def_id); + self.res.get_or_overwrite_sym( + id, + self.curr_mod, + DefKind::ResolverHandler(objid), + ); } } } expr.visit_children_with(self); } Expr::Object(ObjectLit { props, .. }) => { - let (def_id, obj_id) = self.def_objlike(id, ObjKind::Lit); + let def_id = + self.res + .get_or_overwrite_sym(id, self.curr_mod, DefKind::GlobalObj(())); let old_def = self.curr_def.replace(def_id); + // TODO:add parent for prop in props { match prop { // TODO: track 'spreaded' objects @@ -735,15 +951,19 @@ impl Visit for Lowerer<'_> { Prop::Shorthand(id) => { let id = id.to_id(); let sym = id.0.clone(); - let def_id = self.reserve_symbol(id); - self.defs.classes[obj_id].pub_members.push((sym, def_id)); + let def_id = self.get_or_insert_sym(id); + self.res + .def_mut(def_id) + .expect_class() + .pub_members + .push((sym, self.curr_def.unwrap())); } Prop::KeyValue(KeyValueProp { key, value }) => { - let cls = &mut self.defs.classes[obj_id]; if let sym @ Some(_) = key.as_symbol() { let defid = value.as_ident().map(|id| { self.res.get_or_insert_sym(id.to_id(), self.curr_mod) }); + let cls = self.res.def_mut(def_id).expect_class(); cls.pub_members.extend(sym.zip(defid)); } } @@ -756,9 +976,16 @@ impl Visit for Lowerer<'_> { Prop::Method(MethodProp { key, function }) => { function.body.visit_with(self); if let Some(sym) = key.as_symbol() { - let def_id = self.def_method(sym.clone()); - let cls = &mut self.defs.classes[obj_id]; - cls.pub_members.push((sym, def_id)); + let def_id = self.res.add_anonymous( + sym.clone(), + AnonType::Closure, + self.curr_mod, + ); + self.res + .def_mut(def_id) + .expect_class() + .pub_members + .push((sym, def_id)); } } }, @@ -774,10 +1001,8 @@ impl Visit for Lowerer<'_> { if Some(&ImportKind::Default) == self.as_foreign_import(callee_id, "@forge/resolver") { - self.add_sym_or_else(id, |this, to_insert| { - let obj_id = this.defs.classes.push_and_get_key(Class::new(to_insert)); - DefKind::Resolver(obj_id) - }); + self.res + .get_or_overwrite_sym(id, self.curr_mod, DefKind::Resolver(())); } expr.visit_children_with(self); } @@ -811,14 +1036,14 @@ impl AsSymbol for PropName { } impl ExportCollector<'_> { - fn add_export(&mut self, def: DefKind, id: Id) -> DefId { + fn add_export(&mut self, def: DefRes, id: Id) -> DefId { let exported_sym = id.0.clone(); let defid = self.res_table.add_sym(def, id, self.curr_mod); self.exports.push((exported_sym, defid)); defid } - fn add_default(&mut self, def: DefKind, id: Option) { + fn add_default(&mut self, def: DefRes, id: Option) { let defid = match id { Some(id) => self.res_table.add_sym(def, id, self.curr_mod), None => { @@ -863,7 +1088,7 @@ impl Visit for ImportCollector<'_> { .resolver .resolve_local_export(ModId::from(id), &import_name) { - self.resolver.res_table.symbol_to_id.insert( + self.resolver.resolver.symbol_to_id.insert( Symbol { module: self.curr_mod, id: local, @@ -878,8 +1103,8 @@ impl Visit for ImportCollector<'_> { module_name: self.current_import.clone(), }); self.resolver - .res_table - .add_sym(DefKind::Foreign(foreign_id), local, self.curr_mod); + .resolver + .add_sym(DefRes::Foreign(foreign_id), local, self.curr_mod); } } } @@ -895,7 +1120,7 @@ impl Visit for ImportCollector<'_> { debug_assert_ne!(self.curr_mod, mod_id); match self.resolver.default_export(mod_id) { Some(def) => { - self.resolver.res_table.symbol_to_id.insert( + self.resolver.resolver.symbol_to_id.insert( Symbol { module: self.curr_mod, id: local, @@ -913,8 +1138,8 @@ impl Visit for ImportCollector<'_> { }); self.resolver - .res_table - .add_sym(DefKind::Foreign(foreign_id), local, self.curr_mod); + .resolver + .add_sym(DefRes::Foreign(foreign_id), local, self.curr_mod); } }; } @@ -928,18 +1153,18 @@ impl Visit for ImportCollector<'_> { Ok(id) => { let mod_id = ModId::from(id); debug_assert_ne!(self.curr_mod, mod_id); - DefKind::ModuleNs(mod_id) + DefRes::ModuleNs(mod_id) } Err(_) => { let foreign_id = self.foreign_defs.push_and_get_key(ForeignItem { kind: ImportKind::Star, module_name: self.current_import.clone(), }); - DefKind::Foreign(foreign_id) + DefRes::Foreign(foreign_id) } }; self.resolver - .res_table + .resolver .add_sym(defkind, local, self.curr_mod); } @@ -956,11 +1181,11 @@ impl Visit for ExportCollector<'_> { match &n.decl { Decl::Class(ClassDecl { ident, .. }) => { let ident = ident.to_id(); - self.add_export(DefKind::Class(INVALID_CLASS), ident); + self.add_export(DefRes::Class(()), ident); } Decl::Fn(FnDecl { ident, .. }) => { let ident = ident.to_id(); - self.add_export(DefKind::Function(INVALID_FUNC), ident); + self.add_export(DefRes::Function(()), ident); } Decl::Var(vardecls) => { let VarDecl { decls, .. } = &**vardecls; @@ -977,7 +1202,7 @@ impl Visit for ExportCollector<'_> { // TODO: handle other kinds of destructuring patterns if let Pat::Ident(BindingIdent { id, .. }) = &n.name { let id = id.to_id(); - self.add_export(DefKind::Undefined, id); + self.add_export(DefRes::Undefined, id); } } @@ -1003,54 +1228,156 @@ impl Visit for ExportCollector<'_> { fn visit_export_default_decl(&mut self, n: &ExportDefaultDecl) { match &n.decl { - DefaultDecl::Class(ClassExpr { ident, .. }) => self.add_default( - DefKind::Class(INVALID_CLASS), - ident.as_ref().map(Ident::to_id), - ), - DefaultDecl::Fn(FnExpr { ident, .. }) => self.add_default( - DefKind::Function(INVALID_FUNC), - ident.as_ref().map(Ident::to_id), - ), + DefaultDecl::Class(ClassExpr { ident, .. }) => { + self.add_default(DefRes::Class(()), ident.as_ref().map(Ident::to_id)) + } + DefaultDecl::Fn(FnExpr { ident, .. }) => { + self.add_default(DefRes::Function(()), ident.as_ref().map(Ident::to_id)) + } DefaultDecl::TsInterfaceDecl(_) => {} } } fn visit_export_named_specifier(&mut self, n: &ExportNamedSpecifier) { let orig_id = n.orig.as_id(); - let orig = self.add_export(DefKind::Undefined, orig_id); + let orig = self.add_export(DefRes::default(), orig_id); if let Some(id) = &n.exported { let exported_id = id.as_id(); - self.add_export(DefKind::ExportAlias(orig), exported_id); + self.add_export(DefRes::ExportAlias(orig), exported_id); } } fn visit_export_default_expr(&mut self, _: &ExportDefaultExpr) { - self.add_default(DefKind::Undefined, None); + self.add_default(DefRes::Undefined, None); } } -impl Resolver { +impl Environment { + #[inline] fn new() -> Self { Self::default() } + #[inline] fn next_key(&self) -> DefId { - self.res_table.defs.next_key() + self.resolver.defs.next_key() } #[inline] - fn add_prop(&mut self, kind: DefKind, sym: JsWord, module: ModId) -> DefId { - self.res_table.add_prop(kind, sym, module) + fn try_add_parent(&mut self, child: DefId, parent: DefId) { + self.resolver.parent.entry(child).or_insert(parent); } #[inline] - fn add_sym(&mut self, kind: DefKind, id: Id, module: ModId) -> DefId { - self.res_table.add_sym(kind, id, module) + fn add_parent(&mut self, child: DefId, parent: DefId) { + self.resolver.parent.insert(child, parent); } #[inline] fn get_or_insert_sym(&mut self, id: Id, module: ModId) -> DefId { - self.res_table.get_or_insert_sym(id, module) + let def_id = self.resolver.get_or_insert_sym(id, module); + let def_id2 = self.defs.defs.get(def_id).copied().map_or_else( + || self.defs.defs.push_and_get_key(DefKey::default()), + |_| def_id, + ); + debug_assert_eq!(def_id, def_id2); + def_id + } + + fn new_key_from_res(&mut self, id: DefId, res: DefRes) -> DefKey { + match res { + DefKind::Arg => DefKind::Arg, + DefKind::Function(_) => { + let func_id = self.defs.funcs.push_and_get_key(Body::with_owner(id)); + DefKind::Function(func_id) + } + DefKind::Closure(_) => { + let closure_id = self.defs.funcs.push_and_get_key(Body::with_owner(id)); + DefKind::Closure(closure_id) + } + DefKind::GlobalObj(_) => { + let obj_id = self.defs.classes.push_and_get_key(Class::new(id)); + DefKind::GlobalObj(obj_id) + } + DefKind::Class(_) => { + let class_id = self.defs.classes.push_and_get_key(Class::new(id)); + DefKind::Class(class_id) + } + DefKind::Resolver(_) => { + let obj_id = self.defs.classes.push_and_get_key(Class::new(id)); + DefKind::Resolver(obj_id) + } + DefKind::ExportAlias(i) => DefKind::ExportAlias(i), + DefKind::Foreign(i) => DefKind::Foreign(i), + DefKind::ResolverHandler(i) => DefKind::ResolverHandler(i), + DefKind::ResolverDef(i) => DefKind::ResolverDef(i), + DefKind::ModuleNs(i) => DefKind::ModuleNs(i), + DefKind::Undefined => DefKind::Undefined, + } + } + + fn add_anonymous(&mut self, name: impl Into, kind: AnonType, module: ModId) -> DefId { + match kind { + AnonType::Obj => { + let id = self + .resolver + .add_anon(DefRes::GlobalObj(()), name.into(), module); + let obj_id = self.defs.classes.push_and_get_key(Class::new(id)); + let id2 = self.defs.defs.push_and_get_key(DefKind::GlobalObj(obj_id)); + debug_assert_eq!(id, id2); + id + } + AnonType::Closure => { + let id = self + .resolver + .add_anon(DefRes::Closure(()), name.into(), module); + let func_id = self.defs.funcs.push_and_get_key(Body::with_owner(id)); + let id2 = self.defs.defs.push_and_get_key(DefKind::Closure(func_id)); + debug_assert_eq!(id, id2); + id + } + AnonType::Unknown => { + let id = self + .resolver + .add_anon(DefRes::Undefined, name.into(), module); + let id2 = self.defs.defs.push_and_get_key(DefKind::Undefined); + debug_assert_eq!(id, id2); + id + } + } + } + + fn module_export + ?Sized>( + &self, + module: ModId, + export_name: &I, + ) -> Option { + if *export_name == *"default" { + self.default_export(module) + } else { + self.exports[module] + .iter() + .find_map(|(ident, defid)| (*export_name == **ident).then_some(*defid)) + } + } + + #[inline] + fn get_or_overwrite_sym(&mut self, id: Id, module: ModId, kind: DefRes) -> DefId { + let defid = self.resolver.get_or_insert_sym(id, module); + self.resolver.defs[defid] = kind; + match self.defs.defs.get(defid).copied() { + Some(key) if key == kind => return defid, + Some(_) => { + let key = self.new_key_from_res(defid, kind); + self.defs.defs[defid] = key; + } + None => { + let key = self.new_key_from_res(defid, kind); + let def2 = self.defs.defs.push_and_get_key(key); + debug_assert_eq!(defid, def2); + } + } + defid } #[inline] @@ -1060,7 +1387,7 @@ impl Resolver { #[inline] pub fn def_name(&self, def: DefId) -> &str { - &self.res_table.names[def] + &self.resolver.names[def] } #[inline] @@ -1069,29 +1396,110 @@ impl Resolver { } #[inline] - pub fn def_kind(&self, def: DefId) -> DefKind { - self.res_table.defs[def] + pub fn def_ref(&self, def: DefId) -> DefRef<'_> { + match self.defs.defs[def] { + DefKind::Arg => DefKind::Arg, + DefKind::Function(f) => { + let body = &self.defs.funcs[f]; + DefKind::Function(body) + } + DefKind::ExportAlias(d) => DefKind::ExportAlias(d), + DefKind::GlobalObj(id) => { + let class = &self.defs.classes[id]; + DefKind::GlobalObj(class) + } + DefKind::Class(id) => { + let class = &self.defs.classes[id]; + DefKind::Class(class) + } + DefKind::Foreign(id) => { + let foreign = &self.defs.foreign[id]; + DefKind::Foreign(foreign) + } + DefKind::ResolverHandler(id) => DefKind::ResolverHandler(id), + DefKind::Resolver(id) => { + let class = &self.defs.classes[id]; + DefKind::Resolver(class) + } + DefKind::ResolverDef(id) => DefKind::ResolverDef(id), + DefKind::Closure(id) => { + let body = &self.defs.funcs[id]; + DefKind::Closure(body) + } + DefKind::ModuleNs(id) => DefKind::ModuleNs(id), + DefKind::Undefined => DefKind::Undefined, + } } - #[inline] - fn reserve_sym(&mut self, id: Id, module: ModId) -> DefId { - self.res_table.reserve_symbol(id, module) + fn lookup_prop(&self, obj: DefId, prop: &JsWord) -> Option { + match self.def_ref(obj) { + DefKind::GlobalObj(obj) | DefKind::Class(obj) | DefKind::Resolver(obj) => { + obj.find_member(prop) + } + DefKind::ExportAlias(def) | DefKind::ResolverHandler(def) => { + self.lookup_prop(def, prop) + } + DefKind::ModuleNs(mid) => self.module_export(mid, prop), + // FIXME: fully resolve foreign items here as well + DefKind::Foreign(_) + | DefKind::Arg + | DefKind::Function(_) + | DefKind::Closure(_) + | DefKind::ResolverDef(_) + | DefKind::Undefined => None, + } } #[inline] fn sym_to_id(&self, id: Id, module: ModId) -> Option { let sym = Symbol { module, id }; - self.res_table.symbol_to_id.get(&sym).copied() + self.resolver.symbol_to_id.get(&sym).copied() + } + + #[inline] + fn sym_to_def(&self, id: Id, module: ModId) -> Option> { + self.resolver.sym_id(id, module).map(|id| self.def_ref(id)) } #[inline] - fn sym_to_kind(&self, id: Id, module: ModId) -> Option { - self.res_table.sym_kind(id, module) + fn sym_to_def_mut(&mut self, id: Id, module: ModId) -> Option> { + self.resolver.sym_id(id, module).map(|id| self.def_mut(id)) } #[inline] - fn def_kind_mut(&mut self, def: DefId) -> &mut DefKind { - &mut self.res_table.defs[def] + fn def_mut(&mut self, def: DefId) -> DefMut<'_> { + match self.defs.defs[def] { + DefKind::Arg => DefKind::Arg, + DefKind::Function(f) => { + let body = &mut self.defs.funcs[f]; + DefKind::Function(body) + } + DefKind::ExportAlias(d) => DefKind::ExportAlias(d), + DefKind::GlobalObj(id) => { + let class = &mut self.defs.classes[id]; + DefKind::GlobalObj(class) + } + DefKind::Class(id) => { + let class = &mut self.defs.classes[id]; + DefKind::Class(class) + } + DefKind::Foreign(id) => { + let foreign = &mut self.defs.foreign[id]; + DefKind::Foreign(foreign) + } + DefKind::ResolverHandler(id) => DefKind::ResolverHandler(id), + DefKind::Resolver(id) => { + let class = &mut self.defs.classes[id]; + DefKind::Resolver(class) + } + DefKind::ResolverDef(id) => DefKind::ResolverDef(id), + DefKind::Closure(id) => { + let body = &mut self.defs.funcs[id]; + DefKind::Closure(body) + } + DefKind::ModuleNs(id) => DefKind::ModuleNs(id), + DefKind::Undefined => DefKind::Undefined, + } } fn resolve_local_export(&self, module: ModId, name: &JsWord) -> Option { @@ -1106,6 +1514,54 @@ impl Resolver { } } +impl Definitions { + fn new( + res: impl IntoIterator, + foreign: TiVec, + ) -> Self { + let mut funcs = TiVec::new(); + let mut classes = TiVec::new(); + let defs: TiVec<_, _> = res + .into_iter() + .map(|(id, def)| match def { + DefKind::Arg => DefKind::Arg, + DefKind::Function(_) => { + let fid = funcs.push_and_get_key(Body::with_owner(id)); + DefKind::Function(fid) + } + DefKind::ExportAlias(d) => DefKind::ExportAlias(d), + DefKind::GlobalObj(_) => { + let objid = classes.push_and_get_key(Class::new(id)); + DefKind::GlobalObj(objid) + } + DefKind::Class(_) => { + let objid = classes.push_and_get_key(Class::new(id)); + DefKind::Class(objid) + } + DefKind::Foreign(d) => DefKind::Foreign(d), + DefKind::ResolverHandler(d) => DefKind::ResolverHandler(d), + DefKind::Resolver(_) => { + let objid = classes.push_and_get_key(Class::new(id)); + DefKind::Resolver(objid) + } + DefKind::ResolverDef(d) => DefKind::ResolverDef(d), + DefKind::Closure(_) => { + let fid = funcs.push_and_get_key(Body::with_owner(id)); + DefKind::Closure(fid) + } + DefKind::ModuleNs(d) => DefKind::ModuleNs(d), + DefKind::Undefined => DefKind::Undefined, + }) + .collect(); + Self { + defs, + funcs, + classes, + foreign, + } + } +} + trait Database { type Value; fn get(&self, key: K) -> Option<&Self::Value>; @@ -1143,39 +1599,45 @@ impl ObjKind { } #[inline] - fn as_defkind(&self) -> DefKind { + fn as_defkind(&self) -> DefKey { match *self { - ObjKind::Class(id) => DefKind::Class(id), - ObjKind::Lit(id) => DefKind::ObjLit(id), - ObjKind::Resolver(id) => DefKind::Resolver(id), + ObjKind::Class(id) => DefKey::Class(id), + ObjKind::Lit(id) => DefKey::GlobalObj(id), + ObjKind::Resolver(id) => DefKey::Resolver(id), } } } -impl DefKind { +impl DefKey { #[inline] fn as_objkind(&self) -> Option { match *self { - DefKind::Class(id) => Some(ObjKind::Class(id)), - DefKind::Resolver(id) => Some(ObjKind::Resolver(id)), - DefKind::ObjLit(id) => Some(ObjKind::Lit(id)), - DefKind::Function(_) - | DefKind::ExportAlias(_) - | DefKind::ResolverHandler(_) - | DefKind::ModuleNs(_) - | DefKind::Foreign(_) - | DefKind::Undefined => None, + DefKey::Class(id) => Some(ObjKind::Class(id)), + DefKey::Resolver(id) => Some(ObjKind::Resolver(id)), + DefKey::GlobalObj(id) => Some(ObjKind::Lit(id)), + DefKey::Function(_) + | DefKey::ResolverDef(_) + | DefKey::Closure(_) + | DefKey::Arg + | DefKey::ExportAlias(_) + | DefKey::ResolverHandler(_) + | DefKey::ModuleNs(_) + | DefKey::Foreign(_) + | DefKey::Undefined => None, } } } -impl fmt::Display for DefKind { +impl fmt::Display for DefKind { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { DefKind::Class(_) => write!(f, "class"), + DefKind::ResolverDef(_) => write!(f, "resolver def"), DefKind::Resolver(_) => write!(f, "resolver"), - DefKind::ObjLit(_) => write!(f, "object literal"), + DefKind::Arg => write!(f, "argument"), + DefKind::GlobalObj(_) => write!(f, "object literal"), DefKind::Function(_) => write!(f, "function"), + DefKind::Closure(_) => write!(f, "closure"), DefKind::ExportAlias(_) => write!(f, "export alias"), DefKind::ResolverHandler(_) => write!(f, "resolver handler"), DefKind::ModuleNs(_) => write!(f, "module namespace"), @@ -1201,7 +1663,7 @@ impl fmt::Display for ForeignItem { } } -impl From for DefKind { +impl From for DefKey { #[inline] fn from(value: ObjKind) -> Self { value.as_defkind() diff --git a/crates/forge_analyzer/src/ir.rs b/crates/forge_analyzer/src/ir.rs index 82781412..52b5bb85 100644 --- a/crates/forge_analyzer/src/ir.rs +++ b/crates/forge_analyzer/src/ir.rs @@ -11,11 +11,14 @@ use std::mem; use forge_utils::create_newtype; use forge_utils::FxHashMap; +use smallvec::smallvec; use smallvec::SmallVec; +use swc_core::ecma::atoms::JsWord; use swc_core::ecma::{ast::Id, atoms::Atom}; use typed_index_collections::TiVec; use crate::ctx::ModId; +use crate::definitions::DefId; create_newtype! { pub struct BasicBlockId(u32); @@ -44,11 +47,21 @@ pub(crate) enum Terminator { }, } +#[derive(Clone, Debug, Copy, PartialEq, Eq)] +pub(crate) enum Intrinsic { + Authorize, + Fetch, + ApiCall, + EnvRead, + StorageOp, +} + #[derive(Clone, Debug)] pub(crate) enum Rvalue { Unary(UnOp, Operand), Bin(BinOp, Operand, Operand), - Read(Variable), + Read(Operand), + Intrinsics(Intrinsic), Template { quasis: Vec, exprs: Vec, @@ -69,13 +82,20 @@ struct Location { stmt: u32, } -#[derive(Clone, Default, Debug)] -pub(crate) struct Body { +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +enum VarKind { + UserDef(DefId), + Temp(DefId), + Arg(DefId), + Ret, +} + +#[derive(Clone, Debug)] +pub struct Body { + owner: Option, blocks: TiVec, - local_vars: TiVec, + local_vars: TiVec, id_to_local: FxHashMap, - predecessors: BTreeSet<(BasicBlockId, BasicBlockId)>, - successors: BTreeSet<(BasicBlockId, BasicBlockId)>, } #[derive(Clone, Copy, Debug, PartialEq, Eq)] @@ -146,7 +166,6 @@ pub(crate) enum UnOp { pub(crate) enum Operand { Var(Variable), Lit(Literal), - Global(ModId, Id), } create_newtype! { @@ -165,8 +184,8 @@ pub(crate) struct Variable { #[derive(Clone, Debug, PartialEq, Eq, Hash)] pub(crate) enum Projection { - Lit(Literal), - Var(VarId), + Known(JsWord), + Computed(VarId), } impl fmt::Display for VarId { @@ -180,9 +199,8 @@ impl fmt::Display for Variable { write!(f, "{}", self.var)?; for proj in &self.projections { match proj { - Projection::Lit(Literal::Str((s, _))) => write!(f, ".{s}")?, - Projection::Lit(lit) => write!(f, "[{lit}]")?, - Projection::Var(id) => write!(f, "[{id}]")?, + Projection::Known(lit) => write!(f, "[\"{lit}\"]")?, + Projection::Computed(id) => write!(f, "[{id}]")?, } } Ok(()) @@ -192,7 +210,34 @@ impl fmt::Display for Variable { impl Body { #[inline] fn new() -> Self { - Body::default() + let local_vars = vec![VarKind::Ret].into(); + Self { + local_vars, + owner: None, + blocks: Default::default(), + id_to_local: Default::default(), + } + } + + #[inline] + pub(crate) fn with_owner(owner: DefId) -> Self { + Self { + owner: Some(owner), + ..Self::new() + } + } + + #[inline] + pub(crate) fn add_local_def(&mut self, def: DefId, id: Id) { + self.id_to_local + .insert(id, self.local_vars.push_and_get_key(VarKind::UserDef(def))); + } + + #[inline] + pub(crate) fn add_arg(&mut self, def: DefId, id: Id) { + self.local_vars.push(VarKind::Arg(def)); + self.id_to_local + .insert(id, VarId((self.local_vars.len() - 1) as u32)); } #[inline] @@ -217,6 +262,23 @@ impl Body { pub(crate) fn push_inst(&mut self, bb: BasicBlockId, inst: Inst) { self.blocks[bb].insts.push(inst); } + + #[inline] + pub(crate) fn push_assign(&mut self, bb: BasicBlockId, var: Variable, val: Rvalue) { + self.blocks[bb].insts.push(Inst::Assign(var, val)); + } + + #[inline] + pub(crate) fn push_expr(&mut self, bb: BasicBlockId, val: Rvalue) { + self.blocks[bb].insts.push(Inst::Expr(val)); + } +} + +impl Default for Body { + #[inline] + fn default() -> Self { + Self::new() + } } impl PartialEq for Literal { @@ -262,3 +324,33 @@ impl fmt::Display for Literal { } } } + +impl Rvalue { + pub(crate) fn with_literal(lit: Literal) -> Self { + Rvalue::Read(Operand::Lit(lit)) + } + + pub(crate) fn with_name(name: VarId) -> Self { + Rvalue::Read(Operand::Var(Variable::new(name))) + } +} + +impl Variable { + #[inline] + pub(crate) fn new(var: VarId) -> Self { + Self { + var, + projections: smallvec![], + } + } + + #[inline] + pub(crate) fn add_computed(&mut self, var: VarId) { + self.projections.push(Projection::Computed(var)); + } + + #[inline] + pub(crate) fn add_known(&mut self, lit: JsWord) { + self.projections.push(Projection::Known(lit)); + } +} diff --git a/crates/forge_utils/src/lib.rs b/crates/forge_utils/src/lib.rs index df2bc0f4..970de9ab 100644 --- a/crates/forge_utils/src/lib.rs +++ b/crates/forge_utils/src/lib.rs @@ -14,7 +14,7 @@ pub use rustc_hash::{FxHashMap, FxHashSet}; #[macro_export] macro_rules! create_newtype { ($vis:vis struct $ident:ident ( $tyvis:vis $ty:ty );) => { - #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)] + #[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)] $vis struct $ident($tyvis $ty); impl ::core::convert::From for $ident { diff --git a/crates/fsrt/src/main.rs b/crates/fsrt/src/main.rs index 34c99ea2..890895e6 100644 --- a/crates/fsrt/src/main.rs +++ b/crates/fsrt/src/main.rs @@ -203,7 +203,7 @@ fn scan_directory(dir: PathBuf, function: Option<&str>, opts: Opts) -> Result, opts: Opts) -> Result Date: Wed, 30 Nov 2022 11:54:54 -0600 Subject: [PATCH 021/517] chore: enable const-new feature for smallvec --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index f147dd33..ac4165ec 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -22,7 +22,7 @@ indexmap = { version = "1.9.2", features = ["std"] } once_cell = "1.16.0" regex = "1.7.0" rustc-hash = "1.1.0" -smallvec = { version = "1.10.0", features = ["union"] } +smallvec = { version = "1.10.0", features = ["union", "const_new"] } swc_core = { version = "0.43.33", features = ["common_perf", "common", "common_sourcemap", "ecma_visit_path", "ecma_utils", "ecma_ast", "swc", "ecma_visit", "ecma_transforms", "ecma_transforms_module", "ecma_transforms_typescript", "ecma_parser_typescript"] } thiserror = "1.0.37" tracing = "0.1.37" From 5aa8677c682cc7ca73ed69dfdb08147eb898d5cf Mon Sep 17 00:00:00 2001 From: Joshua Wong Date: Wed, 30 Nov 2022 11:55:43 -0600 Subject: [PATCH 022/517] feat: lower to IR 2.0 --- crates/forge_analyzer/src/definitions.rs | 293 +++++++++++++++++++++-- crates/forge_analyzer/src/ir.rs | 243 +++++++++++++++++-- 2 files changed, 486 insertions(+), 50 deletions(-) diff --git a/crates/forge_analyzer/src/definitions.rs b/crates/forge_analyzer/src/definitions.rs index 5780e133..17468c4a 100644 --- a/crates/forge_analyzer/src/definitions.rs +++ b/crates/forge_analyzer/src/definitions.rs @@ -9,14 +9,20 @@ use swc_core::{ common::SyntaxContext, ecma::{ ast::{ - ArrowExpr, AssignPat, AssignPatProp, AssignProp, BindingIdent, CallExpr, Callee, - ClassDecl, ClassExpr, ComputedPropName, Decl, DefaultDecl, ExportAll, ExportDecl, - ExportDefaultDecl, ExportDefaultExpr, ExportNamedSpecifier, Expr, ExprOrSpread, FnDecl, - FnExpr, Function, Id, Ident, ImportDecl, ImportDefaultSpecifier, ImportNamedSpecifier, - ImportStarAsSpecifier, KeyValuePatProp, KeyValueProp, Lit, MemberExpr, MemberProp, - MethodProp, Module, ModuleDecl, ModuleExportName, ModuleItem, NewExpr, ObjectLit, - ObjectPat, ObjectPatProp, Pat, PrivateName, Prop, PropName, PropOrSpread, Stmt, Str, - VarDecl, VarDeclarator, + ArrayLit, ArrowExpr, AssignExpr, AssignOp, AssignPat, AssignPatProp, AssignProp, + AwaitExpr, BinExpr, BindingIdent, BlockStmt, BreakStmt, CallExpr, Callee, ClassDecl, + ClassExpr, ComputedPropName, CondExpr, ContinueStmt, Decl, DefaultDecl, DoWhileStmt, + ExportAll, ExportDecl, ExportDefaultDecl, ExportDefaultExpr, ExportNamedSpecifier, + Expr, ExprOrSpread, ExprStmt, FnDecl, FnExpr, ForInStmt, ForOfStmt, ForStmt, Function, + Id, Ident, IfStmt, ImportDecl, ImportDefaultSpecifier, ImportNamedSpecifier, + ImportStarAsSpecifier, KeyValuePatProp, KeyValueProp, LabeledStmt, Lit, MemberExpr, + MemberProp, MetaPropExpr, MethodProp, Module, ModuleDecl, ModuleExportName, ModuleItem, + NewExpr, ObjectLit, ObjectPat, ObjectPatProp, OptCall, OptChainBase, OptChainExpr, + ParenExpr, Pat, PatOrExpr, PrivateName, Prop, PropName, PropOrSpread, ReturnStmt, + SeqExpr, Stmt, Str, Super, SuperProp, SuperPropExpr, SwitchStmt, TaggedTpl, ThisExpr, + ThrowStmt, Tpl, TryStmt, TsAsExpr, TsConstAssertion, TsInstantiation, TsNonNullExpr, + TsSatisfiesExpr, TsTypeAssertion, UnaryExpr, UpdateExpr, VarDecl, VarDeclarator, + WhileStmt, WithStmt, YieldExpr, }, atoms::JsWord, visit::{noop_visit_type, Visit, VisitWith}, @@ -27,7 +33,7 @@ use typed_index_collections::{TiSlice, TiVec}; use crate::{ ctx::ModId, - ir::{BasicBlockId, Body, Inst, Operand, Terminator}, + ir::{BasicBlockId, Body, Inst, Literal, Operand, Projection, Rvalue, Terminator, Variable}, }; create_newtype! { @@ -562,10 +568,11 @@ struct FunctionAnalyzer<'cx> { res: &'cx mut Environment, module: ModId, current_def: DefId, + assigning_to: Option, body: Body, block: BasicBlockId, operand_stack: Vec, - in_rhs: bool, + in_lhs: bool, } impl<'cx> FunctionAnalyzer<'cx> { @@ -578,22 +585,256 @@ impl<'cx> FunctionAnalyzer<'cx> { fn push_curr_inst(&mut self, inst: Inst) { self.body.push_inst(self.block, inst); } -} -impl Visit for FunctionAnalyzer<'_> { - fn visit_call_expr(&mut self, n: &CallExpr) { - n.visit_children_with(self); - let _props = normalize_callee_expr(&n.callee, self.res, self.module); - match _props.first() { - Some(&PropPath::Def(id)) => { - debug!("call from: {}", self.res.def_name(id)); - debug!("call expr: {:?}", _props); + fn lower_member(&mut self, obj: &Expr, prop: &MemberProp) -> Operand { + let obj = self.lower_expr(obj); + let Operand::Var(mut var) = obj else { + // FIXME: handle literals + return obj; + }; + match prop { + MemberProp::Ident(id) | MemberProp::PrivateName(PrivateName { id, .. }) => { + let id = id.to_id(); + var.projections.push(Projection::Known(id.0)); + } + MemberProp::Computed(ComputedPropName { expr, .. }) => { + let opnd = self.lower_expr(expr); + var.projections + .push(self.body.resolve_prop(self.block, opnd)); + } + } + Operand::Var(var) + } + + // TODO: This can probably be made into a trait + fn lower_expr(&mut self, n: &Expr) -> Operand { + match n { + Expr::This(_) => Operand::Var(Variable::THIS), + Expr::Array(ArrayLit { elems, .. }) => { + let array_lit: Vec<_> = elems + .iter() + .map(|e| { + e.as_ref() + .map_or(Operand::UNDEF, |ExprOrSpread { spread, expr }| { + self.lower_expr(expr) + }) + }) + .collect(); + Operand::UNDEF + } + Expr::Object(ObjectLit { span, props }) => { + // TODO: lower object literals + Operand::UNDEF + } + Expr::Fn(_) => Operand::UNDEF, + Expr::Unary(UnaryExpr { op, arg, .. }) => { + let arg = self.lower_expr(arg); + let tmp = self + .body + .push_tmp(self.block, Rvalue::Unary(op.into(), arg), None); + Operand::with_var(tmp) + } + Expr::Update(UpdateExpr { + op, prefix, arg, .. + }) => { + // FIXME: Handle op + self.lower_expr(arg) + } + Expr::Bin(BinExpr { + op, left, right, .. + }) => { + let left = self.lower_expr(left); + let right = self.lower_expr(right); + let tmp = self + .body + .push_tmp(self.block, Rvalue::Bin(op.into(), left, right), None); + Operand::with_var(tmp) + } + + Expr::SuperProp(SuperPropExpr { obj, prop, .. }) => { + let mut super_var = Variable::SUPER; + match prop { + SuperProp::Ident(id) => { + let id = id.to_id().0; + super_var.projections.push(Projection::Known(id)); + } + SuperProp::Computed(ComputedPropName { expr, .. }) => { + let opnd = self.lower_expr(expr); + let prop = self.body.resolve_prop(self.block, opnd); + super_var.projections.push(prop); + } + } + Operand::Var(super_var) } - Some(PropPath::Unknown(id)) => { - debug!("call from: {}", id.0); - debug!("call expr: {:?}", _props); + Expr::Assign(AssignExpr { + op, left, right, .. + }) => match left { + PatOrExpr::Expr(_) => todo!(), + PatOrExpr::Pat(_) => todo!(), + }, + Expr::Member(MemberExpr { obj, prop, .. }) => self.lower_member(obj, prop), + Expr::Cond(CondExpr { + test, cons, alt, .. + }) => self.lower_expr(test), + Expr::Call(n) => { + let mut args = Vec::with_capacity(n.args.len()); + for ExprOrSpread { spread, expr } in &n.args { + let arg = self.lower_expr(expr); + args.push(arg); + } + let callee = match &n.callee { + Callee::Super(_) => Operand::Var(Variable::SUPER), + Callee::Import(_) => Operand::UNDEF, + Callee::Expr(expr) => self.lower_expr(expr), + }; + let props = normalize_callee_expr(&n.callee, self.res, self.module); + match props.first() { + Some(&PropPath::Def(id)) => { + debug!("call from: {}", self.res.def_name(id)); + debug!("call expr: {:?}", props); + } + Some(PropPath::Unknown(id)) => { + debug!("call from: {}", id.0); + debug!("call expr: {:?}", props); + } + _ => (), + } + todo!() } - _ => (), + Expr::New(NewExpr { callee, args, .. }) => Operand::UNDEF, + Expr::Seq(SeqExpr { exprs, .. }) => { + if let Some((last, rest)) = exprs.split_last() { + for expr in rest { + let opnd = self.lower_expr(expr); + self.body.push_expr(self.block, Rvalue::Read(opnd)); + } + self.lower_expr(last) + } else { + Literal::Undef.into() + } + } + Expr::Ident(id) => { + let id = id.to_id(); + let Some(def) = self.res.sym_to_id(id.clone(), self.module) else { + warn!("unknown symbol: {}", id.0); + return Literal::Undef.into(); + }; + let var = self.body.get_or_insert_global(def); + Operand::with_var(var) + } + Expr::Lit(lit) => lit.clone().into(), + Expr::Tpl(Tpl { exprs, quasis, .. }) => todo!(), + Expr::TaggedTpl(TaggedTpl { tag, tpl, .. }) => todo!(), + Expr::Arrow(_) => Operand::UNDEF, + Expr::Class(_) => Operand::UNDEF, + Expr::Yield(YieldExpr { arg, .. }) => arg + .as_deref() + .map_or(Operand::UNDEF, |expr| self.lower_expr(expr)), + Expr::MetaProp(_) => Operand::UNDEF, + Expr::Await(AwaitExpr { arg, .. }) => self.lower_expr(arg), + Expr::Paren(ParenExpr { expr, .. }) => self.lower_expr(expr), + Expr::JSXMember(_) => todo!(), + Expr::JSXNamespacedName(_) => todo!(), + Expr::JSXEmpty(_) => todo!(), + Expr::JSXElement(_) => todo!(), + Expr::JSXFragment(_) => todo!(), + Expr::TsTypeAssertion(TsTypeAssertion { expr, .. }) + | Expr::TsConstAssertion(TsConstAssertion { expr, .. }) + | Expr::TsNonNull(TsNonNullExpr { expr, .. }) + | Expr::TsAs(TsAsExpr { expr, .. }) + | Expr::TsInstantiation(TsInstantiation { expr, .. }) + | Expr::TsSatisfies(TsSatisfiesExpr { expr, .. }) => self.lower_expr(expr), + Expr::PrivateName(PrivateName { id, .. }) => todo!(), + Expr::OptChain(OptChainExpr { base, .. }) => match base { + OptChainBase::Call(OptCall { callee, args, .. }) => todo!(), + OptChainBase::Member(MemberExpr { obj, prop, .. }) => { + // TODO: create separate basic blocks + self.lower_member(obj, prop) + } + }, + Expr::Invalid(_) => Operand::UNDEF, + } + } + + fn lower_stmt(&mut self, n: &Stmt) { + match n { + Stmt::Block(BlockStmt { stmts, .. }) => todo!(), + Stmt::Empty(_) => todo!(), + Stmt::Debugger(_) => todo!(), + Stmt::With(WithStmt { obj, body, .. }) => todo!(), + Stmt::Return(ReturnStmt { arg, .. }) => todo!(), + Stmt::Labeled(LabeledStmt { label, body, .. }) => todo!(), + Stmt::Break(BreakStmt { label, .. }) => todo!(), + Stmt::Continue(ContinueStmt { label, .. }) => todo!(), + Stmt::If(IfStmt { + test, cons, alt, .. + }) => todo!(), + Stmt::Switch(SwitchStmt { + discriminant, + cases, + .. + }) => todo!(), + Stmt::Throw(ThrowStmt { arg, .. }) => todo!(), + Stmt::Try(stmt) => { + let TryStmt { + block, + handler, + finalizer, + .. + } = &**stmt; + todo!() + } + Stmt::While(WhileStmt { test, body, .. }) => todo!(), + Stmt::DoWhile(DoWhileStmt { test, body, .. }) => todo!(), + Stmt::For(ForStmt { + init, + test, + update, + body, + .. + }) => todo!(), + Stmt::ForIn(ForInStmt { + left, right, body, .. + }) => todo!(), + Stmt::ForOf(ForOfStmt { + left, right, body, .. + }) => todo!(), + Stmt::Decl(decl) => match decl { + Decl::Class(_) => todo!(), + Decl::Fn(_) => todo!(), + Decl::Var(_) => todo!(), + Decl::TsInterface(_) => todo!(), + Decl::TsTypeAlias(_) => todo!(), + Decl::TsEnum(_) => todo!(), + Decl::TsModule(_) => todo!(), + }, + Stmt::Expr(ExprStmt { expr, .. }) => todo!(), + } + } +} + +impl Visit for FunctionAnalyzer<'_> { + fn visit_stmt(&mut self, n: &Stmt) { + match n { + Stmt::Block(_) => todo!(), + Stmt::Empty(_) => todo!(), + Stmt::Debugger(_) => todo!(), + Stmt::With(_) => todo!(), + Stmt::Return(_) => todo!(), + Stmt::Labeled(_) => todo!(), + Stmt::Break(_) => todo!(), + Stmt::Continue(_) => todo!(), + Stmt::If(_) => todo!(), + Stmt::Switch(_) => todo!(), + Stmt::Throw(_) => todo!(), + Stmt::Try(_) => todo!(), + Stmt::While(_) => todo!(), + Stmt::DoWhile(_) => todo!(), + Stmt::For(_) => todo!(), + Stmt::ForIn(_) => todo!(), + Stmt::ForOf(_) => todo!(), + Stmt::Decl(_) => todo!(), + Stmt::Expr(_) => todo!(), } } } @@ -701,10 +942,11 @@ impl Visit for FunctionCollector<'_> { res: self.res, module: self.module, current_def: owner, + assigning_to: None, body, block: BasicBlockId::default(), operand_stack: vec![], - in_rhs: false, + in_lhs: false, }; n.body.visit_with(&mut analyzer); } @@ -747,10 +989,11 @@ impl Visit for FunctionCollector<'_> { res: self.res, module: self.module, current_def: def, + assigning_to: None, body: Body::with_owner(def), block: BasicBlockId::default(), operand_stack: vec![], - in_rhs: false, + in_lhs: false, }; expr.visit_with(&mut analyzer); } diff --git a/crates/forge_analyzer/src/ir.rs b/crates/forge_analyzer/src/ir.rs index 52b5bb85..01fd083f 100644 --- a/crates/forge_analyzer/src/ir.rs +++ b/crates/forge_analyzer/src/ir.rs @@ -12,7 +12,15 @@ use std::mem; use forge_utils::create_newtype; use forge_utils::FxHashMap; use smallvec::smallvec; +use smallvec::smallvec_inline; use smallvec::SmallVec; +use swc_core::ecma::ast; +use swc_core::ecma::ast::BinaryOp; +use swc_core::ecma::ast::JSXText; +use swc_core::ecma::ast::Lit; +use swc_core::ecma::ast::Null; +use swc_core::ecma::ast::Number; +use swc_core::ecma::ast::UnaryOp; use swc_core::ecma::atoms::JsWord; use swc_core::ecma::{ast::Id, atoms::Atom}; use typed_index_collections::TiVec; @@ -41,10 +49,15 @@ pub(crate) enum Terminator { args: Vec, ret: Option, }, - Branch { + Switch { scrutinee: Operand, targets: BranchTargets, }, + If { + cond: Operand, + cons: BasicBlockId, + alt: BasicBlockId, + }, } #[derive(Clone, Debug, Copy, PartialEq, Eq)] @@ -53,7 +66,7 @@ pub(crate) enum Intrinsic { Fetch, ApiCall, EnvRead, - StorageOp, + StorageRead, } #[derive(Clone, Debug)] @@ -84,8 +97,10 @@ struct Location { #[derive(Debug, Clone, PartialEq, Eq, Hash)] enum VarKind { - UserDef(DefId), - Temp(DefId), + LocalDef(DefId), + GlobalRef(DefId), + Temp { parent: Option }, + AnonClosure(DefId), Arg(DefId), Ret, } @@ -94,8 +109,9 @@ enum VarKind { pub struct Body { owner: Option, blocks: TiVec, - local_vars: TiVec, - id_to_local: FxHashMap, + vars: TiVec, + ident_to_local: FxHashMap, + def_id_to_vars: FxHashMap, } #[derive(Clone, Copy, Debug, PartialEq, Eq)] @@ -114,7 +130,8 @@ pub(crate) enum Inst { #[derive(Clone, Debug, Default)] pub(crate) enum Literal { - Str(Id), + Str(JsWord), + JSXText(Atom), Bool(bool), Null, #[default] @@ -131,6 +148,7 @@ pub(crate) enum BinOp { Gt, EqEq, Neq, + NeqEq, EqEqEq, Ge, Le, @@ -150,6 +168,7 @@ pub(crate) enum BinOp { RshiftLogical, In, InstanceOf, + NullishCoalesce, } #[derive(Clone, Copy, Debug, PartialEq, Eq)] @@ -160,6 +179,7 @@ pub(crate) enum UnOp { Plus, TypeOf, Delete, + Void, } #[derive(Clone, Debug, PartialEq, Eq, Hash)] @@ -168,6 +188,13 @@ pub(crate) enum Operand { Lit(Literal), } +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] +pub(crate) enum Base { + This, + Super, + Var(VarId), +} + create_newtype! { pub struct Label(u32); } @@ -178,14 +205,14 @@ create_newtype! { #[derive(Clone, Debug, PartialEq, Eq, Hash)] pub(crate) struct Variable { - var: VarId, - projections: SmallVec<[Projection; 1]>, + pub(crate) base: Base, + pub(crate) projections: SmallVec<[Projection; 1]>, } #[derive(Clone, Debug, PartialEq, Eq, Hash)] pub(crate) enum Projection { Known(JsWord), - Computed(VarId), + Computed(Base), } impl fmt::Display for VarId { @@ -196,7 +223,7 @@ impl fmt::Display for VarId { impl fmt::Display for Variable { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{}", self.var)?; + write!(f, "{}", self.base)?; for proj in &self.projections { match proj { Projection::Known(lit) => write!(f, "[\"{lit}\"]")?, @@ -207,15 +234,26 @@ impl fmt::Display for Variable { } } +impl fmt::Display for Base { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match *self { + Base::This => write!(f, "this"), + Base::Super => write!(f, "super"), + Base::Var(id) => write!(f, "{}", id), + } + } +} + impl Body { #[inline] fn new() -> Self { let local_vars = vec![VarKind::Ret].into(); Self { - local_vars, + vars: local_vars, owner: None, blocks: Default::default(), - id_to_local: Default::default(), + ident_to_local: Default::default(), + def_id_to_vars: Default::default(), } } @@ -229,15 +267,23 @@ impl Body { #[inline] pub(crate) fn add_local_def(&mut self, def: DefId, id: Id) { - self.id_to_local - .insert(id, self.local_vars.push_and_get_key(VarKind::UserDef(def))); + self.ident_to_local + .insert(id, self.vars.push_and_get_key(VarKind::LocalDef(def))); } #[inline] pub(crate) fn add_arg(&mut self, def: DefId, id: Id) { - self.local_vars.push(VarKind::Arg(def)); - self.id_to_local - .insert(id, VarId((self.local_vars.len() - 1) as u32)); + self.vars.push(VarKind::Arg(def)); + self.ident_to_local + .insert(id, VarId((self.vars.len() - 1) as u32)); + } + + #[inline] + pub(crate) fn get_or_insert_global(&mut self, def: DefId) -> VarId { + *self + .def_id_to_vars + .entry(def) + .or_insert_with(|| self.vars.push_and_get_key(VarKind::GlobalRef(def))) } #[inline] @@ -263,6 +309,27 @@ impl Body { self.blocks[bb].insts.push(inst); } + pub(crate) fn resolve_prop(&mut self, bb: BasicBlockId, opnd: Operand) -> Projection { + match opnd { + Operand::Lit(lit) => Projection::Known(lit.as_jsword()), + Operand::Var(var) if var.projections.is_empty() => Projection::Computed(var.base), + Operand::Var(_) => { + Projection::Computed(Base::Var(self.push_tmp(bb, Rvalue::Read(opnd), None))) + } + } + } + + pub(crate) fn push_tmp( + &mut self, + bb: BasicBlockId, + val: Rvalue, + parent: Option, + ) -> VarId { + let var = self.vars.push_and_get_key(VarKind::Temp { parent }); + self.push_inst(bb, Inst::Assign(Variable::new(var), val)); + var + } + #[inline] pub(crate) fn push_assign(&mut self, bb: BasicBlockId, var: Variable, val: Rvalue) { self.blocks[bb].insts.push(Inst::Assign(var, val)); @@ -307,6 +374,7 @@ impl Hash for Literal { Literal::Number(n) => n.to_bits().hash(state), Literal::BigInt(bn) => bn.hash(state), Literal::RegExp(s, t) => (s, t).hash(state), + Literal::JSXText(r) => r.hash(state), } } } @@ -314,13 +382,29 @@ impl Hash for Literal { impl fmt::Display for Literal { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match *self { - Literal::Str((ref s, _)) => write!(f, "\"{s}\""), + Literal::Str(ref s) => write!(f, "\"{s}\""), Literal::Bool(b) => write!(f, "{b}"), Literal::Null => write!(f, "[null]"), Literal::Undef => write!(f, "[undefined]"), Literal::Number(n) => write!(f, "{n}"), Literal::BigInt(ref n) => write!(f, "{n}"), Literal::RegExp(ref regex, ref flags) => write!(f, "/{regex}/{flags}"), + Literal::JSXText(ref r) => write!(f, "JSX: \"{r}\""), + } + } +} + +impl Literal { + fn as_jsword(&self) -> JsWord { + match self { + Literal::Str(s) => s.clone(), + Literal::Bool(b) => b.to_string().into(), + Literal::Null => "null".into(), + Literal::Undef => "undefined".into(), + Literal::Number(n) => n.to_string().into(), + Literal::BigInt(n) => n.to_string().into(), + Literal::RegExp(regex, flags) => format!("/{regex}/{flags}").into(), + Literal::JSXText(r) => r.to_string().into(), } } } @@ -330,23 +414,132 @@ impl Rvalue { Rvalue::Read(Operand::Lit(lit)) } - pub(crate) fn with_name(name: VarId) -> Self { + pub(crate) fn with_var(name: VarId) -> Self { Rvalue::Read(Operand::Var(Variable::new(name))) } } +impl Operand { + pub(crate) const UNDEF: Self = Self::Lit(Literal::Undef); + + #[inline] + pub(crate) fn with_literal(lit: Literal) -> Self { + Self::Lit(lit) + } + + #[inline] + pub(crate) const fn with_var(name: VarId) -> Self { + Self::Var(Variable::new(name)) + } +} + +impl From for Operand { + #[inline] + fn from(value: Literal) -> Self { + Self::Lit(value) + } +} + +impl From for Operand { + fn from(value: Lit) -> Self { + Self::Lit(value.into()) + } +} + +impl From for UnOp { + fn from(value: UnaryOp) -> Self { + match value { + UnaryOp::Minus => Self::Neg, + UnaryOp::Plus => Self::Plus, + UnaryOp::Bang => Self::Not, + UnaryOp::Tilde => Self::BitNot, + UnaryOp::TypeOf => Self::TypeOf, + UnaryOp::Void => Self::Void, + UnaryOp::Delete => Self::Delete, + } + } +} + +impl From<&UnaryOp> for UnOp { + fn from(value: &UnaryOp) -> Self { + Self::from(*value) + } +} + +impl From for Literal { + fn from(value: Lit) -> Self { + match value { + Lit::Str(value) => Self::Str(value.value), + Lit::Bool(b) => Self::Bool(b.value), + Lit::Null(_) => Self::Null, + Lit::Num(Number { value, .. }) => Self::Number(value), + Lit::BigInt(ast::BigInt { value, .. }) => Self::BigInt(*value), + Lit::Regex(ast::Regex { exp, flags, .. }) => Self::RegExp(exp, flags), + Lit::JSXText(JSXText { value, .. }) => Self::JSXText(value), + } + } +} + +impl From for BinOp { + fn from(value: BinaryOp) -> Self { + match value { + BinaryOp::EqEq => Self::EqEq, + BinaryOp::NotEq => Self::Neq, + BinaryOp::EqEqEq => Self::EqEqEq, + BinaryOp::NotEqEq => Self::NeqEq, + BinaryOp::Lt => Self::Lt, + BinaryOp::LtEq => Self::Le, + BinaryOp::Gt => Self::Gt, + BinaryOp::GtEq => Self::Ge, + BinaryOp::LShift => Self::Lshift, + BinaryOp::RShift => Self::Rshift, + BinaryOp::ZeroFillRShift => Self::RshiftLogical, + BinaryOp::Add => Self::Add, + BinaryOp::Sub => Self::Sub, + BinaryOp::Mul => Self::Mul, + BinaryOp::Div => Self::Div, + BinaryOp::Mod => Self::Mod, + BinaryOp::BitOr => Self::BitOr, + BinaryOp::BitXor => Self::BitXor, + BinaryOp::BitAnd => Self::BitAnd, + BinaryOp::LogicalOr => Self::Or, + BinaryOp::LogicalAnd => Self::And, + BinaryOp::In => Self::In, + BinaryOp::InstanceOf => Self::InstanceOf, + BinaryOp::Exp => Self::Exp, + BinaryOp::NullishCoalescing => Self::NullishCoalesce, + } + } +} + +impl From<&BinaryOp> for BinOp { + fn from(value: &BinaryOp) -> Self { + Self::from(*value) + } +} + impl Variable { + pub(crate) const THIS: Self = Self { + base: Base::This, + projections: SmallVec::new_const(), + }; + + pub(crate) const SUPER: Self = Self { + base: Base::Super, + projections: SmallVec::new_const(), + }; + #[inline] - pub(crate) fn new(var: VarId) -> Self { + pub(crate) const fn new(var: VarId) -> Self { Self { - var, - projections: smallvec![], + base: Base::Var(var), + projections: SmallVec::new_const(), } } #[inline] - pub(crate) fn add_computed(&mut self, var: VarId) { - self.projections.push(Projection::Computed(var)); + pub(crate) fn add_computed_var(&mut self, var: VarId) { + self.projections.push(Projection::Computed(Base::Var(var))); } #[inline] From ccf51f96707f4225395650cd9d13bcf964d3892e Mon Sep 17 00:00:00 2001 From: Joshua Wong Date: Wed, 30 Nov 2022 11:56:18 -0600 Subject: [PATCH 023/517] chore: add test case for issue #1 --- test-apps/issue-1-resolver/manifest.yml | 20 + test-apps/issue-1-resolver/src/index.js | 2971 +++++++++++++++++++++++ 2 files changed, 2991 insertions(+) create mode 100644 test-apps/issue-1-resolver/manifest.yml create mode 100644 test-apps/issue-1-resolver/src/index.js diff --git a/test-apps/issue-1-resolver/manifest.yml b/test-apps/issue-1-resolver/manifest.yml new file mode 100644 index 00000000..89f4b9b8 --- /dev/null +++ b/test-apps/issue-1-resolver/manifest.yml @@ -0,0 +1,20 @@ +modules: + jira:globalPage: + - key: forge-sandbox + resource: web + resolver: + function: resolver + layout: basic + title: Forge Sandbox + function: + - key: resolver + handler: index.handler +resources: + - key: web + path: web +permissions: + content: + styles: + - unsafe-inline +app: + id: ari:cloud:ecosystem::app/register_to_get_id diff --git a/test-apps/issue-1-resolver/src/index.js b/test-apps/issue-1-resolver/src/index.js new file mode 100644 index 00000000..abc1edd4 --- /dev/null +++ b/test-apps/issue-1-resolver/src/index.js @@ -0,0 +1,2971 @@ +// src/index.ts +import Resolver from "@forge/resolver"; + +// src/lib/get-text.ts +function getText({ text }) { + return "Hello, world!\n" + text; +} + +// src/lib/permissions.ts +import { authorize } from "@forge/api"; +var administerPermission = "ADMINISTER"; +function isGlobalAdminPermission(permission) { + return permission.permission === administerPermission; +} +async function isJiraGlobalAdmin() { + const permissions = await authorize().onJira([ + { permissions: [administerPermission] } + ]); + return permissions.every(isGlobalAdminPermission); +} + +// node_modules/zod/lib/index.mjs +var util; +(function(util2) { + util2.assertEqual = (val) => val; + function assertIs(_arg) { + } + util2.assertIs = assertIs; + function assertNever(_x) { + throw new Error(); + } + util2.assertNever = assertNever; + util2.arrayToEnum = (items) => { + const obj = {}; + for (const item of items) { + obj[item] = item; + } + return obj; + }; + util2.getValidEnumValues = (obj) => { + const validKeys = util2.objectKeys(obj).filter((k) => typeof obj[obj[k]] !== "number"); + const filtered = {}; + for (const k of validKeys) { + filtered[k] = obj[k]; + } + return util2.objectValues(filtered); + }; + util2.objectValues = (obj) => { + return util2.objectKeys(obj).map(function(e) { + return obj[e]; + }); + }; + util2.objectKeys = typeof Object.keys === "function" ? (obj) => Object.keys(obj) : (object) => { + const keys = []; + for (const key in object) { + if (Object.prototype.hasOwnProperty.call(object, key)) { + keys.push(key); + } + } + return keys; + }; + util2.find = (arr, checker) => { + for (const item of arr) { + if (checker(item)) + return item; + } + return void 0; + }; + util2.isInteger = typeof Number.isInteger === "function" ? (val) => Number.isInteger(val) : (val) => typeof val === "number" && isFinite(val) && Math.floor(val) === val; + function joinValues(array, separator = " | ") { + return array.map((val) => typeof val === "string" ? `'${val}'` : val).join(separator); + } + util2.joinValues = joinValues; + util2.jsonStringifyReplacer = (_, value) => { + if (typeof value === "bigint") { + return value.toString(); + } + return value; + }; +})(util || (util = {})); +var ZodParsedType = util.arrayToEnum([ + "string", + "nan", + "number", + "integer", + "float", + "boolean", + "date", + "bigint", + "symbol", + "function", + "undefined", + "null", + "array", + "object", + "unknown", + "promise", + "void", + "never", + "map", + "set" +]); +var getParsedType = (data) => { + const t = typeof data; + switch (t) { + case "undefined": + return ZodParsedType.undefined; + case "string": + return ZodParsedType.string; + case "number": + return isNaN(data) ? ZodParsedType.nan : ZodParsedType.number; + case "boolean": + return ZodParsedType.boolean; + case "function": + return ZodParsedType.function; + case "bigint": + return ZodParsedType.bigint; + case "object": + if (Array.isArray(data)) { + return ZodParsedType.array; + } + if (data === null) { + return ZodParsedType.null; + } + if (data.then && typeof data.then === "function" && data.catch && typeof data.catch === "function") { + return ZodParsedType.promise; + } + if (typeof Map !== "undefined" && data instanceof Map) { + return ZodParsedType.map; + } + if (typeof Set !== "undefined" && data instanceof Set) { + return ZodParsedType.set; + } + if (typeof Date !== "undefined" && data instanceof Date) { + return ZodParsedType.date; + } + return ZodParsedType.object; + default: + return ZodParsedType.unknown; + } +}; +var ZodIssueCode = util.arrayToEnum([ + "invalid_type", + "invalid_literal", + "custom", + "invalid_union", + "invalid_union_discriminator", + "invalid_enum_value", + "unrecognized_keys", + "invalid_arguments", + "invalid_return_type", + "invalid_date", + "invalid_string", + "too_small", + "too_big", + "invalid_intersection_types", + "not_multiple_of" +]); +var quotelessJson = (obj) => { + const json = JSON.stringify(obj, null, 2); + return json.replace(/"([^"]+)":/g, "$1:"); +}; +var ZodError = class extends Error { + constructor(issues) { + super(); + this.issues = []; + this.addIssue = (sub) => { + this.issues = [...this.issues, sub]; + }; + this.addIssues = (subs = []) => { + this.issues = [...this.issues, ...subs]; + }; + const actualProto = new.target.prototype; + if (Object.setPrototypeOf) { + Object.setPrototypeOf(this, actualProto); + } else { + this.__proto__ = actualProto; + } + this.name = "ZodError"; + this.issues = issues; + } + get errors() { + return this.issues; + } + format(_mapper) { + const mapper = _mapper || function(issue) { + return issue.message; + }; + const fieldErrors = { _errors: [] }; + const processError = (error) => { + for (const issue of error.issues) { + if (issue.code === "invalid_union") { + issue.unionErrors.map(processError); + } else if (issue.code === "invalid_return_type") { + processError(issue.returnTypeError); + } else if (issue.code === "invalid_arguments") { + processError(issue.argumentsError); + } else if (issue.path.length === 0) { + fieldErrors._errors.push(mapper(issue)); + } else { + let curr = fieldErrors; + let i = 0; + while (i < issue.path.length) { + const el = issue.path[i]; + const terminal = i === issue.path.length - 1; + if (!terminal) { + curr[el] = curr[el] || { _errors: [] }; + } else { + curr[el] = curr[el] || { _errors: [] }; + curr[el]._errors.push(mapper(issue)); + } + curr = curr[el]; + i++; + } + } + } + }; + processError(this); + return fieldErrors; + } + toString() { + return this.message; + } + get message() { + return JSON.stringify(this.issues, util.jsonStringifyReplacer, 2); + } + get isEmpty() { + return this.issues.length === 0; + } + flatten(mapper = (issue) => issue.message) { + const fieldErrors = {}; + const formErrors = []; + for (const sub of this.issues) { + if (sub.path.length > 0) { + fieldErrors[sub.path[0]] = fieldErrors[sub.path[0]] || []; + fieldErrors[sub.path[0]].push(mapper(sub)); + } else { + formErrors.push(mapper(sub)); + } + } + return { formErrors, fieldErrors }; + } + get formErrors() { + return this.flatten(); + } +}; +ZodError.create = (issues) => { + const error = new ZodError(issues); + return error; +}; +var errorMap = (issue, _ctx) => { + let message; + switch (issue.code) { + case ZodIssueCode.invalid_type: + if (issue.received === ZodParsedType.undefined) { + message = "Required"; + } else { + message = `Expected ${issue.expected}, received ${issue.received}`; + } + break; + case ZodIssueCode.invalid_literal: + message = `Invalid literal value, expected ${JSON.stringify(issue.expected, util.jsonStringifyReplacer)}`; + break; + case ZodIssueCode.unrecognized_keys: + message = `Unrecognized key(s) in object: ${util.joinValues(issue.keys, ", ")}`; + break; + case ZodIssueCode.invalid_union: + message = `Invalid input`; + break; + case ZodIssueCode.invalid_union_discriminator: + message = `Invalid discriminator value. Expected ${util.joinValues(issue.options)}`; + break; + case ZodIssueCode.invalid_enum_value: + message = `Invalid enum value. Expected ${util.joinValues(issue.options)}, received '${issue.received}'`; + break; + case ZodIssueCode.invalid_arguments: + message = `Invalid function arguments`; + break; + case ZodIssueCode.invalid_return_type: + message = `Invalid function return type`; + break; + case ZodIssueCode.invalid_date: + message = `Invalid date`; + break; + case ZodIssueCode.invalid_string: + if (typeof issue.validation === "object") { + if ("startsWith" in issue.validation) { + message = `Invalid input: must start with "${issue.validation.startsWith}"`; + } else if ("endsWith" in issue.validation) { + message = `Invalid input: must end with "${issue.validation.endsWith}"`; + } else { + util.assertNever(issue.validation); + } + } else if (issue.validation !== "regex") { + message = `Invalid ${issue.validation}`; + } else { + message = "Invalid"; + } + break; + case ZodIssueCode.too_small: + if (issue.type === "array") + message = `Array must contain ${issue.inclusive ? `at least` : `more than`} ${issue.minimum} element(s)`; + else if (issue.type === "string") + message = `String must contain ${issue.inclusive ? `at least` : `over`} ${issue.minimum} character(s)`; + else if (issue.type === "number") + message = `Number must be greater than ${issue.inclusive ? `or equal to ` : ``}${issue.minimum}`; + else if (issue.type === "date") + message = `Date must be greater than ${issue.inclusive ? `or equal to ` : ``}${new Date(issue.minimum)}`; + else + message = "Invalid input"; + break; + case ZodIssueCode.too_big: + if (issue.type === "array") + message = `Array must contain ${issue.inclusive ? `at most` : `less than`} ${issue.maximum} element(s)`; + else if (issue.type === "string") + message = `String must contain ${issue.inclusive ? `at most` : `under`} ${issue.maximum} character(s)`; + else if (issue.type === "number") + message = `Number must be less than ${issue.inclusive ? `or equal to ` : ``}${issue.maximum}`; + else if (issue.type === "date") + message = `Date must be smaller than ${issue.inclusive ? `or equal to ` : ``}${new Date(issue.maximum)}`; + else + message = "Invalid input"; + break; + case ZodIssueCode.custom: + message = `Invalid input`; + break; + case ZodIssueCode.invalid_intersection_types: + message = `Intersection results could not be merged`; + break; + case ZodIssueCode.not_multiple_of: + message = `Number must be a multiple of ${issue.multipleOf}`; + break; + default: + message = _ctx.defaultError; + util.assertNever(issue); + } + return { message }; +}; +var overrideErrorMap = errorMap; +function setErrorMap(map) { + overrideErrorMap = map; +} +function getErrorMap() { + return overrideErrorMap; +} +var makeIssue = (params) => { + const { data, path, errorMaps, issueData } = params; + const fullPath = [...path, ...issueData.path || []]; + const fullIssue = { + ...issueData, + path: fullPath + }; + let errorMessage = ""; + const maps = errorMaps.filter((m) => !!m).slice().reverse(); + for (const map of maps) { + errorMessage = map(fullIssue, { data, defaultError: errorMessage }).message; + } + return { + ...issueData, + path: fullPath, + message: issueData.message || errorMessage + }; +}; +var EMPTY_PATH = []; +function addIssueToContext(ctx, issueData) { + const issue = makeIssue({ + issueData, + data: ctx.data, + path: ctx.path, + errorMaps: [ + ctx.common.contextualErrorMap, + ctx.schemaErrorMap, + getErrorMap(), + errorMap + ].filter((x) => !!x) + }); + ctx.common.issues.push(issue); +} +var ParseStatus = class { + constructor() { + this.value = "valid"; + } + dirty() { + if (this.value === "valid") + this.value = "dirty"; + } + abort() { + if (this.value !== "aborted") + this.value = "aborted"; + } + static mergeArray(status, results) { + const arrayValue = []; + for (const s of results) { + if (s.status === "aborted") + return INVALID; + if (s.status === "dirty") + status.dirty(); + arrayValue.push(s.value); + } + return { status: status.value, value: arrayValue }; + } + static async mergeObjectAsync(status, pairs) { + const syncPairs = []; + for (const pair of pairs) { + syncPairs.push({ + key: await pair.key, + value: await pair.value + }); + } + return ParseStatus.mergeObjectSync(status, syncPairs); + } + static mergeObjectSync(status, pairs) { + const finalObject = {}; + for (const pair of pairs) { + const { key, value } = pair; + if (key.status === "aborted") + return INVALID; + if (value.status === "aborted") + return INVALID; + if (key.status === "dirty") + status.dirty(); + if (value.status === "dirty") + status.dirty(); + if (typeof value.value !== "undefined" || pair.alwaysSet) { + finalObject[key.value] = value.value; + } + } + return { status: status.value, value: finalObject }; + } +}; +var INVALID = Object.freeze({ + status: "aborted" +}); +var DIRTY = (value) => ({ status: "dirty", value }); +var OK = (value) => ({ status: "valid", value }); +var isAborted = (x) => x.status === "aborted"; +var isDirty = (x) => x.status === "dirty"; +var isValid = (x) => x.status === "valid"; +var isAsync = (x) => typeof Promise !== void 0 && x instanceof Promise; +var errorUtil; +(function(errorUtil2) { + errorUtil2.errToObj = (message) => typeof message === "string" ? { message } : message || {}; + errorUtil2.toString = (message) => typeof message === "string" ? message : message === null || message === void 0 ? void 0 : message.message; +})(errorUtil || (errorUtil = {})); +var ParseInputLazyPath = class { + constructor(parent, value, path, key) { + this.parent = parent; + this.data = value; + this._path = path; + this._key = key; + } + get path() { + return this._path.concat(this._key); + } +}; +var handleResult = (ctx, result) => { + if (isValid(result)) { + return { success: true, data: result.value }; + } else { + if (!ctx.common.issues.length) { + throw new Error("Validation failed but no issues detected."); + } + const error = new ZodError(ctx.common.issues); + return { success: false, error }; + } +}; +function processCreateParams(params) { + if (!params) + return {}; + const { errorMap: errorMap2, invalid_type_error, required_error, description } = params; + if (errorMap2 && (invalid_type_error || required_error)) { + throw new Error(`Can't use "invalid_type_error" or "required_error" in conjunction with custom error map.`); + } + if (errorMap2) + return { errorMap: errorMap2, description }; + const customMap = (iss, ctx) => { + if (iss.code !== "invalid_type") + return { message: ctx.defaultError }; + if (typeof ctx.data === "undefined") { + return { message: required_error !== null && required_error !== void 0 ? required_error : ctx.defaultError }; + } + return { message: invalid_type_error !== null && invalid_type_error !== void 0 ? invalid_type_error : ctx.defaultError }; + }; + return { errorMap: customMap, description }; +} +var ZodType = class { + constructor(def) { + this.spa = this.safeParseAsync; + this.superRefine = this._refinement; + this._def = def; + this.parse = this.parse.bind(this); + this.safeParse = this.safeParse.bind(this); + this.parseAsync = this.parseAsync.bind(this); + this.safeParseAsync = this.safeParseAsync.bind(this); + this.spa = this.spa.bind(this); + this.refine = this.refine.bind(this); + this.refinement = this.refinement.bind(this); + this.superRefine = this.superRefine.bind(this); + this.optional = this.optional.bind(this); + this.nullable = this.nullable.bind(this); + this.nullish = this.nullish.bind(this); + this.array = this.array.bind(this); + this.promise = this.promise.bind(this); + this.or = this.or.bind(this); + this.and = this.and.bind(this); + this.transform = this.transform.bind(this); + this.default = this.default.bind(this); + this.describe = this.describe.bind(this); + this.isNullable = this.isNullable.bind(this); + this.isOptional = this.isOptional.bind(this); + } + get description() { + return this._def.description; + } + _getType(input) { + return getParsedType(input.data); + } + _getOrReturnCtx(input, ctx) { + return ctx || { + common: input.parent.common, + data: input.data, + parsedType: getParsedType(input.data), + schemaErrorMap: this._def.errorMap, + path: input.path, + parent: input.parent + }; + } + _processInputParams(input) { + return { + status: new ParseStatus(), + ctx: { + common: input.parent.common, + data: input.data, + parsedType: getParsedType(input.data), + schemaErrorMap: this._def.errorMap, + path: input.path, + parent: input.parent + } + }; + } + _parseSync(input) { + const result = this._parse(input); + if (isAsync(result)) { + throw new Error("Synchronous parse encountered promise."); + } + return result; + } + _parseAsync(input) { + const result = this._parse(input); + return Promise.resolve(result); + } + parse(data, params) { + const result = this.safeParse(data, params); + if (result.success) + return result.data; + throw result.error; + } + safeParse(data, params) { + var _a; + const ctx = { + common: { + issues: [], + async: (_a = params === null || params === void 0 ? void 0 : params.async) !== null && _a !== void 0 ? _a : false, + contextualErrorMap: params === null || params === void 0 ? void 0 : params.errorMap + }, + path: (params === null || params === void 0 ? void 0 : params.path) || [], + schemaErrorMap: this._def.errorMap, + parent: null, + data, + parsedType: getParsedType(data) + }; + const result = this._parseSync({ data, path: ctx.path, parent: ctx }); + return handleResult(ctx, result); + } + async parseAsync(data, params) { + const result = await this.safeParseAsync(data, params); + if (result.success) + return result.data; + throw result.error; + } + async safeParseAsync(data, params) { + const ctx = { + common: { + issues: [], + contextualErrorMap: params === null || params === void 0 ? void 0 : params.errorMap, + async: true + }, + path: (params === null || params === void 0 ? void 0 : params.path) || [], + schemaErrorMap: this._def.errorMap, + parent: null, + data, + parsedType: getParsedType(data) + }; + const maybeAsyncResult = this._parse({ data, path: [], parent: ctx }); + const result = await (isAsync(maybeAsyncResult) ? maybeAsyncResult : Promise.resolve(maybeAsyncResult)); + return handleResult(ctx, result); + } + refine(check, message) { + const getIssueProperties = (val) => { + if (typeof message === "string" || typeof message === "undefined") { + return { message }; + } else if (typeof message === "function") { + return message(val); + } else { + return message; + } + }; + return this._refinement((val, ctx) => { + const result = check(val); + const setError = () => ctx.addIssue({ + code: ZodIssueCode.custom, + ...getIssueProperties(val) + }); + if (typeof Promise !== "undefined" && result instanceof Promise) { + return result.then((data) => { + if (!data) { + setError(); + return false; + } else { + return true; + } + }); + } + if (!result) { + setError(); + return false; + } else { + return true; + } + }); + } + refinement(check, refinementData) { + return this._refinement((val, ctx) => { + if (!check(val)) { + ctx.addIssue(typeof refinementData === "function" ? refinementData(val, ctx) : refinementData); + return false; + } else { + return true; + } + }); + } + _refinement(refinement) { + return new ZodEffects({ + schema: this, + typeName: ZodFirstPartyTypeKind.ZodEffects, + effect: { type: "refinement", refinement } + }); + } + optional() { + return ZodOptional.create(this); + } + nullable() { + return ZodNullable.create(this); + } + nullish() { + return this.optional().nullable(); + } + array() { + return ZodArray.create(this); + } + promise() { + return ZodPromise.create(this); + } + or(option) { + return ZodUnion.create([this, option]); + } + and(incoming) { + return ZodIntersection.create(this, incoming); + } + transform(transform) { + return new ZodEffects({ + schema: this, + typeName: ZodFirstPartyTypeKind.ZodEffects, + effect: { type: "transform", transform } + }); + } + default(def) { + const defaultValueFunc = typeof def === "function" ? def : () => def; + return new ZodDefault({ + innerType: this, + defaultValue: defaultValueFunc, + typeName: ZodFirstPartyTypeKind.ZodDefault + }); + } + brand() { + return new ZodBranded({ + typeName: ZodFirstPartyTypeKind.ZodBranded, + type: this, + ...processCreateParams(void 0) + }); + } + describe(description) { + const This = this.constructor; + return new This({ + ...this._def, + description + }); + } + isOptional() { + return this.safeParse(void 0).success; + } + isNullable() { + return this.safeParse(null).success; + } +}; +var cuidRegex = /^c[^\s-]{8,}$/i; +var uuidRegex = /^([a-f0-9]{8}-[a-f0-9]{4}-[1-5][a-f0-9]{3}-[a-f0-9]{4}-[a-f0-9]{12}|00000000-0000-0000-0000-000000000000)$/i; +var emailRegex = /^(([^<>()[\]\.,;:\s@\"]+(\.[^<>()[\]\.,;:\s@\"]+)*)|(\".+\"))@(([^<>()[\]\.,;:\s@\"]+\.)+[^<>()[\]\.,;:\s@\"]{2,})$/i; +var ZodString = class extends ZodType { + constructor() { + super(...arguments); + this._regex = (regex, validation, message) => this.refinement((data) => regex.test(data), { + validation, + code: ZodIssueCode.invalid_string, + ...errorUtil.errToObj(message) + }); + this.nonempty = (message) => this.min(1, errorUtil.errToObj(message)); + this.trim = () => new ZodString({ + ...this._def, + checks: [...this._def.checks, { kind: "trim" }] + }); + } + _parse(input) { + const parsedType = this._getType(input); + if (parsedType !== ZodParsedType.string) { + const ctx2 = this._getOrReturnCtx(input); + addIssueToContext( + ctx2, + { + code: ZodIssueCode.invalid_type, + expected: ZodParsedType.string, + received: ctx2.parsedType + } + ); + return INVALID; + } + const status = new ParseStatus(); + let ctx = void 0; + for (const check of this._def.checks) { + if (check.kind === "min") { + if (input.data.length < check.value) { + ctx = this._getOrReturnCtx(input, ctx); + addIssueToContext(ctx, { + code: ZodIssueCode.too_small, + minimum: check.value, + type: "string", + inclusive: true, + message: check.message + }); + status.dirty(); + } + } else if (check.kind === "max") { + if (input.data.length > check.value) { + ctx = this._getOrReturnCtx(input, ctx); + addIssueToContext(ctx, { + code: ZodIssueCode.too_big, + maximum: check.value, + type: "string", + inclusive: true, + message: check.message + }); + status.dirty(); + } + } else if (check.kind === "email") { + if (!emailRegex.test(input.data)) { + ctx = this._getOrReturnCtx(input, ctx); + addIssueToContext(ctx, { + validation: "email", + code: ZodIssueCode.invalid_string, + message: check.message + }); + status.dirty(); + } + } else if (check.kind === "uuid") { + if (!uuidRegex.test(input.data)) { + ctx = this._getOrReturnCtx(input, ctx); + addIssueToContext(ctx, { + validation: "uuid", + code: ZodIssueCode.invalid_string, + message: check.message + }); + status.dirty(); + } + } else if (check.kind === "cuid") { + if (!cuidRegex.test(input.data)) { + ctx = this._getOrReturnCtx(input, ctx); + addIssueToContext(ctx, { + validation: "cuid", + code: ZodIssueCode.invalid_string, + message: check.message + }); + status.dirty(); + } + } else if (check.kind === "url") { + try { + new URL(input.data); + } catch (_a) { + ctx = this._getOrReturnCtx(input, ctx); + addIssueToContext(ctx, { + validation: "url", + code: ZodIssueCode.invalid_string, + message: check.message + }); + status.dirty(); + } + } else if (check.kind === "regex") { + check.regex.lastIndex = 0; + const testResult = check.regex.test(input.data); + if (!testResult) { + ctx = this._getOrReturnCtx(input, ctx); + addIssueToContext(ctx, { + validation: "regex", + code: ZodIssueCode.invalid_string, + message: check.message + }); + status.dirty(); + } + } else if (check.kind === "trim") { + input.data = input.data.trim(); + } else if (check.kind === "startsWith") { + if (!input.data.startsWith(check.value)) { + ctx = this._getOrReturnCtx(input, ctx); + addIssueToContext(ctx, { + code: ZodIssueCode.invalid_string, + validation: { startsWith: check.value }, + message: check.message + }); + status.dirty(); + } + } else if (check.kind === "endsWith") { + if (!input.data.endsWith(check.value)) { + ctx = this._getOrReturnCtx(input, ctx); + addIssueToContext(ctx, { + code: ZodIssueCode.invalid_string, + validation: { endsWith: check.value }, + message: check.message + }); + status.dirty(); + } + } else { + util.assertNever(check); + } + } + return { status: status.value, value: input.data }; + } + _addCheck(check) { + return new ZodString({ + ...this._def, + checks: [...this._def.checks, check] + }); + } + email(message) { + return this._addCheck({ kind: "email", ...errorUtil.errToObj(message) }); + } + url(message) { + return this._addCheck({ kind: "url", ...errorUtil.errToObj(message) }); + } + uuid(message) { + return this._addCheck({ kind: "uuid", ...errorUtil.errToObj(message) }); + } + cuid(message) { + return this._addCheck({ kind: "cuid", ...errorUtil.errToObj(message) }); + } + regex(regex, message) { + return this._addCheck({ + kind: "regex", + regex, + ...errorUtil.errToObj(message) + }); + } + startsWith(value, message) { + return this._addCheck({ + kind: "startsWith", + value, + ...errorUtil.errToObj(message) + }); + } + endsWith(value, message) { + return this._addCheck({ + kind: "endsWith", + value, + ...errorUtil.errToObj(message) + }); + } + min(minLength, message) { + return this._addCheck({ + kind: "min", + value: minLength, + ...errorUtil.errToObj(message) + }); + } + max(maxLength, message) { + return this._addCheck({ + kind: "max", + value: maxLength, + ...errorUtil.errToObj(message) + }); + } + length(len, message) { + return this.min(len, message).max(len, message); + } + get isEmail() { + return !!this._def.checks.find((ch) => ch.kind === "email"); + } + get isURL() { + return !!this._def.checks.find((ch) => ch.kind === "url"); + } + get isUUID() { + return !!this._def.checks.find((ch) => ch.kind === "uuid"); + } + get isCUID() { + return !!this._def.checks.find((ch) => ch.kind === "cuid"); + } + get minLength() { + let min = null; + for (const ch of this._def.checks) { + if (ch.kind === "min") { + if (min === null || ch.value > min) + min = ch.value; + } + } + return min; + } + get maxLength() { + let max = null; + for (const ch of this._def.checks) { + if (ch.kind === "max") { + if (max === null || ch.value < max) + max = ch.value; + } + } + return max; + } +}; +ZodString.create = (params) => { + return new ZodString({ + checks: [], + typeName: ZodFirstPartyTypeKind.ZodString, + ...processCreateParams(params) + }); +}; +function floatSafeRemainder(val, step) { + const valDecCount = (val.toString().split(".")[1] || "").length; + const stepDecCount = (step.toString().split(".")[1] || "").length; + const decCount = valDecCount > stepDecCount ? valDecCount : stepDecCount; + const valInt = parseInt(val.toFixed(decCount).replace(".", "")); + const stepInt = parseInt(step.toFixed(decCount).replace(".", "")); + return valInt % stepInt / Math.pow(10, decCount); +} +var ZodNumber = class extends ZodType { + constructor() { + super(...arguments); + this.min = this.gte; + this.max = this.lte; + this.step = this.multipleOf; + } + _parse(input) { + const parsedType = this._getType(input); + if (parsedType !== ZodParsedType.number) { + const ctx2 = this._getOrReturnCtx(input); + addIssueToContext(ctx2, { + code: ZodIssueCode.invalid_type, + expected: ZodParsedType.number, + received: ctx2.parsedType + }); + return INVALID; + } + let ctx = void 0; + const status = new ParseStatus(); + for (const check of this._def.checks) { + if (check.kind === "int") { + if (!util.isInteger(input.data)) { + ctx = this._getOrReturnCtx(input, ctx); + addIssueToContext(ctx, { + code: ZodIssueCode.invalid_type, + expected: "integer", + received: "float", + message: check.message + }); + status.dirty(); + } + } else if (check.kind === "min") { + const tooSmall = check.inclusive ? input.data < check.value : input.data <= check.value; + if (tooSmall) { + ctx = this._getOrReturnCtx(input, ctx); + addIssueToContext(ctx, { + code: ZodIssueCode.too_small, + minimum: check.value, + type: "number", + inclusive: check.inclusive, + message: check.message + }); + status.dirty(); + } + } else if (check.kind === "max") { + const tooBig = check.inclusive ? input.data > check.value : input.data >= check.value; + if (tooBig) { + ctx = this._getOrReturnCtx(input, ctx); + addIssueToContext(ctx, { + code: ZodIssueCode.too_big, + maximum: check.value, + type: "number", + inclusive: check.inclusive, + message: check.message + }); + status.dirty(); + } + } else if (check.kind === "multipleOf") { + if (floatSafeRemainder(input.data, check.value) !== 0) { + ctx = this._getOrReturnCtx(input, ctx); + addIssueToContext(ctx, { + code: ZodIssueCode.not_multiple_of, + multipleOf: check.value, + message: check.message + }); + status.dirty(); + } + } else { + util.assertNever(check); + } + } + return { status: status.value, value: input.data }; + } + gte(value, message) { + return this.setLimit("min", value, true, errorUtil.toString(message)); + } + gt(value, message) { + return this.setLimit("min", value, false, errorUtil.toString(message)); + } + lte(value, message) { + return this.setLimit("max", value, true, errorUtil.toString(message)); + } + lt(value, message) { + return this.setLimit("max", value, false, errorUtil.toString(message)); + } + setLimit(kind, value, inclusive, message) { + return new ZodNumber({ + ...this._def, + checks: [ + ...this._def.checks, + { + kind, + value, + inclusive, + message: errorUtil.toString(message) + } + ] + }); + } + _addCheck(check) { + return new ZodNumber({ + ...this._def, + checks: [...this._def.checks, check] + }); + } + int(message) { + return this._addCheck({ + kind: "int", + message: errorUtil.toString(message) + }); + } + positive(message) { + return this._addCheck({ + kind: "min", + value: 0, + inclusive: false, + message: errorUtil.toString(message) + }); + } + negative(message) { + return this._addCheck({ + kind: "max", + value: 0, + inclusive: false, + message: errorUtil.toString(message) + }); + } + nonpositive(message) { + return this._addCheck({ + kind: "max", + value: 0, + inclusive: true, + message: errorUtil.toString(message) + }); + } + nonnegative(message) { + return this._addCheck({ + kind: "min", + value: 0, + inclusive: true, + message: errorUtil.toString(message) + }); + } + multipleOf(value, message) { + return this._addCheck({ + kind: "multipleOf", + value, + message: errorUtil.toString(message) + }); + } + get minValue() { + let min = null; + for (const ch of this._def.checks) { + if (ch.kind === "min") { + if (min === null || ch.value > min) + min = ch.value; + } + } + return min; + } + get maxValue() { + let max = null; + for (const ch of this._def.checks) { + if (ch.kind === "max") { + if (max === null || ch.value < max) + max = ch.value; + } + } + return max; + } + get isInt() { + return !!this._def.checks.find((ch) => ch.kind === "int"); + } +}; +ZodNumber.create = (params) => { + return new ZodNumber({ + checks: [], + typeName: ZodFirstPartyTypeKind.ZodNumber, + ...processCreateParams(params) + }); +}; +var ZodBigInt = class extends ZodType { + _parse(input) { + const parsedType = this._getType(input); + if (parsedType !== ZodParsedType.bigint) { + const ctx = this._getOrReturnCtx(input); + addIssueToContext(ctx, { + code: ZodIssueCode.invalid_type, + expected: ZodParsedType.bigint, + received: ctx.parsedType + }); + return INVALID; + } + return OK(input.data); + } +}; +ZodBigInt.create = (params) => { + return new ZodBigInt({ + typeName: ZodFirstPartyTypeKind.ZodBigInt, + ...processCreateParams(params) + }); +}; +var ZodBoolean = class extends ZodType { + _parse(input) { + const parsedType = this._getType(input); + if (parsedType !== ZodParsedType.boolean) { + const ctx = this._getOrReturnCtx(input); + addIssueToContext(ctx, { + code: ZodIssueCode.invalid_type, + expected: ZodParsedType.boolean, + received: ctx.parsedType + }); + return INVALID; + } + return OK(input.data); + } +}; +ZodBoolean.create = (params) => { + return new ZodBoolean({ + typeName: ZodFirstPartyTypeKind.ZodBoolean, + ...processCreateParams(params) + }); +}; +var ZodDate = class extends ZodType { + _parse(input) { + const parsedType = this._getType(input); + if (parsedType !== ZodParsedType.date) { + const ctx2 = this._getOrReturnCtx(input); + addIssueToContext(ctx2, { + code: ZodIssueCode.invalid_type, + expected: ZodParsedType.date, + received: ctx2.parsedType + }); + return INVALID; + } + if (isNaN(input.data.getTime())) { + const ctx2 = this._getOrReturnCtx(input); + addIssueToContext(ctx2, { + code: ZodIssueCode.invalid_date + }); + return INVALID; + } + const status = new ParseStatus(); + let ctx = void 0; + for (const check of this._def.checks) { + if (check.kind === "min") { + if (input.data.getTime() < check.value) { + ctx = this._getOrReturnCtx(input, ctx); + addIssueToContext(ctx, { + code: ZodIssueCode.too_small, + message: check.message, + inclusive: true, + minimum: check.value, + type: "date" + }); + status.dirty(); + } + } else if (check.kind === "max") { + if (input.data.getTime() > check.value) { + ctx = this._getOrReturnCtx(input, ctx); + addIssueToContext(ctx, { + code: ZodIssueCode.too_big, + message: check.message, + inclusive: true, + maximum: check.value, + type: "date" + }); + status.dirty(); + } + } else { + util.assertNever(check); + } + } + return { + status: status.value, + value: new Date(input.data.getTime()) + }; + } + _addCheck(check) { + return new ZodDate({ + ...this._def, + checks: [...this._def.checks, check] + }); + } + min(minDate, message) { + return this._addCheck({ + kind: "min", + value: minDate.getTime(), + message: errorUtil.toString(message) + }); + } + max(maxDate, message) { + return this._addCheck({ + kind: "max", + value: maxDate.getTime(), + message: errorUtil.toString(message) + }); + } + get minDate() { + let min = null; + for (const ch of this._def.checks) { + if (ch.kind === "min") { + if (min === null || ch.value > min) + min = ch.value; + } + } + return min != null ? new Date(min) : null; + } + get maxDate() { + let max = null; + for (const ch of this._def.checks) { + if (ch.kind === "max") { + if (max === null || ch.value < max) + max = ch.value; + } + } + return max != null ? new Date(max) : null; + } +}; +ZodDate.create = (params) => { + return new ZodDate({ + checks: [], + typeName: ZodFirstPartyTypeKind.ZodDate, + ...processCreateParams(params) + }); +}; +var ZodUndefined = class extends ZodType { + _parse(input) { + const parsedType = this._getType(input); + if (parsedType !== ZodParsedType.undefined) { + const ctx = this._getOrReturnCtx(input); + addIssueToContext(ctx, { + code: ZodIssueCode.invalid_type, + expected: ZodParsedType.undefined, + received: ctx.parsedType + }); + return INVALID; + } + return OK(input.data); + } +}; +ZodUndefined.create = (params) => { + return new ZodUndefined({ + typeName: ZodFirstPartyTypeKind.ZodUndefined, + ...processCreateParams(params) + }); +}; +var ZodNull = class extends ZodType { + _parse(input) { + const parsedType = this._getType(input); + if (parsedType !== ZodParsedType.null) { + const ctx = this._getOrReturnCtx(input); + addIssueToContext(ctx, { + code: ZodIssueCode.invalid_type, + expected: ZodParsedType.null, + received: ctx.parsedType + }); + return INVALID; + } + return OK(input.data); + } +}; +ZodNull.create = (params) => { + return new ZodNull({ + typeName: ZodFirstPartyTypeKind.ZodNull, + ...processCreateParams(params) + }); +}; +var ZodAny = class extends ZodType { + constructor() { + super(...arguments); + this._any = true; + } + _parse(input) { + return OK(input.data); + } +}; +ZodAny.create = (params) => { + return new ZodAny({ + typeName: ZodFirstPartyTypeKind.ZodAny, + ...processCreateParams(params) + }); +}; +var ZodUnknown = class extends ZodType { + constructor() { + super(...arguments); + this._unknown = true; + } + _parse(input) { + return OK(input.data); + } +}; +ZodUnknown.create = (params) => { + return new ZodUnknown({ + typeName: ZodFirstPartyTypeKind.ZodUnknown, + ...processCreateParams(params) + }); +}; +var ZodNever = class extends ZodType { + _parse(input) { + const ctx = this._getOrReturnCtx(input); + addIssueToContext(ctx, { + code: ZodIssueCode.invalid_type, + expected: ZodParsedType.never, + received: ctx.parsedType + }); + return INVALID; + } +}; +ZodNever.create = (params) => { + return new ZodNever({ + typeName: ZodFirstPartyTypeKind.ZodNever, + ...processCreateParams(params) + }); +}; +var ZodVoid = class extends ZodType { + _parse(input) { + const parsedType = this._getType(input); + if (parsedType !== ZodParsedType.undefined) { + const ctx = this._getOrReturnCtx(input); + addIssueToContext(ctx, { + code: ZodIssueCode.invalid_type, + expected: ZodParsedType.void, + received: ctx.parsedType + }); + return INVALID; + } + return OK(input.data); + } +}; +ZodVoid.create = (params) => { + return new ZodVoid({ + typeName: ZodFirstPartyTypeKind.ZodVoid, + ...processCreateParams(params) + }); +}; +var ZodArray = class extends ZodType { + _parse(input) { + const { ctx, status } = this._processInputParams(input); + const def = this._def; + if (ctx.parsedType !== ZodParsedType.array) { + addIssueToContext(ctx, { + code: ZodIssueCode.invalid_type, + expected: ZodParsedType.array, + received: ctx.parsedType + }); + return INVALID; + } + if (def.minLength !== null) { + if (ctx.data.length < def.minLength.value) { + addIssueToContext(ctx, { + code: ZodIssueCode.too_small, + minimum: def.minLength.value, + type: "array", + inclusive: true, + message: def.minLength.message + }); + status.dirty(); + } + } + if (def.maxLength !== null) { + if (ctx.data.length > def.maxLength.value) { + addIssueToContext(ctx, { + code: ZodIssueCode.too_big, + maximum: def.maxLength.value, + type: "array", + inclusive: true, + message: def.maxLength.message + }); + status.dirty(); + } + } + if (ctx.common.async) { + return Promise.all(ctx.data.map((item, i) => { + return def.type._parseAsync(new ParseInputLazyPath(ctx, item, ctx.path, i)); + })).then((result2) => { + return ParseStatus.mergeArray(status, result2); + }); + } + const result = ctx.data.map((item, i) => { + return def.type._parseSync(new ParseInputLazyPath(ctx, item, ctx.path, i)); + }); + return ParseStatus.mergeArray(status, result); + } + get element() { + return this._def.type; + } + min(minLength, message) { + return new ZodArray({ + ...this._def, + minLength: { value: minLength, message: errorUtil.toString(message) } + }); + } + max(maxLength, message) { + return new ZodArray({ + ...this._def, + maxLength: { value: maxLength, message: errorUtil.toString(message) } + }); + } + length(len, message) { + return this.min(len, message).max(len, message); + } + nonempty(message) { + return this.min(1, message); + } +}; +ZodArray.create = (schema, params) => { + return new ZodArray({ + type: schema, + minLength: null, + maxLength: null, + typeName: ZodFirstPartyTypeKind.ZodArray, + ...processCreateParams(params) + }); +}; +var objectUtil; +(function(objectUtil2) { + objectUtil2.mergeShapes = (first, second) => { + return { + ...first, + ...second + }; + }; +})(objectUtil || (objectUtil = {})); +var AugmentFactory = (def) => (augmentation) => { + return new ZodObject({ + ...def, + shape: () => ({ + ...def.shape(), + ...augmentation + }) + }); +}; +function deepPartialify(schema) { + if (schema instanceof ZodObject) { + const newShape = {}; + for (const key in schema.shape) { + const fieldSchema = schema.shape[key]; + newShape[key] = ZodOptional.create(deepPartialify(fieldSchema)); + } + return new ZodObject({ + ...schema._def, + shape: () => newShape + }); + } else if (schema instanceof ZodArray) { + return ZodArray.create(deepPartialify(schema.element)); + } else if (schema instanceof ZodOptional) { + return ZodOptional.create(deepPartialify(schema.unwrap())); + } else if (schema instanceof ZodNullable) { + return ZodNullable.create(deepPartialify(schema.unwrap())); + } else if (schema instanceof ZodTuple) { + return ZodTuple.create(schema.items.map((item) => deepPartialify(item))); + } else { + return schema; + } +} +var ZodObject = class extends ZodType { + constructor() { + super(...arguments); + this._cached = null; + this.nonstrict = this.passthrough; + this.augment = AugmentFactory(this._def); + this.extend = AugmentFactory(this._def); + } + _getCached() { + if (this._cached !== null) + return this._cached; + const shape = this._def.shape(); + const keys = util.objectKeys(shape); + return this._cached = { shape, keys }; + } + _parse(input) { + const parsedType = this._getType(input); + if (parsedType !== ZodParsedType.object) { + const ctx2 = this._getOrReturnCtx(input); + addIssueToContext(ctx2, { + code: ZodIssueCode.invalid_type, + expected: ZodParsedType.object, + received: ctx2.parsedType + }); + return INVALID; + } + const { status, ctx } = this._processInputParams(input); + const { shape, keys: shapeKeys } = this._getCached(); + const extraKeys = []; + if (!(this._def.catchall instanceof ZodNever && this._def.unknownKeys === "strip")) { + for (const key in ctx.data) { + if (!shapeKeys.includes(key)) { + extraKeys.push(key); + } + } + } + const pairs = []; + for (const key of shapeKeys) { + const keyValidator = shape[key]; + const value = ctx.data[key]; + pairs.push({ + key: { status: "valid", value: key }, + value: keyValidator._parse(new ParseInputLazyPath(ctx, value, ctx.path, key)), + alwaysSet: key in ctx.data + }); + } + if (this._def.catchall instanceof ZodNever) { + const unknownKeys = this._def.unknownKeys; + if (unknownKeys === "passthrough") { + for (const key of extraKeys) { + pairs.push({ + key: { status: "valid", value: key }, + value: { status: "valid", value: ctx.data[key] } + }); + } + } else if (unknownKeys === "strict") { + if (extraKeys.length > 0) { + addIssueToContext(ctx, { + code: ZodIssueCode.unrecognized_keys, + keys: extraKeys + }); + status.dirty(); + } + } else if (unknownKeys === "strip") + ; + else { + throw new Error(`Internal ZodObject error: invalid unknownKeys value.`); + } + } else { + const catchall = this._def.catchall; + for (const key of extraKeys) { + const value = ctx.data[key]; + pairs.push({ + key: { status: "valid", value: key }, + value: catchall._parse( + new ParseInputLazyPath(ctx, value, ctx.path, key) + ), + alwaysSet: key in ctx.data + }); + } + } + if (ctx.common.async) { + return Promise.resolve().then(async () => { + const syncPairs = []; + for (const pair of pairs) { + const key = await pair.key; + syncPairs.push({ + key, + value: await pair.value, + alwaysSet: pair.alwaysSet + }); + } + return syncPairs; + }).then((syncPairs) => { + return ParseStatus.mergeObjectSync(status, syncPairs); + }); + } else { + return ParseStatus.mergeObjectSync(status, pairs); + } + } + get shape() { + return this._def.shape(); + } + strict(message) { + errorUtil.errToObj; + return new ZodObject({ + ...this._def, + unknownKeys: "strict", + ...message !== void 0 ? { + errorMap: (issue, ctx) => { + var _a, _b, _c, _d; + const defaultError = (_c = (_b = (_a = this._def).errorMap) === null || _b === void 0 ? void 0 : _b.call(_a, issue, ctx).message) !== null && _c !== void 0 ? _c : ctx.defaultError; + if (issue.code === "unrecognized_keys") + return { + message: (_d = errorUtil.errToObj(message).message) !== null && _d !== void 0 ? _d : defaultError + }; + return { + message: defaultError + }; + } + } : {} + }); + } + strip() { + return new ZodObject({ + ...this._def, + unknownKeys: "strip" + }); + } + passthrough() { + return new ZodObject({ + ...this._def, + unknownKeys: "passthrough" + }); + } + setKey(key, schema) { + return this.augment({ [key]: schema }); + } + merge(merging) { + const merged = new ZodObject({ + unknownKeys: merging._def.unknownKeys, + catchall: merging._def.catchall, + shape: () => objectUtil.mergeShapes(this._def.shape(), merging._def.shape()), + typeName: ZodFirstPartyTypeKind.ZodObject + }); + return merged; + } + catchall(index) { + return new ZodObject({ + ...this._def, + catchall: index + }); + } + pick(mask) { + const shape = {}; + util.objectKeys(mask).map((key) => { + if (this.shape[key]) + shape[key] = this.shape[key]; + }); + return new ZodObject({ + ...this._def, + shape: () => shape + }); + } + omit(mask) { + const shape = {}; + util.objectKeys(this.shape).map((key) => { + if (util.objectKeys(mask).indexOf(key) === -1) { + shape[key] = this.shape[key]; + } + }); + return new ZodObject({ + ...this._def, + shape: () => shape + }); + } + deepPartial() { + return deepPartialify(this); + } + partial(mask) { + const newShape = {}; + if (mask) { + util.objectKeys(this.shape).map((key) => { + if (util.objectKeys(mask).indexOf(key) === -1) { + newShape[key] = this.shape[key]; + } else { + newShape[key] = this.shape[key].optional(); + } + }); + return new ZodObject({ + ...this._def, + shape: () => newShape + }); + } else { + for (const key in this.shape) { + const fieldSchema = this.shape[key]; + newShape[key] = fieldSchema.optional(); + } + } + return new ZodObject({ + ...this._def, + shape: () => newShape + }); + } + required() { + const newShape = {}; + for (const key in this.shape) { + const fieldSchema = this.shape[key]; + let newField = fieldSchema; + while (newField instanceof ZodOptional) { + newField = newField._def.innerType; + } + newShape[key] = newField; + } + return new ZodObject({ + ...this._def, + shape: () => newShape + }); + } + keyof() { + return createZodEnum(util.objectKeys(this.shape)); + } +}; +ZodObject.create = (shape, params) => { + return new ZodObject({ + shape: () => shape, + unknownKeys: "strip", + catchall: ZodNever.create(), + typeName: ZodFirstPartyTypeKind.ZodObject, + ...processCreateParams(params) + }); +}; +ZodObject.strictCreate = (shape, params) => { + return new ZodObject({ + shape: () => shape, + unknownKeys: "strict", + catchall: ZodNever.create(), + typeName: ZodFirstPartyTypeKind.ZodObject, + ...processCreateParams(params) + }); +}; +ZodObject.lazycreate = (shape, params) => { + return new ZodObject({ + shape, + unknownKeys: "strip", + catchall: ZodNever.create(), + typeName: ZodFirstPartyTypeKind.ZodObject, + ...processCreateParams(params) + }); +}; +var ZodUnion = class extends ZodType { + _parse(input) { + const { ctx } = this._processInputParams(input); + const options = this._def.options; + function handleResults(results) { + for (const result of results) { + if (result.result.status === "valid") { + return result.result; + } + } + for (const result of results) { + if (result.result.status === "dirty") { + ctx.common.issues.push(...result.ctx.common.issues); + return result.result; + } + } + const unionErrors = results.map((result) => new ZodError(result.ctx.common.issues)); + addIssueToContext(ctx, { + code: ZodIssueCode.invalid_union, + unionErrors + }); + return INVALID; + } + if (ctx.common.async) { + return Promise.all(options.map(async (option) => { + const childCtx = { + ...ctx, + common: { + ...ctx.common, + issues: [] + }, + parent: null + }; + return { + result: await option._parseAsync({ + data: ctx.data, + path: ctx.path, + parent: childCtx + }), + ctx: childCtx + }; + })).then(handleResults); + } else { + let dirty = void 0; + const issues = []; + for (const option of options) { + const childCtx = { + ...ctx, + common: { + ...ctx.common, + issues: [] + }, + parent: null + }; + const result = option._parseSync({ + data: ctx.data, + path: ctx.path, + parent: childCtx + }); + if (result.status === "valid") { + return result; + } else if (result.status === "dirty" && !dirty) { + dirty = { result, ctx: childCtx }; + } + if (childCtx.common.issues.length) { + issues.push(childCtx.common.issues); + } + } + if (dirty) { + ctx.common.issues.push(...dirty.ctx.common.issues); + return dirty.result; + } + const unionErrors = issues.map((issues2) => new ZodError(issues2)); + addIssueToContext(ctx, { + code: ZodIssueCode.invalid_union, + unionErrors + }); + return INVALID; + } + } + get options() { + return this._def.options; + } +}; +ZodUnion.create = (types, params) => { + return new ZodUnion({ + options: types, + typeName: ZodFirstPartyTypeKind.ZodUnion, + ...processCreateParams(params) + }); +}; +var ZodDiscriminatedUnion = class extends ZodType { + _parse(input) { + const { ctx } = this._processInputParams(input); + if (ctx.parsedType !== ZodParsedType.object) { + addIssueToContext(ctx, { + code: ZodIssueCode.invalid_type, + expected: ZodParsedType.object, + received: ctx.parsedType + }); + return INVALID; + } + const discriminator = this.discriminator; + const discriminatorValue = ctx.data[discriminator]; + const option = this.options.get(discriminatorValue); + if (!option) { + addIssueToContext(ctx, { + code: ZodIssueCode.invalid_union_discriminator, + options: this.validDiscriminatorValues, + path: [discriminator] + }); + return INVALID; + } + if (ctx.common.async) { + return option._parseAsync({ + data: ctx.data, + path: ctx.path, + parent: ctx + }); + } else { + return option._parseSync({ + data: ctx.data, + path: ctx.path, + parent: ctx + }); + } + } + get discriminator() { + return this._def.discriminator; + } + get validDiscriminatorValues() { + return Array.from(this.options.keys()); + } + get options() { + return this._def.options; + } + static create(discriminator, types, params) { + const options = /* @__PURE__ */ new Map(); + try { + types.forEach((type) => { + const discriminatorValue = type.shape[discriminator].value; + options.set(discriminatorValue, type); + }); + } catch (e) { + throw new Error("The discriminator value could not be extracted from all the provided schemas"); + } + if (options.size !== types.length) { + throw new Error("Some of the discriminator values are not unique"); + } + return new ZodDiscriminatedUnion({ + typeName: ZodFirstPartyTypeKind.ZodDiscriminatedUnion, + discriminator, + options, + ...processCreateParams(params) + }); + } +}; +function mergeValues(a, b) { + const aType = getParsedType(a); + const bType = getParsedType(b); + if (a === b) { + return { valid: true, data: a }; + } else if (aType === ZodParsedType.object && bType === ZodParsedType.object) { + const bKeys = util.objectKeys(b); + const sharedKeys = util.objectKeys(a).filter((key) => bKeys.indexOf(key) !== -1); + const newObj = { ...a, ...b }; + for (const key of sharedKeys) { + const sharedValue = mergeValues(a[key], b[key]); + if (!sharedValue.valid) { + return { valid: false }; + } + newObj[key] = sharedValue.data; + } + return { valid: true, data: newObj }; + } else if (aType === ZodParsedType.array && bType === ZodParsedType.array) { + if (a.length !== b.length) { + return { valid: false }; + } + const newArray = []; + for (let index = 0; index < a.length; index++) { + const itemA = a[index]; + const itemB = b[index]; + const sharedValue = mergeValues(itemA, itemB); + if (!sharedValue.valid) { + return { valid: false }; + } + newArray.push(sharedValue.data); + } + return { valid: true, data: newArray }; + } else if (aType === ZodParsedType.date && bType === ZodParsedType.date && +a === +b) { + return { valid: true, data: a }; + } else { + return { valid: false }; + } +} +var ZodIntersection = class extends ZodType { + _parse(input) { + const { status, ctx } = this._processInputParams(input); + const handleParsed = (parsedLeft, parsedRight) => { + if (isAborted(parsedLeft) || isAborted(parsedRight)) { + return INVALID; + } + const merged = mergeValues(parsedLeft.value, parsedRight.value); + if (!merged.valid) { + addIssueToContext(ctx, { + code: ZodIssueCode.invalid_intersection_types + }); + return INVALID; + } + if (isDirty(parsedLeft) || isDirty(parsedRight)) { + status.dirty(); + } + return { status: status.value, value: merged.data }; + }; + if (ctx.common.async) { + return Promise.all([ + this._def.left._parseAsync({ + data: ctx.data, + path: ctx.path, + parent: ctx + }), + this._def.right._parseAsync({ + data: ctx.data, + path: ctx.path, + parent: ctx + }) + ]).then(([left, right]) => handleParsed(left, right)); + } else { + return handleParsed(this._def.left._parseSync({ + data: ctx.data, + path: ctx.path, + parent: ctx + }), this._def.right._parseSync({ + data: ctx.data, + path: ctx.path, + parent: ctx + })); + } + } +}; +ZodIntersection.create = (left, right, params) => { + return new ZodIntersection({ + left, + right, + typeName: ZodFirstPartyTypeKind.ZodIntersection, + ...processCreateParams(params) + }); +}; +var ZodTuple = class extends ZodType { + _parse(input) { + const { status, ctx } = this._processInputParams(input); + if (ctx.parsedType !== ZodParsedType.array) { + addIssueToContext(ctx, { + code: ZodIssueCode.invalid_type, + expected: ZodParsedType.array, + received: ctx.parsedType + }); + return INVALID; + } + if (ctx.data.length < this._def.items.length) { + addIssueToContext(ctx, { + code: ZodIssueCode.too_small, + minimum: this._def.items.length, + inclusive: true, + type: "array" + }); + return INVALID; + } + const rest = this._def.rest; + if (!rest && ctx.data.length > this._def.items.length) { + addIssueToContext(ctx, { + code: ZodIssueCode.too_big, + maximum: this._def.items.length, + inclusive: true, + type: "array" + }); + status.dirty(); + } + const items = ctx.data.map((item, itemIndex) => { + const schema = this._def.items[itemIndex] || this._def.rest; + if (!schema) + return null; + return schema._parse(new ParseInputLazyPath(ctx, item, ctx.path, itemIndex)); + }).filter((x) => !!x); + if (ctx.common.async) { + return Promise.all(items).then((results) => { + return ParseStatus.mergeArray(status, results); + }); + } else { + return ParseStatus.mergeArray(status, items); + } + } + get items() { + return this._def.items; + } + rest(rest) { + return new ZodTuple({ + ...this._def, + rest + }); + } +}; +ZodTuple.create = (schemas, params) => { + if (!Array.isArray(schemas)) { + throw new Error("You must pass an array of schemas to z.tuple([ ... ])"); + } + return new ZodTuple({ + items: schemas, + typeName: ZodFirstPartyTypeKind.ZodTuple, + rest: null, + ...processCreateParams(params) + }); +}; +var ZodRecord = class extends ZodType { + get keySchema() { + return this._def.keyType; + } + get valueSchema() { + return this._def.valueType; + } + _parse(input) { + const { status, ctx } = this._processInputParams(input); + if (ctx.parsedType !== ZodParsedType.object) { + addIssueToContext(ctx, { + code: ZodIssueCode.invalid_type, + expected: ZodParsedType.object, + received: ctx.parsedType + }); + return INVALID; + } + const pairs = []; + const keyType = this._def.keyType; + const valueType = this._def.valueType; + for (const key in ctx.data) { + pairs.push({ + key: keyType._parse(new ParseInputLazyPath(ctx, key, ctx.path, key)), + value: valueType._parse(new ParseInputLazyPath(ctx, ctx.data[key], ctx.path, key)) + }); + } + if (ctx.common.async) { + return ParseStatus.mergeObjectAsync(status, pairs); + } else { + return ParseStatus.mergeObjectSync(status, pairs); + } + } + get element() { + return this._def.valueType; + } + static create(first, second, third) { + if (second instanceof ZodType) { + return new ZodRecord({ + keyType: first, + valueType: second, + typeName: ZodFirstPartyTypeKind.ZodRecord, + ...processCreateParams(third) + }); + } + return new ZodRecord({ + keyType: ZodString.create(), + valueType: first, + typeName: ZodFirstPartyTypeKind.ZodRecord, + ...processCreateParams(second) + }); + } +}; +var ZodMap = class extends ZodType { + _parse(input) { + const { status, ctx } = this._processInputParams(input); + if (ctx.parsedType !== ZodParsedType.map) { + addIssueToContext(ctx, { + code: ZodIssueCode.invalid_type, + expected: ZodParsedType.map, + received: ctx.parsedType + }); + return INVALID; + } + const keyType = this._def.keyType; + const valueType = this._def.valueType; + const pairs = [...ctx.data.entries()].map(([key, value], index) => { + return { + key: keyType._parse(new ParseInputLazyPath(ctx, key, ctx.path, [index, "key"])), + value: valueType._parse(new ParseInputLazyPath(ctx, value, ctx.path, [index, "value"])) + }; + }); + if (ctx.common.async) { + const finalMap = /* @__PURE__ */ new Map(); + return Promise.resolve().then(async () => { + for (const pair of pairs) { + const key = await pair.key; + const value = await pair.value; + if (key.status === "aborted" || value.status === "aborted") { + return INVALID; + } + if (key.status === "dirty" || value.status === "dirty") { + status.dirty(); + } + finalMap.set(key.value, value.value); + } + return { status: status.value, value: finalMap }; + }); + } else { + const finalMap = /* @__PURE__ */ new Map(); + for (const pair of pairs) { + const key = pair.key; + const value = pair.value; + if (key.status === "aborted" || value.status === "aborted") { + return INVALID; + } + if (key.status === "dirty" || value.status === "dirty") { + status.dirty(); + } + finalMap.set(key.value, value.value); + } + return { status: status.value, value: finalMap }; + } + } +}; +ZodMap.create = (keyType, valueType, params) => { + return new ZodMap({ + valueType, + keyType, + typeName: ZodFirstPartyTypeKind.ZodMap, + ...processCreateParams(params) + }); +}; +var ZodSet = class extends ZodType { + _parse(input) { + const { status, ctx } = this._processInputParams(input); + if (ctx.parsedType !== ZodParsedType.set) { + addIssueToContext(ctx, { + code: ZodIssueCode.invalid_type, + expected: ZodParsedType.set, + received: ctx.parsedType + }); + return INVALID; + } + const def = this._def; + if (def.minSize !== null) { + if (ctx.data.size < def.minSize.value) { + addIssueToContext(ctx, { + code: ZodIssueCode.too_small, + minimum: def.minSize.value, + type: "set", + inclusive: true, + message: def.minSize.message + }); + status.dirty(); + } + } + if (def.maxSize !== null) { + if (ctx.data.size > def.maxSize.value) { + addIssueToContext(ctx, { + code: ZodIssueCode.too_big, + maximum: def.maxSize.value, + type: "set", + inclusive: true, + message: def.maxSize.message + }); + status.dirty(); + } + } + const valueType = this._def.valueType; + function finalizeSet(elements2) { + const parsedSet = /* @__PURE__ */ new Set(); + for (const element of elements2) { + if (element.status === "aborted") + return INVALID; + if (element.status === "dirty") + status.dirty(); + parsedSet.add(element.value); + } + return { status: status.value, value: parsedSet }; + } + const elements = [...ctx.data.values()].map((item, i) => valueType._parse(new ParseInputLazyPath(ctx, item, ctx.path, i))); + if (ctx.common.async) { + return Promise.all(elements).then((elements2) => finalizeSet(elements2)); + } else { + return finalizeSet(elements); + } + } + min(minSize, message) { + return new ZodSet({ + ...this._def, + minSize: { value: minSize, message: errorUtil.toString(message) } + }); + } + max(maxSize, message) { + return new ZodSet({ + ...this._def, + maxSize: { value: maxSize, message: errorUtil.toString(message) } + }); + } + size(size, message) { + return this.min(size, message).max(size, message); + } + nonempty(message) { + return this.min(1, message); + } +}; +ZodSet.create = (valueType, params) => { + return new ZodSet({ + valueType, + minSize: null, + maxSize: null, + typeName: ZodFirstPartyTypeKind.ZodSet, + ...processCreateParams(params) + }); +}; +var ZodFunction = class extends ZodType { + constructor() { + super(...arguments); + this.validate = this.implement; + } + _parse(input) { + const { ctx } = this._processInputParams(input); + if (ctx.parsedType !== ZodParsedType.function) { + addIssueToContext(ctx, { + code: ZodIssueCode.invalid_type, + expected: ZodParsedType.function, + received: ctx.parsedType + }); + return INVALID; + } + function makeArgsIssue(args, error) { + return makeIssue({ + data: args, + path: ctx.path, + errorMaps: [ + ctx.common.contextualErrorMap, + ctx.schemaErrorMap, + getErrorMap(), + errorMap + ].filter((x) => !!x), + issueData: { + code: ZodIssueCode.invalid_arguments, + argumentsError: error + } + }); + } + function makeReturnsIssue(returns, error) { + return makeIssue({ + data: returns, + path: ctx.path, + errorMaps: [ + ctx.common.contextualErrorMap, + ctx.schemaErrorMap, + getErrorMap(), + errorMap + ].filter((x) => !!x), + issueData: { + code: ZodIssueCode.invalid_return_type, + returnTypeError: error + } + }); + } + const params = { errorMap: ctx.common.contextualErrorMap }; + const fn = ctx.data; + if (this._def.returns instanceof ZodPromise) { + return OK(async (...args) => { + const error = new ZodError([]); + const parsedArgs = await this._def.args.parseAsync(args, params).catch((e) => { + error.addIssue(makeArgsIssue(args, e)); + throw error; + }); + const result = await fn(...parsedArgs); + const parsedReturns = await this._def.returns._def.type.parseAsync(result, params).catch((e) => { + error.addIssue(makeReturnsIssue(result, e)); + throw error; + }); + return parsedReturns; + }); + } else { + return OK((...args) => { + const parsedArgs = this._def.args.safeParse(args, params); + if (!parsedArgs.success) { + throw new ZodError([makeArgsIssue(args, parsedArgs.error)]); + } + const result = fn(...parsedArgs.data); + const parsedReturns = this._def.returns.safeParse(result, params); + if (!parsedReturns.success) { + throw new ZodError([makeReturnsIssue(result, parsedReturns.error)]); + } + return parsedReturns.data; + }); + } + } + parameters() { + return this._def.args; + } + returnType() { + return this._def.returns; + } + args(...items) { + return new ZodFunction({ + ...this._def, + args: ZodTuple.create(items).rest(ZodUnknown.create()) + }); + } + returns(returnType) { + return new ZodFunction({ + ...this._def, + returns: returnType + }); + } + implement(func) { + const validatedFunc = this.parse(func); + return validatedFunc; + } + strictImplement(func) { + const validatedFunc = this.parse(func); + return validatedFunc; + } + static create(args, returns, params) { + return new ZodFunction({ + args: args ? args : ZodTuple.create([]).rest(ZodUnknown.create()), + returns: returns || ZodUnknown.create(), + typeName: ZodFirstPartyTypeKind.ZodFunction, + ...processCreateParams(params) + }); + } +}; +var ZodLazy = class extends ZodType { + get schema() { + return this._def.getter(); + } + _parse(input) { + const { ctx } = this._processInputParams(input); + const lazySchema = this._def.getter(); + return lazySchema._parse({ data: ctx.data, path: ctx.path, parent: ctx }); + } +}; +ZodLazy.create = (getter, params) => { + return new ZodLazy({ + getter, + typeName: ZodFirstPartyTypeKind.ZodLazy, + ...processCreateParams(params) + }); +}; +var ZodLiteral = class extends ZodType { + _parse(input) { + if (input.data !== this._def.value) { + const ctx = this._getOrReturnCtx(input); + addIssueToContext(ctx, { + code: ZodIssueCode.invalid_literal, + expected: this._def.value + }); + return INVALID; + } + return { status: "valid", value: input.data }; + } + get value() { + return this._def.value; + } +}; +ZodLiteral.create = (value, params) => { + return new ZodLiteral({ + value, + typeName: ZodFirstPartyTypeKind.ZodLiteral, + ...processCreateParams(params) + }); +}; +function createZodEnum(values, params) { + return new ZodEnum({ + values, + typeName: ZodFirstPartyTypeKind.ZodEnum, + ...processCreateParams(params) + }); +} +var ZodEnum = class extends ZodType { + _parse(input) { + if (typeof input.data !== "string") { + const ctx = this._getOrReturnCtx(input); + const expectedValues = this._def.values; + addIssueToContext(ctx, { + expected: util.joinValues(expectedValues), + received: ctx.parsedType, + code: ZodIssueCode.invalid_type + }); + return INVALID; + } + if (this._def.values.indexOf(input.data) === -1) { + const ctx = this._getOrReturnCtx(input); + const expectedValues = this._def.values; + addIssueToContext(ctx, { + received: ctx.data, + code: ZodIssueCode.invalid_enum_value, + options: expectedValues + }); + return INVALID; + } + return OK(input.data); + } + get options() { + return this._def.values; + } + get enum() { + const enumValues = {}; + for (const val of this._def.values) { + enumValues[val] = val; + } + return enumValues; + } + get Values() { + const enumValues = {}; + for (const val of this._def.values) { + enumValues[val] = val; + } + return enumValues; + } + get Enum() { + const enumValues = {}; + for (const val of this._def.values) { + enumValues[val] = val; + } + return enumValues; + } +}; +ZodEnum.create = createZodEnum; +var ZodNativeEnum = class extends ZodType { + _parse(input) { + const nativeEnumValues = util.getValidEnumValues(this._def.values); + const ctx = this._getOrReturnCtx(input); + if (ctx.parsedType !== ZodParsedType.string && ctx.parsedType !== ZodParsedType.number) { + const expectedValues = util.objectValues(nativeEnumValues); + addIssueToContext(ctx, { + expected: util.joinValues(expectedValues), + received: ctx.parsedType, + code: ZodIssueCode.invalid_type + }); + return INVALID; + } + if (nativeEnumValues.indexOf(input.data) === -1) { + const expectedValues = util.objectValues(nativeEnumValues); + addIssueToContext(ctx, { + received: ctx.data, + code: ZodIssueCode.invalid_enum_value, + options: expectedValues + }); + return INVALID; + } + return OK(input.data); + } + get enum() { + return this._def.values; + } +}; +ZodNativeEnum.create = (values, params) => { + return new ZodNativeEnum({ + values, + typeName: ZodFirstPartyTypeKind.ZodNativeEnum, + ...processCreateParams(params) + }); +}; +var ZodPromise = class extends ZodType { + _parse(input) { + const { ctx } = this._processInputParams(input); + if (ctx.parsedType !== ZodParsedType.promise && ctx.common.async === false) { + addIssueToContext(ctx, { + code: ZodIssueCode.invalid_type, + expected: ZodParsedType.promise, + received: ctx.parsedType + }); + return INVALID; + } + const promisified = ctx.parsedType === ZodParsedType.promise ? ctx.data : Promise.resolve(ctx.data); + return OK(promisified.then((data) => { + return this._def.type.parseAsync(data, { + path: ctx.path, + errorMap: ctx.common.contextualErrorMap + }); + })); + } +}; +ZodPromise.create = (schema, params) => { + return new ZodPromise({ + type: schema, + typeName: ZodFirstPartyTypeKind.ZodPromise, + ...processCreateParams(params) + }); +}; +var ZodEffects = class extends ZodType { + innerType() { + return this._def.schema; + } + _parse(input) { + const { status, ctx } = this._processInputParams(input); + const effect = this._def.effect || null; + if (effect.type === "preprocess") { + const processed = effect.transform(ctx.data); + if (ctx.common.async) { + return Promise.resolve(processed).then((processed2) => { + return this._def.schema._parseAsync({ + data: processed2, + path: ctx.path, + parent: ctx + }); + }); + } else { + return this._def.schema._parseSync({ + data: processed, + path: ctx.path, + parent: ctx + }); + } + } + const checkCtx = { + addIssue: (arg) => { + addIssueToContext(ctx, arg); + if (arg.fatal) { + status.abort(); + } else { + status.dirty(); + } + }, + get path() { + return ctx.path; + } + }; + checkCtx.addIssue = checkCtx.addIssue.bind(checkCtx); + if (effect.type === "refinement") { + const executeRefinement = (acc) => { + const result = effect.refinement(acc, checkCtx); + if (ctx.common.async) { + return Promise.resolve(result); + } + if (result instanceof Promise) { + throw new Error("Async refinement encountered during synchronous parse operation. Use .parseAsync instead."); + } + return acc; + }; + if (ctx.common.async === false) { + const inner = this._def.schema._parseSync({ + data: ctx.data, + path: ctx.path, + parent: ctx + }); + if (inner.status === "aborted") + return INVALID; + if (inner.status === "dirty") + status.dirty(); + executeRefinement(inner.value); + return { status: status.value, value: inner.value }; + } else { + return this._def.schema._parseAsync({ data: ctx.data, path: ctx.path, parent: ctx }).then((inner) => { + if (inner.status === "aborted") + return INVALID; + if (inner.status === "dirty") + status.dirty(); + return executeRefinement(inner.value).then(() => { + return { status: status.value, value: inner.value }; + }); + }); + } + } + if (effect.type === "transform") { + if (ctx.common.async === false) { + const base = this._def.schema._parseSync({ + data: ctx.data, + path: ctx.path, + parent: ctx + }); + if (!isValid(base)) + return base; + const result = effect.transform(base.value, checkCtx); + if (result instanceof Promise) { + throw new Error(`Asynchronous transform encountered during synchronous parse operation. Use .parseAsync instead.`); + } + return { status: status.value, value: result }; + } else { + return this._def.schema._parseAsync({ data: ctx.data, path: ctx.path, parent: ctx }).then((base) => { + if (!isValid(base)) + return base; + return Promise.resolve(effect.transform(base.value, checkCtx)).then((result) => ({ status: status.value, value: result })); + }); + } + } + util.assertNever(effect); + } +}; +ZodEffects.create = (schema, effect, params) => { + return new ZodEffects({ + schema, + typeName: ZodFirstPartyTypeKind.ZodEffects, + effect, + ...processCreateParams(params) + }); +}; +ZodEffects.createWithPreprocess = (preprocess, schema, params) => { + return new ZodEffects({ + schema, + effect: { type: "preprocess", transform: preprocess }, + typeName: ZodFirstPartyTypeKind.ZodEffects, + ...processCreateParams(params) + }); +}; +var ZodOptional = class extends ZodType { + _parse(input) { + const parsedType = this._getType(input); + if (parsedType === ZodParsedType.undefined) { + return OK(void 0); + } + return this._def.innerType._parse(input); + } + unwrap() { + return this._def.innerType; + } +}; +ZodOptional.create = (type, params) => { + return new ZodOptional({ + innerType: type, + typeName: ZodFirstPartyTypeKind.ZodOptional, + ...processCreateParams(params) + }); +}; +var ZodNullable = class extends ZodType { + _parse(input) { + const parsedType = this._getType(input); + if (parsedType === ZodParsedType.null) { + return OK(null); + } + return this._def.innerType._parse(input); + } + unwrap() { + return this._def.innerType; + } +}; +ZodNullable.create = (type, params) => { + return new ZodNullable({ + innerType: type, + typeName: ZodFirstPartyTypeKind.ZodNullable, + ...processCreateParams(params) + }); +}; +var ZodDefault = class extends ZodType { + _parse(input) { + const { ctx } = this._processInputParams(input); + let data = ctx.data; + if (ctx.parsedType === ZodParsedType.undefined) { + data = this._def.defaultValue(); + } + return this._def.innerType._parse({ + data, + path: ctx.path, + parent: ctx + }); + } + removeDefault() { + return this._def.innerType; + } +}; +ZodDefault.create = (type, params) => { + return new ZodOptional({ + innerType: type, + typeName: ZodFirstPartyTypeKind.ZodOptional, + ...processCreateParams(params) + }); +}; +var ZodNaN = class extends ZodType { + _parse(input) { + const parsedType = this._getType(input); + if (parsedType !== ZodParsedType.nan) { + const ctx = this._getOrReturnCtx(input); + addIssueToContext(ctx, { + code: ZodIssueCode.invalid_type, + expected: ZodParsedType.nan, + received: ctx.parsedType + }); + return INVALID; + } + return { status: "valid", value: input.data }; + } +}; +ZodNaN.create = (params) => { + return new ZodNaN({ + typeName: ZodFirstPartyTypeKind.ZodNaN, + ...processCreateParams(params) + }); +}; +var BRAND = Symbol("zod_brand"); +var ZodBranded = class extends ZodType { + _parse(input) { + const { ctx } = this._processInputParams(input); + const data = ctx.data; + return this._def.type._parse({ + data, + path: ctx.path, + parent: ctx + }); + } + unwrap() { + return this._def.type; + } +}; +var custom = (check, params = {}, fatal) => { + if (check) + return ZodAny.create().superRefine((data, ctx) => { + if (!check(data)) { + const p = typeof params === "function" ? params(data) : params; + const p2 = typeof p === "string" ? { message: p } : p; + ctx.addIssue({ code: "custom", ...p2, fatal }); + } + }); + return ZodAny.create(); +}; +var late = { + object: ZodObject.lazycreate +}; +var ZodFirstPartyTypeKind; +(function(ZodFirstPartyTypeKind2) { + ZodFirstPartyTypeKind2["ZodString"] = "ZodString"; + ZodFirstPartyTypeKind2["ZodNumber"] = "ZodNumber"; + ZodFirstPartyTypeKind2["ZodNaN"] = "ZodNaN"; + ZodFirstPartyTypeKind2["ZodBigInt"] = "ZodBigInt"; + ZodFirstPartyTypeKind2["ZodBoolean"] = "ZodBoolean"; + ZodFirstPartyTypeKind2["ZodDate"] = "ZodDate"; + ZodFirstPartyTypeKind2["ZodUndefined"] = "ZodUndefined"; + ZodFirstPartyTypeKind2["ZodNull"] = "ZodNull"; + ZodFirstPartyTypeKind2["ZodAny"] = "ZodAny"; + ZodFirstPartyTypeKind2["ZodUnknown"] = "ZodUnknown"; + ZodFirstPartyTypeKind2["ZodNever"] = "ZodNever"; + ZodFirstPartyTypeKind2["ZodVoid"] = "ZodVoid"; + ZodFirstPartyTypeKind2["ZodArray"] = "ZodArray"; + ZodFirstPartyTypeKind2["ZodObject"] = "ZodObject"; + ZodFirstPartyTypeKind2["ZodUnion"] = "ZodUnion"; + ZodFirstPartyTypeKind2["ZodDiscriminatedUnion"] = "ZodDiscriminatedUnion"; + ZodFirstPartyTypeKind2["ZodIntersection"] = "ZodIntersection"; + ZodFirstPartyTypeKind2["ZodTuple"] = "ZodTuple"; + ZodFirstPartyTypeKind2["ZodRecord"] = "ZodRecord"; + ZodFirstPartyTypeKind2["ZodMap"] = "ZodMap"; + ZodFirstPartyTypeKind2["ZodSet"] = "ZodSet"; + ZodFirstPartyTypeKind2["ZodFunction"] = "ZodFunction"; + ZodFirstPartyTypeKind2["ZodLazy"] = "ZodLazy"; + ZodFirstPartyTypeKind2["ZodLiteral"] = "ZodLiteral"; + ZodFirstPartyTypeKind2["ZodEnum"] = "ZodEnum"; + ZodFirstPartyTypeKind2["ZodEffects"] = "ZodEffects"; + ZodFirstPartyTypeKind2["ZodNativeEnum"] = "ZodNativeEnum"; + ZodFirstPartyTypeKind2["ZodOptional"] = "ZodOptional"; + ZodFirstPartyTypeKind2["ZodNullable"] = "ZodNullable"; + ZodFirstPartyTypeKind2["ZodDefault"] = "ZodDefault"; + ZodFirstPartyTypeKind2["ZodPromise"] = "ZodPromise"; + ZodFirstPartyTypeKind2["ZodBranded"] = "ZodBranded"; +})(ZodFirstPartyTypeKind || (ZodFirstPartyTypeKind = {})); +var instanceOfType = (cls, params = { + message: `Input not instance of ${cls.name}` +}) => custom((data) => data instanceof cls, params, true); +var stringType = ZodString.create; +var numberType = ZodNumber.create; +var nanType = ZodNaN.create; +var bigIntType = ZodBigInt.create; +var booleanType = ZodBoolean.create; +var dateType = ZodDate.create; +var undefinedType = ZodUndefined.create; +var nullType = ZodNull.create; +var anyType = ZodAny.create; +var unknownType = ZodUnknown.create; +var neverType = ZodNever.create; +var voidType = ZodVoid.create; +var arrayType = ZodArray.create; +var objectType = ZodObject.create; +var strictObjectType = ZodObject.strictCreate; +var unionType = ZodUnion.create; +var discriminatedUnionType = ZodDiscriminatedUnion.create; +var intersectionType = ZodIntersection.create; +var tupleType = ZodTuple.create; +var recordType = ZodRecord.create; +var mapType = ZodMap.create; +var setType = ZodSet.create; +var functionType = ZodFunction.create; +var lazyType = ZodLazy.create; +var literalType = ZodLiteral.create; +var enumType = ZodEnum.create; +var nativeEnumType = ZodNativeEnum.create; +var promiseType = ZodPromise.create; +var effectsType = ZodEffects.create; +var optionalType = ZodOptional.create; +var nullableType = ZodNullable.create; +var preprocessType = ZodEffects.createWithPreprocess; +var ostring = () => stringType().optional(); +var onumber = () => numberType().optional(); +var oboolean = () => booleanType().optional(); +var NEVER = INVALID; +var mod = /* @__PURE__ */ Object.freeze({ + __proto__: null, + getParsedType, + ZodParsedType, + defaultErrorMap: errorMap, + setErrorMap, + getErrorMap, + makeIssue, + EMPTY_PATH, + addIssueToContext, + ParseStatus, + INVALID, + DIRTY, + OK, + isAborted, + isDirty, + isValid, + isAsync, + ZodType, + ZodString, + ZodNumber, + ZodBigInt, + ZodBoolean, + ZodDate, + ZodUndefined, + ZodNull, + ZodAny, + ZodUnknown, + ZodNever, + ZodVoid, + ZodArray, + get objectUtil() { + return objectUtil; + }, + ZodObject, + ZodUnion, + ZodDiscriminatedUnion, + ZodIntersection, + ZodTuple, + ZodRecord, + ZodMap, + ZodSet, + ZodFunction, + ZodLazy, + ZodLiteral, + ZodEnum, + ZodNativeEnum, + ZodPromise, + ZodEffects, + ZodTransformer: ZodEffects, + ZodOptional, + ZodNullable, + ZodDefault, + ZodNaN, + BRAND, + ZodBranded, + custom, + Schema: ZodType, + ZodSchema: ZodType, + late, + get ZodFirstPartyTypeKind() { + return ZodFirstPartyTypeKind; + }, + any: anyType, + array: arrayType, + bigint: bigIntType, + boolean: booleanType, + date: dateType, + discriminatedUnion: discriminatedUnionType, + effect: effectsType, + "enum": enumType, + "function": functionType, + "instanceof": instanceOfType, + intersection: intersectionType, + lazy: lazyType, + literal: literalType, + map: mapType, + nan: nanType, + nativeEnum: nativeEnumType, + never: neverType, + "null": nullType, + nullable: nullableType, + number: numberType, + object: objectType, + oboolean, + onumber, + optional: optionalType, + ostring, + preprocess: preprocessType, + promise: promiseType, + record: recordType, + set: setType, + strictObject: strictObjectType, + string: stringType, + transformer: effectsType, + tuple: tupleType, + "undefined": undefinedType, + union: unionType, + unknown: unknownType, + "void": voidType, + NEVER, + ZodIssueCode, + quotelessJson, + ZodError +}); + +// src/lib/get-text-schema.ts +var getTextSchema = mod.object({ + text: mod.string().min(2).max(255) +}); + +// src/index.ts +var resolver = new Resolver(); +resolver.define("getText" /* getText */, async (req) => { + console.log("called getText()"); + await requireAccess({ req }); + const accountId = requireAccountId(req); + const payload = getTextSchema.parse(req.payload); + console.log("accessed getText()"); + return getText({ ...payload, accountId }); +}); +async function requireAccess({ req }) { + const isAdmin = await isJiraGlobalAdmin(); + if (!isAdmin) { + throw new Error("not permitted"); + } +} +function requireAccountId(req) { + return mod.string().parse(req.context.accountId); +} +var handler = resolver.getDefinitions(); +export { + handler +}; From 23579a2a65e39079f380f3fe25262b33c69c0f25 Mon Sep 17 00:00:00 2001 From: Joshua Wong Date: Wed, 30 Nov 2022 13:58:31 -0600 Subject: [PATCH 024/517] fix: compare content of the `JSXText` variant --- crates/forge_analyzer/src/ir.rs | 26 ++++++++++++++++++++------ 1 file changed, 20 insertions(+), 6 deletions(-) diff --git a/crates/forge_analyzer/src/ir.rs b/crates/forge_analyzer/src/ir.rs index 01fd083f..57594f39 100644 --- a/crates/forge_analyzer/src/ir.rs +++ b/crates/forge_analyzer/src/ir.rs @@ -351,12 +351,26 @@ impl Default for Body { impl PartialEq for Literal { fn eq(&self, other: &Self) -> bool { match (self, other) { - (Self::Str(l0), Self::Str(r0)) => l0 == r0, - (Self::Bool(l0), Self::Bool(r0)) => l0 == r0, - (Self::Number(l0), Self::Number(r0)) => l0 == r0, - (Self::RegExp(l0, l1), Self::RegExp(r0, r1)) => l0 == r0 && l1 == r1, - (Self::BigInt(l0), Self::BigInt(r0)) => l0 == r0, - (l0, r0) => mem::discriminant(l0) == mem::discriminant(r0), + (Self::Str(l0), Self::Str(r0)) => *l0 == *r0, + (Self::Bool(l0), Self::Bool(r0)) => *l0 == *r0, + (Self::Number(l0), Self::Number(r0)) => *l0 == *r0, + (Self::RegExp(l0, l1), Self::RegExp(r0, r1)) => *l0 == *r0 && *l1 == *r1, + (Self::BigInt(l0), Self::BigInt(r0)) => *l0 == *r0, + (Self::JSXText(l0), Self::JSXText(r0)) => *l0 == *r0, + (Self::Null, Self::Null) | (Self::Undef, Self::Undef) => true, + // We intentionally list out every possibility instead of using [`std::mem::discriminant`] to trigger a compile error + // in the event that a new variant with a field is added. + ( + Self::Bool(_) + | Self::Str(_) + | Self::JSXText(_) + | Self::BigInt(_) + | Self::Number(_) + | Self::RegExp(_, _) + | Self::Null + | Self::Undef, + _, + ) => false, } } } From acc7c49c8392cdee1357186654a6fa87645620c7 Mon Sep 17 00:00:00 2001 From: Joshua Wong Date: Sun, 4 Dec 2022 17:35:21 -0600 Subject: [PATCH 025/517] feat: add .dockerignore --- .dockerignore | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 .dockerignore diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 00000000..38bd013f --- /dev/null +++ b/.dockerignore @@ -0,0 +1,3 @@ +*.md +LICENSE-* +test-apps From 6124874ab8d286c34d01664441037d476bfe81d8 Mon Sep 17 00:00:00 2001 From: Joshua Wong Date: Thu, 8 Dec 2022 13:10:08 -0600 Subject: [PATCH 026/517] feat: interp v2 --- crates/forge_analyzer/src/definitions.rs | 872 ++++++++++--- crates/forge_analyzer/src/interp.rs | 115 ++ crates/forge_analyzer/src/ir.rs | 57 +- crates/forge_analyzer/src/lib.rs | 1 + crates/fsrt/src/main.rs | 7 + test-apps/issue-1-resolver/src/index.js | 1437 ++++++++++++---------- 6 files changed, 1663 insertions(+), 826 deletions(-) create mode 100644 crates/forge_analyzer/src/interp.rs diff --git a/crates/forge_analyzer/src/definitions.rs b/crates/forge_analyzer/src/definitions.rs index 17468c4a..6fcfecee 100644 --- a/crates/forge_analyzer/src/definitions.rs +++ b/crates/forge_analyzer/src/definitions.rs @@ -9,20 +9,22 @@ use swc_core::{ common::SyntaxContext, ecma::{ ast::{ - ArrayLit, ArrowExpr, AssignExpr, AssignOp, AssignPat, AssignPatProp, AssignProp, - AwaitExpr, BinExpr, BindingIdent, BlockStmt, BreakStmt, CallExpr, Callee, ClassDecl, - ClassExpr, ComputedPropName, CondExpr, ContinueStmt, Decl, DefaultDecl, DoWhileStmt, - ExportAll, ExportDecl, ExportDefaultDecl, ExportDefaultExpr, ExportNamedSpecifier, - Expr, ExprOrSpread, ExprStmt, FnDecl, FnExpr, ForInStmt, ForOfStmt, ForStmt, Function, - Id, Ident, IfStmt, ImportDecl, ImportDefaultSpecifier, ImportNamedSpecifier, - ImportStarAsSpecifier, KeyValuePatProp, KeyValueProp, LabeledStmt, Lit, MemberExpr, - MemberProp, MetaPropExpr, MethodProp, Module, ModuleDecl, ModuleExportName, ModuleItem, - NewExpr, ObjectLit, ObjectPat, ObjectPatProp, OptCall, OptChainBase, OptChainExpr, - ParenExpr, Pat, PatOrExpr, PrivateName, Prop, PropName, PropOrSpread, ReturnStmt, - SeqExpr, Stmt, Str, Super, SuperProp, SuperPropExpr, SwitchStmt, TaggedTpl, ThisExpr, - ThrowStmt, Tpl, TryStmt, TsAsExpr, TsConstAssertion, TsInstantiation, TsNonNullExpr, - TsSatisfiesExpr, TsTypeAssertion, UnaryExpr, UpdateExpr, VarDecl, VarDeclarator, - WhileStmt, WithStmt, YieldExpr, + ArrayLit, ArrayPat, ArrowExpr, AssignExpr, AssignOp, AssignPat, AssignPatProp, + AssignProp, AwaitExpr, BinExpr, BindingIdent, BlockStmt, BlockStmtOrExpr, BreakStmt, + CallExpr, Callee, ClassDecl, ClassExpr, ComputedPropName, CondExpr, ContinueStmt, Decl, + DefaultDecl, DoWhileStmt, ExportAll, ExportDecl, ExportDefaultDecl, ExportDefaultExpr, + ExportNamedSpecifier, Expr, ExprOrSpread, ExprStmt, FnDecl, FnExpr, ForInStmt, + ForOfStmt, ForStmt, Function, Id, Ident, IfStmt, Import, ImportDecl, + ImportDefaultSpecifier, ImportNamedSpecifier, ImportStarAsSpecifier, JSXFragment, + JSXMemberExpr, JSXNamespacedName, KeyValuePatProp, KeyValueProp, LabeledStmt, Lit, + MemberExpr, MemberProp, MetaPropExpr, MethodProp, Module, ModuleDecl, ModuleExportName, + ModuleItem, NewExpr, Number, ObjectLit, ObjectPat, ObjectPatProp, OptCall, + OptChainBase, OptChainExpr, ParenExpr, Pat, PatOrExpr, PrivateName, Prop, PropName, + PropOrSpread, RestPat, ReturnStmt, SeqExpr, Stmt, Str, Super, SuperProp, SuperPropExpr, + SwitchStmt, TaggedTpl, ThisExpr, ThrowStmt, Tpl, TplElement, TryStmt, TsAsExpr, + TsConstAssertion, TsInstantiation, TsNonNullExpr, TsSatisfiesExpr, TsTypeAssertion, + UnaryExpr, UpdateExpr, VarDecl, VarDeclOrExpr, VarDeclOrPat, VarDeclarator, WhileStmt, + WithStmt, YieldExpr, }, atoms::JsWord, visit::{noop_visit_type, Visit, VisitWith}, @@ -33,7 +35,10 @@ use typed_index_collections::{TiSlice, TiVec}; use crate::{ ctx::ModId, - ir::{BasicBlockId, Body, Inst, Literal, Operand, Projection, Rvalue, Terminator, Variable}, + ir::{ + BasicBlockId, Body, Inst, Literal, Operand, Projection, Rvalue, Template, Terminator, + Variable, RETURN_VAR, + }, }; create_newtype! { @@ -101,19 +106,21 @@ pub fn run_resolver( modules: &TiSlice, file_resolver: &ForgeResolver, ) -> Environment { - let mut resolver = Environment::new(); + let mut environment = Environment::new(); for (curr_mod, module) in modules.iter_enumerated() { let mut export_collector = ExportCollector { - res_table: &mut resolver.resolver, + res_table: &mut environment.resolver, curr_mod, exports: vec![], default: None, }; module.visit_children_with(&mut export_collector); - let mod_id = resolver.exports.push_and_get_key(export_collector.exports); + let mod_id = environment + .exports + .push_and_get_key(export_collector.exports); debug_assert_eq!(curr_mod, mod_id); if let Some(default) = export_collector.default { - let def_id = resolver.default_exports.insert(curr_mod, default); + let def_id = environment.default_exports.insert(curr_mod, default); debug_assert_eq!(def_id, None, "def_id shouldn't be set"); } } @@ -121,7 +128,7 @@ pub fn run_resolver( let mut foreign = TiVec::default(); for (curr_mod, module) in modules.iter_enumerated() { let mut import_collector = ImportCollector { - resolver: &mut resolver, + resolver: &mut environment, file_resolver, foreign_defs: &mut foreign, curr_mod, @@ -132,17 +139,17 @@ pub fn run_resolver( } let defs = Definitions::new( - resolver + environment .resolver .defs .iter_enumerated() .map(|(id, &d)| (id, d)), foreign, ); - resolver.defs = defs; + environment.defs = defs; for (curr_mod, module) in modules.iter_enumerated() { let mut lowerer = Lowerer { - res: &mut resolver, + res: &mut environment, curr_mod, parents: vec![], curr_def: None, @@ -152,14 +159,14 @@ pub fn run_resolver( for (curr_mod, module) in modules.iter_enumerated() { let mut collector = FunctionCollector { - res: &mut resolver, + res: &mut environment, module: curr_mod, parent: None, }; module.visit_with(&mut collector); } - resolver + environment } /// this struct is a bit of a hack, because we also use it for @@ -211,11 +218,29 @@ impl Class { } } - fn find_member(&self, name: &JsWord) -> Option { + /// Locate a property on this object. + /// + /// Note that this does not look up the prototype chain. + /// + /// # Example + /// + /// ```javascript + /// const obj = { + /// foo: 1, + /// }; + /// ``` + /// + /// ```rust + /// let obj = Class::new(obj_id); + /// obj.find_member("foo"); + /// ``` + fn find_member(&self, name: &N) -> Option + where + JsWord: PartialEq, + { self.pub_members .iter() - .find(|(n, _)| n == name) - .map(|(_, d)| *d) + .find_map(|(n, d)| (*n == *name).then_some(*d)) } } @@ -224,13 +249,13 @@ create_newtype! { } #[derive(Debug, Clone, PartialEq, Eq)] -enum ImportKind { +pub enum ImportKind { Star, Default, Named(JsWord), } -#[derive(Debug, Clone)] +#[derive(Debug, Clone, PartialEq, Eq)] pub struct ForeignItem { kind: ImportKind, module_name: JsWord, @@ -245,15 +270,26 @@ type DefRes = DefKind<(), (), I>; pub enum DefKind { Arg, Function(F), + /// The renamed [`DefId`] + /// + /// # Example + /// + /// ```javascript + /// function foo() {} + /// // The [`DefId`] of `foo` will be the field in [`ExportAlias`] + /// // The [`DefKind`] of bar will be [`ExportAlias`] + /// export { foo as bar }; + /// ``` ExportAlias(DefId), GlobalObj(O), Class(O), + /// any imports that are not from the current project Foreign(I), /// exported(usual) handler to the actual resolver definitions /// /// [`DefId`] should point to [`DefKind::Resolver`] /// - /// # Example: + /// # Example /// /// ```javascript /// import Resolver from '@forge/resolver'; @@ -267,10 +303,28 @@ pub enum DefKind { /// export const handler = resolver.getDefinitions(); /// ``` ResolverHandler(DefId), + /// the actual resolver object + /// + /// # Example + /// + /// ```javascript + /// import Resolver from '@forge/resolver'; + /// + /// const resolver = new Resolver(); + /// // `resolver` resolves to a DefId pointing to [`DefKind::Resolver`] + /// ``` Resolver(O), + /// function defined on a resolver object + /// + /// # Example + /// + /// ```javascript + /// // the `handler` symbol resolves to a DefId for [`DefKind::ResolverHandler`] + /// resolver.define('handlerFunc', ({ payload, context }) => {} + /// ``` ResolverDef(DefId), Closure(F), - // Ex: `module` in import * as 'foo' from 'module' + // Example: `module` in import * as 'foo' from 'module' ModuleNs(ModId), Undefined, } @@ -282,36 +336,49 @@ impl Default for DefKind { } impl DefKind { - fn expect_body(self) -> F { + pub fn expect_body(self) -> F { match self { Self::Function(f) | Self::Closure(f) => f, - _ => panic!("expected function"), + k => panic!("expected function"), } } - fn expect_class(self) -> O { + pub fn expect_class(self) -> O { match self { Self::Class(c) | Self::GlobalObj(c) | Self::Resolver(c) => c, _ => panic!("expected class"), } } + + pub fn as_handler(&self) -> Option { + match *self { + Self::ResolverHandler(id) => Some(id), + _ => None, + } + } + + pub fn as_resolver(&self) -> Option<&O> { + match self { + Self::Resolver(id) => Some(id), + _ => None, + } + } + + pub fn as_resolver_def(&self) -> Option { + match *self { + Self::ResolverDef(id) => Some(id), + _ => None, + } + } + + pub fn is_resolver_handler(&self) -> bool { + matches!(self, Self::ResolverHandler(_)) + } } impl PartialEq for DefRes { fn eq(&self, other: &DefKey) -> bool { - match (*self, *other) { - (DefKind::Function(()), DefKind::Function(_)) => true, - (DefKind::ExportAlias(l0), DefKind::ExportAlias(r0)) => l0 == r0, - (DefKind::GlobalObj(()), DefKind::GlobalObj(_)) => true, - (DefKind::Class(()), DefKind::Class(_)) => true, - (DefKind::Foreign(l0), DefKind::Foreign(r0)) => l0 == r0, - (DefKind::ResolverHandler(l0), DefKind::ResolverHandler(r0)) => l0 == r0, - (DefKind::Resolver(()), DefKind::Resolver(_)) => true, - (DefKind::ResolverDef(l0), DefKind::ResolverDef(r0)) => l0 == r0, - (DefKind::Closure(()), DefKind::Closure(_)) => true, - (DefKind::ModuleNs(l0), DefKind::ModuleNs(r0)) => l0 == r0, - _ => false, - } + *other == *self } } @@ -328,7 +395,23 @@ impl PartialEq for DefKey { (DefKind::ResolverDef(l0), DefKind::ResolverDef(r0)) => l0 == r0, (DefKind::Closure(_), DefKind::Closure(())) => true, (DefKind::ModuleNs(l0), DefKind::ModuleNs(r0)) => l0 == r0, - _ => false, + (DefKind::Arg, DefKind::Arg) => true, + (DefKind::Undefined, DefKind::Undefined) => true, + ( + DefKind::Function(_) + | DefKind::Arg + | DefKind::Undefined + | DefKind::ExportAlias(_) + | DefKind::GlobalObj(_) + | DefKind::Class(_) + | DefKind::Foreign(_) + | DefKind::Resolver(_) + | DefKind::ResolverHandler(_) + | DefKind::ResolverDef(_) + | DefKind::Closure(_) + | DefKind::ModuleNs(_), + _, + ) => false, } } } @@ -395,7 +478,7 @@ enum PropPath { } fn normalize_callee_expr( - callee: &Callee, + callee: CalleeRef<'_>, res_table: &Environment, curr_mod: ModId, ) -> Vec { @@ -480,17 +563,19 @@ fn normalize_callee_expr( } } } - if let Some(expr) = callee.as_expr() { - let mut normalizer = CalleeNormalizer { - res_table, - curr_mod, - path: vec![], - in_prop: false, - }; - normalizer.visit_expr(expr); - normalizer.path - } else { - vec![] + match callee { + CalleeRef::Expr(expr) => { + let mut normalizer = CalleeNormalizer { + res_table, + curr_mod, + path: vec![], + in_prop: false, + }; + normalizer.visit_expr(expr); + normalizer.path + } + CalleeRef::Import => vec![], + CalleeRef::Super => vec![PropPath::Super], } } @@ -575,12 +660,87 @@ struct FunctionAnalyzer<'cx> { in_lhs: bool, } +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +enum CalleeRef<'a> { + Expr(&'a Expr), + Import, + Super, +} + +impl<'a> From<&'a Callee> for CalleeRef<'a> { + fn from(value: &'a Callee) -> Self { + match value { + Callee::Super(_) => Self::Super, + Callee::Import(_) => Self::Import, + Callee::Expr(expr) => Self::Expr(expr), + } + } +} + +impl<'a> From<&'a Expr> for CalleeRef<'a> { + fn from(value: &'a Expr) -> Self { + Self::Expr(value) + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] +enum ApiCallKind { + Unknown, + Trivial, + Authorize, +} + +fn classify_api_call(expr: &Expr) -> ApiCallKind { + use once_cell::sync::Lazy; + use regex::Regex; + + static TRIVIAL: Lazy = + Lazy::new(|| Regex::new(r"user|instance|avatar|license|preferences|serverinfo").unwrap()); + + struct ApiCallClassifier { + kind: ApiCallKind, + } + + impl ApiCallClassifier { + fn check(&mut self, name: &str) { + if name.contains("permission") { + self.kind = self.kind.max(ApiCallKind::Authorize); + } else if TRIVIAL.is_match(name) { + self.kind = self.kind.max(ApiCallKind::Trivial); + } + } + } + + impl Visit for ApiCallClassifier { + fn visit_tpl_element(&mut self, n: &TplElement) { + self.check(&n.raw); + } + + fn visit_str(&mut self, s: &Str) { + self.check(&s.value); + } + } + + let mut classifier = ApiCallClassifier { + kind: ApiCallKind::Unknown, + }; + expr.visit_with(&mut classifier); + classifier.kind +} + impl<'cx> FunctionAnalyzer<'cx> { #[inline] fn set_curr_terminator(&mut self, term: Terminator) { self.body.set_terminator(self.block, term); } + /// Sets the current block to `block` and returns the previous block. + #[inline] + fn goto_block(&mut self, block: BasicBlockId) -> BasicBlockId { + self.set_curr_terminator(Terminator::Goto(block)); + mem::replace(&mut self.block, block) + } + #[inline] fn push_curr_inst(&mut self, inst: Inst) { self.body.push_inst(self.block, inst); @@ -606,6 +766,150 @@ impl<'cx> FunctionAnalyzer<'cx> { Operand::Var(var) } + fn bind_pats_helper(&mut self, n: &Pat, rhs: Variable) { + match n { + Pat::Ident(BindingIdent { id, .. }) => { + let id = id.to_id(); + let def = self.res.get_or_insert_sym(id, self.module); + let var = self.body.get_or_insert_global(def); + self.push_curr_inst(Inst::Assign( + Variable::new(var), + Rvalue::Read(Operand::Var(rhs)), + )); + } + Pat::Array(ArrayPat { elems, .. }) => { + for (i, elem) in elems.iter().enumerate() { + if let Some(elem) = elem { + let prop = Projection::Known(i.to_string().into()); + let mut var = rhs.clone(); + var.projections.push(prop); + self.bind_pats_helper(elem, var); + } + } + } + Pat::Rest(RestPat { arg, .. }) => self.bind_pats_helper(arg, rhs), + Pat::Object(obj) => { + for prop in &obj.props { + let mut rhs = rhs.clone(); + match prop { + ObjectPatProp::KeyValue(KeyValuePatProp { key, value }) => match key { + PropName::Ident(Ident { sym: prop, .. }) + | PropName::Str(Str { value: prop, .. }) => { + let prop = Projection::Known(prop.clone()); + rhs.projections.push(prop); + self.bind_pats_helper(value, rhs); + } + PropName::Num(Number { value: num, .. }) => { + // FIXME: derive Eq and Hash for Projection so we can use floats + // (Yes, I know ^ is cringe, but it matches JS semantics) + let prop = Projection::Known(num.to_string().into()); + rhs.projections.push(prop); + self.bind_pats_helper(value, rhs); + } + PropName::Computed(ComputedPropName { expr, .. }) => { + let opnd = self.lower_expr(expr); + let proj = self.body.resolve_prop(self.block, opnd); + rhs.projections.push(proj); + self.bind_pats_helper(value, rhs); + } + PropName::BigInt(bigint) => { + let proj = Projection::Known(bigint.value.to_string().into()); + rhs.projections.push(proj); + self.bind_pats_helper(value, rhs); + } + }, + ObjectPatProp::Assign(AssignPatProp { key, value, .. }) => { + let id = key.to_id(); + rhs.projections.push(Projection::Known(id.0.clone())); + let def = self.res.get_or_insert_sym(id, self.module); + let var = self.body.get_or_insert_global(def); + self.push_curr_inst(Inst::Assign( + Variable::new(var), + Rvalue::Read(Operand::Var(rhs)), + )); + } + ObjectPatProp::Rest(rest) => self.bind_pats_helper(&rest.arg, rhs), + } + } + } + Pat::Assign(AssignPat { left, right, .. }) => self.bind_pats_helper(left, rhs), + Pat::Invalid(_) => {} + Pat::Expr(_) => {} + } + } + + fn lower_call(&mut self, callee: CalleeRef<'_>, args: &[ExprOrSpread]) -> Operand { + let args = args.iter().map(|arg| self.lower_expr(&arg.expr)).collect(); + let props = normalize_callee_expr(callee, self.res, self.module); + match props.first() { + Some(&PropPath::Def(id)) => { + debug!("call from: {}", self.res.def_name(id)); + debug!("call expr: {:?}", props); + } + Some(PropPath::Unknown(id)) => { + debug!("call from: {}", id.0); + debug!("call expr: {:?}", props); + } + _ => (), + } + let callee = match callee { + CalleeRef::Super => Operand::Var(Variable::SUPER), + CalleeRef::Import => Operand::UNDEF, + CalleeRef::Expr(expr) => self.lower_expr(expr), + }; + Operand::with_var( + self.body + .push_tmp(self.block, Rvalue::Call { callee, args }, None), + ) + } + + fn bind_pats(&mut self, n: &Pat, val: Rvalue) { + match n { + Pat::Ident(BindingIdent { id, .. }) => { + let id = id.to_id(); + let def = self.res.get_or_insert_sym(id, self.module); + let var = self.body.get_or_insert_global(def); + self.push_curr_inst(Inst::Assign(Variable::new(var), val)); + } + Pat::Array(ArrayPat { elems, .. }) => { + let lval = self.body.push_tmp(self.block, val, None); + self.bind_pats_helper(n, Variable::new(lval)); + } + Pat::Rest(RestPat { arg, .. }) => self.bind_pats(arg, val), + Pat::Object(ObjectPat { props, .. }) => { + let lval = self.body.push_tmp(self.block, val, None); + self.bind_pats_helper(n, Variable::new(lval)); + } + Pat::Assign(AssignPat { left, right, .. }) => { + // TODO: handle default + self.bind_pats(left, val); + } + Pat::Invalid(_) => {} + Pat::Expr(expr) => { + let opnd = self.lower_expr(expr); + self.body.coerce_to_lval(self.block, opnd); + } + } + } + + fn lower_tpl(&mut self, n: &Tpl) -> Template { + let exprs = n + .exprs + .iter() + .map(|expr| self.lower_expr(expr)) + .collect::>(); + let quasis = n + .quasis + .iter() + .map(|quasi| quasi.raw.clone()) + .collect::>(); + Template { + exprs, + quasis, + ..Default::default() + } + } + // TODO: This can probably be made into a trait fn lower_expr(&mut self, n: &Expr) -> Operand { match n { @@ -668,39 +972,51 @@ impl<'cx> FunctionAnalyzer<'cx> { } Expr::Assign(AssignExpr { op, left, right, .. - }) => match left { - PatOrExpr::Expr(_) => todo!(), - PatOrExpr::Pat(_) => todo!(), - }, + }) => { + let rhs = self.lower_expr(right); + match left { + PatOrExpr::Expr(expr) => { + let opnd = self.lower_expr(expr); + let lval = self.body.coerce_to_lval(self.block, opnd); + self.push_curr_inst(Inst::Assign(lval, Rvalue::Read(rhs.clone()))); + } + PatOrExpr::Pat(pat) => { + self.bind_pats(pat, Rvalue::Read(rhs.clone())); + } + }; + rhs + } Expr::Member(MemberExpr { obj, prop, .. }) => self.lower_member(obj, prop), Expr::Cond(CondExpr { test, cons, alt, .. - }) => self.lower_expr(test), - Expr::Call(n) => { - let mut args = Vec::with_capacity(n.args.len()); - for ExprOrSpread { spread, expr } in &n.args { - let arg = self.lower_expr(expr); - args.push(arg); - } - let callee = match &n.callee { - Callee::Super(_) => Operand::Var(Variable::SUPER), - Callee::Import(_) => Operand::UNDEF, - Callee::Expr(expr) => self.lower_expr(expr), - }; - let props = normalize_callee_expr(&n.callee, self.res, self.module); - match props.first() { - Some(&PropPath::Def(id)) => { - debug!("call from: {}", self.res.def_name(id)); - debug!("call expr: {:?}", props); - } - Some(PropPath::Unknown(id)) => { - debug!("call from: {}", id.0); - debug!("call expr: {:?}", props); - } - _ => (), - } - todo!() + }) => { + let cond = self.lower_expr(test); + let curr = self.block; + let rest = self.body.new_block(); + let cons_block = self.body.new_block(); + let alt_block = self.body.new_block(); + self.set_curr_terminator(Terminator::If { + cond, + cons: cons_block, + alt: alt_block, + }); + self.block = cons_block; + let cons = self.lower_expr(cons); + let cons_phi = self.body.push_tmp(self.block, Rvalue::Read(cons), None); + self.set_curr_terminator(Terminator::Goto(rest)); + self.block = alt_block; + let alt = self.lower_expr(alt); + let alt_phi = self.body.push_tmp(self.block, Rvalue::Read(alt), None); + self.set_curr_terminator(Terminator::Goto(rest)); + self.block = rest; + let phi = self.body.push_tmp( + self.block, + Rvalue::Phi(vec![(cons_phi, cons_block), (alt_phi, alt_block)]), + None, + ); + Operand::with_var(phi) } + Expr::Call(CallExpr { callee, args, .. }) => self.lower_call(callee.into(), args), Expr::New(NewExpr { callee, args, .. }) => Operand::UNDEF, Expr::Seq(SeqExpr { exprs, .. }) => { if let Some((last, rest)) = exprs.split_last() { @@ -723,8 +1039,18 @@ impl<'cx> FunctionAnalyzer<'cx> { Operand::with_var(var) } Expr::Lit(lit) => lit.clone().into(), - Expr::Tpl(Tpl { exprs, quasis, .. }) => todo!(), - Expr::TaggedTpl(TaggedTpl { tag, tpl, .. }) => todo!(), + Expr::Tpl(tpl) => { + let tpl = self.lower_tpl(tpl); + Operand::with_var(self.body.push_tmp(self.block, Rvalue::Template(tpl), None)) + } + Expr::TaggedTpl(TaggedTpl { tag, tpl, .. }) => { + let tag = Some(self.lower_expr(tag)); + let tpl = Template { + tag, + ..self.lower_tpl(tpl) + }; + Operand::with_var(self.body.push_tmp(self.block, Rvalue::Template(tpl), None)) + } Expr::Arrow(_) => Operand::UNDEF, Expr::Class(_) => Operand::UNDEF, Expr::Yield(YieldExpr { arg, .. }) => arg @@ -733,11 +1059,16 @@ impl<'cx> FunctionAnalyzer<'cx> { Expr::MetaProp(_) => Operand::UNDEF, Expr::Await(AwaitExpr { arg, .. }) => self.lower_expr(arg), Expr::Paren(ParenExpr { expr, .. }) => self.lower_expr(expr), - Expr::JSXMember(_) => todo!(), - Expr::JSXNamespacedName(_) => todo!(), - Expr::JSXEmpty(_) => todo!(), - Expr::JSXElement(_) => todo!(), - Expr::JSXFragment(_) => todo!(), + Expr::JSXMember(JSXMemberExpr { obj, prop, .. }) => todo!(), + Expr::JSXNamespacedName(JSXNamespacedName { ns, name, .. }) => todo!(), + Expr::JSXEmpty(_) => Operand::UNDEF, + Expr::JSXElement(elem) => todo!(), + Expr::JSXFragment(JSXFragment { + opening, + children, + closing, + .. + }) => todo!(), Expr::TsTypeAssertion(TsTypeAssertion { expr, .. }) | Expr::TsConstAssertion(TsConstAssertion { expr, .. }) | Expr::TsNonNull(TsNonNullExpr { expr, .. }) @@ -746,7 +1077,9 @@ impl<'cx> FunctionAnalyzer<'cx> { | Expr::TsSatisfies(TsSatisfiesExpr { expr, .. }) => self.lower_expr(expr), Expr::PrivateName(PrivateName { id, .. }) => todo!(), Expr::OptChain(OptChainExpr { base, .. }) => match base { - OptChainBase::Call(OptCall { callee, args, .. }) => todo!(), + OptChainBase::Call(OptCall { callee, args, .. }) => { + self.lower_call(callee.as_ref().into(), args) + } OptChainBase::Member(MemberExpr { obj, prop, .. }) => { // TODO: create separate basic blocks self.lower_member(obj, prop) @@ -756,85 +1089,191 @@ impl<'cx> FunctionAnalyzer<'cx> { } } + fn lower_stmts(&mut self, stmts: &[Stmt]) { + for stmt in stmts { + self.lower_stmt(stmt); + } + } + fn lower_stmt(&mut self, n: &Stmt) { match n { - Stmt::Block(BlockStmt { stmts, .. }) => todo!(), - Stmt::Empty(_) => todo!(), - Stmt::Debugger(_) => todo!(), - Stmt::With(WithStmt { obj, body, .. }) => todo!(), - Stmt::Return(ReturnStmt { arg, .. }) => todo!(), - Stmt::Labeled(LabeledStmt { label, body, .. }) => todo!(), - Stmt::Break(BreakStmt { label, .. }) => todo!(), - Stmt::Continue(ContinueStmt { label, .. }) => todo!(), + Stmt::Block(BlockStmt { stmts, .. }) => self.lower_stmts(stmts), + Stmt::Empty(_) => {} + Stmt::Debugger(_) => {} + Stmt::With(WithStmt { obj, body, .. }) => { + let opnd = self.lower_expr(obj); + self.body.push_expr(self.block, Rvalue::Read(opnd)); + self.lower_stmt(body); + } + Stmt::Return(ReturnStmt { arg, .. }) => { + if let Some(arg) = arg { + let opnd = self.lower_expr(arg); + self.body + .push_inst(self.block, Inst::Assign(RETURN_VAR, Rvalue::Read(opnd))); + } + self.body.set_terminator(self.block, Terminator::Ret); + } + Stmt::Labeled(LabeledStmt { label, body, .. }) => { + self.lower_stmt(body); + } + Stmt::Break(BreakStmt { label, .. }) => {} + Stmt::Continue(ContinueStmt { label, .. }) => {} Stmt::If(IfStmt { test, cons, alt, .. - }) => todo!(), + }) => { + let [cons_block, cont] = self.body.new_blocks(); + let alt_block = if let Some(alt) = alt { + let alt_block = self.body.new_block(); + let old_block = mem::replace(&mut self.block, alt_block); + self.lower_stmt(alt); + self.set_curr_terminator(Terminator::Goto(cont)); + self.block = old_block; + alt_block + } else { + cont + }; + let cond = self.lower_expr(test); + self.set_curr_terminator(Terminator::If { + cond, + cons: cons_block, + alt: alt_block, + }); + self.block = cons_block; + self.lower_stmt(cons); + self.goto_block(cont); + } Stmt::Switch(SwitchStmt { discriminant, cases, .. - }) => todo!(), - Stmt::Throw(ThrowStmt { arg, .. }) => todo!(), + }) => { + let opnd = self.lower_expr(discriminant); + // TODO: lower switch + } + Stmt::Throw(ThrowStmt { arg, .. }) => { + let opnd = self.lower_expr(arg); + self.body.push_expr(self.block, Rvalue::Read(opnd)); + self.body.set_terminator(self.block, Terminator::Throw); + } Stmt::Try(stmt) => { let TryStmt { - block, + block: BlockStmt { stmts, .. }, handler, finalizer, .. } = &**stmt; - todo!() + self.lower_stmts(stmts); + if let Some(BlockStmt { stmts, .. }) = finalizer { + self.lower_stmts(stmts); + } + } + Stmt::While(WhileStmt { test, body, .. }) => { + let [check, cont, body_id] = self.body.new_blocks(); + self.set_curr_terminator(Terminator::Goto(check)); + self.block = check; + let cond = self.lower_expr(test); + self.set_curr_terminator(Terminator::If { + cond, + cons: body_id, + alt: cont, + }); + let check = mem::replace(&mut self.block, body_id); + self.lower_stmt(body); + self.set_curr_terminator(Terminator::Goto(check)); + self.block = cont; + } + Stmt::DoWhile(DoWhileStmt { test, body, .. }) => { + let [check, cont, body_id] = self.body.new_blocks(); + self.set_curr_terminator(Terminator::Goto(body_id)); + self.block = body_id; + self.lower_stmt(body); + self.set_curr_terminator(Terminator::Goto(check)); + self.block = check; + let cond = self.lower_expr(test); + self.set_curr_terminator(Terminator::If { + cond, + cons: body_id, + alt: cont, + }); + self.block = cont; } - Stmt::While(WhileStmt { test, body, .. }) => todo!(), - Stmt::DoWhile(DoWhileStmt { test, body, .. }) => todo!(), Stmt::For(ForStmt { init, test, update, body, .. - }) => todo!(), + }) => { + match init { + Some(VarDeclOrExpr::VarDecl(decl)) => { + self.lower_var_decl(decl); + } + Some(VarDeclOrExpr::Expr(expr)) => { + self.lower_expr(expr); + } + None => {} + } + let [check, cont, body_id] = self.body.new_blocks(); + self.goto_block(check); + if let Some(test) = test { + let cond = self.lower_expr(test); + self.set_curr_terminator(Terminator::If { + cond, + cons: body_id, + alt: cont, + }); + } else { + self.set_curr_terminator(Terminator::Goto(body_id)); + } + self.block = body_id; + self.lower_stmt(body); + if let Some(update) = update { + self.lower_expr(update); + } + self.set_curr_terminator(Terminator::Goto(check)); + self.goto_block(cont); + } Stmt::ForIn(ForInStmt { left, right, body, .. - }) => todo!(), + }) => self.lower_loop(left, right, body), Stmt::ForOf(ForOfStmt { left, right, body, .. - }) => todo!(), + }) => self.lower_loop(left, right, body), Stmt::Decl(decl) => match decl { - Decl::Class(_) => todo!(), - Decl::Fn(_) => todo!(), - Decl::Var(_) => todo!(), - Decl::TsInterface(_) => todo!(), - Decl::TsTypeAlias(_) => todo!(), - Decl::TsEnum(_) => todo!(), - Decl::TsModule(_) => todo!(), + Decl::Class(_) => {} + Decl::Fn(_) => {} + Decl::Var(var) => { + self.lower_var_decl(var); + } + Decl::TsInterface(_) + | Decl::TsTypeAlias(_) + | Decl::TsEnum(_) + | Decl::TsModule(_) => {} }, - Stmt::Expr(ExprStmt { expr, .. }) => todo!(), + Stmt::Expr(ExprStmt { expr, .. }) => { + let opnd = self.lower_expr(expr); + self.body.push_expr(self.block, Rvalue::Read(opnd)); + } } } -} -impl Visit for FunctionAnalyzer<'_> { - fn visit_stmt(&mut self, n: &Stmt) { - match n { - Stmt::Block(_) => todo!(), - Stmt::Empty(_) => todo!(), - Stmt::Debugger(_) => todo!(), - Stmt::With(_) => todo!(), - Stmt::Return(_) => todo!(), - Stmt::Labeled(_) => todo!(), - Stmt::Break(_) => todo!(), - Stmt::Continue(_) => todo!(), - Stmt::If(_) => todo!(), - Stmt::Switch(_) => todo!(), - Stmt::Throw(_) => todo!(), - Stmt::Try(_) => todo!(), - Stmt::While(_) => todo!(), - Stmt::DoWhile(_) => todo!(), - Stmt::For(_) => todo!(), - Stmt::ForIn(_) => todo!(), - Stmt::ForOf(_) => todo!(), - Stmt::Decl(_) => todo!(), - Stmt::Expr(_) => todo!(), + fn lower_loop(&mut self, left: &VarDeclOrPat, right: &Expr, body: &Stmt) { + // FIXME: don't assume loops are infinite + let opnd = self.lower_expr(right); + match left { + VarDeclOrPat::VarDecl(var) => self.lower_var_decl(var), + VarDeclOrPat::Pat(pat) => self.bind_pats(pat, Rvalue::Read(opnd)), + } + self.lower_stmt(body); + } + + fn lower_var_decl(&mut self, var: &VarDecl) { + for decl in &var.decls { + let opnd = decl + .init + .as_deref() + .map_or(Operand::UNDEF, |init| self.lower_expr(init)); + self.bind_pats(&decl.name, Rvalue::Read(opnd)); } } } @@ -948,7 +1387,67 @@ impl Visit for FunctionCollector<'_> { operand_stack: vec![], in_lhs: false, }; - n.body.visit_with(&mut analyzer); + if let Some(BlockStmt { stmts, .. }) = &n.body { + analyzer.lower_stmts(stmts); + let body = analyzer.body; + println!("name: {}", self.res.def_name(owner)); + *self.res.def_mut(owner).expect_body() = body; + } + } + + fn visit_arrow_expr( + &mut self, + ArrowExpr { + body: func_body, + params, + .. + }: &ArrowExpr, + ) { + let owner = self.parent.unwrap_or_else(|| { + self.res + .add_anonymous("__UNKNOWN", AnonType::Closure, self.module) + }); + let old_parent = self.parent.replace(owner); + func_body.visit_with(self); + let mut argdef = ArgDefiner { + res: self.res, + module: self.module, + func: owner, + body: Body::with_owner(owner), + }; + params.visit_children_with(&mut argdef); + let body = argdef.body; + let mut localdef = LocalDefiner { + res: self.res, + module: self.module, + func: owner, + body, + }; + func_body.visit_children_with(&mut localdef); + let body = localdef.body; + let mut analyzer = FunctionAnalyzer { + res: self.res, + module: self.module, + current_def: owner, + assigning_to: None, + body, + block: BasicBlockId::default(), + operand_stack: vec![], + in_lhs: false, + }; + match func_body { + BlockStmtOrExpr::BlockStmt(BlockStmt { stmts, .. }) => { + analyzer.lower_stmts(stmts); + } + BlockStmtOrExpr::Expr(e) => { + let opnd = analyzer.lower_expr(e); + analyzer + .body + .push_inst(analyzer.block, Inst::Assign(RETURN_VAR, Rvalue::Read(opnd))); + } + } + *self.res.def_mut(owner).expect_body() = analyzer.body; + self.parent = old_parent; } fn visit_var_declarator(&mut self, n: &VarDeclarator) { @@ -966,12 +1465,12 @@ impl Visit for FunctionCollector<'_> { f.visit_with(self); self.parent = old_parent; } - Some(Expr::Arrow(f)) => { - let defid = self + Some(Expr::Arrow(arrow)) => { + let owner = self .res .get_or_overwrite_sym(id, self.module, DefKind::Function(())); - let old_parent = self.parent.replace(defid); - f.visit_with(self); + let old_parent = self.parent.replace(owner); + self.visit_arrow_expr(arrow); self.parent = old_parent; } _ => {} @@ -984,18 +1483,17 @@ impl Visit for FunctionCollector<'_> { info!("found possible resolver: {propname}"); match self.res.lookup_prop(def_id, propname) { Some(def) => { + self.res.overwrite_def(def, DefKind::Function(())); info!("analyzing resolver def: {def:?}"); - let mut analyzer = FunctionAnalyzer { - res: self.res, - module: self.module, - current_def: def, - assigning_to: None, - body: Body::with_owner(def), - block: BasicBlockId::default(), - operand_stack: vec![], - in_lhs: false, - }; - expr.visit_with(&mut analyzer); + let old_parent = self.parent.replace(def); + match expr { + Expr::Fn(f) => { + f.visit_with(self); + } + Expr::Arrow(arrow) => self.visit_arrow_expr(arrow), + _ => {} + } + self.parent = old_parent; } None => { warn!("resolver def not found"); @@ -1006,7 +1504,9 @@ impl Visit for FunctionCollector<'_> { fn visit_fn_decl(&mut self, n: &FnDecl) { let id = n.ident.to_id(); - let def = self.res.get_or_insert_sym(id, self.module); + let def = self + .res + .get_or_overwrite_sym(id, self.module, DefKind::Function(())); self.parent = Some(def); n.function.visit_with(self); self.parent = None; @@ -1516,6 +2016,11 @@ impl Environment { self.resolver.parent.insert(child, parent); } + fn overwrite_def(&mut self, def: DefId, res: DefRes) { + let key = self.new_key_from_res(def, res); + self.defs.defs[def] = key; + } + #[inline] fn get_or_insert_sym(&mut self, id: Id, module: ModId) -> DefId { let def_id = self.resolver.get_or_insert_sym(id, module); @@ -1745,6 +2250,45 @@ impl Environment { } } + /// Check if def is exported from the foreign module specified in `module_name`. + pub fn as_foreign_import(&self, def: DefId, module_name: &str) -> Option<&ImportKind> { + match self.def_ref(def) { + DefKind::Foreign(f) if f.module_name == *module_name => Some(&f.kind), + _ => None, + } + } + + pub fn resolve_alias(&self, def: DefId) -> DefId { + match self.def_ref(def) { + DefKind::Arg + | DefKind::GlobalObj(_) + | DefKind::Class(_) + | DefKind::Foreign(_) + | DefKind::Resolver(_) + | DefKind::ModuleNs(_) + | DefKind::Undefined + | DefKind::Closure(_) + | DefKind::Function(_) => def, + DefKind::ExportAlias(def) + | DefKind::ResolverDef(def) + | DefKind::ResolverHandler(def) => self.resolve_alias(def), + } + } + + pub fn resolver_defs(&self, def: DefId) -> Vec<(JsWord, DefId)> { + let def = self.resolve_alias(def); + // TODO: return an iterator instead of a Vec + if let DefKind::Resolver(class) = self.def_ref(def) { + class + .pub_members + .iter() + .map(|(k, v)| (k.clone(), self.resolve_alias(*v))) + .collect() + } else { + vec![] + } + } + fn resolve_local_export(&self, module: ModId, name: &JsWord) -> Option { match &**name { "default" => self.default_exports.get(&module).copied(), diff --git a/crates/forge_analyzer/src/interp.rs b/crates/forge_analyzer/src/interp.rs new file mode 100644 index 00000000..7d58108d --- /dev/null +++ b/crates/forge_analyzer/src/interp.rs @@ -0,0 +1,115 @@ +use std::{ + cell::RefCell, + fmt::Display, + io::{self, Write}, +}; + +use forge_utils::{FxHashMap, FxHashSet}; + +use crate::{ + definitions::{DefId, Environment}, + ir::{BasicBlock, BasicBlockId, Operand, Variable, STARTING_BLOCK}, +}; + +trait JoinSemiLattice: Sized { + const BOTTOM: Self; + + fn join(&mut self, other: &Self) -> bool; +} + +enum Transition { + Call, + Break, + StepOver, +} + +trait WithCallStack { + fn add_call_stack(&mut self, stack: Vec); +} + +trait Dataflow<'cx>: Sized { + type State: JoinSemiLattice + Clone; + + fn with_interp>(interp: &Interp<'cx, C>) -> Self; + + fn transfer_block>( + &mut self, + interp: &Interp<'cx, C>, + bb: BasicBlockId, + block: &'cx BasicBlock, + initial_state: &Self::State, + ) -> Self::State; +} + +trait Checker<'cx>: Sized { + type State: JoinSemiLattice + Clone; + type Vuln: Display + WithCallStack; + type Dataflow: Dataflow<'cx, State = Self::State>; + + fn visit_call( + &mut self, + interp: &Interp<'cx, Self>, + callee: &'cx Variable, + args: &'cx [Operand], + curr_state: &Self::State, + ) -> (Transition, Self::State); +} + +struct Frame { + calling_function: DefId, + block: BasicBlockId, + inst_idx: usize, +} + +struct Interp<'cx, C: Checker<'cx>> { + env: &'cx Environment, + checker: C, + func_state: FxHashMap, + visited: FxHashSet, + callstack: Vec, + func_branches: FxHashMap>, + vulns: RefCell>, +} + +impl<'cx, C: Checker<'cx>> Interp<'cx, C> { + #[inline] + fn env(&self) -> &'cx Environment { + self.env + } + + fn note_vuln(&self, mut vuln: C::Vuln) { + vuln.add_call_stack( + self.callstack + .iter() + .map(|f| f.calling_function) + .collect::>(), + ); + self.vulns.borrow_mut().push(vuln); + } + + fn run(&mut self, func: DefId) { + if self.visited.contains(&func) { + return; + } + self.visited.insert(func); + let func = self.env().def_ref(func).expect_body(); + let mut dataflow = C::Dataflow::with_interp(self); + let mut block_id = STARTING_BLOCK; + let block = func.block(block_id); + let initial_state = C::State::BOTTOM; + let final_state = dataflow.transfer_block(self, block_id, &block, &initial_state); + } + + fn dump_results(&self, out: &mut dyn Write) -> io::Result<()> { + let vulns = &**self.vulns.borrow(); + if vulns.is_empty() { + writeln!(out, "No vulnerabilities found") + } else { + writeln!(out, "Found {} vulnerabilities", vulns.len())?; + for vuln in vulns { + writeln!(out, "{vuln}")?; + } + Ok(()) + } + } +} diff --git a/crates/forge_analyzer/src/ir.rs b/crates/forge_analyzer/src/ir.rs index 57594f39..b6e5d57d 100644 --- a/crates/forge_analyzer/src/ir.rs +++ b/crates/forge_analyzer/src/ir.rs @@ -4,10 +4,12 @@ // [`SSA`]: https://pfalcon.github.io/ssabook/latest/book-full.pdf use core::fmt; +use std::array; use std::collections::BTreeSet; use std::hash; use std::hash::Hash; use std::mem; +use std::num::NonZeroUsize; use forge_utils::create_newtype; use forge_utils::FxHashMap; @@ -28,6 +30,8 @@ use typed_index_collections::TiVec; use crate::ctx::ModId; use crate::definitions::DefId; +pub const STARTING_BLOCK: BasicBlockId = BasicBlockId(0); + create_newtype! { pub struct BasicBlockId(u32); } @@ -44,11 +48,6 @@ pub(crate) enum Terminator { Ret, Goto(BasicBlockId), Throw, - Call { - callee: Operand, - args: Vec, - ret: Option, - }, Switch { scrutinee: Operand, targets: BranchTargets, @@ -69,18 +68,24 @@ pub(crate) enum Intrinsic { StorageRead, } +#[derive(Clone, Debug, Default)] +pub(crate) struct Template { + pub(crate) quasis: Vec, + pub(crate) exprs: Vec, + // TODO: make this more memory efficient + // the semantics of this operation can probably be moved to call + pub(crate) tag: Option, +} + #[derive(Clone, Debug)] pub(crate) enum Rvalue { Unary(UnOp, Operand), Bin(BinOp, Operand, Operand), + Call { callee: Operand, args: Vec }, Read(Operand), + Phi(Vec<(VarId, BasicBlockId)>), Intrinsics(Intrinsic), - Template { - quasis: Vec, - exprs: Vec, - // TODO: make this more memory efficient - tag: Option, - }, + Template(Template), } #[derive(Clone, Debug, Default)] @@ -105,6 +110,11 @@ enum VarKind { Ret, } +pub(crate) const RETURN_VAR: Variable = Variable { + base: Base::Var(VarId(0)), + projections: SmallVec::new_const(), +}; + #[derive(Clone, Debug)] pub struct Body { owner: Option, @@ -251,7 +261,7 @@ impl Body { Self { vars: local_vars, owner: None, - blocks: Default::default(), + blocks: vec![BasicBlock::default()].into(), ident_to_local: Default::default(), def_id_to_vars: Default::default(), } @@ -291,6 +301,10 @@ impl Body { self.blocks.push_and_get_key(BasicBlock::default()) } + pub(crate) fn new_blocks(&mut self) -> [BasicBlockId; NUM] { + array::from_fn(|_| self.new_block()) + } + #[inline] pub(crate) fn new_block_with_terminator(&mut self, term: Terminator) -> BasicBlockId { self.blocks.push_and_get_key(BasicBlock { @@ -319,6 +333,20 @@ impl Body { } } + pub(crate) fn coerce_to_lval(&mut self, bb: BasicBlockId, val: Operand) -> Variable { + match val { + Operand::Var(var) => var, + Operand::Lit(_) => Variable { + base: Base::Var({ + let var = self.vars.push_and_get_key(VarKind::Temp { parent: None }); + self.push_inst(bb, Inst::Assign(Variable::new(var), Rvalue::Read(val))); + var + }), + projections: Default::default(), + }, + } + } + pub(crate) fn push_tmp( &mut self, bb: BasicBlockId, @@ -339,6 +367,11 @@ impl Body { pub(crate) fn push_expr(&mut self, bb: BasicBlockId, val: Rvalue) { self.blocks[bb].insts.push(Inst::Expr(val)); } + + #[inline] + pub(crate) fn block(&self, bb: BasicBlockId) -> &BasicBlock { + &self.blocks[bb] + } } impl Default for Body { diff --git a/crates/forge_analyzer/src/lib.rs b/crates/forge_analyzer/src/lib.rs index fd20cd06..4e54184d 100644 --- a/crates/forge_analyzer/src/lib.rs +++ b/crates/forge_analyzer/src/lib.rs @@ -3,6 +3,7 @@ pub mod ctx; pub mod definitions; pub mod engine; pub mod exports; +pub mod interp; pub mod ir; pub mod lattice; pub mod resolver; diff --git a/crates/fsrt/src/main.rs b/crates/fsrt/src/main.rs index 890895e6..1408ece1 100644 --- a/crates/fsrt/src/main.rs +++ b/crates/fsrt/src/main.rs @@ -216,6 +216,13 @@ fn scan_directory(dir: PathBuf, function: Option<&str>, opts: Opts) -> Result val; - function assertIs(_arg) { - } + function assertIs(_arg) {} util2.assertIs = assertIs; function assertNever(_x) { throw new Error(); @@ -38,7 +37,9 @@ var util; return obj; }; util2.getValidEnumValues = (obj) => { - const validKeys = util2.objectKeys(obj).filter((k) => typeof obj[obj[k]] !== "number"); + const validKeys = util2 + .objectKeys(obj) + .filter((k) => typeof obj[obj[k]] !== 'number'); const filtered = {}; for (const k of validKeys) { filtered[k] = obj[k]; @@ -46,92 +47,105 @@ var util; return util2.objectValues(filtered); }; util2.objectValues = (obj) => { - return util2.objectKeys(obj).map(function(e) { + return util2.objectKeys(obj).map(function (e) { return obj[e]; }); }; - util2.objectKeys = typeof Object.keys === "function" ? (obj) => Object.keys(obj) : (object) => { - const keys = []; - for (const key in object) { - if (Object.prototype.hasOwnProperty.call(object, key)) { - keys.push(key); - } - } - return keys; - }; + util2.objectKeys = + typeof Object.keys === 'function' + ? (obj) => Object.keys(obj) + : (object) => { + const keys = []; + for (const key in object) { + if (Object.prototype.hasOwnProperty.call(object, key)) { + keys.push(key); + } + } + return keys; + }; util2.find = (arr, checker) => { for (const item of arr) { - if (checker(item)) - return item; + if (checker(item)) return item; } return void 0; }; - util2.isInteger = typeof Number.isInteger === "function" ? (val) => Number.isInteger(val) : (val) => typeof val === "number" && isFinite(val) && Math.floor(val) === val; - function joinValues(array, separator = " | ") { - return array.map((val) => typeof val === "string" ? `'${val}'` : val).join(separator); + util2.isInteger = + typeof Number.isInteger === 'function' + ? (val) => Number.isInteger(val) + : (val) => + typeof val === 'number' && isFinite(val) && Math.floor(val) === val; + function joinValues(array, separator = ' | ') { + return array + .map((val) => (typeof val === 'string' ? `'${val}'` : val)) + .join(separator); } util2.joinValues = joinValues; util2.jsonStringifyReplacer = (_, value) => { - if (typeof value === "bigint") { + if (typeof value === 'bigint') { return value.toString(); } return value; }; })(util || (util = {})); var ZodParsedType = util.arrayToEnum([ - "string", - "nan", - "number", - "integer", - "float", - "boolean", - "date", - "bigint", - "symbol", - "function", - "undefined", - "null", - "array", - "object", - "unknown", - "promise", - "void", - "never", - "map", - "set" + 'string', + 'nan', + 'number', + 'integer', + 'float', + 'boolean', + 'date', + 'bigint', + 'symbol', + 'function', + 'undefined', + 'null', + 'array', + 'object', + 'unknown', + 'promise', + 'void', + 'never', + 'map', + 'set', ]); var getParsedType = (data) => { const t = typeof data; switch (t) { - case "undefined": + case 'undefined': return ZodParsedType.undefined; - case "string": + case 'string': return ZodParsedType.string; - case "number": + case 'number': return isNaN(data) ? ZodParsedType.nan : ZodParsedType.number; - case "boolean": + case 'boolean': return ZodParsedType.boolean; - case "function": + case 'function': return ZodParsedType.function; - case "bigint": + case 'bigint': return ZodParsedType.bigint; - case "object": + case 'object': if (Array.isArray(data)) { return ZodParsedType.array; } if (data === null) { return ZodParsedType.null; } - if (data.then && typeof data.then === "function" && data.catch && typeof data.catch === "function") { + if ( + data.then && + typeof data.then === 'function' && + data.catch && + typeof data.catch === 'function' + ) { return ZodParsedType.promise; } - if (typeof Map !== "undefined" && data instanceof Map) { + if (typeof Map !== 'undefined' && data instanceof Map) { return ZodParsedType.map; } - if (typeof Set !== "undefined" && data instanceof Set) { + if (typeof Set !== 'undefined' && data instanceof Set) { return ZodParsedType.set; } - if (typeof Date !== "undefined" && data instanceof Date) { + if (typeof Date !== 'undefined' && data instanceof Date) { return ZodParsedType.date; } return ZodParsedType.object; @@ -140,25 +154,25 @@ var getParsedType = (data) => { } }; var ZodIssueCode = util.arrayToEnum([ - "invalid_type", - "invalid_literal", - "custom", - "invalid_union", - "invalid_union_discriminator", - "invalid_enum_value", - "unrecognized_keys", - "invalid_arguments", - "invalid_return_type", - "invalid_date", - "invalid_string", - "too_small", - "too_big", - "invalid_intersection_types", - "not_multiple_of" + 'invalid_type', + 'invalid_literal', + 'custom', + 'invalid_union', + 'invalid_union_discriminator', + 'invalid_enum_value', + 'unrecognized_keys', + 'invalid_arguments', + 'invalid_return_type', + 'invalid_date', + 'invalid_string', + 'too_small', + 'too_big', + 'invalid_intersection_types', + 'not_multiple_of', ]); var quotelessJson = (obj) => { const json = JSON.stringify(obj, null, 2); - return json.replace(/"([^"]+)":/g, "$1:"); + return json.replace(/"([^"]+)":/g, '$1:'); }; var ZodError = class extends Error { constructor(issues) { @@ -176,24 +190,26 @@ var ZodError = class extends Error { } else { this.__proto__ = actualProto; } - this.name = "ZodError"; + this.name = 'ZodError'; this.issues = issues; } get errors() { return this.issues; } format(_mapper) { - const mapper = _mapper || function(issue) { - return issue.message; - }; + const mapper = + _mapper || + function (issue) { + return issue.message; + }; const fieldErrors = { _errors: [] }; const processError = (error) => { for (const issue of error.issues) { - if (issue.code === "invalid_union") { + if (issue.code === 'invalid_union') { issue.unionErrors.map(processError); - } else if (issue.code === "invalid_return_type") { + } else if (issue.code === 'invalid_return_type') { processError(issue.returnTypeError); - } else if (issue.code === "invalid_arguments") { + } else if (issue.code === 'invalid_arguments') { processError(issue.argumentsError); } else if (issue.path.length === 0) { fieldErrors._errors.push(mapper(issue)); @@ -253,25 +269,35 @@ var errorMap = (issue, _ctx) => { switch (issue.code) { case ZodIssueCode.invalid_type: if (issue.received === ZodParsedType.undefined) { - message = "Required"; + message = 'Required'; } else { message = `Expected ${issue.expected}, received ${issue.received}`; } break; case ZodIssueCode.invalid_literal: - message = `Invalid literal value, expected ${JSON.stringify(issue.expected, util.jsonStringifyReplacer)}`; + message = `Invalid literal value, expected ${JSON.stringify( + issue.expected, + util.jsonStringifyReplacer + )}`; break; case ZodIssueCode.unrecognized_keys: - message = `Unrecognized key(s) in object: ${util.joinValues(issue.keys, ", ")}`; + message = `Unrecognized key(s) in object: ${util.joinValues( + issue.keys, + ', ' + )}`; break; case ZodIssueCode.invalid_union: message = `Invalid input`; break; case ZodIssueCode.invalid_union_discriminator: - message = `Invalid discriminator value. Expected ${util.joinValues(issue.options)}`; + message = `Invalid discriminator value. Expected ${util.joinValues( + issue.options + )}`; break; case ZodIssueCode.invalid_enum_value: - message = `Invalid enum value. Expected ${util.joinValues(issue.options)}, received '${issue.received}'`; + message = `Invalid enum value. Expected ${util.joinValues( + issue.options + )}, received '${issue.received}'`; break; case ZodIssueCode.invalid_arguments: message = `Invalid function arguments`; @@ -283,43 +309,57 @@ var errorMap = (issue, _ctx) => { message = `Invalid date`; break; case ZodIssueCode.invalid_string: - if (typeof issue.validation === "object") { - if ("startsWith" in issue.validation) { + if (typeof issue.validation === 'object') { + if ('startsWith' in issue.validation) { message = `Invalid input: must start with "${issue.validation.startsWith}"`; - } else if ("endsWith" in issue.validation) { + } else if ('endsWith' in issue.validation) { message = `Invalid input: must end with "${issue.validation.endsWith}"`; } else { util.assertNever(issue.validation); } - } else if (issue.validation !== "regex") { + } else if (issue.validation !== 'regex') { message = `Invalid ${issue.validation}`; } else { - message = "Invalid"; + message = 'Invalid'; } break; case ZodIssueCode.too_small: - if (issue.type === "array") - message = `Array must contain ${issue.inclusive ? `at least` : `more than`} ${issue.minimum} element(s)`; - else if (issue.type === "string") - message = `String must contain ${issue.inclusive ? `at least` : `over`} ${issue.minimum} character(s)`; - else if (issue.type === "number") - message = `Number must be greater than ${issue.inclusive ? `or equal to ` : ``}${issue.minimum}`; - else if (issue.type === "date") - message = `Date must be greater than ${issue.inclusive ? `or equal to ` : ``}${new Date(issue.minimum)}`; - else - message = "Invalid input"; + if (issue.type === 'array') + message = `Array must contain ${ + issue.inclusive ? `at least` : `more than` + } ${issue.minimum} element(s)`; + else if (issue.type === 'string') + message = `String must contain ${ + issue.inclusive ? `at least` : `over` + } ${issue.minimum} character(s)`; + else if (issue.type === 'number') + message = `Number must be greater than ${ + issue.inclusive ? `or equal to ` : `` + }${issue.minimum}`; + else if (issue.type === 'date') + message = `Date must be greater than ${ + issue.inclusive ? `or equal to ` : `` + }${new Date(issue.minimum)}`; + else message = 'Invalid input'; break; case ZodIssueCode.too_big: - if (issue.type === "array") - message = `Array must contain ${issue.inclusive ? `at most` : `less than`} ${issue.maximum} element(s)`; - else if (issue.type === "string") - message = `String must contain ${issue.inclusive ? `at most` : `under`} ${issue.maximum} character(s)`; - else if (issue.type === "number") - message = `Number must be less than ${issue.inclusive ? `or equal to ` : ``}${issue.maximum}`; - else if (issue.type === "date") - message = `Date must be smaller than ${issue.inclusive ? `or equal to ` : ``}${new Date(issue.maximum)}`; - else - message = "Invalid input"; + if (issue.type === 'array') + message = `Array must contain ${ + issue.inclusive ? `at most` : `less than` + } ${issue.maximum} element(s)`; + else if (issue.type === 'string') + message = `String must contain ${ + issue.inclusive ? `at most` : `under` + } ${issue.maximum} character(s)`; + else if (issue.type === 'number') + message = `Number must be less than ${ + issue.inclusive ? `or equal to ` : `` + }${issue.maximum}`; + else if (issue.type === 'date') + message = `Date must be smaller than ${ + issue.inclusive ? `or equal to ` : `` + }${new Date(issue.maximum)}`; + else message = 'Invalid input'; break; case ZodIssueCode.custom: message = `Invalid input`; @@ -345,20 +385,23 @@ function getErrorMap() { } var makeIssue = (params) => { const { data, path, errorMaps, issueData } = params; - const fullPath = [...path, ...issueData.path || []]; + const fullPath = [...path, ...(issueData.path || [])]; const fullIssue = { ...issueData, - path: fullPath + path: fullPath, }; - let errorMessage = ""; - const maps = errorMaps.filter((m) => !!m).slice().reverse(); + let errorMessage = ''; + const maps = errorMaps + .filter((m) => !!m) + .slice() + .reverse(); for (const map of maps) { errorMessage = map(fullIssue, { data, defaultError: errorMessage }).message; } return { ...issueData, path: fullPath, - message: issueData.message || errorMessage + message: issueData.message || errorMessage, }; }; var EMPTY_PATH = []; @@ -371,30 +414,26 @@ function addIssueToContext(ctx, issueData) { ctx.common.contextualErrorMap, ctx.schemaErrorMap, getErrorMap(), - errorMap - ].filter((x) => !!x) + errorMap, + ].filter((x) => !!x), }); ctx.common.issues.push(issue); } var ParseStatus = class { constructor() { - this.value = "valid"; + this.value = 'valid'; } dirty() { - if (this.value === "valid") - this.value = "dirty"; + if (this.value === 'valid') this.value = 'dirty'; } abort() { - if (this.value !== "aborted") - this.value = "aborted"; + if (this.value !== 'aborted') this.value = 'aborted'; } static mergeArray(status, results) { const arrayValue = []; for (const s of results) { - if (s.status === "aborted") - return INVALID; - if (s.status === "dirty") - status.dirty(); + if (s.status === 'aborted') return INVALID; + if (s.status === 'dirty') status.dirty(); arrayValue.push(s.value); } return { status: status.value, value: arrayValue }; @@ -404,7 +443,7 @@ var ParseStatus = class { for (const pair of pairs) { syncPairs.push({ key: await pair.key, - value: await pair.value + value: await pair.value, }); } return ParseStatus.mergeObjectSync(status, syncPairs); @@ -413,15 +452,11 @@ var ParseStatus = class { const finalObject = {}; for (const pair of pairs) { const { key, value } = pair; - if (key.status === "aborted") - return INVALID; - if (value.status === "aborted") - return INVALID; - if (key.status === "dirty") - status.dirty(); - if (value.status === "dirty") - status.dirty(); - if (typeof value.value !== "undefined" || pair.alwaysSet) { + if (key.status === 'aborted') return INVALID; + if (value.status === 'aborted') return INVALID; + if (key.status === 'dirty') status.dirty(); + if (value.status === 'dirty') status.dirty(); + if (typeof value.value !== 'undefined' || pair.alwaysSet) { finalObject[key.value] = value.value; } } @@ -429,18 +464,24 @@ var ParseStatus = class { } }; var INVALID = Object.freeze({ - status: "aborted" + status: 'aborted', }); -var DIRTY = (value) => ({ status: "dirty", value }); -var OK = (value) => ({ status: "valid", value }); -var isAborted = (x) => x.status === "aborted"; -var isDirty = (x) => x.status === "dirty"; -var isValid = (x) => x.status === "valid"; +var DIRTY = (value) => ({ status: 'dirty', value }); +var OK = (value) => ({ status: 'valid', value }); +var isAborted = (x) => x.status === 'aborted'; +var isDirty = (x) => x.status === 'dirty'; +var isValid = (x) => x.status === 'valid'; var isAsync = (x) => typeof Promise !== void 0 && x instanceof Promise; var errorUtil; -(function(errorUtil2) { - errorUtil2.errToObj = (message) => typeof message === "string" ? { message } : message || {}; - errorUtil2.toString = (message) => typeof message === "string" ? message : message === null || message === void 0 ? void 0 : message.message; +(function (errorUtil2) { + errorUtil2.errToObj = (message) => + typeof message === 'string' ? { message } : message || {}; + errorUtil2.toString = (message) => + typeof message === 'string' + ? message + : message === null || message === void 0 + ? void 0 + : message.message; })(errorUtil || (errorUtil = {})); var ParseInputLazyPath = class { constructor(parent, value, path, key) { @@ -458,28 +499,42 @@ var handleResult = (ctx, result) => { return { success: true, data: result.value }; } else { if (!ctx.common.issues.length) { - throw new Error("Validation failed but no issues detected."); + throw new Error('Validation failed but no issues detected.'); } const error = new ZodError(ctx.common.issues); return { success: false, error }; } }; function processCreateParams(params) { - if (!params) - return {}; - const { errorMap: errorMap2, invalid_type_error, required_error, description } = params; + if (!params) return {}; + const { + errorMap: errorMap2, + invalid_type_error, + required_error, + description, + } = params; if (errorMap2 && (invalid_type_error || required_error)) { - throw new Error(`Can't use "invalid_type_error" or "required_error" in conjunction with custom error map.`); + throw new Error( + `Can't use "invalid_type_error" or "required_error" in conjunction with custom error map.` + ); } - if (errorMap2) - return { errorMap: errorMap2, description }; + if (errorMap2) return { errorMap: errorMap2, description }; const customMap = (iss, ctx) => { - if (iss.code !== "invalid_type") - return { message: ctx.defaultError }; - if (typeof ctx.data === "undefined") { - return { message: required_error !== null && required_error !== void 0 ? required_error : ctx.defaultError }; + if (iss.code !== 'invalid_type') return { message: ctx.defaultError }; + if (typeof ctx.data === 'undefined') { + return { + message: + required_error !== null && required_error !== void 0 + ? required_error + : ctx.defaultError, + }; } - return { message: invalid_type_error !== null && invalid_type_error !== void 0 ? invalid_type_error : ctx.defaultError }; + return { + message: + invalid_type_error !== null && invalid_type_error !== void 0 + ? invalid_type_error + : ctx.defaultError, + }; }; return { errorMap: customMap, description }; } @@ -516,14 +571,16 @@ var ZodType = class { return getParsedType(input.data); } _getOrReturnCtx(input, ctx) { - return ctx || { - common: input.parent.common, - data: input.data, - parsedType: getParsedType(input.data), - schemaErrorMap: this._def.errorMap, - path: input.path, - parent: input.parent - }; + return ( + ctx || { + common: input.parent.common, + data: input.data, + parsedType: getParsedType(input.data), + schemaErrorMap: this._def.errorMap, + path: input.path, + parent: input.parent, + } + ); } _processInputParams(input) { return { @@ -534,14 +591,14 @@ var ZodType = class { parsedType: getParsedType(input.data), schemaErrorMap: this._def.errorMap, path: input.path, - parent: input.parent - } + parent: input.parent, + }, }; } _parseSync(input) { const result = this._parse(input); if (isAsync(result)) { - throw new Error("Synchronous parse encountered promise."); + throw new Error('Synchronous parse encountered promise.'); } return result; } @@ -551,8 +608,7 @@ var ZodType = class { } parse(data, params) { const result = this.safeParse(data, params); - if (result.success) - return result.data; + if (result.success) return result.data; throw result.error; } safeParse(data, params) { @@ -560,46 +616,54 @@ var ZodType = class { const ctx = { common: { issues: [], - async: (_a = params === null || params === void 0 ? void 0 : params.async) !== null && _a !== void 0 ? _a : false, - contextualErrorMap: params === null || params === void 0 ? void 0 : params.errorMap + async: + (_a = + params === null || params === void 0 ? void 0 : params.async) !== + null && _a !== void 0 + ? _a + : false, + contextualErrorMap: + params === null || params === void 0 ? void 0 : params.errorMap, }, path: (params === null || params === void 0 ? void 0 : params.path) || [], schemaErrorMap: this._def.errorMap, parent: null, data, - parsedType: getParsedType(data) + parsedType: getParsedType(data), }; const result = this._parseSync({ data, path: ctx.path, parent: ctx }); return handleResult(ctx, result); } async parseAsync(data, params) { const result = await this.safeParseAsync(data, params); - if (result.success) - return result.data; + if (result.success) return result.data; throw result.error; } async safeParseAsync(data, params) { const ctx = { common: { issues: [], - contextualErrorMap: params === null || params === void 0 ? void 0 : params.errorMap, - async: true + contextualErrorMap: + params === null || params === void 0 ? void 0 : params.errorMap, + async: true, }, path: (params === null || params === void 0 ? void 0 : params.path) || [], schemaErrorMap: this._def.errorMap, parent: null, data, - parsedType: getParsedType(data) + parsedType: getParsedType(data), }; const maybeAsyncResult = this._parse({ data, path: [], parent: ctx }); - const result = await (isAsync(maybeAsyncResult) ? maybeAsyncResult : Promise.resolve(maybeAsyncResult)); + const result = await (isAsync(maybeAsyncResult) + ? maybeAsyncResult + : Promise.resolve(maybeAsyncResult)); return handleResult(ctx, result); } refine(check, message) { const getIssueProperties = (val) => { - if (typeof message === "string" || typeof message === "undefined") { + if (typeof message === 'string' || typeof message === 'undefined') { return { message }; - } else if (typeof message === "function") { + } else if (typeof message === 'function') { return message(val); } else { return message; @@ -607,11 +671,12 @@ var ZodType = class { }; return this._refinement((val, ctx) => { const result = check(val); - const setError = () => ctx.addIssue({ - code: ZodIssueCode.custom, - ...getIssueProperties(val) - }); - if (typeof Promise !== "undefined" && result instanceof Promise) { + const setError = () => + ctx.addIssue({ + code: ZodIssueCode.custom, + ...getIssueProperties(val), + }); + if (typeof Promise !== 'undefined' && result instanceof Promise) { return result.then((data) => { if (!data) { setError(); @@ -632,7 +697,11 @@ var ZodType = class { refinement(check, refinementData) { return this._refinement((val, ctx) => { if (!check(val)) { - ctx.addIssue(typeof refinementData === "function" ? refinementData(val, ctx) : refinementData); + ctx.addIssue( + typeof refinementData === 'function' + ? refinementData(val, ctx) + : refinementData + ); return false; } else { return true; @@ -643,7 +712,7 @@ var ZodType = class { return new ZodEffects({ schema: this, typeName: ZodFirstPartyTypeKind.ZodEffects, - effect: { type: "refinement", refinement } + effect: { type: 'refinement', refinement }, }); } optional() { @@ -671,29 +740,29 @@ var ZodType = class { return new ZodEffects({ schema: this, typeName: ZodFirstPartyTypeKind.ZodEffects, - effect: { type: "transform", transform } + effect: { type: 'transform', transform }, }); } default(def) { - const defaultValueFunc = typeof def === "function" ? def : () => def; + const defaultValueFunc = typeof def === 'function' ? def : () => def; return new ZodDefault({ innerType: this, defaultValue: defaultValueFunc, - typeName: ZodFirstPartyTypeKind.ZodDefault + typeName: ZodFirstPartyTypeKind.ZodDefault, }); } brand() { return new ZodBranded({ typeName: ZodFirstPartyTypeKind.ZodBranded, type: this, - ...processCreateParams(void 0) + ...processCreateParams(void 0), }); } describe(description) { const This = this.constructor; return new This({ ...this._def, - description + description, }); } isOptional() { @@ -704,136 +773,137 @@ var ZodType = class { } }; var cuidRegex = /^c[^\s-]{8,}$/i; -var uuidRegex = /^([a-f0-9]{8}-[a-f0-9]{4}-[1-5][a-f0-9]{3}-[a-f0-9]{4}-[a-f0-9]{12}|00000000-0000-0000-0000-000000000000)$/i; -var emailRegex = /^(([^<>()[\]\.,;:\s@\"]+(\.[^<>()[\]\.,;:\s@\"]+)*)|(\".+\"))@(([^<>()[\]\.,;:\s@\"]+\.)+[^<>()[\]\.,;:\s@\"]{2,})$/i; +var uuidRegex = + /^([a-f0-9]{8}-[a-f0-9]{4}-[1-5][a-f0-9]{3}-[a-f0-9]{4}-[a-f0-9]{12}|00000000-0000-0000-0000-000000000000)$/i; +var emailRegex = + /^(([^<>()[\]\.,;:\s@\"]+(\.[^<>()[\]\.,;:\s@\"]+)*)|(\".+\"))@(([^<>()[\]\.,;:\s@\"]+\.)+[^<>()[\]\.,;:\s@\"]{2,})$/i; var ZodString = class extends ZodType { constructor() { super(...arguments); - this._regex = (regex, validation, message) => this.refinement((data) => regex.test(data), { - validation, - code: ZodIssueCode.invalid_string, - ...errorUtil.errToObj(message) - }); + this._regex = (regex, validation, message) => + this.refinement((data) => regex.test(data), { + validation, + code: ZodIssueCode.invalid_string, + ...errorUtil.errToObj(message), + }); this.nonempty = (message) => this.min(1, errorUtil.errToObj(message)); - this.trim = () => new ZodString({ - ...this._def, - checks: [...this._def.checks, { kind: "trim" }] - }); + this.trim = () => + new ZodString({ + ...this._def, + checks: [...this._def.checks, { kind: 'trim' }], + }); } _parse(input) { const parsedType = this._getType(input); if (parsedType !== ZodParsedType.string) { const ctx2 = this._getOrReturnCtx(input); - addIssueToContext( - ctx2, - { - code: ZodIssueCode.invalid_type, - expected: ZodParsedType.string, - received: ctx2.parsedType - } - ); + addIssueToContext(ctx2, { + code: ZodIssueCode.invalid_type, + expected: ZodParsedType.string, + received: ctx2.parsedType, + }); return INVALID; } const status = new ParseStatus(); let ctx = void 0; for (const check of this._def.checks) { - if (check.kind === "min") { + if (check.kind === 'min') { if (input.data.length < check.value) { ctx = this._getOrReturnCtx(input, ctx); addIssueToContext(ctx, { code: ZodIssueCode.too_small, minimum: check.value, - type: "string", + type: 'string', inclusive: true, - message: check.message + message: check.message, }); status.dirty(); } - } else if (check.kind === "max") { + } else if (check.kind === 'max') { if (input.data.length > check.value) { ctx = this._getOrReturnCtx(input, ctx); addIssueToContext(ctx, { code: ZodIssueCode.too_big, maximum: check.value, - type: "string", + type: 'string', inclusive: true, - message: check.message + message: check.message, }); status.dirty(); } - } else if (check.kind === "email") { + } else if (check.kind === 'email') { if (!emailRegex.test(input.data)) { ctx = this._getOrReturnCtx(input, ctx); addIssueToContext(ctx, { - validation: "email", + validation: 'email', code: ZodIssueCode.invalid_string, - message: check.message + message: check.message, }); status.dirty(); } - } else if (check.kind === "uuid") { + } else if (check.kind === 'uuid') { if (!uuidRegex.test(input.data)) { ctx = this._getOrReturnCtx(input, ctx); addIssueToContext(ctx, { - validation: "uuid", + validation: 'uuid', code: ZodIssueCode.invalid_string, - message: check.message + message: check.message, }); status.dirty(); } - } else if (check.kind === "cuid") { + } else if (check.kind === 'cuid') { if (!cuidRegex.test(input.data)) { ctx = this._getOrReturnCtx(input, ctx); addIssueToContext(ctx, { - validation: "cuid", + validation: 'cuid', code: ZodIssueCode.invalid_string, - message: check.message + message: check.message, }); status.dirty(); } - } else if (check.kind === "url") { + } else if (check.kind === 'url') { try { new URL(input.data); } catch (_a) { ctx = this._getOrReturnCtx(input, ctx); addIssueToContext(ctx, { - validation: "url", + validation: 'url', code: ZodIssueCode.invalid_string, - message: check.message + message: check.message, }); status.dirty(); } - } else if (check.kind === "regex") { + } else if (check.kind === 'regex') { check.regex.lastIndex = 0; const testResult = check.regex.test(input.data); if (!testResult) { ctx = this._getOrReturnCtx(input, ctx); addIssueToContext(ctx, { - validation: "regex", + validation: 'regex', code: ZodIssueCode.invalid_string, - message: check.message + message: check.message, }); status.dirty(); } - } else if (check.kind === "trim") { + } else if (check.kind === 'trim') { input.data = input.data.trim(); - } else if (check.kind === "startsWith") { + } else if (check.kind === 'startsWith') { if (!input.data.startsWith(check.value)) { ctx = this._getOrReturnCtx(input, ctx); addIssueToContext(ctx, { code: ZodIssueCode.invalid_string, validation: { startsWith: check.value }, - message: check.message + message: check.message, }); status.dirty(); } - } else if (check.kind === "endsWith") { + } else if (check.kind === 'endsWith') { if (!input.data.endsWith(check.value)) { ctx = this._getOrReturnCtx(input, ctx); addIssueToContext(ctx, { code: ZodIssueCode.invalid_string, validation: { endsWith: check.value }, - message: check.message + message: check.message, }); status.dirty(); } @@ -846,77 +916,76 @@ var ZodString = class extends ZodType { _addCheck(check) { return new ZodString({ ...this._def, - checks: [...this._def.checks, check] + checks: [...this._def.checks, check], }); } email(message) { - return this._addCheck({ kind: "email", ...errorUtil.errToObj(message) }); + return this._addCheck({ kind: 'email', ...errorUtil.errToObj(message) }); } url(message) { - return this._addCheck({ kind: "url", ...errorUtil.errToObj(message) }); + return this._addCheck({ kind: 'url', ...errorUtil.errToObj(message) }); } uuid(message) { - return this._addCheck({ kind: "uuid", ...errorUtil.errToObj(message) }); + return this._addCheck({ kind: 'uuid', ...errorUtil.errToObj(message) }); } cuid(message) { - return this._addCheck({ kind: "cuid", ...errorUtil.errToObj(message) }); + return this._addCheck({ kind: 'cuid', ...errorUtil.errToObj(message) }); } regex(regex, message) { return this._addCheck({ - kind: "regex", + kind: 'regex', regex, - ...errorUtil.errToObj(message) + ...errorUtil.errToObj(message), }); } startsWith(value, message) { return this._addCheck({ - kind: "startsWith", + kind: 'startsWith', value, - ...errorUtil.errToObj(message) + ...errorUtil.errToObj(message), }); } endsWith(value, message) { return this._addCheck({ - kind: "endsWith", + kind: 'endsWith', value, - ...errorUtil.errToObj(message) + ...errorUtil.errToObj(message), }); } min(minLength, message) { return this._addCheck({ - kind: "min", + kind: 'min', value: minLength, - ...errorUtil.errToObj(message) + ...errorUtil.errToObj(message), }); } max(maxLength, message) { return this._addCheck({ - kind: "max", + kind: 'max', value: maxLength, - ...errorUtil.errToObj(message) + ...errorUtil.errToObj(message), }); } length(len, message) { return this.min(len, message).max(len, message); } get isEmail() { - return !!this._def.checks.find((ch) => ch.kind === "email"); + return !!this._def.checks.find((ch) => ch.kind === 'email'); } get isURL() { - return !!this._def.checks.find((ch) => ch.kind === "url"); + return !!this._def.checks.find((ch) => ch.kind === 'url'); } get isUUID() { - return !!this._def.checks.find((ch) => ch.kind === "uuid"); + return !!this._def.checks.find((ch) => ch.kind === 'uuid'); } get isCUID() { - return !!this._def.checks.find((ch) => ch.kind === "cuid"); + return !!this._def.checks.find((ch) => ch.kind === 'cuid'); } get minLength() { let min = null; for (const ch of this._def.checks) { - if (ch.kind === "min") { - if (min === null || ch.value > min) - min = ch.value; + if (ch.kind === 'min') { + if (min === null || ch.value > min) min = ch.value; } } return min; @@ -924,9 +993,8 @@ var ZodString = class extends ZodType { get maxLength() { let max = null; for (const ch of this._def.checks) { - if (ch.kind === "max") { - if (max === null || ch.value < max) - max = ch.value; + if (ch.kind === 'max') { + if (max === null || ch.value < max) max = ch.value; } } return max; @@ -936,16 +1004,16 @@ ZodString.create = (params) => { return new ZodString({ checks: [], typeName: ZodFirstPartyTypeKind.ZodString, - ...processCreateParams(params) + ...processCreateParams(params), }); }; function floatSafeRemainder(val, step) { - const valDecCount = (val.toString().split(".")[1] || "").length; - const stepDecCount = (step.toString().split(".")[1] || "").length; + const valDecCount = (val.toString().split('.')[1] || '').length; + const stepDecCount = (step.toString().split('.')[1] || '').length; const decCount = valDecCount > stepDecCount ? valDecCount : stepDecCount; - const valInt = parseInt(val.toFixed(decCount).replace(".", "")); - const stepInt = parseInt(step.toFixed(decCount).replace(".", "")); - return valInt % stepInt / Math.pow(10, decCount); + const valInt = parseInt(val.toFixed(decCount).replace('.', '')); + const stepInt = parseInt(step.toFixed(decCount).replace('.', '')); + return (valInt % stepInt) / Math.pow(10, decCount); } var ZodNumber = class extends ZodType { constructor() { @@ -961,57 +1029,61 @@ var ZodNumber = class extends ZodType { addIssueToContext(ctx2, { code: ZodIssueCode.invalid_type, expected: ZodParsedType.number, - received: ctx2.parsedType + received: ctx2.parsedType, }); return INVALID; } let ctx = void 0; const status = new ParseStatus(); for (const check of this._def.checks) { - if (check.kind === "int") { + if (check.kind === 'int') { if (!util.isInteger(input.data)) { ctx = this._getOrReturnCtx(input, ctx); addIssueToContext(ctx, { code: ZodIssueCode.invalid_type, - expected: "integer", - received: "float", - message: check.message + expected: 'integer', + received: 'float', + message: check.message, }); status.dirty(); } - } else if (check.kind === "min") { - const tooSmall = check.inclusive ? input.data < check.value : input.data <= check.value; + } else if (check.kind === 'min') { + const tooSmall = check.inclusive + ? input.data < check.value + : input.data <= check.value; if (tooSmall) { ctx = this._getOrReturnCtx(input, ctx); addIssueToContext(ctx, { code: ZodIssueCode.too_small, minimum: check.value, - type: "number", + type: 'number', inclusive: check.inclusive, - message: check.message + message: check.message, }); status.dirty(); } - } else if (check.kind === "max") { - const tooBig = check.inclusive ? input.data > check.value : input.data >= check.value; + } else if (check.kind === 'max') { + const tooBig = check.inclusive + ? input.data > check.value + : input.data >= check.value; if (tooBig) { ctx = this._getOrReturnCtx(input, ctx); addIssueToContext(ctx, { code: ZodIssueCode.too_big, maximum: check.value, - type: "number", + type: 'number', inclusive: check.inclusive, - message: check.message + message: check.message, }); status.dirty(); } - } else if (check.kind === "multipleOf") { + } else if (check.kind === 'multipleOf') { if (floatSafeRemainder(input.data, check.value) !== 0) { ctx = this._getOrReturnCtx(input, ctx); addIssueToContext(ctx, { code: ZodIssueCode.not_multiple_of, multipleOf: check.value, - message: check.message + message: check.message, }); status.dirty(); } @@ -1022,16 +1094,16 @@ var ZodNumber = class extends ZodType { return { status: status.value, value: input.data }; } gte(value, message) { - return this.setLimit("min", value, true, errorUtil.toString(message)); + return this.setLimit('min', value, true, errorUtil.toString(message)); } gt(value, message) { - return this.setLimit("min", value, false, errorUtil.toString(message)); + return this.setLimit('min', value, false, errorUtil.toString(message)); } lte(value, message) { - return this.setLimit("max", value, true, errorUtil.toString(message)); + return this.setLimit('max', value, true, errorUtil.toString(message)); } lt(value, message) { - return this.setLimit("max", value, false, errorUtil.toString(message)); + return this.setLimit('max', value, false, errorUtil.toString(message)); } setLimit(kind, value, inclusive, message) { return new ZodNumber({ @@ -1042,68 +1114,67 @@ var ZodNumber = class extends ZodType { kind, value, inclusive, - message: errorUtil.toString(message) - } - ] + message: errorUtil.toString(message), + }, + ], }); } _addCheck(check) { return new ZodNumber({ ...this._def, - checks: [...this._def.checks, check] + checks: [...this._def.checks, check], }); } int(message) { return this._addCheck({ - kind: "int", - message: errorUtil.toString(message) + kind: 'int', + message: errorUtil.toString(message), }); } positive(message) { return this._addCheck({ - kind: "min", + kind: 'min', value: 0, inclusive: false, - message: errorUtil.toString(message) + message: errorUtil.toString(message), }); } negative(message) { return this._addCheck({ - kind: "max", + kind: 'max', value: 0, inclusive: false, - message: errorUtil.toString(message) + message: errorUtil.toString(message), }); } nonpositive(message) { return this._addCheck({ - kind: "max", + kind: 'max', value: 0, inclusive: true, - message: errorUtil.toString(message) + message: errorUtil.toString(message), }); } nonnegative(message) { return this._addCheck({ - kind: "min", + kind: 'min', value: 0, inclusive: true, - message: errorUtil.toString(message) + message: errorUtil.toString(message), }); } multipleOf(value, message) { return this._addCheck({ - kind: "multipleOf", + kind: 'multipleOf', value, - message: errorUtil.toString(message) + message: errorUtil.toString(message), }); } get minValue() { let min = null; for (const ch of this._def.checks) { - if (ch.kind === "min") { - if (min === null || ch.value > min) - min = ch.value; + if (ch.kind === 'min') { + if (min === null || ch.value > min) min = ch.value; } } return min; @@ -1111,22 +1182,21 @@ var ZodNumber = class extends ZodType { get maxValue() { let max = null; for (const ch of this._def.checks) { - if (ch.kind === "max") { - if (max === null || ch.value < max) - max = ch.value; + if (ch.kind === 'max') { + if (max === null || ch.value < max) max = ch.value; } } return max; } get isInt() { - return !!this._def.checks.find((ch) => ch.kind === "int"); + return !!this._def.checks.find((ch) => ch.kind === 'int'); } }; ZodNumber.create = (params) => { return new ZodNumber({ checks: [], typeName: ZodFirstPartyTypeKind.ZodNumber, - ...processCreateParams(params) + ...processCreateParams(params), }); }; var ZodBigInt = class extends ZodType { @@ -1137,7 +1207,7 @@ var ZodBigInt = class extends ZodType { addIssueToContext(ctx, { code: ZodIssueCode.invalid_type, expected: ZodParsedType.bigint, - received: ctx.parsedType + received: ctx.parsedType, }); return INVALID; } @@ -1147,7 +1217,7 @@ var ZodBigInt = class extends ZodType { ZodBigInt.create = (params) => { return new ZodBigInt({ typeName: ZodFirstPartyTypeKind.ZodBigInt, - ...processCreateParams(params) + ...processCreateParams(params), }); }; var ZodBoolean = class extends ZodType { @@ -1158,7 +1228,7 @@ var ZodBoolean = class extends ZodType { addIssueToContext(ctx, { code: ZodIssueCode.invalid_type, expected: ZodParsedType.boolean, - received: ctx.parsedType + received: ctx.parsedType, }); return INVALID; } @@ -1168,7 +1238,7 @@ var ZodBoolean = class extends ZodType { ZodBoolean.create = (params) => { return new ZodBoolean({ typeName: ZodFirstPartyTypeKind.ZodBoolean, - ...processCreateParams(params) + ...processCreateParams(params), }); }; var ZodDate = class extends ZodType { @@ -1179,21 +1249,21 @@ var ZodDate = class extends ZodType { addIssueToContext(ctx2, { code: ZodIssueCode.invalid_type, expected: ZodParsedType.date, - received: ctx2.parsedType + received: ctx2.parsedType, }); return INVALID; } if (isNaN(input.data.getTime())) { const ctx2 = this._getOrReturnCtx(input); addIssueToContext(ctx2, { - code: ZodIssueCode.invalid_date + code: ZodIssueCode.invalid_date, }); return INVALID; } const status = new ParseStatus(); let ctx = void 0; for (const check of this._def.checks) { - if (check.kind === "min") { + if (check.kind === 'min') { if (input.data.getTime() < check.value) { ctx = this._getOrReturnCtx(input, ctx); addIssueToContext(ctx, { @@ -1201,11 +1271,11 @@ var ZodDate = class extends ZodType { message: check.message, inclusive: true, minimum: check.value, - type: "date" + type: 'date', }); status.dirty(); } - } else if (check.kind === "max") { + } else if (check.kind === 'max') { if (input.data.getTime() > check.value) { ctx = this._getOrReturnCtx(input, ctx); addIssueToContext(ctx, { @@ -1213,7 +1283,7 @@ var ZodDate = class extends ZodType { message: check.message, inclusive: true, maximum: check.value, - type: "date" + type: 'date', }); status.dirty(); } @@ -1223,35 +1293,34 @@ var ZodDate = class extends ZodType { } return { status: status.value, - value: new Date(input.data.getTime()) + value: new Date(input.data.getTime()), }; } _addCheck(check) { return new ZodDate({ ...this._def, - checks: [...this._def.checks, check] + checks: [...this._def.checks, check], }); } min(minDate, message) { return this._addCheck({ - kind: "min", + kind: 'min', value: minDate.getTime(), - message: errorUtil.toString(message) + message: errorUtil.toString(message), }); } max(maxDate, message) { return this._addCheck({ - kind: "max", + kind: 'max', value: maxDate.getTime(), - message: errorUtil.toString(message) + message: errorUtil.toString(message), }); } get minDate() { let min = null; for (const ch of this._def.checks) { - if (ch.kind === "min") { - if (min === null || ch.value > min) - min = ch.value; + if (ch.kind === 'min') { + if (min === null || ch.value > min) min = ch.value; } } return min != null ? new Date(min) : null; @@ -1259,9 +1328,8 @@ var ZodDate = class extends ZodType { get maxDate() { let max = null; for (const ch of this._def.checks) { - if (ch.kind === "max") { - if (max === null || ch.value < max) - max = ch.value; + if (ch.kind === 'max') { + if (max === null || ch.value < max) max = ch.value; } } return max != null ? new Date(max) : null; @@ -1271,7 +1339,7 @@ ZodDate.create = (params) => { return new ZodDate({ checks: [], typeName: ZodFirstPartyTypeKind.ZodDate, - ...processCreateParams(params) + ...processCreateParams(params), }); }; var ZodUndefined = class extends ZodType { @@ -1282,7 +1350,7 @@ var ZodUndefined = class extends ZodType { addIssueToContext(ctx, { code: ZodIssueCode.invalid_type, expected: ZodParsedType.undefined, - received: ctx.parsedType + received: ctx.parsedType, }); return INVALID; } @@ -1292,7 +1360,7 @@ var ZodUndefined = class extends ZodType { ZodUndefined.create = (params) => { return new ZodUndefined({ typeName: ZodFirstPartyTypeKind.ZodUndefined, - ...processCreateParams(params) + ...processCreateParams(params), }); }; var ZodNull = class extends ZodType { @@ -1303,7 +1371,7 @@ var ZodNull = class extends ZodType { addIssueToContext(ctx, { code: ZodIssueCode.invalid_type, expected: ZodParsedType.null, - received: ctx.parsedType + received: ctx.parsedType, }); return INVALID; } @@ -1313,7 +1381,7 @@ var ZodNull = class extends ZodType { ZodNull.create = (params) => { return new ZodNull({ typeName: ZodFirstPartyTypeKind.ZodNull, - ...processCreateParams(params) + ...processCreateParams(params), }); }; var ZodAny = class extends ZodType { @@ -1328,7 +1396,7 @@ var ZodAny = class extends ZodType { ZodAny.create = (params) => { return new ZodAny({ typeName: ZodFirstPartyTypeKind.ZodAny, - ...processCreateParams(params) + ...processCreateParams(params), }); }; var ZodUnknown = class extends ZodType { @@ -1343,7 +1411,7 @@ var ZodUnknown = class extends ZodType { ZodUnknown.create = (params) => { return new ZodUnknown({ typeName: ZodFirstPartyTypeKind.ZodUnknown, - ...processCreateParams(params) + ...processCreateParams(params), }); }; var ZodNever = class extends ZodType { @@ -1352,7 +1420,7 @@ var ZodNever = class extends ZodType { addIssueToContext(ctx, { code: ZodIssueCode.invalid_type, expected: ZodParsedType.never, - received: ctx.parsedType + received: ctx.parsedType, }); return INVALID; } @@ -1360,7 +1428,7 @@ var ZodNever = class extends ZodType { ZodNever.create = (params) => { return new ZodNever({ typeName: ZodFirstPartyTypeKind.ZodNever, - ...processCreateParams(params) + ...processCreateParams(params), }); }; var ZodVoid = class extends ZodType { @@ -1371,7 +1439,7 @@ var ZodVoid = class extends ZodType { addIssueToContext(ctx, { code: ZodIssueCode.invalid_type, expected: ZodParsedType.void, - received: ctx.parsedType + received: ctx.parsedType, }); return INVALID; } @@ -1381,7 +1449,7 @@ var ZodVoid = class extends ZodType { ZodVoid.create = (params) => { return new ZodVoid({ typeName: ZodFirstPartyTypeKind.ZodVoid, - ...processCreateParams(params) + ...processCreateParams(params), }); }; var ZodArray = class extends ZodType { @@ -1392,7 +1460,7 @@ var ZodArray = class extends ZodType { addIssueToContext(ctx, { code: ZodIssueCode.invalid_type, expected: ZodParsedType.array, - received: ctx.parsedType + received: ctx.parsedType, }); return INVALID; } @@ -1401,9 +1469,9 @@ var ZodArray = class extends ZodType { addIssueToContext(ctx, { code: ZodIssueCode.too_small, minimum: def.minLength.value, - type: "array", + type: 'array', inclusive: true, - message: def.minLength.message + message: def.minLength.message, }); status.dirty(); } @@ -1413,22 +1481,28 @@ var ZodArray = class extends ZodType { addIssueToContext(ctx, { code: ZodIssueCode.too_big, maximum: def.maxLength.value, - type: "array", + type: 'array', inclusive: true, - message: def.maxLength.message + message: def.maxLength.message, }); status.dirty(); } } if (ctx.common.async) { - return Promise.all(ctx.data.map((item, i) => { - return def.type._parseAsync(new ParseInputLazyPath(ctx, item, ctx.path, i)); - })).then((result2) => { + return Promise.all( + ctx.data.map((item, i) => { + return def.type._parseAsync( + new ParseInputLazyPath(ctx, item, ctx.path, i) + ); + }) + ).then((result2) => { return ParseStatus.mergeArray(status, result2); }); } const result = ctx.data.map((item, i) => { - return def.type._parseSync(new ParseInputLazyPath(ctx, item, ctx.path, i)); + return def.type._parseSync( + new ParseInputLazyPath(ctx, item, ctx.path, i) + ); }); return ParseStatus.mergeArray(status, result); } @@ -1438,13 +1512,13 @@ var ZodArray = class extends ZodType { min(minLength, message) { return new ZodArray({ ...this._def, - minLength: { value: minLength, message: errorUtil.toString(message) } + minLength: { value: minLength, message: errorUtil.toString(message) }, }); } max(maxLength, message) { return new ZodArray({ ...this._def, - maxLength: { value: maxLength, message: errorUtil.toString(message) } + maxLength: { value: maxLength, message: errorUtil.toString(message) }, }); } length(len, message) { @@ -1460,15 +1534,15 @@ ZodArray.create = (schema, params) => { minLength: null, maxLength: null, typeName: ZodFirstPartyTypeKind.ZodArray, - ...processCreateParams(params) + ...processCreateParams(params), }); }; var objectUtil; -(function(objectUtil2) { +(function (objectUtil2) { objectUtil2.mergeShapes = (first, second) => { return { ...first, - ...second + ...second, }; }; })(objectUtil || (objectUtil = {})); @@ -1477,8 +1551,8 @@ var AugmentFactory = (def) => (augmentation) => { ...def, shape: () => ({ ...def.shape(), - ...augmentation - }) + ...augmentation, + }), }); }; function deepPartialify(schema) { @@ -1490,7 +1564,7 @@ function deepPartialify(schema) { } return new ZodObject({ ...schema._def, - shape: () => newShape + shape: () => newShape, }); } else if (schema instanceof ZodArray) { return ZodArray.create(deepPartialify(schema.element)); @@ -1513,11 +1587,10 @@ var ZodObject = class extends ZodType { this.extend = AugmentFactory(this._def); } _getCached() { - if (this._cached !== null) - return this._cached; + if (this._cached !== null) return this._cached; const shape = this._def.shape(); const keys = util.objectKeys(shape); - return this._cached = { shape, keys }; + return (this._cached = { shape, keys }); } _parse(input) { const parsedType = this._getType(input); @@ -1526,14 +1599,19 @@ var ZodObject = class extends ZodType { addIssueToContext(ctx2, { code: ZodIssueCode.invalid_type, expected: ZodParsedType.object, - received: ctx2.parsedType + received: ctx2.parsedType, }); return INVALID; } const { status, ctx } = this._processInputParams(input); const { shape, keys: shapeKeys } = this._getCached(); const extraKeys = []; - if (!(this._def.catchall instanceof ZodNever && this._def.unknownKeys === "strip")) { + if ( + !( + this._def.catchall instanceof ZodNever && + this._def.unknownKeys === 'strip' + ) + ) { for (const key in ctx.data) { if (!shapeKeys.includes(key)) { extraKeys.push(key); @@ -1545,30 +1623,31 @@ var ZodObject = class extends ZodType { const keyValidator = shape[key]; const value = ctx.data[key]; pairs.push({ - key: { status: "valid", value: key }, - value: keyValidator._parse(new ParseInputLazyPath(ctx, value, ctx.path, key)), - alwaysSet: key in ctx.data + key: { status: 'valid', value: key }, + value: keyValidator._parse( + new ParseInputLazyPath(ctx, value, ctx.path, key) + ), + alwaysSet: key in ctx.data, }); } if (this._def.catchall instanceof ZodNever) { const unknownKeys = this._def.unknownKeys; - if (unknownKeys === "passthrough") { + if (unknownKeys === 'passthrough') { for (const key of extraKeys) { pairs.push({ - key: { status: "valid", value: key }, - value: { status: "valid", value: ctx.data[key] } + key: { status: 'valid', value: key }, + value: { status: 'valid', value: ctx.data[key] }, }); } - } else if (unknownKeys === "strict") { + } else if (unknownKeys === 'strict') { if (extraKeys.length > 0) { addIssueToContext(ctx, { code: ZodIssueCode.unrecognized_keys, - keys: extraKeys + keys: extraKeys, }); status.dirty(); } - } else if (unknownKeys === "strip") - ; + } else if (unknownKeys === 'strip'); else { throw new Error(`Internal ZodObject error: invalid unknownKeys value.`); } @@ -1577,29 +1656,31 @@ var ZodObject = class extends ZodType { for (const key of extraKeys) { const value = ctx.data[key]; pairs.push({ - key: { status: "valid", value: key }, + key: { status: 'valid', value: key }, value: catchall._parse( new ParseInputLazyPath(ctx, value, ctx.path, key) ), - alwaysSet: key in ctx.data + alwaysSet: key in ctx.data, }); } } if (ctx.common.async) { - return Promise.resolve().then(async () => { - const syncPairs = []; - for (const pair of pairs) { - const key = await pair.key; - syncPairs.push({ - key, - value: await pair.value, - alwaysSet: pair.alwaysSet - }); - } - return syncPairs; - }).then((syncPairs) => { - return ParseStatus.mergeObjectSync(status, syncPairs); - }); + return Promise.resolve() + .then(async () => { + const syncPairs = []; + for (const pair of pairs) { + const key = await pair.key; + syncPairs.push({ + key, + value: await pair.value, + alwaysSet: pair.alwaysSet, + }); + } + return syncPairs; + }) + .then((syncPairs) => { + return ParseStatus.mergeObjectSync(status, syncPairs); + }); } else { return ParseStatus.mergeObjectSync(status, pairs); } @@ -1611,32 +1692,44 @@ var ZodObject = class extends ZodType { errorUtil.errToObj; return new ZodObject({ ...this._def, - unknownKeys: "strict", - ...message !== void 0 ? { - errorMap: (issue, ctx) => { - var _a, _b, _c, _d; - const defaultError = (_c = (_b = (_a = this._def).errorMap) === null || _b === void 0 ? void 0 : _b.call(_a, issue, ctx).message) !== null && _c !== void 0 ? _c : ctx.defaultError; - if (issue.code === "unrecognized_keys") - return { - message: (_d = errorUtil.errToObj(message).message) !== null && _d !== void 0 ? _d : defaultError - }; - return { - message: defaultError - }; - } - } : {} + unknownKeys: 'strict', + ...(message !== void 0 + ? { + errorMap: (issue, ctx) => { + var _a, _b, _c, _d; + const defaultError = + (_c = + (_b = (_a = this._def).errorMap) === null || _b === void 0 + ? void 0 + : _b.call(_a, issue, ctx).message) !== null && _c !== void 0 + ? _c + : ctx.defaultError; + if (issue.code === 'unrecognized_keys') + return { + message: + (_d = errorUtil.errToObj(message).message) !== null && + _d !== void 0 + ? _d + : defaultError, + }; + return { + message: defaultError, + }; + }, + } + : {}), }); } strip() { return new ZodObject({ ...this._def, - unknownKeys: "strip" + unknownKeys: 'strip', }); } passthrough() { return new ZodObject({ ...this._def, - unknownKeys: "passthrough" + unknownKeys: 'passthrough', }); } setKey(key, schema) { @@ -1646,26 +1739,26 @@ var ZodObject = class extends ZodType { const merged = new ZodObject({ unknownKeys: merging._def.unknownKeys, catchall: merging._def.catchall, - shape: () => objectUtil.mergeShapes(this._def.shape(), merging._def.shape()), - typeName: ZodFirstPartyTypeKind.ZodObject + shape: () => + objectUtil.mergeShapes(this._def.shape(), merging._def.shape()), + typeName: ZodFirstPartyTypeKind.ZodObject, }); return merged; } catchall(index) { return new ZodObject({ ...this._def, - catchall: index + catchall: index, }); } pick(mask) { const shape = {}; util.objectKeys(mask).map((key) => { - if (this.shape[key]) - shape[key] = this.shape[key]; + if (this.shape[key]) shape[key] = this.shape[key]; }); return new ZodObject({ ...this._def, - shape: () => shape + shape: () => shape, }); } omit(mask) { @@ -1677,7 +1770,7 @@ var ZodObject = class extends ZodType { }); return new ZodObject({ ...this._def, - shape: () => shape + shape: () => shape, }); } deepPartial() { @@ -1695,7 +1788,7 @@ var ZodObject = class extends ZodType { }); return new ZodObject({ ...this._def, - shape: () => newShape + shape: () => newShape, }); } else { for (const key in this.shape) { @@ -1705,7 +1798,7 @@ var ZodObject = class extends ZodType { } return new ZodObject({ ...this._def, - shape: () => newShape + shape: () => newShape, }); } required() { @@ -1720,7 +1813,7 @@ var ZodObject = class extends ZodType { } return new ZodObject({ ...this._def, - shape: () => newShape + shape: () => newShape, }); } keyof() { @@ -1730,28 +1823,28 @@ var ZodObject = class extends ZodType { ZodObject.create = (shape, params) => { return new ZodObject({ shape: () => shape, - unknownKeys: "strip", + unknownKeys: 'strip', catchall: ZodNever.create(), typeName: ZodFirstPartyTypeKind.ZodObject, - ...processCreateParams(params) + ...processCreateParams(params), }); }; ZodObject.strictCreate = (shape, params) => { return new ZodObject({ shape: () => shape, - unknownKeys: "strict", + unknownKeys: 'strict', catchall: ZodNever.create(), typeName: ZodFirstPartyTypeKind.ZodObject, - ...processCreateParams(params) + ...processCreateParams(params), }); }; ZodObject.lazycreate = (shape, params) => { return new ZodObject({ shape, - unknownKeys: "strip", + unknownKeys: 'strip', catchall: ZodNever.create(), typeName: ZodFirstPartyTypeKind.ZodObject, - ...processCreateParams(params) + ...processCreateParams(params), }); }; var ZodUnion = class extends ZodType { @@ -1760,42 +1853,46 @@ var ZodUnion = class extends ZodType { const options = this._def.options; function handleResults(results) { for (const result of results) { - if (result.result.status === "valid") { + if (result.result.status === 'valid') { return result.result; } } for (const result of results) { - if (result.result.status === "dirty") { + if (result.result.status === 'dirty') { ctx.common.issues.push(...result.ctx.common.issues); return result.result; } } - const unionErrors = results.map((result) => new ZodError(result.ctx.common.issues)); + const unionErrors = results.map( + (result) => new ZodError(result.ctx.common.issues) + ); addIssueToContext(ctx, { code: ZodIssueCode.invalid_union, - unionErrors + unionErrors, }); return INVALID; } if (ctx.common.async) { - return Promise.all(options.map(async (option) => { - const childCtx = { - ...ctx, - common: { - ...ctx.common, - issues: [] - }, - parent: null - }; - return { - result: await option._parseAsync({ - data: ctx.data, - path: ctx.path, - parent: childCtx - }), - ctx: childCtx - }; - })).then(handleResults); + return Promise.all( + options.map(async (option) => { + const childCtx = { + ...ctx, + common: { + ...ctx.common, + issues: [], + }, + parent: null, + }; + return { + result: await option._parseAsync({ + data: ctx.data, + path: ctx.path, + parent: childCtx, + }), + ctx: childCtx, + }; + }) + ).then(handleResults); } else { let dirty = void 0; const issues = []; @@ -1804,18 +1901,18 @@ var ZodUnion = class extends ZodType { ...ctx, common: { ...ctx.common, - issues: [] + issues: [], }, - parent: null + parent: null, }; const result = option._parseSync({ data: ctx.data, path: ctx.path, - parent: childCtx + parent: childCtx, }); - if (result.status === "valid") { + if (result.status === 'valid') { return result; - } else if (result.status === "dirty" && !dirty) { + } else if (result.status === 'dirty' && !dirty) { dirty = { result, ctx: childCtx }; } if (childCtx.common.issues.length) { @@ -1829,7 +1926,7 @@ var ZodUnion = class extends ZodType { const unionErrors = issues.map((issues2) => new ZodError(issues2)); addIssueToContext(ctx, { code: ZodIssueCode.invalid_union, - unionErrors + unionErrors, }); return INVALID; } @@ -1842,7 +1939,7 @@ ZodUnion.create = (types, params) => { return new ZodUnion({ options: types, typeName: ZodFirstPartyTypeKind.ZodUnion, - ...processCreateParams(params) + ...processCreateParams(params), }); }; var ZodDiscriminatedUnion = class extends ZodType { @@ -1852,7 +1949,7 @@ var ZodDiscriminatedUnion = class extends ZodType { addIssueToContext(ctx, { code: ZodIssueCode.invalid_type, expected: ZodParsedType.object, - received: ctx.parsedType + received: ctx.parsedType, }); return INVALID; } @@ -1863,7 +1960,7 @@ var ZodDiscriminatedUnion = class extends ZodType { addIssueToContext(ctx, { code: ZodIssueCode.invalid_union_discriminator, options: this.validDiscriminatorValues, - path: [discriminator] + path: [discriminator], }); return INVALID; } @@ -1871,13 +1968,13 @@ var ZodDiscriminatedUnion = class extends ZodType { return option._parseAsync({ data: ctx.data, path: ctx.path, - parent: ctx + parent: ctx, }); } else { return option._parseSync({ data: ctx.data, path: ctx.path, - parent: ctx + parent: ctx, }); } } @@ -1898,16 +1995,18 @@ var ZodDiscriminatedUnion = class extends ZodType { options.set(discriminatorValue, type); }); } catch (e) { - throw new Error("The discriminator value could not be extracted from all the provided schemas"); + throw new Error( + 'The discriminator value could not be extracted from all the provided schemas' + ); } if (options.size !== types.length) { - throw new Error("Some of the discriminator values are not unique"); + throw new Error('Some of the discriminator values are not unique'); } return new ZodDiscriminatedUnion({ typeName: ZodFirstPartyTypeKind.ZodDiscriminatedUnion, discriminator, options, - ...processCreateParams(params) + ...processCreateParams(params), }); } }; @@ -1918,7 +2017,9 @@ function mergeValues(a, b) { return { valid: true, data: a }; } else if (aType === ZodParsedType.object && bType === ZodParsedType.object) { const bKeys = util.objectKeys(b); - const sharedKeys = util.objectKeys(a).filter((key) => bKeys.indexOf(key) !== -1); + const sharedKeys = util + .objectKeys(a) + .filter((key) => bKeys.indexOf(key) !== -1); const newObj = { ...a, ...b }; for (const key of sharedKeys) { const sharedValue = mergeValues(a[key], b[key]); @@ -1943,7 +2044,11 @@ function mergeValues(a, b) { newArray.push(sharedValue.data); } return { valid: true, data: newArray }; - } else if (aType === ZodParsedType.date && bType === ZodParsedType.date && +a === +b) { + } else if ( + aType === ZodParsedType.date && + bType === ZodParsedType.date && + +a === +b + ) { return { valid: true, data: a }; } else { return { valid: false }; @@ -1959,7 +2064,7 @@ var ZodIntersection = class extends ZodType { const merged = mergeValues(parsedLeft.value, parsedRight.value); if (!merged.valid) { addIssueToContext(ctx, { - code: ZodIssueCode.invalid_intersection_types + code: ZodIssueCode.invalid_intersection_types, }); return INVALID; } @@ -1973,24 +2078,27 @@ var ZodIntersection = class extends ZodType { this._def.left._parseAsync({ data: ctx.data, path: ctx.path, - parent: ctx + parent: ctx, }), this._def.right._parseAsync({ data: ctx.data, path: ctx.path, - parent: ctx - }) + parent: ctx, + }), ]).then(([left, right]) => handleParsed(left, right)); } else { - return handleParsed(this._def.left._parseSync({ - data: ctx.data, - path: ctx.path, - parent: ctx - }), this._def.right._parseSync({ - data: ctx.data, - path: ctx.path, - parent: ctx - })); + return handleParsed( + this._def.left._parseSync({ + data: ctx.data, + path: ctx.path, + parent: ctx, + }), + this._def.right._parseSync({ + data: ctx.data, + path: ctx.path, + parent: ctx, + }) + ); } } }; @@ -1999,7 +2107,7 @@ ZodIntersection.create = (left, right, params) => { left, right, typeName: ZodFirstPartyTypeKind.ZodIntersection, - ...processCreateParams(params) + ...processCreateParams(params), }); }; var ZodTuple = class extends ZodType { @@ -2009,7 +2117,7 @@ var ZodTuple = class extends ZodType { addIssueToContext(ctx, { code: ZodIssueCode.invalid_type, expected: ZodParsedType.array, - received: ctx.parsedType + received: ctx.parsedType, }); return INVALID; } @@ -2018,7 +2126,7 @@ var ZodTuple = class extends ZodType { code: ZodIssueCode.too_small, minimum: this._def.items.length, inclusive: true, - type: "array" + type: 'array', }); return INVALID; } @@ -2028,16 +2136,19 @@ var ZodTuple = class extends ZodType { code: ZodIssueCode.too_big, maximum: this._def.items.length, inclusive: true, - type: "array" + type: 'array', }); status.dirty(); } - const items = ctx.data.map((item, itemIndex) => { - const schema = this._def.items[itemIndex] || this._def.rest; - if (!schema) - return null; - return schema._parse(new ParseInputLazyPath(ctx, item, ctx.path, itemIndex)); - }).filter((x) => !!x); + const items = ctx.data + .map((item, itemIndex) => { + const schema = this._def.items[itemIndex] || this._def.rest; + if (!schema) return null; + return schema._parse( + new ParseInputLazyPath(ctx, item, ctx.path, itemIndex) + ); + }) + .filter((x) => !!x); if (ctx.common.async) { return Promise.all(items).then((results) => { return ParseStatus.mergeArray(status, results); @@ -2052,19 +2163,19 @@ var ZodTuple = class extends ZodType { rest(rest) { return new ZodTuple({ ...this._def, - rest + rest, }); } }; ZodTuple.create = (schemas, params) => { if (!Array.isArray(schemas)) { - throw new Error("You must pass an array of schemas to z.tuple([ ... ])"); + throw new Error('You must pass an array of schemas to z.tuple([ ... ])'); } return new ZodTuple({ items: schemas, typeName: ZodFirstPartyTypeKind.ZodTuple, rest: null, - ...processCreateParams(params) + ...processCreateParams(params), }); }; var ZodRecord = class extends ZodType { @@ -2080,7 +2191,7 @@ var ZodRecord = class extends ZodType { addIssueToContext(ctx, { code: ZodIssueCode.invalid_type, expected: ZodParsedType.object, - received: ctx.parsedType + received: ctx.parsedType, }); return INVALID; } @@ -2090,7 +2201,9 @@ var ZodRecord = class extends ZodType { for (const key in ctx.data) { pairs.push({ key: keyType._parse(new ParseInputLazyPath(ctx, key, ctx.path, key)), - value: valueType._parse(new ParseInputLazyPath(ctx, ctx.data[key], ctx.path, key)) + value: valueType._parse( + new ParseInputLazyPath(ctx, ctx.data[key], ctx.path, key) + ), }); } if (ctx.common.async) { @@ -2108,14 +2221,14 @@ var ZodRecord = class extends ZodType { keyType: first, valueType: second, typeName: ZodFirstPartyTypeKind.ZodRecord, - ...processCreateParams(third) + ...processCreateParams(third), }); } return new ZodRecord({ keyType: ZodString.create(), valueType: first, typeName: ZodFirstPartyTypeKind.ZodRecord, - ...processCreateParams(second) + ...processCreateParams(second), }); } }; @@ -2126,7 +2239,7 @@ var ZodMap = class extends ZodType { addIssueToContext(ctx, { code: ZodIssueCode.invalid_type, expected: ZodParsedType.map, - received: ctx.parsedType + received: ctx.parsedType, }); return INVALID; } @@ -2134,8 +2247,12 @@ var ZodMap = class extends ZodType { const valueType = this._def.valueType; const pairs = [...ctx.data.entries()].map(([key, value], index) => { return { - key: keyType._parse(new ParseInputLazyPath(ctx, key, ctx.path, [index, "key"])), - value: valueType._parse(new ParseInputLazyPath(ctx, value, ctx.path, [index, "value"])) + key: keyType._parse( + new ParseInputLazyPath(ctx, key, ctx.path, [index, 'key']) + ), + value: valueType._parse( + new ParseInputLazyPath(ctx, value, ctx.path, [index, 'value']) + ), }; }); if (ctx.common.async) { @@ -2144,10 +2261,10 @@ var ZodMap = class extends ZodType { for (const pair of pairs) { const key = await pair.key; const value = await pair.value; - if (key.status === "aborted" || value.status === "aborted") { + if (key.status === 'aborted' || value.status === 'aborted') { return INVALID; } - if (key.status === "dirty" || value.status === "dirty") { + if (key.status === 'dirty' || value.status === 'dirty') { status.dirty(); } finalMap.set(key.value, value.value); @@ -2159,10 +2276,10 @@ var ZodMap = class extends ZodType { for (const pair of pairs) { const key = pair.key; const value = pair.value; - if (key.status === "aborted" || value.status === "aborted") { + if (key.status === 'aborted' || value.status === 'aborted') { return INVALID; } - if (key.status === "dirty" || value.status === "dirty") { + if (key.status === 'dirty' || value.status === 'dirty') { status.dirty(); } finalMap.set(key.value, value.value); @@ -2176,7 +2293,7 @@ ZodMap.create = (keyType, valueType, params) => { valueType, keyType, typeName: ZodFirstPartyTypeKind.ZodMap, - ...processCreateParams(params) + ...processCreateParams(params), }); }; var ZodSet = class extends ZodType { @@ -2186,7 +2303,7 @@ var ZodSet = class extends ZodType { addIssueToContext(ctx, { code: ZodIssueCode.invalid_type, expected: ZodParsedType.set, - received: ctx.parsedType + received: ctx.parsedType, }); return INVALID; } @@ -2196,9 +2313,9 @@ var ZodSet = class extends ZodType { addIssueToContext(ctx, { code: ZodIssueCode.too_small, minimum: def.minSize.value, - type: "set", + type: 'set', inclusive: true, - message: def.minSize.message + message: def.minSize.message, }); status.dirty(); } @@ -2208,9 +2325,9 @@ var ZodSet = class extends ZodType { addIssueToContext(ctx, { code: ZodIssueCode.too_big, maximum: def.maxSize.value, - type: "set", + type: 'set', inclusive: true, - message: def.maxSize.message + message: def.maxSize.message, }); status.dirty(); } @@ -2219,15 +2336,15 @@ var ZodSet = class extends ZodType { function finalizeSet(elements2) { const parsedSet = /* @__PURE__ */ new Set(); for (const element of elements2) { - if (element.status === "aborted") - return INVALID; - if (element.status === "dirty") - status.dirty(); + if (element.status === 'aborted') return INVALID; + if (element.status === 'dirty') status.dirty(); parsedSet.add(element.value); } return { status: status.value, value: parsedSet }; } - const elements = [...ctx.data.values()].map((item, i) => valueType._parse(new ParseInputLazyPath(ctx, item, ctx.path, i))); + const elements = [...ctx.data.values()].map((item, i) => + valueType._parse(new ParseInputLazyPath(ctx, item, ctx.path, i)) + ); if (ctx.common.async) { return Promise.all(elements).then((elements2) => finalizeSet(elements2)); } else { @@ -2237,13 +2354,13 @@ var ZodSet = class extends ZodType { min(minSize, message) { return new ZodSet({ ...this._def, - minSize: { value: minSize, message: errorUtil.toString(message) } + minSize: { value: minSize, message: errorUtil.toString(message) }, }); } max(maxSize, message) { return new ZodSet({ ...this._def, - maxSize: { value: maxSize, message: errorUtil.toString(message) } + maxSize: { value: maxSize, message: errorUtil.toString(message) }, }); } size(size, message) { @@ -2259,7 +2376,7 @@ ZodSet.create = (valueType, params) => { minSize: null, maxSize: null, typeName: ZodFirstPartyTypeKind.ZodSet, - ...processCreateParams(params) + ...processCreateParams(params), }); }; var ZodFunction = class extends ZodType { @@ -2273,7 +2390,7 @@ var ZodFunction = class extends ZodType { addIssueToContext(ctx, { code: ZodIssueCode.invalid_type, expected: ZodParsedType.function, - received: ctx.parsedType + received: ctx.parsedType, }); return INVALID; } @@ -2285,12 +2402,12 @@ var ZodFunction = class extends ZodType { ctx.common.contextualErrorMap, ctx.schemaErrorMap, getErrorMap(), - errorMap + errorMap, ].filter((x) => !!x), issueData: { code: ZodIssueCode.invalid_arguments, - argumentsError: error - } + argumentsError: error, + }, }); } function makeReturnsIssue(returns, error) { @@ -2301,12 +2418,12 @@ var ZodFunction = class extends ZodType { ctx.common.contextualErrorMap, ctx.schemaErrorMap, getErrorMap(), - errorMap + errorMap, ].filter((x) => !!x), issueData: { code: ZodIssueCode.invalid_return_type, - returnTypeError: error - } + returnTypeError: error, + }, }); } const params = { errorMap: ctx.common.contextualErrorMap }; @@ -2314,15 +2431,19 @@ var ZodFunction = class extends ZodType { if (this._def.returns instanceof ZodPromise) { return OK(async (...args) => { const error = new ZodError([]); - const parsedArgs = await this._def.args.parseAsync(args, params).catch((e) => { - error.addIssue(makeArgsIssue(args, e)); - throw error; - }); + const parsedArgs = await this._def.args + .parseAsync(args, params) + .catch((e) => { + error.addIssue(makeArgsIssue(args, e)); + throw error; + }); const result = await fn(...parsedArgs); - const parsedReturns = await this._def.returns._def.type.parseAsync(result, params).catch((e) => { - error.addIssue(makeReturnsIssue(result, e)); - throw error; - }); + const parsedReturns = await this._def.returns._def.type + .parseAsync(result, params) + .catch((e) => { + error.addIssue(makeReturnsIssue(result, e)); + throw error; + }); return parsedReturns; }); } else { @@ -2349,13 +2470,13 @@ var ZodFunction = class extends ZodType { args(...items) { return new ZodFunction({ ...this._def, - args: ZodTuple.create(items).rest(ZodUnknown.create()) + args: ZodTuple.create(items).rest(ZodUnknown.create()), }); } returns(returnType) { return new ZodFunction({ ...this._def, - returns: returnType + returns: returnType, }); } implement(func) { @@ -2371,7 +2492,7 @@ var ZodFunction = class extends ZodType { args: args ? args : ZodTuple.create([]).rest(ZodUnknown.create()), returns: returns || ZodUnknown.create(), typeName: ZodFirstPartyTypeKind.ZodFunction, - ...processCreateParams(params) + ...processCreateParams(params), }); } }; @@ -2389,7 +2510,7 @@ ZodLazy.create = (getter, params) => { return new ZodLazy({ getter, typeName: ZodFirstPartyTypeKind.ZodLazy, - ...processCreateParams(params) + ...processCreateParams(params), }); }; var ZodLiteral = class extends ZodType { @@ -2398,11 +2519,11 @@ var ZodLiteral = class extends ZodType { const ctx = this._getOrReturnCtx(input); addIssueToContext(ctx, { code: ZodIssueCode.invalid_literal, - expected: this._def.value + expected: this._def.value, }); return INVALID; } - return { status: "valid", value: input.data }; + return { status: 'valid', value: input.data }; } get value() { return this._def.value; @@ -2412,25 +2533,25 @@ ZodLiteral.create = (value, params) => { return new ZodLiteral({ value, typeName: ZodFirstPartyTypeKind.ZodLiteral, - ...processCreateParams(params) + ...processCreateParams(params), }); }; function createZodEnum(values, params) { return new ZodEnum({ values, typeName: ZodFirstPartyTypeKind.ZodEnum, - ...processCreateParams(params) + ...processCreateParams(params), }); } var ZodEnum = class extends ZodType { _parse(input) { - if (typeof input.data !== "string") { + if (typeof input.data !== 'string') { const ctx = this._getOrReturnCtx(input); const expectedValues = this._def.values; addIssueToContext(ctx, { expected: util.joinValues(expectedValues), received: ctx.parsedType, - code: ZodIssueCode.invalid_type + code: ZodIssueCode.invalid_type, }); return INVALID; } @@ -2440,7 +2561,7 @@ var ZodEnum = class extends ZodType { addIssueToContext(ctx, { received: ctx.data, code: ZodIssueCode.invalid_enum_value, - options: expectedValues + options: expectedValues, }); return INVALID; } @@ -2476,12 +2597,15 @@ var ZodNativeEnum = class extends ZodType { _parse(input) { const nativeEnumValues = util.getValidEnumValues(this._def.values); const ctx = this._getOrReturnCtx(input); - if (ctx.parsedType !== ZodParsedType.string && ctx.parsedType !== ZodParsedType.number) { + if ( + ctx.parsedType !== ZodParsedType.string && + ctx.parsedType !== ZodParsedType.number + ) { const expectedValues = util.objectValues(nativeEnumValues); addIssueToContext(ctx, { expected: util.joinValues(expectedValues), received: ctx.parsedType, - code: ZodIssueCode.invalid_type + code: ZodIssueCode.invalid_type, }); return INVALID; } @@ -2490,7 +2614,7 @@ var ZodNativeEnum = class extends ZodType { addIssueToContext(ctx, { received: ctx.data, code: ZodIssueCode.invalid_enum_value, - options: expectedValues + options: expectedValues, }); return INVALID; } @@ -2504,34 +2628,42 @@ ZodNativeEnum.create = (values, params) => { return new ZodNativeEnum({ values, typeName: ZodFirstPartyTypeKind.ZodNativeEnum, - ...processCreateParams(params) + ...processCreateParams(params), }); }; var ZodPromise = class extends ZodType { _parse(input) { const { ctx } = this._processInputParams(input); - if (ctx.parsedType !== ZodParsedType.promise && ctx.common.async === false) { + if ( + ctx.parsedType !== ZodParsedType.promise && + ctx.common.async === false + ) { addIssueToContext(ctx, { code: ZodIssueCode.invalid_type, expected: ZodParsedType.promise, - received: ctx.parsedType + received: ctx.parsedType, }); return INVALID; } - const promisified = ctx.parsedType === ZodParsedType.promise ? ctx.data : Promise.resolve(ctx.data); - return OK(promisified.then((data) => { - return this._def.type.parseAsync(data, { - path: ctx.path, - errorMap: ctx.common.contextualErrorMap - }); - })); + const promisified = + ctx.parsedType === ZodParsedType.promise + ? ctx.data + : Promise.resolve(ctx.data); + return OK( + promisified.then((data) => { + return this._def.type.parseAsync(data, { + path: ctx.path, + errorMap: ctx.common.contextualErrorMap, + }); + }) + ); } }; ZodPromise.create = (schema, params) => { return new ZodPromise({ type: schema, typeName: ZodFirstPartyTypeKind.ZodPromise, - ...processCreateParams(params) + ...processCreateParams(params), }); }; var ZodEffects = class extends ZodType { @@ -2541,21 +2673,21 @@ var ZodEffects = class extends ZodType { _parse(input) { const { status, ctx } = this._processInputParams(input); const effect = this._def.effect || null; - if (effect.type === "preprocess") { + if (effect.type === 'preprocess') { const processed = effect.transform(ctx.data); if (ctx.common.async) { return Promise.resolve(processed).then((processed2) => { return this._def.schema._parseAsync({ data: processed2, path: ctx.path, - parent: ctx + parent: ctx, }); }); } else { return this._def.schema._parseSync({ data: processed, path: ctx.path, - parent: ctx + parent: ctx, }); } } @@ -2570,17 +2702,19 @@ var ZodEffects = class extends ZodType { }, get path() { return ctx.path; - } + }, }; checkCtx.addIssue = checkCtx.addIssue.bind(checkCtx); - if (effect.type === "refinement") { + if (effect.type === 'refinement') { const executeRefinement = (acc) => { const result = effect.refinement(acc, checkCtx); if (ctx.common.async) { return Promise.resolve(result); } if (result instanceof Promise) { - throw new Error("Async refinement encountered during synchronous parse operation. Use .parseAsync instead."); + throw new Error( + 'Async refinement encountered during synchronous parse operation. Use .parseAsync instead.' + ); } return acc; }; @@ -2588,46 +2722,48 @@ var ZodEffects = class extends ZodType { const inner = this._def.schema._parseSync({ data: ctx.data, path: ctx.path, - parent: ctx + parent: ctx, }); - if (inner.status === "aborted") - return INVALID; - if (inner.status === "dirty") - status.dirty(); + if (inner.status === 'aborted') return INVALID; + if (inner.status === 'dirty') status.dirty(); executeRefinement(inner.value); return { status: status.value, value: inner.value }; } else { - return this._def.schema._parseAsync({ data: ctx.data, path: ctx.path, parent: ctx }).then((inner) => { - if (inner.status === "aborted") - return INVALID; - if (inner.status === "dirty") - status.dirty(); - return executeRefinement(inner.value).then(() => { - return { status: status.value, value: inner.value }; + return this._def.schema + ._parseAsync({ data: ctx.data, path: ctx.path, parent: ctx }) + .then((inner) => { + if (inner.status === 'aborted') return INVALID; + if (inner.status === 'dirty') status.dirty(); + return executeRefinement(inner.value).then(() => { + return { status: status.value, value: inner.value }; + }); }); - }); } } - if (effect.type === "transform") { + if (effect.type === 'transform') { if (ctx.common.async === false) { const base = this._def.schema._parseSync({ data: ctx.data, path: ctx.path, - parent: ctx + parent: ctx, }); - if (!isValid(base)) - return base; + if (!isValid(base)) return base; const result = effect.transform(base.value, checkCtx); if (result instanceof Promise) { - throw new Error(`Asynchronous transform encountered during synchronous parse operation. Use .parseAsync instead.`); + throw new Error( + `Asynchronous transform encountered during synchronous parse operation. Use .parseAsync instead.` + ); } return { status: status.value, value: result }; } else { - return this._def.schema._parseAsync({ data: ctx.data, path: ctx.path, parent: ctx }).then((base) => { - if (!isValid(base)) - return base; - return Promise.resolve(effect.transform(base.value, checkCtx)).then((result) => ({ status: status.value, value: result })); - }); + return this._def.schema + ._parseAsync({ data: ctx.data, path: ctx.path, parent: ctx }) + .then((base) => { + if (!isValid(base)) return base; + return Promise.resolve(effect.transform(base.value, checkCtx)).then( + (result) => ({ status: status.value, value: result }) + ); + }); } } util.assertNever(effect); @@ -2638,15 +2774,15 @@ ZodEffects.create = (schema, effect, params) => { schema, typeName: ZodFirstPartyTypeKind.ZodEffects, effect, - ...processCreateParams(params) + ...processCreateParams(params), }); }; ZodEffects.createWithPreprocess = (preprocess, schema, params) => { return new ZodEffects({ schema, - effect: { type: "preprocess", transform: preprocess }, + effect: { type: 'preprocess', transform: preprocess }, typeName: ZodFirstPartyTypeKind.ZodEffects, - ...processCreateParams(params) + ...processCreateParams(params), }); }; var ZodOptional = class extends ZodType { @@ -2665,7 +2801,7 @@ ZodOptional.create = (type, params) => { return new ZodOptional({ innerType: type, typeName: ZodFirstPartyTypeKind.ZodOptional, - ...processCreateParams(params) + ...processCreateParams(params), }); }; var ZodNullable = class extends ZodType { @@ -2684,7 +2820,7 @@ ZodNullable.create = (type, params) => { return new ZodNullable({ innerType: type, typeName: ZodFirstPartyTypeKind.ZodNullable, - ...processCreateParams(params) + ...processCreateParams(params), }); }; var ZodDefault = class extends ZodType { @@ -2697,7 +2833,7 @@ var ZodDefault = class extends ZodType { return this._def.innerType._parse({ data, path: ctx.path, - parent: ctx + parent: ctx, }); } removeDefault() { @@ -2708,7 +2844,7 @@ ZodDefault.create = (type, params) => { return new ZodOptional({ innerType: type, typeName: ZodFirstPartyTypeKind.ZodOptional, - ...processCreateParams(params) + ...processCreateParams(params), }); }; var ZodNaN = class extends ZodType { @@ -2719,20 +2855,20 @@ var ZodNaN = class extends ZodType { addIssueToContext(ctx, { code: ZodIssueCode.invalid_type, expected: ZodParsedType.nan, - received: ctx.parsedType + received: ctx.parsedType, }); return INVALID; } - return { status: "valid", value: input.data }; + return { status: 'valid', value: input.data }; } }; ZodNaN.create = (params) => { return new ZodNaN({ typeName: ZodFirstPartyTypeKind.ZodNaN, - ...processCreateParams(params) + ...processCreateParams(params), }); }; -var BRAND = Symbol("zod_brand"); +var BRAND = Symbol('zod_brand'); var ZodBranded = class extends ZodType { _parse(input) { const { ctx } = this._processInputParams(input); @@ -2740,7 +2876,7 @@ var ZodBranded = class extends ZodType { return this._def.type._parse({ data, path: ctx.path, - parent: ctx + parent: ctx, }); } unwrap() { @@ -2751,54 +2887,57 @@ var custom = (check, params = {}, fatal) => { if (check) return ZodAny.create().superRefine((data, ctx) => { if (!check(data)) { - const p = typeof params === "function" ? params(data) : params; - const p2 = typeof p === "string" ? { message: p } : p; - ctx.addIssue({ code: "custom", ...p2, fatal }); + const p = typeof params === 'function' ? params(data) : params; + const p2 = typeof p === 'string' ? { message: p } : p; + ctx.addIssue({ code: 'custom', ...p2, fatal }); } }); return ZodAny.create(); }; var late = { - object: ZodObject.lazycreate + object: ZodObject.lazycreate, }; var ZodFirstPartyTypeKind; -(function(ZodFirstPartyTypeKind2) { - ZodFirstPartyTypeKind2["ZodString"] = "ZodString"; - ZodFirstPartyTypeKind2["ZodNumber"] = "ZodNumber"; - ZodFirstPartyTypeKind2["ZodNaN"] = "ZodNaN"; - ZodFirstPartyTypeKind2["ZodBigInt"] = "ZodBigInt"; - ZodFirstPartyTypeKind2["ZodBoolean"] = "ZodBoolean"; - ZodFirstPartyTypeKind2["ZodDate"] = "ZodDate"; - ZodFirstPartyTypeKind2["ZodUndefined"] = "ZodUndefined"; - ZodFirstPartyTypeKind2["ZodNull"] = "ZodNull"; - ZodFirstPartyTypeKind2["ZodAny"] = "ZodAny"; - ZodFirstPartyTypeKind2["ZodUnknown"] = "ZodUnknown"; - ZodFirstPartyTypeKind2["ZodNever"] = "ZodNever"; - ZodFirstPartyTypeKind2["ZodVoid"] = "ZodVoid"; - ZodFirstPartyTypeKind2["ZodArray"] = "ZodArray"; - ZodFirstPartyTypeKind2["ZodObject"] = "ZodObject"; - ZodFirstPartyTypeKind2["ZodUnion"] = "ZodUnion"; - ZodFirstPartyTypeKind2["ZodDiscriminatedUnion"] = "ZodDiscriminatedUnion"; - ZodFirstPartyTypeKind2["ZodIntersection"] = "ZodIntersection"; - ZodFirstPartyTypeKind2["ZodTuple"] = "ZodTuple"; - ZodFirstPartyTypeKind2["ZodRecord"] = "ZodRecord"; - ZodFirstPartyTypeKind2["ZodMap"] = "ZodMap"; - ZodFirstPartyTypeKind2["ZodSet"] = "ZodSet"; - ZodFirstPartyTypeKind2["ZodFunction"] = "ZodFunction"; - ZodFirstPartyTypeKind2["ZodLazy"] = "ZodLazy"; - ZodFirstPartyTypeKind2["ZodLiteral"] = "ZodLiteral"; - ZodFirstPartyTypeKind2["ZodEnum"] = "ZodEnum"; - ZodFirstPartyTypeKind2["ZodEffects"] = "ZodEffects"; - ZodFirstPartyTypeKind2["ZodNativeEnum"] = "ZodNativeEnum"; - ZodFirstPartyTypeKind2["ZodOptional"] = "ZodOptional"; - ZodFirstPartyTypeKind2["ZodNullable"] = "ZodNullable"; - ZodFirstPartyTypeKind2["ZodDefault"] = "ZodDefault"; - ZodFirstPartyTypeKind2["ZodPromise"] = "ZodPromise"; - ZodFirstPartyTypeKind2["ZodBranded"] = "ZodBranded"; +(function (ZodFirstPartyTypeKind2) { + ZodFirstPartyTypeKind2['ZodString'] = 'ZodString'; + ZodFirstPartyTypeKind2['ZodNumber'] = 'ZodNumber'; + ZodFirstPartyTypeKind2['ZodNaN'] = 'ZodNaN'; + ZodFirstPartyTypeKind2['ZodBigInt'] = 'ZodBigInt'; + ZodFirstPartyTypeKind2['ZodBoolean'] = 'ZodBoolean'; + ZodFirstPartyTypeKind2['ZodDate'] = 'ZodDate'; + ZodFirstPartyTypeKind2['ZodUndefined'] = 'ZodUndefined'; + ZodFirstPartyTypeKind2['ZodNull'] = 'ZodNull'; + ZodFirstPartyTypeKind2['ZodAny'] = 'ZodAny'; + ZodFirstPartyTypeKind2['ZodUnknown'] = 'ZodUnknown'; + ZodFirstPartyTypeKind2['ZodNever'] = 'ZodNever'; + ZodFirstPartyTypeKind2['ZodVoid'] = 'ZodVoid'; + ZodFirstPartyTypeKind2['ZodArray'] = 'ZodArray'; + ZodFirstPartyTypeKind2['ZodObject'] = 'ZodObject'; + ZodFirstPartyTypeKind2['ZodUnion'] = 'ZodUnion'; + ZodFirstPartyTypeKind2['ZodDiscriminatedUnion'] = 'ZodDiscriminatedUnion'; + ZodFirstPartyTypeKind2['ZodIntersection'] = 'ZodIntersection'; + ZodFirstPartyTypeKind2['ZodTuple'] = 'ZodTuple'; + ZodFirstPartyTypeKind2['ZodRecord'] = 'ZodRecord'; + ZodFirstPartyTypeKind2['ZodMap'] = 'ZodMap'; + ZodFirstPartyTypeKind2['ZodSet'] = 'ZodSet'; + ZodFirstPartyTypeKind2['ZodFunction'] = 'ZodFunction'; + ZodFirstPartyTypeKind2['ZodLazy'] = 'ZodLazy'; + ZodFirstPartyTypeKind2['ZodLiteral'] = 'ZodLiteral'; + ZodFirstPartyTypeKind2['ZodEnum'] = 'ZodEnum'; + ZodFirstPartyTypeKind2['ZodEffects'] = 'ZodEffects'; + ZodFirstPartyTypeKind2['ZodNativeEnum'] = 'ZodNativeEnum'; + ZodFirstPartyTypeKind2['ZodOptional'] = 'ZodOptional'; + ZodFirstPartyTypeKind2['ZodNullable'] = 'ZodNullable'; + ZodFirstPartyTypeKind2['ZodDefault'] = 'ZodDefault'; + ZodFirstPartyTypeKind2['ZodPromise'] = 'ZodPromise'; + ZodFirstPartyTypeKind2['ZodBranded'] = 'ZodBranded'; })(ZodFirstPartyTypeKind || (ZodFirstPartyTypeKind = {})); -var instanceOfType = (cls, params = { - message: `Input not instance of ${cls.name}` -}) => custom((data) => data instanceof cls, params, true); +var instanceOfType = ( + cls, + params = { + message: `Input not instance of ${cls.name}`, + } +) => custom((data) => data instanceof cls, params, true); var stringType = ZodString.create; var numberType = ZodNumber.create; var nanType = ZodNaN.create; @@ -2905,9 +3044,9 @@ var mod = /* @__PURE__ */ Object.freeze({ date: dateType, discriminatedUnion: discriminatedUnionType, effect: effectsType, - "enum": enumType, - "function": functionType, - "instanceof": instanceOfType, + enum: enumType, + function: functionType, + instanceof: instanceOfType, intersection: intersectionType, lazy: lazyType, literal: literalType, @@ -2915,7 +3054,7 @@ var mod = /* @__PURE__ */ Object.freeze({ nan: nanType, nativeEnum: nativeEnumType, never: neverType, - "null": nullType, + null: nullType, nullable: nullableType, number: numberType, object: objectType, @@ -2931,41 +3070,39 @@ var mod = /* @__PURE__ */ Object.freeze({ string: stringType, transformer: effectsType, tuple: tupleType, - "undefined": undefinedType, + undefined: undefinedType, union: unionType, unknown: unknownType, - "void": voidType, + void: voidType, NEVER, ZodIssueCode, quotelessJson, - ZodError + ZodError, }); // src/lib/get-text-schema.ts var getTextSchema = mod.object({ - text: mod.string().min(2).max(255) + text: mod.string().min(2).max(255), }); // src/index.ts var resolver = new Resolver(); -resolver.define("getText" /* getText */, async (req) => { - console.log("called getText()"); - await requireAccess({ req }); +resolver.define('getText' /* getText */, async (req) => { + console.log('called getText()'); + //await requireAccess({ req }); const accountId = requireAccountId(req); const payload = getTextSchema.parse(req.payload); - console.log("accessed getText()"); + console.log('accessed getText()'); return getText({ ...payload, accountId }); }); async function requireAccess({ req }) { const isAdmin = await isJiraGlobalAdmin(); if (!isAdmin) { - throw new Error("not permitted"); + throw new Error('not permitted'); } } function requireAccountId(req) { return mod.string().parse(req.context.accountId); } var handler = resolver.getDefinitions(); -export { - handler -}; +export { handler }; From 214266c6d704869efff281a3b657b726e0b5c516 Mon Sep 17 00:00:00 2001 From: Joshua Wong Date: Sun, 18 Dec 2022 11:34:30 -0600 Subject: [PATCH 027/517] chore: bump deps and add time crate --- Cargo.lock | 199 ++++++++++++++++++------------- Cargo.toml | 7 +- crates/forge_analyzer/Cargo.toml | 1 + 3 files changed, 120 insertions(+), 87 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 3833bee1..2d6114ec 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -159,9 +159,9 @@ dependencies = [ [[package]] name = "browserslist-rs" -version = "0.11.0" +version = "0.12.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c689fb4e42bd511c1927856b078d8a582690f5be196199d1c9005b9d4feae8c" +checksum = "421478dde88feb4281328dea29dbf6d2b57bc19a8968214fc3694c8c574bc47f" dependencies = [ "ahash", "anyhow", @@ -216,9 +216,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.0.27" +version = "4.0.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0acbd8d28a0a60d7108d7ae850af6ba34cf2d1257fc646980e5f97ce14275966" +checksum = "4d63b9e9c07271b9957ad22c173bae2a4d9a81127680962039296abcd2f8251d" dependencies = [ "bitflags", "clap_derive", @@ -538,6 +538,7 @@ dependencies = [ "serde_yaml", "smallvec", "swc_core", + "time 0.3.17", "tracing", "tracing-subscriber", "typed-arena", @@ -553,6 +554,7 @@ name = "forge_loader" version = "0.1.0" dependencies = [ "clap", + "forge_utils", "itertools", "miette 5.5.0", "pretty_assertions", @@ -748,9 +750,9 @@ checksum = "59ce5ef949d49ee85593fc4d3f3f95ad61657076395cbbce23e2121fc5542074" [[package]] name = "io-lifetimes" -version = "1.0.1" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7d367024b3f3414d8e01f437f704f41a9f64ab36f9067fa73e526ad4c763c87" +checksum = "46112a93252b123d31a119a8d1a1ac19deac4fac6e0e8b0df58f0d4e5870e63c" dependencies = [ "libc", "windows-sys", @@ -771,13 +773,13 @@ dependencies = [ [[package]] name = "is-terminal" -version = "0.4.0" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aae5bc6e2eb41c9def29a3e0f1306382807764b9b53112030eff57435667352d" +checksum = "927609f78c2913a6f6ac3c27a4fe87f43e2a35367c0c4b0f8265e8f49a104330" dependencies = [ "hermit-abi 0.2.6", - "io-lifetimes 1.0.1", - "rustix 0.36.3", + "io-lifetimes 1.0.3", + "rustix 0.36.5", "windows-sys", ] @@ -901,9 +903,9 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.137" +version = "0.2.138" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc7fcc620a3bff7cdd7a365be3376c97191aeaccc2a603e600951e452615bf89" +checksum = "db6d7e329c562c5dfab7a46a2afabc8b987ab9a4834c9d1ca04dc54c1546cef8" [[package]] name = "link-cplusplus" @@ -922,9 +924,9 @@ checksum = "d4d2456c373231a208ad294c33dc5bff30051eafd954cd4caae83a712b12854d" [[package]] name = "linux-raw-sys" -version = "0.1.3" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f9f08d8963a6c613f4b1a78f4f4a4dbfadf8e6545b2d72861731e4858b8b47f" +checksum = "f051f77a7c8e6957c0696eac88f26b0117e54f52d3fc682ab19397a8812846a4" [[package]] name = "lock_api" @@ -1131,6 +1133,15 @@ dependencies = [ "libc", ] +[[package]] +name = "num_threads" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2819ce041d2ee131036f4fc9d6ae7ae125a3a40e97ba64d04fe799ad9dabbb44" +dependencies = [ + "libc", +] + [[package]] name = "object" version = "0.29.0" @@ -1299,9 +1310,9 @@ checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c" [[package]] name = "preset_env_base" -version = "0.3.2" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "371fa3d5cd3a90724d8e8ad1e3201854dded11e79b5365dabd5e1e389274d001" +checksum = "97cc85a18e7f8246f3ccdd764d1f51fa3c910293942f84483a1cf1647df47198" dependencies = [ "ahash", "anyhow", @@ -1512,15 +1523,15 @@ dependencies = [ [[package]] name = "rustix" -version = "0.36.3" +version = "0.36.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b1fbb4dfc4eb1d390c02df47760bb19a84bb80b301ecc947ab5406394d8223e" +checksum = "a3807b5d10909833d3e9acd1eb5fb988f79376ff10fce42937de71a449c4c588" dependencies = [ "bitflags", "errno", - "io-lifetimes 1.0.1", + "io-lifetimes 1.0.3", "libc", - "linux-raw-sys 0.1.3", + "linux-raw-sys 0.1.4", "windows-sys", ] @@ -1589,9 +1600,9 @@ checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" [[package]] name = "serde" -version = "1.0.148" +version = "1.0.150" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e53f64bb4ba0191d6d0676e1b141ca55047d83b74f5607e6d8eb88126c52c2dc" +checksum = "e326c9ec8042f1b5da33252c8a37e9ffbd2c9bef0155215b6e6c80c790e05f91" dependencies = [ "serde_derive", ] @@ -1609,9 +1620,9 @@ dependencies = [ [[package]] name = "serde_derive" -version = "1.0.148" +version = "1.0.150" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a55492425aa53521babf6137309e7d34c20bbfbbfcfe2c7f3a047fd1f6b92c0c" +checksum = "42a3df25b0713732468deadad63ab9da1f1fd75a48a15024b50363f128db627e" dependencies = [ "proc-macro2", "quote", @@ -1805,9 +1816,9 @@ dependencies = [ [[package]] name = "swc" -version = "0.232.113" +version = "0.236.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2673b6c50a317d1ee557d0a6fd93870a0b41bbcd809388f9daa0be882830bfd8" +checksum = "c0d79b5e643f546d1aefb9d8a93963d5046671e7aef2cca6946bfa4b33bb2e91" dependencies = [ "ahash", "anyhow", @@ -1848,13 +1859,14 @@ dependencies = [ "swc_timer", "swc_visit", "tracing", + "url", ] [[package]] name = "swc_atoms" -version = "0.4.25" +version = "0.4.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "63b8033a868fbebf5829797ac0c543499622b657e2d33a08ca6ab12547b8bafc" +checksum = "cef7796df1985447f1fb8803ca2a00b445b20abbc65c8e73acb08835d7651ff0" dependencies = [ "once_cell", "rustc-hash", @@ -1880,9 +1892,9 @@ dependencies = [ [[package]] name = "swc_common" -version = "0.29.16" +version = "0.29.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "876ef0da27185c6b263ad6353a0314f10a3c077f9197d3e3dd71f8e07d2f0fae" +checksum = "811faf77280a5f43fedf06769c391d4f2ed274b1ce9267e3a47e9b13527930b7" dependencies = [ "ahash", "ast_node", @@ -1934,9 +1946,9 @@ dependencies = [ [[package]] name = "swc_core" -version = "0.43.33" +version = "0.48.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3cd78577b4cbfa2f43f65f5ab720248b771d4107f10d474ac8bd838cd506b39" +checksum = "bb4a88e87e9b82c1799b56e5985973af841f02fcbc47316d91f0468b4c97d742" dependencies = [ "swc", "swc_atoms", @@ -1953,9 +1965,9 @@ dependencies = [ [[package]] name = "swc_ecma_ast" -version = "0.94.21" +version = "0.95.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7823e863bebbcbcabba5d5781dd8cedcf1acb35a770a871c914855b43783e17" +checksum = "724a26e6f2c9fdbeee174ebfd9941c52ed13169b59afa2b056d45a731af10dc1" dependencies = [ "bitflags", "is-macro", @@ -1970,9 +1982,9 @@ dependencies = [ [[package]] name = "swc_ecma_codegen" -version = "0.127.38" +version = "0.128.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f6fb2b7f7f0017d80b7b5ae025c7070a0845b774e6543f79415b8c398f45af7" +checksum = "e4efb3e85c0c8ff5ef8164397f571afb6b7ccd40d29191fa6fe1deef9503db3a" dependencies = [ "memchr", "num-bigint", @@ -2002,9 +2014,9 @@ dependencies = [ [[package]] name = "swc_ecma_ext_transforms" -version = "0.91.39" +version = "0.92.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "66e6bf70ae6092e31212325bb1228d7cb57be283074c5ce5cb9bc4f01053db25" +checksum = "952c2023394e4585751f829874e3f90c1ed8378c66a52dafd76da50cc5cc6439" dependencies = [ "phf", "swc_atoms", @@ -2016,9 +2028,9 @@ dependencies = [ [[package]] name = "swc_ecma_lints" -version = "0.66.59" +version = "0.67.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d5d40f1f5c378a359f16126a8448c422110d294f5c18790b4497bf3d47c631c" +checksum = "8825e741afd2267bb1190629b312362098532ad4e0b07cb3895567318fe14f07" dependencies = [ "ahash", "auto_impl", @@ -2037,9 +2049,9 @@ dependencies = [ [[package]] name = "swc_ecma_loader" -version = "0.41.17" +version = "0.41.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d1bd9051cbd8d792f7bed99f1dbe4ad550bcf885cce2359f298bde8508a24c31" +checksum = "c1256d24d66594cd11f5e7ed12fde994b23c6fadc5df5f05a1a18b28c5fc7239" dependencies = [ "ahash", "anyhow", @@ -2059,9 +2071,9 @@ dependencies = [ [[package]] name = "swc_ecma_minifier" -version = "0.159.99" +version = "0.160.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d136af52a9d97ff4e2a92f02644b04238e686c25e1d42891acacc8339c15d3d1" +checksum = "02b877c297c47c79fa134027e4766827659c059613dad23cd11319979adce149" dependencies = [ "ahash", "arrayvec", @@ -2085,6 +2097,7 @@ dependencies = [ "swc_ecma_parser", "swc_ecma_transforms_base", "swc_ecma_transforms_optimization", + "swc_ecma_usage_analyzer", "swc_ecma_utils", "swc_ecma_visit", "swc_timer", @@ -2093,9 +2106,9 @@ dependencies = [ [[package]] name = "swc_ecma_parser" -version = "0.122.31" +version = "0.123.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2676e6a964ea14e900024db687aabe9c2162a756338e3ba9d331e2ef44534f4" +checksum = "da6b8e8a20fedc5fb981a78e2e4b2654b90bc6e080e0339e8bc9f8341ee11ccb" dependencies = [ "either", "enum_kind", @@ -2112,9 +2125,9 @@ dependencies = [ [[package]] name = "swc_ecma_preset_env" -version = "0.174.63" +version = "0.175.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55847ec7ea485d2fbc80863a4073c2f91401637a0eba057106726b7a84760fc3" +checksum = "6bb75c62a6c6dabc5a7fd3dee32a4a66953b2b8209d2ed3891d30424b2e97129" dependencies = [ "ahash", "anyhow", @@ -2137,9 +2150,9 @@ dependencies = [ [[package]] name = "swc_ecma_transforms" -version = "0.198.63" +version = "0.199.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "912527d1d2af88f3c3a8010a196890930ee9dc2295eeeb0f61ee885fdac23591" +checksum = "11af4ad5fc187308312757dec19dce74f81c3562c43e6d236882f1d985d023c9" dependencies = [ "swc_atoms", "swc_common", @@ -2157,9 +2170,9 @@ dependencies = [ [[package]] name = "swc_ecma_transforms_base" -version = "0.111.58" +version = "0.112.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c0ad15396b65635844021483cb6c83e0a8a253e959ec2f546eeddc6faee4fab" +checksum = "7d6b38b6df37d25c4dd1faa4fdf9149eb19dc493e43deacd79ac8834d5afbe65" dependencies = [ "better_scoped_tls", "bitflags", @@ -2179,9 +2192,9 @@ dependencies = [ [[package]] name = "swc_ecma_transforms_classes" -version = "0.100.57" +version = "0.101.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4b1d890c82fd59cb2cfcc0ca501563103fd77ed6e04349b65432534d018b970" +checksum = "4f601b41a3d1482a03c2c560e79cef0f496adb8a9ff3eb1157511df163df4036" dependencies = [ "swc_atoms", "swc_common", @@ -2193,9 +2206,9 @@ dependencies = [ [[package]] name = "swc_ecma_transforms_compat" -version = "0.136.51" +version = "0.137.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6a90bf33231b48cfc42690c6131374f422fdb89e4dd347742a90e55971bed69" +checksum = "2fcfc1420c092fb45760dd615b1f3e29499371f8188b3d88dbf85ac35b5020c9" dependencies = [ "ahash", "arrayvec", @@ -2232,9 +2245,9 @@ dependencies = [ [[package]] name = "swc_ecma_transforms_module" -version = "0.153.55" +version = "0.154.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f27675cfa7c9bd4a679bc02045125a23269f81ded0549bb65806dc6e53d0eee4" +checksum = "1ac2f2869576ae4859294f07cc26809a027345359b5ecd9c71eef0114a51ec96" dependencies = [ "Inflector", "ahash", @@ -2260,9 +2273,9 @@ dependencies = [ [[package]] name = "swc_ecma_transforms_optimization" -version = "0.167.63" +version = "0.168.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f67c3adf40cd1a5f85108759181a36cdb0e4b5422279ace33dd4f4e49e9c4cf1" +checksum = "5cc33b6d03f18066730d07615ef7317fd12ae72c814792978612dab20dc6054d" dependencies = [ "ahash", "dashmap", @@ -2285,9 +2298,9 @@ dependencies = [ [[package]] name = "swc_ecma_transforms_proposal" -version = "0.144.51" +version = "0.145.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "71f12817cfcb25433aed1f02194dd6bb6a49c91528eddca3039e88af87da086c" +checksum = "44f1f8ca548616af6a2081dfe699202f5abadcc0045fc85f219ba83a5b960844" dependencies = [ "either", "serde", @@ -2304,9 +2317,9 @@ dependencies = [ [[package]] name = "swc_ecma_transforms_react" -version = "0.155.56" +version = "0.156.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74e008af280dd3f9dc5580c3219619fe9dba46e3373db00787ba40d60f93eee5" +checksum = "54746247c87a02c2c4006ec21502cfa9e9d72dfa897b1603a3c1b460ee2f90bf" dependencies = [ "ahash", "base64", @@ -2330,9 +2343,9 @@ dependencies = [ [[package]] name = "swc_ecma_transforms_typescript" -version = "0.159.58" +version = "0.160.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55d13d1239328e3a66f243e2d51d039af704bb890d1bee059623a2dec02c147a" +checksum = "964aa70be0218e8d3a89b8600818f9ba798884322b19094dfdb35a684f2728c6" dependencies = [ "serde", "swc_atoms", @@ -2344,11 +2357,29 @@ dependencies = [ "swc_ecma_visit", ] +[[package]] +name = "swc_ecma_usage_analyzer" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "54e7e7291e380c3030a1340d14bdbf99da915e4b789b17e13f06a63cbd9d7646" +dependencies = [ + "ahash", + "indexmap", + "rustc-hash", + "swc_atoms", + "swc_common", + "swc_ecma_ast", + "swc_ecma_utils", + "swc_ecma_visit", + "swc_timer", + "tracing", +] + [[package]] name = "swc_ecma_utils" -version = "0.105.39" +version = "0.106.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3972d422059ab3009a2ed78be32d86abb6ebf9d2a1025de0fd9c6743e4b8afaa" +checksum = "a0c47371c25d2cb110d71e351376be57a718d3a64710ac79b3169ab24165c54f" dependencies = [ "indexmap", "num_cpus", @@ -2363,9 +2394,9 @@ dependencies = [ [[package]] name = "swc_ecma_visit" -version = "0.80.21" +version = "0.81.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07c3c5ca0a76f58b8b40e3733e4c67c47a6875c318e40ad11d535a26e44609b6" +checksum = "6fa0f5e65af0764045ef268c19d8e0f7fed27ff95c69c198427dcfd5b10685a8" dependencies = [ "num-bigint", "swc_atoms", @@ -2389,9 +2420,9 @@ dependencies = [ [[package]] name = "swc_error_reporters" -version = "0.13.17" +version = "0.13.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7f7b896139b023c3b07522f5c2264ab433941d7277cc979acd331b1d472932c" +checksum = "035712357b19deefa23cec538f5cca48e6a799b1f37f6bf7cfbf1328f035c030" dependencies = [ "anyhow", "miette 4.7.1", @@ -2402,9 +2433,9 @@ dependencies = [ [[package]] name = "swc_fast_graph" -version = "0.17.17" +version = "0.17.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dcb71b75f0b74f3d00500930d3cb0a43729abf3f5799fe2d90b2815738e0935f" +checksum = "e69603ddc8607e2ea0b8482f868c6b0f30269e790c6cea227b9ae288a35f7d04" dependencies = [ "ahash", "indexmap", @@ -2426,9 +2457,9 @@ dependencies = [ [[package]] name = "swc_node_comments" -version = "0.16.16" +version = "0.16.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b14ee59e2f0adcaa7dc3e4e1e97bac26e03b31700be82eb9b4b9be469a38c54" +checksum = "ba3fddb25502adcfe78b4802ae3704f3d32f38412be55961034ab7361e73d943" dependencies = [ "ahash", "dashmap", @@ -2438,9 +2469,9 @@ dependencies = [ [[package]] name = "swc_timer" -version = "0.17.17" +version = "0.17.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f191978a63b86e2b0e93283efb1fe1a44d49a1fd1bdb9c2c8911f40215dbf172" +checksum = "cec4226d8a7a27aef59a5694912f01dcc90dd2c688d07aa00a118a13eb2ea74e" dependencies = [ "tracing", ] @@ -2482,9 +2513,9 @@ dependencies = [ [[package]] name = "syn" -version = "1.0.104" +version = "1.0.105" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ae548ec36cf198c0ef7710d3c230987c2d6d7bd98ad6edc0274462724c585ce" +checksum = "60b9b43d45702de4c839cb9b51d9f529c5dd26a4aff255b42b1ebc03e88ee908" dependencies = [ "proc-macro2", "quote", @@ -2578,6 +2609,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a561bf4617eebd33bca6434b988f39ed798e527f51a1e797d0ee4f61c0a38376" dependencies = [ "itoa", + "libc", + "num_threads", "serde", "time-core", "time-macros", @@ -2712,9 +2745,9 @@ checksum = "183496e014253d15abbe6235677b1392dba2d40524c88938991226baa38ac7c4" [[package]] name = "typenum" -version = "1.15.0" +version = "1.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dcf81ac59edc17cc8697ff311e8f5ef2d99fcbd9817b34cec66f90b6c3dfd987" +checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba" [[package]] name = "unicode-bidi" @@ -2833,8 +2866,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eaf9f5aceeec8be17c128b2e93e031fb8a4d469bb9c4ae2d7dc1888b26887268" dependencies = [ "cfg-if", - "serde", - "serde_json", "wasm-bindgen-macro", ] diff --git a/Cargo.toml b/Cargo.toml index ac4165ec..9b58cc6d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,12 +8,12 @@ edition = "2021" license = "MIT OR Apache-2.0" [workspace.dependencies] -clap = { version = "4.0.27", features = ["derive", "wrap_help"] } +clap = { version = "4.0.29", features = ["derive", "wrap_help"] } fixedbitset = "0.4.2" itertools = "0.10.5" miette = { version = "5.5.0", features = ["fancy"] } num-bigint = { version = "0.4.3" } -serde = { version = "1.0.148", features = ["derive"] } +serde = { version = "1.0.150", features = ["derive"] } serde_json = "1.0.89" serde_yaml = "0.9.14" petgraph = "0.6.2" @@ -23,8 +23,9 @@ once_cell = "1.16.0" regex = "1.7.0" rustc-hash = "1.1.0" smallvec = { version = "1.10.0", features = ["union", "const_new"] } -swc_core = { version = "0.43.33", features = ["common_perf", "common", "common_sourcemap", "ecma_visit_path", "ecma_utils", "ecma_ast", "swc", "ecma_visit", "ecma_transforms", "ecma_transforms_module", "ecma_transforms_typescript", "ecma_parser_typescript"] } +swc_core = { version = "0.48.24", features = ["common_perf", "common", "common_sourcemap", "ecma_visit_path", "ecma_utils", "ecma_ast", "swc", "ecma_visit", "ecma_transforms", "ecma_transforms_module", "ecma_transforms_typescript", "ecma_parser_typescript"] } thiserror = "1.0.37" +time = { version = "0.3.17", features = ["local-offset", "serde-human-readable"] } tracing = "0.1.37" tracing-subscriber = { version = "0.3.16", features = ["env-filter"] } tracing-tree = "0.2.2" diff --git a/crates/forge_analyzer/Cargo.toml b/crates/forge_analyzer/Cargo.toml index 3260c535..b9b83003 100644 --- a/crates/forge_analyzer/Cargo.toml +++ b/crates/forge_analyzer/Cargo.toml @@ -22,6 +22,7 @@ serde.workspace = true serde_yaml.workspace = true smallvec.workspace = true swc_core.workspace = true +time.workspace = true tracing-subscriber.workspace = true tracing.workspace = true typed-arena.workspace = true From 0e74edbc11e4fd9001bff204e030df7b897b2314 Mon Sep 17 00:00:00 2001 From: Joshua Wong Date: Sun, 18 Dec 2022 11:35:08 -0600 Subject: [PATCH 028/517] feat: migrate to IR v2 --- crates/forge_analyzer/src/checkers.rs | 175 ++++++ crates/forge_analyzer/src/definitions.rs | 121 +++- crates/forge_analyzer/src/interp.rs | 500 ++++++++++++++-- crates/forge_analyzer/src/ir.rs | 552 ++++++++++++++---- crates/forge_analyzer/src/lib.rs | 4 + crates/forge_analyzer/src/pretty.rs | 32 + crates/forge_analyzer/src/reporter.rs | 109 ++++ crates/forge_analyzer/src/worklist.rs | 101 ++++ crates/forge_loader/Cargo.toml | 1 + crates/forge_loader/src/manifest.rs | 3 + crates/fsrt/src/main.rs | 83 +-- .../issue-1-resolver-with-vuln/manifest.yml | 20 + .../issue-1-resolver-with-vuln/src/index.js | 47 ++ 13 files changed, 1519 insertions(+), 229 deletions(-) create mode 100644 crates/forge_analyzer/src/checkers.rs create mode 100644 crates/forge_analyzer/src/pretty.rs create mode 100644 crates/forge_analyzer/src/reporter.rs create mode 100644 crates/forge_analyzer/src/worklist.rs create mode 100644 test-apps/issue-1-resolver-with-vuln/manifest.yml create mode 100644 test-apps/issue-1-resolver-with-vuln/src/index.js diff --git a/crates/forge_analyzer/src/checkers.rs b/crates/forge_analyzer/src/checkers.rs new file mode 100644 index 00000000..acdd9e6b --- /dev/null +++ b/crates/forge_analyzer/src/checkers.rs @@ -0,0 +1,175 @@ +use core::fmt; +use std::{cmp::max, mem, ops::ControlFlow}; + +use tracing::{debug, info}; + +use crate::{ + definitions::DefId, + interp::{Checker, Dataflow, Frame, Interp, JoinSemiLattice, WithCallStack}, + ir::{BasicBlock, BasicBlockId, Intrinsic, Location}, + worklist::WorkList, +}; + +pub struct AuthorizeDataflow { + needs_call: Vec, +} + +#[derive(Debug, PartialEq, Eq, Clone, Copy, PartialOrd, Ord)] +pub enum AuthorizeState { + No, + Yes, +} + +impl JoinSemiLattice for AuthorizeState { + const BOTTOM: Self = Self::No; + + #[inline] + fn join_changed(&mut self, other: &Self) -> bool { + let old = mem::replace(self, max(*other, *self)); + old == *self + } + + #[inline] + fn join(&self, other: &Self) -> Self { + max(*other, *self) + } +} + +impl<'cx> Dataflow<'cx> for AuthorizeDataflow { + type State = AuthorizeState; + + fn with_interp>( + interp: &Interp<'cx, C>, + ) -> Self { + Self { needs_call: vec![] } + } + + fn transfer_intrinsic>( + &mut self, + _interp: &Interp<'cx, C>, + _def: DefId, + _loc: Location, + _block: &'cx BasicBlock, + intrinsic: &'cx Intrinsic, + initial_state: Self::State, + ) -> Self::State { + match *intrinsic { + Intrinsic::Authorize => { + debug!("authorize intrinsic found"); + AuthorizeState::Yes + } + Intrinsic::Fetch => initial_state, + Intrinsic::ApiCall => initial_state, + Intrinsic::SafeCall => initial_state, + Intrinsic::EnvRead => initial_state, + Intrinsic::StorageRead => initial_state, + } + } + + fn transfer_call>( + &mut self, + interp: &Interp<'cx, C>, + def: DefId, + loc: Location, + block: &'cx BasicBlock, + callee: &'cx crate::ir::Operand, + initial_state: Self::State, + ) -> Self::State { + let Some((callee_def, body)) = self.resolve_call(interp, callee) else { + return initial_state; + }; + match interp.func_state(callee_def) { + Some(state) => { + if state == AuthorizeState::Yes { + debug!("Found call to authorize at {def:?} {loc:?}"); + } + initial_state.join(&state) + } + None => { + let callee_name = interp.env().def_name(callee_def); + let caller_name = interp.env().def_name(def); + debug!("Found call to {callee_name} at {def:?} {caller_name}"); + self.needs_call.push(callee_def); + initial_state + } + } + } + + fn join_term>( + &mut self, + interp: &Interp<'cx, C>, + def: DefId, + block: &'cx BasicBlock, + state: Self::State, + worklist: &mut WorkList, + ) { + self.super_join_term(interp, def, block, state, worklist); + for def in self.needs_call.drain(..) { + worklist.push_front_blocks(interp.env(), def); + } + } +} + +pub struct AuthZChecker { + vulns: Vec, +} + +impl AuthZChecker { + pub fn new() -> Self { + Self { vulns: vec![] } + } +} + +impl Default for AuthZChecker { + fn default() -> Self { + Self::new() + } +} + +#[derive(Debug)] +pub struct AuthZVuln { + stackframe: Vec, +} + +impl fmt::Display for AuthZVuln { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "Authorization vulnerability") + } +} + +impl WithCallStack for AuthZVuln { + fn add_call_stack(&mut self, stack: Vec) {} +} + +impl<'cx> Checker<'cx> for AuthZChecker { + type State = AuthorizeState; + type Dataflow = AuthorizeDataflow; + type Vuln = AuthZVuln; + + fn visit_intrinsic( + &mut self, + interp: &Interp<'cx, Self>, + intrinsic: &'cx Intrinsic, + state: &Self::State, + ) -> ControlFlow<(), Self::State> { + match *intrinsic { + Intrinsic::Authorize => { + debug!("authorize intrinsic found"); + ControlFlow::Continue(AuthorizeState::Yes) + } + Intrinsic::Fetch => ControlFlow::Continue(*state), + Intrinsic::ApiCall if *state == AuthorizeState::No => { + let vuln = AuthZVuln { + stackframe: interp.callstack(), + }; + info!("Found a vuln!"); + self.vulns.push(vuln); + ControlFlow::Break(()) + } + Intrinsic::ApiCall => ControlFlow::Continue(*state), + Intrinsic::SafeCall => ControlFlow::Continue(*state), + Intrinsic::EnvRead => ControlFlow::Continue(*state), + Intrinsic::StorageRead => ControlFlow::Continue(*state), + } + } +} diff --git a/crates/forge_analyzer/src/definitions.rs b/crates/forge_analyzer/src/definitions.rs index 6fcfecee..9345af33 100644 --- a/crates/forge_analyzer/src/definitions.rs +++ b/crates/forge_analyzer/src/definitions.rs @@ -36,8 +36,8 @@ use typed_index_collections::{TiSlice, TiVec}; use crate::{ ctx::ModId, ir::{ - BasicBlockId, Body, Inst, Literal, Operand, Projection, Rvalue, Template, Terminator, - Variable, RETURN_VAR, + BasicBlockId, Body, Inst, Intrinsic, Literal, Operand, Projection, Rvalue, Template, + Terminator, VarKind, Variable, RETURN_VAR, }, }; @@ -234,7 +234,7 @@ impl Class { /// let obj = Class::new(obj_id); /// obj.find_member("foo"); /// ``` - fn find_member(&self, name: &N) -> Option + pub(crate) fn find_member(&self, name: &N) -> Option where JsWord: PartialEq, { @@ -350,6 +350,13 @@ impl DefKind { } } + pub fn as_body(&self) -> Option<&F> { + match self { + Self::Function(f) | Self::Closure(f) => Some(f), + _ => None, + } + } + pub fn as_handler(&self) -> Option { match *self { Self::ResolverHandler(id) => Some(id), @@ -468,7 +475,8 @@ struct Lowerer<'cx> { #[derive(Debug, Clone, PartialEq, Eq, Hash)] enum PropPath { Def(DefId), - Str(JsWord), + Static(JsWord), + MemberCall(JsWord), Unknown(Id), Expr, This, @@ -500,7 +508,9 @@ fn normalize_callee_expr( impl Visit for CalleeNormalizer<'_> { fn visit_member_prop(&mut self, n: &MemberProp) { match n { - MemberProp::Ident(n) => n.visit_with(self), + MemberProp::Ident(n) => { + self.path.push(PropPath::Static(n.sym.clone())); + } MemberProp::PrivateName(PrivateName { id, .. }) => { self.path.push(PropPath::Private(id.to_id())) } @@ -527,7 +537,7 @@ fn normalize_callee_expr( fn visit_str(&mut self, n: &Str) { let str = n.value.clone(); - self.path.push(PropPath::Str(str)); + self.path.push(PropPath::Static(str)); } fn visit_lit(&mut self, n: &Lit) { @@ -558,6 +568,25 @@ fn normalize_callee_expr( expr @ (Expr::Lit(_) | Expr::Paren(_) | Expr::Ident(_)) => { expr.visit_children_with(self) } + Expr::Call(CallExpr { callee, .. }) => { + let Some(expr) = callee.as_expr() else { + self.path.push(PropPath::Expr); + return; + }; + match &**expr { + Expr::Member(MemberExpr { + obj, + prop: MemberProp::Ident(ident), + .. + }) => { + obj.visit_with(self); + self.path.push(PropPath::MemberCall(ident.sym.clone())); + } + _ => { + self.path.push(PropPath::Expr); + } + } + } _ => self.path.push(PropPath::Expr), } @@ -683,8 +712,9 @@ impl<'a> From<&'a Expr> for CalleeRef<'a> { } } -#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Default)] enum ApiCallKind { + #[default] Unknown, Trivial, Authorize, @@ -697,6 +727,7 @@ fn classify_api_call(expr: &Expr) -> ApiCallKind { static TRIVIAL: Lazy = Lazy::new(|| Regex::new(r"user|instance|avatar|license|preferences|serverinfo").unwrap()); + #[derive(Default)] struct ApiCallClassifier { kind: ApiCallKind, } @@ -721,9 +752,7 @@ fn classify_api_call(expr: &Expr) -> ApiCallKind { } } - let mut classifier = ApiCallClassifier { - kind: ApiCallKind::Unknown, - }; + let mut classifier = ApiCallClassifier::default(); expr.visit_with(&mut classifier); classifier.kind } @@ -734,6 +763,50 @@ impl<'cx> FunctionAnalyzer<'cx> { self.body.set_terminator(self.block, term); } + fn as_intrinsic(&self, callee: &[PropPath], first_arg: Option<&Expr>) -> Option { + fn is_storage_read(prop: &JsWord) -> bool { + *prop == *"get" || *prop == *"getSecret" || *prop == *"query" + } + + match *callee { + [PropPath::Unknown((ref name, ..))] if *name == *"fetch" => Some(Intrinsic::Fetch), + [PropPath::Def(def), ref authn @ .., PropPath::Static(ref last)] + if *last == *"requestJira" + || *last == *"requestConfluence" + && Some(&ImportKind::Default) + == self.res.as_foreign_import(def, "@forge/api") => + { + let first_arg = first_arg?; + match classify_api_call(first_arg) { + ApiCallKind::Unknown => { + if authn.first() == Some(&PropPath::MemberCall("asApp".into())) { + Some(Intrinsic::ApiCall) + } else { + Some(Intrinsic::SafeCall) + } + } + ApiCallKind::Trivial => Some(Intrinsic::SafeCall), + ApiCallKind::Authorize => Some(Intrinsic::Authorize), + } + } + [PropPath::Def(def), PropPath::Static(ref s), ..] if is_storage_read(s) => { + match self.res.as_foreign_import(def, "@forge/api") { + Some(ImportKind::Named(ref name)) if *name == *"storage" => { + Some(Intrinsic::StorageRead) + } + _ => None, + } + } + [PropPath::Def(def), ..] => match self.res.as_foreign_import(def, "@forge/api") { + Some(ImportKind::Named(ref name)) if *name == *"authorize" => { + Some(Intrinsic::Authorize) + } + _ => None, + }, + _ => None, + } + } + /// Sets the current block to `block` and returns the previous block. #[inline] fn goto_block(&mut self, block: BasicBlockId) -> BasicBlockId { @@ -839,7 +912,7 @@ impl<'cx> FunctionAnalyzer<'cx> { } fn lower_call(&mut self, callee: CalleeRef<'_>, args: &[ExprOrSpread]) -> Operand { - let args = args.iter().map(|arg| self.lower_expr(&arg.expr)).collect(); + let lowered_args = args.iter().map(|arg| self.lower_expr(&arg.expr)).collect(); let props = normalize_callee_expr(callee, self.res, self.module); match props.first() { Some(&PropPath::Def(id)) => { @@ -857,10 +930,13 @@ impl<'cx> FunctionAnalyzer<'cx> { CalleeRef::Import => Operand::UNDEF, CalleeRef::Expr(expr) => self.lower_expr(expr), }; - Operand::with_var( - self.body - .push_tmp(self.block, Rvalue::Call { callee, args }, None), - ) + let first_arg = args.first().map(|expr| &*expr.expr); + let call = match self.as_intrinsic(&props, first_arg) { + Some(int) => Rvalue::Intrinsic(int, lowered_args), + None => Rvalue::Call(callee, lowered_args), + }; + let res = self.body.push_tmp(self.block, call, None); + Operand::with_var(res) } fn bind_pats(&mut self, n: &Pat, val: Rvalue) { @@ -1390,7 +1466,6 @@ impl Visit for FunctionCollector<'_> { if let Some(BlockStmt { stmts, .. }) = &n.body { analyzer.lower_stmts(stmts); let body = analyzer.body; - println!("name: {}", self.res.def_name(owner)); *self.res.def_mut(owner).expect_body() = body; } } @@ -1483,7 +1558,7 @@ impl Visit for FunctionCollector<'_> { info!("found possible resolver: {propname}"); match self.res.lookup_prop(def_id, propname) { Some(def) => { - self.res.overwrite_def(def, DefKind::Function(())); + //self.res.overwrite_def(def, DefKind::Function(())); info!("analyzing resolver def: {def:?}"); let old_parent = self.parent.replace(def); match expr { @@ -1605,8 +1680,11 @@ impl Visit for Lowerer<'_> { { if let Expr::Lit(Lit::Str(Str { value, .. })) = &**name { let fname = value.clone(); + let new_def = + self.res + .add_anonymous(fname.clone(), AnonType::Closure, self.curr_mod); let class = self.res.def_mut(objid).expect_class(); - class.pub_members.push((fname, self.curr_def.unwrap())); + class.pub_members.push((fname, new_def)); } } } @@ -2021,6 +2099,11 @@ impl Environment { self.defs.defs[def] = key; } + #[inline] + pub fn bodies(&self) -> impl Iterator + '_ { + self.defs.funcs.iter() + } + #[inline] fn get_or_insert_sym(&mut self, id: Id, module: ModId) -> DefId { let def_id = self.resolver.get_or_insert_sym(id, module); @@ -2095,7 +2178,7 @@ impl Environment { } } - fn module_export + ?Sized>( + pub fn module_export + ?Sized>( &self, module: ModId, export_name: &I, diff --git a/crates/forge_analyzer/src/interp.rs b/crates/forge_analyzer/src/interp.rs index 7d58108d..8e21a957 100644 --- a/crates/forge_analyzer/src/interp.rs +++ b/crates/forge_analyzer/src/interp.rs @@ -1,85 +1,408 @@ use std::{ - cell::RefCell, - fmt::Display, + cell::{Cell, Ref, RefCell, RefMut}, + collections::BTreeMap, + fmt::{self, Display}, io::{self, Write}, + iter, + marker::PhantomData, + ops::ControlFlow, }; use forge_utils::{FxHashMap, FxHashSet}; +use tracing::{debug, info, instrument, warn}; use crate::{ definitions::{DefId, Environment}, - ir::{BasicBlock, BasicBlockId, Operand, Variable, STARTING_BLOCK}, + ir::{ + BasicBlock, BasicBlockId, Body, Inst, Intrinsic, Location, Operand, Rvalue, Successors, + STARTING_BLOCK, + }, + worklist::WorkList, }; -trait JoinSemiLattice: Sized { +pub trait JoinSemiLattice: Sized + Ord { const BOTTOM: Self; - fn join(&mut self, other: &Self) -> bool; + fn join_changed(&mut self, other: &Self) -> bool; + fn join(&self, other: &Self) -> Self; } -enum Transition { +pub enum Transition { Call, Break, StepOver, } -trait WithCallStack { +pub trait WithCallStack { fn add_call_stack(&mut self, stack: Vec); } -trait Dataflow<'cx>: Sized { +pub trait Dataflow<'cx>: Sized { type State: JoinSemiLattice + Clone; fn with_interp>(interp: &Interp<'cx, C>) -> Self; + #[inline] + fn resolve_call>( + &mut self, + interp: &Interp<'cx, C>, + callee: &Operand, + ) -> Option<(DefId, &'cx Body)> { + interp.body().resolve_call(interp.env(), callee) + } + + fn transfer_intrinsic>( + &mut self, + interp: &Interp<'cx, C>, + def: DefId, + loc: Location, + block: &'cx BasicBlock, + intrinsic: &'cx Intrinsic, + initial_state: Self::State, + ) -> Self::State; + + fn transfer_call>( + &mut self, + interp: &Interp<'cx, C>, + def: DefId, + loc: Location, + block: &'cx BasicBlock, + callee: &'cx Operand, + initial_state: Self::State, + ) -> Self::State; + + fn transfer_rvalue>( + &mut self, + interp: &Interp<'cx, C>, + def: DefId, + loc: Location, + block: &'cx BasicBlock, + rvalue: &'cx Rvalue, + initial_state: Self::State, + ) -> Self::State { + match rvalue { + Rvalue::Intrinsic(intrinsic, _) => { + self.transfer_intrinsic(interp, def, loc, block, intrinsic, initial_state) + } + Rvalue::Call(callee, _) => { + self.transfer_call(interp, def, loc, block, callee, initial_state) + } + Rvalue::Unary(_, _) => initial_state, + Rvalue::Bin(_, _, _) => initial_state, + Rvalue::Read(_) => initial_state, + Rvalue::Phi(_) => initial_state, + Rvalue::Template(_) => initial_state, + } + } + + fn transfer_inst>( + &mut self, + interp: &Interp<'cx, C>, + def: DefId, + loc: Location, + block: &'cx BasicBlock, + inst: &'cx Inst, + initial_state: Self::State, + ) -> Self::State { + match inst { + Inst::Expr(rvalue) => { + self.transfer_rvalue(interp, def, loc, block, rvalue, initial_state) + } + Inst::Assign(_, rvalue) => { + self.transfer_rvalue(interp, def, loc, block, rvalue, initial_state) + } + } + } + fn transfer_block>( &mut self, interp: &Interp<'cx, C>, + def: DefId, bb: BasicBlockId, block: &'cx BasicBlock, - initial_state: &Self::State, - ) -> Self::State; + initial_state: Self::State, + ) -> Self::State { + let mut state = initial_state; + for (stmt, inst) in block.iter().enumerate() { + let loc = Location::new(bb, stmt as u32); + state = self.transfer_inst(interp, def, loc, block, inst, state); + } + state + } + + fn join_term>( + &mut self, + interp: &Interp<'cx, C>, + def: DefId, + block: &'cx BasicBlock, + state: Self::State, + worklist: &mut WorkList, + ) { + self.super_join_term(interp, def, block, state, worklist); + } + + fn super_join_term>( + &mut self, + interp: &Interp<'cx, C>, + def: DefId, + block: &'cx BasicBlock, + state: Self::State, + worklist: &mut WorkList, + ) { + match block.successors() { + Successors::Return => { + if interp + .func_state(def) + .map_or(true, |old_state| old_state < state) + { + interp.set_func_state(def, state); + let calls = interp.called_from(def); + let name = interp.env().def_name(def); + debug!("{name} {def:?} is called from {calls:?}"); + for &(def, loc) in calls { + if worklist.visited(&def) { + worklist.push_back_force(def, loc.block); + } + } + } + } + Successors::One(succ) => { + let mut succ_state = interp.block_state_mut(def, succ); + if succ_state.join_changed(&state) { + worklist.push_back(def, succ); + } + } + Successors::Two(succ1, succ2) => { + if interp.block_state_mut(def, succ1).join_changed(&state) { + worklist.push_back(def, succ1); + } + + if interp.block_state_mut(def, succ2).join_changed(&state) { + worklist.push_back(def, succ2); + } + } + } + } } -trait Checker<'cx>: Sized { - type State: JoinSemiLattice + Clone; +pub trait Checker<'cx>: Sized { + type State: JoinSemiLattice + Clone + fmt::Debug; type Vuln: Display + WithCallStack; type Dataflow: Dataflow<'cx, State = Self::State>; + fn visit_intrinsic( + &mut self, + interp: &Interp<'cx, Self>, + intrinsic: &'cx Intrinsic, + state: &Self::State, + ) -> ControlFlow<(), Self::State>; + fn visit_call( &mut self, interp: &Interp<'cx, Self>, - callee: &'cx Variable, - args: &'cx [Operand], + callee: &'cx Operand, + _args: &'cx [Operand], + block: BasicBlockId, curr_state: &Self::State, - ) -> (Transition, Self::State); + ) -> ControlFlow<(), Self::State> { + let Some((callee, body)) = interp.body().resolve_call(interp.env(), callee) else { + return ControlFlow::Continue(curr_state.clone()); + }; + let func_state = interp.func_state(callee).unwrap_or(Self::State::BOTTOM); + if func_state < *curr_state || !interp.checker_visit(callee) { + return ControlFlow::Continue(curr_state.clone()); + } + interp.push_frame(callee, block); + let res = self.visit_body(interp, callee, body, curr_state); + interp.pop_frame(); + // FIXME: Should probably join instead of relying on the caller to propogate state + res + } + + fn visit_body( + &mut self, + interp: &Interp<'cx, Self>, + def: DefId, + body: &'cx Body, + curr_state: &Self::State, + ) -> ControlFlow<(), Self::State> { + let name = interp.env.def_name(def); + debug!("visiting body of {name}"); + let old_body = interp.body(); + interp.set_body(body); + let block = body.block(STARTING_BLOCK); + let res = self.visit_block(interp, def, STARTING_BLOCK, block, curr_state); + interp.set_body(old_body); + res + } + + fn visit_rvalue( + &mut self, + interp: &Interp<'cx, Self>, + rvalue: &'cx Rvalue, + id: BasicBlockId, + curr_state: &Self::State, + ) -> ControlFlow<(), Self::State> { + debug!("visiting rvalue {rvalue:?} with {curr_state:?}"); + match rvalue { + Rvalue::Intrinsic(intrinsic, _) => self.visit_intrinsic(interp, intrinsic, curr_state), + Rvalue::Call(callee, args) => self.visit_call(interp, callee, args, id, curr_state), + Rvalue::Unary(_, _) + | Rvalue::Bin(_, _, _) + | Rvalue::Read(_) + | Rvalue::Phi(_) + | Rvalue::Template(_) => ControlFlow::Continue(curr_state.clone()), + } + } + + #[instrument(skip(self, interp, block))] + fn visit_block( + &mut self, + interp: &Interp<'cx, Self>, + def: DefId, + id: BasicBlockId, + block: &'cx BasicBlock, + curr_state: &Self::State, + ) -> ControlFlow<(), Self::State> { + let mut curr_state = interp.block_state(def, id).join(curr_state); + for stmt in block { + match stmt { + Inst::Expr(r) => curr_state = self.visit_rvalue(interp, r, id, &curr_state)?, + Inst::Assign(_, r) => curr_state = self.visit_rvalue(interp, r, id, &curr_state)?, + } + } + match block.successors() { + Successors::Return => ControlFlow::Continue(curr_state), + Successors::One(succ) => { + let bb = interp.body().block(id); + self.visit_block(interp, def, succ, bb, &curr_state) + } + Successors::Two(succ1, succ2) => { + let bb = interp.body().block(succ1); + self.visit_block(interp, def, succ1, bb, &curr_state)?; + let bb = interp.body().block(succ2); + self.visit_block(interp, def, succ2, bb, &curr_state) + } + } + } } -struct Frame { - calling_function: DefId, - block: BasicBlockId, - inst_idx: usize, +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub(crate) struct Frame { + pub(crate) calling_function: DefId, + pub(crate) block: BasicBlockId, + pub(crate) inst_idx: usize, } -struct Interp<'cx, C: Checker<'cx>> { +pub struct Interp<'cx, C: Checker<'cx>> { env: &'cx Environment, - checker: C, - func_state: FxHashMap, - visited: FxHashSet, - callstack: Vec, - func_branches: FxHashMap>, + // We can probably get rid of these RefCells by refactoring the Interp and Checker into + // two fields in another struct. + call_graph: CallGraph, + func_state: RefCell>, + curr_body: Cell>, + states: RefCell>, + dataflow_visited: FxHashSet, + checker_visited: RefCell>, + callstack: RefCell>, vulns: RefCell>, + _checker: PhantomData, +} + +struct CallGraph { + called_from: FxHashMap>, + // (Caller, Callee) -> Location + callgraph: BTreeMap<(DefId, DefId), Location>, +} + +impl CallGraph { + fn new(env: &Environment) -> Self { + let mut called_from: FxHashMap<_, Vec<(_, Location)>> = FxHashMap::default(); + let callgraph = env + .bodies() + .filter_map(|body| body.owner().zip(Some(body))) + .flat_map(|(def, body)| { + iter::repeat((def, body)).zip( + body.iter_blocks_enumerated() + .flat_map(|(bb, block)| iter::repeat(bb).zip(block.iter().enumerate())), + ) + }) + .filter_map(|((def, body), (bb, (inst_idx, inst)))| { + let (callee, _) = inst.rvalue().as_call()?; + let (callee_def, _) = body.resolve_call(env, callee)?; + debug!( + "found call from {def:?} {} to {callee_def:?} {}", + env.def_name(def), + env.def_name(callee_def) + ); + let loc = Location::new(bb, inst_idx as u32); + called_from.entry(callee_def).or_default().push((def, loc)); + Some(((def, callee_def), loc)) + }) + .collect(); + Self { + called_from, + callgraph, + } + } +} + +#[derive(Debug, PartialEq, Eq)] +pub enum Error { + NotAFunction(String), +} + +impl fmt::Display for Error { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Error::NotAFunction(name) => write!(f, "Not a function: {name}"), + } + } } +impl std::error::Error for Error {} + impl<'cx, C: Checker<'cx>> Interp<'cx, C> { + pub fn new(env: &'cx Environment) -> Self { + let call_graph = CallGraph::new(env); + Self { + env, + call_graph, + func_state: RefCell::new(FxHashMap::default()), + curr_body: Cell::new(None), + states: RefCell::new(BTreeMap::new()), + dataflow_visited: FxHashSet::default(), + checker_visited: RefCell::new(FxHashSet::default()), + callstack: RefCell::new(Vec::new()), + vulns: RefCell::new(Vec::new()), + _checker: PhantomData, + } + } + #[inline] - fn env(&self) -> &'cx Environment { + pub(crate) fn env(&self) -> &'cx Environment { self.env } - fn note_vuln(&self, mut vuln: C::Vuln) { + #[inline] + fn body(&self) -> &'cx Body { + self.curr_body.get().unwrap() + } + + #[inline] + fn set_body(&self, body: &'cx Body) { + self.curr_body.set(Some(body)); + } + + #[inline] + pub(crate) fn callstack(&self) -> Vec { + (*self.callstack.borrow()).clone() + } + + pub(crate) fn note_vuln(&self, mut vuln: C::Vuln) { vuln.add_call_stack( self.callstack + .borrow() .iter() .map(|f| f.calling_function) .collect::>(), @@ -87,20 +410,125 @@ impl<'cx, C: Checker<'cx>> Interp<'cx, C> { self.vulns.borrow_mut().push(vuln); } - fn run(&mut self, func: DefId) { - if self.visited.contains(&func) { + pub(crate) fn checker_visit(&self, def: DefId) -> bool { + self.checker_visited.borrow_mut().insert(def) + } + + #[inline] + fn called_from(&self, def: DefId) -> &[(DefId, Location)] { + self.call_graph.called_from.get(&def).map_or(&[], |v| v) + } + + #[inline] + fn block_state(&self, def: DefId, block: BasicBlockId) -> C::State { + self.states + .borrow() + .get(&(def, block)) + .cloned() + .unwrap_or(C::State::BOTTOM) + } + + #[inline] + fn block_state_mut(&self, def: DefId, block: BasicBlockId) -> RefMut<'_, C::State> { + let states = self.states.borrow_mut(); + RefMut::map(states, |states| { + states.entry((def, block)).or_insert(C::State::BOTTOM) + }) + } + + #[inline] + pub(crate) fn func_state(&self, def: DefId) -> Option { + self.func_state.borrow().get(&def).cloned() + } + + #[inline] + fn set_func_state(&self, def: DefId, state: C::State) -> Option { + self.func_state.borrow_mut().insert(def, state) + } + + #[inline] + fn push_frame(&self, def: DefId, block: BasicBlockId) { + self.callstack.borrow_mut().push(Frame { + calling_function: def, + block, + inst_idx: 0, + }); + } + + #[inline] + fn pop_frame(&self) -> Option { + self.callstack.borrow_mut().pop() + } + + #[inline] + fn func_state_mut(&self, def: DefId) -> RefMut<'_, C::State> { + let func_state = self.func_state.borrow_mut(); + RefMut::map(func_state, |state| { + state.entry(def).or_insert(C::State::BOTTOM) + }) + } + + fn run(&mut self, func_def: DefId) { + if self.dataflow_visited.contains(&func_def) { return; } - self.visited.insert(func); - let func = self.env().def_ref(func).expect_body(); + self.dataflow_visited.insert(func_def); let mut dataflow = C::Dataflow::with_interp(self); - let mut block_id = STARTING_BLOCK; - let block = func.block(block_id); - let initial_state = C::State::BOTTOM; - let final_state = dataflow.transfer_block(self, block_id, &block, &initial_state); + let mut worklist = WorkList::new(); + worklist.push_front_blocks(self.env, func_def); + let old_body = self.curr_body.get(); + while let Some((def, block_id)) = worklist.pop_front() { + let name = self.env.def_name(def); + debug!("Dataflow: {name} - {block_id}"); + self.dataflow_visited.insert(def); + let func = self.env().def_ref(def).expect_body(); + self.curr_body.set(Some(func)); + let mut before_state = self.block_state(def, block_id); + let block = func.block(block_id); + for &pred in func.predecessors(block_id) { + before_state = before_state.join(&self.block_state(def, pred)); + } + let state = dataflow.transfer_block(self, def, block_id, block, before_state); + dataflow.join_term(self, def, block, state, &mut worklist); + } + self.curr_body.set(old_body); + } + + fn try_check_function(&mut self, def: DefId, checker: &mut C) -> Result<(), Error> { + let resolved_def = self.env.resolve_alias(def); + let name = self.env.def_name(resolved_def); + info!("Checking function: {name}"); + let body = *self + .env + .def_ref(resolved_def) + .as_body() + .ok_or_else(|| Error::NotAFunction(name.to_owned()))?; + self.set_body(body); + self.run(resolved_def); + checker.visit_body(self, resolved_def, body, &C::State::BOTTOM); + Ok(()) + } + + #[instrument(skip(self, checker))] + pub fn run_checker(&mut self, def: DefId, checker: &mut C) -> Result<(), Error> { + let Err(error) = self.try_check_function(def, checker) else { + return Ok(()); + }; + let resolver = self.env.resolver_defs(def); + if resolver.is_empty() { + return Err(error); + } + info!("Found potential resolver"); + for (name, prop) in resolver { + debug!("Checking resolver prop: {name}"); + if let Err(error) = self.try_check_function(prop, checker) { + warn!("Resolver prop {name} failed: {error}"); + } + } + Ok(()) } - fn dump_results(&self, out: &mut dyn Write) -> io::Result<()> { + pub fn dump_results(&self, out: &mut dyn Write) -> io::Result<()> { let vulns = &**self.vulns.borrow(); if vulns.is_empty() { writeln!(out, "No vulnerabilities found") diff --git a/crates/forge_analyzer/src/ir.rs b/crates/forge_analyzer/src/ir.rs index b6e5d57d..517ef324 100644 --- a/crates/forge_analyzer/src/ir.rs +++ b/crates/forge_analyzer/src/ir.rs @@ -10,9 +10,11 @@ use std::hash; use std::hash::Hash; use std::mem; use std::num::NonZeroUsize; +use std::slice; use forge_utils::create_newtype; use forge_utils::FxHashMap; +use once_cell::unsync::OnceCell; use smallvec::smallvec; use smallvec::smallvec_inline; use smallvec::SmallVec; @@ -29,6 +31,8 @@ use typed_index_collections::TiVec; use crate::ctx::ModId; use crate::definitions::DefId; +use crate::definitions::DefKind; +use crate::definitions::Environment; pub const STARTING_BLOCK: BasicBlockId = BasicBlockId(0); @@ -60,16 +64,17 @@ pub(crate) enum Terminator { } #[derive(Clone, Debug, Copy, PartialEq, Eq)] -pub(crate) enum Intrinsic { +pub enum Intrinsic { Authorize, Fetch, ApiCall, + SafeCall, EnvRead, StorageRead, } #[derive(Clone, Debug, Default)] -pub(crate) struct Template { +pub struct Template { pub(crate) quasis: Vec, pub(crate) exprs: Vec, // TODO: make this more memory efficient @@ -78,30 +83,30 @@ pub(crate) struct Template { } #[derive(Clone, Debug)] -pub(crate) enum Rvalue { +pub enum Rvalue { Unary(UnOp, Operand), Bin(BinOp, Operand, Operand), - Call { callee: Operand, args: Vec }, Read(Operand), + Call(Operand, SmallVec<[Operand; 4]>), + Intrinsic(Intrinsic, SmallVec<[Operand; 4]>), Phi(Vec<(VarId, BasicBlockId)>), - Intrinsics(Intrinsic), Template(Template), } #[derive(Clone, Debug, Default)] -pub(crate) struct BasicBlock { +pub struct BasicBlock { insts: Vec, term: Terminator, } #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] -struct Location { - block: BasicBlockId, - stmt: u32, +pub struct Location { + pub block: BasicBlockId, + pub stmt: u32, } #[derive(Debug, Clone, PartialEq, Eq, Hash)] -enum VarKind { +pub(crate) enum VarKind { LocalDef(DefId), GlobalRef(DefId), Temp { parent: Option }, @@ -122,6 +127,7 @@ pub struct Body { vars: TiVec, ident_to_local: FxHashMap, def_id_to_vars: FxHashMap, + predecessors: OnceCell>>, } #[derive(Clone, Copy, Debug, PartialEq, Eq)] @@ -132,14 +138,14 @@ enum ApiCall { } #[derive(Clone, Debug)] -pub(crate) enum Inst { +pub enum Inst { // maybe just use assign with a dummy VARIABLE for these? Expr(Rvalue), Assign(Variable, Rvalue), } #[derive(Clone, Debug, Default)] -pub(crate) enum Literal { +pub enum Literal { Str(JsWord), JSXText(Atom), Bool(bool), @@ -153,7 +159,7 @@ pub(crate) enum Literal { } #[derive(Clone, Copy, Debug, PartialEq, Eq)] -pub(crate) enum BinOp { +pub enum BinOp { Lt, Gt, EqEq, @@ -182,7 +188,7 @@ pub(crate) enum BinOp { } #[derive(Clone, Copy, Debug, PartialEq, Eq)] -pub(crate) enum UnOp { +pub enum UnOp { Neg, Not, BitNot, @@ -193,7 +199,7 @@ pub(crate) enum UnOp { } #[derive(Clone, Debug, PartialEq, Eq, Hash)] -pub(crate) enum Operand { +pub enum Operand { Var(Variable), Lit(Literal), } @@ -214,7 +220,7 @@ create_newtype! { } #[derive(Clone, Debug, PartialEq, Eq, Hash)] -pub(crate) struct Variable { +pub struct Variable { pub(crate) base: Base, pub(crate) projections: SmallVec<[Projection; 1]>, } @@ -225,31 +231,32 @@ pub(crate) enum Projection { Computed(Base), } -impl fmt::Display for VarId { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "%{}", self.0) - } +#[derive(Clone, Debug, PartialEq, Eq, Copy)] +pub(crate) enum Successors { + Return, + One(BasicBlockId), + Two(BasicBlockId, BasicBlockId), } -impl fmt::Display for Variable { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{}", self.base)?; - for proj in &self.projections { - match proj { - Projection::Known(lit) => write!(f, "[\"{lit}\"]")?, - Projection::Computed(id) => write!(f, "[{id}]")?, +impl BasicBlock { + #[inline] + pub(crate) fn iter(&self) -> impl DoubleEndedIterator + ExactSizeIterator { + self.insts.iter() + } + + pub(crate) fn successors(&self) -> Successors { + match self.term { + Terminator::Ret => Successors::Return, + Terminator::Goto(bb) => Successors::One(bb), + Terminator::Throw => Successors::Return, + Terminator::Switch { ref targets, .. } => { + if targets.branch.len() == 1 { + Successors::One(targets.branch[0]) + } else { + Successors::Two(targets.branch[0], targets.branch[1]) + } } - } - Ok(()) - } -} - -impl fmt::Display for Base { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match *self { - Base::This => write!(f, "this"), - Base::Super => write!(f, "super"), - Base::Var(id) => write!(f, "{}", id), + Terminator::If { cons, alt, .. } => Successors::Two(cons, alt), } } } @@ -264,6 +271,7 @@ impl Body { blocks: vec![BasicBlock::default()].into(), ident_to_local: Default::default(), def_id_to_vars: Default::default(), + predecessors: Default::default(), } } @@ -275,6 +283,39 @@ impl Body { } } + #[inline] + pub(crate) fn iter_vars(&self) -> impl Iterator { + self.vars.iter() + } + + #[inline] + pub(crate) fn iter_vars_enumerated(&self) -> impl Iterator { + self.vars.iter_enumerated() + } + + pub(crate) fn iter_block_keys( + &self, + ) -> impl ExactSizeIterator + DoubleEndedIterator + '_ { + self.blocks.iter_enumerated().map(|(bb, _)| bb) + } + + #[inline] + pub(crate) fn iter_blocks_enumerated( + &self, + ) -> impl ExactSizeIterator + DoubleEndedIterator { + self.blocks.iter_enumerated() + } + + #[inline] + pub(crate) fn owner(&self) -> Option { + self.owner + } + + #[inline] + pub(crate) fn add_var(&mut self, kind: VarKind) -> VarId { + self.vars.push_and_get_key(kind) + } + #[inline] pub(crate) fn add_local_def(&mut self, def: DefId, id: Id) { self.ident_to_local @@ -313,6 +354,23 @@ impl Body { }) } + pub(crate) fn predecessors(&self, block: BasicBlockId) -> &[BasicBlockId] { + &self.predecessors.get_or_init(|| { + let mut preds: TiVec<_, _> = vec![SmallVec::new(); self.blocks.len()].into(); + for (bb, block) in self.iter_blocks_enumerated() { + match block.successors() { + Successors::Return => {} + Successors::One(s) => preds[s].push(bb), + Successors::Two(s1, s2) => { + preds[s1].push(bb); + preds[s2].push(bb); + } + } + } + preds + })[block] + } + #[inline] pub(crate) fn set_terminator(&mut self, bb: BasicBlockId, term: Terminator) -> Terminator { mem::replace(&mut self.blocks[bb].term, term) @@ -333,6 +391,49 @@ impl Body { } } + pub fn resolve_call<'cx>( + &self, + env: &'cx Environment, + callee: &Operand, + ) -> Option<(DefId, &'cx Body)> { + match callee { + Operand::Var(Variable { + base: Base::Var(var), + projections, + }) => match self.vars[*var] { + VarKind::LocalDef(def) | VarKind::GlobalRef(def) => { + let def = env.resolve_alias(def); + match env.def_ref(def) { + DefKind::Function(f) | DefKind::Closure(f) => Some((def, f)), + DefKind::GlobalObj(o) | DefKind::Class(o) => { + let [Projection::Known(ref mem)] = **projections else { + return None; + }; + let mem_def = o.find_member(mem); + mem_def.zip(env.def_ref(mem_def?).as_body().copied()) + } + DefKind::ModuleNs(n) => { + let [Projection::Known(ref mem)] = **projections else { + return None; + }; + let module_export = env.module_export(n, mem); + module_export.zip(env.def_ref(module_export?).as_body().copied()) + } + DefKind::ExportAlias(_) + | DefKind::Foreign(_) + | DefKind::ResolverHandler(_) + | DefKind::Resolver(_) + | DefKind::ResolverDef(_) + | DefKind::Undefined + | DefKind::Arg => None, + } + } + _ => None, + }, + _ => None, + } + } + pub(crate) fn coerce_to_lval(&mut self, bb: BasicBlockId, val: Operand) -> Variable { match val { Operand::Var(var) => var, @@ -374,70 +475,33 @@ impl Body { } } -impl Default for Body { - #[inline] - fn default() -> Self { - Self::new() - } -} +impl Variable { + pub(crate) const THIS: Self = Self { + base: Base::This, + projections: SmallVec::new_const(), + }; -impl PartialEq for Literal { - fn eq(&self, other: &Self) -> bool { - match (self, other) { - (Self::Str(l0), Self::Str(r0)) => *l0 == *r0, - (Self::Bool(l0), Self::Bool(r0)) => *l0 == *r0, - (Self::Number(l0), Self::Number(r0)) => *l0 == *r0, - (Self::RegExp(l0, l1), Self::RegExp(r0, r1)) => *l0 == *r0 && *l1 == *r1, - (Self::BigInt(l0), Self::BigInt(r0)) => *l0 == *r0, - (Self::JSXText(l0), Self::JSXText(r0)) => *l0 == *r0, - (Self::Null, Self::Null) | (Self::Undef, Self::Undef) => true, - // We intentionally list out every possibility instead of using [`std::mem::discriminant`] to trigger a compile error - // in the event that a new variant with a field is added. - ( - Self::Bool(_) - | Self::Str(_) - | Self::JSXText(_) - | Self::BigInt(_) - | Self::Number(_) - | Self::RegExp(_, _) - | Self::Null - | Self::Undef, - _, - ) => false, + pub(crate) const SUPER: Self = Self { + base: Base::Super, + projections: SmallVec::new_const(), + }; + + #[inline] + pub(crate) const fn new(var: VarId) -> Self { + Self { + base: Base::Var(var), + projections: SmallVec::new_const(), } } -} - -// I don't like it either, but in JS NaNs are compared bitwise -impl Eq for Literal {} -impl Hash for Literal { - fn hash(&self, state: &mut H) { - mem::discriminant(self).hash(state); - match self { - Literal::Str(s) => s.hash(state), - Literal::Bool(b) => b.hash(state), - Literal::Null | Literal::Undef => {} - Literal::Number(n) => n.to_bits().hash(state), - Literal::BigInt(bn) => bn.hash(state), - Literal::RegExp(s, t) => (s, t).hash(state), - Literal::JSXText(r) => r.hash(state), - } + #[inline] + pub(crate) fn add_computed_var(&mut self, var: VarId) { + self.projections.push(Projection::Computed(Base::Var(var))); } -} -impl fmt::Display for Literal { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match *self { - Literal::Str(ref s) => write!(f, "\"{s}\""), - Literal::Bool(b) => write!(f, "{b}"), - Literal::Null => write!(f, "[null]"), - Literal::Undef => write!(f, "[undefined]"), - Literal::Number(n) => write!(f, "{n}"), - Literal::BigInt(ref n) => write!(f, "{n}"), - Literal::RegExp(ref regex, ref flags) => write!(f, "/{regex}/{flags}"), - Literal::JSXText(ref r) => write!(f, "JSX: \"{r}\""), - } + #[inline] + pub(crate) fn add_known(&mut self, lit: JsWord) { + self.projections.push(Projection::Known(lit)); } } @@ -464,6 +528,28 @@ impl Rvalue { pub(crate) fn with_var(name: VarId) -> Self { Rvalue::Read(Operand::Var(Variable::new(name))) } + + pub(crate) fn as_call(&self) -> Option<(&Operand, &[Operand])> { + match self { + Rvalue::Call(callee, args) => Some((callee, args)), + _ => None, + } + } +} + +impl Location { + #[inline] + pub fn new(block: BasicBlockId, stmt: u32) -> Self { + Self { block, stmt } + } +} + +impl Inst { + pub(crate) fn rvalue(&self) -> &Rvalue { + match self { + Inst::Assign(_, r) | Inst::Expr(r) => r, + } + } } impl Operand { @@ -480,6 +566,213 @@ impl Operand { } } +impl<'a> IntoIterator for &'a BasicBlock { + type Item = &'a Inst; + + type IntoIter = slice::Iter<'a, Inst>; + + #[inline] + fn into_iter(self) -> Self::IntoIter { + self.insts.iter() + } +} + +impl fmt::Display for Literal { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match *self { + Literal::Str(ref s) => write!(f, "\"{s}\""), + Literal::Bool(b) => write!(f, "{b}"), + Literal::Null => write!(f, "[null]"), + Literal::Undef => write!(f, "[undefined]"), + Literal::Number(n) => write!(f, "{n}"), + Literal::BigInt(ref n) => write!(f, "{n}"), + Literal::RegExp(ref regex, ref flags) => write!(f, "/{regex}/{flags}"), + Literal::JSXText(ref r) => write!(f, "JSX: \"{r}\""), + } + } +} + +impl fmt::Display for VarId { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "%{}", self.0) + } +} + +impl fmt::Display for Variable { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", self.base)?; + for proj in &self.projections { + match proj { + Projection::Known(lit) => write!(f, "[\"{lit}\"]")?, + Projection::Computed(id) => write!(f, "[{id}]")?, + } + } + Ok(()) + } +} + +impl fmt::Display for Base { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match *self { + Base::This => write!(f, "this"), + Base::Super => write!(f, "super"), + Base::Var(id) => write!(f, "{id}"), + } + } +} + +impl fmt::Display for BasicBlockId { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "bb{}", self.0) + } +} + +impl fmt::Display for Operand { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Operand::Var(v) => write!(f, "{v}"), + Operand::Lit(l) => write!(f, "{l}"), + } + } +} + +impl fmt::Display for Terminator { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Terminator::Ret => write!(f, "return"), + Terminator::Goto(bb) => write!(f, "goto {bb}"), + Terminator::Throw => write!(f, "throw"), + Terminator::Switch { .. } => write!(f, "switch"), + Terminator::If { cond, cons, alt } => { + write!(f, "if ({cond}) then goto {cons} else goto {alt}") + } + } + } +} + +impl fmt::Display for BasicBlock { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + for inst in &self.insts { + writeln!(f, " {inst}")?; + } + write!(f, " {}", &self.term) + } +} + +impl fmt::Display for UnOp { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match *self { + UnOp::Neg => write!(f, "-"), + UnOp::Not => write!(f, "!"), + UnOp::BitNot => write!(f, "~"), + UnOp::Plus => write!(f, "+"), + UnOp::TypeOf => write!(f, "typeof"), + UnOp::Delete => write!(f, "delete"), + UnOp::Void => write!(f, "void"), + } + } +} + +impl fmt::Display for BinOp { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match *self { + BinOp::Lt => write!(f, "<"), + BinOp::Gt => write!(f, ">"), + BinOp::EqEq => write!(f, "=="), + BinOp::Neq => write!(f, "!="), + BinOp::NeqEq => write!(f, "!=="), + BinOp::EqEqEq => write!(f, "==="), + BinOp::Ge => write!(f, ">="), + BinOp::Le => write!(f, "<="), + BinOp::Add => write!(f, "+"), + BinOp::Sub => write!(f, "-"), + BinOp::Mul => write!(f, "*"), + BinOp::Div => write!(f, "/"), + BinOp::Exp => write!(f, "**"), + BinOp::Mod => write!(f, "%"), + BinOp::Or => write!(f, "||"), + BinOp::And => write!(f, "&&"), + BinOp::BitOr => write!(f, "|"), + BinOp::BitAnd => write!(f, "&"), + BinOp::BitXor => write!(f, "^"), + BinOp::Lshift => write!(f, "<<"), + BinOp::Rshift => write!(f, ">>"), + BinOp::RshiftLogical => write!(f, ">>>"), + BinOp::In => write!(f, "in"), + BinOp::InstanceOf => write!(f, "instanceof"), + BinOp::NullishCoalesce => write!(f, "??"), + } + } +} + +impl fmt::Display for Template { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "`")?; + let mut exprs = self.exprs.iter(); + for quasi in &self.quasis { + write!(f, "{quasi}")?; + if let Some(expr) = exprs.next() { + write!(f, "{{{}}}", expr)?; + } + } + Ok(()) + } +} + +impl fmt::Display for Inst { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Inst::Expr(rval) => write!(f, "_ = {rval}"), + Inst::Assign(lval, rval) => write!(f, "{lval} = {rval}"), + } + } +} + +impl fmt::Display for Intrinsic { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match *self { + Intrinsic::Fetch => write!(f, "fetch"), + Intrinsic::Authorize => write!(f, "authorize"), + Intrinsic::ApiCall => write!(f, "api call"), + Intrinsic::SafeCall => write!(f, "safe api call"), + Intrinsic::EnvRead => write!(f, "env read"), + Intrinsic::StorageRead => write!(f, "forge storage read"), + } + } +} + +impl fmt::Display for Rvalue { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match *self { + Rvalue::Unary(op, ref opnd) => write!(f, "{op} {opnd}"), + Rvalue::Bin(op, ref lhs, ref rhs) => write!(f, "{lhs} {op} {rhs}"), + Rvalue::Call(ref op, ref args) => { + write!(f, "{op}(")?; + for arg in args { + write!(f, "{arg}, ")?; + } + write!(f, ")") + } + Rvalue::Intrinsic(ref intrinsic, ref args) => { + write!(f, "{intrinsic}(")?; + for arg in args { + write!(f, "{arg}, ")?; + } + write!(f, ")") + } + Rvalue::Read(ref opnd) => write!(f, "{opnd}"), + Rvalue::Phi(ref phis) => { + write!(f, "phi(")?; + for &(var, block) in phis { + write!(f, "{block}: {var}, ")?; + } + write!(f, ")") + } + Rvalue::Template(ref template) => write!(f, "{template}"), + } + } +} + impl From for Operand { #[inline] fn from(value: Literal) -> Self { @@ -564,33 +857,54 @@ impl From<&BinaryOp> for BinOp { Self::from(*value) } } - -impl Variable { - pub(crate) const THIS: Self = Self { - base: Base::This, - projections: SmallVec::new_const(), - }; - - pub(crate) const SUPER: Self = Self { - base: Base::Super, - projections: SmallVec::new_const(), - }; - +impl Default for Body { #[inline] - pub(crate) const fn new(var: VarId) -> Self { - Self { - base: Base::Var(var), - projections: SmallVec::new_const(), - } + fn default() -> Self { + Self::new() } +} - #[inline] - pub(crate) fn add_computed_var(&mut self, var: VarId) { - self.projections.push(Projection::Computed(Base::Var(var))); +impl PartialEq for Literal { + fn eq(&self, other: &Self) -> bool { + match (self, other) { + (Self::Str(l0), Self::Str(r0)) => *l0 == *r0, + (Self::Bool(l0), Self::Bool(r0)) => *l0 == *r0, + (Self::Number(l0), Self::Number(r0)) => *l0 == *r0, + (Self::RegExp(l0, l1), Self::RegExp(r0, r1)) => *l0 == *r0 && *l1 == *r1, + (Self::BigInt(l0), Self::BigInt(r0)) => *l0 == *r0, + (Self::JSXText(l0), Self::JSXText(r0)) => *l0 == *r0, + (Self::Null, Self::Null) | (Self::Undef, Self::Undef) => true, + // We intentionally list out every possibility instead of using [`std::mem::discriminant`] to trigger a compile error + // in the event that a new variant with a field is added. + ( + Self::Bool(_) + | Self::Str(_) + | Self::JSXText(_) + | Self::BigInt(_) + | Self::Number(_) + | Self::RegExp(_, _) + | Self::Null + | Self::Undef, + _, + ) => false, + } } +} - #[inline] - pub(crate) fn add_known(&mut self, lit: JsWord) { - self.projections.push(Projection::Known(lit)); +// I don't like it either, but in JS NaNs are compared bitwise +impl Eq for Literal {} + +impl Hash for Literal { + fn hash(&self, state: &mut H) { + mem::discriminant(self).hash(state); + match self { + Literal::Str(s) => s.hash(state), + Literal::Bool(b) => b.hash(state), + Literal::Null | Literal::Undef => {} + Literal::Number(n) => n.to_bits().hash(state), + Literal::BigInt(bn) => bn.hash(state), + Literal::RegExp(s, t) => (s, t).hash(state), + Literal::JSXText(r) => r.hash(state), + } } } diff --git a/crates/forge_analyzer/src/lib.rs b/crates/forge_analyzer/src/lib.rs index 4e54184d..58f004a9 100644 --- a/crates/forge_analyzer/src/lib.rs +++ b/crates/forge_analyzer/src/lib.rs @@ -1,4 +1,5 @@ pub mod analyzer; +pub mod checkers; pub mod ctx; pub mod definitions; pub mod engine; @@ -6,7 +7,10 @@ pub mod exports; pub mod interp; pub mod ir; pub mod lattice; +pub mod pretty; +pub mod reporter; pub mod resolver; +pub mod worklist; use ctx::ModuleCtx; use exports::ExportCollector; diff --git a/crates/forge_analyzer/src/pretty.rs b/crates/forge_analyzer/src/pretty.rs new file mode 100644 index 00000000..774a3229 --- /dev/null +++ b/crates/forge_analyzer/src/pretty.rs @@ -0,0 +1,32 @@ +use std::io::{self, Write}; + +use crate::{ + definitions::Environment, + ir::{Body, VarKind}, +}; + +pub fn dump_ir(output: &mut dyn Write, env: &Environment, body: &Body) -> io::Result<()> { + let name = body + .owner() + .map_or("__ANONYMOUS", |owner| env.def_name(owner)); + writeln!(output, "IR for {name}")?; + writeln!(output, "Variables:")?; + for (id, var) in body.iter_vars_enumerated() { + write!(output, "{id}: ")?; + match *var { + VarKind::LocalDef(def) => { + writeln!(output, "local definition of {}", env.def_name(def))? + } + VarKind::GlobalRef(def) => writeln!(output, "global ref to {}", env.def_name(def))?, + VarKind::Temp { parent: _parent } => writeln!(output, "temporary")?, + VarKind::AnonClosure(_) => writeln!(output, "closure")?, + VarKind::Arg(_) => writeln!(output, "arg")?, + VarKind::Ret => writeln!(output, "return value")?, + } + } + + for (id, block) in body.iter_blocks_enumerated() { + writeln!(output, "{id}:\n{block}")?; + } + Ok(()) +} diff --git a/crates/forge_analyzer/src/reporter.rs b/crates/forge_analyzer/src/reporter.rs new file mode 100644 index 00000000..8bbabbce --- /dev/null +++ b/crates/forge_analyzer/src/reporter.rs @@ -0,0 +1,109 @@ +use std::mem; + +use serde::Serialize; +use time::{Date, OffsetDateTime}; + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)] +pub enum Severity { + Low, + Medium, + High, + Critical, +} + +// TODO: Can probably use [`Rc`] instead of [`String`] +#[derive(Debug, Clone, PartialEq, Eq, Serialize)] +pub struct Vulnerability { + check_name: String, + description: &'static str, + recommendation: &'static str, + proof: String, + severity: Severity, + app_key: String, + app_name: String, + date: Date, +} + +pub trait IntoVuln { + fn into_vuln(self, reporter: &Reporter) -> Vulnerability; +} + +#[derive(Debug, Clone, PartialEq, Eq, Serialize)] +pub struct Report { + vulns: Vec, + scanner: &'static str, + #[serde(with = "time::serde::iso8601")] + started_at: OffsetDateTime, + #[serde(with = "time::serde::iso8601")] + ended_at: OffsetDateTime, + scanned: Vec, + errors: bool, +} + +pub struct Reporter { + vulns: Vec, + started_at: OffsetDateTime, + // (key, name) + apps: Vec<(String, String)>, + current_app: usize, +} + +impl Reporter { + #[inline] + pub fn new() -> Self { + Self { + vulns: Vec::new(), + started_at: OffsetDateTime::now_utc(), + apps: Vec::new(), + current_app: 0, + } + } + + #[inline] + pub fn add_app(&mut self, key: String, name: String) { + self.apps.push((key, name)); + self.current_app = self.apps.len() - 1; + } + + #[inline] + pub fn app_name(&self) -> &str { + &self.apps[self.current_app].1 + } + + #[inline] + pub fn app_key(&self) -> &str { + &self.apps[self.current_app].0 + } + + #[inline] + pub fn current_date(&self) -> Date { + self.started_at.date() + } + + pub fn add_vulnerabilities(&mut self, vuln_reports: impl IntoIterator) { + let vuln_reports = vuln_reports.into_iter(); + self.vulns.reserve(vuln_reports.size_hint().0); + for vuln in vuln_reports { + self.vulns.push(vuln.into_vuln(self)); + } + } + + #[inline] + pub fn into_report(self) -> Report { + Report { + vulns: self.vulns, + scanner: "Forge", + started_at: self.started_at, + ended_at: OffsetDateTime::now_utc(), + scanned: self.apps.into_iter().map(|(key, _)| key).collect(), + errors: false, + } + } +} + +impl Default for Reporter { + #[inline] + fn default() -> Self { + Self::new() + } +} diff --git a/crates/forge_analyzer/src/worklist.rs b/crates/forge_analyzer/src/worklist.rs new file mode 100644 index 00000000..708e3df5 --- /dev/null +++ b/crates/forge_analyzer/src/worklist.rs @@ -0,0 +1,101 @@ +use std::{borrow::Borrow, collections::VecDeque, hash::Hash}; + +use forge_utils::FxHashSet; +use tracing::debug; + +use crate::{ + definitions::{DefId, Environment}, + ir::BasicBlockId, +}; + +#[derive(Debug, Clone)] +pub struct WorkList { + worklist: VecDeque<(V, W)>, + visited: FxHashSet, +} + +impl WorkList +where + V: Eq + Hash, +{ + pub(crate) fn new() -> Self { + Self { + worklist: VecDeque::new(), + visited: FxHashSet::default(), + } + } + + #[inline] + pub(crate) fn pop_front(&mut self) -> Option<(V, W)> { + self.worklist.pop_front() + } + + #[inline] + pub(crate) fn len(&self) -> usize { + self.worklist.len() + } + + #[inline] + pub(crate) fn is_empty(&self) -> bool { + self.worklist.is_empty() + } + + #[inline] + pub(crate) fn reserve(&mut self, n: usize) { + self.worklist.reserve(n); + } + + #[inline] + pub(crate) fn visited(&self, key: &Q) -> bool + where + V: Borrow, + Q: Eq + Hash + ?Sized, + { + self.visited.contains(key) + } +} + +impl WorkList +where + V: Eq + Hash + Copy, +{ + #[inline] + pub(crate) fn push_back(&mut self, v: V, w: W) { + if self.visited.insert(v) { + self.worklist.push_back((v, w)); + } + } + + #[inline] + pub(crate) fn push_back_force(&mut self, v: V, w: W) { + self.worklist.push_back((v, w)); + } +} + +impl WorkList { + #[inline] + pub(crate) fn push_front_blocks(&mut self, env: &Environment, def: DefId) -> bool { + if self.visited.insert(def) { + debug!("adding function: {}", env.def_name(def)); + let body = env.def_ref(def).expect_body(); + let blocks = body.iter_block_keys().map(|bb| (def, bb)).rev(); + self.worklist.reserve(blocks.len()); + for work in blocks { + debug!(?work, "push_front_blocks"); + self.worklist.push_front(work); + } + return true; + } + false + } +} + +impl Extend<(V, W)> for WorkList +where + V: Eq + Hash, +{ + #[inline] + fn extend>(&mut self, iter: T) { + self.worklist.extend(iter); + } +} diff --git a/crates/forge_loader/Cargo.toml b/crates/forge_loader/Cargo.toml index 7b75ffa9..d7fd349d 100644 --- a/crates/forge_loader/Cargo.toml +++ b/crates/forge_loader/Cargo.toml @@ -9,6 +9,7 @@ license.workspace = true [dependencies] clap.workspace = true +forge_utils.workspace = true itertools.workspace = true miette.workspace = true serde.workspace = true diff --git a/crates/forge_loader/src/manifest.rs b/crates/forge_loader/src/manifest.rs index 8f8bdf49..2b89fe18 100644 --- a/crates/forge_loader/src/manifest.rs +++ b/crates/forge_loader/src/manifest.rs @@ -6,6 +6,7 @@ use std::{ }; use crate::Error; +use forge_utils::FxHashMap; use itertools::{Either, Itertools}; use serde::Deserialize; use tracing::trace; @@ -79,6 +80,8 @@ pub struct ForgeModules<'a> { scheduled_triggers: Vec>, #[serde(rename = "consumer", default, borrow)] consumers: Vec>, + #[serde(flatten)] + extra: FxHashMap, } #[derive(Debug, Clone, PartialEq, Eq, Deserialize)] diff --git a/crates/fsrt/src/main.rs b/crates/fsrt/src/main.rs index 1408ece1..b7e8302d 100644 --- a/crates/fsrt/src/main.rs +++ b/crates/fsrt/src/main.rs @@ -3,7 +3,7 @@ use std::{ collections::HashSet, convert::TryFrom, fs, - io::stdout, + io::{self, stdout, Write as _}, os::unix::prelude::OsStrExt, path::{Path, PathBuf}, sync::Arc, @@ -27,9 +27,12 @@ use tracing_tree::HierarchicalLayer; use forge_analyzer::{ analyzer::AuthZVal, + checkers::AuthZChecker, ctx::{AppCtx, ModId, ModItem}, - definitions::run_resolver, + definitions::{run_resolver, DefId, Environment}, engine::Machine, + interp::Interp, + pretty::dump_ir, resolver::{dump_callgraph_dot, dump_cfg_dot, resolve_calls}, }; use forge_file_resolver::FileResolver; @@ -70,48 +73,12 @@ struct ForgeProject { #[allow(dead_code)] sm: Arc, ctx: AppCtx, - funcs: Vec>, + env: Environment, + funcs: Vec>, opts: Opts, } impl ForgeProject { - #[instrument(level = "debug", skip(self))] - fn verify_funs(&mut self) -> impl Iterator + '_ { - self.funcs.iter().cloned().map(|fty| { - let (modid, func) = fty.into_inner(); - // TODO(perf): reuse the same `Machine` between iterations - let func_item = ModItem::new(modid, func.clone()); - let mut machine = Machine::new(modid, func.clone(), &mut self.ctx); - let res = (modid, func, machine.run()); - if self.opts.dump_callgraph { - let _ = dump_cfg_dot(&self.ctx, &func_item, stdout()); - } - res - }) - } - - fn verify_fun(&mut self, func: &str) -> (ModItem, AuthZVal) { - let (modid, ref ident) = *self - .funcs - .iter() - .find(|&ty| { - let (_, ref ident) = *ty.as_ref(); - *ident.0 == *func - }) - .unwrap() - .as_ref(); - let funcitem = ModItem::new(modid, ident.clone()); - let mut machine = Machine::new(modid, ident.clone(), &mut self.ctx); - let res = machine.run(); - if self.opts.dump_cfg { - let _ = dump_cfg_dot(&self.ctx, &funcitem, stdout()); - } - if self.opts.dump_callgraph { - let _ = dump_callgraph_dot(&self.ctx, &funcitem, stdout()); - } - (funcitem, res) - } - fn with_files_and_sourceroot, I: IntoIterator>( src: P, iter: I, @@ -144,9 +111,11 @@ impl ForgeProject { }); let keys = ctx.module_ids().collect::>(); debug!(?keys); + let env = run_resolver(ctx.modules(), ctx.file_resolver()); Self { sm, ctx, + env, funcs: vec![], opts: Opts::default(), } @@ -156,7 +125,7 @@ impl ForgeProject { self.funcs.extend(iter.into_iter().flat_map(|ftype| { ftype.sequence(|(func, path)| { let modid = self.ctx.modid_from_path(&path)?; - let func = self.ctx.export(modid, func)?; + let func = self.env.module_export(modid, func)?; Some((modid, func)) }) })); @@ -203,7 +172,6 @@ fn scan_directory(dir: PathBuf, function: Option<&str>, opts: Opts) -> Result, opts: Opts) -> Result, opts: Opts) -> Result { + interp.run_checker(def, &mut checker); + } + FunctionTy::WebTrigger((_, def)) => {} } } Ok(proj) diff --git a/test-apps/issue-1-resolver-with-vuln/manifest.yml b/test-apps/issue-1-resolver-with-vuln/manifest.yml new file mode 100644 index 00000000..89f4b9b8 --- /dev/null +++ b/test-apps/issue-1-resolver-with-vuln/manifest.yml @@ -0,0 +1,20 @@ +modules: + jira:globalPage: + - key: forge-sandbox + resource: web + resolver: + function: resolver + layout: basic + title: Forge Sandbox + function: + - key: resolver + handler: index.handler +resources: + - key: web + path: web +permissions: + content: + styles: + - unsafe-inline +app: + id: ari:cloud:ecosystem::app/register_to_get_id diff --git a/test-apps/issue-1-resolver-with-vuln/src/index.js b/test-apps/issue-1-resolver-with-vuln/src/index.js new file mode 100644 index 00000000..ad2ed34b --- /dev/null +++ b/test-apps/issue-1-resolver-with-vuln/src/index.js @@ -0,0 +1,47 @@ +// src/index.ts +import Resolver from '@forge/resolver'; +import api, { route } from '@forge/api'; + +// src/lib/get-text.ts +function getText({ text }) { + api.asApp().requestJira(route`rest/api/3/issue`); + return 'Hello, world!\n' + text; +} + +// src/lib/permissions.ts +import { authorize } from '@forge/api'; +var administerPermission = 'ADMINISTER'; +function isGlobalAdminPermission(permission) { + return permission.permission === administerPermission; +} +async function isJiraGlobalAdmin() { + const permissions = await authorize().onJira([ + { permissions: [administerPermission] }, + ]); + return permissions.every(isGlobalAdminPermission); +} + +// src/index.ts +var resolver = new Resolver(); +resolver.define('getText' /* getText */, async (req) => { + console.log('called getText()'); + await requireAccess({ req }); + const accountId = requireAccountId(req); + //const payload = getTextSchema.parse(req.payload); + const payload = { text: 'hi' }; + console.log('accessed getText()'); + return getText({ ...payload, accountId }); +}); + +async function requireAccess({ req }) { + const isAdmin = await isJiraGlobalAdmin(); + if (!isAdmin) { + throw new Error('not permitted'); + } +} +function requireAccountId(req) { + //return mod.string().parse(req.context.accountId); + return 'hi'; +} +var handler = resolver.getDefinitions(); +export { handler }; From 6bb6b116c4f07f7f05bf55ae2f1db7db2aed426d Mon Sep 17 00:00:00 2001 From: Joshua Wong Date: Tue, 20 Dec 2022 19:34:03 -0600 Subject: [PATCH 029/517] build(fsrt): add serde-json --- Cargo.lock | 1 + crates/fsrt/Cargo.toml | 1 + 2 files changed, 2 insertions(+) diff --git a/Cargo.lock b/Cargo.lock index 2d6114ec..6ff8c230 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -605,6 +605,7 @@ dependencies = [ "miette 5.5.0", "rustc-hash", "serde", + "serde_json", "serde_yaml", "swc_core", "tracing", diff --git a/crates/fsrt/Cargo.toml b/crates/fsrt/Cargo.toml index 55e1ec06..fb344a97 100644 --- a/crates/fsrt/Cargo.toml +++ b/crates/fsrt/Cargo.toml @@ -15,6 +15,7 @@ forge_loader.workspace = true miette.workspace = true rustc-hash.workspace = true serde.workspace = true +serde_json.workspace = true serde_yaml.workspace = true swc_core.workspace = true tracing-subscriber.workspace = true From 1bc7ec683ace7f38dc0b3133cbe5a3c3d079cfb5 Mon Sep 17 00:00:00 2001 From: Joshua Wong Date: Tue, 20 Dec 2022 19:34:46 -0600 Subject: [PATCH 030/517] feat: make AppInfo fields public --- crates/forge_loader/src/manifest.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/crates/forge_loader/src/manifest.rs b/crates/forge_loader/src/manifest.rs index 2b89fe18..251b4225 100644 --- a/crates/forge_loader/src/manifest.rs +++ b/crates/forge_loader/src/manifest.rs @@ -115,9 +115,9 @@ struct Perms<'a> { } #[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] -struct AppInfo<'a> { - name: Option<&'a str>, - id: &'a str, +pub struct AppInfo<'a> { + pub name: Option<&'a str>, + pub id: &'a str, } /// The representation of a Forge app's `manifest.yml` @@ -128,7 +128,7 @@ struct AppInfo<'a> { #[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] pub struct ForgeManifest<'a> { #[serde(borrow)] - app: AppInfo<'a>, + pub app: AppInfo<'a>, #[serde(borrow)] pub modules: ForgeModules<'a>, #[serde(borrow)] From 3e266354587ff0c8c916589c9cde6fc5f23514ce Mon Sep 17 00:00:00 2001 From: Joshua Wong Date: Tue, 20 Dec 2022 19:35:24 -0600 Subject: [PATCH 031/517] feat: serialize AuthZVuln --- crates/forge_analyzer/src/checkers.rs | 39 +++++++++++++++++++++++---- crates/forge_analyzer/src/reporter.rs | 18 ++++++------- crates/fsrt/src/main.rs | 30 ++++++++++++++++++--- 3 files changed, 70 insertions(+), 17 deletions(-) diff --git a/crates/forge_analyzer/src/checkers.rs b/crates/forge_analyzer/src/checkers.rs index acdd9e6b..1ec3ff78 100644 --- a/crates/forge_analyzer/src/checkers.rs +++ b/crates/forge_analyzer/src/checkers.rs @@ -1,12 +1,14 @@ use core::fmt; use std::{cmp::max, mem, ops::ControlFlow}; +use itertools::Itertools; use tracing::{debug, info}; use crate::{ - definitions::DefId, + definitions::{DefId, Environment}, interp::{Checker, Dataflow, Frame, Interp, JoinSemiLattice, WithCallStack}, ir::{BasicBlock, BasicBlockId, Intrinsic, Location}, + reporter::{Vulnerability, IntoVuln, Reporter, Severity}, worklist::WorkList, }; @@ -118,6 +120,11 @@ impl AuthZChecker { pub fn new() -> Self { Self { vulns: vec![] } } + + pub fn into_vulns(self) -> impl IntoIterator { + // TODO: make this an associated function on the Checker trait. + self.vulns.into_iter() + } } impl Default for AuthZChecker { @@ -128,7 +135,16 @@ impl Default for AuthZChecker { #[derive(Debug)] pub struct AuthZVuln { - stackframe: Vec, + stack: String, +} + +impl AuthZVuln { + fn new(mut callstack: Vec, env: &Environment) -> Self { + let stack = callstack.into_iter().rev().map(|frame| env.def_name(frame.calling_function)).intersperse(" -> ").collect(); + Self { + stack, + } + } } impl fmt::Display for AuthZVuln { @@ -137,6 +153,21 @@ impl fmt::Display for AuthZVuln { } } +impl IntoVuln for AuthZVuln { + fn into_vuln(self, reporter: &Reporter) -> Vulnerability { + Vulnerability { + check_name: "Custom_Check".to_owned(), + description: "Authorization bypass detected.", + recommendation: "", + proof: format!("API call via asApp() found via {}", self.stack), + severity: Severity::High, + app_key: reporter.app_key().to_owned(), + app_name: reporter.app_name().to_owned(), + date: reporter.current_date(), + } + } +} + impl WithCallStack for AuthZVuln { fn add_call_stack(&mut self, stack: Vec) {} } @@ -159,9 +190,7 @@ impl<'cx> Checker<'cx> for AuthZChecker { } Intrinsic::Fetch => ControlFlow::Continue(*state), Intrinsic::ApiCall if *state == AuthorizeState::No => { - let vuln = AuthZVuln { - stackframe: interp.callstack(), - }; + let vuln = AuthZVuln::new(interp.callstack(), interp.env()); info!("Found a vuln!"); self.vulns.push(vuln); ControlFlow::Break(()) diff --git a/crates/forge_analyzer/src/reporter.rs b/crates/forge_analyzer/src/reporter.rs index 8bbabbce..bf0a086b 100644 --- a/crates/forge_analyzer/src/reporter.rs +++ b/crates/forge_analyzer/src/reporter.rs @@ -14,14 +14,14 @@ pub enum Severity { // TODO: Can probably use [`Rc`] instead of [`String`] #[derive(Debug, Clone, PartialEq, Eq, Serialize)] pub struct Vulnerability { - check_name: String, - description: &'static str, - recommendation: &'static str, - proof: String, - severity: Severity, - app_key: String, - app_name: String, - date: Date, + pub(crate) check_name: String, + pub(crate) description: &'static str, + pub(crate) recommendation: &'static str, + pub(crate) proof: String, + pub(crate) severity: Severity, + pub(crate) app_key: String, + pub(crate) app_name: String, + pub(crate) date: Date, } pub trait IntoVuln { @@ -92,7 +92,7 @@ impl Reporter { pub fn into_report(self) -> Report { Report { vulns: self.vulns, - scanner: "Forge", + scanner: "FSRT", started_at: self.started_at, ended_at: OffsetDateTime::now_utc(), scanned: self.apps.into_iter().map(|(key, _)| key).collect(), diff --git a/crates/fsrt/src/main.rs b/crates/fsrt/src/main.rs index b7e8302d..77f9d792 100644 --- a/crates/fsrt/src/main.rs +++ b/crates/fsrt/src/main.rs @@ -33,6 +33,7 @@ use forge_analyzer::{ engine::Machine, interp::Interp, pretty::dump_ir, + reporter::Reporter, resolver::{dump_callgraph_dot, dump_cfg_dot, resolve_calls}, }; use forge_file_resolver::FileResolver; @@ -57,16 +58,26 @@ struct Args { #[arg(short, long)] function: Option, + /// The Marketplace app key. + #[arg(long)] + appkey: Option, + + /// A file to redirect output to. + #[arg(short, long)] + out: Option, + /// The directory to scan. Assumes there is a `manifest.ya?ml` file in the top level /// directory, and that the source code is located in `src/` #[arg(name = "DIRS", value_hint = ValueHint::DirPath)] dirs: Vec, } -#[derive(Debug, Clone, Copy, Default)] +#[derive(Debug, Clone, Default)] struct Opts { dump_cfg: bool, dump_callgraph: bool, + appkey: Option, + out: Option, } struct ForgeProject { @@ -160,6 +171,7 @@ fn scan_directory(dir: PathBuf, function: Option<&str>, opts: Opts) -> Result>(); let funcrefs = manifest.modules.into_analyzable_functions().flat_map(|f| { f.sequence(|fmod| { @@ -169,7 +181,7 @@ fn scan_directory(dir: PathBuf, function: Option<&str>, opts: Opts) -> Result, opts: Opts) -> Result { @@ -226,6 +240,14 @@ fn scan_directory(dir: PathBuf, function: Option<&str>, opts: Opts) -> Result {} } } + reporter.add_vulnerabilities(checker.into_vulns()); + let report = serde_json::to_string(&reporter.into_report()).into_diagnostic()?; + match opts.out { + Some(path) => { + fs::write(path, report).into_diagnostic()?; + } + None => println!("{report}"), + } Ok(proj) } @@ -239,10 +261,12 @@ fn main() -> Result<()> { let opts = Opts { dump_callgraph: args.callgraph, dump_cfg: args.cfg, + out: args.out, + appkey: args.appkey, }; for dir in args.dirs { debug!(?dir); - scan_directory(dir, function, opts)?; + scan_directory(dir, function, opts.clone())?; } Ok(()) } From bb7961e8e69a185bffd8764067b29a2fffee15ef Mon Sep 17 00:00:00 2001 From: Joshua Wong Date: Wed, 21 Dec 2022 13:53:08 -0600 Subject: [PATCH 032/517] feat: use IR 2.0 with AuthN checker --- crates/forge_analyzer/src/checkers.rs | 266 ++++++++++++++++++++++- crates/forge_analyzer/src/definitions.rs | 184 +++++++++++++--- crates/forge_analyzer/src/interp.rs | 35 ++- crates/forge_analyzer/src/reporter.rs | 2 +- crates/fsrt/src/main.rs | 85 +++----- 5 files changed, 474 insertions(+), 98 deletions(-) diff --git a/crates/forge_analyzer/src/checkers.rs b/crates/forge_analyzer/src/checkers.rs index 1ec3ff78..8b9a3125 100644 --- a/crates/forge_analyzer/src/checkers.rs +++ b/crates/forge_analyzer/src/checkers.rs @@ -1,14 +1,16 @@ use core::fmt; -use std::{cmp::max, mem, ops::ControlFlow}; use itertools::Itertools; +use std::{cmp::max, iter, mem, ops::ControlFlow, path::PathBuf}; -use tracing::{debug, info}; +use tracing::{debug, info, warn}; use crate::{ definitions::{DefId, Environment}, - interp::{Checker, Dataflow, Frame, Interp, JoinSemiLattice, WithCallStack}, + interp::{ + Checker, Dataflow, EntryKind, EntryPoint, Frame, Interp, JoinSemiLattice, WithCallStack, + }, ir::{BasicBlock, BasicBlockId, Intrinsic, Location}, - reporter::{Vulnerability, IntoVuln, Reporter, Severity}, + reporter::{IntoVuln, Reporter, Severity, Vulnerability}, worklist::WorkList, }; @@ -136,13 +138,34 @@ impl Default for AuthZChecker { #[derive(Debug)] pub struct AuthZVuln { stack: String, + entry_func: String, + file: PathBuf, } impl AuthZVuln { - fn new(mut callstack: Vec, env: &Environment) -> Self { - let stack = callstack.into_iter().rev().map(|frame| env.def_name(frame.calling_function)).intersperse(" -> ").collect(); + fn new(mut callstack: Vec, env: &Environment, entry: &EntryPoint) -> Self { + let entry_func = match &entry.kind { + EntryKind::Function(func) => func.clone(), + EntryKind::Resolver(res, prop) => format!("{res}.{prop}"), + EntryKind::Empty => { + warn!("empty function"); + String::new() + } + }; + let file = entry.file.clone(); + let stack = iter::once(&*entry_func) + .chain( + callstack + .into_iter() + .rev() + .map(|frame| env.def_name(frame.calling_function)), + ) + .intersperse(" -> ") + .collect(); Self { stack, + entry_func, + file, } } } @@ -155,11 +178,17 @@ impl fmt::Display for AuthZVuln { impl IntoVuln for AuthZVuln { fn into_vuln(self, reporter: &Reporter) -> Vulnerability { + use std::collections::hash_map::DefaultHasher; + use std::hash::{Hash, Hasher}; + + let mut hasher = DefaultHasher::new(); + self.file.hash(&mut hasher); + self.entry_func.hash(&mut hasher); Vulnerability { - check_name: "Custom_Check".to_owned(), - description: "Authorization bypass detected.", - recommendation: "", - proof: format!("API call via asApp() found via {}", self.stack), + check_name: format!("Custom-Check-Authorization-{}", hasher.finish()), + description: format!("Authorization bypass detected through {} in {:?}.", self.entry_func, self.file), + recommendation: "Use the authorize API _https://developer.atlassian.com/platform/forge/runtime-reference/authorize-api/_ or manually authorize the user via the product REST APIs.", + proof: format!("Unauthorized API call via asApp() found via {}", self.stack), severity: Severity::High, app_key: reporter.app_key().to_owned(), app_name: reporter.app_name().to_owned(), @@ -190,7 +219,7 @@ impl<'cx> Checker<'cx> for AuthZChecker { } Intrinsic::Fetch => ControlFlow::Continue(*state), Intrinsic::ApiCall if *state == AuthorizeState::No => { - let vuln = AuthZVuln::new(interp.callstack(), interp.env()); + let vuln = AuthZVuln::new(interp.callstack(), interp.env(), interp.entry()); info!("Found a vuln!"); self.vulns.push(vuln); ControlFlow::Break(()) @@ -202,3 +231,218 @@ impl<'cx> Checker<'cx> for AuthZChecker { } } } + +#[derive(Debug, PartialEq, Eq, Clone, Copy, PartialOrd, Ord)] +pub enum Authenticated { + No, + Yes, +} + +impl JoinSemiLattice for Authenticated { + const BOTTOM: Self = Self::No; + + #[inline] + fn join_changed(&mut self, other: &Self) -> bool { + let old = mem::replace(self, max(*other, *self)); + old == *self + } + + #[inline] + fn join(&self, other: &Self) -> Self { + max(*other, *self) + } +} + +pub struct AuthenticateDataflow { + needs_call: Vec, +} + +impl<'cx> Dataflow<'cx> for AuthenticateDataflow { + type State = Authenticated; + + fn with_interp>( + interp: &Interp<'cx, C>, + ) -> Self { + Self { needs_call: vec![] } + } + + fn transfer_intrinsic>( + &mut self, + _interp: &Interp<'cx, C>, + _def: DefId, + _loc: Location, + _block: &'cx BasicBlock, + intrinsic: &'cx Intrinsic, + initial_state: Self::State, + ) -> Self::State { + match *intrinsic { + Intrinsic::Authorize => initial_state, + Intrinsic::Fetch | Intrinsic::EnvRead | Intrinsic::StorageRead => { + debug!("authenticated"); + Authenticated::Yes + } + Intrinsic::ApiCall => initial_state, + Intrinsic::SafeCall => initial_state, + } + } + + fn transfer_call>( + &mut self, + interp: &Interp<'cx, C>, + def: DefId, + loc: Location, + block: &'cx BasicBlock, + callee: &'cx crate::ir::Operand, + initial_state: Self::State, + ) -> Self::State { + let Some((callee_def, body)) = self.resolve_call(interp, callee) else { + return initial_state; + }; + match interp.func_state(callee_def) { + Some(state) => { + if state == Authenticated::Yes { + debug!("Found call to authenticate at {def:?} {loc:?}"); + } + initial_state.join(&state) + } + None => { + let callee_name = interp.env().def_name(callee_def); + let caller_name = interp.env().def_name(def); + debug!("Found call to {callee_name} at {def:?} {caller_name}"); + self.needs_call.push(callee_def); + initial_state + } + } + } + + fn join_term>( + &mut self, + interp: &Interp<'cx, C>, + def: DefId, + block: &'cx BasicBlock, + state: Self::State, + worklist: &mut WorkList, + ) { + self.super_join_term(interp, def, block, state, worklist); + for def in self.needs_call.drain(..) { + worklist.push_front_blocks(interp.env(), def); + } + } +} + +pub struct AuthenticateChecker { + vulns: Vec, +} + +impl AuthenticateChecker { + pub fn new() -> Self { + Self { vulns: vec![] } + } + + pub fn into_vulns(self) -> impl IntoIterator { + self.vulns.into_iter() + } +} + +impl Default for AuthenticateChecker { + fn default() -> Self { + Self::new() + } +} + +impl<'cx> Checker<'cx> for AuthenticateChecker { + type State = Authenticated; + type Dataflow = AuthenticateDataflow; + type Vuln = AuthNVuln; + + fn visit_intrinsic( + &mut self, + interp: &Interp<'cx, Self>, + intrinsic: &'cx Intrinsic, + state: &Self::State, + ) -> ControlFlow<(), Self::State> { + match *intrinsic { + Intrinsic::Authorize => ControlFlow::Continue(*state), + Intrinsic::Fetch + | Intrinsic::EnvRead + | Intrinsic::StorageRead => { + debug!("authenticated"); + ControlFlow::Continue(Authenticated::Yes) + } + Intrinsic::ApiCall if *state == Authenticated::No => { + let vuln = AuthNVuln::new(interp.callstack(), interp.env(), interp.entry()); + info!("Found a vuln!"); + self.vulns.push(vuln); + ControlFlow::Break(()) + } + Intrinsic::ApiCall => ControlFlow::Continue(*state), + Intrinsic::SafeCall => ControlFlow::Continue(*state), + } + } +} + +#[derive(Debug)] +pub struct AuthNVuln { + stack: String, + entry_func: String, + file: PathBuf, +} + +impl AuthNVuln { + fn new(mut callstack: Vec, env: &Environment, entry: &EntryPoint) -> Self { + let entry_func = match &entry.kind { + EntryKind::Function(func) => func.clone(), + EntryKind::Resolver(res, prop) => format!("{res}.{prop}"), + EntryKind::Empty => { + warn!("empty function"); + String::new() + } + }; + let file = entry.file.clone(); + let stack = iter::once(&*entry_func) + .chain( + callstack + .into_iter() + .rev() + .map(|frame| env.def_name(frame.calling_function)), + ) + .intersperse(" -> ") + .collect(); + Self { + stack, + entry_func, + file, + } + } +} + +impl fmt::Display for AuthNVuln { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "Authentication vulnerability") + } +} + +impl IntoVuln for AuthNVuln { + fn into_vuln(self, reporter: &Reporter) -> Vulnerability { + use std::collections::hash_map::DefaultHasher; + use std::hash::{Hash, Hasher}; + + let mut hasher = DefaultHasher::new(); + self.file.hash(&mut hasher); + self.entry_func.hash(&mut hasher); + Vulnerability { + check_name: format!("Custom-Check-Authentication-{}", hasher.finish()), + description: format!("Insufficient Authentication through webhook {} in {:?}.", self.entry_func, self.file), + recommendation: "Properly authenticate incoming webhooks and ensure that any shared secrets are stored in Forge Secure Storage.", + proof: format!("Unauthenticated API call via asApp() found via {}", self.stack), + severity: Severity::High, + app_key: reporter.app_key().to_owned(), + app_name: reporter.app_name().to_owned(), + date: reporter.current_date(), + } + } +} + +impl WithCallStack for AuthNVuln { + fn add_call_stack(&mut self, stack: Vec) {} +} diff --git a/crates/forge_analyzer/src/definitions.rs b/crates/forge_analyzer/src/definitions.rs index 9345af33..2c2dee5d 100644 --- a/crates/forge_analyzer/src/definitions.rs +++ b/crates/forge_analyzer/src/definitions.rs @@ -15,18 +15,19 @@ use swc_core::{ DefaultDecl, DoWhileStmt, ExportAll, ExportDecl, ExportDefaultDecl, ExportDefaultExpr, ExportNamedSpecifier, Expr, ExprOrSpread, ExprStmt, FnDecl, FnExpr, ForInStmt, ForOfStmt, ForStmt, Function, Id, Ident, IfStmt, Import, ImportDecl, - ImportDefaultSpecifier, ImportNamedSpecifier, ImportStarAsSpecifier, JSXFragment, - JSXMemberExpr, JSXNamespacedName, KeyValuePatProp, KeyValueProp, LabeledStmt, Lit, - MemberExpr, MemberProp, MetaPropExpr, MethodProp, Module, ModuleDecl, ModuleExportName, - ModuleItem, NewExpr, Number, ObjectLit, ObjectPat, ObjectPatProp, OptCall, - OptChainBase, OptChainExpr, ParenExpr, Pat, PatOrExpr, PrivateName, Prop, PropName, - PropOrSpread, RestPat, ReturnStmt, SeqExpr, Stmt, Str, Super, SuperProp, SuperPropExpr, - SwitchStmt, TaggedTpl, ThisExpr, ThrowStmt, Tpl, TplElement, TryStmt, TsAsExpr, - TsConstAssertion, TsInstantiation, TsNonNullExpr, TsSatisfiesExpr, TsTypeAssertion, - UnaryExpr, UpdateExpr, VarDecl, VarDeclOrExpr, VarDeclOrPat, VarDeclarator, WhileStmt, - WithStmt, YieldExpr, + ImportDefaultSpecifier, ImportNamedSpecifier, ImportStarAsSpecifier, JSXElement, + JSXElementChild, JSXElementName, JSXExpr, JSXExprContainer, JSXFragment, JSXMemberExpr, + JSXNamespacedName, JSXSpreadChild, JSXText, KeyValuePatProp, KeyValueProp, LabeledStmt, + Lit, MemberExpr, MemberProp, MetaPropExpr, MethodProp, Module, ModuleDecl, + ModuleExportName, ModuleItem, NewExpr, Number, ObjectLit, ObjectPat, ObjectPatProp, + OptCall, OptChainBase, OptChainExpr, ParenExpr, Pat, PatOrExpr, PrivateName, Prop, + PropName, PropOrSpread, RestPat, ReturnStmt, SeqExpr, Stmt, Str, Super, SuperProp, + SuperPropExpr, SwitchStmt, TaggedTpl, ThisExpr, ThrowStmt, Tpl, TplElement, TryStmt, + TsAsExpr, TsConstAssertion, TsInstantiation, TsNonNullExpr, TsSatisfiesExpr, + TsTypeAssertion, UnaryExpr, UpdateExpr, VarDecl, VarDeclOrExpr, VarDeclOrPat, + VarDeclarator, WhileStmt, WithStmt, YieldExpr, JSXObject, }, - atoms::JsWord, + atoms::{JsWord, Atom}, visit::{noop_visit_type, Visit, VisitWith}, }, }; @@ -911,20 +912,47 @@ impl<'cx> FunctionAnalyzer<'cx> { } } + fn lower_jsx_member(&mut self, n: &JSXMemberExpr) -> Operand { + let mut var = match &n.obj { + JSXObject::JSXMemberExpr(obj) => self.lower_jsx_member(&obj), + JSXObject::Ident(ident) => self.lower_ident(&ident), + }; + if let Operand::Var(var) = &mut var { + var.projections.push(Projection::Known(n.prop.sym.clone())); + } + var + } + fn lower_call(&mut self, callee: CalleeRef<'_>, args: &[ExprOrSpread]) -> Operand { - let lowered_args = args.iter().map(|arg| self.lower_expr(&arg.expr)).collect(); let props = normalize_callee_expr(callee, self.res, self.module); - match props.first() { - Some(&PropPath::Def(id)) => { - debug!("call from: {}", self.res.def_name(id)); - debug!("call expr: {:?}", props); - } - Some(PropPath::Unknown(id)) => { - debug!("call from: {}", id.0); - debug!("call expr: {:?}", props); + if let Some(&PropPath::Def(id)) = props.first() { + if self.res.as_foreign_import(id, "@forge/ui").map_or(false, |imp| matches!(imp, ImportKind::Named(s) if *s == *"useState")) { + if let [ExprOrSpread { expr, .. }] = args { + debug!("found useState"); + match &**expr { + Expr::Arrow(ArrowExpr {body, ..}) => { + match body { + BlockStmtOrExpr::BlockStmt(stmt) => { + self.lower_stmts(&stmt.stmts); + return Operand::UNDEF; + } + BlockStmtOrExpr::Expr(expr) => { + return self.lower_expr(&expr); + } + } + } + Expr::Fn(FnExpr { ident: _, function }) => { + if let Some(body) = &function.body { + self.lower_stmts(&body.stmts); + return Operand::UNDEF; + } + } + _ => {} + } + } } - _ => (), } + let lowered_args = args.iter().map(|arg| self.lower_expr(&arg.expr)).collect(); let callee = match callee { CalleeRef::Super => Operand::Var(Variable::SUPER), CalleeRef::Import => Operand::UNDEF, @@ -986,6 +1014,72 @@ impl<'cx> FunctionAnalyzer<'cx> { } } + fn lower_jsx_child(&mut self, n: &JSXElementChild) -> Operand { + match n { + JSXElementChild::JSXText(JSXText { value, .. }) => { + let value = JsWord::from(value.to_string()); + Operand::Lit(Literal::Str(value)) + } + JSXElementChild::JSXExprContainer(JSXExprContainer { expr, .. }) => { + match expr { + JSXExpr::JSXEmptyExpr(_) => Operand::UNDEF, + JSXExpr::Expr(expr) => self.lower_expr(expr), + } + } + JSXElementChild::JSXSpreadChild(JSXSpreadChild { expr, .. }) => self.lower_expr(expr), + JSXElementChild::JSXElement(elem) => self.lower_jsx_elem(elem), + JSXElementChild::JSXFragment(JSXFragment { children, .. }) => { + for child in children { + self.lower_jsx_child(child); + } + Operand::UNDEF + } + } + } + + fn lower_ident(&mut self, ident: &Ident) -> Operand { + let id = ident.to_id(); + let Some(def) = self.res.sym_to_id(id.clone(), self.module) else { + warn!("unknown symbol: {}", id.0); + return Literal::Undef.into(); + }; + let var = self.body.get_or_insert_global(def); + Operand::with_var(var) + } + + fn lower_jsx_elem(&mut self, n: &JSXElement) -> Operand { + let args = n + .children + .iter() + .map(|child| self.lower_jsx_child(child)) + .collect(); + let callee = match &n.opening.name { + JSXElementName::Ident(ident) => { + let id = ident.to_id(); + let Some(def) = self.res.sym_to_id(id.clone(), self.module) else { + warn!("unknown symbol: {}", id.0); + return Literal::Undef.into(); + }; + let var = self.body.get_or_insert_global(def); + Operand::with_var(var) + } + JSXElementName::JSXMemberExpr(mem) => self.lower_jsx_member(&mem), + JSXElementName::JSXNamespacedName(JSXNamespacedName { ns, name }) => { + let ns = ns.to_id(); + let Some(def) = self.res.sym_to_id(ns.clone(), self.module) else { + warn!("unknown symbol: {}", ns.0); + return Literal::Undef.into(); + }; + let var = self.body.get_or_insert_global(def); + let mut var = Variable::new(var); + var.projections.push(Projection::Known(name.sym.clone())); + Operand::Var(var) + } + }; + let call = Rvalue::Call(callee, args); + Operand::with_var(self.body.push_tmp(self.block, call, None)) + } + // TODO: This can probably be made into a trait fn lower_expr(&mut self, n: &Expr) -> Operand { match n { @@ -1135,16 +1229,27 @@ impl<'cx> FunctionAnalyzer<'cx> { Expr::MetaProp(_) => Operand::UNDEF, Expr::Await(AwaitExpr { arg, .. }) => self.lower_expr(arg), Expr::Paren(ParenExpr { expr, .. }) => self.lower_expr(expr), - Expr::JSXMember(JSXMemberExpr { obj, prop, .. }) => todo!(), - Expr::JSXNamespacedName(JSXNamespacedName { ns, name, .. }) => todo!(), + Expr::JSXMember(mem) => self.lower_jsx_member(&mem), + Expr::JSXNamespacedName(JSXNamespacedName { ns, name, .. }) => { + let mut ident = self.lower_ident(&ns); + if let Operand::Var(var) = &mut ident { + var.projections.push(Projection::Known(name.sym.clone())); + } + ident + } Expr::JSXEmpty(_) => Operand::UNDEF, - Expr::JSXElement(elem) => todo!(), + Expr::JSXElement(elem) => self.lower_jsx_elem(&elem), Expr::JSXFragment(JSXFragment { opening, children, closing, .. - }) => todo!(), + }) => { + for child in children { + self.lower_jsx_child(child); + } + Operand::UNDEF + } Expr::TsTypeAssertion(TsTypeAssertion { expr, .. }) | Expr::TsConstAssertion(TsConstAssertion { expr, .. }) | Expr::TsNonNull(TsNonNullExpr { expr, .. }) @@ -1548,6 +1653,35 @@ impl Visit for FunctionCollector<'_> { self.visit_arrow_expr(arrow); self.parent = old_parent; } + Some(Expr::Call(CallExpr { callee: Callee::Expr(expr), args, .. })) => { + if let Expr::Ident(ident) = &**expr { + let ident = ident.to_id(); + let Some(def) = self.res.sym_to_id(ident, self.module) else { + return; + }; + if matches!(self.res.as_foreign_import(def, "@forge/ui"), Some(ImportKind::Named(imp)) if *imp == *"render") { + let owner = self.res.get_or_overwrite_sym(id, self.module, DefKind::Function(())); + let Some(ExprOrSpread { expr, .. }) = &args.first() else { return; }; + let old_parent = self.parent.replace(owner); + let mut analyzer = FunctionAnalyzer { + res: self.res, + module: self.module, + current_def: owner, + assigning_to: None, + body: Body::with_owner(owner), + block: BasicBlockId::default(), + operand_stack: vec![], + in_lhs: false, + }; + let opnd = analyzer.lower_expr(expr); + analyzer + .body + .push_inst(analyzer.block, Inst::Assign(RETURN_VAR, Rvalue::Read(opnd))); + *self.res.def_mut(owner).expect_body() = analyzer.body; + self.parent = old_parent; + } + } + } _ => {} } } diff --git a/crates/forge_analyzer/src/interp.rs b/crates/forge_analyzer/src/interp.rs index 8e21a957..68236178 100644 --- a/crates/forge_analyzer/src/interp.rs +++ b/crates/forge_analyzer/src/interp.rs @@ -5,10 +5,11 @@ use std::{ io::{self, Write}, iter, marker::PhantomData, - ops::ControlFlow, + ops::ControlFlow, path::{PathBuf, Path}, }; use forge_utils::{FxHashMap, FxHashSet}; +use swc_core::ecma::atoms::JsWord; use tracing::{debug, info, instrument, warn}; use crate::{ @@ -294,11 +295,26 @@ pub(crate) struct Frame { pub(crate) inst_idx: usize, } +#[derive(Clone, Debug, PartialEq, Eq, Default)] +pub(crate) enum EntryKind { + Function(String), + Resolver(String, JsWord), + #[default] + Empty, +} + +#[derive(Clone, Debug, PartialEq, Eq, Default)] +pub(crate) struct EntryPoint { + pub(crate) file: PathBuf, + pub(crate) kind: EntryKind, +} + pub struct Interp<'cx, C: Checker<'cx>> { env: &'cx Environment, // We can probably get rid of these RefCells by refactoring the Interp and Checker into // two fields in another struct. call_graph: CallGraph, + entry: EntryPoint, func_state: RefCell>, curr_body: Cell>, states: RefCell>, @@ -368,6 +384,7 @@ impl<'cx, C: Checker<'cx>> Interp<'cx, C> { Self { env, call_graph, + entry: Default::default(), func_state: RefCell::new(FxHashMap::default()), curr_body: Cell::new(None), states: RefCell::new(BTreeMap::new()), @@ -468,6 +485,11 @@ impl<'cx, C: Checker<'cx>> Interp<'cx, C> { }) } + #[inline] + pub(crate) fn entry(&self) -> &EntryPoint { + &self.entry + } + fn run(&mut self, func_def: DefId) { if self.dataflow_visited.contains(&func_def) { return; @@ -510,7 +532,11 @@ impl<'cx, C: Checker<'cx>> Interp<'cx, C> { } #[instrument(skip(self, checker))] - pub fn run_checker(&mut self, def: DefId, checker: &mut C) -> Result<(), Error> { + pub fn run_checker(&mut self, def: DefId, checker: &mut C, entry_file: PathBuf, fname: String) -> Result<(), Error> { + self.entry = EntryPoint { + file: entry_file, + kind: EntryKind::Function(fname), + }; let Err(error) = self.try_check_function(def, checker) else { return Ok(()); }; @@ -521,6 +547,11 @@ impl<'cx, C: Checker<'cx>> Interp<'cx, C> { info!("Found potential resolver"); for (name, prop) in resolver { debug!("Checking resolver prop: {name}"); + self.entry.kind = match std::mem::take(&mut self.entry.kind) { + EntryKind::Function(fname) => EntryKind::Resolver(fname, name.clone()), + EntryKind::Resolver(res, _) => EntryKind::Resolver(res, name.clone()), + EntryKind::Empty => unreachable!(), + }; if let Err(error) = self.try_check_function(prop, checker) { warn!("Resolver prop {name} failed: {error}"); } diff --git a/crates/forge_analyzer/src/reporter.rs b/crates/forge_analyzer/src/reporter.rs index bf0a086b..e0fa84bd 100644 --- a/crates/forge_analyzer/src/reporter.rs +++ b/crates/forge_analyzer/src/reporter.rs @@ -15,7 +15,7 @@ pub enum Severity { #[derive(Debug, Clone, PartialEq, Eq, Serialize)] pub struct Vulnerability { pub(crate) check_name: String, - pub(crate) description: &'static str, + pub(crate) description: String, pub(crate) recommendation: &'static str, pub(crate) proof: String, pub(crate) severity: Severity, diff --git a/crates/fsrt/src/main.rs b/crates/fsrt/src/main.rs index 77f9d792..917fe40d 100644 --- a/crates/fsrt/src/main.rs +++ b/crates/fsrt/src/main.rs @@ -3,7 +3,7 @@ use std::{ collections::HashSet, convert::TryFrom, fs, - io::{self, stdout, Write as _}, + io::{self, Write as _}, os::unix::prelude::OsStrExt, path::{Path, PathBuf}, sync::Arc, @@ -15,22 +15,20 @@ use miette::{IntoDiagnostic, Result}; use swc_core::{ common::{Globals, Mark, SourceMap, GLOBALS}, ecma::{ - ast::{EsVersion, Id}, + ast::EsVersion, parser::{parse_file_as_module, Syntax, TsConfig}, transforms::base::resolver, visit::FoldWith, }, }; -use tracing::{debug, instrument, warn}; +use tracing::{debug, warn}; use tracing_subscriber::{prelude::*, EnvFilter}; use tracing_tree::HierarchicalLayer; use forge_analyzer::{ - analyzer::AuthZVal, - checkers::AuthZChecker, - ctx::{AppCtx, ModId, ModItem}, + checkers::{AuthZChecker, AuthNVuln, AuthenticateChecker}, + ctx::{AppCtx, ModId}, definitions::{run_resolver, DefId, Environment}, - engine::Machine, interp::Interp, pretty::dump_ir, reporter::Reporter, @@ -85,7 +83,7 @@ struct ForgeProject { sm: Arc, ctx: AppCtx, env: Environment, - funcs: Vec>, + funcs: Vec>, opts: Opts, } @@ -134,10 +132,10 @@ impl ForgeProject { fn add_funcs<'a, I: IntoIterator>>(&mut self, iter: I) { self.funcs.extend(iter.into_iter().flat_map(|ftype| { - ftype.sequence(|(func, path)| { + ftype.sequence(|(func_name, path)| { let modid = self.ctx.modid_from_path(&path)?; - let func = self.env.module_export(modid, func)?; - Some((modid, func)) + let func = self.env.module_export(modid, func_name)?; + Some((func_name.to_owned(), path, modid, func)) }) })); } @@ -184,64 +182,33 @@ fn scan_directory(dir: PathBuf, function: Option<&str>, opts: Opts) -> Result { - interp.run_checker(def, &mut checker); + FunctionTy::Invokable((ref func, ref path, _, def)) => { + let mut checker = AuthZChecker::new(); + debug!("checking {func} at {path:?}"); + if let Err(err) = interp.run_checker(def, &mut checker, path.clone(), func.clone()) { + warn!("error while scanning {func} in {path:?}: {err}"); + } + reporter.add_vulnerabilities(checker.into_vulns()); + } + FunctionTy::WebTrigger((ref func, ref path, _, def)) => { + let mut checker = AuthenticateChecker::new(); + debug!("checking webtrigger {func} at {path:?}"); + if let Err(err) = authn_interp.run_checker(def, &mut checker, path.clone(), func.clone()) { + warn!("error while scanning {func} in {path:?}: {err}"); + } + reporter.add_vulnerabilities(checker.into_vulns()); } - FunctionTy::WebTrigger((_, def)) => {} } } - reporter.add_vulnerabilities(checker.into_vulns()); let report = serde_json::to_string(&reporter.into_report()).into_diagnostic()?; + debug!("Writing Report"); match opts.out { Some(path) => { fs::write(path, report).into_diagnostic()?; From 64ad87debde99a94eab11e3df177df733e99662c Mon Sep 17 00:00:00 2001 From: Joshua Wong Date: Wed, 21 Dec 2022 14:06:38 -0600 Subject: [PATCH 033/517] fix(definitions): make class functions public --- crates/forge_analyzer/src/definitions.rs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/crates/forge_analyzer/src/definitions.rs b/crates/forge_analyzer/src/definitions.rs index 2c2dee5d..3b2d9168 100644 --- a/crates/forge_analyzer/src/definitions.rs +++ b/crates/forge_analyzer/src/definitions.rs @@ -211,7 +211,7 @@ struct Object { } impl Class { - fn new(def: DefId) -> Self { + pub fn new(def: DefId) -> Self { Self { def, pub_members: vec![], @@ -232,10 +232,14 @@ impl Class { /// ``` /// /// ```rust + /// use forge_analyzer::definitions::{Class, DefId}; + /// + /// # fn foo(obj_id: DefId) { /// let obj = Class::new(obj_id); /// obj.find_member("foo"); + /// # } /// ``` - pub(crate) fn find_member(&self, name: &N) -> Option + pub fn find_member(&self, name: &N) -> Option where JsWord: PartialEq, { From 50384a760609d60dcfab5ba1f628b3306013430f Mon Sep 17 00:00:00 2001 From: Joshua Wong Date: Wed, 21 Dec 2022 14:13:13 -0600 Subject: [PATCH 034/517] style: run rustfmt --- crates/forge_analyzer/src/checkers.rs | 4 +- crates/forge_analyzer/src/definitions.rs | 61 +++++++++++++----------- crates/forge_analyzer/src/interp.rs | 11 ++++- crates/fsrt/src/main.rs | 9 ++-- 4 files changed, 50 insertions(+), 35 deletions(-) diff --git a/crates/forge_analyzer/src/checkers.rs b/crates/forge_analyzer/src/checkers.rs index 8b9a3125..692286af 100644 --- a/crates/forge_analyzer/src/checkers.rs +++ b/crates/forge_analyzer/src/checkers.rs @@ -363,9 +363,7 @@ impl<'cx> Checker<'cx> for AuthenticateChecker { ) -> ControlFlow<(), Self::State> { match *intrinsic { Intrinsic::Authorize => ControlFlow::Continue(*state), - Intrinsic::Fetch - | Intrinsic::EnvRead - | Intrinsic::StorageRead => { + Intrinsic::Fetch | Intrinsic::EnvRead | Intrinsic::StorageRead => { debug!("authenticated"); ControlFlow::Continue(Authenticated::Yes) } diff --git a/crates/forge_analyzer/src/definitions.rs b/crates/forge_analyzer/src/definitions.rs index 3b2d9168..85a42092 100644 --- a/crates/forge_analyzer/src/definitions.rs +++ b/crates/forge_analyzer/src/definitions.rs @@ -17,17 +17,17 @@ use swc_core::{ ForOfStmt, ForStmt, Function, Id, Ident, IfStmt, Import, ImportDecl, ImportDefaultSpecifier, ImportNamedSpecifier, ImportStarAsSpecifier, JSXElement, JSXElementChild, JSXElementName, JSXExpr, JSXExprContainer, JSXFragment, JSXMemberExpr, - JSXNamespacedName, JSXSpreadChild, JSXText, KeyValuePatProp, KeyValueProp, LabeledStmt, - Lit, MemberExpr, MemberProp, MetaPropExpr, MethodProp, Module, ModuleDecl, + JSXNamespacedName, JSXObject, JSXSpreadChild, JSXText, KeyValuePatProp, KeyValueProp, + LabeledStmt, Lit, MemberExpr, MemberProp, MetaPropExpr, MethodProp, Module, ModuleDecl, ModuleExportName, ModuleItem, NewExpr, Number, ObjectLit, ObjectPat, ObjectPatProp, OptCall, OptChainBase, OptChainExpr, ParenExpr, Pat, PatOrExpr, PrivateName, Prop, PropName, PropOrSpread, RestPat, ReturnStmt, SeqExpr, Stmt, Str, Super, SuperProp, SuperPropExpr, SwitchStmt, TaggedTpl, ThisExpr, ThrowStmt, Tpl, TplElement, TryStmt, TsAsExpr, TsConstAssertion, TsInstantiation, TsNonNullExpr, TsSatisfiesExpr, TsTypeAssertion, UnaryExpr, UpdateExpr, VarDecl, VarDeclOrExpr, VarDeclOrPat, - VarDeclarator, WhileStmt, WithStmt, YieldExpr, JSXObject, + VarDeclarator, WhileStmt, WithStmt, YieldExpr, }, - atoms::{JsWord, Atom}, + atoms::{Atom, JsWord}, visit::{noop_visit_type, Visit, VisitWith}, }, }; @@ -930,21 +930,22 @@ impl<'cx> FunctionAnalyzer<'cx> { fn lower_call(&mut self, callee: CalleeRef<'_>, args: &[ExprOrSpread]) -> Operand { let props = normalize_callee_expr(callee, self.res, self.module); if let Some(&PropPath::Def(id)) = props.first() { - if self.res.as_foreign_import(id, "@forge/ui").map_or(false, |imp| matches!(imp, ImportKind::Named(s) if *s == *"useState")) { + if self.res.as_foreign_import(id, "@forge/ui").map_or( + false, + |imp| matches!(imp, ImportKind::Named(s) if *s == *"useState"), + ) { if let [ExprOrSpread { expr, .. }] = args { debug!("found useState"); match &**expr { - Expr::Arrow(ArrowExpr {body, ..}) => { - match body { - BlockStmtOrExpr::BlockStmt(stmt) => { - self.lower_stmts(&stmt.stmts); - return Operand::UNDEF; - } - BlockStmtOrExpr::Expr(expr) => { - return self.lower_expr(&expr); - } + Expr::Arrow(ArrowExpr { body, .. }) => match body { + BlockStmtOrExpr::BlockStmt(stmt) => { + self.lower_stmts(&stmt.stmts); + return Operand::UNDEF; } - } + BlockStmtOrExpr::Expr(expr) => { + return self.lower_expr(&expr); + } + }, Expr::Fn(FnExpr { ident: _, function }) => { if let Some(body) = &function.body { self.lower_stmts(&body.stmts); @@ -1024,12 +1025,10 @@ impl<'cx> FunctionAnalyzer<'cx> { let value = JsWord::from(value.to_string()); Operand::Lit(Literal::Str(value)) } - JSXElementChild::JSXExprContainer(JSXExprContainer { expr, .. }) => { - match expr { - JSXExpr::JSXEmptyExpr(_) => Operand::UNDEF, - JSXExpr::Expr(expr) => self.lower_expr(expr), - } - } + JSXElementChild::JSXExprContainer(JSXExprContainer { expr, .. }) => match expr { + JSXExpr::JSXEmptyExpr(_) => Operand::UNDEF, + JSXExpr::Expr(expr) => self.lower_expr(expr), + }, JSXElementChild::JSXSpreadChild(JSXSpreadChild { expr, .. }) => self.lower_expr(expr), JSXElementChild::JSXElement(elem) => self.lower_jsx_elem(elem), JSXElementChild::JSXFragment(JSXFragment { children, .. }) => { @@ -1657,14 +1656,21 @@ impl Visit for FunctionCollector<'_> { self.visit_arrow_expr(arrow); self.parent = old_parent; } - Some(Expr::Call(CallExpr { callee: Callee::Expr(expr), args, .. })) => { + Some(Expr::Call(CallExpr { + callee: Callee::Expr(expr), + args, + .. + })) => { if let Expr::Ident(ident) = &**expr { let ident = ident.to_id(); let Some(def) = self.res.sym_to_id(ident, self.module) else { return; }; - if matches!(self.res.as_foreign_import(def, "@forge/ui"), Some(ImportKind::Named(imp)) if *imp == *"render") { - let owner = self.res.get_or_overwrite_sym(id, self.module, DefKind::Function(())); + if matches!(self.res.as_foreign_import(def, "@forge/ui"), Some(ImportKind::Named(imp)) if *imp == *"render") + { + let owner = + self.res + .get_or_overwrite_sym(id, self.module, DefKind::Function(())); let Some(ExprOrSpread { expr, .. }) = &args.first() else { return; }; let old_parent = self.parent.replace(owner); let mut analyzer = FunctionAnalyzer { @@ -1678,9 +1684,10 @@ impl Visit for FunctionCollector<'_> { in_lhs: false, }; let opnd = analyzer.lower_expr(expr); - analyzer - .body - .push_inst(analyzer.block, Inst::Assign(RETURN_VAR, Rvalue::Read(opnd))); + analyzer.body.push_inst( + analyzer.block, + Inst::Assign(RETURN_VAR, Rvalue::Read(opnd)), + ); *self.res.def_mut(owner).expect_body() = analyzer.body; self.parent = old_parent; } diff --git a/crates/forge_analyzer/src/interp.rs b/crates/forge_analyzer/src/interp.rs index 68236178..77cf5f4a 100644 --- a/crates/forge_analyzer/src/interp.rs +++ b/crates/forge_analyzer/src/interp.rs @@ -5,7 +5,8 @@ use std::{ io::{self, Write}, iter, marker::PhantomData, - ops::ControlFlow, path::{PathBuf, Path}, + ops::ControlFlow, + path::{Path, PathBuf}, }; use forge_utils::{FxHashMap, FxHashSet}; @@ -532,7 +533,13 @@ impl<'cx, C: Checker<'cx>> Interp<'cx, C> { } #[instrument(skip(self, checker))] - pub fn run_checker(&mut self, def: DefId, checker: &mut C, entry_file: PathBuf, fname: String) -> Result<(), Error> { + pub fn run_checker( + &mut self, + def: DefId, + checker: &mut C, + entry_file: PathBuf, + fname: String, + ) -> Result<(), Error> { self.entry = EntryPoint { file: entry_file, kind: EntryKind::Function(fname), diff --git a/crates/fsrt/src/main.rs b/crates/fsrt/src/main.rs index 917fe40d..2120e088 100644 --- a/crates/fsrt/src/main.rs +++ b/crates/fsrt/src/main.rs @@ -26,7 +26,7 @@ use tracing_subscriber::{prelude::*, EnvFilter}; use tracing_tree::HierarchicalLayer; use forge_analyzer::{ - checkers::{AuthZChecker, AuthNVuln, AuthenticateChecker}, + checkers::{AuthNVuln, AuthZChecker, AuthenticateChecker}, ctx::{AppCtx, ModId}, definitions::{run_resolver, DefId, Environment}, interp::Interp, @@ -192,7 +192,8 @@ fn scan_directory(dir: PathBuf, function: Option<&str>, opts: Opts) -> Result { let mut checker = AuthZChecker::new(); debug!("checking {func} at {path:?}"); - if let Err(err) = interp.run_checker(def, &mut checker, path.clone(), func.clone()) { + if let Err(err) = interp.run_checker(def, &mut checker, path.clone(), func.clone()) + { warn!("error while scanning {func} in {path:?}: {err}"); } reporter.add_vulnerabilities(checker.into_vulns()); @@ -200,7 +201,9 @@ fn scan_directory(dir: PathBuf, function: Option<&str>, opts: Opts) -> Result { let mut checker = AuthenticateChecker::new(); debug!("checking webtrigger {func} at {path:?}"); - if let Err(err) = authn_interp.run_checker(def, &mut checker, path.clone(), func.clone()) { + if let Err(err) = + authn_interp.run_checker(def, &mut checker, path.clone(), func.clone()) + { warn!("error while scanning {func} in {path:?}: {err}"); } reporter.add_vulnerabilities(checker.into_vulns()); From 324f17694b7651463dddefb9ccb6bd57fb0e4a8e Mon Sep 17 00:00:00 2001 From: Joshua Wong Date: Wed, 21 Dec 2022 17:03:04 -0600 Subject: [PATCH 035/517] Update issue templates --- .github/ISSUE_TEMPLATE/bug_report.md | 46 ++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) create mode 100644 .github/ISSUE_TEMPLATE/bug_report.md diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 00000000..204447f2 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,46 @@ +--- +name: Bug report +about: Create a bug report for FSRT +title: '' +labels: bug +assignees: '' + +--- + + + +I ran this command: + +``` +fsrt [args] +``` + +I expected this to happen: *explanation* + +Instead, this happened: *explanation* + + +I ran `fsrt` on this app: + +`fsrt --version`: +``` +fsrt +``` + + +
Backtrace +

+ +``` + +``` + +

+
From 5c1e4c49beeb126a14dadd92cc90ecc789ed70a1 Mon Sep 17 00:00:00 2001 From: Joshua Wong Date: Wed, 21 Dec 2022 17:37:07 -0600 Subject: [PATCH 036/517] docs: add .mailmap --- .mailmap | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 .mailmap diff --git a/.mailmap b/.mailmap new file mode 100644 index 00000000..c641e410 --- /dev/null +++ b/.mailmap @@ -0,0 +1,2 @@ +Joshua Wong +Joshua Wong From 6fb3f6e5f2c3a4cd0e1903a9ac204c84445f4582 Mon Sep 17 00:00:00 2001 From: Joshua Wong Date: Wed, 21 Dec 2022 17:37:42 -0600 Subject: [PATCH 037/517] refactor(fsrt): remove unused imports --- crates/fsrt/src/main.rs | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/crates/fsrt/src/main.rs b/crates/fsrt/src/main.rs index 2120e088..2722afc6 100644 --- a/crates/fsrt/src/main.rs +++ b/crates/fsrt/src/main.rs @@ -3,7 +3,6 @@ use std::{ collections::HashSet, convert::TryFrom, fs, - io::{self, Write as _}, os::unix::prelude::OsStrExt, path::{Path, PathBuf}, sync::Arc, @@ -26,15 +25,14 @@ use tracing_subscriber::{prelude::*, EnvFilter}; use tracing_tree::HierarchicalLayer; use forge_analyzer::{ - checkers::{AuthNVuln, AuthZChecker, AuthenticateChecker}, + checkers::{AuthZChecker, AuthenticateChecker}, ctx::{AppCtx, ModId}, definitions::{run_resolver, DefId, Environment}, interp::Interp, - pretty::dump_ir, reporter::Reporter, - resolver::{dump_callgraph_dot, dump_cfg_dot, resolve_calls}, + resolver::{resolve_calls}, }; -use forge_file_resolver::FileResolver; + use forge_loader::manifest::{ForgeManifest, FunctionRef, FunctionTy}; use walkdir::WalkDir; From 3927e06deac70880b4135d8a29c08419ce8c2eeb Mon Sep 17 00:00:00 2001 From: Joshua Wong Date: Wed, 21 Dec 2022 17:38:42 -0600 Subject: [PATCH 038/517] refactor(analyzer): remove dead code --- crates/forge_analyzer/src/checkers.rs | 20 ++++++++++---------- crates/forge_analyzer/src/interp.rs | 4 ++-- crates/forge_analyzer/src/reporter.rs | 2 +- 3 files changed, 13 insertions(+), 13 deletions(-) diff --git a/crates/forge_analyzer/src/checkers.rs b/crates/forge_analyzer/src/checkers.rs index 692286af..655d28ca 100644 --- a/crates/forge_analyzer/src/checkers.rs +++ b/crates/forge_analyzer/src/checkers.rs @@ -43,7 +43,7 @@ impl<'cx> Dataflow<'cx> for AuthorizeDataflow { type State = AuthorizeState; fn with_interp>( - interp: &Interp<'cx, C>, + _interp: &Interp<'cx, C>, ) -> Self { Self { needs_call: vec![] } } @@ -75,11 +75,11 @@ impl<'cx> Dataflow<'cx> for AuthorizeDataflow { interp: &Interp<'cx, C>, def: DefId, loc: Location, - block: &'cx BasicBlock, + _block: &'cx BasicBlock, callee: &'cx crate::ir::Operand, initial_state: Self::State, ) -> Self::State { - let Some((callee_def, body)) = self.resolve_call(interp, callee) else { + let Some((callee_def, _body)) = self.resolve_call(interp, callee) else { return initial_state; }; match interp.func_state(callee_def) { @@ -143,7 +143,7 @@ pub struct AuthZVuln { } impl AuthZVuln { - fn new(mut callstack: Vec, env: &Environment, entry: &EntryPoint) -> Self { + fn new(callstack: Vec, env: &Environment, entry: &EntryPoint) -> Self { let entry_func = match &entry.kind { EntryKind::Function(func) => func.clone(), EntryKind::Resolver(res, prop) => format!("{res}.{prop}"), @@ -198,7 +198,7 @@ impl IntoVuln for AuthZVuln { } impl WithCallStack for AuthZVuln { - fn add_call_stack(&mut self, stack: Vec) {} + fn add_call_stack(&mut self, _stack: Vec) {} } impl<'cx> Checker<'cx> for AuthZChecker { @@ -261,7 +261,7 @@ impl<'cx> Dataflow<'cx> for AuthenticateDataflow { type State = Authenticated; fn with_interp>( - interp: &Interp<'cx, C>, + _interp: &Interp<'cx, C>, ) -> Self { Self { needs_call: vec![] } } @@ -291,11 +291,11 @@ impl<'cx> Dataflow<'cx> for AuthenticateDataflow { interp: &Interp<'cx, C>, def: DefId, loc: Location, - block: &'cx BasicBlock, + _block: &'cx BasicBlock, callee: &'cx crate::ir::Operand, initial_state: Self::State, ) -> Self::State { - let Some((callee_def, body)) = self.resolve_call(interp, callee) else { + let Some((callee_def, _body)) = self.resolve_call(interp, callee) else { return initial_state; }; match interp.func_state(callee_def) { @@ -387,7 +387,7 @@ pub struct AuthNVuln { } impl AuthNVuln { - fn new(mut callstack: Vec, env: &Environment, entry: &EntryPoint) -> Self { + fn new(callstack: Vec, env: &Environment, entry: &EntryPoint) -> Self { let entry_func = match &entry.kind { EntryKind::Function(func) => func.clone(), EntryKind::Resolver(res, prop) => format!("{res}.{prop}"), @@ -442,5 +442,5 @@ impl IntoVuln for AuthNVuln { } impl WithCallStack for AuthNVuln { - fn add_call_stack(&mut self, stack: Vec) {} + fn add_call_stack(&mut self, _stack: Vec) {} } diff --git a/crates/forge_analyzer/src/interp.rs b/crates/forge_analyzer/src/interp.rs index 77cf5f4a..a15ba453 100644 --- a/crates/forge_analyzer/src/interp.rs +++ b/crates/forge_analyzer/src/interp.rs @@ -1,12 +1,12 @@ use std::{ - cell::{Cell, Ref, RefCell, RefMut}, + cell::{Cell, RefCell, RefMut}, collections::BTreeMap, fmt::{self, Display}, io::{self, Write}, iter, marker::PhantomData, ops::ControlFlow, - path::{Path, PathBuf}, + path::{PathBuf}, }; use forge_utils::{FxHashMap, FxHashSet}; diff --git a/crates/forge_analyzer/src/reporter.rs b/crates/forge_analyzer/src/reporter.rs index e0fa84bd..97ca9350 100644 --- a/crates/forge_analyzer/src/reporter.rs +++ b/crates/forge_analyzer/src/reporter.rs @@ -1,4 +1,4 @@ -use std::mem; + use serde::Serialize; use time::{Date, OffsetDateTime}; From 1dd979221b786850fd722e34e2bf57a31fa5f931 Mon Sep 17 00:00:00 2001 From: Joshua Wong Date: Wed, 21 Dec 2022 17:52:44 -0600 Subject: [PATCH 039/517] refactor: clean up unused code --- crates/forge_analyzer/src/checkers.rs | 22 +++++++++-------- crates/forge_analyzer/src/definitions.rs | 7 ++++++ crates/forge_analyzer/src/interp.rs | 30 +++++++++--------------- crates/forge_analyzer/src/reporter.rs | 2 -- crates/forge_analyzer/src/worklist.rs | 17 +++++++------- crates/fsrt/src/main.rs | 2 +- 6 files changed, 40 insertions(+), 40 deletions(-) diff --git a/crates/forge_analyzer/src/checkers.rs b/crates/forge_analyzer/src/checkers.rs index 655d28ca..89d64312 100644 --- a/crates/forge_analyzer/src/checkers.rs +++ b/crates/forge_analyzer/src/checkers.rs @@ -153,15 +153,16 @@ impl AuthZVuln { } }; let file = entry.file.clone(); - let stack = iter::once(&*entry_func) - .chain( + let stack = Itertools::intersperse( + iter::once(&*entry_func).chain( callstack .into_iter() .rev() .map(|frame| env.def_name(frame.calling_function)), - ) - .intersperse(" -> ") - .collect(); + ), + " -> ", + ) + .collect(); Self { stack, entry_func, @@ -397,15 +398,16 @@ impl AuthNVuln { } }; let file = entry.file.clone(); - let stack = iter::once(&*entry_func) - .chain( + let stack = Itertools::intersperse( + iter::once(&*entry_func).chain( callstack .into_iter() .rev() .map(|frame| env.def_name(frame.calling_function)), - ) - .intersperse(" -> ") - .collect(); + ), + " -> ", + ) + .collect(); Self { stack, entry_func, diff --git a/crates/forge_analyzer/src/definitions.rs b/crates/forge_analyzer/src/definitions.rs index 85a42092..c7082829 100644 --- a/crates/forge_analyzer/src/definitions.rs +++ b/crates/forge_analyzer/src/definitions.rs @@ -2693,3 +2693,10 @@ impl AsId for ModuleExportName { } } } + +impl DefId { + #[inline] + pub(crate) fn new(raw: u32) -> Self { + Self(raw) + } +} diff --git a/crates/forge_analyzer/src/interp.rs b/crates/forge_analyzer/src/interp.rs index a15ba453..57988685 100644 --- a/crates/forge_analyzer/src/interp.rs +++ b/crates/forge_analyzer/src/interp.rs @@ -6,7 +6,7 @@ use std::{ iter, marker::PhantomData, ops::ControlFlow, - path::{PathBuf}, + path::PathBuf, }; use forge_utils::{FxHashMap, FxHashSet}; @@ -417,17 +417,6 @@ impl<'cx, C: Checker<'cx>> Interp<'cx, C> { (*self.callstack.borrow()).clone() } - pub(crate) fn note_vuln(&self, mut vuln: C::Vuln) { - vuln.add_call_stack( - self.callstack - .borrow() - .iter() - .map(|f| f.calling_function) - .collect::>(), - ); - self.vulns.borrow_mut().push(vuln); - } - pub(crate) fn checker_visit(&self, def: DefId) -> bool { self.checker_visited.borrow_mut().insert(def) } @@ -479,16 +468,19 @@ impl<'cx, C: Checker<'cx>> Interp<'cx, C> { } #[inline] - fn func_state_mut(&self, def: DefId) -> RefMut<'_, C::State> { - let func_state = self.func_state.borrow_mut(); - RefMut::map(func_state, |state| { - state.entry(def).or_insert(C::State::BOTTOM) - }) + pub(crate) fn entry(&self) -> &EntryPoint { + &self.entry } #[inline] - pub(crate) fn entry(&self) -> &EntryPoint { - &self.entry + pub fn callees( + &self, + caller: DefId, + ) -> impl DoubleEndedIterator + '_ { + self.call_graph + .callgraph + .range((caller, DefId::new(0))..(caller, DefId::new(u32::MAX))) + .map(|(&(_, callee), &loc)| (callee, loc)) } fn run(&mut self, func_def: DefId) { diff --git a/crates/forge_analyzer/src/reporter.rs b/crates/forge_analyzer/src/reporter.rs index 97ca9350..e483463c 100644 --- a/crates/forge_analyzer/src/reporter.rs +++ b/crates/forge_analyzer/src/reporter.rs @@ -1,5 +1,3 @@ - - use serde::Serialize; use time::{Date, OffsetDateTime}; diff --git a/crates/forge_analyzer/src/worklist.rs b/crates/forge_analyzer/src/worklist.rs index 708e3df5..0488548d 100644 --- a/crates/forge_analyzer/src/worklist.rs +++ b/crates/forge_analyzer/src/worklist.rs @@ -18,7 +18,8 @@ impl WorkList where V: Eq + Hash, { - pub(crate) fn new() -> Self { + #[inline] + pub fn new() -> Self { Self { worklist: VecDeque::new(), visited: FxHashSet::default(), @@ -26,27 +27,27 @@ where } #[inline] - pub(crate) fn pop_front(&mut self) -> Option<(V, W)> { + pub fn pop_front(&mut self) -> Option<(V, W)> { self.worklist.pop_front() } #[inline] - pub(crate) fn len(&self) -> usize { + pub fn len(&self) -> usize { self.worklist.len() } #[inline] - pub(crate) fn is_empty(&self) -> bool { + pub fn is_empty(&self) -> bool { self.worklist.is_empty() } #[inline] - pub(crate) fn reserve(&mut self, n: usize) { + pub fn reserve(&mut self, n: usize) { self.worklist.reserve(n); } #[inline] - pub(crate) fn visited(&self, key: &Q) -> bool + pub fn visited(&self, key: &Q) -> bool where V: Borrow, Q: Eq + Hash + ?Sized, @@ -60,14 +61,14 @@ where V: Eq + Hash + Copy, { #[inline] - pub(crate) fn push_back(&mut self, v: V, w: W) { + pub fn push_back(&mut self, v: V, w: W) { if self.visited.insert(v) { self.worklist.push_back((v, w)); } } #[inline] - pub(crate) fn push_back_force(&mut self, v: V, w: W) { + pub fn push_back_force(&mut self, v: V, w: W) { self.worklist.push_back((v, w)); } } diff --git a/crates/fsrt/src/main.rs b/crates/fsrt/src/main.rs index 2722afc6..4f82f05a 100644 --- a/crates/fsrt/src/main.rs +++ b/crates/fsrt/src/main.rs @@ -30,7 +30,7 @@ use forge_analyzer::{ definitions::{run_resolver, DefId, Environment}, interp::Interp, reporter::Reporter, - resolver::{resolve_calls}, + resolver::resolve_calls, }; use forge_loader::manifest::{ForgeManifest, FunctionRef, FunctionTy}; From 3144d906bc7bc24bdfe2db1a93e69f2d2f36286f Mon Sep 17 00:00:00 2001 From: Joshua Wong Date: Wed, 21 Dec 2022 18:00:35 -0600 Subject: [PATCH 040/517] build(docker): bump rustc compiler to 1.66 --- .dockerignore | 4 ++++ Dockerfile | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/.dockerignore b/.dockerignore index 38bd013f..dfacd0a5 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1,3 +1,7 @@ *.md +**/.git +.atlassian +.github +.mailmap LICENSE-* test-apps diff --git a/Dockerfile b/Dockerfile index d632d252..9ec4834a 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM rust:1.65.0 as build-env +FROM rust:1.66.0 as build-env WORKDIR /app COPY . /app RUN cargo build --release From b19aa0fc1d55b2bd103a86f77d8dded864b99283 Mon Sep 17 00:00:00 2001 From: Joshua Wong Date: Thu, 22 Dec 2022 14:17:57 -0600 Subject: [PATCH 041/517] fix(report): use the rfc3339 format --- crates/forge_analyzer/src/reporter.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/forge_analyzer/src/reporter.rs b/crates/forge_analyzer/src/reporter.rs index e483463c..0fb853a4 100644 --- a/crates/forge_analyzer/src/reporter.rs +++ b/crates/forge_analyzer/src/reporter.rs @@ -30,9 +30,9 @@ pub trait IntoVuln { pub struct Report { vulns: Vec, scanner: &'static str, - #[serde(with = "time::serde::iso8601")] + #[serde(with = "time::serde::rfc3339")] started_at: OffsetDateTime, - #[serde(with = "time::serde::iso8601")] + #[serde(with = "time::serde::rfc3339")] ended_at: OffsetDateTime, scanned: Vec, errors: bool, From e2277a5c8eb4bd265acfe16205fe24ceae443b26 Mon Sep 17 00:00:00 2001 From: Joshua Wong Date: Thu, 22 Dec 2022 14:18:50 -0600 Subject: [PATCH 042/517] refactor(docker): set entrypoint instead of cmd --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 9ec4834a..3fd194a5 100644 --- a/Dockerfile +++ b/Dockerfile @@ -5,4 +5,4 @@ RUN cargo build --release FROM gcr.io/distroless/cc COPY --from=build-env /app/target/release/fsrt / -CMD ["./fsrt"] +ENTRYPOINT ["/fsrt"] From b908ba94c350019142a83b778e62cc67c4590509 Mon Sep 17 00:00:00 2001 From: Joshua Wong Date: Thu, 22 Dec 2022 14:36:01 -0600 Subject: [PATCH 043/517] refactor(resolver): simplify iterator chaining --- crates/forge_file_resolver/src/lib.rs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/crates/forge_file_resolver/src/lib.rs b/crates/forge_file_resolver/src/lib.rs index 11e87255..84f125bc 100644 --- a/crates/forge_file_resolver/src/lib.rs +++ b/crates/forge_file_resolver/src/lib.rs @@ -111,11 +111,8 @@ impl ForgeResolver { let path_no_extension = path.with_extension(""); self.modules .iter() - .enumerate() .zip(&self.no_ext) - .find_map(|((modid, modpath), no_ext)| { - (*modpath == path || *no_ext == path_no_extension).then_some(modid) - }) + .position(|(modpath, no_ext)| (*modpath == path || *no_ext == path_no_extension)) } fn resolve_import_path(&self, module: usize, import: &Path) -> Result { From cf63439dba2f214bcd8a53da75ee8454e11fb4fa Mon Sep 17 00:00:00 2001 From: Joshua Wong Date: Wed, 4 Jan 2023 16:15:01 -0600 Subject: [PATCH 044/517] fix(lowerer): properly classify getServerInfo API calls as a no-op --- crates/forge_analyzer/src/definitions.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/crates/forge_analyzer/src/definitions.rs b/crates/forge_analyzer/src/definitions.rs index c7082829..17c15505 100644 --- a/crates/forge_analyzer/src/definitions.rs +++ b/crates/forge_analyzer/src/definitions.rs @@ -729,8 +729,9 @@ fn classify_api_call(expr: &Expr) -> ApiCallKind { use once_cell::sync::Lazy; use regex::Regex; + // FIXME: this should be done as a dataflow analysis instead of on the AST. static TRIVIAL: Lazy = - Lazy::new(|| Regex::new(r"user|instance|avatar|license|preferences|serverinfo").unwrap()); + Lazy::new(|| Regex::new(r"user|instance|avatar|license|preferences|server[iI]nfo").unwrap()); #[derive(Default)] struct ApiCallClassifier { From deadf9a552feaa0b2303162c66017a8c424e9d75 Mon Sep 17 00:00:00 2001 From: Joshua Wong Date: Wed, 4 Jan 2023 16:18:43 -0600 Subject: [PATCH 045/517] chore: bump deps --- Cargo.lock | 334 ++++++++++++++++++++++++----------------------------- 1 file changed, 154 insertions(+), 180 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 6ff8c230..baf19957 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -14,9 +14,9 @@ dependencies = [ [[package]] name = "addr2line" -version = "0.17.0" +version = "0.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9ecd88a8c8378ca913a680cd98f0f13ac67383d35993f86c90a70e3f137816b" +checksum = "a76fd60b23679b7d19bd066031410fb7e458ccc5e958eb5c325888ce4baedc97" dependencies = [ "gimli", ] @@ -59,9 +59,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.66" +version = "1.0.68" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "216261ddc8289130e551ddcd5ce8a064710c0d064a4d2895c67151c92b5443f6" +checksum = "2cb2f989d18dd141ab8ae82f64d1a8cdd37e0840f73a406896cf5e99502fab61" [[package]] name = "arrayvec" @@ -114,9 +114,9 @@ checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" [[package]] name = "backtrace" -version = "0.3.66" +version = "0.3.67" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cab84319d616cfb654d03394f38ab7e6f0919e181b1b57e1fd15e7fb4077d9a7" +checksum = "233d376d6d185f2a3093e58f283f60f880315b6c60075b01f36b3b85154564ca" dependencies = [ "addr2line", "cc", @@ -159,9 +159,9 @@ dependencies = [ [[package]] name = "browserslist-rs" -version = "0.12.2" +version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "421478dde88feb4281328dea29dbf6d2b57bc19a8968214fc3694c8c574bc47f" +checksum = "ef956561c9a03c35af46714efd0c135e21768a2a012f900ca8a59b28e75d0cd1" dependencies = [ "ahash", "anyhow", @@ -183,15 +183,15 @@ dependencies = [ [[package]] name = "bumpalo" -version = "3.11.0" +version = "3.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1ad822118d20d2c234f427000d5acc36eabe1e29a348c89b63dd60b13f28e5d" +checksum = "572f695136211188308f16ad2ca5c851a712c464060ae6974944458eb83880ba" [[package]] name = "cc" -version = "1.0.77" +version = "1.0.78" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e9f73505338f7d905b19d18738976aae232eb46b8efc15554ffc56deb5d9ebe4" +checksum = "a20104e2335ce8a659d6dd92a51a767a0c062599c73b343fd152cb401e828c3d" [[package]] name = "cfg-if" @@ -216,9 +216,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.0.29" +version = "4.0.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4d63b9e9c07271b9957ad22c173bae2a4d9a81127680962039296abcd2f8251d" +checksum = "a7db700bc935f9e43e88d00b0850dae18a63773cfbec6d8e070fccf7fef89a39" dependencies = [ "bitflags", "clap_derive", @@ -227,7 +227,7 @@ dependencies = [ "once_cell", "strsim", "termcolor", - "terminal_size 0.2.2", + "terminal_size 0.2.3", ] [[package]] @@ -332,9 +332,9 @@ dependencies = [ [[package]] name = "ctor" -version = "0.1.23" +version = "0.1.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cdffe87e1d521a10f9696f833fe502293ea446d7f256c06128293a4119bdf4cb" +checksum = "6d2301688392eb071b0bf1a37be05c469d3cc4dbbd95df672fe28ab021e6a096" dependencies = [ "quote", "syn", @@ -342,9 +342,9 @@ dependencies = [ [[package]] name = "cxx" -version = "1.0.81" +version = "1.0.85" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97abf9f0eca9e52b7f81b945524e76710e6cb2366aead23b7d4fbf72e281f888" +checksum = "5add3fc1717409d029b20c5b6903fc0c0b02fa6741d820054f4a2efa5e5816fd" dependencies = [ "cc", "cxxbridge-flags", @@ -354,9 +354,9 @@ dependencies = [ [[package]] name = "cxx-build" -version = "1.0.81" +version = "1.0.85" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7cc32cc5fea1d894b77d269ddb9f192110069a8a9c1f1d441195fba90553dea3" +checksum = "b4c87959ba14bc6fbc61df77c3fcfe180fc32b93538c4f1031dd802ccb5f2ff0" dependencies = [ "cc", "codespan-reporting", @@ -369,15 +369,15 @@ dependencies = [ [[package]] name = "cxxbridge-flags" -version = "1.0.81" +version = "1.0.85" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ca220e4794c934dc6b1207c3b42856ad4c302f2df1712e9f8d2eec5afaacf1f" +checksum = "69a3e162fde4e594ed2b07d0f83c6c67b745e7f28ce58c6df5e6b6bef99dfb59" [[package]] name = "cxxbridge-macro" -version = "1.0.81" +version = "1.0.85" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b846f081361125bfc8dc9d3940c84e1fd83ba54bbca7b17cd29483c828be0704" +checksum = "3e7e2adeb6a0d4a282e581096b06e1791532b7d576dcde5ccd9382acf55db8e6" dependencies = [ "proc-macro2", "quote", @@ -649,9 +649,9 @@ dependencies = [ [[package]] name = "gimli" -version = "0.26.2" +version = "0.27.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22030e2c5a68ec659fde1e949a745124b48e6fa8b045b7ed5bd1fe4ccc5c4e5d" +checksum = "dec7af912d60cdbd3677c1af9352ebae6fb8394d165568a2234df0fa00f87793" [[package]] name = "hashbrown" @@ -743,12 +743,6 @@ dependencies = [ "serde", ] -[[package]] -name = "io-lifetimes" -version = "0.7.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59ce5ef949d49ee85593fc4d3f3f95ad61657076395cbbce23e2121fc5542074" - [[package]] name = "io-lifetimes" version = "1.0.3" @@ -774,13 +768,13 @@ dependencies = [ [[package]] name = "is-terminal" -version = "0.4.1" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "927609f78c2913a6f6ac3c27a4fe87f43e2a35367c0c4b0f8265e8f49a104330" +checksum = "28dfb6c8100ccc63462345b67d1bbc3679177c75ee4bf59bf29c8b1d110b8189" dependencies = [ "hermit-abi 0.2.6", - "io-lifetimes 1.0.3", - "rustix 0.36.5", + "io-lifetimes", + "rustix", "windows-sys", ] @@ -801,9 +795,9 @@ dependencies = [ [[package]] name = "itoa" -version = "1.0.4" +version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4217ad341ebadf8d8e724e264f13e593e0648f5b3e94b3896a5df283be015ecc" +checksum = "fad582f4b9e86b6caa621cabeb0963332d92eea04729ab12892c2533951e6440" [[package]] name = "js-sys" @@ -904,25 +898,19 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.138" +version = "0.2.139" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db6d7e329c562c5dfab7a46a2afabc8b987ab9a4834c9d1ca04dc54c1546cef8" +checksum = "201de327520df007757c1f0adce6e827fe8562fbc28bfd9c15571c66ca1f5f79" [[package]] name = "link-cplusplus" -version = "1.0.7" +version = "1.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9272ab7b96c9046fbc5bc56c06c117cb639fe2d509df0c421cad82d2915cf369" +checksum = "ecd207c9c713c34f95a097a5b029ac2ce6010530c7b49d7fea24d977dede04f5" dependencies = [ "cc", ] -[[package]] -name = "linux-raw-sys" -version = "0.0.46" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4d2456c373231a208ad294c33dc5bff30051eafd954cd4caae83a712b12854d" - [[package]] name = "linux-raw-sys" version = "0.1.4" @@ -1051,9 +1039,9 @@ checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" [[package]] name = "miniz_oxide" -version = "0.5.4" +version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96590ba8f175222643a85693f33d26e9c8a015f599c216509b1a6894af675d34" +checksum = "b275950c28b37e794e8c55d88aeb5e139d0ce23fdbbeda68f8d7174abdf9e8fa" dependencies = [ "adler", ] @@ -1066,9 +1054,9 @@ checksum = "e4a24736216ec316047a1fc4252e27dabb04218aa4a3f37c6e7ddbf1f9782b54" [[package]] name = "nom" -version = "7.1.1" +version = "7.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8903e5a29a317527874d0402f867152a3d21c908bb0b933e416c65e301d4c36" +checksum = "e5507769c4919c998e69e49c839d9dc6e693ede4cc4290d6ad8b41d4f09c548c" dependencies = [ "memchr", "minimal-lexical", @@ -1126,11 +1114,11 @@ dependencies = [ [[package]] name = "num_cpus" -version = "1.14.0" +version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6058e64324c71e02bc2b150e4f3bc8286db6c83092132ffa3f6b1eab0f9def5" +checksum = "0fac9e2da13b5eb447a6ce3d392f23a29d8694bff781bf03a16cd9ac8697593b" dependencies = [ - "hermit-abi 0.1.19", + "hermit-abi 0.2.6", "libc", ] @@ -1145,18 +1133,18 @@ dependencies = [ [[package]] name = "object" -version = "0.29.0" +version = "0.30.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21158b2c33aa6d4561f1c0a6ea283ca92bc54802a93b263e910746d679a7eb53" +checksum = "8d864c91689fdc196779b98dba0aceac6118594c2df6ee5d943eb6a8df4d107a" dependencies = [ "memchr", ] [[package]] name = "once_cell" -version = "1.16.0" +version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86f0b0d4bf799edbc74508c1e8bf170ff5f41238e5f8225603ca7caaae2b7860" +checksum = "6f61fba1741ea2b3d6a1e3178721804bb716a68a6aeba1149b5d52e3d464ea66" [[package]] name = "os_str_bytes" @@ -1197,9 +1185,9 @@ dependencies = [ [[package]] name = "parking_lot_core" -version = "0.9.4" +version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4dc9e0dc2adc1c69d09143aff38d3d30c5c3f0df0dad82e6d25547af174ebec0" +checksum = "7ff9f3fef3968a3ec5945535ed654cb38ff72d7495a25619e2247fb15a2ed9ba" dependencies = [ "cfg-if", "libc", @@ -1321,7 +1309,7 @@ dependencies = [ "dashmap", "from_variant", "once_cell", - "semver 1.0.14", + "semver 1.0.16", "serde", "st-map", "tracing", @@ -1365,24 +1353,24 @@ dependencies = [ [[package]] name = "proc-macro-hack" -version = "0.5.19" +version = "0.5.20+deprecated" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dbf0c48bc1d91375ae5c3cd81e3722dff1abcf81a30960240640d223f59fe0e5" +checksum = "dc375e1527247fe1a97d8b7156678dfe7c1af2fc075c9a4db3690ecd2a148068" [[package]] name = "proc-macro2" -version = "1.0.47" +version = "1.0.49" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ea3d908b0e36316caf9e9e2c4625cdde190a7e6f440d794667ed17a1855e725" +checksum = "57a8eca9f9c4ffde41714334dee777596264c7825420f521abc92b5b5deb63a5" dependencies = [ "unicode-ident", ] [[package]] name = "quote" -version = "1.0.21" +version = "1.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbe448f377a7d6961e30f5955f9b8d106c3f5e449d493ee1b125c1d43c2b5179" +checksum = "8856d8364d252a14d474036ea1358d63c9e6965c8e5c1885c18f73d70bff9c7b" dependencies = [ "proc-macro2", ] @@ -1425,11 +1413,10 @@ dependencies = [ [[package]] name = "rayon" -version = "1.6.0" +version = "1.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e060280438193c554f654141c9ea9417886713b7acd75974c85b18a69a88e0b" +checksum = "6db3a213adf02b3bcfd2d3846bb41cb22857d131789e01df434fb7e7bc0759b7" dependencies = [ - "crossbeam-deque", "either", "rayon-core", ] @@ -1510,43 +1497,29 @@ dependencies = [ [[package]] name = "rustix" -version = "0.35.13" +version = "0.36.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "727a1a6d65f786ec22df8a81ca3121107f235970dc1705ed681d3e6e8b9cd5f9" +checksum = "4feacf7db682c6c329c4ede12649cd36ecab0f3be5b7d74e6a20304725db4549" dependencies = [ "bitflags", "errno", - "io-lifetimes 0.7.5", + "io-lifetimes", "libc", - "linux-raw-sys 0.0.46", - "windows-sys", -] - -[[package]] -name = "rustix" -version = "0.36.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3807b5d10909833d3e9acd1eb5fb988f79376ff10fce42937de71a449c4c588" -dependencies = [ - "bitflags", - "errno", - "io-lifetimes 1.0.3", - "libc", - "linux-raw-sys 0.1.4", + "linux-raw-sys", "windows-sys", ] [[package]] name = "rustversion" -version = "1.0.9" +version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97477e48b4cf8603ad5f7aaf897467cf42ab4218a38ef76fb14c2d6773a6d6a8" +checksum = "5583e89e108996506031660fe09baa5011b9dd0341b89029313006d1fb508d70" [[package]] name = "ryu" -version = "1.0.11" +version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4501abdff3ae82a1c1b477a17252eb69cee9e66eb915c1abaa4f44d873df9f09" +checksum = "7b4b9743ed687d4b4bcedf9ff5eaa7398495ae14e61cba0a295704edbc7decde" [[package]] name = "same-file" @@ -1571,9 +1544,9 @@ checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" [[package]] name = "scratch" -version = "1.0.2" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c8132065adcfd6e02db789d9285a0deb2f3fcb04002865ab67d5fb103533898" +checksum = "ddccb15bcce173023b3fedd9436f882a0739b8dfb45e4f6b6002bee5929f61b2" [[package]] name = "semver" @@ -1586,9 +1559,9 @@ dependencies = [ [[package]] name = "semver" -version = "1.0.14" +version = "1.0.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e25dfac463d778e353db5be2449d1cce89bd6fd23c9f1ea21310ce6e5a1b29c4" +checksum = "58bc9567378fc7690d6b2addae4e60ac2eeea07becb2c64b9f218b53865cba2a" dependencies = [ "serde", ] @@ -1601,18 +1574,18 @@ checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" [[package]] name = "serde" -version = "1.0.150" +version = "1.0.152" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e326c9ec8042f1b5da33252c8a37e9ffbd2c9bef0155215b6e6c80c790e05f91" +checksum = "bb7d1f0d3021d347a83e556fc4683dea2ea09d87bccdf88ff5c12545d89d5efb" dependencies = [ "serde_derive", ] [[package]] name = "serde-wasm-bindgen" -version = "0.4.3" +version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1cfc62771e7b829b517cb213419236475f434fb480eddd76112ae182d274434a" +checksum = "e3b4c031cd0d9014307d82b8abf653c0290fbdaeb4c02d00c63cf52f728628bf" dependencies = [ "js-sys", "serde", @@ -1621,9 +1594,9 @@ dependencies = [ [[package]] name = "serde_derive" -version = "1.0.150" +version = "1.0.152" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42a3df25b0713732468deadad63ab9da1f1fd75a48a15024b50363f128db627e" +checksum = "af487d118eecd09402d70a5d72551860e788df87b464af30e5ea6a38c75c541e" dependencies = [ "proc-macro2", "quote", @@ -1632,9 +1605,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.89" +version = "1.0.91" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "020ff22c755c2ed3f8cf162dbb41a7268d934702f3ed3631656ea597e08fc3db" +checksum = "877c235533714907a8c2464236f5c4b2a17262ef1bd71f38f35ea592c8da6883" dependencies = [ "itoa", "ryu", @@ -1643,9 +1616,9 @@ dependencies = [ [[package]] name = "serde_yaml" -version = "0.9.14" +version = "0.9.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d232d893b10de3eb7258ff01974d6ee20663d8e833263c99409d4b13a0209da" +checksum = "92b5b431e8907b50339b51223b97d102db8d987ced36f6e4d03621db9316c834" dependencies = [ "indexmap", "itoa", @@ -1817,9 +1790,9 @@ dependencies = [ [[package]] name = "swc" -version = "0.236.12" +version = "0.236.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0d79b5e643f546d1aefb9d8a93963d5046671e7aef2cca6946bfa4b33bb2e91" +checksum = "0e02bda1fadcecd18aa755386fd726228708cbb448348e42781f519d65b7ecab" dependencies = [ "ahash", "anyhow", @@ -1865,9 +1838,9 @@ dependencies = [ [[package]] name = "swc_atoms" -version = "0.4.29" +version = "0.4.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cef7796df1985447f1fb8803ca2a00b445b20abbc65c8e73acb08835d7651ff0" +checksum = "9ad59af21529fcd3f4f8fa6b1ae399c2b183ec42c68347d76d68d6e5b657956e" dependencies = [ "once_cell", "rustc-hash", @@ -1893,9 +1866,9 @@ dependencies = [ [[package]] name = "swc_common" -version = "0.29.23" +version = "0.29.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "811faf77280a5f43fedf06769c391d4f2ed274b1ce9267e3a47e9b13527930b7" +checksum = "506321cad7393893018aac83a3b3bd25203883e8c47ab0864bb43195d43b22dd" dependencies = [ "ahash", "ast_node", @@ -1947,9 +1920,9 @@ dependencies = [ [[package]] name = "swc_core" -version = "0.48.24" +version = "0.48.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb4a88e87e9b82c1799b56e5985973af841f02fcbc47316d91f0468b4c97d742" +checksum = "5df312d4172937c513ef33c70ee6043e63bb69c482ed6db97e72e5b624119208" dependencies = [ "swc", "swc_atoms", @@ -1966,9 +1939,9 @@ dependencies = [ [[package]] name = "swc_ecma_ast" -version = "0.95.7" +version = "0.95.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "724a26e6f2c9fdbeee174ebfd9941c52ed13169b59afa2b056d45a731af10dc1" +checksum = "3cc936f04c4e671ae5918b573a50945c5189d3dcdd57e4faddd47889717e1416" dependencies = [ "bitflags", "is-macro", @@ -1983,9 +1956,9 @@ dependencies = [ [[package]] name = "swc_ecma_codegen" -version = "0.128.13" +version = "0.128.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e4efb3e85c0c8ff5ef8164397f571afb6b7ccd40d29191fa6fe1deef9503db3a" +checksum = "14ed1704af20b69f395f412457765b2d50b76428d65325e79a1cab4e27c9405c" dependencies = [ "memchr", "num-bigint", @@ -2015,9 +1988,9 @@ dependencies = [ [[package]] name = "swc_ecma_ext_transforms" -version = "0.92.11" +version = "0.92.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "952c2023394e4585751f829874e3f90c1ed8378c66a52dafd76da50cc5cc6439" +checksum = "5f8f6542f1f02fc723f42ec1b84eefd1eb19453be5fda64e42ac38092c63e4a9" dependencies = [ "phf", "swc_atoms", @@ -2029,9 +2002,9 @@ dependencies = [ [[package]] name = "swc_ecma_lints" -version = "0.67.15" +version = "0.67.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8825e741afd2267bb1190629b312362098532ad4e0b07cb3895567318fe14f07" +checksum = "27759e18f3533499df2034c41f68208a66240e31d8e9b97a78ae828324b223f3" dependencies = [ "ahash", "auto_impl", @@ -2050,9 +2023,9 @@ dependencies = [ [[package]] name = "swc_ecma_loader" -version = "0.41.24" +version = "0.41.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1256d24d66594cd11f5e7ed12fde994b23c6fadc5df5f05a1a18b28c5fc7239" +checksum = "42710b93ec010a5e0354cc86d621a3dd0243351d649d0c273c1887035a256151" dependencies = [ "ahash", "anyhow", @@ -2072,9 +2045,9 @@ dependencies = [ [[package]] name = "swc_ecma_minifier" -version = "0.160.24" +version = "0.160.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02b877c297c47c79fa134027e4766827659c059613dad23cd11319979adce149" +checksum = "944c20eea09e8410ba642f7d9e52f4b1e4e91b0c3ce3be07a6d5923eebc5c7b0" dependencies = [ "ahash", "arrayvec", @@ -2107,9 +2080,9 @@ dependencies = [ [[package]] name = "swc_ecma_parser" -version = "0.123.11" +version = "0.123.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da6b8e8a20fedc5fb981a78e2e4b2654b90bc6e080e0339e8bc9f8341ee11ccb" +checksum = "ca7a997c834b6e890b7a40d922aa353f311e3df2b9f6517c115923b1efa8eab2" dependencies = [ "either", "enum_kind", @@ -2126,9 +2099,9 @@ dependencies = [ [[package]] name = "swc_ecma_preset_env" -version = "0.175.17" +version = "0.175.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6bb75c62a6c6dabc5a7fd3dee32a4a66953b2b8209d2ed3891d30424b2e97129" +checksum = "bd8fde511662a77520869eb8f2b43390c1ff140f4e92f7eeef80012751dc97f5" dependencies = [ "ahash", "anyhow", @@ -2136,7 +2109,7 @@ dependencies = [ "indexmap", "once_cell", "preset_env_base", - "semver 1.0.14", + "semver 1.0.16", "serde", "serde_json", "st-map", @@ -2151,9 +2124,9 @@ dependencies = [ [[package]] name = "swc_ecma_transforms" -version = "0.199.17" +version = "0.199.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11af4ad5fc187308312757dec19dce74f81c3562c43e6d236882f1d985d023c9" +checksum = "edacb417e630c239af3098fca3c77bf75dea9dd228a97cf789c6b139b3462d9a" dependencies = [ "swc_atoms", "swc_common", @@ -2171,9 +2144,9 @@ dependencies = [ [[package]] name = "swc_ecma_transforms_base" -version = "0.112.15" +version = "0.112.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d6b38b6df37d25c4dd1faa4fdf9149eb19dc493e43deacd79ac8834d5afbe65" +checksum = "b4fd70dfa455193b3101beff13558ed09d0253cdbfc7e2147de8a9a439804dbd" dependencies = [ "better_scoped_tls", "bitflags", @@ -2193,9 +2166,9 @@ dependencies = [ [[package]] name = "swc_ecma_transforms_classes" -version = "0.101.15" +version = "0.101.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f601b41a3d1482a03c2c560e79cef0f496adb8a9ff3eb1157511df163df4036" +checksum = "3e16e7b14b835fbfdbe7c24df1e0446ef3cf596928b3911d49e14c46b01caef3" dependencies = [ "swc_atoms", "swc_common", @@ -2207,9 +2180,9 @@ dependencies = [ [[package]] name = "swc_ecma_transforms_compat" -version = "0.137.16" +version = "0.137.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2fcfc1420c092fb45760dd615b1f3e29499371f8188b3d88dbf85ac35b5020c9" +checksum = "11344cc707a9ca1aa1a928d4919d16518d86859c0ef7695ef3c3c0f0aa010cad" dependencies = [ "ahash", "arrayvec", @@ -2246,9 +2219,9 @@ dependencies = [ [[package]] name = "swc_ecma_transforms_module" -version = "0.154.16" +version = "0.154.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ac2f2869576ae4859294f07cc26809a027345359b5ecd9c71eef0114a51ec96" +checksum = "8324b5e86ce9a5d3890b3a7200dd29a22857d5a154ecf041e3aa983e871673f4" dependencies = [ "Inflector", "ahash", @@ -2274,9 +2247,9 @@ dependencies = [ [[package]] name = "swc_ecma_transforms_optimization" -version = "0.168.17" +version = "0.168.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5cc33b6d03f18066730d07615ef7317fd12ae72c814792978612dab20dc6054d" +checksum = "cb61c9a4b7b6e634792a115c5d07eec954f739d2cac0a38f197a06647dc4aae4" dependencies = [ "ahash", "dashmap", @@ -2299,9 +2272,9 @@ dependencies = [ [[package]] name = "swc_ecma_transforms_proposal" -version = "0.145.16" +version = "0.145.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44f1f8ca548616af6a2081dfe699202f5abadcc0045fc85f219ba83a5b960844" +checksum = "833a2323fa26a4353d06e0b3523579bd6ade44bc7be96961e87377a095c9fdd1" dependencies = [ "either", "serde", @@ -2318,9 +2291,9 @@ dependencies = [ [[package]] name = "swc_ecma_transforms_react" -version = "0.156.16" +version = "0.156.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "54746247c87a02c2c4006ec21502cfa9e9d72dfa897b1603a3c1b460ee2f90bf" +checksum = "e40abfb7ba0875a49104c2b76847613543fbab5498b901cf4be26ac8e7fd2a28" dependencies = [ "ahash", "base64", @@ -2344,9 +2317,9 @@ dependencies = [ [[package]] name = "swc_ecma_transforms_typescript" -version = "0.160.17" +version = "0.160.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "964aa70be0218e8d3a89b8600818f9ba798884322b19094dfdb35a684f2728c6" +checksum = "be32bb1f81fea1052cc519ffa8a410f898bf66cba9fc2649c3b698a6314be463" dependencies = [ "serde", "swc_atoms", @@ -2360,9 +2333,9 @@ dependencies = [ [[package]] name = "swc_ecma_usage_analyzer" -version = "0.1.6" +version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "54e7e7291e380c3030a1340d14bdbf99da915e4b789b17e13f06a63cbd9d7646" +checksum = "2aed8e46ccf0d5fe39ccb1998f20b72c9ace0c3314f0e0e9e9190b7d02a38c4f" dependencies = [ "ahash", "indexmap", @@ -2378,13 +2351,14 @@ dependencies = [ [[package]] name = "swc_ecma_utils" -version = "0.106.11" +version = "0.106.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a0c47371c25d2cb110d71e351376be57a718d3a64710ac79b3169ab24165c54f" +checksum = "d513e354d976a0e3d233e81b59a0bdcf85c86c87adb00b28c33901494e829a2b" dependencies = [ "indexmap", "num_cpus", "once_cell", + "rustc-hash", "swc_atoms", "swc_common", "swc_ecma_ast", @@ -2395,9 +2369,9 @@ dependencies = [ [[package]] name = "swc_ecma_visit" -version = "0.81.7" +version = "0.81.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6fa0f5e65af0764045ef268c19d8e0f7fed27ff95c69c198427dcfd5b10685a8" +checksum = "0ebf5de90444c90b1905b7618800a7572fc757faa8c90cc1c6031d1f6ca179df" dependencies = [ "num-bigint", "swc_atoms", @@ -2421,9 +2395,9 @@ dependencies = [ [[package]] name = "swc_error_reporters" -version = "0.13.24" +version = "0.13.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "035712357b19deefa23cec538f5cca48e6a799b1f37f6bf7cfbf1328f035c030" +checksum = "81fbda0a7583deb49edf379e2c119231312ad2d3a37a219143a3d8678a205a5f" dependencies = [ "anyhow", "miette 4.7.1", @@ -2434,9 +2408,9 @@ dependencies = [ [[package]] name = "swc_fast_graph" -version = "0.17.24" +version = "0.17.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e69603ddc8607e2ea0b8482f868c6b0f30269e790c6cea227b9ae288a35f7d04" +checksum = "06584f28662339e1972d164d263b3bfacdc13e1acb5fbe6d568c132a4693034b" dependencies = [ "ahash", "indexmap", @@ -2458,9 +2432,9 @@ dependencies = [ [[package]] name = "swc_node_comments" -version = "0.16.23" +version = "0.16.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba3fddb25502adcfe78b4802ae3704f3d32f38412be55961034ab7361e73d943" +checksum = "d7e61e9d8216db04cad1a5a1621e524ea1e797efde2a61769a60cec5afb8ed9d" dependencies = [ "ahash", "dashmap", @@ -2470,9 +2444,9 @@ dependencies = [ [[package]] name = "swc_timer" -version = "0.17.25" +version = "0.17.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cec4226d8a7a27aef59a5694912f01dcc90dd2c688d07aa00a118a13eb2ea74e" +checksum = "150b8b822dca0edfeca004cb24974720f7e0e8208bdc46e3e6eecfe3a3c37d17" dependencies = [ "tracing", ] @@ -2514,9 +2488,9 @@ dependencies = [ [[package]] name = "syn" -version = "1.0.105" +version = "1.0.107" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60b9b43d45702de4c839cb9b51d9f529c5dd26a4aff255b42b1ebc03e88ee908" +checksum = "1f4064b5b16e03ae50984a5a8ed5d4f8803e6bc1fd170a3cda91a1be4b18e3f5" dependencies = [ "proc-macro2", "quote", @@ -2544,11 +2518,11 @@ dependencies = [ [[package]] name = "terminal_size" -version = "0.2.2" +version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40ca90c434fd12083d1a6bdcbe9f92a14f96c8a1ba600ba451734ac334521f7a" +checksum = "cb20089a8ba2b69debd491f8d2d023761cbf196e999218c591fa1e7e15a21907" dependencies = [ - "rustix 0.35.13", + "rustix", "windows-sys", ] @@ -2565,18 +2539,18 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.37" +version = "1.0.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10deb33631e3c9018b9baf9dcbbc4f737320d2b576bac10f6aefa048fa407e3e" +checksum = "6a9cd18aa97d5c45c6603caea1da6628790b37f7a34b6ca89522331c5180fed0" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.37" +version = "1.0.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "982d17546b47146b28f7c22e3d08465f6b8903d0ea13c1660d9d84a6e7adcdbb" +checksum = "1fb327af4685e4d03fa8cbcf1716380da910eeb2bb8be417e7f9fd3fb164f36f" dependencies = [ "proc-macro2", "quote", @@ -2764,9 +2738,9 @@ checksum = "d70b6494226b36008c8366c288d77190b3fad2eb4c10533139c1c1f461127f1a" [[package]] name = "unicode-ident" -version = "1.0.5" +version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ceab39d59e4c9499d4e5a8ee0e2735b891bb7308ac83dfb4e80cad195c9f6f3" +checksum = "84a22b9f218b40614adcb3f4ff08b703773ad44fa9423e4e0d346d5db86e4ebc" [[package]] name = "unicode-linebreak" @@ -2795,9 +2769,9 @@ checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b" [[package]] name = "unsafe-libyaml" -version = "0.2.4" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1e5fa573d8ac5f1a856f8d7be41d390ee973daf97c806b2c1a465e4e1406e68" +checksum = "bc7ed8ba44ca06be78ea1ad2c3682a43349126c8818054231ee6f4748012aed2" [[package]] name = "url" @@ -2818,9 +2792,9 @@ checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" [[package]] name = "vergen" -version = "7.4.2" +version = "7.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73ba753d713ec3844652ad2cb7eb56bc71e34213a14faddac7852a10ba88f61e" +checksum = "efadd36bc6fde40c6048443897d69511a19161c0756cb704ed403f8dfd2b7d1c" dependencies = [ "anyhow", "cfg-if", From d214166cb5ef125af5880a638c038e810f059098 Mon Sep 17 00:00:00 2001 From: Joshua Wong Date: Thu, 5 Jan 2023 14:20:13 -0600 Subject: [PATCH 046/517] style: run rustfmt --- crates/forge_analyzer/src/definitions.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/crates/forge_analyzer/src/definitions.rs b/crates/forge_analyzer/src/definitions.rs index 17c15505..9539a707 100644 --- a/crates/forge_analyzer/src/definitions.rs +++ b/crates/forge_analyzer/src/definitions.rs @@ -730,8 +730,9 @@ fn classify_api_call(expr: &Expr) -> ApiCallKind { use regex::Regex; // FIXME: this should be done as a dataflow analysis instead of on the AST. - static TRIVIAL: Lazy = - Lazy::new(|| Regex::new(r"user|instance|avatar|license|preferences|server[iI]nfo").unwrap()); + static TRIVIAL: Lazy = Lazy::new(|| { + Regex::new(r"user|instance|avatar|license|preferences|server[iI]nfo").unwrap() + }); #[derive(Default)] struct ApiCallClassifier { From 21046bd7cf516c931d220685740d95807633991b Mon Sep 17 00:00:00 2001 From: Joshua Wong Date: Wed, 18 Jan 2023 14:02:27 -0600 Subject: [PATCH 047/517] fix(parser): parse files with decorators Closes #9 --- crates/fsrt/src/main.rs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/crates/fsrt/src/main.rs b/crates/fsrt/src/main.rs index 4f82f05a..cb89486b 100644 --- a/crates/fsrt/src/main.rs +++ b/crates/fsrt/src/main.rs @@ -20,7 +20,7 @@ use swc_core::{ visit::FoldWith, }, }; -use tracing::{debug, warn}; +use tracing::{debug, warn, instrument}; use tracing_subscriber::{prelude::*, EnvFilter}; use tracing_tree::HierarchicalLayer; @@ -86,6 +86,7 @@ struct ForgeProject { } impl ForgeProject { + #[instrument(skip(src, iter))] fn with_files_and_sourceroot, I: IntoIterator>( src: P, iter: I, @@ -97,12 +98,15 @@ impl ForgeProject { let ctx = iter.into_iter().fold(ctx, |mut ctx, p| { let sourcemap = Arc::clone(&sm); GLOBALS.set(&globals, || { + debug!(file = %p.display(), "parsing"); let src = sourcemap.load_file(&p).unwrap(); + debug!("loaded sourcemap"); let mut recovered_errors = vec![]; let module = parse_file_as_module( &src, Syntax::Typescript(TsConfig { tsx: true, + decorators: true, ..Default::default() }), target, @@ -110,6 +114,7 @@ impl ForgeProject { &mut recovered_errors, ) .unwrap(); + debug!("finished parsing"); let mut hygeine = resolver(Mark::new(), Mark::new(), true); let module = module.fold_with(&mut hygeine); ctx.load_module(p, module); From 8cb376fdef3c55c6ac7d61310e062649083e8e4c Mon Sep 17 00:00:00 2001 From: Joshua Wong Date: Wed, 18 Jan 2023 14:03:55 -0600 Subject: [PATCH 048/517] fix(lowerer): set shorthand props on the parent --- crates/forge_analyzer/src/definitions.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/forge_analyzer/src/definitions.rs b/crates/forge_analyzer/src/definitions.rs index 9539a707..d2407c92 100644 --- a/crates/forge_analyzer/src/definitions.rs +++ b/crates/forge_analyzer/src/definitions.rs @@ -1919,12 +1919,12 @@ impl Visit for Lowerer<'_> { Prop::Shorthand(id) => { let id = id.to_id(); let sym = id.0.clone(); - let def_id = self.get_or_insert_sym(id); + let new_def = self.get_or_insert_sym(id); self.res .def_mut(def_id) .expect_class() .pub_members - .push((sym, self.curr_def.unwrap())); + .push((sym, new_def)); } Prop::KeyValue(KeyValueProp { key, value }) => { if let sym @ Some(_) = key.as_symbol() { From 6bc6f36945cdd94310950c4cb9ec9918b8338b1c Mon Sep 17 00:00:00 2001 From: Joshua Wong Date: Wed, 18 Jan 2023 14:04:21 -0600 Subject: [PATCH 049/517] style: run rustfmt --- crates/fsrt/src/main.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/fsrt/src/main.rs b/crates/fsrt/src/main.rs index cb89486b..086125a8 100644 --- a/crates/fsrt/src/main.rs +++ b/crates/fsrt/src/main.rs @@ -20,7 +20,7 @@ use swc_core::{ visit::FoldWith, }, }; -use tracing::{debug, warn, instrument}; +use tracing::{debug, instrument, warn}; use tracing_subscriber::{prelude::*, EnvFilter}; use tracing_tree::HierarchicalLayer; From f47bf678f53807200f4166118284c3895a17b98b Mon Sep 17 00:00:00 2001 From: Joshua Wong Date: Sat, 21 Jan 2023 12:04:28 -0600 Subject: [PATCH 050/517] chore: bump deps --- Cargo.lock | 192 +++++++++++++++---------------- Cargo.toml | 20 ++-- crates/forge_analyzer/Cargo.toml | 4 - crates/forge_loader/Cargo.toml | 1 - 4 files changed, 101 insertions(+), 116 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index baf19957..a0412ab5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -216,9 +216,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.0.32" +version = "4.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7db700bc935f9e43e88d00b0850dae18a63773cfbec6d8e070fccf7fef89a39" +checksum = "4ec7a4128863c188deefe750ac1d1dfe66c236909f845af04beed823638dc1b2" dependencies = [ "bitflags", "clap_derive", @@ -232,9 +232,9 @@ dependencies = [ [[package]] name = "clap_derive" -version = "4.0.21" +version = "4.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0177313f9f02afc995627906bbd8967e2be069f5261954222dac78290c2b9014" +checksum = "684a277d672e91966334af371f1a7b5833f9aa00b07c84e92fbce95e00208ce8" dependencies = [ "heck", "proc-macro-error", @@ -245,9 +245,9 @@ dependencies = [ [[package]] name = "clap_lex" -version = "0.3.0" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d4198f73e42b4936b35b5bb248d81d2b595ecb170da0bac7655c54eedfa8da8" +checksum = "783fe232adfca04f90f56201b26d79682d4cd2625e0bc7290b95123afe558ade" dependencies = [ "os_str_bytes", ] @@ -342,9 +342,9 @@ dependencies = [ [[package]] name = "cxx" -version = "1.0.85" +version = "1.0.86" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5add3fc1717409d029b20c5b6903fc0c0b02fa6741d820054f4a2efa5e5816fd" +checksum = "51d1075c37807dcf850c379432f0df05ba52cc30f279c5cfc43cc221ce7f8579" dependencies = [ "cc", "cxxbridge-flags", @@ -354,9 +354,9 @@ dependencies = [ [[package]] name = "cxx-build" -version = "1.0.85" +version = "1.0.86" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4c87959ba14bc6fbc61df77c3fcfe180fc32b93538c4f1031dd802ccb5f2ff0" +checksum = "5044281f61b27bc598f2f6647d480aed48d2bf52d6eb0b627d84c0361b17aa70" dependencies = [ "cc", "codespan-reporting", @@ -369,15 +369,15 @@ dependencies = [ [[package]] name = "cxxbridge-flags" -version = "1.0.85" +version = "1.0.86" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69a3e162fde4e594ed2b07d0f83c6c67b745e7f28ce58c6df5e6b6bef99dfb59" +checksum = "61b50bc93ba22c27b0d31128d2d130a0a6b3d267ae27ef7e4fae2167dfe8781c" [[package]] name = "cxxbridge-macro" -version = "1.0.85" +version = "1.0.86" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e7e2adeb6a0d4a282e581096b06e1791532b7d576dcde5ccd9382acf55db8e6" +checksum = "39e61fda7e62115119469c7b3591fd913ecca96fb766cfd3f2e2502ab7bc87a5" dependencies = [ "proc-macro2", "quote", @@ -456,9 +456,9 @@ checksum = "90e5c1c8368803113bf0c9584fc495a58b86dc8a29edbf8fe877d21d9507e797" [[package]] name = "enum-iterator" -version = "1.1.3" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "45a0ac4aeb3a18f92eaf09c6bb9b3ac30ff61ca95514fc58cbead1c9a6bf5401" +checksum = "91a4ec26efacf4aeff80887a175a419493cb6f8b5480d26387eb0bd038976187" dependencies = [ "enum-iterator-derive", ] @@ -523,7 +523,6 @@ checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" name = "forge_analyzer" version = "0.1.0" dependencies = [ - "clap", "fixedbitset", "forge_file_resolver", "forge_utils", @@ -535,13 +534,10 @@ dependencies = [ "regex", "serde", "serde_json", - "serde_yaml", "smallvec", "swc_core", "time 0.3.17", "tracing", - "tracing-subscriber", - "typed-arena", "typed-index-collections", ] @@ -553,7 +549,6 @@ version = "0.1.0" name = "forge_loader" version = "0.1.0" dependencies = [ - "clap", "forge_utils", "itertools", "miette 5.5.0", @@ -745,9 +740,9 @@ dependencies = [ [[package]] name = "io-lifetimes" -version = "1.0.3" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46112a93252b123d31a119a8d1a1ac19deac4fac6e0e8b0df58f0d4e5870e63c" +checksum = "e7d6c6f8c91b4b9ed43484ad1a938e393caf35960fce7f82a040497207bd8e9e" dependencies = [ "libc", "windows-sys", @@ -1299,9 +1294,9 @@ checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c" [[package]] name = "preset_env_base" -version = "0.3.3" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97cc85a18e7f8246f3ccdd764d1f51fa3c910293942f84483a1cf1647df47198" +checksum = "db4a43af74678e784b17db952b7fd726937b0a058c7c972624ddfd366e7603e4" dependencies = [ "ahash", "anyhow", @@ -1359,9 +1354,9 @@ checksum = "dc375e1527247fe1a97d8b7156678dfe7c1af2fc075c9a4db3690ecd2a148068" [[package]] name = "proc-macro2" -version = "1.0.49" +version = "1.0.50" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57a8eca9f9c4ffde41714334dee777596264c7825420f521abc92b5b5deb63a5" +checksum = "6ef7d57beacfaf2d8aee5937dab7b7f28de3cb8b1828479bb5de2a7106f2bae2" dependencies = [ "unicode-ident", ] @@ -1444,9 +1439,9 @@ dependencies = [ [[package]] name = "regex" -version = "1.7.0" +version = "1.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e076559ef8e241f2ae3479e36f97bd5741c0330689e217ad51ce2c76808b868a" +checksum = "48aaa5748ba571fb95cd2c85c09f629215d3a6ece942baa100950af03a34f733" dependencies = [ "aho-corasick", "memchr", @@ -1497,9 +1492,9 @@ dependencies = [ [[package]] name = "rustix" -version = "0.36.6" +version = "0.36.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4feacf7db682c6c329c4ede12649cd36ecab0f3be5b7d74e6a20304725db4549" +checksum = "d4fdebc4b395b7fbb9ab11e462e20ed9051e7b16e42d24042c776eca0ac81b03" dependencies = [ "bitflags", "errno", @@ -1616,9 +1611,9 @@ dependencies = [ [[package]] name = "serde_yaml" -version = "0.9.16" +version = "0.9.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92b5b431e8907b50339b51223b97d102db8d987ced36f6e4d03621db9316c834" +checksum = "8fb06d4b6cdaef0e0c51fa881acb721bed3c924cfaa71d9c94a3b771dfdf6567" dependencies = [ "indexmap", "itoa", @@ -1667,17 +1662,16 @@ checksum = "f67ad224767faa3c7d8b6d91985b78e70a1324408abcb1cfcc2be4c06bc06043" [[package]] name = "sourcemap" -version = "6.2.0" +version = "6.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c46fdc1838ff49cf692226f5c2b0f5b7538f556863d0eca602984714667ac6e7" +checksum = "aebe057d110ddba043708da3fb010bf562ff6e9d4d60c9ee92860527bcbeccd6" dependencies = [ "base64", "if_chain", - "lazy_static", - "regex", "rustc_version", "serde", "serde_json", + "unicode-id", "url", ] @@ -1790,9 +1784,9 @@ dependencies = [ [[package]] name = "swc" -version = "0.236.21" +version = "0.239.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e02bda1fadcecd18aa755386fd726228708cbb448348e42781f519d65b7ecab" +checksum = "a68788142778ec4fb76367b7ae2883b6829763e139d2531a8dce17df63659242" dependencies = [ "ahash", "anyhow", @@ -1838,9 +1832,9 @@ dependencies = [ [[package]] name = "swc_atoms" -version = "0.4.32" +version = "0.4.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ad59af21529fcd3f4f8fa6b1ae399c2b183ec42c68347d76d68d6e5b657956e" +checksum = "93a49e93996e1c1cfdb641cd94888da545d440ff6cada04155ef118adee620c1" dependencies = [ "once_cell", "rustc-hash", @@ -1866,9 +1860,9 @@ dependencies = [ [[package]] name = "swc_common" -version = "0.29.25" +version = "0.29.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "506321cad7393893018aac83a3b3bd25203883e8c47ab0864bb43195d43b22dd" +checksum = "90a2c285d33b47a5e532a662c178dc91956534ff52207892918d3034a534ae12" dependencies = [ "ahash", "ast_node", @@ -1920,9 +1914,9 @@ dependencies = [ [[package]] name = "swc_core" -version = "0.48.40" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5df312d4172937c513ef33c70ee6043e63bb69c482ed6db97e72e5b624119208" +checksum = "b250fdfac2060ec6996c45939ff1ec295f78913738be9dcb6853fca88445a524" dependencies = [ "swc", "swc_atoms", @@ -1939,9 +1933,9 @@ dependencies = [ [[package]] name = "swc_ecma_ast" -version = "0.95.9" +version = "0.95.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3cc936f04c4e671ae5918b573a50945c5189d3dcdd57e4faddd47889717e1416" +checksum = "81e5ff66d8aa3c21c32ffcdd871712f78a50819118690ba8195974d665d008b4" dependencies = [ "bitflags", "is-macro", @@ -1956,9 +1950,9 @@ dependencies = [ [[package]] name = "swc_ecma_codegen" -version = "0.128.16" +version = "0.128.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14ed1704af20b69f395f412457765b2d50b76428d65325e79a1cab4e27c9405c" +checksum = "ecb5143ce3b7089539fb55875a43b6111e4e1af1bb505e54c9cd4095ce12ab09" dependencies = [ "memchr", "num-bigint", @@ -1988,9 +1982,9 @@ dependencies = [ [[package]] name = "swc_ecma_ext_transforms" -version = "0.92.17" +version = "0.92.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f8f6542f1f02fc723f42ec1b84eefd1eb19453be5fda64e42ac38092c63e4a9" +checksum = "f28c4cf9f3fb45067b0300a0d3020dde63f836d87ab4ca12485b37d7868f55d7" dependencies = [ "phf", "swc_atoms", @@ -2002,9 +1996,9 @@ dependencies = [ [[package]] name = "swc_ecma_lints" -version = "0.67.20" +version = "0.67.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "27759e18f3533499df2034c41f68208a66240e31d8e9b97a78ae828324b223f3" +checksum = "64de81f7e8a3af805797766aba92c410bc203f29816fd3d48d3394fe34445030" dependencies = [ "ahash", "auto_impl", @@ -2023,9 +2017,9 @@ dependencies = [ [[package]] name = "swc_ecma_loader" -version = "0.41.26" +version = "0.41.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42710b93ec010a5e0354cc86d621a3dd0243351d649d0c273c1887035a256151" +checksum = "6f85e707c11871e45d103f81699189d8e94d06abf9b7dc958dcea8ff410762cd" dependencies = [ "ahash", "anyhow", @@ -2045,9 +2039,9 @@ dependencies = [ [[package]] name = "swc_ecma_minifier" -version = "0.160.33" +version = "0.160.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "944c20eea09e8410ba642f7d9e52f4b1e4e91b0c3ce3be07a6d5923eebc5c7b0" +checksum = "4d446b81d163c76cc172366ae54cf26c841c67c776dc57e97d327842d6e27b87" dependencies = [ "ahash", "arrayvec", @@ -2080,9 +2074,9 @@ dependencies = [ [[package]] name = "swc_ecma_parser" -version = "0.123.14" +version = "0.123.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca7a997c834b6e890b7a40d922aa353f311e3df2b9f6517c115923b1efa8eab2" +checksum = "928c148fee43281df26871a2d1f242092734622e70aa3ea7ad474723537c702d" dependencies = [ "either", "enum_kind", @@ -2099,9 +2093,9 @@ dependencies = [ [[package]] name = "swc_ecma_preset_env" -version = "0.175.23" +version = "0.176.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd8fde511662a77520869eb8f2b43390c1ff140f4e92f7eeef80012751dc97f5" +checksum = "0ebd54f36f15affcd628a192ef2ef9174e6cecc066036684d613cbc705e15425" dependencies = [ "ahash", "anyhow", @@ -2124,9 +2118,9 @@ dependencies = [ [[package]] name = "swc_ecma_transforms" -version = "0.199.23" +version = "0.199.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "edacb417e630c239af3098fca3c77bf75dea9dd228a97cf789c6b139b3462d9a" +checksum = "947d24e05c1ab93c6e64d9534c41c91add3d40533e8ba4bd8504cf456dfcfe0f" dependencies = [ "swc_atoms", "swc_common", @@ -2144,9 +2138,9 @@ dependencies = [ [[package]] name = "swc_ecma_transforms_base" -version = "0.112.20" +version = "0.112.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4fd70dfa455193b3101beff13558ed09d0253cdbfc7e2147de8a9a439804dbd" +checksum = "8136889c97ee3c8fd9409f836e30f01c97e1ee0ca01d74a887a1943e2a92d41f" dependencies = [ "better_scoped_tls", "bitflags", @@ -2166,9 +2160,9 @@ dependencies = [ [[package]] name = "swc_ecma_transforms_classes" -version = "0.101.20" +version = "0.101.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e16e7b14b835fbfdbe7c24df1e0446ef3cf596928b3911d49e14c46b01caef3" +checksum = "42225b4adf0400ca97852f42c521474d9960623732a7f687804913256936c1f2" dependencies = [ "swc_atoms", "swc_common", @@ -2180,9 +2174,9 @@ dependencies = [ [[package]] name = "swc_ecma_transforms_compat" -version = "0.137.21" +version = "0.137.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11344cc707a9ca1aa1a928d4919d16518d86859c0ef7695ef3c3c0f0aa010cad" +checksum = "c0c867a21058a6918b0e1ebd9f1ec2b3fd947de4ff71a3125c3f98f1cc40628e" dependencies = [ "ahash", "arrayvec", @@ -2219,9 +2213,9 @@ dependencies = [ [[package]] name = "swc_ecma_transforms_module" -version = "0.154.21" +version = "0.154.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8324b5e86ce9a5d3890b3a7200dd29a22857d5a154ecf041e3aa983e871673f4" +checksum = "5745e55266ffd7c14a392474e3a25885857c214bd45074b04c9a0c84a81509df" dependencies = [ "Inflector", "ahash", @@ -2247,9 +2241,9 @@ dependencies = [ [[package]] name = "swc_ecma_transforms_optimization" -version = "0.168.23" +version = "0.168.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb61c9a4b7b6e634792a115c5d07eec954f739d2cac0a38f197a06647dc4aae4" +checksum = "36cea8460fa0f28fea76afc4906a1f8848d9360dcbed181720b0e92a7f2e2a13" dependencies = [ "ahash", "dashmap", @@ -2272,9 +2266,9 @@ dependencies = [ [[package]] name = "swc_ecma_transforms_proposal" -version = "0.145.21" +version = "0.145.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "833a2323fa26a4353d06e0b3523579bd6ade44bc7be96961e87377a095c9fdd1" +checksum = "8972e9b16e89917d99f669af270a41320a6ca08c3480eef075845414526ccc5e" dependencies = [ "either", "serde", @@ -2291,9 +2285,9 @@ dependencies = [ [[package]] name = "swc_ecma_transforms_react" -version = "0.156.22" +version = "0.156.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e40abfb7ba0875a49104c2b76847613543fbab5498b901cf4be26ac8e7fd2a28" +checksum = "1c3499f6c71eeb443a3cdcb0a82a8dcb70d83ce589f5be652fc0ff25ff68344b" dependencies = [ "ahash", "base64", @@ -2317,9 +2311,9 @@ dependencies = [ [[package]] name = "swc_ecma_transforms_typescript" -version = "0.160.23" +version = "0.160.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be32bb1f81fea1052cc519ffa8a410f898bf66cba9fc2649c3b698a6314be463" +checksum = "86e08d5a9a6ecf206585753ff67052c9d936d2914f3683e8b0021204ef355b04" dependencies = [ "serde", "swc_atoms", @@ -2333,9 +2327,9 @@ dependencies = [ [[package]] name = "swc_ecma_usage_analyzer" -version = "0.1.11" +version = "0.1.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2aed8e46ccf0d5fe39ccb1998f20b72c9ace0c3314f0e0e9e9190b7d02a38c4f" +checksum = "95430933ea004dfa5d5da3cc383f57f578dedab27b8e4645c84610c6c72b3ab8" dependencies = [ "ahash", "indexmap", @@ -2351,9 +2345,9 @@ dependencies = [ [[package]] name = "swc_ecma_utils" -version = "0.106.16" +version = "0.106.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d513e354d976a0e3d233e81b59a0bdcf85c86c87adb00b28c33901494e829a2b" +checksum = "6bdb27e68d2c658204efd98c506ea2ecf942737b513f5b2140b8d81d04fcedc1" dependencies = [ "indexmap", "num_cpus", @@ -2369,9 +2363,9 @@ dependencies = [ [[package]] name = "swc_ecma_visit" -version = "0.81.9" +version = "0.81.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ebf5de90444c90b1905b7618800a7572fc757faa8c90cc1c6031d1f6ca179df" +checksum = "aeee9de2cf4e80f0c6802f9a44989e6b1ad973b94849e76b1745f6d87144f517" dependencies = [ "num-bigint", "swc_atoms", @@ -2395,9 +2389,9 @@ dependencies = [ [[package]] name = "swc_error_reporters" -version = "0.13.26" +version = "0.13.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81fbda0a7583deb49edf379e2c119231312ad2d3a37a219143a3d8678a205a5f" +checksum = "ad1b6b90dbfe7a4c7962c21732cc403f071bb795d93c5a82bce5290e31b3a9b5" dependencies = [ "anyhow", "miette 4.7.1", @@ -2408,9 +2402,9 @@ dependencies = [ [[package]] name = "swc_fast_graph" -version = "0.17.26" +version = "0.17.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06584f28662339e1972d164d263b3bfacdc13e1acb5fbe6d568c132a4693034b" +checksum = "1c54361a935915a14d68a906126a8ee6e08ac27a0698a74337cbd6c0e15af40a" dependencies = [ "ahash", "indexmap", @@ -2432,9 +2426,9 @@ dependencies = [ [[package]] name = "swc_node_comments" -version = "0.16.25" +version = "0.16.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7e61e9d8216db04cad1a5a1621e524ea1e797efde2a61769a60cec5afb8ed9d" +checksum = "7fda859d1a9e48fb151e9da8cfc29cac4dd788941ba9cf639a77828c978adcb7" dependencies = [ "ahash", "dashmap", @@ -2444,9 +2438,9 @@ dependencies = [ [[package]] name = "swc_timer" -version = "0.17.27" +version = "0.17.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "150b8b822dca0edfeca004cb24974720f7e0e8208bdc46e3e6eecfe3a3c37d17" +checksum = "1e5e7e11a10d9900fc60809bf560ef7ebf0b571dc5b0733005fb11343270574b" dependencies = [ "tracing", ] @@ -2499,9 +2493,9 @@ dependencies = [ [[package]] name = "termcolor" -version = "1.1.3" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bab24d30b911b2376f3a13cc2cd443142f0c81dda04c118693e35b3835757755" +checksum = "be55cf8942feac5c765c2c993422806843c9a9a45d4d5c407ad6dd2ea95eb9b6" dependencies = [ "winapi-util", ] @@ -2708,9 +2702,9 @@ dependencies = [ [[package]] name = "typed-arena" -version = "2.0.1" +version = "2.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0685c84d5d54d1c26f7d3eb96cd41550adb97baed141a761cf335d3d33bcd0ae" +checksum = "6af6ae20167a9ece4bcb41af5b80f8a1f1df981f6391189ce00fd257af04126a" [[package]] name = "typed-index-collections" @@ -2792,9 +2786,9 @@ checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" [[package]] name = "vergen" -version = "7.4.4" +version = "7.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "efadd36bc6fde40c6048443897d69511a19161c0756cb704ed403f8dfd2b7d1c" +checksum = "571b69f690c855821462709b6f41d42ceccc316fbd17b60bd06d06928cfe6a99" dependencies = [ "anyhow", "cfg-if", diff --git a/Cargo.toml b/Cargo.toml index 9b58cc6d..e333fbf5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,34 +8,30 @@ edition = "2021" license = "MIT OR Apache-2.0" [workspace.dependencies] -clap = { version = "4.0.29", features = ["derive", "wrap_help"] } +clap = { version = "4.1.1", features = ["derive", "wrap_help"] } fixedbitset = "0.4.2" itertools = "0.10.5" miette = { version = "5.5.0", features = ["fancy"] } num-bigint = { version = "0.4.3" } -serde = { version = "1.0.150", features = ["derive"] } -serde_json = "1.0.89" -serde_yaml = "0.9.14" +serde = { version = "1.0.152", features = ["derive"] } +serde_json = "1.0.91" +serde_yaml = "0.9.17" petgraph = "0.6.2" pretty_assertions = "1.3.0" indexmap = { version = "1.9.2", features = ["std"] } -once_cell = "1.16.0" -regex = "1.7.0" +once_cell = "1.17.0" +regex = "1.7.1" rustc-hash = "1.1.0" smallvec = { version = "1.10.0", features = ["union", "const_new"] } -swc_core = { version = "0.48.24", features = ["common_perf", "common", "common_sourcemap", "ecma_visit_path", "ecma_utils", "ecma_ast", "swc", "ecma_visit", "ecma_transforms", "ecma_transforms_module", "ecma_transforms_typescript", "ecma_parser_typescript"] } -thiserror = "1.0.37" +swc_core = { version = "0.53.1", features = ["common_perf", "common", "common_sourcemap", "ecma_visit_path", "ecma_utils", "ecma_ast", "swc", "ecma_visit", "ecma_transforms", "ecma_transforms_module", "ecma_transforms_typescript", "ecma_parser_typescript"] } +thiserror = "1.0.38" time = { version = "0.3.17", features = ["local-offset", "serde-human-readable"] } tracing = "0.1.37" tracing-subscriber = { version = "0.3.16", features = ["env-filter"] } tracing-tree = "0.2.2" -typed-arena = "2.0.1" typed-index-collections = "3.1.0" walkdir = "2.3.2" forge_loader = { path = "crates/forge_loader" } forge_analyzer = { path = "crates/forge_analyzer" } forge_file_resolver = { path = "crates/forge_file_resolver" } forge_utils = { path = "crates/forge_utils" } - -[profile.release] -debug = true diff --git a/crates/forge_analyzer/Cargo.toml b/crates/forge_analyzer/Cargo.toml index b9b83003..130f7fae 100644 --- a/crates/forge_analyzer/Cargo.toml +++ b/crates/forge_analyzer/Cargo.toml @@ -8,7 +8,6 @@ license.workspace = true # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -clap.workspace = true fixedbitset.workspace = true forge_file_resolver.workspace = true forge_utils.workspace = true @@ -19,13 +18,10 @@ petgraph.workspace = true regex.workspace = true serde_json.workspace = true serde.workspace = true -serde_yaml.workspace = true smallvec.workspace = true swc_core.workspace = true time.workspace = true -tracing-subscriber.workspace = true tracing.workspace = true -typed-arena.workspace = true typed-index-collections.workspace = true [dev-dependencies] diff --git a/crates/forge_loader/Cargo.toml b/crates/forge_loader/Cargo.toml index d7fd349d..984321ab 100644 --- a/crates/forge_loader/Cargo.toml +++ b/crates/forge_loader/Cargo.toml @@ -8,7 +8,6 @@ license.workspace = true # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -clap.workspace = true forge_utils.workspace = true itertools.workspace = true miette.workspace = true From 144b8cd3fbacf0f2fe4956479b79d8c4e8d0e610 Mon Sep 17 00:00:00 2001 From: Joshua Wong Date: Sat, 21 Jan 2023 12:04:51 -0600 Subject: [PATCH 051/517] ci: cache dependencies in workflow --- .github/workflows/ci.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ab1119d1..1744b6c2 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -16,6 +16,7 @@ jobs: with: toolchain: stable components: rustfmt clippy rust-src + - uses: Swatinem/rust-cache@v2 - run: cargo test - run: cargo check - run: cargo fmt --all --check From 32556bbfcc6be096f4c075c540bd31aa1c01e2c8 Mon Sep 17 00:00:00 2001 From: James Gersbach Date: Wed, 7 Jun 2023 10:22:47 -0700 Subject: [PATCH 052/517] feat: EAS-1592 mistakingly classifies resolvers exposed from consumers as user reachable --- .DS_Store | Bin 0 -> 6148 bytes .gitignore | 1 + .vscode/launch.json | 120 +++++++++++++++++++++++ crates/forge_analyzer/src/definitions.rs | 1 - crates/forge_loader/src/manifest.rs | 48 +++++++-- crates/fsrt/src/main.rs | 1 + 6 files changed, 163 insertions(+), 8 deletions(-) create mode 100644 .DS_Store create mode 100644 .vscode/launch.json diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..22adfb13bb81fd31264fccb66060aa508ff155a3 GIT binary patch literal 6148 zcmeHKO>YuG7=DK$T_7qkHR;7<6R)&VY>lQDFuOL8)2Db5Xgj?pwQ!!|jHJJ9VV zyg|>XmE=lAzr_?SE^&|1Rwu<0n#Ngq=+Hu2O*#f%reK_r!|c_+?NO9$oe90-b@NIo z#2s*k_^ndy9}IUo9_lyghdT`Xaa5`NWSNcZwcMt)X%(&a&L=)~qFyu^*L$P4;;F-V z;9jJA?rYzhc1v3g9!5Ps^hT24_dQg3_r?$Vd|Ky|us@buM-^CkE8i_`&t`iM_se$m zpgJ$xvq#lR+1|hZU_Q@Vx9{FNJn5bXgOGp0ykQKdm^85};wE%ip~Yus6b3vLZ@~pK z&RX}h9@Kfr~FdJBb;f}oFMnGi?uHe4FYY>@!g UG%ge(2WCD5qzon)1+G+qKRScs!2kdN literal 0 HcmV?d00001 diff --git a/.gitignore b/.gitignore index ea8c4bf7..831f1db6 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ /target +/test-apps diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 00000000..871af283 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,120 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "type": "lldb", + "request": "launch", + "name": "Debug unit tests in library 'forge_analyzer'", + "cargo": { + "args": [ + "test", + "--no-run", + "--lib", + "--package=forge_analyzer" + ], + "filter": { + "name": "forge_analyzer", + "kind": "lib" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug unit tests in library 'forge_file_resolver'", + "cargo": { + "args": [ + "test", + "--no-run", + "--lib", + "--package=forge_file_resolver" + ], + "filter": { + "name": "forge_file_resolver", + "kind": "lib" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug unit tests in library 'forge_utils'", + "cargo": { + "args": [ + "test", + "--no-run", + "--lib", + "--package=forge_utils" + ], + "filter": { + "name": "forge_utils", + "kind": "lib" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug unit tests in library 'forge_loader'", + "cargo": { + "args": [ + "test", + "--no-run", + "--lib", + "--package=forge_loader" + ], + "filter": { + "name": "forge_loader", + "kind": "lib" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug executable 'fsrt'", + "cargo": { + "args": [ + "build", + "--bin=fsrt", + "--package=fsrt" + ], + "filter": { + "name": "fsrt", + "kind": "bin" + } + }, + "args": ["./test-apps/jira-damn-vulnerable-forge-app"], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug unit tests in executable 'fsrt'", + "cargo": { + "args": [ + "test", + "--no-run", + "--bin=fsrt", + "--package=fsrt" + ], + "filter": { + "name": "fsrt", + "kind": "bin" + } + }, + "cwd": "${workspaceFolder}" + } + ] +} \ No newline at end of file diff --git a/crates/forge_analyzer/src/definitions.rs b/crates/forge_analyzer/src/definitions.rs index d2407c92..241c98b3 100644 --- a/crates/forge_analyzer/src/definitions.rs +++ b/crates/forge_analyzer/src/definitions.rs @@ -1872,7 +1872,6 @@ impl Visit for Lowerer<'_> { { if let Expr::Lit(Lit::Str(Str { value, .. })) = &**expr { let fname = value.clone(); - println!("defining function: {}", &*fname); let member_def = match &**args { Expr::Fn(_) | Expr::Arrow(_) => self.res.add_anonymous( fname.clone(), diff --git a/crates/forge_loader/src/manifest.rs b/crates/forge_loader/src/manifest.rs index 251b4225..9b7bcd14 100644 --- a/crates/forge_loader/src/manifest.rs +++ b/crates/forge_loader/src/manifest.rs @@ -79,22 +79,22 @@ pub struct ForgeModules<'a> { #[serde(rename = "scheduledTrigger", default, borrow)] scheduled_triggers: Vec>, #[serde(rename = "consumer", default, borrow)] - consumers: Vec>, + pub consumers: Vec>, #[serde(flatten)] - extra: FxHashMap, + extra: FxHashMap>>, } #[derive(Debug, Clone, PartialEq, Eq, Deserialize)] -struct Consumer<'a> { +pub struct Consumer<'a> { key: &'a str, queue: &'a str, #[serde(borrow)] - resolver: Resolver<'a>, + pub resolver: Resolver<'a>, } #[derive(Debug, Clone, PartialEq, Eq, Deserialize)] -struct Resolver<'a> { - function: &'a str, +pub struct Resolver<'a> { + pub function: &'a str, method: &'a str, } @@ -120,6 +120,21 @@ pub struct AppInfo<'a> { pub id: &'a str, } +#[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] +pub struct ModuleResolve<'a> { + pub function: Option<&'a str> +} + +#[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] +pub struct Module<'a> { + #[serde(default, borrow)] + pub function: Option<&'a str>, + #[serde(default, borrow)] + pub resolver: Option>, + #[serde(flatten)] + extra: FxHashMap, +} + /// The representation of a Forge app's `manifest.yml` /// /// Contains the [properties] that are needed to find function entrypoints @@ -199,7 +214,7 @@ impl<'a> ForgeModules<'a> { .sort_unstable_by_key(|trigger| trigger.function); // same rational for using a BTreeSet - let ignored_functions: BTreeSet<_> = self + let mut ignored_functions: BTreeSet<_> = self .scheduled_triggers .into_iter() .map(|trigger| trigger.raw.function) @@ -209,6 +224,25 @@ impl<'a> ForgeModules<'a> { .map(|trigger| trigger.raw.function), ) .collect(); + + let mut alternate_functions: Vec<&str> = Vec::new(); + for module in self.extra { + for module_functions in module.1 { + if !module_functions.function.is_none() { + alternate_functions.push(&module_functions.function.unwrap()); + } + if !module_functions.resolver.is_none() && !module_functions.clone().resolver.unwrap().function.is_none() { + alternate_functions.push(module_functions.resolver.unwrap().function.unwrap()) + } + } + } + + self.consumers.iter().for_each(|consumer| { + if !alternate_functions.contains(&consumer.resolver.function) { + ignored_functions.insert(consumer.resolver.function); + } + }); + self.functions.into_iter().filter_map(move |func| { if ignored_functions.contains(&func.key) { return None; diff --git a/crates/fsrt/src/main.rs b/crates/fsrt/src/main.rs index 086125a8..d1df4f5e 100644 --- a/crates/fsrt/src/main.rs +++ b/crates/fsrt/src/main.rs @@ -173,6 +173,7 @@ fn scan_directory(dir: PathBuf, function: Option<&str>, opts: Opts) -> Result>(); let funcrefs = manifest.modules.into_analyzable_functions().flat_map(|f| { f.sequence(|fmod| { From 7629d19786f72aee7885e7838afd016f0f0610e2 Mon Sep 17 00:00:00 2001 From: James Gersbach Date: Wed, 7 Jun 2023 10:27:58 -0700 Subject: [PATCH 053/517] removing vscode file --- .gitignore | 1 + .vscode/launch.json | 120 -------------------------------------------- 2 files changed, 1 insertion(+), 120 deletions(-) delete mode 100644 .vscode/launch.json diff --git a/.gitignore b/.gitignore index 831f1db6..18f1e5d4 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ /target /test-apps +/.vscode diff --git a/.vscode/launch.json b/.vscode/launch.json deleted file mode 100644 index 871af283..00000000 --- a/.vscode/launch.json +++ /dev/null @@ -1,120 +0,0 @@ -{ - // Use IntelliSense to learn about possible attributes. - // Hover to view descriptions of existing attributes. - // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 - "version": "0.2.0", - "configurations": [ - { - "type": "lldb", - "request": "launch", - "name": "Debug unit tests in library 'forge_analyzer'", - "cargo": { - "args": [ - "test", - "--no-run", - "--lib", - "--package=forge_analyzer" - ], - "filter": { - "name": "forge_analyzer", - "kind": "lib" - } - }, - "args": [], - "cwd": "${workspaceFolder}" - }, - { - "type": "lldb", - "request": "launch", - "name": "Debug unit tests in library 'forge_file_resolver'", - "cargo": { - "args": [ - "test", - "--no-run", - "--lib", - "--package=forge_file_resolver" - ], - "filter": { - "name": "forge_file_resolver", - "kind": "lib" - } - }, - "args": [], - "cwd": "${workspaceFolder}" - }, - { - "type": "lldb", - "request": "launch", - "name": "Debug unit tests in library 'forge_utils'", - "cargo": { - "args": [ - "test", - "--no-run", - "--lib", - "--package=forge_utils" - ], - "filter": { - "name": "forge_utils", - "kind": "lib" - } - }, - "args": [], - "cwd": "${workspaceFolder}" - }, - { - "type": "lldb", - "request": "launch", - "name": "Debug unit tests in library 'forge_loader'", - "cargo": { - "args": [ - "test", - "--no-run", - "--lib", - "--package=forge_loader" - ], - "filter": { - "name": "forge_loader", - "kind": "lib" - } - }, - "args": [], - "cwd": "${workspaceFolder}" - }, - { - "type": "lldb", - "request": "launch", - "name": "Debug executable 'fsrt'", - "cargo": { - "args": [ - "build", - "--bin=fsrt", - "--package=fsrt" - ], - "filter": { - "name": "fsrt", - "kind": "bin" - } - }, - "args": ["./test-apps/jira-damn-vulnerable-forge-app"], - "cwd": "${workspaceFolder}" - }, - { - "type": "lldb", - "request": "launch", - "name": "Debug unit tests in executable 'fsrt'", - "cargo": { - "args": [ - "test", - "--no-run", - "--bin=fsrt", - "--package=fsrt" - ], - "filter": { - "name": "fsrt", - "kind": "bin" - } - }, - "cwd": "${workspaceFolder}" - } - ] -} \ No newline at end of file From febb4bbea430078bd8a3fbdcb44b2aad3ad91338 Mon Sep 17 00:00:00 2001 From: James Gersbach Date: Wed, 7 Jun 2023 10:30:05 -0700 Subject: [PATCH 054/517] rmeoving vscode file --- .gitignore | 1 - 1 file changed, 1 deletion(-) diff --git a/.gitignore b/.gitignore index 18f1e5d4..831f1db6 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,2 @@ /target /test-apps -/.vscode From eea9d188be14d4b7b75b9558d34ae63c8cdab3f4 Mon Sep 17 00:00:00 2001 From: James Gersbach Date: Wed, 7 Jun 2023 11:40:01 -0700 Subject: [PATCH 055/517] formatting --- crates/forge_loader/src/manifest.rs | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/crates/forge_loader/src/manifest.rs b/crates/forge_loader/src/manifest.rs index 9b7bcd14..a28031be 100644 --- a/crates/forge_loader/src/manifest.rs +++ b/crates/forge_loader/src/manifest.rs @@ -122,7 +122,7 @@ pub struct AppInfo<'a> { #[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] pub struct ModuleResolve<'a> { - pub function: Option<&'a str> + pub function: Option<&'a str>, } #[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] @@ -231,7 +231,14 @@ impl<'a> ForgeModules<'a> { if !module_functions.function.is_none() { alternate_functions.push(&module_functions.function.unwrap()); } - if !module_functions.resolver.is_none() && !module_functions.clone().resolver.unwrap().function.is_none() { + if !module_functions.resolver.is_none() + && !module_functions + .clone() + .resolver + .unwrap() + .function + .is_none() + { alternate_functions.push(module_functions.resolver.unwrap().function.unwrap()) } } From ecb20bdc210dfbb167112fafa9b446fdb87ee180 Mon Sep 17 00:00:00 2001 From: James Gersbach Date: Wed, 7 Jun 2023 12:25:55 -0700 Subject: [PATCH 056/517] resolving suggestions on PR --- .DS_Store | Bin 6148 -> 0 bytes .gitignore | 3 +-- crates/forge_loader/src/manifest.rs | 30 +++++++--------------------- 3 files changed, 8 insertions(+), 25 deletions(-) delete mode 100644 .DS_Store diff --git a/.DS_Store b/.DS_Store deleted file mode 100644 index 22adfb13bb81fd31264fccb66060aa508ff155a3..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6148 zcmeHKO>YuG7=DK$T_7qkHR;7<6R)&VY>lQDFuOL8)2Db5Xgj?pwQ!!|jHJJ9VV zyg|>XmE=lAzr_?SE^&|1Rwu<0n#Ngq=+Hu2O*#f%reK_r!|c_+?NO9$oe90-b@NIo z#2s*k_^ndy9}IUo9_lyghdT`Xaa5`NWSNcZwcMt)X%(&a&L=)~qFyu^*L$P4;;F-V z;9jJA?rYzhc1v3g9!5Ps^hT24_dQg3_r?$Vd|Ky|us@buM-^CkE8i_`&t`iM_se$m zpgJ$xvq#lR+1|hZU_Q@Vx9{FNJn5bXgOGp0ykQKdm^85};wE%ip~Yus6b3vLZ@~pK z&RX}h9@Kfr~FdJBb;f}oFMnGi?uHe4FYY>@!g UG%ge(2WCD5qzon)1+G+qKRScs!2kdN diff --git a/.gitignore b/.gitignore index 831f1db6..c41cc9e3 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1 @@ -/target -/test-apps +/target \ No newline at end of file diff --git a/crates/forge_loader/src/manifest.rs b/crates/forge_loader/src/manifest.rs index a28031be..cd415c53 100644 --- a/crates/forge_loader/src/manifest.rs +++ b/crates/forge_loader/src/manifest.rs @@ -95,7 +95,7 @@ pub struct Consumer<'a> { #[derive(Debug, Clone, PartialEq, Eq, Deserialize)] pub struct Resolver<'a> { pub function: &'a str, - method: &'a str, + method: Option<&'a str>, } #[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] @@ -120,17 +120,12 @@ pub struct AppInfo<'a> { pub id: &'a str, } -#[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] -pub struct ModuleResolve<'a> { - pub function: Option<&'a str>, -} - #[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] pub struct Module<'a> { #[serde(default, borrow)] pub function: Option<&'a str>, - #[serde(default, borrow)] - pub resolver: Option>, + #[serde(default)] + pub resolver: Option>, #[serde(flatten)] extra: FxHashMap, } @@ -226,21 +221,10 @@ impl<'a> ForgeModules<'a> { .collect(); let mut alternate_functions: Vec<&str> = Vec::new(); - for module in self.extra { - for module_functions in module.1 { - if !module_functions.function.is_none() { - alternate_functions.push(&module_functions.function.unwrap()); - } - if !module_functions.resolver.is_none() - && !module_functions - .clone() - .resolver - .unwrap() - .function - .is_none() - { - alternate_functions.push(module_functions.resolver.unwrap().function.unwrap()) - } + for module in self.extra.into_values().flatten() { + alternate_functions.extend(module.function); + if let Some(resolver) = module.resolver { + alternate_functions.push(resolver.function); } } From 33cfd76aca4bbd6a4113806af14797e60ff6fbc6 Mon Sep 17 00:00:00 2001 From: James Gersbach Date: Tue, 13 Jun 2023 09:09:59 -0700 Subject: [PATCH 057/517] lowering object literals as operands --- crates/forge_analyzer/src/definitions.rs | 38 ++++++++++++++++++- .../src/utils.js | 3 ++ 2 files changed, 39 insertions(+), 2 deletions(-) diff --git a/crates/forge_analyzer/src/definitions.rs b/crates/forge_analyzer/src/definitions.rs index 241c98b3..4c21e54d 100644 --- a/crates/forge_analyzer/src/definitions.rs +++ b/crates/forge_analyzer/src/definitions.rs @@ -1102,8 +1102,42 @@ impl<'cx> FunctionAnalyzer<'cx> { Operand::UNDEF } Expr::Object(ObjectLit { span, props }) => { - // TODO: lower object literals - Operand::UNDEF + let def_id = self + .res + .add_anonymous("__UNKNOWN", AnonType::Obj, self.module); + if let DefKind::GlobalObj(class_id) = self.res.defs.defs[def_id] { + let mut class = self.res.defs.classes[class_id].clone(); + props + .iter() + .for_each(|prop_or_spread| match prop_or_spread { + PropOrSpread::Prop(prop) => match &**prop { + Prop::Shorthand(id) => { + let id = id.to_id(); + let sym = id.0.clone(); + let new_def = + self.res.get_or_insert_sym(id.clone(), self.module); + self.res + .def_mut(def_id) + .expect_class() + .pub_members + .push((sym, new_def)); + } + Prop::KeyValue(KeyValueProp { key, value }) => { + if let sym @ Some(_) = key.as_symbol() { + let defid = value.as_ident().map(|id| { + self.res.get_or_insert_sym(id.to_id(), self.module) + }); + let cls = self.res.def_mut(def_id).expect_class(); + cls.pub_members.extend(sym.zip(defid)); + } + } + _ => {} + }, + PropOrSpread::Spread(spread) => {} + }) + } + let var_id = self.body.add_var(VarKind::LocalDef((def_id))); + Operand::Var(Variable::new(var_id)) } Expr::Fn(_) => Operand::UNDEF, Expr::Unary(UnaryExpr { op, arg, .. }) => { diff --git a/test-apps/jira-damn-vulnerable-forge-app/src/utils.js b/test-apps/jira-damn-vulnerable-forge-app/src/utils.js index be76d410..df861f7b 100755 --- a/test-apps/jira-damn-vulnerable-forge-app/src/utils.js +++ b/test-apps/jira-damn-vulnerable-forge-app/src/utils.js @@ -2,6 +2,9 @@ import api, { route } from '@forge/api'; export async function fetchIssueSummary(issueIdOrKey) { /* const api = await import('@forge/api'); */ + + let a = {test_value: "test"} + const resp = await api .asApp() .requestJira(route`/rest/api/3/issue/${issueIdOrKey}?fields=summary`, { From d304cda6b4131ec184db05d1919fbf914f098111 Mon Sep 17 00:00:00 2001 From: James Gersbach Date: Tue, 13 Jun 2023 10:38:52 -0700 Subject: [PATCH 058/517] adding lvalues for literals within objects --- crates/forge_analyzer/src/definitions.rs | 5 ++++- crates/forge_analyzer/src/ir.rs | 8 ++++---- test-apps/jira-damn-vulnerable-forge-app/src/utils.js | 2 ++ 3 files changed, 10 insertions(+), 5 deletions(-) diff --git a/crates/forge_analyzer/src/definitions.rs b/crates/forge_analyzer/src/definitions.rs index 4c21e54d..d196afd1 100644 --- a/crates/forge_analyzer/src/definitions.rs +++ b/crates/forge_analyzer/src/definitions.rs @@ -688,7 +688,7 @@ struct FunctionAnalyzer<'cx> { module: ModId, current_def: DefId, assigning_to: Option, - body: Body, + pub body: Body, block: BasicBlockId, operand_stack: Vec, in_lhs: bool, @@ -1127,6 +1127,9 @@ impl<'cx> FunctionAnalyzer<'cx> { let defid = value.as_ident().map(|id| { self.res.get_or_insert_sym(id.to_id(), self.module) }); + let var = self.body.get_or_insert_global(def_id); + let lowered_value = self.lower_expr(&value); + self.body.coerce_to_lval(self.block, lowered_value.clone()); let cls = self.res.def_mut(def_id).expect_class(); cls.pub_members.extend(sym.zip(defid)); } diff --git a/crates/forge_analyzer/src/ir.rs b/crates/forge_analyzer/src/ir.rs index 517ef324..12179ae8 100644 --- a/crates/forge_analyzer/src/ir.rs +++ b/crates/forge_analyzer/src/ir.rs @@ -95,7 +95,7 @@ pub enum Rvalue { #[derive(Clone, Debug, Default)] pub struct BasicBlock { - insts: Vec, + pub insts: Vec, term: Terminator, } @@ -106,7 +106,7 @@ pub struct Location { } #[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub(crate) enum VarKind { +pub enum VarKind { LocalDef(DefId), GlobalRef(DefId), Temp { parent: Option }, @@ -123,8 +123,8 @@ pub(crate) const RETURN_VAR: Variable = Variable { #[derive(Clone, Debug)] pub struct Body { owner: Option, - blocks: TiVec, - vars: TiVec, + pub blocks: TiVec, + pub vars: TiVec, ident_to_local: FxHashMap, def_id_to_vars: FxHashMap, predecessors: OnceCell>>, diff --git a/test-apps/jira-damn-vulnerable-forge-app/src/utils.js b/test-apps/jira-damn-vulnerable-forge-app/src/utils.js index df861f7b..7a70749b 100755 --- a/test-apps/jira-damn-vulnerable-forge-app/src/utils.js +++ b/test-apps/jira-damn-vulnerable-forge-app/src/utils.js @@ -5,6 +5,8 @@ export async function fetchIssueSummary(issueIdOrKey) { let a = {test_value: "test"} + let b = "test_value_string"; + const resp = await api .asApp() .requestJira(route`/rest/api/3/issue/${issueIdOrKey}?fields=summary`, { From 38c1732a769e16fa5d9962a5eac6e121fff8f47c Mon Sep 17 00:00:00 2001 From: James Gersbach Date: Tue, 13 Jun 2023 10:39:26 -0700 Subject: [PATCH 059/517] removing edits to test-apps --- test-apps/jira-damn-vulnerable-forge-app/src/utils.js | 5 ----- 1 file changed, 5 deletions(-) diff --git a/test-apps/jira-damn-vulnerable-forge-app/src/utils.js b/test-apps/jira-damn-vulnerable-forge-app/src/utils.js index 7a70749b..be76d410 100755 --- a/test-apps/jira-damn-vulnerable-forge-app/src/utils.js +++ b/test-apps/jira-damn-vulnerable-forge-app/src/utils.js @@ -2,11 +2,6 @@ import api, { route } from '@forge/api'; export async function fetchIssueSummary(issueIdOrKey) { /* const api = await import('@forge/api'); */ - - let a = {test_value: "test"} - - let b = "test_value_string"; - const resp = await api .asApp() .requestJira(route`/rest/api/3/issue/${issueIdOrKey}?fields=summary`, { From 62a3c00a1e8b5e06995bf52871643672b9fc2447 Mon Sep 17 00:00:00 2001 From: James Gersbach Date: Tue, 13 Jun 2023 10:40:57 -0700 Subject: [PATCH 060/517] removing changes to files --- crates/forge_analyzer/src/definitions.rs | 2 +- crates/forge_analyzer/src/ir.rs | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/crates/forge_analyzer/src/definitions.rs b/crates/forge_analyzer/src/definitions.rs index d196afd1..8b483adc 100644 --- a/crates/forge_analyzer/src/definitions.rs +++ b/crates/forge_analyzer/src/definitions.rs @@ -688,7 +688,7 @@ struct FunctionAnalyzer<'cx> { module: ModId, current_def: DefId, assigning_to: Option, - pub body: Body, + body: Body, block: BasicBlockId, operand_stack: Vec, in_lhs: bool, diff --git a/crates/forge_analyzer/src/ir.rs b/crates/forge_analyzer/src/ir.rs index 12179ae8..a1b7ecce 100644 --- a/crates/forge_analyzer/src/ir.rs +++ b/crates/forge_analyzer/src/ir.rs @@ -95,7 +95,7 @@ pub enum Rvalue { #[derive(Clone, Debug, Default)] pub struct BasicBlock { - pub insts: Vec, + insts: Vec, term: Terminator, } @@ -123,8 +123,8 @@ pub(crate) const RETURN_VAR: Variable = Variable { #[derive(Clone, Debug)] pub struct Body { owner: Option, - pub blocks: TiVec, - pub vars: TiVec, + blocks: TiVec, + vars: TiVec, ident_to_local: FxHashMap, def_id_to_vars: FxHashMap, predecessors: OnceCell>>, From eb9f31d3c7892564fc06beca09a2e86e16b1fec3 Mon Sep 17 00:00:00 2001 From: James Gersbach Date: Tue, 13 Jun 2023 10:41:36 -0700 Subject: [PATCH 061/517] removing changes to files --- crates/forge_analyzer/src/ir.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/forge_analyzer/src/ir.rs b/crates/forge_analyzer/src/ir.rs index a1b7ecce..517ef324 100644 --- a/crates/forge_analyzer/src/ir.rs +++ b/crates/forge_analyzer/src/ir.rs @@ -106,7 +106,7 @@ pub struct Location { } #[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub enum VarKind { +pub(crate) enum VarKind { LocalDef(DefId), GlobalRef(DefId), Temp { parent: Option }, From 27402c37d205747ff0de04f57e90664585cc9043 Mon Sep 17 00:00:00 2001 From: gersbach <93059258+gersbach@users.noreply.github.com> Date: Wed, 14 Jun 2023 09:27:15 -0700 Subject: [PATCH 062/517] Update crates/forge_analyzer/src/definitions.rs Co-authored-by: Joshua Wong --- crates/forge_analyzer/src/definitions.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/forge_analyzer/src/definitions.rs b/crates/forge_analyzer/src/definitions.rs index 8b483adc..631baa6b 100644 --- a/crates/forge_analyzer/src/definitions.rs +++ b/crates/forge_analyzer/src/definitions.rs @@ -1115,7 +1115,7 @@ impl<'cx> FunctionAnalyzer<'cx> { let id = id.to_id(); let sym = id.0.clone(); let new_def = - self.res.get_or_insert_sym(id.clone(), self.module); + self.res.get_or_insert_sym(id, self.module); self.res .def_mut(def_id) .expect_class() From 5326c9f696e7a3bfbab56d4da79bbc4db237e56a Mon Sep 17 00:00:00 2001 From: gersbach <93059258+gersbach@users.noreply.github.com> Date: Wed, 14 Jun 2023 09:27:21 -0700 Subject: [PATCH 063/517] Update crates/forge_analyzer/src/definitions.rs Co-authored-by: Joshua Wong --- crates/forge_analyzer/src/definitions.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/crates/forge_analyzer/src/definitions.rs b/crates/forge_analyzer/src/definitions.rs index 631baa6b..f25385f6 100644 --- a/crates/forge_analyzer/src/definitions.rs +++ b/crates/forge_analyzer/src/definitions.rs @@ -1106,7 +1106,6 @@ impl<'cx> FunctionAnalyzer<'cx> { .res .add_anonymous("__UNKNOWN", AnonType::Obj, self.module); if let DefKind::GlobalObj(class_id) = self.res.defs.defs[def_id] { - let mut class = self.res.defs.classes[class_id].clone(); props .iter() .for_each(|prop_or_spread| match prop_or_spread { From a674333aed59b14a50e170a5abe52f0fb9d2ce03 Mon Sep 17 00:00:00 2001 From: gersbach <93059258+gersbach@users.noreply.github.com> Date: Wed, 14 Jun 2023 09:27:27 -0700 Subject: [PATCH 064/517] Update crates/forge_analyzer/src/definitions.rs Co-authored-by: Joshua Wong --- crates/forge_analyzer/src/definitions.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/forge_analyzer/src/definitions.rs b/crates/forge_analyzer/src/definitions.rs index f25385f6..7d965d79 100644 --- a/crates/forge_analyzer/src/definitions.rs +++ b/crates/forge_analyzer/src/definitions.rs @@ -1128,7 +1128,7 @@ impl<'cx> FunctionAnalyzer<'cx> { }); let var = self.body.get_or_insert_global(def_id); let lowered_value = self.lower_expr(&value); - self.body.coerce_to_lval(self.block, lowered_value.clone()); + self.body.coerce_to_lval(self.block, lowered_value); let cls = self.res.def_mut(def_id).expect_class(); cls.pub_members.extend(sym.zip(defid)); } From 701dd2b75b1400a3940248e5278ec8b4ec53667c Mon Sep 17 00:00:00 2001 From: James Gersbach Date: Wed, 14 Jun 2023 12:09:15 -0700 Subject: [PATCH 065/517] adding ir for insering symbols --- crates/forge_analyzer/src/definitions.rs | 43 ++++++++++++++++-------- crates/forge_analyzer/src/interp.rs | 2 ++ crates/forge_analyzer/src/ir.rs | 4 +++ 3 files changed, 35 insertions(+), 14 deletions(-) diff --git a/crates/forge_analyzer/src/definitions.rs b/crates/forge_analyzer/src/definitions.rs index 7d965d79..f1ecb338 100644 --- a/crates/forge_analyzer/src/definitions.rs +++ b/crates/forge_analyzer/src/definitions.rs @@ -37,7 +37,7 @@ use typed_index_collections::{TiSlice, TiVec}; use crate::{ ctx::ModId, ir::{ - BasicBlockId, Body, Inst, Intrinsic, Literal, Operand, Projection, Rvalue, Template, + Base, BasicBlockId, Body, Inst, Intrinsic, Literal, Operand, Projection, Rvalue, Template, Terminator, VarKind, Variable, RETURN_VAR, }, }; @@ -1105,6 +1105,7 @@ impl<'cx> FunctionAnalyzer<'cx> { let def_id = self .res .add_anonymous("__UNKNOWN", AnonType::Obj, self.module); + let class_var_id = self.body.add_var(VarKind::LocalDef((def_id))); if let DefKind::GlobalObj(class_id) = self.res.defs.defs[def_id] { props .iter() @@ -1113,33 +1114,47 @@ impl<'cx> FunctionAnalyzer<'cx> { Prop::Shorthand(id) => { let id = id.to_id(); let sym = id.0.clone(); - let new_def = - self.res.get_or_insert_sym(id, self.module); + let new_def = self.res.get_or_insert_sym(id, self.module); self.res .def_mut(def_id) .expect_class() .pub_members .push((sym, new_def)); } - Prop::KeyValue(KeyValueProp { key, value }) => { - if let sym @ Some(_) = key.as_symbol() { - let defid = value.as_ident().map(|id| { - self.res.get_or_insert_sym(id.to_id(), self.module) - }); - let var = self.body.get_or_insert_global(def_id); + Prop::KeyValue(KeyValueProp { key, value }) => match key { + PropName::Ident(ident) => { let lowered_value = self.lower_expr(&value); - self.body.coerce_to_lval(self.block, lowered_value); + let lowered_var = + self.body.coerce_to_lval(self.block, lowered_value); + let def_id_key = + self.res.get_or_insert_sym(ident.to_id(), self.module); + + match lowered_var.base { + Base::Var(var) => { + let rval = Rvalue::ClassAssignment( + class_var_id, + def_id_key, + var, + ); + self.body.push_inst( + self.block, + Inst::Assign(Variable::new(class_var_id), rval), + ); + } + _ => {} + } let cls = self.res.def_mut(def_id).expect_class(); - cls.pub_members.extend(sym.zip(defid)); + cls.pub_members + .push((key.as_symbol().unwrap(), def_id_key)); } - } + _ => {} + }, _ => {} }, PropOrSpread::Spread(spread) => {} }) } - let var_id = self.body.add_var(VarKind::LocalDef((def_id))); - Operand::Var(Variable::new(var_id)) + Operand::Var(Variable::new(class_var_id)) } Expr::Fn(_) => Operand::UNDEF, Expr::Unary(UnaryExpr { op, arg, .. }) => { diff --git a/crates/forge_analyzer/src/interp.rs b/crates/forge_analyzer/src/interp.rs index 57988685..cf635f17 100644 --- a/crates/forge_analyzer/src/interp.rs +++ b/crates/forge_analyzer/src/interp.rs @@ -89,6 +89,7 @@ pub trait Dataflow<'cx>: Sized { Rvalue::Call(callee, _) => { self.transfer_call(interp, def, loc, block, callee, initial_state) } + Rvalue::ClassAssignment(_, _, _) => initial_state, Rvalue::Unary(_, _) => initial_state, Rvalue::Bin(_, _, _) => initial_state, Rvalue::Read(_) => initial_state, @@ -252,6 +253,7 @@ pub trait Checker<'cx>: Sized { Rvalue::Unary(_, _) | Rvalue::Bin(_, _, _) | Rvalue::Read(_) + | Rvalue::ClassAssignment(_, _, _) | Rvalue::Phi(_) | Rvalue::Template(_) => ControlFlow::Continue(curr_state.clone()), } diff --git a/crates/forge_analyzer/src/ir.rs b/crates/forge_analyzer/src/ir.rs index 517ef324..a79fe4f0 100644 --- a/crates/forge_analyzer/src/ir.rs +++ b/crates/forge_analyzer/src/ir.rs @@ -88,6 +88,7 @@ pub enum Rvalue { Bin(BinOp, Operand, Operand), Read(Operand), Call(Operand, SmallVec<[Operand; 4]>), + ClassAssignment(VarId, DefId, VarId), Intrinsic(Intrinsic, SmallVec<[Operand; 4]>), Phi(Vec<(VarId, BasicBlockId)>), Template(Template), @@ -753,6 +754,9 @@ impl fmt::Display for Rvalue { } write!(f, ")") } + Rvalue::ClassAssignment(class_id, def_id, prop_id) => { + write!(f, "{class_id}.insert({def_id:?}, {prop_id})") + } Rvalue::Intrinsic(ref intrinsic, ref args) => { write!(f, "{intrinsic}(")?; for arg in args { From 73f9e9af914fa5fc81eabb392772aa6f297d592c Mon Sep 17 00:00:00 2001 From: James Gersbach Date: Wed, 14 Jun 2023 22:37:41 -0700 Subject: [PATCH 066/517] adding projections instead of assignment --- crates/forge_analyzer/src/definitions.rs | 77 ++++++++++++------- crates/forge_analyzer/src/interp.rs | 2 - crates/forge_analyzer/src/ir.rs | 14 ++-- .../src/utils.js | 4 +- 4 files changed, 59 insertions(+), 38 deletions(-) diff --git a/crates/forge_analyzer/src/definitions.rs b/crates/forge_analyzer/src/definitions.rs index f1ecb338..21b2aa2e 100644 --- a/crates/forge_analyzer/src/definitions.rs +++ b/crates/forge_analyzer/src/definitions.rs @@ -1106,6 +1106,7 @@ impl<'cx> FunctionAnalyzer<'cx> { .res .add_anonymous("__UNKNOWN", AnonType::Obj, self.module); let class_var_id = self.body.add_var(VarKind::LocalDef((def_id))); + let mut var = Variable::new(class_var_id); if let DefKind::GlobalObj(class_id) = self.res.defs.defs[def_id] { props .iter() @@ -1114,41 +1115,65 @@ impl<'cx> FunctionAnalyzer<'cx> { Prop::Shorthand(id) => { let id = id.to_id(); let sym = id.0.clone(); - let new_def = self.res.get_or_insert_sym(id, self.module); + let new_def = + self.res.get_or_insert_sym(id.clone(), self.module); + let var_def_id = self.res.sym_to_id(id.clone(), self.module); + let var_id = + self.body.get_or_insert_global(var_def_id.unwrap()); + var.projections + .push(Projection::Computed(Base::Var((var_id)))); self.res .def_mut(def_id) .expect_class() .pub_members .push((sym, new_def)); } - Prop::KeyValue(KeyValueProp { key, value }) => match key { - PropName::Ident(ident) => { - let lowered_value = self.lower_expr(&value); - let lowered_var = - self.body.coerce_to_lval(self.block, lowered_value); - let def_id_key = - self.res.get_or_insert_sym(ident.to_id(), self.module); - - match lowered_var.base { - Base::Var(var) => { - let rval = Rvalue::ClassAssignment( - class_var_id, - def_id_key, - var, - ); - self.body.push_inst( - self.block, - Inst::Assign(Variable::new(class_var_id), rval), - ); + Prop::KeyValue(KeyValueProp { key, value }) => { + let lowered_value = self.lower_expr(&value); + let lowered_var = + self.body.coerce_to_lval(self.block, lowered_value.clone()); + let rval = Rvalue::Read(lowered_value); + match lowered_var.base { + Base::Var(var_id) => { + var.projections + .push(Projection::Computed(Base::Var((var_id)))); + self.body.push_inst( + self.block, + Inst::Assign(Variable::new(var_id), rval), + ); + + match key { + PropName::Str(str) => { + let def_id_prop = self.res.add_anonymous( + str.value.clone(), + AnonType::Unknown, + self.module, + ); + let cls = + self.res.def_mut(def_id).expect_class(); + cls.pub_members.push(( + key.as_symbol().unwrap(), + def_id_prop, + )); + } + PropName::Ident(ident) => { + let def_id_prop = self.res.get_or_insert_sym( + ident.to_id(), + self.module, + ); + let cls = + self.res.def_mut(def_id).expect_class(); + cls.pub_members.push(( + key.as_symbol().unwrap(), + def_id_prop, + )); + } + _ => {} } - _ => {} } - let cls = self.res.def_mut(def_id).expect_class(); - cls.pub_members - .push((key.as_symbol().unwrap(), def_id_key)); + _ => {} } - _ => {} - }, + } _ => {} }, PropOrSpread::Spread(spread) => {} diff --git a/crates/forge_analyzer/src/interp.rs b/crates/forge_analyzer/src/interp.rs index cf635f17..57988685 100644 --- a/crates/forge_analyzer/src/interp.rs +++ b/crates/forge_analyzer/src/interp.rs @@ -89,7 +89,6 @@ pub trait Dataflow<'cx>: Sized { Rvalue::Call(callee, _) => { self.transfer_call(interp, def, loc, block, callee, initial_state) } - Rvalue::ClassAssignment(_, _, _) => initial_state, Rvalue::Unary(_, _) => initial_state, Rvalue::Bin(_, _, _) => initial_state, Rvalue::Read(_) => initial_state, @@ -253,7 +252,6 @@ pub trait Checker<'cx>: Sized { Rvalue::Unary(_, _) | Rvalue::Bin(_, _, _) | Rvalue::Read(_) - | Rvalue::ClassAssignment(_, _, _) | Rvalue::Phi(_) | Rvalue::Template(_) => ControlFlow::Continue(curr_state.clone()), } diff --git a/crates/forge_analyzer/src/ir.rs b/crates/forge_analyzer/src/ir.rs index a79fe4f0..b142adf2 100644 --- a/crates/forge_analyzer/src/ir.rs +++ b/crates/forge_analyzer/src/ir.rs @@ -88,7 +88,6 @@ pub enum Rvalue { Bin(BinOp, Operand, Operand), Read(Operand), Call(Operand, SmallVec<[Operand; 4]>), - ClassAssignment(VarId, DefId, VarId), Intrinsic(Intrinsic, SmallVec<[Operand; 4]>), Phi(Vec<(VarId, BasicBlockId)>), Template(Template), @@ -96,7 +95,7 @@ pub enum Rvalue { #[derive(Clone, Debug, Default)] pub struct BasicBlock { - insts: Vec, + pub insts: Vec, term: Terminator, } @@ -107,7 +106,7 @@ pub struct Location { } #[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub(crate) enum VarKind { +pub enum VarKind { LocalDef(DefId), GlobalRef(DefId), Temp { parent: Option }, @@ -124,10 +123,10 @@ pub(crate) const RETURN_VAR: Variable = Variable { #[derive(Clone, Debug)] pub struct Body { owner: Option, - blocks: TiVec, - vars: TiVec, + pub blocks: TiVec, + pub vars: TiVec, ident_to_local: FxHashMap, - def_id_to_vars: FxHashMap, + pub def_id_to_vars: FxHashMap, predecessors: OnceCell>>, } @@ -754,9 +753,6 @@ impl fmt::Display for Rvalue { } write!(f, ")") } - Rvalue::ClassAssignment(class_id, def_id, prop_id) => { - write!(f, "{class_id}.insert({def_id:?}, {prop_id})") - } Rvalue::Intrinsic(ref intrinsic, ref args) => { write!(f, "{intrinsic}(")?; for arg in args { diff --git a/test-apps/jira-damn-vulnerable-forge-app/src/utils.js b/test-apps/jira-damn-vulnerable-forge-app/src/utils.js index be76d410..2fa3b13a 100755 --- a/test-apps/jira-damn-vulnerable-forge-app/src/utils.js +++ b/test-apps/jira-damn-vulnerable-forge-app/src/utils.js @@ -14,12 +14,14 @@ export async function fetchIssueSummary(issueIdOrKey) { return data['fields']['summary']; } +let method = 'POST'; + export async function writeComment(issueIdOrKey, comment) { /* const api = require('@forge/api'); */ const resp = await api .asApp() .requestJira(route`/rest/api/3/issue/${issueIdOrKey}/comment`, { - method: 'POST', + method, headers: { Accept: 'application/json', 'Content-Type': 'application/json', From a78b76d2bd76543fc6a1e584c8bbe3fdb0a9da4e Mon Sep 17 00:00:00 2001 From: James Gersbach Date: Thu, 15 Jun 2023 08:12:05 -0700 Subject: [PATCH 067/517] remove unneeded cloning --- crates/forge_analyzer/src/definitions.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/crates/forge_analyzer/src/definitions.rs b/crates/forge_analyzer/src/definitions.rs index 21b2aa2e..7fd9710b 100644 --- a/crates/forge_analyzer/src/definitions.rs +++ b/crates/forge_analyzer/src/definitions.rs @@ -1114,7 +1114,6 @@ impl<'cx> FunctionAnalyzer<'cx> { PropOrSpread::Prop(prop) => match &**prop { Prop::Shorthand(id) => { let id = id.to_id(); - let sym = id.0.clone(); let new_def = self.res.get_or_insert_sym(id.clone(), self.module); let var_def_id = self.res.sym_to_id(id.clone(), self.module); @@ -1126,7 +1125,7 @@ impl<'cx> FunctionAnalyzer<'cx> { .def_mut(def_id) .expect_class() .pub_members - .push((sym, new_def)); + .push((id.0, new_def)); } Prop::KeyValue(KeyValueProp { key, value }) => { let lowered_value = self.lower_expr(&value); From ceb1e091cd64e907962368916d622be4feff680d Mon Sep 17 00:00:00 2001 From: James Gersbach Date: Thu, 15 Jun 2023 08:23:58 -0700 Subject: [PATCH 068/517] reverting changes that are unneeded --- crates/forge_analyzer/src/ir.rs | 8 ++++---- test-apps/jira-damn-vulnerable-forge-app/src/utils.js | 4 +--- 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/crates/forge_analyzer/src/ir.rs b/crates/forge_analyzer/src/ir.rs index b142adf2..a1b7ecce 100644 --- a/crates/forge_analyzer/src/ir.rs +++ b/crates/forge_analyzer/src/ir.rs @@ -95,7 +95,7 @@ pub enum Rvalue { #[derive(Clone, Debug, Default)] pub struct BasicBlock { - pub insts: Vec, + insts: Vec, term: Terminator, } @@ -123,10 +123,10 @@ pub(crate) const RETURN_VAR: Variable = Variable { #[derive(Clone, Debug)] pub struct Body { owner: Option, - pub blocks: TiVec, - pub vars: TiVec, + blocks: TiVec, + vars: TiVec, ident_to_local: FxHashMap, - pub def_id_to_vars: FxHashMap, + def_id_to_vars: FxHashMap, predecessors: OnceCell>>, } diff --git a/test-apps/jira-damn-vulnerable-forge-app/src/utils.js b/test-apps/jira-damn-vulnerable-forge-app/src/utils.js index 2fa3b13a..be76d410 100755 --- a/test-apps/jira-damn-vulnerable-forge-app/src/utils.js +++ b/test-apps/jira-damn-vulnerable-forge-app/src/utils.js @@ -14,14 +14,12 @@ export async function fetchIssueSummary(issueIdOrKey) { return data['fields']['summary']; } -let method = 'POST'; - export async function writeComment(issueIdOrKey, comment) { /* const api = require('@forge/api'); */ const resp = await api .asApp() .requestJira(route`/rest/api/3/issue/${issueIdOrKey}/comment`, { - method, + method: 'POST', headers: { Accept: 'application/json', 'Content-Type': 'application/json', From 610ab62a56a70d29a27155e27699f987625036a2 Mon Sep 17 00:00:00 2001 From: James Gersbach Date: Thu, 15 Jun 2023 10:43:23 -0700 Subject: [PATCH 069/517] fixes to projections --- crates/forge_analyzer/src/definitions.rs | 29 ++++++++++++------------ 1 file changed, 14 insertions(+), 15 deletions(-) diff --git a/crates/forge_analyzer/src/definitions.rs b/crates/forge_analyzer/src/definitions.rs index 7fd9710b..c95ad7d8 100644 --- a/crates/forge_analyzer/src/definitions.rs +++ b/crates/forge_analyzer/src/definitions.rs @@ -1106,19 +1106,16 @@ impl<'cx> FunctionAnalyzer<'cx> { .res .add_anonymous("__UNKNOWN", AnonType::Obj, self.module); let class_var_id = self.body.add_var(VarKind::LocalDef((def_id))); - let mut var = Variable::new(class_var_id); if let DefKind::GlobalObj(class_id) = self.res.defs.defs[def_id] { - props - .iter() - .for_each(|prop_or_spread| match prop_or_spread { + props.iter().for_each(|prop_or_spread| { + let mut var = Variable::new(class_var_id); + match prop_or_spread { PropOrSpread::Prop(prop) => match &**prop { Prop::Shorthand(id) => { let id = id.to_id(); let new_def = self.res.get_or_insert_sym(id.clone(), self.module); - let var_def_id = self.res.sym_to_id(id.clone(), self.module); - let var_id = - self.body.get_or_insert_global(var_def_id.unwrap()); + let var_id = self.body.get_or_insert_global(new_def); var.projections .push(Projection::Computed(Base::Var((var_id)))); self.res @@ -1134,13 +1131,6 @@ impl<'cx> FunctionAnalyzer<'cx> { let rval = Rvalue::Read(lowered_value); match lowered_var.base { Base::Var(var_id) => { - var.projections - .push(Projection::Computed(Base::Var((var_id)))); - self.body.push_inst( - self.block, - Inst::Assign(Variable::new(var_id), rval), - ); - match key { PropName::Str(str) => { let def_id_prop = self.res.add_anonymous( @@ -1154,6 +1144,8 @@ impl<'cx> FunctionAnalyzer<'cx> { key.as_symbol().unwrap(), def_id_prop, )); + var.projections + .push(Projection::Known(str.value.clone())); } PropName::Ident(ident) => { let def_id_prop = self.res.get_or_insert_sym( @@ -1166,9 +1158,15 @@ impl<'cx> FunctionAnalyzer<'cx> { key.as_symbol().unwrap(), def_id_prop, )); + var.projections + .push(Projection::Known(ident.sym.clone())); } _ => {} } + self.body.push_inst( + self.block, + Inst::Assign(Variable::new(var_id), rval), + ); } _ => {} } @@ -1176,7 +1174,8 @@ impl<'cx> FunctionAnalyzer<'cx> { _ => {} }, PropOrSpread::Spread(spread) => {} - }) + } + }) } Operand::Var(Variable::new(class_var_id)) } From 6e98a8a5eafb753d292d3eac7eb690e490555a7d Mon Sep 17 00:00:00 2001 From: gersbach <93059258+gersbach@users.noreply.github.com> Date: Thu, 15 Jun 2023 12:24:27 -0700 Subject: [PATCH 070/517] Update crates/forge_analyzer/src/definitions.rs Co-authored-by: Joshua Wong --- crates/forge_analyzer/src/definitions.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/forge_analyzer/src/definitions.rs b/crates/forge_analyzer/src/definitions.rs index c95ad7d8..d4fe5cc0 100644 --- a/crates/forge_analyzer/src/definitions.rs +++ b/crates/forge_analyzer/src/definitions.rs @@ -1165,7 +1165,7 @@ impl<'cx> FunctionAnalyzer<'cx> { } self.body.push_inst( self.block, - Inst::Assign(Variable::new(var_id), rval), + Inst::Assign(var, rval), ); } _ => {} From 2c55d501951fa2b9f92767edfc8a4ea6a11998a5 Mon Sep 17 00:00:00 2001 From: James Gersbach Date: Thu, 15 Jun 2023 12:37:46 -0700 Subject: [PATCH 071/517] fixes --- crates/forge_analyzer/src/definitions.rs | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/crates/forge_analyzer/src/definitions.rs b/crates/forge_analyzer/src/definitions.rs index c95ad7d8..ec8a55d1 100644 --- a/crates/forge_analyzer/src/definitions.rs +++ b/crates/forge_analyzer/src/definitions.rs @@ -1111,18 +1111,21 @@ impl<'cx> FunctionAnalyzer<'cx> { let mut var = Variable::new(class_var_id); match prop_or_spread { PropOrSpread::Prop(prop) => match &**prop { - Prop::Shorthand(id) => { - let id = id.to_id(); + Prop::Shorthand(ident) => { + let id = ident.to_id(); let new_def = self.res.get_or_insert_sym(id.clone(), self.module); let var_id = self.body.get_or_insert_global(new_def); - var.projections - .push(Projection::Computed(Base::Var((var_id)))); + var.projections.push(Projection::Known(ident.sym.clone())); self.res .def_mut(def_id) .expect_class() .pub_members .push((id.0, new_def)); + self.body.push_inst( + self.block, + Inst::Assign(var, Rvalue::Read(Operand::with_var(var_id))), + ); } Prop::KeyValue(KeyValueProp { key, value }) => { let lowered_value = self.lower_expr(&value); @@ -1163,10 +1166,8 @@ impl<'cx> FunctionAnalyzer<'cx> { } _ => {} } - self.body.push_inst( - self.block, - Inst::Assign(Variable::new(var_id), rval), - ); + self.body + .push_inst(self.block, Inst::Assign(var, rval)); } _ => {} } From 6d2848232e58934a61e260144e13d430a0adcfcf Mon Sep 17 00:00:00 2001 From: James Gersbach Date: Fri, 16 Jun 2023 10:30:42 -0700 Subject: [PATCH 072/517] basic checker --- crates/forge_analyzer/src/checkers.rs | 226 +++++++++++++++++- crates/forge_analyzer/src/ctx.rs | 5 +- crates/forge_analyzer/src/definitions.rs | 39 ++- crates/forge_analyzer/src/engine.rs | 1 - crates/forge_analyzer/src/interp.rs | 21 +- crates/forge_analyzer/src/ir.rs | 17 +- .../forge_analyzer/src/permission_checker.rs | 0 crates/fsrt/src/main.rs | 16 +- .../src/utils.js | 17 +- 9 files changed, 320 insertions(+), 22 deletions(-) create mode 100644 crates/forge_analyzer/src/permission_checker.rs diff --git a/crates/forge_analyzer/src/checkers.rs b/crates/forge_analyzer/src/checkers.rs index 89d64312..46609582 100644 --- a/crates/forge_analyzer/src/checkers.rs +++ b/crates/forge_analyzer/src/checkers.rs @@ -1,15 +1,16 @@ use core::fmt; +use forge_utils::FxHashMap; use itertools::Itertools; use std::{cmp::max, iter, mem, ops::ControlFlow, path::PathBuf}; use tracing::{debug, info, warn}; use crate::{ - definitions::{DefId, Environment}, + definitions::{DefId, Environment, Value, Const}, interp::{ Checker, Dataflow, EntryKind, EntryPoint, Frame, Interp, JoinSemiLattice, WithCallStack, }, - ir::{BasicBlock, BasicBlockId, Intrinsic, Location}, + ir::{BasicBlock, BasicBlockId, Intrinsic, Location, VarId, Inst, Base, Rvalue}, reporter::{IntoVuln, Reporter, Severity, Vulnerability}, worklist::WorkList, }; @@ -446,3 +447,224 @@ impl IntoVuln for AuthNVuln { impl WithCallStack for AuthNVuln { fn add_call_stack(&mut self, _stack: Vec) {} } + +pub struct PermisisionDataflow { + needs_call: Vec, + variables: FxHashMap +} + +impl WithCallStack for PermissionVuln { + fn add_call_stack(&mut self, _stack: Vec) {} +} + +impl<'cx> Dataflow<'cx> for PermisisionDataflow { + type State = PermissionTest; + + fn with_interp>( + _interp: &Interp<'cx, C>, + ) -> Self { + Self { needs_call: vec![], variables: FxHashMap::default() } + } + + fn transfer_intrinsic>( + &mut self, + _interp: &Interp<'cx, C>, + _def: DefId, + _loc: Location, + _block: &'cx BasicBlock, + intrinsic: &'cx Intrinsic, + initial_state: Self::State, + ) -> Self::State { + match *intrinsic { + Intrinsic::Authorize => { + debug!("authorize intrinsic found"); + PermissionTest::Yes + } + Intrinsic::Fetch => initial_state, + Intrinsic::ApiCall => initial_state, + Intrinsic::SafeCall => initial_state, + Intrinsic::EnvRead => initial_state, + Intrinsic::StorageRead => initial_state, + } + } + + fn transfer_call>( + &mut self, + interp: &Interp<'cx, C>, + def: DefId, + loc: Location, + _block: &'cx BasicBlock, + callee: &'cx crate::ir::Operand, + initial_state: Self::State, + ) -> Self::State { + for inst in &_block.insts { + match inst { + Inst::Assign(variable, rvalue) => match variable.base { + Base::Var(varid) => { + match rvalue { + Rvalue::Read(operand) => { + + // case not phi + if self.variables.contains_key(&varid) { + // currently assuming prev value is not phi + let prev_vars = &self.variables[&varid]; + match prev_vars { + Value::Const(prev_var_const) => { + let var_vec = vec![prev_var_const.clone(), Const::Literal(operand.clone())]; + + self.variables.insert(varid, Value::Phi(Vec::from(var_vec))); + }, + _ => {} + } + } else { + self.variables.insert(varid, Value::Const(Const::Literal(operand.clone()))); + + } + } + _ => {} + } + } + _ => {} + } + _ => {} + } + } + + let Some((callee_def, _body)) = self.resolve_call(interp, callee) else { + return initial_state; + }; + match interp.func_state(callee_def) { + Some(state) => { + if state == PermissionTest::Yes { + debug!("Found call to authorize at {def:?} {loc:?}"); + } + initial_state.join(&state) + } + None => { + let callee_name = interp.env().def_name(callee_def); + let caller_name = interp.env().def_name(def); + debug!("Found call to {callee_name} at {def:?} {caller_name}"); + self.needs_call.push(callee_def); + initial_state + } + } + } + + fn join_term>( + &mut self, + interp: &Interp<'cx, C>, + def: DefId, + block: &'cx BasicBlock, + state: Self::State, + worklist: &mut WorkList, + ) { + self.super_join_term(interp, def, block, state, worklist); + for def in self.needs_call.drain(..) { + worklist.push_front_blocks(interp.env(), def); + } + } +} + + +pub struct PermissionChecker { + vulns: Vec, +} + +impl PermissionChecker { + pub fn new() -> Self { + Self { vulns: vec![] } + } + + pub fn into_vulns(self) -> impl IntoIterator { + self.vulns.into_iter() + } +} + +impl Default for PermissionChecker { + fn default() -> Self { + Self::new() + } +} + +impl fmt::Display for PermissionVuln { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "Authentication vulnerability") + } +} + +#[derive(Debug, PartialEq, Eq, Clone, Copy, PartialOrd, Ord)] +pub enum PermissionTest { + Yes +} + +impl JoinSemiLattice for PermissionTest { + const BOTTOM: Self = Self::Yes; + + #[inline] + fn join_changed(&mut self, other: &Self) -> bool { + let old = mem::replace(self, max(*other, *self)); + old == *self + } + + #[inline] + fn join(&self, other: &Self) -> Self { + max(*other, *self) + } +} + +impl<'cx> Checker<'cx> for PermissionChecker { + type State = PermissionTest; + type Dataflow = PermisisionDataflow; + type Vuln = PermissionVuln; + + fn visit_intrinsic( + &mut self, + interp: &Interp<'cx, Self>, + intrinsic: &'cx Intrinsic, + state: &Self::State, + ) -> ControlFlow<(), Self::State> { + match *intrinsic { + Intrinsic::Authorize => ControlFlow::Continue(*state), + Intrinsic::Fetch | Intrinsic::EnvRead | Intrinsic::StorageRead => { + debug!("authenticated"); + ControlFlow::Continue(PermissionTest::Yes) + } + Intrinsic::ApiCall if *state == PermissionTest::Yes => { + let vuln = PermissionVuln::new(); + ControlFlow::Break(()) + } + Intrinsic::ApiCall => ControlFlow::Continue(*state), + Intrinsic::SafeCall => ControlFlow::Continue(*state), + } + } +} + +#[derive(Debug)] +pub struct PermissionVuln { + // unused_permissions: HashSet, +} + +impl PermissionVuln { + pub fn new( /*unused_permissions: HashSet */) -> PermissionVuln { + PermissionVuln { /*unused_permissions*/ } + } +} + +impl IntoVuln for PermissionVuln { + fn into_vuln(self, reporter: &Reporter) -> Vulnerability { + Vulnerability { + check_name: format!("Least-Privilege"), + description: format!( + "Unused permissions listed in manifest file:.", + // self.unused_permissions.into_iter().join(", ") + ), + // unused_permissions: Some(self.unused_permissions), + recommendation: "Remove permissions in manifest file that are not needed.", + proof: format!("Unused permissions found in manifest.yml"), + severity: Severity::Low, + app_key: reporter.app_key().to_string(), + app_name: reporter.app_name().to_string(), + date: reporter.current_date(), + } + } +} \ No newline at end of file diff --git a/crates/forge_analyzer/src/ctx.rs b/crates/forge_analyzer/src/ctx.rs index aba5080f..1c7c3ab8 100644 --- a/crates/forge_analyzer/src/ctx.rs +++ b/crates/forge_analyzer/src/ctx.rs @@ -191,8 +191,8 @@ impl AppCtx { }) }); debug!(?input, "transfer in"); - let next = funcs.blocks[block].stmts.iter_enumerated().fold( - (None, false), + let next = funcs.blocks[block].stmts.iter_enumerated().fold( + (None, false), |(call, curr), (_stmt_id, val)| match val { IrStmt::Call(ref call_id) => { if let Some(meta) = self.func(call_id) { @@ -235,6 +235,7 @@ impl AppCtx { #[inline] pub(crate) fn func_mut(&mut self, func: &ModItem) -> Option<&mut FunctionMeta> { + self.modctx .get_mut(func.mod_id)? .functions diff --git a/crates/forge_analyzer/src/definitions.rs b/crates/forge_analyzer/src/definitions.rs index 7fd9710b..2a9b81cd 100644 --- a/crates/forge_analyzer/src/definitions.rs +++ b/crates/forge_analyzer/src/definitions.rs @@ -54,6 +54,20 @@ create_newtype! { pub struct DefId(u32); } +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum Const { + Literal(Operand), + Object(Operand) +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum Value { + Uninit, + Unknown, + Const(Const), + Phi(Vec) +} + #[derive(Debug, Clone, Copy, PartialEq, Eq)] enum ObjKind { Class(ObjId), @@ -79,9 +93,9 @@ struct SymbolExport { } #[derive(Debug, Clone, Default)] -struct ResolverTable { +pub struct ResolverTable { defs: TiVec, - names: TiVec, + pub names: TiVec, symbol_to_id: FxHashMap, parent: FxHashMap, owning_module: TiVec, @@ -445,7 +459,8 @@ pub struct Environment { exports: TiVec>, defs: Definitions, default_exports: FxHashMap, - resolver: ResolverTable, + pub resolver: ResolverTable, + } struct ImportCollector<'cx> { @@ -688,7 +703,7 @@ struct FunctionAnalyzer<'cx> { module: ModId, current_def: DefId, assigning_to: Option, - body: Body, + pub body: Body, block: BasicBlockId, operand_stack: Vec, in_lhs: bool, @@ -1649,8 +1664,24 @@ impl Visit for FunctionCollector<'_> { in_lhs: false, }; if let Some(BlockStmt { stmts, .. }) = &n.body { + + println!("here test 5567"); + analyzer.lower_stmts(stmts); + + for block in &analyzer.body.blocks { + println!("----------------------",); + for inst in &block.insts { + println!("inst-- {}", inst); + } + println!("block_insts {:#?}", &block.insts); + println!("var_ids {:#?}", analyzer.body.vars); + println!("block_term {:#?}", &block.term) + } + let body = analyzer.body; + + *self.res.def_mut(owner).expect_body() = body; } } diff --git a/crates/forge_analyzer/src/engine.rs b/crates/forge_analyzer/src/engine.rs index 43204fba..65655ef5 100644 --- a/crates/forge_analyzer/src/engine.rs +++ b/crates/forge_analyzer/src/engine.rs @@ -133,7 +133,6 @@ impl<'ctx> Machine<'ctx> { let result = self.app.func_res(&orig_func); info!(?result, "analysis complete"); let fname: &str = &orig_func.ident.0; - println!("Result of analyzing {fname}:"); match result { AuthZVal::Unauthorized => { println!("FAIL: Unauthorized call detected from handler: {fname}") diff --git a/crates/forge_analyzer/src/interp.rs b/crates/forge_analyzer/src/interp.rs index 57988685..3487ac5e 100644 --- a/crates/forge_analyzer/src/interp.rs +++ b/crates/forge_analyzer/src/interp.rs @@ -14,9 +14,9 @@ use swc_core::ecma::atoms::JsWord; use tracing::{debug, info, instrument, warn}; use crate::{ - definitions::{DefId, Environment}, + definitions::{DefId, Environment, Value, Const}, ir::{ - BasicBlock, BasicBlockId, Body, Inst, Intrinsic, Location, Operand, Rvalue, Successors, + BasicBlock, BasicBlockId, Body, Inst, Intrinsic, Location, Operand, Rvalue, Successors, Base, STARTING_BLOCK, }, worklist::WorkList, @@ -210,6 +210,17 @@ pub trait Checker<'cx>: Sized { let Some((callee, body)) = interp.body().resolve_call(interp.env(), callee) else { return ControlFlow::Continue(curr_state.clone()); }; + // println!("------ callstack ------"); + // println!("visiting callee {:#?}", callee); + // println!("interp callstack {:#?}", interp.callstack); + // println!("interp names {:#?}", interp.env.resolver.names[callee]); + // for i in interp.callstack.borrow().iter() { + // println!("calling function {:#?}", i.calling_function); + // println!("calling function {:#?}", interp.env.resolver.names[i.calling_function]); + + // } + + // println!("varids {:#?}", interp.env); let func_state = interp.func_state(callee).unwrap_or(Self::State::BOTTOM); if func_state < *curr_state || !interp.checker_visit(callee) { return ControlFlow::Continue(curr_state.clone()); @@ -310,14 +321,15 @@ pub(crate) struct EntryPoint { pub(crate) kind: EntryKind, } +#[derive(Debug)] pub struct Interp<'cx, C: Checker<'cx>> { - env: &'cx Environment, + pub env: &'cx Environment, // We can probably get rid of these RefCells by refactoring the Interp and Checker into // two fields in another struct. call_graph: CallGraph, entry: EntryPoint, func_state: RefCell>, - curr_body: Cell>, + pub curr_body: Cell>, states: RefCell>, dataflow_visited: FxHashSet, checker_visited: RefCell>, @@ -326,6 +338,7 @@ pub struct Interp<'cx, C: Checker<'cx>> { _checker: PhantomData, } +#[derive(Debug)] struct CallGraph { called_from: FxHashMap>, // (Caller, Callee) -> Location diff --git a/crates/forge_analyzer/src/ir.rs b/crates/forge_analyzer/src/ir.rs index a1b7ecce..9c015cd7 100644 --- a/crates/forge_analyzer/src/ir.rs +++ b/crates/forge_analyzer/src/ir.rs @@ -33,6 +33,7 @@ use crate::ctx::ModId; use crate::definitions::DefId; use crate::definitions::DefKind; use crate::definitions::Environment; +use crate::definitions::Value; pub const STARTING_BLOCK: BasicBlockId = BasicBlockId(0); @@ -41,13 +42,13 @@ create_newtype! { } #[derive(Clone, Debug)] -pub(crate) struct BranchTargets { +pub struct BranchTargets { compare: SmallVec<[Operand; 1]>, branch: SmallVec<[BasicBlockId; 2]>, } #[derive(Clone, Debug, Default)] -pub(crate) enum Terminator { +pub enum Terminator { #[default] Ret, Goto(BasicBlockId), @@ -95,8 +96,8 @@ pub enum Rvalue { #[derive(Clone, Debug, Default)] pub struct BasicBlock { - insts: Vec, - term: Terminator, + pub insts: Vec, + pub term: Terminator, } #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] @@ -123,10 +124,11 @@ pub(crate) const RETURN_VAR: Variable = Variable { #[derive(Clone, Debug)] pub struct Body { owner: Option, - blocks: TiVec, - vars: TiVec, + pub blocks: TiVec, + pub vars: TiVec, ident_to_local: FxHashMap, - def_id_to_vars: FxHashMap, + pub var_id_to_value: FxHashMap, + pub def_id_to_vars: FxHashMap, predecessors: OnceCell>>, } @@ -271,6 +273,7 @@ impl Body { blocks: vec![BasicBlock::default()].into(), ident_to_local: Default::default(), def_id_to_vars: Default::default(), + var_id_to_value: Default::default(), predecessors: Default::default(), } } diff --git a/crates/forge_analyzer/src/permission_checker.rs b/crates/forge_analyzer/src/permission_checker.rs new file mode 100644 index 00000000..e69de29b diff --git a/crates/fsrt/src/main.rs b/crates/fsrt/src/main.rs index d1df4f5e..676329db 100644 --- a/crates/fsrt/src/main.rs +++ b/crates/fsrt/src/main.rs @@ -25,7 +25,7 @@ use tracing_subscriber::{prelude::*, EnvFilter}; use tracing_tree::HierarchicalLayer; use forge_analyzer::{ - checkers::{AuthZChecker, AuthenticateChecker}, + checkers::{AuthZChecker, AuthenticateChecker, PermissionChecker}, ctx::{AppCtx, ModId}, definitions::{run_resolver, DefId, Environment}, interp::Interp, @@ -185,15 +185,19 @@ fn scan_directory(dir: PathBuf, function: Option<&str>, opts: Opts) -> Result { + let mut checker = AuthZChecker::new(); debug!("checking {func} at {path:?}"); if let Err(err) = interp.run_checker(def, &mut checker, path.clone(), func.clone()) @@ -201,6 +205,15 @@ fn scan_directory(dir: PathBuf, function: Option<&str>, opts: Opts) -> Result { let mut checker = AuthenticateChecker::new(); @@ -214,6 +227,7 @@ fn scan_directory(dir: PathBuf, function: Option<&str>, opts: Opts) -> Result 5) { + url = route`/rest/api/3/issue/${issueIdOrKey}?fields=summary`; + } + + let hi_string1 = "hi_string_value" + + test_function1(hi_string1); + /* const api = await import('@forge/api'); */ const resp = await api .asApp() - .requestJira(route`/rest/api/3/issue/${issueIdOrKey}?fields=summary`, { + .requestJira(url, { headers: { Accept: 'application/json', }, @@ -14,6 +25,10 @@ export async function fetchIssueSummary(issueIdOrKey) { return data['fields']['summary']; } +function test_function1(value) { + +} + export async function writeComment(issueIdOrKey, comment) { /* const api = require('@forge/api'); */ const resp = await api From 103612ff1339fc095a73100655465c0b2d3335c0 Mon Sep 17 00:00:00 2001 From: James Gersbach Date: Fri, 16 Jun 2023 11:55:06 -0700 Subject: [PATCH 073/517] moving inst checking to the transfer block --- crates/forge_analyzer/src/checkers.rs | 60 +++++++++++++------ crates/forge_analyzer/src/definitions.rs | 21 +++---- .../src/utils.js | 6 ++ 3 files changed, 56 insertions(+), 31 deletions(-) diff --git a/crates/forge_analyzer/src/checkers.rs b/crates/forge_analyzer/src/checkers.rs index 46609582..bdf5d1e8 100644 --- a/crates/forge_analyzer/src/checkers.rs +++ b/crates/forge_analyzer/src/checkers.rs @@ -497,7 +497,40 @@ impl<'cx> Dataflow<'cx> for PermisisionDataflow { callee: &'cx crate::ir::Operand, initial_state: Self::State, ) -> Self::State { - for inst in &_block.insts { + let Some((callee_def, _body)) = self.resolve_call(interp, callee) else { + return initial_state; + }; + + match interp.func_state(callee_def) { + Some(state) => { + if state == PermissionTest::Yes { + debug!("Found call to authorize at {def:?} {loc:?}"); + } + initial_state.join(&state) + } + None => { + let callee_name = interp.env().def_name(callee_def); + let caller_name = interp.env().def_name(def); + debug!("Found call to {callee_name} at {def:?} {caller_name}"); + self.needs_call.push(callee_def); + initial_state + } + } + + + } + + fn transfer_block>( + &mut self, + interp: &Interp<'cx, C>, + def: DefId, + bb: BasicBlockId, + block: &'cx BasicBlock, + initial_state: Self::State, + ) -> Self::State { + let mut state = initial_state; + + for inst in &block.insts { match inst { Inst::Assign(variable, rvalue) => match variable.base { Base::Var(varid) => { @@ -505,7 +538,7 @@ impl<'cx> Dataflow<'cx> for PermisisionDataflow { Rvalue::Read(operand) => { // case not phi - if self.variables.contains_key(&varid) { + if self.variables.contains_key(&varid) && self.variables.get(&varid).unwrap() != &Value::Const(Const::Literal(operand.clone())) { // currently assuming prev value is not phi let prev_vars = &self.variables[&varid]; match prev_vars { @@ -530,24 +563,13 @@ impl<'cx> Dataflow<'cx> for PermisisionDataflow { } } - let Some((callee_def, _body)) = self.resolve_call(interp, callee) else { - return initial_state; - }; - match interp.func_state(callee_def) { - Some(state) => { - if state == PermissionTest::Yes { - debug!("Found call to authorize at {def:?} {loc:?}"); - } - initial_state.join(&state) - } - None => { - let callee_name = interp.env().def_name(callee_def); - let caller_name = interp.env().def_name(def); - debug!("Found call to {callee_name} at {def:?} {caller_name}"); - self.needs_call.push(callee_def); - initial_state - } + // println!("self.variables {:#?}", self.variables); + + for (stmt, inst) in block.iter().enumerate() { + let loc = Location::new(bb, stmt as u32); + state = self.transfer_inst(interp, def, loc, block, inst, state); } + state } fn join_term>( diff --git a/crates/forge_analyzer/src/definitions.rs b/crates/forge_analyzer/src/definitions.rs index 2a9b81cd..8cfc416e 100644 --- a/crates/forge_analyzer/src/definitions.rs +++ b/crates/forge_analyzer/src/definitions.rs @@ -1664,20 +1664,17 @@ impl Visit for FunctionCollector<'_> { in_lhs: false, }; if let Some(BlockStmt { stmts, .. }) = &n.body { - - println!("here test 5567"); - analyzer.lower_stmts(stmts); - for block in &analyzer.body.blocks { - println!("----------------------",); - for inst in &block.insts { - println!("inst-- {}", inst); - } - println!("block_insts {:#?}", &block.insts); - println!("var_ids {:#?}", analyzer.body.vars); - println!("block_term {:#?}", &block.term) - } + // for block in &analyzer.body.blocks { + // println!("----------------------",); + // for inst in &block.insts { + // println!("inst-- {}", inst); + // } + // println!("block_insts {:#?}", &block.insts); + // println!("var_ids {:#?}", analyzer.body.vars); + // println!("block_term {:#?}", &block.term) + // } let body = analyzer.body; diff --git a/test-apps/jira-damn-vulnerable-forge-app/src/utils.js b/test-apps/jira-damn-vulnerable-forge-app/src/utils.js index 3b19744c..f6e4fbe2 100755 --- a/test-apps/jira-damn-vulnerable-forge-app/src/utils.js +++ b/test-apps/jira-damn-vulnerable-forge-app/src/utils.js @@ -12,6 +12,12 @@ export async function fetchIssueSummary(issueIdOrKey) { test_function1(hi_string1); + let never_phi = "never_phi1"; + + if (true) { + never_phi = "not_never_phi"; + } + /* const api = await import('@forge/api'); */ const resp = await api .asApp() From 017f4fdc5b00a88b3dc6e1e0df404a47e07e8948 Mon Sep 17 00:00:00 2001 From: James Gersbach Date: Fri, 16 Jun 2023 13:28:59 -0700 Subject: [PATCH 074/517] removing bad variable names --- test-apps/jira-damn-vulnerable-forge-app/src/utils.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test-apps/jira-damn-vulnerable-forge-app/src/utils.js b/test-apps/jira-damn-vulnerable-forge-app/src/utils.js index f6e4fbe2..ac04b3ee 100755 --- a/test-apps/jira-damn-vulnerable-forge-app/src/utils.js +++ b/test-apps/jira-damn-vulnerable-forge-app/src/utils.js @@ -8,9 +8,9 @@ export async function fetchIssueSummary(issueIdOrKey) { url = route`/rest/api/3/issue/${issueIdOrKey}?fields=summary`; } - let hi_string1 = "hi_string_value" + let test_string1 = "test_string1_value" - test_function1(hi_string1); + test_function1(test_string1); let never_phi = "never_phi1"; From 6c10c59eea9934084172545ecf0ea7fdf9f65be3 Mon Sep 17 00:00:00 2001 From: James Gersbach Date: Thu, 22 Jun 2023 22:24:10 -0700 Subject: [PATCH 075/517] wip --- crates/forge_analyzer/src/checkers.rs | 436 ++++++++++++++---- crates/forge_analyzer/src/ctx.rs | 5 +- crates/forge_analyzer/src/definitions.rs | 48 +- crates/forge_analyzer/src/interp.rs | 70 +-- crates/forge_analyzer/src/ir.rs | 15 +- crates/forge_analyzer/src/worklist.rs | 26 +- crates/forge_loader/src/forgepermissions.rs | 69 +++ crates/forge_loader/src/lib.rs | 1 + crates/fsrt/src/main.rs | 8 +- test-apps/basic/src/index.tsx | 7 + .../src/utils.js | 23 +- 11 files changed, 534 insertions(+), 174 deletions(-) create mode 100644 crates/forge_loader/src/forgepermissions.rs diff --git a/crates/forge_analyzer/src/checkers.rs b/crates/forge_analyzer/src/checkers.rs index bdf5d1e8..848190b6 100644 --- a/crates/forge_analyzer/src/checkers.rs +++ b/crates/forge_analyzer/src/checkers.rs @@ -1,16 +1,19 @@ use core::fmt; use forge_utils::FxHashMap; use itertools::Itertools; +use smallvec::SmallVec; use std::{cmp::max, iter, mem, ops::ControlFlow, path::PathBuf}; use tracing::{debug, info, warn}; use crate::{ - definitions::{DefId, Environment, Value, Const}, + definitions::{Const, DefId, DefKind, Environment, Value}, interp::{ Checker, Dataflow, EntryKind, EntryPoint, Frame, Interp, JoinSemiLattice, WithCallStack, }, - ir::{BasicBlock, BasicBlockId, Intrinsic, Location, VarId, Inst, Base, Rvalue}, + ir::{ + Base, BasicBlock, BasicBlockId, Inst, Intrinsic, Location, Operand, Rvalue, VarId, VarKind, + }, reporter::{IntoVuln, Reporter, Severity, Vulnerability}, worklist::WorkList, }; @@ -57,6 +60,7 @@ impl<'cx> Dataflow<'cx> for AuthorizeDataflow { _block: &'cx BasicBlock, intrinsic: &'cx Intrinsic, initial_state: Self::State, + operands: SmallVec<[crate::ir::Operand; 4]>, ) -> Self::State { match *intrinsic { Intrinsic::Authorize => { @@ -79,6 +83,7 @@ impl<'cx> Dataflow<'cx> for AuthorizeDataflow { _block: &'cx BasicBlock, callee: &'cx crate::ir::Operand, initial_state: Self::State, + operands: SmallVec<[crate::ir::Operand; 4]>, ) -> Self::State { let Some((callee_def, _body)) = self.resolve_call(interp, callee) else { return initial_state; @@ -110,7 +115,7 @@ impl<'cx> Dataflow<'cx> for AuthorizeDataflow { ) { self.super_join_term(interp, def, block, state, worklist); for def in self.needs_call.drain(..) { - worklist.push_front_blocks(interp.env(), def); + worklist.push_front_blocks(interp.env(), def, vec![]); } } } @@ -213,6 +218,7 @@ impl<'cx> Checker<'cx> for AuthZChecker { interp: &Interp<'cx, Self>, intrinsic: &'cx Intrinsic, state: &Self::State, + operands: Option>, ) -> ControlFlow<(), Self::State> { match *intrinsic { Intrinsic::Authorize => { @@ -224,7 +230,7 @@ impl<'cx> Checker<'cx> for AuthZChecker { let vuln = AuthZVuln::new(interp.callstack(), interp.env(), interp.entry()); info!("Found a vuln!"); self.vulns.push(vuln); - ControlFlow::Break(()) + ControlFlow::Continue(*state) } Intrinsic::ApiCall => ControlFlow::Continue(*state), Intrinsic::SafeCall => ControlFlow::Continue(*state), @@ -276,6 +282,7 @@ impl<'cx> Dataflow<'cx> for AuthenticateDataflow { _block: &'cx BasicBlock, intrinsic: &'cx Intrinsic, initial_state: Self::State, + operands: SmallVec<[crate::ir::Operand; 4]>, ) -> Self::State { match *intrinsic { Intrinsic::Authorize => initial_state, @@ -296,10 +303,11 @@ impl<'cx> Dataflow<'cx> for AuthenticateDataflow { _block: &'cx BasicBlock, callee: &'cx crate::ir::Operand, initial_state: Self::State, + operands: SmallVec<[crate::ir::Operand; 4]>, ) -> Self::State { let Some((callee_def, _body)) = self.resolve_call(interp, callee) else { - return initial_state; - }; + return initial_state; + }; match interp.func_state(callee_def) { Some(state) => { if state == Authenticated::Yes { @@ -327,7 +335,7 @@ impl<'cx> Dataflow<'cx> for AuthenticateDataflow { ) { self.super_join_term(interp, def, block, state, worklist); for def in self.needs_call.drain(..) { - worklist.push_front_blocks(interp.env(), def); + worklist.push_front_blocks(interp.env(), def, vec![]); } } } @@ -362,6 +370,7 @@ impl<'cx> Checker<'cx> for AuthenticateChecker { interp: &Interp<'cx, Self>, intrinsic: &'cx Intrinsic, state: &Self::State, + operands: Option>, ) -> ControlFlow<(), Self::State> { match *intrinsic { Intrinsic::Authorize => ControlFlow::Continue(*state), @@ -373,7 +382,7 @@ impl<'cx> Checker<'cx> for AuthenticateChecker { let vuln = AuthNVuln::new(interp.callstack(), interp.env(), interp.entry()); info!("Found a vuln!"); self.vulns.push(vuln); - ControlFlow::Break(()) + ControlFlow::Continue(*state) } Intrinsic::ApiCall => ControlFlow::Continue(*state), Intrinsic::SafeCall => ControlFlow::Continue(*state), @@ -449,21 +458,64 @@ impl WithCallStack for AuthNVuln { } pub struct PermisisionDataflow { - needs_call: Vec, - variables: FxHashMap + needs_call: Vec<(DefId, Vec)>, + variables: FxHashMap, + variables_from_defid: FxHashMap, } impl WithCallStack for PermissionVuln { fn add_call_stack(&mut self, _stack: Vec) {} } +impl PermisisionDataflow { + fn add_variables(&mut self, rvalue: &Rvalue, defid: &DefId) { + match rvalue { + Rvalue::Read(operand) => { + if self.variables_from_defid.contains_key(&defid) + && self.variables_from_defid.get(&defid).unwrap() + != &Value::Const(Const::Literal(operand.clone())) + { + // currently assuming prev value is not phi + let prev_vars = &self.variables_from_defid[&defid]; + match prev_vars { + Value::Const(prev_var_const) => { + match operand { + Operand::Lit(_) => {} + Operand::Var(var) => match var.base { + Base::Var(var_id) => {} + _ => {} + }, + } + let var_vec = + vec![prev_var_const.clone(), Const::Literal(operand.clone())]; + + self.variables_from_defid + .insert(*defid, Value::Phi(Vec::from(var_vec))); + } + _ => {} + } + } else { + let value = Value::Const(Const::Literal(operand.clone())); + self.variables_from_defid.insert(*defid, value.clone()); + } + } + Rvalue::Template(template) => {} + _ => {} + } + } +} + impl<'cx> Dataflow<'cx> for PermisisionDataflow { type State = PermissionTest; fn with_interp>( _interp: &Interp<'cx, C>, ) -> Self { - Self { needs_call: vec![], variables: FxHashMap::default() } + Self { + needs_call: vec![], + variables: FxHashMap::default(), + variables_from_defid: FxHashMap::default(), + } } fn transfer_intrinsic>( @@ -474,12 +526,85 @@ impl<'cx> Dataflow<'cx> for PermisisionDataflow { _block: &'cx BasicBlock, intrinsic: &'cx Intrinsic, initial_state: Self::State, + operands: SmallVec<[crate::ir::Operand; 4]>, ) -> Self::State { match *intrinsic { - Intrinsic::Authorize => { - debug!("authorize intrinsic found"); - PermissionTest::Yes + Intrinsic::ApiCall | Intrinsic::SafeCall | Intrinsic::Authorize => { + let second = operands.get(1); + if let Some(operand) = second { + match operand { + Operand::Lit(lit) => {} + Operand::Var(var) => { + if let Base::Var(varid) = var.base { + match _interp.curr_body.get().unwrap().vars[varid].clone() { + VarKind::GlobalRef(_def_id) => { + let smthng = self.variables_from_defid.get(&_def_id); + if let Some(value) = smthng { + match value { + Value::Const(const_var) => match const_var { + Const::Literal(_lit) => match _lit { + Operand::Var(var) => { + if let Base::Var(var_id__) = var.base { + let varkind___ = _interp + .curr_body + .get() + .unwrap() + .vars[var_id__] + .clone(); + match varkind___ { + VarKind::LocalDef(def__) => { + let value = &self + .variables_from_defid + .get(&def__.clone()); + let thing = _interp + .env() + .defs + .defs + .get(def__); + if let Some(id) = thing { + if let DefKind::GlobalObj(obj_id) = id { + let class = _interp.env().defs.classes.get(obj_id.clone()); + } + } + } + VarKind::GlobalRef(def__) => { + let value = &self + .variables_from_defid + .get(&def__.clone()); + let thing = _interp + .env() + .defs + .defs + .get(def__); + if let Some(id) = thing { + if let DefKind::GlobalObj(obj_id) = id { + let class = _interp.env().defs.classes.get(obj_id.clone()); + } + } + } + _ => {} + } + } + } + _ => {} + }, + _ => {} + }, + _ => {} + } + } + } + _ => {} + } + } + } + } + } } + _ => {} + } + match *intrinsic { + Intrinsic::Authorize => initial_state, Intrinsic::Fetch => initial_state, Intrinsic::ApiCall => initial_state, Intrinsic::SafeCall => initial_state, @@ -496,28 +621,52 @@ impl<'cx> Dataflow<'cx> for PermisisionDataflow { _block: &'cx BasicBlock, callee: &'cx crate::ir::Operand, initial_state: Self::State, - ) -> Self::State { + operands: SmallVec<[crate::ir::Operand; 4]>, + ) -> Self::State { let Some((callee_def, _body)) = self.resolve_call(interp, callee) else { return initial_state; }; - match interp.func_state(callee_def) { - Some(state) => { - if state == PermissionTest::Yes { - debug!("Found call to authorize at {def:?} {loc:?}"); - } - initial_state.join(&state) - } - None => { - let callee_name = interp.env().def_name(callee_def); - let caller_name = interp.env().def_name(def); - debug!("Found call to {callee_name} at {def:?} {caller_name}"); - self.needs_call.push(callee_def); - initial_state + let mut def_ids_operands = vec![]; + + for operand in operands { + match operand { + Operand::Var(variable) => match variable.base { + Base::Var(varid) => { + let varkind = &interp.curr_body.get().unwrap().vars[varid]; + + match varkind { + VarKind::LocalDef(defid) => { + def_ids_operands.push(defid.clone()); + } + VarKind::GlobalRef(defid) => { + if self.variables_from_defid.contains_key(defid) { + def_ids_operands.push(defid.clone()); + } else { + } + } + VarKind::Arg(defid) => { + def_ids_operands.push(defid.clone()); + } + VarKind::Temp { parent } => { + if let Some(defid) = parent { + def_ids_operands.push(defid.clone()); + } + } + _ => {} + } + } + _ => {} + }, + Operand::Lit(lit) => {} } } - + let callee_name = interp.env().def_name(callee_def); + let caller_name = interp.env().def_name(def); + debug!("Found call to {callee_name} at {def:?} {caller_name}"); + self.needs_call.push((callee_def, def_ids_operands.clone())); + initial_state } fn transfer_block>( @@ -527,47 +676,190 @@ impl<'cx> Dataflow<'cx> for PermisisionDataflow { bb: BasicBlockId, block: &'cx BasicBlock, initial_state: Self::State, + arguments: Option>, ) -> Self::State { - let mut state = initial_state; - - for inst in &block.insts { - match inst { - Inst::Assign(variable, rvalue) => match variable.base { - Base::Var(varid) => { - match rvalue { - Rvalue::Read(operand) => { - - // case not phi - if self.variables.contains_key(&varid) && self.variables.get(&varid).unwrap() != &Value::Const(Const::Literal(operand.clone())) { - // currently assuming prev value is not phi - let prev_vars = &self.variables[&varid]; - match prev_vars { - Value::Const(prev_var_const) => { - let var_vec = vec![prev_var_const.clone(), Const::Literal(operand.clone())]; - - self.variables.insert(varid, Value::Phi(Vec::from(var_vec))); - }, - _ => {} - } - } else { - self.variables.insert(varid, Value::Const(Const::Literal(operand.clone()))); - + let mut state: PermissionTest = initial_state; + + if let Some(args) = arguments { + let mut args = args.clone(); + for var in &interp.curr_body.get().unwrap().vars { + match var { + VarKind::Arg(defid_new) => { + let defid_old = args.pop(); + if let Some(def_id_old_unwrapped) = defid_old { + self.variables_from_defid.insert( + defid_new.clone(), + self.variables_from_defid[&def_id_old_unwrapped.clone()].clone(), + ); } } _ => {} - } - } - _ => {} } - _ => {} } } - // println!("self.variables {:#?}", self.variables); - + /* collecting the variables that were used :) */ for (stmt, inst) in block.iter().enumerate() { let loc = Location::new(bb, stmt as u32); state = self.transfer_inst(interp, def, loc, block, inst, state); + + match inst { + Inst::Assign(variable, rvalue) => match variable.base { + Base::Var(varid) => { + let var_kind = &interp.curr_body.get().unwrap().vars[varid]; + match var_kind { + VarKind::LocalDef(defid) => { + match rvalue { + Rvalue::Read(operand) => { + if self.variables_from_defid.contains_key(&defid) + && self.variables_from_defid.get(&defid).unwrap() + != &Value::Const(Const::Literal(operand.clone())) + { + // currently assuming prev value is not phi + let prev_vars = &self.variables_from_defid[&defid]; + match prev_vars { + Value::Const(prev_var_const) => { + let var_vec = vec![ + prev_var_const.clone(), + Const::Literal(operand.clone()), + ]; + + self.variables_from_defid.insert( + *defid, + Value::Phi(Vec::from(var_vec)), + ); + } + _ => {} + } + } else { + match operand { + Operand::Lit(lit) => { + // let value = Value::Const(Const::Literal(operand.clone())); + // self.variables_from_defid.insert(*defid, value.clone()); + } + Operand::Var(var) => match var.base { + Base::Var(var_id) => { + let something = + &interp.curr_body.get().unwrap().vars + [var_id]; + } + _ => {} + }, + } + let value = + Value::Const(Const::Literal(operand.clone())); + self.variables_from_defid.insert(*defid, value.clone()); + } + } + Rvalue::Template(template) => {} + _ => {} + } + } + VarKind::GlobalRef(defid) => { + match rvalue { + Rvalue::Read(operand) => { + if self.variables_from_defid.contains_key(&defid) + && self.variables_from_defid.get(&defid).unwrap() + != &Value::Const(Const::Literal(operand.clone())) + { + // currently assuming prev value is not phi + let prev_vars = &self.variables_from_defid[&defid]; + match prev_vars { + Value::Const(prev_var_const) => { + let var_vec = vec![ + prev_var_const.clone(), + Const::Literal(operand.clone()), + ]; + + self.variables_from_defid.insert( + *defid, + Value::Phi(Vec::from(var_vec)), + ); + } + _ => {} + } + } else { + match operand { + Operand::Lit(lit) => {} + Operand::Var(var) => match var.base { + Base::Var(var_id) => { + let something = + &interp.curr_body.get().unwrap().vars + [var_id]; + } + _ => {} + }, + } + let value = + Value::Const(Const::Literal(operand.clone())); + self.variables_from_defid.insert(*defid, value.clone()); + } + } + Rvalue::Template(template) => {} + _ => {} + } + } + VarKind::Arg(defid) => { + match rvalue { + Rvalue::Read(operand) => { + if self.variables_from_defid.contains_key(&defid) + && self.variables_from_defid.get(&defid).unwrap() + != &Value::Const(Const::Literal(operand.clone())) + { + // currently assuming prev value is not phi + let prev_vars = &self.variables_from_defid[&defid]; + match prev_vars { + Value::Const(prev_var_const) => { + let var_vec = vec![ + prev_var_const.clone(), + Const::Literal(operand.clone()), + ]; + + self.variables_from_defid.insert( + *defid, + Value::Phi(Vec::from(var_vec)), + ); + } + _ => {} + } + } else { + match operand { + Operand::Lit(lit) => {} + Operand::Var(var) => match var.base { + Base::Var(var_id) => { + let something = + &interp.curr_body.get().unwrap().vars + [var_id]; + } + _ => {} + }, + } + let value = + Value::Const(Const::Literal(operand.clone())); + self.variables_from_defid.insert(*defid, value.clone()); + } + } + Rvalue::Template(template) => {} + _ => {} + } + } + VarKind::Temp { parent } => { + match rvalue { + Rvalue::Template(template) => {} + _ => {} + } + if let Some(defid) = parent { + self.add_variables(rvalue, defid) + } + } + _ => {} + } + } + + _ => {} + }, + _ => {} + } } state } @@ -581,13 +873,12 @@ impl<'cx> Dataflow<'cx> for PermisisionDataflow { worklist: &mut WorkList, ) { self.super_join_term(interp, def, block, state, worklist); - for def in self.needs_call.drain(..) { - worklist.push_front_blocks(interp.env(), def); + for (def, arguments) in self.needs_call.drain(..) { + worklist.push_front_blocks(interp.env(), def, arguments); } } } - pub struct PermissionChecker { vulns: Vec, } @@ -616,7 +907,7 @@ impl fmt::Display for PermissionVuln { #[derive(Debug, PartialEq, Eq, Clone, Copy, PartialOrd, Ord)] pub enum PermissionTest { - Yes + Yes, } impl JoinSemiLattice for PermissionTest { @@ -644,20 +935,9 @@ impl<'cx> Checker<'cx> for PermissionChecker { interp: &Interp<'cx, Self>, intrinsic: &'cx Intrinsic, state: &Self::State, + operands: Option>, ) -> ControlFlow<(), Self::State> { - match *intrinsic { - Intrinsic::Authorize => ControlFlow::Continue(*state), - Intrinsic::Fetch | Intrinsic::EnvRead | Intrinsic::StorageRead => { - debug!("authenticated"); - ControlFlow::Continue(PermissionTest::Yes) - } - Intrinsic::ApiCall if *state == PermissionTest::Yes => { - let vuln = PermissionVuln::new(); - ControlFlow::Break(()) - } - Intrinsic::ApiCall => ControlFlow::Continue(*state), - Intrinsic::SafeCall => ControlFlow::Continue(*state), - } + ControlFlow::Continue(*state) } } @@ -667,7 +947,7 @@ pub struct PermissionVuln { } impl PermissionVuln { - pub fn new( /*unused_permissions: HashSet */) -> PermissionVuln { + pub fn new(/*unused_permissions: HashSet */) -> PermissionVuln { PermissionVuln { /*unused_permissions*/ } } } @@ -689,4 +969,4 @@ impl IntoVuln for PermissionVuln { date: reporter.current_date(), } } -} \ No newline at end of file +} diff --git a/crates/forge_analyzer/src/ctx.rs b/crates/forge_analyzer/src/ctx.rs index 1c7c3ab8..aba5080f 100644 --- a/crates/forge_analyzer/src/ctx.rs +++ b/crates/forge_analyzer/src/ctx.rs @@ -191,8 +191,8 @@ impl AppCtx { }) }); debug!(?input, "transfer in"); - let next = funcs.blocks[block].stmts.iter_enumerated().fold( - (None, false), + let next = funcs.blocks[block].stmts.iter_enumerated().fold( + (None, false), |(call, curr), (_stmt_id, val)| match val { IrStmt::Call(ref call_id) => { if let Some(meta) = self.func(call_id) { @@ -235,7 +235,6 @@ impl AppCtx { #[inline] pub(crate) fn func_mut(&mut self, func: &ModItem) -> Option<&mut FunctionMeta> { - self.modctx .get_mut(func.mod_id)? .functions diff --git a/crates/forge_analyzer/src/definitions.rs b/crates/forge_analyzer/src/definitions.rs index 8cfc416e..048565a4 100644 --- a/crates/forge_analyzer/src/definitions.rs +++ b/crates/forge_analyzer/src/definitions.rs @@ -57,7 +57,7 @@ create_newtype! { #[derive(Debug, Clone, PartialEq, Eq)] pub enum Const { Literal(Operand), - Object(Operand) + Object(Operand), } #[derive(Debug, Clone, PartialEq, Eq)] @@ -65,7 +65,7 @@ pub enum Value { Uninit, Unknown, Const(Const), - Phi(Vec) + Phi(Vec), } #[derive(Debug, Clone, Copy, PartialEq, Eq)] @@ -447,20 +447,19 @@ impl Copy for DefRes {} impl Copy for DefRef<'_> {} #[derive(Debug, Clone, Default)] -struct Definitions { - defs: TiVec, +pub struct Definitions { + pub defs: TiVec, funcs: TiVec, - classes: TiVec, + pub classes: TiVec, foreign: TiVec, } #[derive(Debug, Clone, Default)] pub struct Environment { exports: TiVec>, - defs: Definitions, + pub defs: Definitions, default_exports: FxHashMap, pub resolver: ResolverTable, - } struct ImportCollector<'cx> { @@ -1013,7 +1012,7 @@ impl<'cx> FunctionAnalyzer<'cx> { Pat::Invalid(_) => {} Pat::Expr(expr) => { let opnd = self.lower_expr(expr); - self.body.coerce_to_lval(self.block, opnd); + self.body.coerce_to_lval(self.block, opnd, None); } } } @@ -1143,9 +1142,24 @@ impl<'cx> FunctionAnalyzer<'cx> { .push((id.0, new_def)); } Prop::KeyValue(KeyValueProp { key, value }) => { + let span = match key { + PropName::BigInt(bigint) => bigint.span, + PropName::Computed(computed) => computed.span, + PropName::Ident(ident) => ident.span, + PropName::Num(num) => num.span, + PropName::Str(str) => str.span, + }; let lowered_value = self.lower_expr(&value); - let lowered_var = - self.body.coerce_to_lval(self.block, lowered_value.clone()); + let next_key = self.res.get_or_overwrite_sym( + (key.as_symbol().unwrap(), span.ctxt), + self.module, + DefKind::Arg, + ); + let lowered_var = self.body.coerce_to_lval( + self.block, + lowered_value.clone(), + Some(next_key), + ); let rval = Rvalue::Read(lowered_value); match lowered_var.base { Base::Var(var_id) => { @@ -1242,7 +1256,7 @@ impl<'cx> FunctionAnalyzer<'cx> { match left { PatOrExpr::Expr(expr) => { let opnd = self.lower_expr(expr); - let lval = self.body.coerce_to_lval(self.block, opnd); + let lval = self.body.coerce_to_lval(self.block, opnd, None); self.push_curr_inst(Inst::Assign(lval, Rvalue::Read(rhs.clone()))); } PatOrExpr::Pat(pat) => { @@ -1665,20 +1679,8 @@ impl Visit for FunctionCollector<'_> { }; if let Some(BlockStmt { stmts, .. }) = &n.body { analyzer.lower_stmts(stmts); - - // for block in &analyzer.body.blocks { - // println!("----------------------",); - // for inst in &block.insts { - // println!("inst-- {}", inst); - // } - // println!("block_insts {:#?}", &block.insts); - // println!("var_ids {:#?}", analyzer.body.vars); - // println!("block_term {:#?}", &block.term) - // } - let body = analyzer.body; - *self.res.def_mut(owner).expect_body() = body; } } diff --git a/crates/forge_analyzer/src/interp.rs b/crates/forge_analyzer/src/interp.rs index 3487ac5e..940cf112 100644 --- a/crates/forge_analyzer/src/interp.rs +++ b/crates/forge_analyzer/src/interp.rs @@ -10,14 +10,15 @@ use std::{ }; use forge_utils::{FxHashMap, FxHashSet}; +use smallvec::SmallVec; use swc_core::ecma::atoms::JsWord; use tracing::{debug, info, instrument, warn}; use crate::{ - definitions::{DefId, Environment, Value, Const}, + definitions::{Const, DefId, Environment, Value}, ir::{ - BasicBlock, BasicBlockId, Body, Inst, Intrinsic, Location, Operand, Rvalue, Successors, Base, - STARTING_BLOCK, + Base, BasicBlock, BasicBlockId, Body, Inst, Intrinsic, Location, Operand, Rvalue, + Successors, STARTING_BLOCK, }, worklist::WorkList, }; @@ -61,6 +62,7 @@ pub trait Dataflow<'cx>: Sized { block: &'cx BasicBlock, intrinsic: &'cx Intrinsic, initial_state: Self::State, + operands: SmallVec<[crate::ir::Operand; 4]>, ) -> Self::State; fn transfer_call>( @@ -71,6 +73,8 @@ pub trait Dataflow<'cx>: Sized { block: &'cx BasicBlock, callee: &'cx Operand, initial_state: Self::State, + // operands: &SmallVec<[Operand; 4]>, + oprands: SmallVec<[crate::ir::Operand; 4]>, ) -> Self::State; fn transfer_rvalue>( @@ -83,12 +87,24 @@ pub trait Dataflow<'cx>: Sized { initial_state: Self::State, ) -> Self::State { match rvalue { - Rvalue::Intrinsic(intrinsic, _) => { - self.transfer_intrinsic(interp, def, loc, block, intrinsic, initial_state) - } - Rvalue::Call(callee, _) => { - self.transfer_call(interp, def, loc, block, callee, initial_state) - } + Rvalue::Intrinsic(intrinsic, args) => self.transfer_intrinsic( + interp, + def, + loc, + block, + intrinsic, + initial_state, + args.clone(), + ), + Rvalue::Call(callee, operands) => self.transfer_call( + interp, + def, + loc, + block, + callee, + initial_state, + operands.clone(), + ), Rvalue::Unary(_, _) => initial_state, Rvalue::Bin(_, _, _) => initial_state, Rvalue::Read(_) => initial_state, @@ -123,6 +139,7 @@ pub trait Dataflow<'cx>: Sized { bb: BasicBlockId, block: &'cx BasicBlock, initial_state: Self::State, + arguments: Option>, ) -> Self::State { let mut state = initial_state; for (stmt, inst) in block.iter().enumerate() { @@ -163,7 +180,7 @@ pub trait Dataflow<'cx>: Sized { debug!("{name} {def:?} is called from {calls:?}"); for &(def, loc) in calls { if worklist.visited(&def) { - worklist.push_back_force(def, loc.block); + worklist.push_back_force(def, loc.block, vec![]); } } } @@ -171,16 +188,15 @@ pub trait Dataflow<'cx>: Sized { Successors::One(succ) => { let mut succ_state = interp.block_state_mut(def, succ); if succ_state.join_changed(&state) { - worklist.push_back(def, succ); + worklist.push_back(def, succ, vec![]); } } Successors::Two(succ1, succ2) => { if interp.block_state_mut(def, succ1).join_changed(&state) { - worklist.push_back(def, succ1); + worklist.push_back(def, succ1, vec![]); } - if interp.block_state_mut(def, succ2).join_changed(&state) { - worklist.push_back(def, succ2); + worklist.push_back(def, succ2, vec![]); } } } @@ -197,6 +213,7 @@ pub trait Checker<'cx>: Sized { interp: &Interp<'cx, Self>, intrinsic: &'cx Intrinsic, state: &Self::State, + operands: Option>, ) -> ControlFlow<(), Self::State>; fn visit_call( @@ -210,17 +227,7 @@ pub trait Checker<'cx>: Sized { let Some((callee, body)) = interp.body().resolve_call(interp.env(), callee) else { return ControlFlow::Continue(curr_state.clone()); }; - // println!("------ callstack ------"); - // println!("visiting callee {:#?}", callee); - // println!("interp callstack {:#?}", interp.callstack); - // println!("interp names {:#?}", interp.env.resolver.names[callee]); - // for i in interp.callstack.borrow().iter() { - // println!("calling function {:#?}", i.calling_function); - // println!("calling function {:#?}", interp.env.resolver.names[i.calling_function]); - - // } - - // println!("varids {:#?}", interp.env); + let func_state = interp.func_state(callee).unwrap_or(Self::State::BOTTOM); if func_state < *curr_state || !interp.checker_visit(callee) { return ControlFlow::Continue(curr_state.clone()); @@ -258,7 +265,9 @@ pub trait Checker<'cx>: Sized { ) -> ControlFlow<(), Self::State> { debug!("visiting rvalue {rvalue:?} with {curr_state:?}"); match rvalue { - Rvalue::Intrinsic(intrinsic, _) => self.visit_intrinsic(interp, intrinsic, curr_state), + Rvalue::Intrinsic(intrinsic, operands) => { + self.visit_intrinsic(interp, intrinsic, curr_state, Some(operands.clone())) + } Rvalue::Call(callee, args) => self.visit_call(interp, callee, args, id, curr_state), Rvalue::Unary(_, _) | Rvalue::Bin(_, _, _) @@ -503,20 +512,23 @@ impl<'cx, C: Checker<'cx>> Interp<'cx, C> { self.dataflow_visited.insert(func_def); let mut dataflow = C::Dataflow::with_interp(self); let mut worklist = WorkList::new(); - worklist.push_front_blocks(self.env, func_def); + worklist.push_front_blocks(self.env, func_def, vec![]); let old_body = self.curr_body.get(); - while let Some((def, block_id)) = worklist.pop_front() { + while let Some((def, block_id, args)) = worklist.pop_front() { let name = self.env.def_name(def); debug!("Dataflow: {name} - {block_id}"); self.dataflow_visited.insert(def); let func = self.env().def_ref(def).expect_body(); self.curr_body.set(Some(func)); + let block = func.block(block_id); let mut before_state = self.block_state(def, block_id); let block = func.block(block_id); for &pred in func.predecessors(block_id) { + let block_ = func.block(pred); before_state = before_state.join(&self.block_state(def, pred)); } - let state = dataflow.transfer_block(self, def, block_id, block, before_state); + let state = + dataflow.transfer_block(self, def, block_id, block, before_state, Some(args)); dataflow.join_term(self, def, block, state, &mut worklist); } self.curr_body.set(old_body); diff --git a/crates/forge_analyzer/src/ir.rs b/crates/forge_analyzer/src/ir.rs index 9c015cd7..a9ddb020 100644 --- a/crates/forge_analyzer/src/ir.rs +++ b/crates/forge_analyzer/src/ir.rs @@ -127,7 +127,6 @@ pub struct Body { pub blocks: TiVec, pub vars: TiVec, ident_to_local: FxHashMap, - pub var_id_to_value: FxHashMap, pub def_id_to_vars: FxHashMap, predecessors: OnceCell>>, } @@ -273,7 +272,6 @@ impl Body { blocks: vec![BasicBlock::default()].into(), ident_to_local: Default::default(), def_id_to_vars: Default::default(), - var_id_to_value: Default::default(), predecessors: Default::default(), } } @@ -399,6 +397,8 @@ impl Body { env: &'cx Environment, callee: &Operand, ) -> Option<(DefId, &'cx Body)> { + /* callee is the name of the function that is being called */ + match callee { Operand::Var(Variable { base: Base::Var(var), @@ -437,12 +437,19 @@ impl Body { } } - pub(crate) fn coerce_to_lval(&mut self, bb: BasicBlockId, val: Operand) -> Variable { + pub(crate) fn coerce_to_lval( + &mut self, + bb: BasicBlockId, + val: Operand, + parent_key: Option, + ) -> Variable { match val { Operand::Var(var) => var, Operand::Lit(_) => Variable { base: Base::Var({ - let var = self.vars.push_and_get_key(VarKind::Temp { parent: None }); + let var = self + .vars + .push_and_get_key(VarKind::Temp { parent: parent_key }); self.push_inst(bb, Inst::Assign(Variable::new(var), Rvalue::Read(val))); var }), diff --git a/crates/forge_analyzer/src/worklist.rs b/crates/forge_analyzer/src/worklist.rs index 0488548d..37f452c6 100644 --- a/crates/forge_analyzer/src/worklist.rs +++ b/crates/forge_analyzer/src/worklist.rs @@ -10,7 +10,7 @@ use crate::{ #[derive(Debug, Clone)] pub struct WorkList { - worklist: VecDeque<(V, W)>, + worklist: VecDeque<(V, W, Vec)>, visited: FxHashSet, } @@ -27,7 +27,7 @@ where } #[inline] - pub fn pop_front(&mut self) -> Option<(V, W)> { + pub fn pop_front(&mut self) -> (Option<(V, W, Vec)>) { self.worklist.pop_front() } @@ -61,21 +61,26 @@ where V: Eq + Hash + Copy, { #[inline] - pub fn push_back(&mut self, v: V, w: W) { + pub fn push_back(&mut self, v: V, w: W, args: Vec) { if self.visited.insert(v) { - self.worklist.push_back((v, w)); + self.worklist.push_back((v, w, args)); } } #[inline] - pub fn push_back_force(&mut self, v: V, w: W) { - self.worklist.push_back((v, w)); + pub fn push_back_force(&mut self, v: V, w: W, args: Vec) { + self.worklist.push_back((v, w, args)); } } impl WorkList { #[inline] - pub(crate) fn push_front_blocks(&mut self, env: &Environment, def: DefId) -> bool { + pub(crate) fn push_front_blocks( + &mut self, + env: &Environment, + def: DefId, + arguments: Vec, + ) -> bool { if self.visited.insert(def) { debug!("adding function: {}", env.def_name(def)); let body = env.def_ref(def).expect_body(); @@ -83,7 +88,8 @@ impl WorkList { self.worklist.reserve(blocks.len()); for work in blocks { debug!(?work, "push_front_blocks"); - self.worklist.push_front(work); + self.worklist + .push_front((work.0, work.1, arguments.clone())); } return true; } @@ -91,12 +97,12 @@ impl WorkList { } } -impl Extend<(V, W)> for WorkList +impl Extend<(V, W, Vec)> for WorkList where V: Eq + Hash, { #[inline] - fn extend>(&mut self, iter: T) { + fn extend)>>(&mut self, iter: T) { self.worklist.extend(iter); } } diff --git a/crates/forge_loader/src/forgepermissions.rs b/crates/forge_loader/src/forgepermissions.rs new file mode 100644 index 00000000..2414fb32 --- /dev/null +++ b/crates/forge_loader/src/forgepermissions.rs @@ -0,0 +1,69 @@ +use serde::{self, Deserialize, Serialize}; + +#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize, Hash)] +pub enum ForgePermissions { + #[serde(rename = "read:audit-log:confluence")] + WriteAuditLogsConfluence, + #[serde(rename = "write:audit-log:confluence")] + ReadAuditLogsConfluence, + #[serde(rename = "write:confluence-content")] + WriteConfluenceContent, + #[serde(rename = "read:confluence-space.summary")] + ReadConfluenceSpaceSummary, + #[serde(rename = "write:confluence-space")] + WriteConfluenceSpace, + #[serde(rename = "write:confluence-file")] + WriteConfluenceFile, + #[serde(rename = "read:confluence-props")] + ReadConfluenceProps, + #[serde(rename = "write:confluence-props")] + WriteConfluenceProps, + #[serde(rename = "read:confluence-content.all")] + ReadConfluenceContentAll, + #[serde(rename = "read:confluence-content.summary")] + ReadConfluenceContentSummary, + #[serde(rename = "read:inlinetask:confluence")] + ReadInlineTaskConfluence, + #[serde(rename = "search:confluence")] + SearchConfluence, + #[serde(rename = "read:confluence-content.permission")] + ReadConfluenceContentPermission, + #[serde(rename = "read:content-details:confluence")] + ReadContentDetailsConfluence, + #[serde(rename = "read:confluence-user")] + ReadConfluenceUser, + #[serde(rename = "read:user.property:confluence")] + ReadUserPropertyConfluence, + #[serde(rename = "manage:confluence-configuration")] + ManageConfluenceConfiguration, + #[serde(rename = "read:content.metadata:confluence")] + ReadContentMetadataConfluence, + #[serde(rename = "read:confluence-content.permission")] + ReadConfluenceGroups, + #[serde(rename = "write:confluence-groups")] + WriteConfluenceGroups, + #[serde(rename = "write:user.property:confluence")] + WriteUserPropertyConfluence, + #[serde(rename = "read:space.permission:confluence")] + ReadSpacePermissionConfluence, + #[serde(rename = "write:space.permission:confluence")] + WriteSpacePermissionsConfluence, + #[serde(rename = "write:inlinetask:confluence")] + WriteInlineTaskConfluence, + #[serde(rename = "readonly:content.attachment:confluence")] + ReadOnlyContentAttachmentConfluence, + #[serde(rename = "read:jira-user")] + ReadJiraUser, + #[serde(rename = "read:jira-work")] + ReadJiraWork, + #[serde(rename = "write:jira-work")] + WriteJiraWork, + #[serde(rename = "manage:jira-project")] + ManageJiraProject, + #[serde(rename = "manage:jira-configuration")] + ManageJiraConfiguration, + #[serde(rename = "manage:jira-webhook")] + ManageJiraWebhook, + #[serde(other)] + Unknown, +} diff --git a/crates/forge_loader/src/lib.rs b/crates/forge_loader/src/lib.rs index ea67c574..07cf618c 100644 --- a/crates/forge_loader/src/lib.rs +++ b/crates/forge_loader/src/lib.rs @@ -1,4 +1,5 @@ pub mod error; +pub mod forgepermissions; pub mod manifest; pub use self::error::{Error, Result}; diff --git a/crates/fsrt/src/main.rs b/crates/fsrt/src/main.rs index 676329db..3b00fe66 100644 --- a/crates/fsrt/src/main.rs +++ b/crates/fsrt/src/main.rs @@ -193,11 +193,10 @@ fn scan_directory(dir: PathBuf, function: Option<&str>, opts: Opts) -> Result { - let mut checker = AuthZChecker::new(); debug!("checking {func} at {path:?}"); if let Err(err) = interp.run_checker(def, &mut checker, path.clone(), func.clone()) @@ -207,13 +206,12 @@ fn scan_directory(dir: PathBuf, function: Option<&str>, opts: Opts) -> Result { let mut checker = AuthenticateChecker::new(); diff --git a/test-apps/basic/src/index.tsx b/test-apps/basic/src/index.tsx index a098da68..b3f910fd 100644 --- a/test-apps/basic/src/index.tsx +++ b/test-apps/basic/src/index.tsx @@ -3,11 +3,18 @@ import api, { route } from '@forge/api'; const foo = () => { const res = api.asApp().requestConfluence(route`/rest/api/3/test`); + test_function("hi") return res; }; +let test_function = (word) => { + console.log(word); + let test_var = "test_var"; +} + const App = () => { foo(); + test_function("test_word"); return ( Hello world! diff --git a/test-apps/jira-damn-vulnerable-forge-app/src/utils.js b/test-apps/jira-damn-vulnerable-forge-app/src/utils.js index ac04b3ee..f1882ce6 100755 --- a/test-apps/jira-damn-vulnerable-forge-app/src/utils.js +++ b/test-apps/jira-damn-vulnerable-forge-app/src/utils.js @@ -2,26 +2,9 @@ import api, { route } from '@forge/api'; export async function fetchIssueSummary(issueIdOrKey) { - let url = route`/rest/api/3/issue/${issueIdOrKey}?fields=summary`; - - if (10 > 5) { - url = route`/rest/api/3/issue/${issueIdOrKey}?fields=summary`; - } - - let test_string1 = "test_string1_value" - - test_function1(test_string1); - - let never_phi = "never_phi1"; - - if (true) { - never_phi = "not_never_phi"; - } - - /* const api = await import('@forge/api'); */ const resp = await api .asApp() - .requestJira(url, { + .requestJira(`/rest/api/3/issue/${issueIdOrKey}?fields=summary`, { headers: { Accept: 'application/json', }, @@ -31,10 +14,6 @@ export async function fetchIssueSummary(issueIdOrKey) { return data['fields']['summary']; } -function test_function1(value) { - -} - export async function writeComment(issueIdOrKey, comment) { /* const api = require('@forge/api'); */ const resp = await api From f1ecab7bb7e9f274c3ce5e8e257d374da2436b0c Mon Sep 17 00:00:00 2001 From: James Gersbach Date: Fri, 23 Jun 2023 09:19:57 -0700 Subject: [PATCH 076/517] definition analysis --- crates/forge_analyzer/src/checkers.rs | 111 +++++++++--------- crates/forge_analyzer/src/interp.rs | 14 +++ .../src/utils.js | 3 +- 3 files changed, 71 insertions(+), 57 deletions(-) diff --git a/crates/forge_analyzer/src/checkers.rs b/crates/forge_analyzer/src/checkers.rs index 848190b6..084496d8 100644 --- a/crates/forge_analyzer/src/checkers.rs +++ b/crates/forge_analyzer/src/checkers.rs @@ -503,6 +503,8 @@ impl PermisisionDataflow { _ => {} } } + + fn add_something() {} } impl<'cx> Dataflow<'cx> for PermisisionDataflow { @@ -533,66 +535,18 @@ impl<'cx> Dataflow<'cx> for PermisisionDataflow { let second = operands.get(1); if let Some(operand) = second { match operand { - Operand::Lit(lit) => {} + Operand::Lit(lit) => { + println!("lit {:?}", lit); + } Operand::Var(var) => { if let Base::Var(varid) = var.base { match _interp.curr_body.get().unwrap().vars[varid].clone() { VarKind::GlobalRef(_def_id) => { - let smthng = self.variables_from_defid.get(&_def_id); - if let Some(value) = smthng { - match value { - Value::Const(const_var) => match const_var { - Const::Literal(_lit) => match _lit { - Operand::Var(var) => { - if let Base::Var(var_id__) = var.base { - let varkind___ = _interp - .curr_body - .get() - .unwrap() - .vars[var_id__] - .clone(); - match varkind___ { - VarKind::LocalDef(def__) => { - let value = &self - .variables_from_defid - .get(&def__.clone()); - let thing = _interp - .env() - .defs - .defs - .get(def__); - if let Some(id) = thing { - if let DefKind::GlobalObj(obj_id) = id { - let class = _interp.env().defs.classes.get(obj_id.clone()); - } - } - } - VarKind::GlobalRef(def__) => { - let value = &self - .variables_from_defid - .get(&def__.clone()); - let thing = _interp - .env() - .defs - .defs - .get(def__); - if let Some(id) = thing { - if let DefKind::GlobalObj(obj_id) = id { - let class = _interp.env().defs.classes.get(obj_id.clone()); - } - } - } - _ => {} - } - } - } - _ => {} - }, - _ => {} - }, - _ => {} - } - } + self.read_variable_from_variable(_interp, _def_id); + } + VarKind::LocalDef(_def_id) => { + self.read_variable_from_variable(_interp, _def_id); + println!("parent3 {:?}", _def_id); } _ => {} } @@ -613,6 +567,51 @@ impl<'cx> Dataflow<'cx> for PermisisionDataflow { } } + fn read_variable_from_variable>( + &mut self, + _interp: &Interp<'cx, C>, + defid: DefId, + ) { + if let Some(value) = self.variables_from_defid.get(&defid) { + if let Value::Const(const_var) = value { + match const_var { + Const::Literal(_lit) => match _lit { + Operand::Var(var) => { + if let Base::Var(var_id__) = var.base { + let varkind = + _interp.curr_body.get().unwrap().vars[var_id__].clone(); + match varkind { + VarKind::LocalDef(def__) => { + self.read_variable_from_class(_interp, def__); + } + VarKind::GlobalRef(def__) => { + self.read_variable_from_class(_interp, def__); + } + _ => {} + } + } + } + _ => {} + }, + _ => {} + } + } + } + } + + fn read_variable_from_class>( + &mut self, + _interp: &Interp<'cx, C>, + defid: DefId, + ) { + let def_kind = _interp.env().defs.defs.get(defid); + if let Some(id) = def_kind { + if let DefKind::GlobalObj(obj_id) = id { + let class = _interp.env().defs.classes.get(obj_id.clone()); + } + } + } + fn transfer_call>( &mut self, interp: &Interp<'cx, C>, diff --git a/crates/forge_analyzer/src/interp.rs b/crates/forge_analyzer/src/interp.rs index 940cf112..b901b81f 100644 --- a/crates/forge_analyzer/src/interp.rs +++ b/crates/forge_analyzer/src/interp.rs @@ -201,6 +201,20 @@ pub trait Dataflow<'cx>: Sized { } } } + + fn read_variable_from_class>( + &mut self, + _interp: &Interp<'cx, C>, + defid: DefId, + ) { + } + + fn read_variable_from_variable>( + &mut self, + _interp: &Interp<'cx, C>, + defid: DefId, + ) { + } } pub trait Checker<'cx>: Sized { diff --git a/test-apps/jira-damn-vulnerable-forge-app/src/utils.js b/test-apps/jira-damn-vulnerable-forge-app/src/utils.js index f1882ce6..a5f5aa94 100755 --- a/test-apps/jira-damn-vulnerable-forge-app/src/utils.js +++ b/test-apps/jira-damn-vulnerable-forge-app/src/utils.js @@ -4,8 +4,9 @@ export async function fetchIssueSummary(issueIdOrKey) { const resp = await api .asApp() - .requestJira(`/rest/api/3/issue/${issueIdOrKey}?fields=summary`, { + .requestJira(`/rest/api/3/issue/${issueIdOrKey}?fields=summary`, obj = { headers: { + method: 'POST', Accept: 'application/json', }, }); From d81fe5fdb8d82a72115ff04e3b8576633ddc5889 Mon Sep 17 00:00:00 2001 From: James Gersbach Date: Sun, 25 Jun 2023 12:30:35 -0700 Subject: [PATCH 077/517] wip --- .vscode/launch.json | 121 +++++ crates/forge_analyzer/src/checkers.rs | 459 ++++++++++-------- crates/forge_analyzer/src/definitions.rs | 146 +++--- crates/forge_analyzer/src/interp.rs | 29 +- .../src/index.jsx | 2 +- .../src/utils.js | 36 +- 6 files changed, 509 insertions(+), 284 deletions(-) create mode 100644 .vscode/launch.json diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 00000000..627662fb --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,121 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "type": "lldb", + "request": "launch", + "name": "Debug unit tests in library 'forge_analyzer'", + "cargo": { + "args": [ + "test", + "--no-run", + "--lib", + "--package=forge_analyzer" + ], + "filter": { + "name": "forge_analyzer", + "kind": "lib" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug unit tests in library 'forge_file_resolver'", + "cargo": { + "args": [ + "test", + "--no-run", + "--lib", + "--package=forge_file_resolver" + ], + "filter": { + "name": "forge_file_resolver", + "kind": "lib" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug unit tests in library 'forge_utils'", + "cargo": { + "args": [ + "test", + "--no-run", + "--lib", + "--package=forge_utils" + ], + "filter": { + "name": "forge_utils", + "kind": "lib" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug unit tests in library 'forge_loader'", + "cargo": { + "args": [ + "test", + "--no-run", + "--lib", + "--package=forge_loader" + ], + "filter": { + "name": "forge_loader", + "kind": "lib" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug executable 'fsrt'", + "cargo": { + "args": [ + "build", + "--bin=fsrt", + "--package=fsrt" + ], + "filter": { + "name": "fsrt", + "kind": "bin" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug unit tests in executable 'fsrt'", + "cargo": { + "args": [ + "test", + "--no-run", + "--bin=fsrt", + "--package=fsrt" + ], + "filter": { + "name": "fsrt", + "kind": "bin" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + } + ] +} \ No newline at end of file diff --git a/crates/forge_analyzer/src/checkers.rs b/crates/forge_analyzer/src/checkers.rs index 084496d8..c5da1f9a 100644 --- a/crates/forge_analyzer/src/checkers.rs +++ b/crates/forge_analyzer/src/checkers.rs @@ -7,12 +7,13 @@ use std::{cmp::max, iter, mem, ops::ControlFlow, path::PathBuf}; use tracing::{debug, info, warn}; use crate::{ - definitions::{Const, DefId, DefKind, Environment, Value}, + definitions::{Class, Const, DefId, DefKind, Environment, Value}, interp::{ Checker, Dataflow, EntryKind, EntryPoint, Frame, Interp, JoinSemiLattice, WithCallStack, }, ir::{ - Base, BasicBlock, BasicBlockId, Inst, Intrinsic, Location, Operand, Rvalue, VarId, VarKind, + Base, BasicBlock, BasicBlockId, Inst, Intrinsic, Literal, Location, Operand, Rvalue, VarId, + VarKind, }, reporter::{IntoVuln, Reporter, Severity, Vulnerability}, worklist::WorkList, @@ -467,46 +468,6 @@ impl WithCallStack for PermissionVuln { fn add_call_stack(&mut self, _stack: Vec) {} } -impl PermisisionDataflow { - fn add_variables(&mut self, rvalue: &Rvalue, defid: &DefId) { - match rvalue { - Rvalue::Read(operand) => { - if self.variables_from_defid.contains_key(&defid) - && self.variables_from_defid.get(&defid).unwrap() - != &Value::Const(Const::Literal(operand.clone())) - { - // currently assuming prev value is not phi - let prev_vars = &self.variables_from_defid[&defid]; - match prev_vars { - Value::Const(prev_var_const) => { - match operand { - Operand::Lit(_) => {} - Operand::Var(var) => match var.base { - Base::Var(var_id) => {} - _ => {} - }, - } - let var_vec = - vec![prev_var_const.clone(), Const::Literal(operand.clone())]; - - self.variables_from_defid - .insert(*defid, Value::Phi(Vec::from(var_vec))); - } - _ => {} - } - } else { - let value = Value::Const(Const::Literal(operand.clone())); - self.variables_from_defid.insert(*defid, value.clone()); - } - } - Rvalue::Template(template) => {} - _ => {} - } - } - - fn add_something() {} -} - impl<'cx> Dataflow<'cx> for PermisisionDataflow { type State = PermissionTest; @@ -530,9 +491,30 @@ impl<'cx> Dataflow<'cx> for PermisisionDataflow { initial_state: Self::State, operands: SmallVec<[crate::ir::Operand; 4]>, ) -> Self::State { + println!("operands {operands:?}"); match *intrinsic { Intrinsic::ApiCall | Intrinsic::SafeCall | Intrinsic::Authorize => { - let second = operands.get(1); + let (first, second) = (operands.get(0), operands.get(1)); + if let Some(operand) = first { + match operand { + Operand::Lit(lit) => { + println!("lit from first operand {:?}", lit); + } + Operand::Var(var) => match var.base { + Base::Var(varid) => { + let varkind = &_interp.curr_body.get().unwrap().vars[varid]; + let defid = get_varid_from_defid(&varkind); + if let Some(defid) = defid { + println!( + "actual value {:?}", + self.variables_from_defid.get(&defid) + ); + } + } + _ => {} + }, + } + } if let Some(operand) = second { match operand { Operand::Lit(lit) => { @@ -542,11 +524,32 @@ impl<'cx> Dataflow<'cx> for PermisisionDataflow { if let Base::Var(varid) = var.base { match _interp.curr_body.get().unwrap().vars[varid].clone() { VarKind::GlobalRef(_def_id) => { - self.read_variable_from_variable(_interp, _def_id); + /* case where it is passed in as a variable */ + match &self.variables_from_defid.get(&_def_id).unwrap() { + Value::Const(const_var) => { + if let Const::Object(obj) = const_var { + let defid = find_member_of_obj("method", obj); + if let Some(defid) = defid { + let value = + self.variables_from_defid.get(&defid); + println!("value of method {value:?}"); + } + } + } + Value::Phi(phi_var) => {} + _ => {} + } } VarKind::LocalDef(_def_id) => { - self.read_variable_from_variable(_interp, _def_id); - println!("parent3 {:?}", _def_id); + let class = self.read_class_from_object(_interp, _def_id); + if let Some(obj) = class { + let defid = find_member_of_obj("method", &obj); + if let Some(defid) = defid { + let value = self.variables_from_defid.get(&defid); + println!("value of method {value:?}"); + } + } + // } _ => {} } @@ -567,49 +570,21 @@ impl<'cx> Dataflow<'cx> for PermisisionDataflow { } } - fn read_variable_from_variable>( - &mut self, - _interp: &Interp<'cx, C>, - defid: DefId, - ) { - if let Some(value) = self.variables_from_defid.get(&defid) { - if let Value::Const(const_var) = value { - match const_var { - Const::Literal(_lit) => match _lit { - Operand::Var(var) => { - if let Base::Var(var_id__) = var.base { - let varkind = - _interp.curr_body.get().unwrap().vars[var_id__].clone(); - match varkind { - VarKind::LocalDef(def__) => { - self.read_variable_from_class(_interp, def__); - } - VarKind::GlobalRef(def__) => { - self.read_variable_from_class(_interp, def__); - } - _ => {} - } - } - } - _ => {} - }, - _ => {} - } - } - } - } - - fn read_variable_from_class>( + fn read_class_from_object>( &mut self, _interp: &Interp<'cx, C>, defid: DefId, - ) { + ) -> Option { let def_kind = _interp.env().defs.defs.get(defid); if let Some(id) = def_kind { if let DefKind::GlobalObj(obj_id) = id { let class = _interp.env().defs.classes.get(obj_id.clone()); + if let Some(class) = class { + return Some(class.clone()); + } } } + None } fn transfer_call>( @@ -708,159 +683,167 @@ impl<'cx> Dataflow<'cx> for PermisisionDataflow { let var_kind = &interp.curr_body.get().unwrap().vars[varid]; match var_kind { VarKind::LocalDef(defid) => { - match rvalue { - Rvalue::Read(operand) => { - if self.variables_from_defid.contains_key(&defid) - && self.variables_from_defid.get(&defid).unwrap() - != &Value::Const(Const::Literal(operand.clone())) - { - // currently assuming prev value is not phi - let prev_vars = &self.variables_from_defid[&defid]; - match prev_vars { - Value::Const(prev_var_const) => { - let var_vec = vec![ - prev_var_const.clone(), - Const::Literal(operand.clone()), - ]; - - self.variables_from_defid.insert( - *defid, - Value::Phi(Vec::from(var_vec)), - ); - } - _ => {} - } - } else { - match operand { - Operand::Lit(lit) => { - // let value = Value::Const(Const::Literal(operand.clone())); - // self.variables_from_defid.insert(*defid, value.clone()); - } - Operand::Var(var) => match var.base { - Base::Var(var_id) => { - let something = - &interp.curr_body.get().unwrap().vars - [var_id]; - } - _ => {} - }, - } - let value = - Value::Const(Const::Literal(operand.clone())); - self.variables_from_defid.insert(*defid, value.clone()); - } - } - Rvalue::Template(template) => {} - _ => {} - } + self.add_variable(interp, defid, rvalue); } VarKind::GlobalRef(defid) => { - match rvalue { - Rvalue::Read(operand) => { - if self.variables_from_defid.contains_key(&defid) - && self.variables_from_defid.get(&defid).unwrap() - != &Value::Const(Const::Literal(operand.clone())) - { - // currently assuming prev value is not phi - let prev_vars = &self.variables_from_defid[&defid]; - match prev_vars { - Value::Const(prev_var_const) => { - let var_vec = vec![ - prev_var_const.clone(), - Const::Literal(operand.clone()), - ]; - - self.variables_from_defid.insert( - *defid, - Value::Phi(Vec::from(var_vec)), - ); - } - _ => {} - } - } else { - match operand { - Operand::Lit(lit) => {} - Operand::Var(var) => match var.base { - Base::Var(var_id) => { - let something = - &interp.curr_body.get().unwrap().vars - [var_id]; - } - _ => {} - }, - } - let value = - Value::Const(Const::Literal(operand.clone())); - self.variables_from_defid.insert(*defid, value.clone()); - } - } - Rvalue::Template(template) => {} - _ => {} - } + self.add_variable(interp, defid, rvalue); } VarKind::Arg(defid) => { - match rvalue { - Rvalue::Read(operand) => { - if self.variables_from_defid.contains_key(&defid) - && self.variables_from_defid.get(&defid).unwrap() - != &Value::Const(Const::Literal(operand.clone())) - { - // currently assuming prev value is not phi - let prev_vars = &self.variables_from_defid[&defid]; - match prev_vars { - Value::Const(prev_var_const) => { - let var_vec = vec![ - prev_var_const.clone(), - Const::Literal(operand.clone()), - ]; - - self.variables_from_defid.insert( - *defid, - Value::Phi(Vec::from(var_vec)), - ); + self.add_variable(interp, defid, rvalue); + } + VarKind::Temp { parent } => { + if let Some(defid) = parent { + self.add_variable(interp, defid, rvalue); + } + } + _ => {} + } + } + _ => {} + }, + _ => {} + } + } + state + } + + fn add_variable>( + &mut self, + interp: &Interp<'cx, C>, + defid: &DefId, + rvalue: &Rvalue, + ) { + match rvalue { + Rvalue::Read(operand) => { + if let Some(values) = self.variables_from_defid.get(&defid) { + match values { + Value::Const(const_value) => { + let prev_value = vec![const_value.clone()]; + self.insert_value(operand, defid, interp, Some(prev_value.clone())); + } + Value::Phi(phi_value) => { + self.insert_value(operand, defid, interp, Some(phi_value.clone())); + } + _ => {} + } + } else { + self.insert_value(operand, defid, interp, None) + } + } + Rvalue::Template(template) => { + // self.insert_value(operand, defid, interp, None); + + let quasis_joined = template.quasis.join(""); + let mut all_potential_values = vec![quasis_joined]; + for expr in &template.exprs { + if let Some(varid) = resolve_var_from_operand(&expr) { + if let Some(varkind) = interp.curr_body.get().unwrap().vars.get(varid) { + let defid = get_varid_from_defid(&varkind); + if let Some(defid) = defid { + if let Some(value) = self.variables_from_defid.get(&defid) { + match value { + Value::Const(const_value) => { + let mut new_all_values = vec![]; + if let Const::Literal(literal_string) = const_value { + for values in &all_potential_values { + new_all_values + .push(values.clone() + literal_string); } - _ => {} } - } else { - match operand { - Operand::Lit(lit) => {} - Operand::Var(var) => match var.base { - Base::Var(var_id) => { - let something = - &interp.curr_body.get().unwrap().vars - [var_id]; + all_potential_values = new_all_values; + } + Value::Phi(phi_value) => { + let mut new_all_values = vec![]; + for constant in phi_value { + if let Const::Literal(literal_string) = constant { + for values in &all_potential_values { + new_all_values + .push(values.clone() + literal_string); } - _ => {} - }, + } } - let value = - Value::Const(Const::Literal(operand.clone())); - self.variables_from_defid.insert(*defid, value.clone()); + all_potential_values = new_all_values; } + _ => {} } - Rvalue::Template(template) => {} - _ => {} } } - VarKind::Temp { parent } => { - match rvalue { - Rvalue::Template(template) => {} - _ => {} - } - if let Some(defid) = parent { - self.add_variables(rvalue, defid) + } + } else if let Some(literal) = resolve_literal_from_operand(&expr) { + println!("literal {literal:?}"); + } + } + + if all_potential_values.len() > 1 { + let consts = all_potential_values + .into_iter() + .map(|value| Const::Literal(value.clone())) + .collect::>(); + let value = Value::Phi(consts); + self.variables_from_defid.insert(*defid, value.clone()); + } else if all_potential_values.len() == 1 { + self.variables_from_defid.insert( + *defid, + Value::Const(Const::Literal(all_potential_values.get(0).unwrap().clone())), + ); + } + } + _ => {} + } + } + + fn insert_value>( + &mut self, + operand: &Operand, + defid: &DefId, + interp: &Interp<'cx, C>, + prev_values: Option>, + ) { + match operand { + Operand::Lit(_lit) => { + if let Some(prev_values) = prev_values { + if let Some(lit_value) = convert_operand_to_raw(operand) { + let const_value = Const::Literal(lit_value); + let mut all_values = prev_values.clone(); + all_values.push(const_value); + let value = Value::Phi(all_values); + self.variables_from_defid.insert(*defid, value.clone()); + } + } else { + if let Some(lit_value) = convert_operand_to_raw(operand) { + let value = Value::Const(Const::Literal(lit_value)); + self.variables_from_defid.insert(*defid, value.clone()); + } + } + } + Operand::Var(var) => { + match var.base { + Base::Var(var_id) => { + let something = &interp.curr_body.get().unwrap().vars[var_id]; + if let VarKind::LocalDef(local_defid) = something { + /* add objects to the variables */ + if let Some(class) = + self.read_class_from_object(interp, local_defid.clone()) + { + if let Some(prev_values) = prev_values { + let const_value = Const::Object(class.clone()); + let mut all_values = prev_values.clone(); + all_values.push(const_value); + let value = Value::Phi(all_values); + self.variables_from_defid.insert(*defid, value.clone()); + } else { + let value = Value::Const(Const::Object(class.clone())); + self.variables_from_defid.insert(*defid, value.clone()); } + } else if let Some(value) = self.variables_from_defid.get(defid) { + println!("found value {:?}", value) } - _ => {} } } - _ => {} - }, - _ => {} + } } } - state } fn join_term>( @@ -878,6 +861,54 @@ impl<'cx> Dataflow<'cx> for PermisisionDataflow { } } +fn resolve_var_from_operand(operand: &Operand) -> Option { + if let Operand::Var(var) = operand { + if let Base::Var(varid) = var.base { + return Some(varid); + } + } + None +} + +fn resolve_literal_from_operand(operand: &Operand) -> Option { + if let Operand::Lit(lit) = operand { + return Some(lit.clone()); + } + None +} + +fn get_varid_from_defid(varkind: &VarKind) -> Option { + match varkind { + VarKind::GlobalRef(defid) => Some(defid.clone()), + VarKind::LocalDef(defid) => Some(defid.clone()), + VarKind::Arg(defid) => Some(defid.clone()), + VarKind::Temp { parent } => parent.clone(), + _ => None, + } +} + +fn convert_operand_to_raw(operand: &Operand) -> Option { + if let Operand::Lit(lit) = operand { + match lit { + Literal::BigInt(bigint) => Some(bigint.to_string()), + Literal::Number(num) => Some(num.to_string()), + Literal::Str(str) => Some(str.to_string()), + _ => None, + } + } else { + None + } +} + +fn find_member_of_obj(member: &str, obj: &Class) -> Option { + for (mem, memdefid) in &obj.pub_members { + if mem == member { + return Some(memdefid.clone()); + } + } + None +} + pub struct PermissionChecker { vulns: Vec, } diff --git a/crates/forge_analyzer/src/definitions.rs b/crates/forge_analyzer/src/definitions.rs index 048565a4..0a0936b4 100644 --- a/crates/forge_analyzer/src/definitions.rs +++ b/crates/forge_analyzer/src/definitions.rs @@ -54,13 +54,13 @@ create_newtype! { pub struct DefId(u32); } -#[derive(Debug, Clone, PartialEq, Eq)] +#[derive(Debug, Clone)] pub enum Const { - Literal(Operand), - Object(Operand), + Literal(String), + Object(Class), } -#[derive(Debug, Clone, PartialEq, Eq)] +#[derive(Debug, Clone)] pub enum Value { Uninit, Unknown, @@ -200,7 +200,7 @@ pub fn run_resolver( #[derive(Debug, Clone)] pub struct Class { def: DefId, - pub_members: Vec<(JsWord, DefId)>, + pub pub_members: Vec<(JsWord, DefId)>, constructor: Option, } @@ -698,7 +698,7 @@ struct FunctionCollector<'cx> { } struct FunctionAnalyzer<'cx> { - res: &'cx mut Environment, + pub res: &'cx mut Environment, module: ModId, current_def: DefId, assigning_to: Option, @@ -841,7 +841,7 @@ impl<'cx> FunctionAnalyzer<'cx> { } fn lower_member(&mut self, obj: &Expr, prop: &MemberProp) -> Operand { - let obj = self.lower_expr(obj); + let obj = self.lower_expr(obj, None); let Operand::Var(mut var) = obj else { // FIXME: handle literals return obj; @@ -852,7 +852,7 @@ impl<'cx> FunctionAnalyzer<'cx> { var.projections.push(Projection::Known(id.0)); } MemberProp::Computed(ComputedPropName { expr, .. }) => { - let opnd = self.lower_expr(expr); + let opnd = self.lower_expr(expr, None); var.projections .push(self.body.resolve_prop(self.block, opnd)); } @@ -901,7 +901,7 @@ impl<'cx> FunctionAnalyzer<'cx> { self.bind_pats_helper(value, rhs); } PropName::Computed(ComputedPropName { expr, .. }) => { - let opnd = self.lower_expr(expr); + let opnd = self.lower_expr(expr, None); let proj = self.body.resolve_prop(self.block, opnd); rhs.projections.push(proj); self.bind_pats_helper(value, rhs); @@ -959,7 +959,7 @@ impl<'cx> FunctionAnalyzer<'cx> { return Operand::UNDEF; } BlockStmtOrExpr::Expr(expr) => { - return self.lower_expr(&expr); + return self.lower_expr(&expr, None); } }, Expr::Fn(FnExpr { ident: _, function }) => { @@ -973,11 +973,20 @@ impl<'cx> FunctionAnalyzer<'cx> { } } } - let lowered_args = args.iter().map(|arg| self.lower_expr(&arg.expr)).collect(); + let lowered_args = args + .iter() + .enumerate() + .map(|(i, arg)| { + let defid = + self.res + .add_anonymous(i.to_string() + "test", AnonType::Unknown, self.module); + self.lower_expr(&arg.expr, Some(defid)) + }) + .collect(); let callee = match callee { CalleeRef::Super => Operand::Var(Variable::SUPER), CalleeRef::Import => Operand::UNDEF, - CalleeRef::Expr(expr) => self.lower_expr(expr), + CalleeRef::Expr(expr) => self.lower_expr(expr, None), }; let first_arg = args.first().map(|expr| &*expr.expr); let call = match self.as_intrinsic(&props, first_arg) { @@ -1011,7 +1020,7 @@ impl<'cx> FunctionAnalyzer<'cx> { } Pat::Invalid(_) => {} Pat::Expr(expr) => { - let opnd = self.lower_expr(expr); + let opnd = self.lower_expr(expr, None); self.body.coerce_to_lval(self.block, opnd, None); } } @@ -1021,7 +1030,7 @@ impl<'cx> FunctionAnalyzer<'cx> { let exprs = n .exprs .iter() - .map(|expr| self.lower_expr(expr)) + .map(|expr| self.lower_expr(expr, None)) .collect::>(); let quasis = n .quasis @@ -1043,9 +1052,11 @@ impl<'cx> FunctionAnalyzer<'cx> { } JSXElementChild::JSXExprContainer(JSXExprContainer { expr, .. }) => match expr { JSXExpr::JSXEmptyExpr(_) => Operand::UNDEF, - JSXExpr::Expr(expr) => self.lower_expr(expr), + JSXExpr::Expr(expr) => self.lower_expr(expr, None), }, - JSXElementChild::JSXSpreadChild(JSXSpreadChild { expr, .. }) => self.lower_expr(expr), + JSXElementChild::JSXSpreadChild(JSXSpreadChild { expr, .. }) => { + self.lower_expr(expr, None) + } JSXElementChild::JSXElement(elem) => self.lower_jsx_elem(elem), JSXElementChild::JSXFragment(JSXFragment { children, .. }) => { for child in children { @@ -1100,7 +1111,7 @@ impl<'cx> FunctionAnalyzer<'cx> { } // TODO: This can probably be made into a trait - fn lower_expr(&mut self, n: &Expr) -> Operand { + fn lower_expr(&mut self, n: &Expr, parent: Option) -> Operand { match n { Expr::This(_) => Operand::Var(Variable::THIS), Expr::Array(ArrayLit { elems, .. }) => { @@ -1109,7 +1120,7 @@ impl<'cx> FunctionAnalyzer<'cx> { .map(|e| { e.as_ref() .map_or(Operand::UNDEF, |ExprOrSpread { spread, expr }| { - self.lower_expr(expr) + self.lower_expr(expr, None) }) }) .collect(); @@ -1149,17 +1160,19 @@ impl<'cx> FunctionAnalyzer<'cx> { PropName::Num(num) => num.span, PropName::Str(str) => str.span, }; - let lowered_value = self.lower_expr(&value); + let lowered_value = self.lower_expr(&value, None); let next_key = self.res.get_or_overwrite_sym( (key.as_symbol().unwrap(), span.ctxt), self.module, DefKind::Arg, ); - let lowered_var = self.body.coerce_to_lval( + let mut lowered_var = self.body.coerce_to_lval( self.block, lowered_value.clone(), Some(next_key), ); + if let Base::Var(varid) = lowered_var.base {} + let rval = Rvalue::Read(lowered_value); match lowered_var.base { Base::Var(var_id) => { @@ -1195,6 +1208,7 @@ impl<'cx> FunctionAnalyzer<'cx> { key.as_symbol().unwrap(), def_id_prop, )); + // lowered_var.base =cls.def } _ => {} } @@ -1211,7 +1225,7 @@ impl<'cx> FunctionAnalyzer<'cx> { } Expr::Fn(_) => Operand::UNDEF, Expr::Unary(UnaryExpr { op, arg, .. }) => { - let arg = self.lower_expr(arg); + let arg = self.lower_expr(arg, None); let tmp = self .body .push_tmp(self.block, Rvalue::Unary(op.into(), arg), None); @@ -1221,13 +1235,13 @@ impl<'cx> FunctionAnalyzer<'cx> { op, prefix, arg, .. }) => { // FIXME: Handle op - self.lower_expr(arg) + self.lower_expr(arg, None) } Expr::Bin(BinExpr { op, left, right, .. }) => { - let left = self.lower_expr(left); - let right = self.lower_expr(right); + let left = self.lower_expr(left, None); + let right = self.lower_expr(right, None); let tmp = self .body .push_tmp(self.block, Rvalue::Bin(op.into(), left, right), None); @@ -1242,7 +1256,7 @@ impl<'cx> FunctionAnalyzer<'cx> { super_var.projections.push(Projection::Known(id)); } SuperProp::Computed(ComputedPropName { expr, .. }) => { - let opnd = self.lower_expr(expr); + let opnd = self.lower_expr(expr, None); let prop = self.body.resolve_prop(self.block, opnd); super_var.projections.push(prop); } @@ -1252,10 +1266,10 @@ impl<'cx> FunctionAnalyzer<'cx> { Expr::Assign(AssignExpr { op, left, right, .. }) => { - let rhs = self.lower_expr(right); + let rhs = self.lower_expr(right, None); match left { PatOrExpr::Expr(expr) => { - let opnd = self.lower_expr(expr); + let opnd = self.lower_expr(expr, None); let lval = self.body.coerce_to_lval(self.block, opnd, None); self.push_curr_inst(Inst::Assign(lval, Rvalue::Read(rhs.clone()))); } @@ -1269,7 +1283,7 @@ impl<'cx> FunctionAnalyzer<'cx> { Expr::Cond(CondExpr { test, cons, alt, .. }) => { - let cond = self.lower_expr(test); + let cond = self.lower_expr(test, None); let curr = self.block; let rest = self.body.new_block(); let cons_block = self.body.new_block(); @@ -1280,11 +1294,11 @@ impl<'cx> FunctionAnalyzer<'cx> { alt: alt_block, }); self.block = cons_block; - let cons = self.lower_expr(cons); + let cons = self.lower_expr(cons, None); let cons_phi = self.body.push_tmp(self.block, Rvalue::Read(cons), None); self.set_curr_terminator(Terminator::Goto(rest)); self.block = alt_block; - let alt = self.lower_expr(alt); + let alt = self.lower_expr(alt, None); let alt_phi = self.body.push_tmp(self.block, Rvalue::Read(alt), None); self.set_curr_terminator(Terminator::Goto(rest)); self.block = rest; @@ -1300,10 +1314,10 @@ impl<'cx> FunctionAnalyzer<'cx> { Expr::Seq(SeqExpr { exprs, .. }) => { if let Some((last, rest)) = exprs.split_last() { for expr in rest { - let opnd = self.lower_expr(expr); + let opnd = self.lower_expr(expr, None); self.body.push_expr(self.block, Rvalue::Read(opnd)); } - self.lower_expr(last) + self.lower_expr(last, None) } else { Literal::Undef.into() } @@ -1320,24 +1334,30 @@ impl<'cx> FunctionAnalyzer<'cx> { Expr::Lit(lit) => lit.clone().into(), Expr::Tpl(tpl) => { let tpl = self.lower_tpl(tpl); - Operand::with_var(self.body.push_tmp(self.block, Rvalue::Template(tpl), None)) + Operand::with_var( + self.body + .push_tmp(self.block, Rvalue::Template(tpl), parent), + ) } Expr::TaggedTpl(TaggedTpl { tag, tpl, .. }) => { - let tag = Some(self.lower_expr(tag)); + let tag = Some(self.lower_expr(tag, parent)); let tpl = Template { tag, ..self.lower_tpl(tpl) }; - Operand::with_var(self.body.push_tmp(self.block, Rvalue::Template(tpl), None)) + Operand::with_var( + self.body + .push_tmp(self.block, Rvalue::Template(tpl), parent), + ) } Expr::Arrow(_) => Operand::UNDEF, Expr::Class(_) => Operand::UNDEF, Expr::Yield(YieldExpr { arg, .. }) => arg .as_deref() - .map_or(Operand::UNDEF, |expr| self.lower_expr(expr)), + .map_or(Operand::UNDEF, |expr| self.lower_expr(expr, None)), Expr::MetaProp(_) => Operand::UNDEF, - Expr::Await(AwaitExpr { arg, .. }) => self.lower_expr(arg), - Expr::Paren(ParenExpr { expr, .. }) => self.lower_expr(expr), + Expr::Await(AwaitExpr { arg, .. }) => self.lower_expr(arg, None), + Expr::Paren(ParenExpr { expr, .. }) => self.lower_expr(expr, None), Expr::JSXMember(mem) => self.lower_jsx_member(&mem), Expr::JSXNamespacedName(JSXNamespacedName { ns, name, .. }) => { let mut ident = self.lower_ident(&ns); @@ -1364,7 +1384,7 @@ impl<'cx> FunctionAnalyzer<'cx> { | Expr::TsNonNull(TsNonNullExpr { expr, .. }) | Expr::TsAs(TsAsExpr { expr, .. }) | Expr::TsInstantiation(TsInstantiation { expr, .. }) - | Expr::TsSatisfies(TsSatisfiesExpr { expr, .. }) => self.lower_expr(expr), + | Expr::TsSatisfies(TsSatisfiesExpr { expr, .. }) => self.lower_expr(expr, None), Expr::PrivateName(PrivateName { id, .. }) => todo!(), Expr::OptChain(OptChainExpr { base, .. }) => match base { OptChainBase::Call(OptCall { callee, args, .. }) => { @@ -1391,13 +1411,13 @@ impl<'cx> FunctionAnalyzer<'cx> { Stmt::Empty(_) => {} Stmt::Debugger(_) => {} Stmt::With(WithStmt { obj, body, .. }) => { - let opnd = self.lower_expr(obj); + let opnd = self.lower_expr(obj, None); self.body.push_expr(self.block, Rvalue::Read(opnd)); self.lower_stmt(body); } Stmt::Return(ReturnStmt { arg, .. }) => { if let Some(arg) = arg { - let opnd = self.lower_expr(arg); + let opnd = self.lower_expr(arg, None); self.body .push_inst(self.block, Inst::Assign(RETURN_VAR, Rvalue::Read(opnd))); } @@ -1422,7 +1442,7 @@ impl<'cx> FunctionAnalyzer<'cx> { } else { cont }; - let cond = self.lower_expr(test); + let cond = self.lower_expr(test, None); self.set_curr_terminator(Terminator::If { cond, cons: cons_block, @@ -1437,11 +1457,11 @@ impl<'cx> FunctionAnalyzer<'cx> { cases, .. }) => { - let opnd = self.lower_expr(discriminant); + let opnd = self.lower_expr(discriminant, None); // TODO: lower switch } Stmt::Throw(ThrowStmt { arg, .. }) => { - let opnd = self.lower_expr(arg); + let opnd = self.lower_expr(arg, None); self.body.push_expr(self.block, Rvalue::Read(opnd)); self.body.set_terminator(self.block, Terminator::Throw); } @@ -1461,7 +1481,7 @@ impl<'cx> FunctionAnalyzer<'cx> { let [check, cont, body_id] = self.body.new_blocks(); self.set_curr_terminator(Terminator::Goto(check)); self.block = check; - let cond = self.lower_expr(test); + let cond = self.lower_expr(test, None); self.set_curr_terminator(Terminator::If { cond, cons: body_id, @@ -1479,7 +1499,7 @@ impl<'cx> FunctionAnalyzer<'cx> { self.lower_stmt(body); self.set_curr_terminator(Terminator::Goto(check)); self.block = check; - let cond = self.lower_expr(test); + let cond = self.lower_expr(test, None); self.set_curr_terminator(Terminator::If { cond, cons: body_id, @@ -1499,14 +1519,14 @@ impl<'cx> FunctionAnalyzer<'cx> { self.lower_var_decl(decl); } Some(VarDeclOrExpr::Expr(expr)) => { - self.lower_expr(expr); + self.lower_expr(expr, None); } None => {} } let [check, cont, body_id] = self.body.new_blocks(); self.goto_block(check); if let Some(test) = test { - let cond = self.lower_expr(test); + let cond = self.lower_expr(test, None); self.set_curr_terminator(Terminator::If { cond, cons: body_id, @@ -1518,7 +1538,7 @@ impl<'cx> FunctionAnalyzer<'cx> { self.block = body_id; self.lower_stmt(body); if let Some(update) = update { - self.lower_expr(update); + self.lower_expr(update, None); } self.set_curr_terminator(Terminator::Goto(check)); self.goto_block(cont); @@ -1541,7 +1561,7 @@ impl<'cx> FunctionAnalyzer<'cx> { | Decl::TsModule(_) => {} }, Stmt::Expr(ExprStmt { expr, .. }) => { - let opnd = self.lower_expr(expr); + let opnd = self.lower_expr(expr, None); self.body.push_expr(self.block, Rvalue::Read(opnd)); } } @@ -1549,7 +1569,7 @@ impl<'cx> FunctionAnalyzer<'cx> { fn lower_loop(&mut self, left: &VarDeclOrPat, right: &Expr, body: &Stmt) { // FIXME: don't assume loops are infinite - let opnd = self.lower_expr(right); + let opnd = self.lower_expr(right, None); match left { VarDeclOrPat::VarDecl(var) => self.lower_var_decl(var), VarDeclOrPat::Pat(pat) => self.bind_pats(pat, Rvalue::Read(opnd)), @@ -1559,11 +1579,21 @@ impl<'cx> FunctionAnalyzer<'cx> { fn lower_var_decl(&mut self, var: &VarDecl) { for decl in &var.decls { - let opnd = decl - .init - .as_deref() - .map_or(Operand::UNDEF, |init| self.lower_expr(init)); - self.bind_pats(&decl.name, Rvalue::Read(opnd)); + if let Pat::Ident(id) = &decl.name { + let id = id.to_id(); + let def = self.res.get_or_insert_sym(id, self.module); + let opnd = decl + .init + .as_deref() + .map_or(Operand::UNDEF, |init| self.lower_expr(init, Some(def))); + self.bind_pats(&decl.name, Rvalue::Read(opnd)); + } else { + let opnd = decl + .init + .as_deref() + .map_or(Operand::UNDEF, |init| self.lower_expr(init, None)); + self.bind_pats(&decl.name, Rvalue::Read(opnd)); + } } } } @@ -1730,7 +1760,7 @@ impl Visit for FunctionCollector<'_> { analyzer.lower_stmts(stmts); } BlockStmtOrExpr::Expr(e) => { - let opnd = analyzer.lower_expr(e); + let opnd = analyzer.lower_expr(e, None); analyzer .body .push_inst(analyzer.block, Inst::Assign(RETURN_VAR, Rvalue::Read(opnd))); @@ -1790,7 +1820,7 @@ impl Visit for FunctionCollector<'_> { operand_stack: vec![], in_lhs: false, }; - let opnd = analyzer.lower_expr(expr); + let opnd = analyzer.lower_expr(expr, None); analyzer.body.push_inst( analyzer.block, Inst::Assign(RETURN_VAR, Rvalue::Read(opnd)), diff --git a/crates/forge_analyzer/src/interp.rs b/crates/forge_analyzer/src/interp.rs index b901b81f..aa3bc30a 100644 --- a/crates/forge_analyzer/src/interp.rs +++ b/crates/forge_analyzer/src/interp.rs @@ -15,7 +15,7 @@ use swc_core::ecma::atoms::JsWord; use tracing::{debug, info, instrument, warn}; use crate::{ - definitions::{Const, DefId, Environment, Value}, + definitions::{Class, Const, DefId, Environment, Value}, ir::{ Base, BasicBlock, BasicBlockId, Body, Inst, Intrinsic, Location, Operand, Rvalue, Successors, STARTING_BLOCK, @@ -149,6 +149,23 @@ pub trait Dataflow<'cx>: Sized { state } + fn add_variable>( + &mut self, + interp: &Interp<'cx, C>, + defid: &DefId, + rvalue: &Rvalue, + ) { + } + + fn insert_value>( + &mut self, + operand: &Operand, + defid: &DefId, + interp: &Interp<'cx, C>, + prev_values: Option>, + ) { + } + fn join_term>( &mut self, interp: &Interp<'cx, C>, @@ -202,18 +219,20 @@ pub trait Dataflow<'cx>: Sized { } } - fn read_variable_from_class>( + fn read_class_from_variable>( &mut self, _interp: &Interp<'cx, C>, defid: DefId, - ) { + ) -> Option { + None } - fn read_variable_from_variable>( + fn read_class_from_object>( &mut self, _interp: &Interp<'cx, C>, defid: DefId, - ) { + ) -> Option { + None } } diff --git a/test-apps/jira-damn-vulnerable-forge-app/src/index.jsx b/test-apps/jira-damn-vulnerable-forge-app/src/index.jsx index 9103d0cb..157616ff 100755 --- a/test-apps/jira-damn-vulnerable-forge-app/src/index.jsx +++ b/test-apps/jira-damn-vulnerable-forge-app/src/index.jsx @@ -96,7 +96,7 @@ function SecureGlance() { return ''; } const [flagVal] = useState(async () => { - const issueData = await fetchIssueSummary(platformContext.issueKey); + const issueData = await fetchIssueSummary("test_value_replicated"); return JSON.stringify(issueData); }); diff --git a/test-apps/jira-damn-vulnerable-forge-app/src/utils.js b/test-apps/jira-damn-vulnerable-forge-app/src/utils.js index a5f5aa94..28c650aa 100755 --- a/test-apps/jira-damn-vulnerable-forge-app/src/utils.js +++ b/test-apps/jira-damn-vulnerable-forge-app/src/utils.js @@ -2,14 +2,38 @@ import api, { route } from '@forge/api'; export async function fetchIssueSummary(issueIdOrKey) { + let obj = { + method: 'POST', + headers: { + Accept: 'application/json', + }, + }; + + let a = "test_a_value"; + + a = "test_a_value_new"; + + let b = a; + + // issueIdOrKey = "new_test" + + // let issueIdOrKey = "issueIdOrKey_value"; + // let issueIdOrKey = "issueIdOrKey_value2"; + + let obj2 = { + method: 'POST', + headers: { + Accept: 'application/json', + }, + } + + obj2.method = "GET"; + + let a_url = route`/rest/api/3/issue/${issueIdOrKey}?fields=summary`; + const resp = await api .asApp() - .requestJira(`/rest/api/3/issue/${issueIdOrKey}?fields=summary`, obj = { - headers: { - method: 'POST', - Accept: 'application/json', - }, - }); + .requestJira(route`/rest/api/3/issue/${issueIdOrKey}?fields=summary`, obj2); const data = await resp.json(); console.log(JSON.stringify(data)); return data['fields']['summary']; From abbb6a88a5a8ae913d7e8a00bc665bac13e9f91b Mon Sep 17 00:00:00 2001 From: James Gersbach Date: Sun, 25 Jun 2023 12:31:40 -0700 Subject: [PATCH 078/517] removing launch.json --- .vscode/launch.json | 121 -------------------------------------------- 1 file changed, 121 deletions(-) delete mode 100644 .vscode/launch.json diff --git a/.vscode/launch.json b/.vscode/launch.json deleted file mode 100644 index 627662fb..00000000 --- a/.vscode/launch.json +++ /dev/null @@ -1,121 +0,0 @@ -{ - // Use IntelliSense to learn about possible attributes. - // Hover to view descriptions of existing attributes. - // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 - "version": "0.2.0", - "configurations": [ - { - "type": "lldb", - "request": "launch", - "name": "Debug unit tests in library 'forge_analyzer'", - "cargo": { - "args": [ - "test", - "--no-run", - "--lib", - "--package=forge_analyzer" - ], - "filter": { - "name": "forge_analyzer", - "kind": "lib" - } - }, - "args": [], - "cwd": "${workspaceFolder}" - }, - { - "type": "lldb", - "request": "launch", - "name": "Debug unit tests in library 'forge_file_resolver'", - "cargo": { - "args": [ - "test", - "--no-run", - "--lib", - "--package=forge_file_resolver" - ], - "filter": { - "name": "forge_file_resolver", - "kind": "lib" - } - }, - "args": [], - "cwd": "${workspaceFolder}" - }, - { - "type": "lldb", - "request": "launch", - "name": "Debug unit tests in library 'forge_utils'", - "cargo": { - "args": [ - "test", - "--no-run", - "--lib", - "--package=forge_utils" - ], - "filter": { - "name": "forge_utils", - "kind": "lib" - } - }, - "args": [], - "cwd": "${workspaceFolder}" - }, - { - "type": "lldb", - "request": "launch", - "name": "Debug unit tests in library 'forge_loader'", - "cargo": { - "args": [ - "test", - "--no-run", - "--lib", - "--package=forge_loader" - ], - "filter": { - "name": "forge_loader", - "kind": "lib" - } - }, - "args": [], - "cwd": "${workspaceFolder}" - }, - { - "type": "lldb", - "request": "launch", - "name": "Debug executable 'fsrt'", - "cargo": { - "args": [ - "build", - "--bin=fsrt", - "--package=fsrt" - ], - "filter": { - "name": "fsrt", - "kind": "bin" - } - }, - "args": [], - "cwd": "${workspaceFolder}" - }, - { - "type": "lldb", - "request": "launch", - "name": "Debug unit tests in executable 'fsrt'", - "cargo": { - "args": [ - "test", - "--no-run", - "--bin=fsrt", - "--package=fsrt" - ], - "filter": { - "name": "fsrt", - "kind": "bin" - } - }, - "args": [], - "cwd": "${workspaceFolder}" - } - ] -} \ No newline at end of file From b2ac39e040d2f32064f507fc25a040f84b5afd08 Mon Sep 17 00:00:00 2001 From: James Gersbach Date: Sun, 25 Jun 2023 14:44:15 -0700 Subject: [PATCH 079/517] updates to parsing worklist arguments --- crates/forge_analyzer/src/checkers.rs | 118 +++----- crates/forge_analyzer/src/interp.rs | 6 +- .../forge_analyzer/src/permission_checker.rs | 0 .../src/permission_classifier.rs | 281 ++++++++++++++++++ crates/forge_analyzer/src/worklist.rs | 24 +- .../src/index.jsx | 2 +- 6 files changed, 340 insertions(+), 91 deletions(-) delete mode 100644 crates/forge_analyzer/src/permission_checker.rs create mode 100644 crates/forge_analyzer/src/permission_classifier.rs diff --git a/crates/forge_analyzer/src/checkers.rs b/crates/forge_analyzer/src/checkers.rs index c5da1f9a..ac0ac9ee 100644 --- a/crates/forge_analyzer/src/checkers.rs +++ b/crates/forge_analyzer/src/checkers.rs @@ -112,7 +112,7 @@ impl<'cx> Dataflow<'cx> for AuthorizeDataflow { def: DefId, block: &'cx BasicBlock, state: Self::State, - worklist: &mut WorkList, + worklist: &mut WorkList, ) { self.super_join_term(interp, def, block, state, worklist); for def in self.needs_call.drain(..) { @@ -332,7 +332,7 @@ impl<'cx> Dataflow<'cx> for AuthenticateDataflow { def: DefId, block: &'cx BasicBlock, state: Self::State, - worklist: &mut WorkList, + worklist: &mut WorkList, ) { self.super_join_term(interp, def, block, state, worklist); for def in self.needs_call.drain(..) { @@ -459,7 +459,7 @@ impl WithCallStack for AuthNVuln { } pub struct PermisisionDataflow { - needs_call: Vec<(DefId, Vec)>, + needs_call: Vec<(DefId, Vec)>, variables: FxHashMap, variables_from_defid: FxHashMap, } @@ -601,45 +601,10 @@ impl<'cx> Dataflow<'cx> for PermisisionDataflow { return initial_state; }; - let mut def_ids_operands = vec![]; - - for operand in operands { - match operand { - Operand::Var(variable) => match variable.base { - Base::Var(varid) => { - let varkind = &interp.curr_body.get().unwrap().vars[varid]; - - match varkind { - VarKind::LocalDef(defid) => { - def_ids_operands.push(defid.clone()); - } - VarKind::GlobalRef(defid) => { - if self.variables_from_defid.contains_key(defid) { - def_ids_operands.push(defid.clone()); - } else { - } - } - VarKind::Arg(defid) => { - def_ids_operands.push(defid.clone()); - } - VarKind::Temp { parent } => { - if let Some(defid) = parent { - def_ids_operands.push(defid.clone()); - } - } - _ => {} - } - } - _ => {} - }, - Operand::Lit(lit) => {} - } - } - let callee_name = interp.env().def_name(callee_def); let caller_name = interp.env().def_name(def); debug!("Found call to {callee_name} at {def:?} {caller_name}"); - self.needs_call.push((callee_def, def_ids_operands.clone())); + self.needs_call.push((callee_def, operands.into_vec())); initial_state } @@ -650,53 +615,52 @@ impl<'cx> Dataflow<'cx> for PermisisionDataflow { bb: BasicBlockId, block: &'cx BasicBlock, initial_state: Self::State, - arguments: Option>, + arguments: Option>, ) -> Self::State { let mut state: PermissionTest = initial_state; if let Some(args) = arguments { let mut args = args.clone(); for var in &interp.curr_body.get().unwrap().vars { - match var { - VarKind::Arg(defid_new) => { - let defid_old = args.pop(); - if let Some(def_id_old_unwrapped) = defid_old { - self.variables_from_defid.insert( - defid_new.clone(), - self.variables_from_defid[&def_id_old_unwrapped.clone()].clone(), - ); + if let VarKind::Arg(defid_new) = var { + if let Some(operand) = args.pop() { + match operand { + Operand::Var(variable) => match variable.base { + Base::Var(varid) => { + let varkind = &interp.curr_body.get().unwrap().vars[varid]; + if let Some(defid) = get_varid_from_defid(varkind) { + if let Some(value) = + self.variables_from_defid.get(&defid.clone()) + { + self.variables_from_defid + .insert(defid_new.clone(), value.clone()); + } + } + } + _ => {} + }, + Operand::Lit(lit) => { + if let Some(lit_value) = convert_lit_to_raw(&lit) { + let value = Value::Const(Const::Literal(lit_value)); + self.variables_from_defid + .insert(defid_new.clone(), value.clone()); + } + } } } - _ => {} } } } - /* collecting the variables that were used :) */ for (stmt, inst) in block.iter().enumerate() { let loc = Location::new(bb, stmt as u32); state = self.transfer_inst(interp, def, loc, block, inst, state); - match inst { Inst::Assign(variable, rvalue) => match variable.base { Base::Var(varid) => { - let var_kind = &interp.curr_body.get().unwrap().vars[varid]; - match var_kind { - VarKind::LocalDef(defid) => { - self.add_variable(interp, defid, rvalue); - } - VarKind::GlobalRef(defid) => { - self.add_variable(interp, defid, rvalue); - } - VarKind::Arg(defid) => { - self.add_variable(interp, defid, rvalue); - } - VarKind::Temp { parent } => { - if let Some(defid) = parent { - self.add_variable(interp, defid, rvalue); - } - } - _ => {} + let varkind = &interp.curr_body.get().unwrap().vars[varid]; + if let Some(defid) = get_varid_from_defid(varkind) { + self.add_variable(interp, &defid, rvalue); } } _ => {} @@ -852,7 +816,7 @@ impl<'cx> Dataflow<'cx> for PermisisionDataflow { def: DefId, block: &'cx BasicBlock, state: Self::State, - worklist: &mut WorkList, + worklist: &mut WorkList, ) { self.super_join_term(interp, def, block, state, worklist); for (def, arguments) in self.needs_call.drain(..) { @@ -889,17 +853,21 @@ fn get_varid_from_defid(varkind: &VarKind) -> Option { fn convert_operand_to_raw(operand: &Operand) -> Option { if let Operand::Lit(lit) = operand { - match lit { - Literal::BigInt(bigint) => Some(bigint.to_string()), - Literal::Number(num) => Some(num.to_string()), - Literal::Str(str) => Some(str.to_string()), - _ => None, - } + convert_lit_to_raw(lit) } else { None } } +fn convert_lit_to_raw(lit: &Literal) -> Option { + match lit { + Literal::BigInt(bigint) => Some(bigint.to_string()), + Literal::Number(num) => Some(num.to_string()), + Literal::Str(str) => Some(str.to_string()), + _ => None, + } +} + fn find_member_of_obj(member: &str, obj: &Class) -> Option { for (mem, memdefid) in &obj.pub_members { if mem == member { diff --git a/crates/forge_analyzer/src/interp.rs b/crates/forge_analyzer/src/interp.rs index aa3bc30a..1d965e85 100644 --- a/crates/forge_analyzer/src/interp.rs +++ b/crates/forge_analyzer/src/interp.rs @@ -139,7 +139,7 @@ pub trait Dataflow<'cx>: Sized { bb: BasicBlockId, block: &'cx BasicBlock, initial_state: Self::State, - arguments: Option>, + arguments: Option>, ) -> Self::State { let mut state = initial_state; for (stmt, inst) in block.iter().enumerate() { @@ -172,7 +172,7 @@ pub trait Dataflow<'cx>: Sized { def: DefId, block: &'cx BasicBlock, state: Self::State, - worklist: &mut WorkList, + worklist: &mut WorkList, ) { self.super_join_term(interp, def, block, state, worklist); } @@ -183,7 +183,7 @@ pub trait Dataflow<'cx>: Sized { def: DefId, block: &'cx BasicBlock, state: Self::State, - worklist: &mut WorkList, + worklist: &mut WorkList, ) { match block.successors() { Successors::Return => { diff --git a/crates/forge_analyzer/src/permission_checker.rs b/crates/forge_analyzer/src/permission_checker.rs deleted file mode 100644 index e69de29b..00000000 diff --git a/crates/forge_analyzer/src/permission_classifier.rs b/crates/forge_analyzer/src/permission_classifier.rs new file mode 100644 index 00000000..ddbb6e57 --- /dev/null +++ b/crates/forge_analyzer/src/permission_classifier.rs @@ -0,0 +1,281 @@ +pub(crate) fn check_permission_used( + function_name: &str, + first_arg: &String, + second_arg: Option<&Expr>, +) -> Vec { + let mut used_permissions: Vec = Vec::new(); + + let joined_args = first_arg; + + let post_call = joined_args.contains("POST"); + let delete_call = joined_args.contains("DELTE"); + let put_call = joined_args.contains("PUT"); + + let contains_audit = joined_args.contains("audit"); + let contains_issue = joined_args.contains("issue"); + let contains_content = joined_args.contains("content"); + let contains_user = joined_args.contains("user"); + let contains_theme = joined_args.contains("theme"); + let contains_template = joined_args.contains("template"); + let contains_space = joined_args.contains("space"); + let contains_analytics = joined_args.contains("analytics"); + let contains_cql = joined_args.contains("cql"); + let contains_attachment = joined_args.contains("attachment"); + let contains_contentbody = joined_args.contains("contentbody"); + let contians_permissions = joined_args.contains("permissions"); + let contains_property = joined_args.contains("property"); + let contains_page_tree = joined_args.contains("pageTree"); + let contains_group = joined_args.contains("group"); + let contains_inlinetasks = joined_args.contains("inlinetasks"); + let contains_relation = joined_args.contains("relation"); + let contains_settings = joined_args.contains("settings"); + let contains_permission = joined_args.contains("permission"); + let contains_download = joined_args.contains("download"); + let contains_descendants = joined_args.contains("descendants"); + let contains_comment = joined_args.contains("comment"); + let contains_label = joined_args.contains("contains_label"); + let contains_search = joined_args.contains("contains_search"); + let contains_longtask = joined_args.contains("contains_longtask"); + let contains_notification = joined_args.contains("notification"); + let contains_watch = joined_args.contains("watch"); + let contains_version = joined_args.contains("version"); + let contains_state = joined_args.contains("contains_state"); + let contains_available = joined_args.contains("available"); + let contains_announcement_banner = joined_args.contains("announcementBanner"); + let contains_avatar = joined_args.contains("avatar"); + let contains_size = joined_args.contains("size"); + let contains_dashboard = joined_args.contains("dashboard"); + let contains_gadget = joined_args.contains("gadget"); + let contains_filter = joined_args.contains("filter"); + let contains_tracking = joined_args.contains("tracking"); + let contains_groupuserpicker = joined_args.contains("groupuserpicker"); + let contains_workflow = joined_args.contains("workflow"); + let contains_status = joined_args.contains("status"); + let contains_task = joined_args.contains("task"); + let contains_screen = joined_args.contains("screen"); + let non_get_call = post_call || delete_call || put_call; + let contains_webhook = joined_args.contains("webhook"); + let contains_project = joined_args.contains("project"); + let contains_actor = joined_args.contains("actor"); + let contains_role = joined_args.contains("contains_role"); + let contains_project_validate = joined_args.contains("projectvalidate"); + let contains_email = joined_args.contains("email"); + let contains_notification_scheme = joined_args.contains("notificationscheme"); + let contains_priority = joined_args.contains("priority"); + let contains_properties = joined_args.contains("properties"); + let contains_remote_link = joined_args.contains("remotelink"); + let contains_resolution = joined_args.contains("resolution"); + let contains_security_level = joined_args.contains("securitylevel"); + let contains_issue_security_schemes = joined_args.contains("issuesecurityschemes"); + let contains_issue_type = joined_args.contains("issuetype"); + let contains_issue_type_schemes = joined_args.contains("issuetypescheme"); + let contains_votes = joined_args.contains("contains_votes"); + let contains_worklog = joined_args.contains("worklog"); + let contains_expression = joined_args.contains("expression"); + let contains_configuration = joined_args.contains("configuration"); + let contains_application_properties = joined_args.contains("application-properties"); + + match function_name { + "requestJira" => { + if (contains_dashboard && non_get_call) + || (contains_user && non_get_call) + || contains_task + { + used_permissions.push(ForgePermissions::WriteJiraWork); + if contains_gadget { + used_permissions.push(ForgePermissions::ReadJiraWork) + } + } else if contains_expression { + used_permissions.push(ForgePermissions::ReadJiraUser); + used_permissions.push(ForgePermissions::ReadJiraUser) + } else if (contains_avatar && contains_size) + || contains_dashboard + || contains_status + || contains_groupuserpicker + { + used_permissions.push(ForgePermissions::ReadJiraWork) + } else if (!non_get_call && contains_user) || contains_configuration { + used_permissions.push(ForgePermissions::ReadJiraUser) + } else if contains_webhook { + used_permissions.push(ForgePermissions::ManageJiraWebhook); + used_permissions.push(ForgePermissions::ReadJiraWork) + } else if (contains_remote_link && non_get_call) + || (contains_issue && contains_votes && non_get_call) + || (contains_worklog && non_get_call) + { + used_permissions.push(ForgePermissions::WriteJiraWork) + } else if (contains_issue_type && non_get_call) + || (contains_issue_type && non_get_call) + || (contains_project && non_get_call) + || (contains_project && contains_actor) + || (contains_project && contains_role) + || (contains_project && contains_email) + || (contains_priority && (non_get_call || contains_search)) + || (contains_properties && contains_issue && non_get_call) + || (contains_resolution && non_get_call) + || contains_audit + || contains_avatar + || contains_workflow + || contains_tracking + || contains_status + || contains_screen + || contains_notification_scheme + || contains_security_level + || contains_issue_security_schemes + || contains_issue_type_schemes + || contains_announcement_banner + || contains_application_properties + { + used_permissions.push(ForgePermissions::ManageJiraConfiguration) + } else if contains_filter { + if non_get_call { + used_permissions.push(ForgePermissions::WriteJiraWork) + } else { + used_permissions.push(ForgePermissions::ReadJiraWork) + } + } else if contains_project + || contains_project_validate + || contains_priority + || contains_search + || contains_issue_type + || (contains_issue && contains_votes) + || (contains_properties && contains_issue) + || (contains_remote_link && !non_get_call) + || (contains_resolution && !non_get_call) + || contains_worklog + { + used_permissions.push(ForgePermissions::ReadJiraWork) + } else if post_call { + if contains_issue { + used_permissions.push(ForgePermissions::WriteJiraWork); + } else { + used_permissions.push(ForgePermissions::Unknown); + } + } else { + if contains_issue { + used_permissions.push(ForgePermissions::ReadJiraWork); + } else { + used_permissions.push(ForgePermissions::Unknown); + } + } + } + + // bit flags + "requestConfluence" => { + if non_get_call { + if contains_content { + used_permissions.push(ForgePermissions::WriteConfluenceContent); + } else if contains_audit { + used_permissions.push(ForgePermissions::WriteAuditLogsConfluence); + if post_call { + used_permissions.push(ForgePermissions::ReadAuditLogsConfluence); + } + } else if contains_content && contains_attachment { + if put_call { + // review this more specifically + // /wiki/rest/api/content/{id}/child/attachment/{attachmentId}`, + used_permissions.push(ForgePermissions::WriteConfluenceFile); + used_permissions.push(ForgePermissions::WriteConfluenceProps) + } else { + used_permissions.push(ForgePermissions::WriteConfluenceFile) + } + } else if contains_contentbody { + used_permissions.push(ForgePermissions::ReadConfluenceContentAll) + } else if contains_content && contians_permissions { + used_permissions.push(ForgePermissions::ReadConfluenceContentPermission) + } else if contains_property { + used_permissions.push(ForgePermissions::WriteConfluenceProps) + } else if contains_content + || contains_page_tree + || contains_relation + || contains_template + { + used_permissions.push(ForgePermissions::WriteConfluenceContent) + } else if contains_group { + used_permissions.push(ForgePermissions::WriteConfluenceGroups) + } else if contains_settings { + used_permissions.push(ForgePermissions::ManageConfluenceConfiguration) + } else if contains_space && contains_permission { + if !delete_call { + used_permissions.push(ForgePermissions::ReadSpacePermissionConfluence); + } + used_permissions.push(ForgePermissions::WriteSpacePermissionsConfluence) + } else if contains_space || contains_theme { + used_permissions.push(ForgePermissions::WriteConfluenceSpace); + } else if contains_inlinetasks { + used_permissions.push(ForgePermissions::WriteInlineTaskConfluence) + } else if contains_user && contains_property { + used_permissions.push(ForgePermissions::WriteUserPropertyConfluence); + } else { + used_permissions.push(ForgePermissions::Unknown); + } + } else { + if contains_issue { + used_permissions.push(ForgePermissions::ReadJiraWork); + } else if contains_audit { + used_permissions.push(ForgePermissions::ReadAuditLogsConfluence) + } else if contains_cql { + if contains_user { + used_permissions.push(ForgePermissions::ReadContentDetailsConfluence); + } else { + used_permissions.push(ForgePermissions::SearchConfluence); + } + } else if contains_attachment && contains_download { + used_permissions.push(ForgePermissions::ReadOnlyContentAttachmentConfluence) + } else if contains_longtask { + used_permissions.push(ForgePermissions::ReadContentMetadataConfluence); + used_permissions.push(ForgePermissions::ReadConfluenceSpaceSummary) + } else if contains_content && contains_property { + used_permissions.push(ForgePermissions::ReadConfluenceProps); + } else if contains_template + || contains_relation + || (contains_content + && (contains_comment || contains_descendants || contains_label)) + { + used_permissions.push(ForgePermissions::ReadConfluenceContentSummary) + } else if contains_space && contains_settings { + used_permissions.push(ForgePermissions::ReadConfluenceSpaceSummary) + } else if contains_space && contains_theme { + used_permissions.push(ForgePermissions::ManageConfluenceConfiguration) + } else if contains_space && contains_content && contains_state { + used_permissions.push(ForgePermissions::ReadConfluenceContentAll) + } else if contains_space && contains_content { + used_permissions.push(ForgePermissions::ReadConfluenceContentSummary) + } else if contains_state && contains_content && contains_available { + used_permissions.push(ForgePermissions::WriteConfluenceContent) + } else if contains_content + && (contains_notification + || contains_watch + || contains_version + || contains_state) + { + used_permissions.push(ForgePermissions::ReadConfluenceContentSummary) + } else if contains_space { + used_permissions.push(ForgePermissions::ReadConfluenceProps) + } else if contains_content || contains_analytics { + used_permissions.push(ForgePermissions::ReadConfluenceContentAll) + } else if contains_user && contains_property { + used_permissions.push(ForgePermissions::WriteUserPropertyConfluence) + } else if contains_settings { + used_permissions.push(ForgePermissions::ManageConfluenceConfiguration) + } else if contains_search { + used_permissions.push(ForgePermissions::ReadContentDetailsConfluence) + } else if contains_space { + used_permissions.push(ForgePermissions::ReadConfluenceSpaceSummary) + } else if contains_user { + used_permissions.push(ForgePermissions::ReadConfluenceUser) + } else if contains_label { + used_permissions.push(ForgePermissions::ReadConfluenceContentSummary) + } else if contains_inlinetasks { + used_permissions.push(ForgePermissions::ReadConfluenceContentAll); + } else { + used_permissions.push(ForgePermissions::Unknown); + } + } + } + _ => { + used_permissions.push(ForgePermissions::Unknown); + } + } + used_permissions +} \ No newline at end of file diff --git a/crates/forge_analyzer/src/worklist.rs b/crates/forge_analyzer/src/worklist.rs index 37f452c6..fdf0a6b3 100644 --- a/crates/forge_analyzer/src/worklist.rs +++ b/crates/forge_analyzer/src/worklist.rs @@ -5,16 +5,16 @@ use tracing::debug; use crate::{ definitions::{DefId, Environment}, - ir::BasicBlockId, + ir::{BasicBlockId, Operand}, }; #[derive(Debug, Clone)] -pub struct WorkList { - worklist: VecDeque<(V, W, Vec)>, +pub struct WorkList { + worklist: VecDeque<(V, W, Vec)>, visited: FxHashSet, } -impl WorkList +impl WorkList where V: Eq + Hash, { @@ -27,7 +27,7 @@ where } #[inline] - pub fn pop_front(&mut self) -> (Option<(V, W, Vec)>) { + pub fn pop_front(&mut self) -> (Option<(V, W, Vec)>) { self.worklist.pop_front() } @@ -56,30 +56,30 @@ where } } -impl WorkList +impl WorkList where V: Eq + Hash + Copy, { #[inline] - pub fn push_back(&mut self, v: V, w: W, args: Vec) { + pub fn push_back(&mut self, v: V, w: W, args: Vec) { if self.visited.insert(v) { self.worklist.push_back((v, w, args)); } } #[inline] - pub fn push_back_force(&mut self, v: V, w: W, args: Vec) { + pub fn push_back_force(&mut self, v: V, w: W, args: Vec) { self.worklist.push_back((v, w, args)); } } -impl WorkList { +impl WorkList { #[inline] pub(crate) fn push_front_blocks( &mut self, env: &Environment, def: DefId, - arguments: Vec, + arguments: Vec, ) -> bool { if self.visited.insert(def) { debug!("adding function: {}", env.def_name(def)); @@ -97,12 +97,12 @@ impl WorkList { } } -impl Extend<(V, W, Vec)> for WorkList +impl Extend<(V, W, Vec)> for WorkList where V: Eq + Hash, { #[inline] - fn extend)>>(&mut self, iter: T) { + fn extend)>>(&mut self, iter: T) { self.worklist.extend(iter); } } diff --git a/test-apps/jira-damn-vulnerable-forge-app/src/index.jsx b/test-apps/jira-damn-vulnerable-forge-app/src/index.jsx index 157616ff..9103d0cb 100755 --- a/test-apps/jira-damn-vulnerable-forge-app/src/index.jsx +++ b/test-apps/jira-damn-vulnerable-forge-app/src/index.jsx @@ -96,7 +96,7 @@ function SecureGlance() { return ''; } const [flagVal] = useState(async () => { - const issueData = await fetchIssueSummary("test_value_replicated"); + const issueData = await fetchIssueSummary(platformContext.issueKey); return JSON.stringify(issueData); }); From d3d67b3dadc79463ff7da61a784bf3f19f675522 Mon Sep 17 00:00:00 2001 From: James Gersbach Date: Sun, 25 Jun 2023 22:36:48 -0700 Subject: [PATCH 080/517] correclty resolving permissions --- Cargo.lock | 1 + crates/forge_analyzer/Cargo.toml | 1 + crates/forge_analyzer/src/checkers.rs | 190 +++++++++++++----- crates/forge_analyzer/src/definitions.rs | 10 +- crates/forge_analyzer/src/engine.rs | 1 + crates/forge_analyzer/src/interp.rs | 18 +- crates/forge_analyzer/src/ir.rs | 16 +- crates/forge_analyzer/src/lib.rs | 1 + ..._classifier.rs => permissionclassifier.rs} | 148 +++++++------- crates/forge_analyzer/src/reporter.rs | 4 + crates/forge_loader/src/manifest.rs | 10 +- crates/fsrt/src/main.rs | 14 +- .../src/index.jsx | 3 + 13 files changed, 266 insertions(+), 151 deletions(-) rename crates/forge_analyzer/src/{permission_classifier.rs => permissionclassifier.rs} (71%) diff --git a/Cargo.lock b/Cargo.lock index a0412ab5..ea3105d8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -525,6 +525,7 @@ version = "0.1.0" dependencies = [ "fixedbitset", "forge_file_resolver", + "forge_loader", "forge_utils", "itertools", "num-bigint", diff --git a/crates/forge_analyzer/Cargo.toml b/crates/forge_analyzer/Cargo.toml index 130f7fae..f4695687 100644 --- a/crates/forge_analyzer/Cargo.toml +++ b/crates/forge_analyzer/Cargo.toml @@ -11,6 +11,7 @@ license.workspace = true fixedbitset.workspace = true forge_file_resolver.workspace = true forge_utils.workspace = true +forge_loader.workspace = true itertools.workspace = true num-bigint.workspace = true once_cell.workspace = true diff --git a/crates/forge_analyzer/src/checkers.rs b/crates/forge_analyzer/src/checkers.rs index ac0ac9ee..76a73e06 100644 --- a/crates/forge_analyzer/src/checkers.rs +++ b/crates/forge_analyzer/src/checkers.rs @@ -1,8 +1,9 @@ use core::fmt; +use forge_loader::forgepermissions::ForgePermissions; use forge_utils::FxHashMap; use itertools::Itertools; use smallvec::SmallVec; -use std::{cmp::max, iter, mem, ops::ControlFlow, path::PathBuf}; +use std::{cmp::max, collections::HashSet, iter, mem, ops::ControlFlow, path::PathBuf}; use tracing::{debug, info, warn}; @@ -12,9 +13,10 @@ use crate::{ Checker, Dataflow, EntryKind, EntryPoint, Frame, Interp, JoinSemiLattice, WithCallStack, }, ir::{ - Base, BasicBlock, BasicBlockId, Inst, Intrinsic, Literal, Location, Operand, Rvalue, VarId, - VarKind, + Base, BasicBlock, BasicBlockId, Inst, Intrinsic, Literal, Location, Operand, Rvalue, + Successors, VarId, VarKind, }, + permissionclassifier::check_permission_used, reporter::{IntoVuln, Reporter, Severity, Vulnerability}, worklist::WorkList, }; @@ -55,7 +57,7 @@ impl<'cx> Dataflow<'cx> for AuthorizeDataflow { fn transfer_intrinsic>( &mut self, - _interp: &Interp<'cx, C>, + _interp: &mut Interp<'cx, C>, _def: DefId, _loc: Location, _block: &'cx BasicBlock, @@ -64,13 +66,13 @@ impl<'cx> Dataflow<'cx> for AuthorizeDataflow { operands: SmallVec<[crate::ir::Operand; 4]>, ) -> Self::State { match *intrinsic { - Intrinsic::Authorize => { + Intrinsic::Authorize(_) => { debug!("authorize intrinsic found"); AuthorizeState::Yes } Intrinsic::Fetch => initial_state, - Intrinsic::ApiCall => initial_state, - Intrinsic::SafeCall => initial_state, + Intrinsic::ApiCall(_) => initial_state, + Intrinsic::SafeCall(_) => initial_state, Intrinsic::EnvRead => initial_state, Intrinsic::StorageRead => initial_state, } @@ -201,6 +203,7 @@ impl IntoVuln for AuthZVuln { app_key: reporter.app_key().to_owned(), app_name: reporter.app_name().to_owned(), date: reporter.current_date(), + unused_permissions: None, } } } @@ -222,19 +225,19 @@ impl<'cx> Checker<'cx> for AuthZChecker { operands: Option>, ) -> ControlFlow<(), Self::State> { match *intrinsic { - Intrinsic::Authorize => { + Intrinsic::Authorize(_) => { debug!("authorize intrinsic found"); ControlFlow::Continue(AuthorizeState::Yes) } Intrinsic::Fetch => ControlFlow::Continue(*state), - Intrinsic::ApiCall if *state == AuthorizeState::No => { + Intrinsic::ApiCall(_) if *state == AuthorizeState::No => { let vuln = AuthZVuln::new(interp.callstack(), interp.env(), interp.entry()); info!("Found a vuln!"); self.vulns.push(vuln); ControlFlow::Continue(*state) } - Intrinsic::ApiCall => ControlFlow::Continue(*state), - Intrinsic::SafeCall => ControlFlow::Continue(*state), + Intrinsic::ApiCall(_) => ControlFlow::Continue(*state), + Intrinsic::SafeCall(_) => ControlFlow::Continue(*state), Intrinsic::EnvRead => ControlFlow::Continue(*state), Intrinsic::StorageRead => ControlFlow::Continue(*state), } @@ -277,7 +280,7 @@ impl<'cx> Dataflow<'cx> for AuthenticateDataflow { fn transfer_intrinsic>( &mut self, - _interp: &Interp<'cx, C>, + _interp: &mut Interp<'cx, C>, _def: DefId, _loc: Location, _block: &'cx BasicBlock, @@ -286,13 +289,13 @@ impl<'cx> Dataflow<'cx> for AuthenticateDataflow { operands: SmallVec<[crate::ir::Operand; 4]>, ) -> Self::State { match *intrinsic { - Intrinsic::Authorize => initial_state, + Intrinsic::Authorize(_) => initial_state, Intrinsic::Fetch | Intrinsic::EnvRead | Intrinsic::StorageRead => { debug!("authenticated"); Authenticated::Yes } - Intrinsic::ApiCall => initial_state, - Intrinsic::SafeCall => initial_state, + Intrinsic::ApiCall(_) => initial_state, + Intrinsic::SafeCall(_) => initial_state, } } @@ -374,19 +377,19 @@ impl<'cx> Checker<'cx> for AuthenticateChecker { operands: Option>, ) -> ControlFlow<(), Self::State> { match *intrinsic { - Intrinsic::Authorize => ControlFlow::Continue(*state), + Intrinsic::Authorize(_) => ControlFlow::Continue(*state), Intrinsic::Fetch | Intrinsic::EnvRead | Intrinsic::StorageRead => { debug!("authenticated"); ControlFlow::Continue(Authenticated::Yes) } - Intrinsic::ApiCall if *state == Authenticated::No => { + Intrinsic::ApiCall(_) if *state == Authenticated::No => { let vuln = AuthNVuln::new(interp.callstack(), interp.env(), interp.entry()); info!("Found a vuln!"); self.vulns.push(vuln); ControlFlow::Continue(*state) } - Intrinsic::ApiCall => ControlFlow::Continue(*state), - Intrinsic::SafeCall => ControlFlow::Continue(*state), + Intrinsic::ApiCall(_) => ControlFlow::Continue(*state), + Intrinsic::SafeCall(_) => ControlFlow::Continue(*state), } } } @@ -450,6 +453,7 @@ impl IntoVuln for AuthNVuln { app_key: reporter.app_key().to_owned(), app_name: reporter.app_name().to_owned(), date: reporter.current_date(), + unused_permissions: None, } } } @@ -458,9 +462,8 @@ impl WithCallStack for AuthNVuln { fn add_call_stack(&mut self, _stack: Vec) {} } -pub struct PermisisionDataflow { +pub struct PermissionDataflow { needs_call: Vec<(DefId, Vec)>, - variables: FxHashMap, variables_from_defid: FxHashMap, } @@ -468,7 +471,20 @@ impl WithCallStack for PermissionVuln { fn add_call_stack(&mut self, _stack: Vec) {} } -impl<'cx> Dataflow<'cx> for PermisisionDataflow { +#[derive(Debug, Default, Clone)] +struct IntrinsicArguments { + name: Option, + first_arg: Option>, + second_arg: Option>, +} + +#[derive(Debug, Clone, Copy)] +pub enum IntrinsicName { + RequestConfluence, + RequestJira, +} + +impl<'cx> Dataflow<'cx> for PermissionDataflow { type State = PermissionTest; fn with_interp>( @@ -476,14 +492,13 @@ impl<'cx> Dataflow<'cx> for PermisisionDataflow { ) -> Self { Self { needs_call: vec![], - variables: FxHashMap::default(), variables_from_defid: FxHashMap::default(), } } fn transfer_intrinsic>( &mut self, - _interp: &Interp<'cx, C>, + _interp: &mut Interp<'cx, C>, _def: DefId, _loc: Location, _block: &'cx BasicBlock, @@ -491,24 +506,31 @@ impl<'cx> Dataflow<'cx> for PermisisionDataflow { initial_state: Self::State, operands: SmallVec<[crate::ir::Operand; 4]>, ) -> Self::State { - println!("operands {operands:?}"); - match *intrinsic { - Intrinsic::ApiCall | Intrinsic::SafeCall | Intrinsic::Authorize => { + let mut intrinsic_argument = IntrinsicArguments::default(); + + match &*intrinsic { + Intrinsic::ApiCall(value) + | Intrinsic::SafeCall(value) + | Intrinsic::Authorize(value) => { + intrinsic_argument.name = Some(value.clone()); let (first, second) = (operands.get(0), operands.get(1)); if let Some(operand) = first { match operand { Operand::Lit(lit) => { - println!("lit from first operand {:?}", lit); + intrinsic_argument.first_arg = Some(vec![lit.to_string()]); } Operand::Var(var) => match var.base { Base::Var(varid) => { let varkind = &_interp.curr_body.get().unwrap().vars[varid]; let defid = get_varid_from_defid(&varkind); if let Some(defid) = defid { - println!( - "actual value {:?}", - self.variables_from_defid.get(&defid) - ); + if let Some(value) = self.variables_from_defid.get(&defid) { + intrinsic_argument.first_arg = Some(vec![]); + add_elements_to_intrinsic_struct( + value, + &mut intrinsic_argument.first_arg, + ); + } } } _ => {} @@ -517,9 +539,7 @@ impl<'cx> Dataflow<'cx> for PermisisionDataflow { } if let Some(operand) = second { match operand { - Operand::Lit(lit) => { - println!("lit {:?}", lit); - } + Operand::Lit(_) => {} Operand::Var(var) => { if let Base::Var(varid) = var.base { match _interp.curr_body.get().unwrap().vars[varid].clone() { @@ -530,9 +550,17 @@ impl<'cx> Dataflow<'cx> for PermisisionDataflow { if let Const::Object(obj) = const_var { let defid = find_member_of_obj("method", obj); if let Some(defid) = defid { - let value = - self.variables_from_defid.get(&defid); - println!("value of method {value:?}"); + if let Some(value) = + self.variables_from_defid.get(&defid) + { + println!("value of method {value:?}"); + intrinsic_argument.second_arg = + Some(vec![]); + add_elements_to_intrinsic_struct( + value, + &mut intrinsic_argument.second_arg, + ); + } } } } @@ -545,8 +573,16 @@ impl<'cx> Dataflow<'cx> for PermisisionDataflow { if let Some(obj) = class { let defid = find_member_of_obj("method", &obj); if let Some(defid) = defid { - let value = self.variables_from_defid.get(&defid); - println!("value of method {value:?}"); + if let Some(value) = + self.variables_from_defid.get(&defid) + { + intrinsic_argument.second_arg = Some(vec![]); + add_elements_to_intrinsic_struct( + value, + &mut intrinsic_argument.second_arg, + ); + } + // println!("value of method {value:?}"); } } // @@ -560,11 +596,43 @@ impl<'cx> Dataflow<'cx> for PermisisionDataflow { } _ => {} } + + let mut all_permissions_used: Vec = vec![]; + let function_name = if intrinsic_argument.name.unwrap() == String::from("requestJira") { + IntrinsicName::RequestJira + } else { + IntrinsicName::RequestConfluence + }; + + intrinsic_argument + .first_arg + .iter() + .for_each(|first_arg_vec| { + intrinsic_argument + .second_arg + .iter() + .for_each(|second_arg_vec| { + first_arg_vec.iter().for_each(|first_arg| { + second_arg_vec.iter().for_each(|second_arg| { + let permissions = check_permission_used( + function_name, + first_arg, + Some(second_arg), + ); + println!("permissions {:?}", permissions); + all_permissions_used.extend_from_slice(&permissions); + }) + }) + }) + }); + + // check_permission_used() + match *intrinsic { - Intrinsic::Authorize => initial_state, + Intrinsic::Authorize(_) => initial_state, Intrinsic::Fetch => initial_state, - Intrinsic::ApiCall => initial_state, - Intrinsic::SafeCall => initial_state, + Intrinsic::ApiCall(_) => initial_state, + Intrinsic::SafeCall(_) => initial_state, Intrinsic::EnvRead => initial_state, Intrinsic::StorageRead => initial_state, } @@ -610,14 +678,14 @@ impl<'cx> Dataflow<'cx> for PermisisionDataflow { fn transfer_block>( &mut self, - interp: &Interp<'cx, C>, + interp: &mut Interp<'cx, C>, def: DefId, bb: BasicBlockId, block: &'cx BasicBlock, initial_state: Self::State, arguments: Option>, ) -> Self::State { - let mut state: PermissionTest = initial_state; + let mut state = initial_state; if let Some(args) = arguments { let mut args = args.clone(); @@ -877,13 +945,35 @@ fn find_member_of_obj(member: &str, obj: &Class) -> Option { None } +fn add_elements_to_intrinsic_struct(value: &Value, args: &mut Option>) { + match value { + Value::Const(const_value) => { + if let Const::Literal(literal) = const_value { + args.as_mut().unwrap().push(literal.clone()); + } + } + Value::Phi(phi_value) => { + for value in phi_value { + if let Const::Literal(literal) = value { + args.as_mut().unwrap().push(literal.clone()); + } + } + } + _ => {} + } +} + pub struct PermissionChecker { vulns: Vec, + declared_permissions: HashSet, } impl PermissionChecker { - pub fn new() -> Self { - Self { vulns: vec![] } + pub fn new(declared_permissions: HashSet) -> Self { + Self { + vulns: vec![], + declared_permissions, + } } pub fn into_vulns(self) -> impl IntoIterator { @@ -893,7 +983,7 @@ impl PermissionChecker { impl Default for PermissionChecker { fn default() -> Self { - Self::new() + Self::new(HashSet::new()) } } @@ -925,7 +1015,7 @@ impl JoinSemiLattice for PermissionTest { impl<'cx> Checker<'cx> for PermissionChecker { type State = PermissionTest; - type Dataflow = PermisisionDataflow; + type Dataflow = PermissionDataflow; type Vuln = PermissionVuln; fn visit_intrinsic( @@ -935,6 +1025,7 @@ impl<'cx> Checker<'cx> for PermissionChecker { state: &Self::State, operands: Option>, ) -> ControlFlow<(), Self::State> { + println!("visitng intrsinsic"); ControlFlow::Continue(*state) } } @@ -965,6 +1056,7 @@ impl IntoVuln for PermissionVuln { app_key: reporter.app_key().to_string(), app_name: reporter.app_name().to_string(), date: reporter.current_date(), + unused_permissions: None, } } } diff --git a/crates/forge_analyzer/src/definitions.rs b/crates/forge_analyzer/src/definitions.rs index 0a0936b4..8557e653 100644 --- a/crates/forge_analyzer/src/definitions.rs +++ b/crates/forge_analyzer/src/definitions.rs @@ -801,13 +801,13 @@ impl<'cx> FunctionAnalyzer<'cx> { match classify_api_call(first_arg) { ApiCallKind::Unknown => { if authn.first() == Some(&PropPath::MemberCall("asApp".into())) { - Some(Intrinsic::ApiCall) + Some(Intrinsic::ApiCall(last.to_string())) } else { - Some(Intrinsic::SafeCall) + Some(Intrinsic::SafeCall(last.to_string())) } } - ApiCallKind::Trivial => Some(Intrinsic::SafeCall), - ApiCallKind::Authorize => Some(Intrinsic::Authorize), + ApiCallKind::Trivial => Some(Intrinsic::SafeCall(last.to_string())), + ApiCallKind::Authorize => Some(Intrinsic::Authorize(last.to_string())), } } [PropPath::Def(def), PropPath::Static(ref s), ..] if is_storage_read(s) => { @@ -820,7 +820,7 @@ impl<'cx> FunctionAnalyzer<'cx> { } [PropPath::Def(def), ..] => match self.res.as_foreign_import(def, "@forge/api") { Some(ImportKind::Named(ref name)) if *name == *"authorize" => { - Some(Intrinsic::Authorize) + Some(Intrinsic::Authorize(String::from(""))) } _ => None, }, diff --git a/crates/forge_analyzer/src/engine.rs b/crates/forge_analyzer/src/engine.rs index 65655ef5..43204fba 100644 --- a/crates/forge_analyzer/src/engine.rs +++ b/crates/forge_analyzer/src/engine.rs @@ -133,6 +133,7 @@ impl<'ctx> Machine<'ctx> { let result = self.app.func_res(&orig_func); info!(?result, "analysis complete"); let fname: &str = &orig_func.ident.0; + println!("Result of analyzing {fname}:"); match result { AuthZVal::Unauthorized => { println!("FAIL: Unauthorized call detected from handler: {fname}") diff --git a/crates/forge_analyzer/src/interp.rs b/crates/forge_analyzer/src/interp.rs index 1d965e85..76f6088b 100644 --- a/crates/forge_analyzer/src/interp.rs +++ b/crates/forge_analyzer/src/interp.rs @@ -1,4 +1,5 @@ use std::{ + borrow::BorrowMut, cell::{Cell, RefCell, RefMut}, collections::BTreeMap, fmt::{self, Display}, @@ -9,6 +10,7 @@ use std::{ path::PathBuf, }; +use forge_loader::forgepermissions::ForgePermissions; use forge_utils::{FxHashMap, FxHashSet}; use smallvec::SmallVec; use swc_core::ecma::atoms::JsWord; @@ -56,7 +58,7 @@ pub trait Dataflow<'cx>: Sized { fn transfer_intrinsic>( &mut self, - interp: &Interp<'cx, C>, + interp: &mut Interp<'cx, C>, def: DefId, loc: Location, block: &'cx BasicBlock, @@ -79,7 +81,7 @@ pub trait Dataflow<'cx>: Sized { fn transfer_rvalue>( &mut self, - interp: &Interp<'cx, C>, + interp: &mut Interp<'cx, C>, def: DefId, loc: Location, block: &'cx BasicBlock, @@ -115,7 +117,7 @@ pub trait Dataflow<'cx>: Sized { fn transfer_inst>( &mut self, - interp: &Interp<'cx, C>, + interp: &mut Interp<'cx, C>, def: DefId, loc: Location, block: &'cx BasicBlock, @@ -134,7 +136,7 @@ pub trait Dataflow<'cx>: Sized { fn transfer_block>( &mut self, - interp: &Interp<'cx, C>, + interp: &mut Interp<'cx, C>, def: DefId, bb: BasicBlockId, block: &'cx BasicBlock, @@ -377,6 +379,7 @@ pub struct Interp<'cx, C: Checker<'cx>> { checker_visited: RefCell>, callstack: RefCell>, vulns: RefCell>, + pub permissions: Vec, _checker: PhantomData, } @@ -448,6 +451,7 @@ impl<'cx, C: Checker<'cx>> Interp<'cx, C> { checker_visited: RefCell::new(FxHashSet::default()), callstack: RefCell::new(Vec::new()), vulns: RefCell::new(Vec::new()), + permissions: Vec::default(), _checker: PhantomData, } } @@ -458,12 +462,12 @@ impl<'cx, C: Checker<'cx>> Interp<'cx, C> { } #[inline] - fn body(&self) -> &'cx Body { + pub fn body(&self) -> &'cx Body { self.curr_body.get().unwrap() } #[inline] - fn set_body(&self, body: &'cx Body) { + pub fn set_body(&self, body: &'cx Body) { self.curr_body.set(Some(body)); } @@ -482,7 +486,7 @@ impl<'cx, C: Checker<'cx>> Interp<'cx, C> { } #[inline] - fn block_state(&self, def: DefId, block: BasicBlockId) -> C::State { + pub fn block_state(&self, def: DefId, block: BasicBlockId) -> C::State { self.states .borrow() .get(&(def, block)) diff --git a/crates/forge_analyzer/src/ir.rs b/crates/forge_analyzer/src/ir.rs index a9ddb020..148331e6 100644 --- a/crates/forge_analyzer/src/ir.rs +++ b/crates/forge_analyzer/src/ir.rs @@ -64,12 +64,12 @@ pub enum Terminator { }, } -#[derive(Clone, Debug, Copy, PartialEq, Eq)] +#[derive(Clone, Debug, PartialEq, Eq)] pub enum Intrinsic { - Authorize, + Authorize(String), Fetch, - ApiCall, - SafeCall, + ApiCall(String), + SafeCall(String), EnvRead, StorageRead, } @@ -126,6 +126,7 @@ pub struct Body { owner: Option, pub blocks: TiVec, pub vars: TiVec, + pub values: FxHashMap, ident_to_local: FxHashMap, pub def_id_to_vars: FxHashMap, predecessors: OnceCell>>, @@ -270,6 +271,7 @@ impl Body { vars: local_vars, owner: None, blocks: vec![BasicBlock::default()].into(), + values: FxHashMap::default(), ident_to_local: Default::default(), def_id_to_vars: Default::default(), predecessors: Default::default(), @@ -742,9 +744,9 @@ impl fmt::Display for Intrinsic { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match *self { Intrinsic::Fetch => write!(f, "fetch"), - Intrinsic::Authorize => write!(f, "authorize"), - Intrinsic::ApiCall => write!(f, "api call"), - Intrinsic::SafeCall => write!(f, "safe api call"), + Intrinsic::Authorize(_) => write!(f, "authorize"), + Intrinsic::ApiCall(_) => write!(f, "api call"), + Intrinsic::SafeCall(_) => write!(f, "safe api call"), Intrinsic::EnvRead => write!(f, "env read"), Intrinsic::StorageRead => write!(f, "forge storage read"), } diff --git a/crates/forge_analyzer/src/lib.rs b/crates/forge_analyzer/src/lib.rs index 58f004a9..4baa5042 100644 --- a/crates/forge_analyzer/src/lib.rs +++ b/crates/forge_analyzer/src/lib.rs @@ -7,6 +7,7 @@ pub mod exports; pub mod interp; pub mod ir; pub mod lattice; +pub mod permissionclassifier; pub mod pretty; pub mod reporter; pub mod resolver; diff --git a/crates/forge_analyzer/src/permission_classifier.rs b/crates/forge_analyzer/src/permissionclassifier.rs similarity index 71% rename from crates/forge_analyzer/src/permission_classifier.rs rename to crates/forge_analyzer/src/permissionclassifier.rs index ddbb6e57..e3934916 100644 --- a/crates/forge_analyzer/src/permission_classifier.rs +++ b/crates/forge_analyzer/src/permissionclassifier.rs @@ -1,82 +1,84 @@ +use crate::checkers::IntrinsicName; +use core::fmt; +use forge_loader::forgepermissions::ForgePermissions; + pub(crate) fn check_permission_used( - function_name: &str, + function_name: IntrinsicName, first_arg: &String, - second_arg: Option<&Expr>, + second_arg: Option<&String>, ) -> Vec { let mut used_permissions: Vec = Vec::new(); - let joined_args = first_arg; - - let post_call = joined_args.contains("POST"); - let delete_call = joined_args.contains("DELTE"); - let put_call = joined_args.contains("PUT"); + let post_call = second_arg.unwrap_or(&String::from("")).contains("POST"); + let delete_call = second_arg.unwrap_or(&String::from("")).contains("DELTE"); + let put_call = second_arg.unwrap_or(&String::from("")).contains("PUT"); - let contains_audit = joined_args.contains("audit"); - let contains_issue = joined_args.contains("issue"); - let contains_content = joined_args.contains("content"); - let contains_user = joined_args.contains("user"); - let contains_theme = joined_args.contains("theme"); - let contains_template = joined_args.contains("template"); - let contains_space = joined_args.contains("space"); - let contains_analytics = joined_args.contains("analytics"); - let contains_cql = joined_args.contains("cql"); - let contains_attachment = joined_args.contains("attachment"); - let contains_contentbody = joined_args.contains("contentbody"); - let contians_permissions = joined_args.contains("permissions"); - let contains_property = joined_args.contains("property"); - let contains_page_tree = joined_args.contains("pageTree"); - let contains_group = joined_args.contains("group"); - let contains_inlinetasks = joined_args.contains("inlinetasks"); - let contains_relation = joined_args.contains("relation"); - let contains_settings = joined_args.contains("settings"); - let contains_permission = joined_args.contains("permission"); - let contains_download = joined_args.contains("download"); - let contains_descendants = joined_args.contains("descendants"); - let contains_comment = joined_args.contains("comment"); - let contains_label = joined_args.contains("contains_label"); - let contains_search = joined_args.contains("contains_search"); - let contains_longtask = joined_args.contains("contains_longtask"); - let contains_notification = joined_args.contains("notification"); - let contains_watch = joined_args.contains("watch"); - let contains_version = joined_args.contains("version"); - let contains_state = joined_args.contains("contains_state"); - let contains_available = joined_args.contains("available"); - let contains_announcement_banner = joined_args.contains("announcementBanner"); - let contains_avatar = joined_args.contains("avatar"); - let contains_size = joined_args.contains("size"); - let contains_dashboard = joined_args.contains("dashboard"); - let contains_gadget = joined_args.contains("gadget"); - let contains_filter = joined_args.contains("filter"); - let contains_tracking = joined_args.contains("tracking"); - let contains_groupuserpicker = joined_args.contains("groupuserpicker"); - let contains_workflow = joined_args.contains("workflow"); - let contains_status = joined_args.contains("status"); - let contains_task = joined_args.contains("task"); - let contains_screen = joined_args.contains("screen"); + let contains_audit = first_arg.contains("audit"); + let contains_issue = first_arg.contains("issue"); + let contains_content = first_arg.contains("content"); + let contains_user = first_arg.contains("user"); + let contains_theme = first_arg.contains("theme"); + let contains_template = first_arg.contains("template"); + let contains_space = first_arg.contains("space"); + let contains_analytics = first_arg.contains("analytics"); + let contains_cql = first_arg.contains("cql"); + let contains_attachment = first_arg.contains("attachment"); + let contains_contentbody = first_arg.contains("contentbody"); + let contians_permissions = first_arg.contains("permissions"); + let contains_property = first_arg.contains("property"); + let contains_page_tree = first_arg.contains("pageTree"); + let contains_group = first_arg.contains("group"); + let contains_inlinetasks = first_arg.contains("inlinetasks"); + let contains_relation = first_arg.contains("relation"); + let contains_settings = first_arg.contains("settings"); + let contains_permission = first_arg.contains("permission"); + let contains_download = first_arg.contains("download"); + let contains_descendants = first_arg.contains("descendants"); + let contains_comment = first_arg.contains("comment"); + let contains_label = first_arg.contains("contains_label"); + let contains_search = first_arg.contains("contains_search"); + let contains_longtask = first_arg.contains("contains_longtask"); + let contains_notification = first_arg.contains("notification"); + let contains_watch = first_arg.contains("watch"); + let contains_version = first_arg.contains("version"); + let contains_state = first_arg.contains("contains_state"); + let contains_available = first_arg.contains("available"); + let contains_announcement_banner = first_arg.contains("announcementBanner"); + let contains_avatar = first_arg.contains("avatar"); + let contains_size = first_arg.contains("size"); + let contains_dashboard = first_arg.contains("dashboard"); + let contains_gadget = first_arg.contains("gadget"); + let contains_filter = first_arg.contains("filter"); + let contains_tracking = first_arg.contains("tracking"); + let contains_groupuserpicker = first_arg.contains("groupuserpicker"); + let contains_workflow = first_arg.contains("workflow"); + let contains_status = first_arg.contains("status"); + let contains_task = first_arg.contains("task"); + let contains_screen = first_arg.contains("screen"); let non_get_call = post_call || delete_call || put_call; - let contains_webhook = joined_args.contains("webhook"); - let contains_project = joined_args.contains("project"); - let contains_actor = joined_args.contains("actor"); - let contains_role = joined_args.contains("contains_role"); - let contains_project_validate = joined_args.contains("projectvalidate"); - let contains_email = joined_args.contains("email"); - let contains_notification_scheme = joined_args.contains("notificationscheme"); - let contains_priority = joined_args.contains("priority"); - let contains_properties = joined_args.contains("properties"); - let contains_remote_link = joined_args.contains("remotelink"); - let contains_resolution = joined_args.contains("resolution"); - let contains_security_level = joined_args.contains("securitylevel"); - let contains_issue_security_schemes = joined_args.contains("issuesecurityschemes"); - let contains_issue_type = joined_args.contains("issuetype"); - let contains_issue_type_schemes = joined_args.contains("issuetypescheme"); - let contains_votes = joined_args.contains("contains_votes"); - let contains_worklog = joined_args.contains("worklog"); - let contains_expression = joined_args.contains("expression"); - let contains_configuration = joined_args.contains("configuration"); - let contains_application_properties = joined_args.contains("application-properties"); + let contains_webhook = first_arg.contains("webhook"); + let contains_project = first_arg.contains("project"); + let contains_actor = first_arg.contains("actor"); + let contains_role = first_arg.contains("contains_role"); + let contains_project_validate = first_arg.contains("projectvalidate"); + let contains_email = first_arg.contains("email"); + let contains_notification_scheme = first_arg.contains("notificationscheme"); + let contains_priority = first_arg.contains("priority"); + let contains_properties = first_arg.contains("properties"); + let contains_remote_link = first_arg.contains("remotelink"); + let contains_resolution = first_arg.contains("resolution"); + let contains_security_level = first_arg.contains("securitylevel"); + let contains_issue_security_schemes = first_arg.contains("issuesecurityschemes"); + let contains_issue_type = first_arg.contains("issuetype"); + let contains_issue_type_schemes = first_arg.contains("issuetypescheme"); + let contains_votes = first_arg.contains("contains_votes"); + let contains_worklog = first_arg.contains("worklog"); + let contains_expression = first_arg.contains("expression"); + let contains_configuration = first_arg.contains("configuration"); + let contains_application_properties = first_arg.contains("application-properties"); match function_name { - "requestJira" => { + IntrinsicName::RequestJira => { if (contains_dashboard && non_get_call) || (contains_user && non_get_call) || contains_task @@ -159,9 +161,7 @@ pub(crate) fn check_permission_used( } } } - - // bit flags - "requestConfluence" => { + IntrinsicName::RequestConfluence => { if non_get_call { if contains_content { used_permissions.push(ForgePermissions::WriteConfluenceContent); @@ -278,4 +278,4 @@ pub(crate) fn check_permission_used( } } used_permissions -} \ No newline at end of file +} diff --git a/crates/forge_analyzer/src/reporter.rs b/crates/forge_analyzer/src/reporter.rs index 0fb853a4..b2fdc1e6 100644 --- a/crates/forge_analyzer/src/reporter.rs +++ b/crates/forge_analyzer/src/reporter.rs @@ -1,4 +1,6 @@ +use forge_loader::forgepermissions::ForgePermissions; use serde::Serialize; +use std::collections::HashSet; use time::{Date, OffsetDateTime}; #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)] @@ -14,6 +16,8 @@ pub enum Severity { pub struct Vulnerability { pub(crate) check_name: String, pub(crate) description: String, + #[serde(skip_serializing_if = "Option::is_none")] + pub(crate) unused_permissions: Option>, pub(crate) recommendation: &'static str, pub(crate) proof: String, pub(crate) severity: Severity, diff --git a/crates/forge_loader/src/manifest.rs b/crates/forge_loader/src/manifest.rs index cd415c53..ef31be88 100644 --- a/crates/forge_loader/src/manifest.rs +++ b/crates/forge_loader/src/manifest.rs @@ -5,7 +5,7 @@ use std::{ path::{Path, PathBuf}, }; -use crate::Error; +use crate::{forgepermissions::ForgePermissions, Error}; use forge_utils::FxHashMap; use itertools::{Either, Itertools}; use serde::Deserialize; @@ -107,9 +107,9 @@ struct Content<'a> { } #[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] -struct Perms<'a> { - #[serde(default, borrow)] - scopes: Vec<&'a str>, +pub struct Perms<'a> { + #[serde(default)] + pub scopes: Vec, #[serde(default, borrow)] content: Content<'a>, } @@ -142,7 +142,7 @@ pub struct ForgeManifest<'a> { #[serde(borrow)] pub modules: ForgeModules<'a>, #[serde(borrow)] - permissions: Perms<'a>, + pub permissions: Perms<'a>, } #[derive(Debug, PartialEq, Eq, Clone, Copy)] diff --git a/crates/fsrt/src/main.rs b/crates/fsrt/src/main.rs index 3b00fe66..73298c51 100644 --- a/crates/fsrt/src/main.rs +++ b/crates/fsrt/src/main.rs @@ -1,4 +1,7 @@ #![allow(clippy::type_complexity)] +use clap::{Parser, ValueHint}; +use forge_loader::forgepermissions::ForgePermissions; +use miette::{IntoDiagnostic, Result}; use std::{ collections::HashSet, convert::TryFrom, @@ -8,9 +11,6 @@ use std::{ sync::Arc, }; -use clap::{Parser, ValueHint}; -use miette::{IntoDiagnostic, Result}; - use swc_core::{ common::{Globals, Mark, SourceMap, GLOBALS}, ecma::{ @@ -170,10 +170,16 @@ fn scan_directory(dir: PathBuf, function: Option<&str>, opts: Opts) -> Result = + HashSet::from_iter(permission_scopes.iter().cloned()); + let paths = collect_sourcefiles(dir.join("src/")).collect::>(); let funcrefs = manifest.modules.into_analyzable_functions().flat_map(|f| { f.sequence(|fmod| { @@ -205,7 +211,7 @@ fn scan_directory(dir: PathBuf, function: Option<&str>, opts: Opts) -> Result { + let test_values = "should not be seen"; const issueData = await fetchIssueSummary(platformContext.issueKey); + test_values = "should not be seen 2"; + // const issueData2 = await fetchIssueSummary(platformContext.issueKey); return JSON.stringify(issueData); }); From e90b9b25671d2003b48d7e04e6cfe89073ad9f39 Mon Sep 17 00:00:00 2001 From: James Gersbach Date: Sun, 25 Jun 2023 22:39:26 -0700 Subject: [PATCH 081/517] cleaning up examples --- .../src/index.jsx | 3 --- .../src/utils.js | 20 ------------------- 2 files changed, 23 deletions(-) diff --git a/test-apps/jira-damn-vulnerable-forge-app/src/index.jsx b/test-apps/jira-damn-vulnerable-forge-app/src/index.jsx index 2c181d59..9103d0cb 100755 --- a/test-apps/jira-damn-vulnerable-forge-app/src/index.jsx +++ b/test-apps/jira-damn-vulnerable-forge-app/src/index.jsx @@ -96,10 +96,7 @@ function SecureGlance() { return ''; } const [flagVal] = useState(async () => { - let test_values = "should not be seen"; const issueData = await fetchIssueSummary(platformContext.issueKey); - test_values = "should not be seen 2"; - // const issueData2 = await fetchIssueSummary(platformContext.issueKey); return JSON.stringify(issueData); }); diff --git a/test-apps/jira-damn-vulnerable-forge-app/src/utils.js b/test-apps/jira-damn-vulnerable-forge-app/src/utils.js index 28c650aa..2ae0527e 100755 --- a/test-apps/jira-damn-vulnerable-forge-app/src/utils.js +++ b/test-apps/jira-damn-vulnerable-forge-app/src/utils.js @@ -9,26 +9,6 @@ export async function fetchIssueSummary(issueIdOrKey) { }, }; - let a = "test_a_value"; - - a = "test_a_value_new"; - - let b = a; - - // issueIdOrKey = "new_test" - - // let issueIdOrKey = "issueIdOrKey_value"; - // let issueIdOrKey = "issueIdOrKey_value2"; - - let obj2 = { - method: 'POST', - headers: { - Accept: 'application/json', - }, - } - - obj2.method = "GET"; - let a_url = route`/rest/api/3/issue/${issueIdOrKey}?fields=summary`; const resp = await api From 307cfb660f20eadc735df89683a81669f7824bfe Mon Sep 17 00:00:00 2001 From: James Gersbach Date: Tue, 27 Jun 2023 08:41:32 -0700 Subject: [PATCH 082/517] fix bug with adding arguments --- crates/forge_analyzer/src/checkers.rs | 62 +++++++------------ crates/forge_analyzer/src/interp.rs | 3 +- .../src/permissionclassifier.rs | 5 +- crates/forge_analyzer/src/reporter.rs | 2 - crates/fsrt/src/main.rs | 3 + .../src/index.jsx | 2 +- .../src/utils.js | 9 ++- 7 files changed, 36 insertions(+), 50 deletions(-) diff --git a/crates/forge_analyzer/src/checkers.rs b/crates/forge_analyzer/src/checkers.rs index 76a73e06..bc53002b 100644 --- a/crates/forge_analyzer/src/checkers.rs +++ b/crates/forge_analyzer/src/checkers.rs @@ -203,7 +203,6 @@ impl IntoVuln for AuthZVuln { app_key: reporter.app_key().to_owned(), app_name: reporter.app_name().to_owned(), date: reporter.current_date(), - unused_permissions: None, } } } @@ -453,7 +452,6 @@ impl IntoVuln for AuthNVuln { app_key: reporter.app_key().to_owned(), app_name: reporter.app_name().to_owned(), date: reporter.current_date(), - unused_permissions: None, } } } @@ -597,7 +595,7 @@ impl<'cx> Dataflow<'cx> for PermissionDataflow { _ => {} } - let mut all_permissions_used: Vec = vec![]; + let mut permissions_within_call: Vec = vec![]; let function_name = if intrinsic_argument.name.unwrap() == String::from("requestJira") { IntrinsicName::RequestJira } else { @@ -620,13 +618,15 @@ impl<'cx> Dataflow<'cx> for PermissionDataflow { Some(second_arg), ); println!("permissions {:?}", permissions); - all_permissions_used.extend_from_slice(&permissions); + permissions_within_call.extend_from_slice(&permissions); }) }) }) }); - // check_permission_used() + _interp + .permissions + .extend_from_slice(&permissions_within_call); match *intrinsic { Intrinsic::Authorize(_) => initial_state, @@ -687,34 +687,16 @@ impl<'cx> Dataflow<'cx> for PermissionDataflow { ) -> Self::State { let mut state = initial_state; + let mut function_var = interp.curr_body.get().unwrap().vars.clone(); + function_var.pop(); + if let Some(args) = arguments { let mut args = args.clone(); - for var in &interp.curr_body.get().unwrap().vars { + args.reverse(); + for var in function_var { if let VarKind::Arg(defid_new) = var { if let Some(operand) = args.pop() { - match operand { - Operand::Var(variable) => match variable.base { - Base::Var(varid) => { - let varkind = &interp.curr_body.get().unwrap().vars[varid]; - if let Some(defid) = get_varid_from_defid(varkind) { - if let Some(value) = - self.variables_from_defid.get(&defid.clone()) - { - self.variables_from_defid - .insert(defid_new.clone(), value.clone()); - } - } - } - _ => {} - }, - Operand::Lit(lit) => { - if let Some(lit_value) = convert_lit_to_raw(&lit) { - let value = Value::Const(Const::Literal(lit_value)); - self.variables_from_defid - .insert(defid_new.clone(), value.clone()); - } - } - } + self.insert_value(&operand, &defid_new, interp, None); } } } @@ -851,9 +833,8 @@ impl<'cx> Dataflow<'cx> for PermissionDataflow { Operand::Var(var) => { match var.base { Base::Var(var_id) => { - let something = &interp.curr_body.get().unwrap().vars[var_id]; - if let VarKind::LocalDef(local_defid) = something { - /* add objects to the variables */ + let varkind = &interp.curr_body.get().unwrap().vars[var_id]; + if let VarKind::LocalDef(local_defid) = varkind { if let Some(class) = self.read_class_from_object(interp, local_defid.clone()) { @@ -964,8 +945,8 @@ fn add_elements_to_intrinsic_struct(value: &Value, args: &mut Option } pub struct PermissionChecker { - vulns: Vec, - declared_permissions: HashSet, + pub vulns: Vec, + pub declared_permissions: HashSet, } impl PermissionChecker { @@ -976,7 +957,7 @@ impl PermissionChecker { } } - pub fn into_vulns(self) -> impl IntoIterator { + pub fn into_vulns(self) -> impl IntoIterator { self.vulns.into_iter() } } @@ -1026,18 +1007,21 @@ impl<'cx> Checker<'cx> for PermissionChecker { operands: Option>, ) -> ControlFlow<(), Self::State> { println!("visitng intrsinsic"); + for permission in &interp.permissions { + self.declared_permissions.remove(permission); + } ControlFlow::Continue(*state) } } #[derive(Debug)] pub struct PermissionVuln { - // unused_permissions: HashSet, + unused_permissions: HashSet, } impl PermissionVuln { - pub fn new(/*unused_permissions: HashSet */) -> PermissionVuln { - PermissionVuln { /*unused_permissions*/ } + pub fn new(unused_permissions: HashSet) -> PermissionVuln { + PermissionVuln { unused_permissions } } } @@ -1049,14 +1033,12 @@ impl IntoVuln for PermissionVuln { "Unused permissions listed in manifest file:.", // self.unused_permissions.into_iter().join(", ") ), - // unused_permissions: Some(self.unused_permissions), recommendation: "Remove permissions in manifest file that are not needed.", proof: format!("Unused permissions found in manifest.yml"), severity: Severity::Low, app_key: reporter.app_key().to_string(), app_name: reporter.app_name().to_string(), date: reporter.current_date(), - unused_permissions: None, } } } diff --git a/crates/forge_analyzer/src/interp.rs b/crates/forge_analyzer/src/interp.rs index 76f6088b..31c4a588 100644 --- a/crates/forge_analyzer/src/interp.rs +++ b/crates/forge_analyzer/src/interp.rs @@ -451,7 +451,7 @@ impl<'cx, C: Checker<'cx>> Interp<'cx, C> { checker_visited: RefCell::new(FxHashSet::default()), callstack: RefCell::new(Vec::new()), vulns: RefCell::new(Vec::new()), - permissions: Vec::default(), + permissions: Vec::new(), _checker: PhantomData, } } @@ -561,7 +561,6 @@ impl<'cx, C: Checker<'cx>> Interp<'cx, C> { let mut before_state = self.block_state(def, block_id); let block = func.block(block_id); for &pred in func.predecessors(block_id) { - let block_ = func.block(pred); before_state = before_state.join(&self.block_state(def, pred)); } let state = diff --git a/crates/forge_analyzer/src/permissionclassifier.rs b/crates/forge_analyzer/src/permissionclassifier.rs index e3934916..af755b78 100644 --- a/crates/forge_analyzer/src/permissionclassifier.rs +++ b/crates/forge_analyzer/src/permissionclassifier.rs @@ -1,11 +1,10 @@ use crate::checkers::IntrinsicName; -use core::fmt; use forge_loader::forgepermissions::ForgePermissions; pub(crate) fn check_permission_used( function_name: IntrinsicName, - first_arg: &String, - second_arg: Option<&String>, + first_arg: &str, + second_arg: Option<&str>, ) -> Vec { let mut used_permissions: Vec = Vec::new(); diff --git a/crates/forge_analyzer/src/reporter.rs b/crates/forge_analyzer/src/reporter.rs index b2fdc1e6..1b993d7c 100644 --- a/crates/forge_analyzer/src/reporter.rs +++ b/crates/forge_analyzer/src/reporter.rs @@ -16,8 +16,6 @@ pub enum Severity { pub struct Vulnerability { pub(crate) check_name: String, pub(crate) description: String, - #[serde(skip_serializing_if = "Option::is_none")] - pub(crate) unused_permissions: Option>, pub(crate) recommendation: &'static str, pub(crate) proof: String, pub(crate) severity: Severity, diff --git a/crates/fsrt/src/main.rs b/crates/fsrt/src/main.rs index 73298c51..988c5149 100644 --- a/crates/fsrt/src/main.rs +++ b/crates/fsrt/src/main.rs @@ -217,6 +217,9 @@ fn scan_directory(dir: PathBuf, function: Option<&str>, opts: Opts) -> Result 0 { + // checker2.vulns.push(value); + } reporter.add_vulnerabilities(checker2.into_vulns()); } FunctionTy::WebTrigger((ref func, ref path, _, def)) => { diff --git a/test-apps/jira-damn-vulnerable-forge-app/src/index.jsx b/test-apps/jira-damn-vulnerable-forge-app/src/index.jsx index 9103d0cb..5107f2bf 100755 --- a/test-apps/jira-damn-vulnerable-forge-app/src/index.jsx +++ b/test-apps/jira-damn-vulnerable-forge-app/src/index.jsx @@ -96,7 +96,7 @@ function SecureGlance() { return ''; } const [flagVal] = useState(async () => { - const issueData = await fetchIssueSummary(platformContext.issueKey); + const issueData = await fetchIssueSummary(platformContext.issueKey, "/rest/api/3/issue/${issueIdOrKey}?fields=summary"); return JSON.stringify(issueData); }); diff --git a/test-apps/jira-damn-vulnerable-forge-app/src/utils.js b/test-apps/jira-damn-vulnerable-forge-app/src/utils.js index 2ae0527e..62bf788e 100755 --- a/test-apps/jira-damn-vulnerable-forge-app/src/utils.js +++ b/test-apps/jira-damn-vulnerable-forge-app/src/utils.js @@ -1,6 +1,6 @@ import api, { route } from '@forge/api'; -export async function fetchIssueSummary(issueIdOrKey) { +export async function fetchIssueSummary(issueIdOrKey, url) { let obj = { method: 'POST', @@ -13,7 +13,12 @@ export async function fetchIssueSummary(issueIdOrKey) { const resp = await api .asApp() - .requestJira(route`/rest/api/3/issue/${issueIdOrKey}?fields=summary`, obj2); + .requestJira(url, { + method: 'POST', + headers: { + Accept: 'application/json', + }, + }); const data = await resp.json(); console.log(JSON.stringify(data)); return data['fields']['summary']; From 19837ff12ecc934338acf0d802072fde162094d3 Mon Sep 17 00:00:00 2001 From: James Gersbach Date: Tue, 27 Jun 2023 09:52:15 -0700 Subject: [PATCH 083/517] fix to the worklist --- crates/forge_analyzer/src/checkers.rs | 15 ++++++++------- crates/forge_analyzer/src/interp.rs | 25 ++++++++++++++----------- crates/forge_analyzer/src/worklist.rs | 26 +++++++++++++------------- 3 files changed, 35 insertions(+), 31 deletions(-) diff --git a/crates/forge_analyzer/src/checkers.rs b/crates/forge_analyzer/src/checkers.rs index bc53002b..9c4cc826 100644 --- a/crates/forge_analyzer/src/checkers.rs +++ b/crates/forge_analyzer/src/checkers.rs @@ -110,11 +110,11 @@ impl<'cx> Dataflow<'cx> for AuthorizeDataflow { fn join_term>( &mut self, - interp: &Interp<'cx, C>, + interp: &mut Interp<'cx, C>, def: DefId, block: &'cx BasicBlock, state: Self::State, - worklist: &mut WorkList, + worklist: &mut WorkList, ) { self.super_join_term(interp, def, block, state, worklist); for def in self.needs_call.drain(..) { @@ -330,11 +330,11 @@ impl<'cx> Dataflow<'cx> for AuthenticateDataflow { fn join_term>( &mut self, - interp: &Interp<'cx, C>, + interp: &mut Interp<'cx, C>, def: DefId, block: &'cx BasicBlock, state: Self::State, - worklist: &mut WorkList, + worklist: &mut WorkList, ) { self.super_join_term(interp, def, block, state, worklist); for def in self.needs_call.drain(..) { @@ -861,15 +861,16 @@ impl<'cx> Dataflow<'cx> for PermissionDataflow { fn join_term>( &mut self, - interp: &Interp<'cx, C>, + interp: &mut Interp<'cx, C>, def: DefId, block: &'cx BasicBlock, state: Self::State, - worklist: &mut WorkList, + worklist: &mut WorkList, ) { self.super_join_term(interp, def, block, state, worklist); for (def, arguments) in self.needs_call.drain(..) { - worklist.push_front_blocks(interp.env(), def, arguments); + worklist.push_front_blocks(interp.env(), def, arguments.clone()); + interp.callstack_arguments.push(arguments.clone()); } } } diff --git a/crates/forge_analyzer/src/interp.rs b/crates/forge_analyzer/src/interp.rs index 31c4a588..21fd6d71 100644 --- a/crates/forge_analyzer/src/interp.rs +++ b/crates/forge_analyzer/src/interp.rs @@ -170,22 +170,22 @@ pub trait Dataflow<'cx>: Sized { fn join_term>( &mut self, - interp: &Interp<'cx, C>, + interp: &mut Interp<'cx, C>, def: DefId, block: &'cx BasicBlock, state: Self::State, - worklist: &mut WorkList, + worklist: &mut WorkList, ) { - self.super_join_term(interp, def, block, state, worklist); + self.super_join_term(interp.borrow_mut(), def, block, state, worklist); } fn super_join_term>( &mut self, - interp: &Interp<'cx, C>, + interp: &mut Interp<'cx, C>, def: DefId, block: &'cx BasicBlock, state: Self::State, - worklist: &mut WorkList, + worklist: &mut WorkList, ) { match block.successors() { Successors::Return => { @@ -199,7 +199,7 @@ pub trait Dataflow<'cx>: Sized { debug!("{name} {def:?} is called from {calls:?}"); for &(def, loc) in calls { if worklist.visited(&def) { - worklist.push_back_force(def, loc.block, vec![]); + worklist.push_back_force(def, loc.block); } } } @@ -207,15 +207,15 @@ pub trait Dataflow<'cx>: Sized { Successors::One(succ) => { let mut succ_state = interp.block_state_mut(def, succ); if succ_state.join_changed(&state) { - worklist.push_back(def, succ, vec![]); + worklist.push_back(def, succ); } } Successors::Two(succ1, succ2) => { if interp.block_state_mut(def, succ1).join_changed(&state) { - worklist.push_back(def, succ1, vec![]); + worklist.push_back(def, succ1); } if interp.block_state_mut(def, succ2).join_changed(&state) { - worklist.push_back(def, succ2, vec![]); + worklist.push_back(def, succ2); } } } @@ -378,6 +378,7 @@ pub struct Interp<'cx, C: Checker<'cx>> { dataflow_visited: FxHashSet, checker_visited: RefCell>, callstack: RefCell>, + pub callstack_arguments: Vec>, vulns: RefCell>, pub permissions: Vec, _checker: PhantomData, @@ -449,6 +450,7 @@ impl<'cx, C: Checker<'cx>> Interp<'cx, C> { states: RefCell::new(BTreeMap::new()), dataflow_visited: FxHashSet::default(), checker_visited: RefCell::new(FxHashSet::default()), + callstack_arguments: Vec::new(), callstack: RefCell::new(Vec::new()), vulns: RefCell::new(Vec::new()), permissions: Vec::new(), @@ -551,7 +553,8 @@ impl<'cx, C: Checker<'cx>> Interp<'cx, C> { let mut worklist = WorkList::new(); worklist.push_front_blocks(self.env, func_def, vec![]); let old_body = self.curr_body.get(); - while let Some((def, block_id, args)) = worklist.pop_front() { + while let Some((def, block_id)) = worklist.pop_front() { + let arguments = self.callstack_arguments.pop(); let name = self.env.def_name(def); debug!("Dataflow: {name} - {block_id}"); self.dataflow_visited.insert(def); @@ -564,7 +567,7 @@ impl<'cx, C: Checker<'cx>> Interp<'cx, C> { before_state = before_state.join(&self.block_state(def, pred)); } let state = - dataflow.transfer_block(self, def, block_id, block, before_state, Some(args)); + dataflow.transfer_block(self, def, block_id, block, before_state, arguments); dataflow.join_term(self, def, block, state, &mut worklist); } self.curr_body.set(old_body); diff --git a/crates/forge_analyzer/src/worklist.rs b/crates/forge_analyzer/src/worklist.rs index fdf0a6b3..ebac9206 100644 --- a/crates/forge_analyzer/src/worklist.rs +++ b/crates/forge_analyzer/src/worklist.rs @@ -9,12 +9,12 @@ use crate::{ }; #[derive(Debug, Clone)] -pub struct WorkList { - worklist: VecDeque<(V, W, Vec)>, +pub struct WorkList { + worklist: VecDeque<(V, W)>, visited: FxHashSet, } -impl WorkList +impl WorkList where V: Eq + Hash, { @@ -27,7 +27,7 @@ where } #[inline] - pub fn pop_front(&mut self) -> (Option<(V, W, Vec)>) { + pub fn pop_front(&mut self) -> (Option<(V, W)>) { self.worklist.pop_front() } @@ -56,24 +56,24 @@ where } } -impl WorkList +impl WorkList where V: Eq + Hash + Copy, { #[inline] - pub fn push_back(&mut self, v: V, w: W, args: Vec) { + pub fn push_back(&mut self, v: V, w: W) { if self.visited.insert(v) { - self.worklist.push_back((v, w, args)); + self.worklist.push_back((v, w)); } } #[inline] - pub fn push_back_force(&mut self, v: V, w: W, args: Vec) { - self.worklist.push_back((v, w, args)); + pub fn push_back_force(&mut self, v: V, w: W) { + self.worklist.push_back((v, w)); } } -impl WorkList { +impl WorkList { #[inline] pub(crate) fn push_front_blocks( &mut self, @@ -89,7 +89,7 @@ impl WorkList { for work in blocks { debug!(?work, "push_front_blocks"); self.worklist - .push_front((work.0, work.1, arguments.clone())); + .push_front((work.0, work.1)); } return true; } @@ -97,12 +97,12 @@ impl WorkList { } } -impl Extend<(V, W, Vec)> for WorkList +impl Extend<(V, W)> for WorkList where V: Eq + Hash, { #[inline] - fn extend)>>(&mut self, iter: T) { + fn extend>(&mut self, iter: T) { self.worklist.extend(iter); } } From c321b431c5222c354d3ad9722f765ffea8e83986 Mon Sep 17 00:00:00 2001 From: James Gersbach Date: Mon, 3 Jul 2023 08:17:47 -0700 Subject: [PATCH 084/517] converting defid to varid --- crates/forge_analyzer/src/checkers.rs | 505 +++++++++++------- crates/forge_analyzer/src/definitions.rs | 43 +- crates/forge_analyzer/src/interp.rs | 46 +- crates/forge_analyzer/src/ir.rs | 7 +- .../src/permissionclassifier.rs | 2 +- crates/forge_analyzer/src/worklist.rs | 12 +- crates/fsrt/src/main.rs | 25 +- .../src/GlobalPageApp.jsx | 1 + .../src/index.jsx | 5 +- .../src/utils.js | 2 +- 10 files changed, 411 insertions(+), 237 deletions(-) diff --git a/crates/forge_analyzer/src/checkers.rs b/crates/forge_analyzer/src/checkers.rs index 9c4cc826..cfb803ed 100644 --- a/crates/forge_analyzer/src/checkers.rs +++ b/crates/forge_analyzer/src/checkers.rs @@ -8,7 +8,7 @@ use std::{cmp::max, collections::HashSet, iter, mem, ops::ControlFlow, path::Pat use tracing::{debug, info, warn}; use crate::{ - definitions::{Class, Const, DefId, DefKind, Environment, Value}, + definitions::{Class, Const, DefId, DefKind, Environment, IntrinsicName, Value}, interp::{ Checker, Dataflow, EntryKind, EntryPoint, Frame, Interp, JoinSemiLattice, WithCallStack, }, @@ -118,7 +118,7 @@ impl<'cx> Dataflow<'cx> for AuthorizeDataflow { ) { self.super_join_term(interp, def, block, state, worklist); for def in self.needs_call.drain(..) { - worklist.push_front_blocks(interp.env(), def, vec![]); + worklist.push_front_blocks(interp.env(), def); } } } @@ -338,7 +338,7 @@ impl<'cx> Dataflow<'cx> for AuthenticateDataflow { ) { self.super_join_term(interp, def, block, state, worklist); for def in self.needs_call.drain(..) { - worklist.push_front_blocks(interp.env(), def, vec![]); + worklist.push_front_blocks(interp.env(), def); } } } @@ -461,8 +461,8 @@ impl WithCallStack for AuthNVuln { } pub struct PermissionDataflow { - needs_call: Vec<(DefId, Vec)>, - variables_from_defid: FxHashMap, + needs_call: Vec<(DefId, Vec, Vec)>, + varid_to_value: FxHashMap<(DefId, VarId), Value>, } impl WithCallStack for PermissionVuln { @@ -471,15 +471,32 @@ impl WithCallStack for PermissionVuln { #[derive(Debug, Default, Clone)] struct IntrinsicArguments { - name: Option, + name: Option, first_arg: Option>, second_arg: Option>, } -#[derive(Debug, Clone, Copy)] -pub enum IntrinsicName { - RequestConfluence, - RequestJira, +impl PermissionDataflow { + fn handle_first_arg( + &self, + operand: &Operand, + _def: DefId, + intrinsic_argument: &mut IntrinsicArguments, + ) { + match operand { + Operand::Lit(lit) => { + intrinsic_argument.first_arg = Some(vec![lit.to_string()]); + } + Operand::Var(var) => { + if let Base::Var(varid) = var.base { + if let Some(value) = self.varid_to_value.get(&(_def, varid)) { + intrinsic_argument.first_arg = Some(vec![]); + add_elements_to_intrinsic_struct(value, &mut intrinsic_argument.first_arg); + } + } + } + } + } } impl<'cx> Dataflow<'cx> for PermissionDataflow { @@ -490,7 +507,7 @@ impl<'cx> Dataflow<'cx> for PermissionDataflow { ) -> Self { Self { needs_call: vec![], - variables_from_defid: FxHashMap::default(), + varid_to_value: FxHashMap::default(), } } @@ -506,136 +523,94 @@ impl<'cx> Dataflow<'cx> for PermissionDataflow { ) -> Self::State { let mut intrinsic_argument = IntrinsicArguments::default(); - match &*intrinsic { - Intrinsic::ApiCall(value) - | Intrinsic::SafeCall(value) - | Intrinsic::Authorize(value) => { - intrinsic_argument.name = Some(value.clone()); - let (first, second) = (operands.get(0), operands.get(1)); - if let Some(operand) = first { - match operand { - Operand::Lit(lit) => { - intrinsic_argument.first_arg = Some(vec![lit.to_string()]); - } - Operand::Var(var) => match var.base { - Base::Var(varid) => { - let varkind = &_interp.curr_body.get().unwrap().vars[varid]; - let defid = get_varid_from_defid(&varkind); - if let Some(defid) = defid { - if let Some(value) = self.variables_from_defid.get(&defid) { - intrinsic_argument.first_arg = Some(vec![]); + if let Intrinsic::ApiCall(name) | Intrinsic::SafeCall(name) | Intrinsic::Authorize(name) = + intrinsic + { + intrinsic_argument.name = Some(name.clone()); + let (first, second) = (operands.get(0), operands.get(1)); + if let Some(operand) = first { + self.handle_first_arg(operand, _def, &mut intrinsic_argument); + } + if let Some(Operand::Var(var)) = second { + if let Base::Var(varid) = var.base { + if let Some(varkind) = _interp.body().vars.get(varid) { + if let Some(defid) = get_varid_from_defid(varkind) { + if let Some(DefKind::GlobalObj(objid)) = + _interp.env().defs.defs.get(defid) + { + if let Some(class) = _interp.env().defs.classes.get(objid.clone()) { + if let Some(val) = + self.read_mem_from_object(_interp, _def, class.clone()) + { + intrinsic_argument.second_arg = Some(vec![]); add_elements_to_intrinsic_struct( - value, - &mut intrinsic_argument.first_arg, + val, + &mut intrinsic_argument.second_arg, ); } } } - _ => {} - }, + } } - } - if let Some(operand) = second { - match operand { - Operand::Lit(_) => {} - Operand::Var(var) => { - if let Base::Var(varid) = var.base { - match _interp.curr_body.get().unwrap().vars[varid].clone() { - VarKind::GlobalRef(_def_id) => { - /* case where it is passed in as a variable */ - match &self.variables_from_defid.get(&_def_id).unwrap() { - Value::Const(const_var) => { - if let Const::Object(obj) = const_var { - let defid = find_member_of_obj("method", obj); - if let Some(defid) = defid { - if let Some(value) = - self.variables_from_defid.get(&defid) - { - println!("value of method {value:?}"); - intrinsic_argument.second_arg = - Some(vec![]); - add_elements_to_intrinsic_struct( - value, - &mut intrinsic_argument.second_arg, - ); - } - } - } - } - Value::Phi(phi_var) => {} - _ => {} - } - } - VarKind::LocalDef(_def_id) => { - let class = self.read_class_from_object(_interp, _def_id); - if let Some(obj) = class { - let defid = find_member_of_obj("method", &obj); - if let Some(defid) = defid { - if let Some(value) = - self.variables_from_defid.get(&defid) - { - intrinsic_argument.second_arg = Some(vec![]); - add_elements_to_intrinsic_struct( - value, - &mut intrinsic_argument.second_arg, - ); - } - // println!("value of method {value:?}"); - } - } - // - } - _ => {} + + /* case where it is passed in as a variable */ + if let Some(val) = &self.varid_to_value.get(&(_def, varid)) { + match val { + Value::Const(const_var) => { + if let Some(val) = self.try_read_mem_from_object( + _interp, + _def.clone(), + const_var.clone(), + ) { + intrinsic_argument.second_arg = Some(vec![]); + add_elements_to_intrinsic_struct( + val, + &mut intrinsic_argument.second_arg, + ); } } + Value::Phi(phi_var) => {} + _ => {} } } + } else { + println!("found other arg {var:?}") } } - _ => {} - } - - let mut permissions_within_call: Vec = vec![]; - let function_name = if intrinsic_argument.name.unwrap() == String::from("requestJira") { - IntrinsicName::RequestJira - } else { - IntrinsicName::RequestConfluence - }; - - intrinsic_argument - .first_arg - .iter() - .for_each(|first_arg_vec| { - intrinsic_argument - .second_arg - .iter() - .for_each(|second_arg_vec| { + let mut permissions_within_call: Vec = vec![]; + let intrinsic_func_type = intrinsic_argument.name.unwrap(); + intrinsic_argument + .first_arg + .iter() + .for_each(|first_arg_vec| { + if let Some(second_arg_vec) = intrinsic_argument.second_arg.clone() { first_arg_vec.iter().for_each(|first_arg| { second_arg_vec.iter().for_each(|second_arg| { let permissions = check_permission_used( - function_name, + intrinsic_func_type, first_arg, Some(second_arg), ); - println!("permissions {:?}", permissions); permissions_within_call.extend_from_slice(&permissions); }) }) - }) - }); + } else { + first_arg_vec.iter().for_each(|first_arg| { + let permissions = + check_permission_used(intrinsic_func_type, first_arg, None); + permissions_within_call.extend_from_slice(&permissions); + }) + } + }); - _interp - .permissions - .extend_from_slice(&permissions_within_call); + println!("intrinisc arg: {:?}", intrinsic_argument); - match *intrinsic { - Intrinsic::Authorize(_) => initial_state, - Intrinsic::Fetch => initial_state, - Intrinsic::ApiCall(_) => initial_state, - Intrinsic::SafeCall(_) => initial_state, - Intrinsic::EnvRead => initial_state, - Intrinsic::StorageRead => initial_state, + _interp + .permissions + .extend_from_slice(&permissions_within_call); } + + initial_state } fn read_class_from_object>( @@ -655,6 +630,42 @@ impl<'cx> Dataflow<'cx> for PermissionDataflow { None } + fn try_read_mem_from_object>( + &mut self, + _interp: &Interp<'cx, C>, + _def: DefId, + const_var: Const, + ) -> Option<&Value> { + if let Const::Object(obj) = const_var { + return self.read_mem_from_object(_interp, _def, obj); + } + None + } + + fn read_mem_from_object>( + &mut self, + _interp: &Interp<'cx, C>, + _def: DefId, + obj: Class, + ) -> Option<&Value> { + let defid_method = obj + .pub_members + .iter() + .filter(|(mem, _)| mem == "method") + .map(|(_, defid)| defid) + .collect_vec(); + if let Some(_alt_defid) = defid_method.get(0) { + for (varid_new, varkind) in _interp.body().vars.clone().into_iter_enumerated() { + if let Some(defid) = get_varid_from_defid(&varkind) { + if &&defid == _alt_defid { + return self.varid_to_value.get(&(_def, varid_new)); + } + } + } + } + None + } + fn transfer_call>( &mut self, interp: &Interp<'cx, C>, @@ -672,7 +683,33 @@ impl<'cx> Dataflow<'cx> for PermissionDataflow { let callee_name = interp.env().def_name(callee_def); let caller_name = interp.env().def_name(def); debug!("Found call to {callee_name} at {def:?} {caller_name}"); - self.needs_call.push((callee_def, operands.into_vec())); + + let mut all_values_to_be_pushed = vec![]; + + for operand in &operands { + match operand.clone() { + Operand::Lit(lit) => { + if let Some(lit_value) = convert_operand_to_raw(&operand.clone()) { + all_values_to_be_pushed.push(Value::Const(Const::Literal(lit_value))); + } else { + all_values_to_be_pushed.push(Value::Unknown) + } + } + Operand::Var(var) => match var.base { + Base::Var(varid) => { + if let Some(value) = self.varid_to_value.get(&(def, varid)) { + all_values_to_be_pushed.push(value.clone()); + } else { + all_values_to_be_pushed.push(Value::Unknown) + } + } + _ => all_values_to_be_pushed.push(Value::Unknown), + }, + } + } + + self.needs_call + .push((callee_def, operands.into_vec(), all_values_to_be_pushed)); initial_state } @@ -683,7 +720,7 @@ impl<'cx> Dataflow<'cx> for PermissionDataflow { bb: BasicBlockId, block: &'cx BasicBlock, initial_state: Self::State, - arguments: Option>, + arguments: Option>, ) -> Self::State { let mut state = initial_state; @@ -693,10 +730,21 @@ impl<'cx> Dataflow<'cx> for PermissionDataflow { if let Some(args) = arguments { let mut args = args.clone(); args.reverse(); - for var in function_var { - if let VarKind::Arg(defid_new) = var { + for (varid, varkind) in function_var.iter_enumerated() { + if let VarKind::Arg(_) = varkind { if let Some(operand) = args.pop() { - self.insert_value(&operand, &defid_new, interp, None); + self.varid_to_value.insert((def, varid), operand.clone()); + interp.body().vars.iter_enumerated().for_each(|(varid_alt, varkind_alt)| { + if let (Some(defid_alt), Some(defid)) = ( + get_varid_from_defid(varkind_alt), + get_varid_from_defid(varkind), + ) { + if defid == defid_alt && varid_alt != varid { + self.varid_to_value + .insert((def, varid_alt), operand.clone()); + } + } + }) } } } @@ -708,40 +756,97 @@ impl<'cx> Dataflow<'cx> for PermissionDataflow { match inst { Inst::Assign(variable, rvalue) => match variable.base { Base::Var(varid) => { - let varkind = &interp.curr_body.get().unwrap().vars[varid]; - if let Some(defid) = get_varid_from_defid(varkind) { - self.add_variable(interp, &defid, rvalue); + match rvalue { + /* this puts any return value back in the thing */ + Rvalue::Call(operand, _) => { + if let Operand::Var(var) = operand { + if let Base::Var(varid) = var.base { + if let Some(varkind) = interp.body().vars.get(varid) { + if let Some(defid) = get_varid_from_defid(varkind) { + interp + .expecting_value + .push_back((defid, (varid, defid))); + } + } + } + } + + if let Some((value, defid)) = interp.return_value.clone() { + if defid != def || true { + self.varid_to_value.insert((def, varid), value.clone()); + } + } + } + Rvalue::Read(_) => { + self.add_variable(interp, &varid, def, rvalue); + } + _ => {} } + + self.add_variable(interp, &varid, def, rvalue); } _ => {} }, _ => {} } } + + for (varid, varkind) in interp.body().vars.iter_enumerated() { + match varkind { + VarKind::Ret => { + for (d, (v, df)) in &interp.expecting_value { + if def == d.clone() { + if let Some(value) = self.varid_to_value.get(&(def, varid)) { + self.varid_to_value + .insert((df.clone(), v.clone()), value.clone()); + } + } + } + if let Some(value) = self.varid_to_value.get(&(def, varid)) { + interp.return_value = Some((value.clone(), def)); + } + } + _ => {} + } + } + state } fn add_variable>( &mut self, interp: &Interp<'cx, C>, - defid: &DefId, + varid: &VarId, + def: DefId, rvalue: &Rvalue, ) { match rvalue { Rvalue::Read(operand) => { - if let Some(values) = self.variables_from_defid.get(&defid) { - match values { + if let Some(value) = self.varid_to_value.get(&(def, *varid)) { + match value { Value::Const(const_value) => { let prev_value = vec![const_value.clone()]; - self.insert_value(operand, defid, interp, Some(prev_value.clone())); + self.insert_value2( + operand, + varid, + def, + interp, + Some(prev_value.clone()), + ); } Value::Phi(phi_value) => { - self.insert_value(operand, defid, interp, Some(phi_value.clone())); + self.insert_value2( + operand, + varid, + def, + interp, + Some(phi_value.clone()), + ); } _ => {} } } else { - self.insert_value(operand, defid, interp, None) + self.insert_value2(operand, varid, def, interp, None); } } Rvalue::Template(template) => { @@ -750,41 +855,30 @@ impl<'cx> Dataflow<'cx> for PermissionDataflow { let quasis_joined = template.quasis.join(""); let mut all_potential_values = vec![quasis_joined]; for expr in &template.exprs { - if let Some(varid) = resolve_var_from_operand(&expr) { - if let Some(varkind) = interp.curr_body.get().unwrap().vars.get(varid) { - let defid = get_varid_from_defid(&varkind); - if let Some(defid) = defid { - if let Some(value) = self.variables_from_defid.get(&defid) { - match value { - Value::Const(const_value) => { - let mut new_all_values = vec![]; - if let Const::Literal(literal_string) = const_value { - for values in &all_potential_values { - new_all_values - .push(values.clone() + literal_string); - } - } - all_potential_values = new_all_values; - } - Value::Phi(phi_value) => { - let mut new_all_values = vec![]; - for constant in phi_value { - if let Const::Literal(literal_string) = constant { - for values in &all_potential_values { - new_all_values - .push(values.clone() + literal_string); - } - } - } - all_potential_values = new_all_values; + if let Some(value) = self.varid_to_value.get(&(def, *varid)) { + match value { + Value::Const(const_value) => { + let mut new_all_values = vec![]; + if let Const::Literal(literal_string) = const_value { + for values in &all_potential_values { + new_all_values.push(values.clone() + literal_string); + } + } + all_potential_values = new_all_values; + } + Value::Phi(phi_value) => { + let mut new_all_values = vec![]; + for constant in phi_value { + if let Const::Literal(literal_string) = constant { + for values in &all_potential_values { + new_all_values.push(values.clone() + literal_string); } - _ => {} } } + all_potential_values = new_all_values; } + _ => {} } - } else if let Some(literal) = resolve_literal_from_operand(&expr) { - println!("literal {literal:?}"); } } @@ -794,22 +888,24 @@ impl<'cx> Dataflow<'cx> for PermissionDataflow { .map(|value| Const::Literal(value.clone())) .collect::>(); let value = Value::Phi(consts); - self.variables_from_defid.insert(*defid, value.clone()); + self.varid_to_value.insert((def, *varid), value.clone()); } else if all_potential_values.len() == 1 { - self.variables_from_defid.insert( - *defid, + self.varid_to_value.insert( + (def, *varid), Value::Const(Const::Literal(all_potential_values.get(0).unwrap().clone())), ); } } + Rvalue::Bin(binop, op1, op2) => { /* add bin op functionality */ } _ => {} } } - fn insert_value>( + fn insert_value2>( &mut self, operand: &Operand, - defid: &DefId, + varid: &VarId, + def: DefId, interp: &Interp<'cx, C>, prev_values: Option>, ) { @@ -821,41 +917,42 @@ impl<'cx> Dataflow<'cx> for PermissionDataflow { let mut all_values = prev_values.clone(); all_values.push(const_value); let value = Value::Phi(all_values); - self.variables_from_defid.insert(*defid, value.clone()); + self.varid_to_value.insert((def, *varid), value.clone()); } } else { if let Some(lit_value) = convert_operand_to_raw(operand) { let value = Value::Const(Const::Literal(lit_value)); - self.variables_from_defid.insert(*defid, value.clone()); + self.varid_to_value.insert((def, *varid), value.clone()); } } } - Operand::Var(var) => { - match var.base { - Base::Var(var_id) => { - let varkind = &interp.curr_body.get().unwrap().vars[var_id]; - if let VarKind::LocalDef(local_defid) = varkind { - if let Some(class) = - self.read_class_from_object(interp, local_defid.clone()) - { - if let Some(prev_values) = prev_values { - let const_value = Const::Object(class.clone()); - let mut all_values = prev_values.clone(); - all_values.push(const_value); - let value = Value::Phi(all_values); - self.variables_from_defid.insert(*defid, value.clone()); - } else { - let value = Value::Const(Const::Object(class.clone())); - self.variables_from_defid.insert(*defid, value.clone()); - } - } else if let Some(value) = self.variables_from_defid.get(defid) { - println!("found value {:?}", value) + Operand::Var(var) => match var.base { + Base::Var(prev_varid) => { + let potential_varkind = &interp.curr_body.get().unwrap().vars.get(prev_varid); + if let Some(VarKind::LocalDef(local_defid)) = potential_varkind { + if let Some(class) = + self.read_class_from_object(interp, local_defid.clone()) + { + if let Some(prev_values) = prev_values { + let const_value = Const::Object(class.clone()); + let mut all_values = prev_values.clone(); + all_values.push(const_value); + let value = Value::Phi(all_values); + self.varid_to_value.insert((def, *varid), value.clone()); + } else { + let value = Value::Const(Const::Object(class.clone())); + self.varid_to_value.insert((def, *varid), value.clone()); } } + } else { + if let Some(potential_value) = self.varid_to_value.get(&(def, prev_varid)) { + self.varid_to_value + .insert((def, *varid), potential_value.clone()); + } } - _ => {} } - } + _ => {} + }, } } @@ -868,9 +965,9 @@ impl<'cx> Dataflow<'cx> for PermissionDataflow { worklist: &mut WorkList, ) { self.super_join_term(interp, def, block, state, worklist); - for (def, arguments) in self.needs_call.drain(..) { - worklist.push_front_blocks(interp.env(), def, arguments.clone()); - interp.callstack_arguments.push(arguments.clone()); + for (def, arguments, values) in self.needs_call.drain(..) { + worklist.push_front_blocks(interp.env(), def); + interp.callstack_arguments.push(values.clone()); } } } @@ -891,11 +988,12 @@ fn resolve_literal_from_operand(operand: &Operand) -> Option { None } -fn get_varid_from_defid(varkind: &VarKind) -> Option { +pub(crate) fn get_varid_from_defid(varkind: &VarKind) -> Option { match varkind { VarKind::GlobalRef(defid) => Some(defid.clone()), VarKind::LocalDef(defid) => Some(defid.clone()), VarKind::Arg(defid) => Some(defid.clone()), + VarKind::AnonClosure(defid) => Some(defid.clone()), VarKind::Temp { parent } => parent.clone(), _ => None, } @@ -948,6 +1046,7 @@ fn add_elements_to_intrinsic_struct(value: &Value, args: &mut Option pub struct PermissionChecker { pub vulns: Vec, pub declared_permissions: HashSet, + pub used_permissions: HashSet, } impl PermissionChecker { @@ -955,10 +1054,17 @@ impl PermissionChecker { Self { vulns: vec![], declared_permissions, + used_permissions: HashSet::default(), } } pub fn into_vulns(self) -> impl IntoIterator { + if self.declared_permissions.len() > 0 { + return Vec::from([PermissionVuln { + unused_permissions: self.declared_permissions.clone(), + }]) + .into_iter(); + } self.vulns.into_iter() } } @@ -1007,9 +1113,9 @@ impl<'cx> Checker<'cx> for PermissionChecker { state: &Self::State, operands: Option>, ) -> ControlFlow<(), Self::State> { - println!("visitng intrsinsic"); for permission in &interp.permissions { self.declared_permissions.remove(permission); + self.used_permissions.insert(permission.clone()); } ControlFlow::Continue(*state) } @@ -1031,11 +1137,14 @@ impl IntoVuln for PermissionVuln { Vulnerability { check_name: format!("Least-Privilege"), description: format!( - "Unused permissions listed in manifest file:.", - // self.unused_permissions.into_iter().join(", ") + "Unused permissions listed in manifest file: {:?}", + self.unused_permissions ), recommendation: "Remove permissions in manifest file that are not needed.", - proof: format!("Unused permissions found in manifest.yml"), + proof: format!( + "Unused permissions found in manifest.yml: {:?}", + self.unused_permissions + ), severity: Severity::Low, app_key: reporter.app_key().to_string(), app_name: reporter.app_name().to_string(), diff --git a/crates/forge_analyzer/src/definitions.rs b/crates/forge_analyzer/src/definitions.rs index 8557e653..3f9b4d4f 100644 --- a/crates/forge_analyzer/src/definitions.rs +++ b/crates/forge_analyzer/src/definitions.rs @@ -484,6 +484,13 @@ enum LowerStage { Create, } +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum IntrinsicName { + RequestConfluence, + RequestJira, + Other, +} + struct Lowerer<'cx> { res: &'cx mut Environment, curr_mod: ModId, @@ -797,17 +804,22 @@ impl<'cx> FunctionAnalyzer<'cx> { && Some(&ImportKind::Default) == self.res.as_foreign_import(def, "@forge/api") => { + let function_name = if *last == String::from("requestJira") { + IntrinsicName::RequestJira + } else { + IntrinsicName::RequestConfluence + }; let first_arg = first_arg?; match classify_api_call(first_arg) { ApiCallKind::Unknown => { if authn.first() == Some(&PropPath::MemberCall("asApp".into())) { - Some(Intrinsic::ApiCall(last.to_string())) + Some(Intrinsic::ApiCall(function_name)) } else { - Some(Intrinsic::SafeCall(last.to_string())) + Some(Intrinsic::SafeCall(function_name)) } } - ApiCallKind::Trivial => Some(Intrinsic::SafeCall(last.to_string())), - ApiCallKind::Authorize => Some(Intrinsic::Authorize(last.to_string())), + ApiCallKind::Trivial => Some(Intrinsic::SafeCall(function_name)), + ApiCallKind::Authorize => Some(Intrinsic::Authorize(function_name)), } } [PropPath::Def(def), PropPath::Static(ref s), ..] if is_storage_read(s) => { @@ -820,7 +832,7 @@ impl<'cx> FunctionAnalyzer<'cx> { } [PropPath::Def(def), ..] => match self.res.as_foreign_import(def, "@forge/api") { Some(ImportKind::Named(ref name)) if *name == *"authorize" => { - Some(Intrinsic::Authorize(String::from(""))) + Some(Intrinsic::Authorize(IntrinsicName::Other)) } _ => None, }, @@ -977,9 +989,11 @@ impl<'cx> FunctionAnalyzer<'cx> { .iter() .enumerate() .map(|(i, arg)| { - let defid = - self.res - .add_anonymous(i.to_string() + "test", AnonType::Unknown, self.module); + let defid = self.res.add_anonymous( + i.to_string() + "argument", + AnonType::Unknown, + self.module, + ); self.lower_expr(&arg.expr, Some(defid)) }) .collect(); @@ -993,6 +1007,7 @@ impl<'cx> FunctionAnalyzer<'cx> { Some(int) => Rvalue::Intrinsic(int, lowered_args), None => Rvalue::Call(callee, lowered_args), }; + let res = self.body.push_tmp(self.block, call, None); Operand::with_var(res) } @@ -1242,9 +1257,15 @@ impl<'cx> FunctionAnalyzer<'cx> { }) => { let left = self.lower_expr(left, None); let right = self.lower_expr(right, None); - let tmp = self - .body - .push_tmp(self.block, Rvalue::Bin(op.into(), left, right), None); + let defid = self + .res + .add_anonymous("replace_this", AnonType::Unknown, self.module); + + let tmp = self.body.push_tmp( + self.block, + Rvalue::Bin(op.into(), left, right), + Some(defid), + ); Operand::with_var(tmp) } diff --git a/crates/forge_analyzer/src/interp.rs b/crates/forge_analyzer/src/interp.rs index 21fd6d71..a8d8c7ba 100644 --- a/crates/forge_analyzer/src/interp.rs +++ b/crates/forge_analyzer/src/interp.rs @@ -1,7 +1,7 @@ use std::{ borrow::BorrowMut, cell::{Cell, RefCell, RefMut}, - collections::BTreeMap, + collections::{BTreeMap, VecDeque}, fmt::{self, Display}, io::{self, Write}, iter, @@ -17,10 +17,11 @@ use swc_core::ecma::atoms::JsWord; use tracing::{debug, info, instrument, warn}; use crate::{ + checkers::get_varid_from_defid, definitions::{Class, Const, DefId, Environment, Value}, ir::{ Base, BasicBlock, BasicBlockId, Body, Inst, Intrinsic, Location, Operand, Rvalue, - Successors, STARTING_BLOCK, + Successors, VarId, STARTING_BLOCK, }, worklist::WorkList, }; @@ -141,7 +142,7 @@ pub trait Dataflow<'cx>: Sized { bb: BasicBlockId, block: &'cx BasicBlock, initial_state: Self::State, - arguments: Option>, + arguments: Option>, ) -> Self::State { let mut state = initial_state; for (stmt, inst) in block.iter().enumerate() { @@ -154,7 +155,8 @@ pub trait Dataflow<'cx>: Sized { fn add_variable>( &mut self, interp: &Interp<'cx, C>, - defid: &DefId, + varid: &VarId, + def: DefId, rvalue: &Rvalue, ) { } @@ -168,6 +170,16 @@ pub trait Dataflow<'cx>: Sized { ) { } + fn insert_value2>( + &mut self, + operand: &Operand, + varid: &VarId, + def: DefId, + interp: &Interp<'cx, C>, + prev_values: Option>, + ) { + } + fn join_term>( &mut self, interp: &mut Interp<'cx, C>, @@ -236,6 +248,24 @@ pub trait Dataflow<'cx>: Sized { ) -> Option { None } + + fn try_read_mem_from_object>( + &mut self, + _interp: &Interp<'cx, C>, + _def: DefId, + const_var: Const, + ) -> Option<&Value> { + None + } + + fn read_mem_from_object>( + &mut self, + _interp: &Interp<'cx, C>, + _def: DefId, + obj: Class, + ) -> Option<&Value> { + None + } } pub trait Checker<'cx>: Sized { @@ -371,6 +401,7 @@ pub struct Interp<'cx, C: Checker<'cx>> { // We can probably get rid of these RefCells by refactoring the Interp and Checker into // two fields in another struct. call_graph: CallGraph, + pub return_value: Option<(Value, DefId)>, entry: EntryPoint, func_state: RefCell>, pub curr_body: Cell>, @@ -378,9 +409,10 @@ pub struct Interp<'cx, C: Checker<'cx>> { dataflow_visited: FxHashSet, checker_visited: RefCell>, callstack: RefCell>, - pub callstack_arguments: Vec>, + pub callstack_arguments: Vec>, vulns: RefCell>, pub permissions: Vec, + pub expecting_value: VecDeque<(DefId, (VarId, DefId))>, _checker: PhantomData, } @@ -445,12 +477,14 @@ impl<'cx, C: Checker<'cx>> Interp<'cx, C> { env, call_graph, entry: Default::default(), + return_value: None, func_state: RefCell::new(FxHashMap::default()), curr_body: Cell::new(None), states: RefCell::new(BTreeMap::new()), dataflow_visited: FxHashSet::default(), checker_visited: RefCell::new(FxHashSet::default()), callstack_arguments: Vec::new(), + expecting_value: VecDeque::default(), callstack: RefCell::new(Vec::new()), vulns: RefCell::new(Vec::new()), permissions: Vec::new(), @@ -551,7 +585,7 @@ impl<'cx, C: Checker<'cx>> Interp<'cx, C> { self.dataflow_visited.insert(func_def); let mut dataflow = C::Dataflow::with_interp(self); let mut worklist = WorkList::new(); - worklist.push_front_blocks(self.env, func_def, vec![]); + worklist.push_front_blocks(self.env, func_def); let old_body = self.curr_body.get(); while let Some((def, block_id)) = worklist.pop_front() { let arguments = self.callstack_arguments.pop(); diff --git a/crates/forge_analyzer/src/ir.rs b/crates/forge_analyzer/src/ir.rs index 148331e6..3793c732 100644 --- a/crates/forge_analyzer/src/ir.rs +++ b/crates/forge_analyzer/src/ir.rs @@ -33,6 +33,7 @@ use crate::ctx::ModId; use crate::definitions::DefId; use crate::definitions::DefKind; use crate::definitions::Environment; +use crate::definitions::IntrinsicName; use crate::definitions::Value; pub const STARTING_BLOCK: BasicBlockId = BasicBlockId(0); @@ -66,10 +67,10 @@ pub enum Terminator { #[derive(Clone, Debug, PartialEq, Eq)] pub enum Intrinsic { - Authorize(String), + Authorize(IntrinsicName), Fetch, - ApiCall(String), - SafeCall(String), + ApiCall(IntrinsicName), + SafeCall(IntrinsicName), EnvRead, StorageRead, } diff --git a/crates/forge_analyzer/src/permissionclassifier.rs b/crates/forge_analyzer/src/permissionclassifier.rs index af755b78..697c8ed0 100644 --- a/crates/forge_analyzer/src/permissionclassifier.rs +++ b/crates/forge_analyzer/src/permissionclassifier.rs @@ -1,4 +1,4 @@ -use crate::checkers::IntrinsicName; +use crate::definitions::IntrinsicName; use forge_loader::forgepermissions::ForgePermissions; pub(crate) fn check_permission_used( diff --git a/crates/forge_analyzer/src/worklist.rs b/crates/forge_analyzer/src/worklist.rs index ebac9206..e4ce0a4a 100644 --- a/crates/forge_analyzer/src/worklist.rs +++ b/crates/forge_analyzer/src/worklist.rs @@ -27,7 +27,7 @@ where } #[inline] - pub fn pop_front(&mut self) -> (Option<(V, W)>) { + pub fn pop_front(&mut self) -> Option<(V, W)> { self.worklist.pop_front() } @@ -75,12 +75,7 @@ where impl WorkList { #[inline] - pub(crate) fn push_front_blocks( - &mut self, - env: &Environment, - def: DefId, - arguments: Vec, - ) -> bool { + pub(crate) fn push_front_blocks(&mut self, env: &Environment, def: DefId) -> bool { if self.visited.insert(def) { debug!("adding function: {}", env.def_name(def)); let body = env.def_ref(def).expect_body(); @@ -88,8 +83,7 @@ impl WorkList { self.worklist.reserve(blocks.len()); for work in blocks { debug!(?work, "push_front_blocks"); - self.worklist - .push_front((work.0, work.1)); + self.worklist.push_front((work.0, work.1)); } return true; } diff --git a/crates/fsrt/src/main.rs b/crates/fsrt/src/main.rs index 988c5149..63627dd7 100644 --- a/crates/fsrt/src/main.rs +++ b/crates/fsrt/src/main.rs @@ -25,7 +25,7 @@ use tracing_subscriber::{prelude::*, EnvFilter}; use tracing_tree::HierarchicalLayer; use forge_analyzer::{ - checkers::{AuthZChecker, AuthenticateChecker, PermissionChecker}, + checkers::{AuthZChecker, AuthenticateChecker, PermissionChecker, PermissionVuln}, ctx::{AppCtx, ModId}, definitions::{run_resolver, DefId, Environment}, interp::Interp, @@ -199,6 +199,7 @@ fn scan_directory(dir: PathBuf, function: Option<&str>, opts: Opts) -> Result, opts: Opts) -> Result 0 { - // checker2.vulns.push(value); - } - reporter.add_vulnerabilities(checker2.into_vulns()); + all_used_permissions.extend(checker2.used_permissions); } FunctionTy::WebTrigger((ref func, ref path, _, def)) => { let mut checker = AuthenticateChecker::new(); @@ -228,12 +225,28 @@ fn scan_directory(dir: PathBuf, function: Option<&str>, opts: Opts) -> Result::from_iter( + unused_permissions.cloned().into_iter(), + ))] + .into_iter(), + ); let report = serde_json::to_string(&reporter.into_report()).into_diagnostic()?; debug!("Writing Report"); diff --git a/test-apps/jira-damn-vulnerable-forge-app/src/GlobalPageApp.jsx b/test-apps/jira-damn-vulnerable-forge-app/src/GlobalPageApp.jsx index d085377d..1c39ec7b 100755 --- a/test-apps/jira-damn-vulnerable-forge-app/src/GlobalPageApp.jsx +++ b/test-apps/jira-damn-vulnerable-forge-app/src/GlobalPageApp.jsx @@ -1,6 +1,7 @@ import { GlobalPage } from "@forge/ui"; import { fetchIssueSummary } from "./utils"; +/* never called can ignore */ const GlobalPageApp = () => { const issue = await fetchIssueSummary('SEC-1'); return ( diff --git a/test-apps/jira-damn-vulnerable-forge-app/src/index.jsx b/test-apps/jira-damn-vulnerable-forge-app/src/index.jsx index 5107f2bf..fe6572a0 100755 --- a/test-apps/jira-damn-vulnerable-forge-app/src/index.jsx +++ b/test-apps/jira-damn-vulnerable-forge-app/src/index.jsx @@ -96,7 +96,8 @@ function SecureGlance() { return ''; } const [flagVal] = useState(async () => { - const issueData = await fetchIssueSummary(platformContext.issueKey, "/rest/api/3/issue/${issueIdOrKey}?fields=summary"); + const issueData = await fetchIssueSummary(platformContext.issueKey, value); + const test = await writeComment("test", "test"); return JSON.stringify(issueData); }); @@ -112,7 +113,7 @@ function SecureGlance() { export const glance = render( - + ); diff --git a/test-apps/jira-damn-vulnerable-forge-app/src/utils.js b/test-apps/jira-damn-vulnerable-forge-app/src/utils.js index 62bf788e..205bf714 100755 --- a/test-apps/jira-damn-vulnerable-forge-app/src/utils.js +++ b/test-apps/jira-damn-vulnerable-forge-app/src/utils.js @@ -13,7 +13,7 @@ export async function fetchIssueSummary(issueIdOrKey, url) { const resp = await api .asApp() - .requestJira(url, { + .requestJira(a_url, { method: 'POST', headers: { Accept: 'application/json', From d92122b6694d870ece25acf5be435afa06d356d3 Mon Sep 17 00:00:00 2001 From: James Gersbach Date: Thu, 6 Jul 2023 11:25:53 -0700 Subject: [PATCH 085/517] various updates --- .gitignore | 3 +- crates/forge_analyzer/src/checkers.rs | 663 ++++++++++++------ crates/forge_analyzer/src/definitions.rs | 32 +- crates/forge_analyzer/src/interp.rs | 125 +++- crates/forge_analyzer/src/ir.rs | 7 +- .../src/permissionclassifier.rs | 8 +- crates/forge_analyzer/src/worklist.rs | 34 +- crates/fsrt/src/main.rs | 31 +- .../src/GlobalPageApp.jsx | 1 + .../src/index.jsx | 5 +- .../src/utils.js | 20 +- 11 files changed, 631 insertions(+), 298 deletions(-) diff --git a/.gitignore b/.gitignore index c41cc9e3..235ba4f6 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ -/target \ No newline at end of file +/target +/test-apps \ No newline at end of file diff --git a/crates/forge_analyzer/src/checkers.rs b/crates/forge_analyzer/src/checkers.rs index bc53002b..89d3a16c 100644 --- a/crates/forge_analyzer/src/checkers.rs +++ b/crates/forge_analyzer/src/checkers.rs @@ -2,18 +2,20 @@ use core::fmt; use forge_loader::forgepermissions::ForgePermissions; use forge_utils::FxHashMap; use itertools::Itertools; +use serde::de::value; use smallvec::SmallVec; use std::{cmp::max, collections::HashSet, iter, mem, ops::ControlFlow, path::PathBuf}; +use swc_core::ecma::transforms::base::perf::Check; use tracing::{debug, info, warn}; use crate::{ - definitions::{Class, Const, DefId, DefKind, Environment, Value}, + definitions::{Class, Const, DefId, DefKind, Environment, IntrinsicName, Value}, interp::{ Checker, Dataflow, EntryKind, EntryPoint, Frame, Interp, JoinSemiLattice, WithCallStack, }, ir::{ - Base, BasicBlock, BasicBlockId, Inst, Intrinsic, Literal, Location, Operand, Rvalue, + Base, BasicBlock, BasicBlockId, BinOp, Inst, Intrinsic, Literal, Location, Operand, Rvalue, Successors, VarId, VarKind, }, permissionclassifier::check_permission_used, @@ -110,15 +112,15 @@ impl<'cx> Dataflow<'cx> for AuthorizeDataflow { fn join_term>( &mut self, - interp: &Interp<'cx, C>, + interp: &mut Interp<'cx, C>, def: DefId, block: &'cx BasicBlock, state: Self::State, - worklist: &mut WorkList, + worklist: &mut WorkList, ) { self.super_join_term(interp, def, block, state, worklist); for def in self.needs_call.drain(..) { - worklist.push_front_blocks(interp.env(), def, vec![]); + worklist.push_front_blocks(interp.env(), def); } } } @@ -330,15 +332,15 @@ impl<'cx> Dataflow<'cx> for AuthenticateDataflow { fn join_term>( &mut self, - interp: &Interp<'cx, C>, + interp: &mut Interp<'cx, C>, def: DefId, block: &'cx BasicBlock, state: Self::State, - worklist: &mut WorkList, + worklist: &mut WorkList, ) { self.super_join_term(interp, def, block, state, worklist); for def in self.needs_call.drain(..) { - worklist.push_front_blocks(interp.env(), def, vec![]); + worklist.push_front_blocks(interp.env(), def); } } } @@ -461,8 +463,8 @@ impl WithCallStack for AuthNVuln { } pub struct PermissionDataflow { - needs_call: Vec<(DefId, Vec)>, - variables_from_defid: FxHashMap, + needs_call: Vec<(DefId, Vec, Vec)>, + varid_to_value: FxHashMap<(DefId, VarId), Value>, } impl WithCallStack for PermissionVuln { @@ -470,16 +472,41 @@ impl WithCallStack for PermissionVuln { } #[derive(Debug, Default, Clone)] -struct IntrinsicArguments { - name: Option, +pub struct IntrinsicArguments { + name: Option, first_arg: Option>, second_arg: Option>, } -#[derive(Debug, Clone, Copy)] -pub enum IntrinsicName { - RequestConfluence, - RequestJira, +impl PermissionDataflow { + fn handle_first_arg( + &self, + operand: &Operand, + _def: DefId, + intrinsic_argument: &mut IntrinsicArguments, + ) { + match operand { + Operand::Lit(lit) => { + intrinsic_argument.first_arg = Some(vec![lit.to_string()]); + } + Operand::Var(var) => { + if let Base::Var(varid) = var.base { + if let Some(value) = self.get_value(_def, varid) { + intrinsic_argument.first_arg = Some(vec![]); + add_elements_to_intrinsic_struct(value, &mut intrinsic_argument.first_arg); + } + } + } + } + } + + fn add_value(&mut self, defid_block: DefId, varid: VarId, value: Value) { + self.varid_to_value.insert((defid_block, varid), value); + } + + fn get_value(&self, defid_block: DefId, varid: VarId) -> Option<&Value> { + self.varid_to_value.get(&(defid_block, varid)) + } } impl<'cx> Dataflow<'cx> for PermissionDataflow { @@ -490,7 +517,55 @@ impl<'cx> Dataflow<'cx> for PermissionDataflow { ) -> Self { Self { needs_call: vec![], - variables_from_defid: FxHashMap::default(), + varid_to_value: FxHashMap::default(), + } + } + + fn try_insert>( + &self, + _interp: &Interp<'cx, C>, + _def: DefId, + const_var: Const, + intrinsic_argument: &mut IntrinsicArguments, + ) { + if let Some(val) = self.try_read_mem_from_object(_interp, _def.clone(), const_var.clone()) { + intrinsic_argument.second_arg = Some(vec![]); + add_elements_to_intrinsic_struct(val, &mut intrinsic_argument.second_arg); + } + } + + fn handle_second_arg>( + &self, + _interp: &Interp<'cx, C>, + operand: &Operand, + _def: DefId, + intrinsic_argument: &mut IntrinsicArguments, + ) { + if let Some((defid, varid)) = self.get_defid_from_operand(_interp, operand) { + if let Some(val) = self.def_to_class_property(_interp, _def, defid) { + intrinsic_argument.second_arg = Some(vec![]); + add_elements_to_intrinsic_struct(val, &mut intrinsic_argument.second_arg); + } + if let Some(val) = self.get_value(_def, varid) { + match val { + Value::Const(const_var) => { + self.try_insert(_interp, _def, const_var.clone(), &mut *intrinsic_argument); + } + Value::Phi(phi_var) => { + phi_var.iter().for_each(|const_val| { + self.try_insert( + _interp, + _def, + const_val.clone(), + &mut *intrinsic_argument, + ); + }); + } + _ => {} + } + } + } else { + // println!("found other arg {var:?}") } } @@ -505,137 +580,54 @@ impl<'cx> Dataflow<'cx> for PermissionDataflow { operands: SmallVec<[crate::ir::Operand; 4]>, ) -> Self::State { let mut intrinsic_argument = IntrinsicArguments::default(); - - match &*intrinsic { - Intrinsic::ApiCall(value) - | Intrinsic::SafeCall(value) - | Intrinsic::Authorize(value) => { - intrinsic_argument.name = Some(value.clone()); - let (first, second) = (operands.get(0), operands.get(1)); - if let Some(operand) = first { - match operand { - Operand::Lit(lit) => { - intrinsic_argument.first_arg = Some(vec![lit.to_string()]); - } - Operand::Var(var) => match var.base { - Base::Var(varid) => { - let varkind = &_interp.curr_body.get().unwrap().vars[varid]; - let defid = get_varid_from_defid(&varkind); - if let Some(defid) = defid { - if let Some(value) = self.variables_from_defid.get(&defid) { - intrinsic_argument.first_arg = Some(vec![]); - add_elements_to_intrinsic_struct( - value, - &mut intrinsic_argument.first_arg, - ); - } - } - } - _ => {} - }, - } - } - if let Some(operand) = second { - match operand { - Operand::Lit(_) => {} - Operand::Var(var) => { - if let Base::Var(varid) = var.base { - match _interp.curr_body.get().unwrap().vars[varid].clone() { - VarKind::GlobalRef(_def_id) => { - /* case where it is passed in as a variable */ - match &self.variables_from_defid.get(&_def_id).unwrap() { - Value::Const(const_var) => { - if let Const::Object(obj) = const_var { - let defid = find_member_of_obj("method", obj); - if let Some(defid) = defid { - if let Some(value) = - self.variables_from_defid.get(&defid) - { - println!("value of method {value:?}"); - intrinsic_argument.second_arg = - Some(vec![]); - add_elements_to_intrinsic_struct( - value, - &mut intrinsic_argument.second_arg, - ); - } - } - } - } - Value::Phi(phi_var) => {} - _ => {} - } - } - VarKind::LocalDef(_def_id) => { - let class = self.read_class_from_object(_interp, _def_id); - if let Some(obj) = class { - let defid = find_member_of_obj("method", &obj); - if let Some(defid) = defid { - if let Some(value) = - self.variables_from_defid.get(&defid) - { - intrinsic_argument.second_arg = Some(vec![]); - add_elements_to_intrinsic_struct( - value, - &mut intrinsic_argument.second_arg, - ); - } - // println!("value of method {value:?}"); - } - } - // - } - _ => {} - } - } - } - } - } + println!("transferring intrinsic"); + if let Intrinsic::ApiCall(name) | Intrinsic::SafeCall(name) | Intrinsic::Authorize(name) = + intrinsic + { + intrinsic_argument.name = Some(name.clone()); + let (first, second) = (operands.get(0), operands.get(1)); + if let Some(operand) = first { + self.handle_first_arg(operand, _def, &mut intrinsic_argument); } - _ => {} - } - - let mut permissions_within_call: Vec = vec![]; - let function_name = if intrinsic_argument.name.unwrap() == String::from("requestJira") { - IntrinsicName::RequestJira - } else { - IntrinsicName::RequestConfluence - }; - - intrinsic_argument - .first_arg - .iter() - .for_each(|first_arg_vec| { - intrinsic_argument - .second_arg - .iter() - .for_each(|second_arg_vec| { + if let Some(operand) = second { + self.handle_second_arg(_interp, operand, _def, &mut intrinsic_argument); + } + let mut permissions_within_call: Vec = vec![]; + let intrinsic_func_type = intrinsic_argument.name.unwrap(); + intrinsic_argument + .first_arg + .iter() + .for_each(|first_arg_vec| { + if let Some(second_arg_vec) = intrinsic_argument.second_arg.clone() { first_arg_vec.iter().for_each(|first_arg| { second_arg_vec.iter().for_each(|second_arg| { let permissions = check_permission_used( - function_name, + intrinsic_func_type, first_arg, Some(second_arg), ); - println!("permissions {:?}", permissions); permissions_within_call.extend_from_slice(&permissions); }) }) - }) - }); + } else { + first_arg_vec.iter().for_each(|first_arg| { + let permissions = + check_permission_used(intrinsic_func_type, first_arg, None); + permissions_within_call.extend_from_slice(&permissions); + }) + } + }); - _interp - .permissions - .extend_from_slice(&permissions_within_call); + println!("intrinisc arg: {:?}", intrinsic_argument); - match *intrinsic { - Intrinsic::Authorize(_) => initial_state, - Intrinsic::Fetch => initial_state, - Intrinsic::ApiCall(_) => initial_state, - Intrinsic::SafeCall(_) => initial_state, - Intrinsic::EnvRead => initial_state, - Intrinsic::StorageRead => initial_state, + println!("all permissions so far {permissions_within_call:?}"); + + _interp + .permissions + .extend_from_slice(&permissions_within_call); } + + initial_state } fn read_class_from_object>( @@ -655,6 +647,68 @@ impl<'cx> Dataflow<'cx> for PermissionDataflow { None } + fn try_read_mem_from_object>( + &self, + _interp: &Interp<'cx, C>, + _def: DefId, + const_var: Const, + ) -> Option<&Value> { + if let Const::Object(obj) = const_var { + return self.read_mem_from_object(_interp, _def, obj); + } + None + } + + fn read_mem_from_object>( + &self, + _interp: &Interp<'cx, C>, + _def: DefId, + obj: Class, + ) -> Option<&Value> { + let defid_method = obj + .pub_members + .iter() + .filter(|(mem, _)| mem == "method") + .map(|(_, defid)| defid) + .collect_vec(); + if let Some(_alt_defid) = defid_method.get(0) { + for (varid_new, varkind) in _interp.body().vars.clone().into_iter_enumerated() { + if let Some(defid) = get_defid_from_varkind(&varkind) { + if &&defid == _alt_defid { + return self.get_value(_def, varid_new); + } + } + } + } + None + } + + fn def_to_class_property>( + &self, + _interp: &Interp<'cx, C>, + _def: DefId, + defid: DefId, + ) -> Option<&Value> { + if let Some(DefKind::GlobalObj(objid)) = _interp.env().defs.defs.get(defid) { + if let Some(class) = _interp.env().defs.classes.get(objid.clone()) { + return self.read_mem_from_object(_interp, _def, class.clone()); + } + } + None + } + + fn get_values_from_operand>( + &self, + _interp: &Interp<'cx, C>, + _def: DefId, + operand: &Operand, + ) -> Option<&Value> { + if let Some((_, varid)) = self.get_defid_from_operand(_interp, operand) { + return self.get_value(_def, varid); + } + None + } + fn transfer_call>( &mut self, interp: &Interp<'cx, C>, @@ -672,7 +726,33 @@ impl<'cx> Dataflow<'cx> for PermissionDataflow { let callee_name = interp.env().def_name(callee_def); let caller_name = interp.env().def_name(def); debug!("Found call to {callee_name} at {def:?} {caller_name}"); - self.needs_call.push((callee_def, operands.into_vec())); + + let mut all_values_to_be_pushed = vec![]; + + for operand in &operands { + match operand.clone() { + Operand::Lit(_) => { + if let Some(lit_value) = convert_operand_to_raw(&operand.clone()) { + all_values_to_be_pushed.push(Value::Const(Const::Literal(lit_value))); + } else { + all_values_to_be_pushed.push(Value::Unknown) + } + } + Operand::Var(var) => match var.base { + Base::Var(varid) => { + if let Some(value) = self.get_value(def, varid) { + all_values_to_be_pushed.push(value.clone()); + } else { + all_values_to_be_pushed.push(Value::Unknown) + } + } + _ => all_values_to_be_pushed.push(Value::Unknown), + }, + } + } + + self.needs_call + .push((callee_def, operands.into_vec(), all_values_to_be_pushed)); initial_state } @@ -683,7 +763,7 @@ impl<'cx> Dataflow<'cx> for PermissionDataflow { bb: BasicBlockId, block: &'cx BasicBlock, initial_state: Self::State, - arguments: Option>, + arguments: Option>, ) -> Self::State { let mut state = initial_state; @@ -693,10 +773,25 @@ impl<'cx> Dataflow<'cx> for PermissionDataflow { if let Some(args) = arguments { let mut args = args.clone(); args.reverse(); - for var in function_var { - if let VarKind::Arg(defid_new) = var { + for (varid, varkind) in function_var.iter_enumerated() { + if let VarKind::Arg(_) = varkind { if let Some(operand) = args.pop() { - self.insert_value(&operand, &defid_new, interp, None); + self.add_value(def, varid, operand.clone()); + interp + .body() + .vars + .iter_enumerated() + .for_each(|(varid_alt, varkind_alt)| { + if let (Some(defid_alt), Some(defid)) = ( + get_defid_from_varkind(varkind_alt), + get_defid_from_varkind(varkind), + ) { + if defid == defid_alt && varid_alt != varid { + self.varid_to_value + .insert((def, varid_alt), operand.clone()); + } + } + }) } } } @@ -705,86 +800,101 @@ impl<'cx> Dataflow<'cx> for PermissionDataflow { for (stmt, inst) in block.iter().enumerate() { let loc = Location::new(bb, stmt as u32); state = self.transfer_inst(interp, def, loc, block, inst, state); - match inst { - Inst::Assign(variable, rvalue) => match variable.base { + if let Inst::Assign(variable, rvalue) = inst { + match variable.base { Base::Var(varid) => { - let varkind = &interp.curr_body.get().unwrap().vars[varid]; - if let Some(defid) = get_varid_from_defid(varkind) { - self.add_variable(interp, &defid, rvalue); + match rvalue { + /* this puts any return value back in the thing */ + Rvalue::Call(operand, _) => { + if let Some((defid, varid)) = + self.get_defid_from_operand(interp, operand) + { + interp.expecting_value.push_back((defid, (varid, defid))); + } + if let Some((value, defid)) = interp.return_value.clone() { + if defid != def || true { + self.add_value(def, varid, value); + } + } + } + Rvalue::Read(_) => { + self.add_variable(interp, &varid, def, rvalue); + } + _ => {} } + + self.add_variable(interp, &varid, def, rvalue); } _ => {} - }, + } + } + } + + for (varid, varkind) in interp.body().vars.iter_enumerated() { + match varkind { + VarKind::Ret => { + for (defid, (varid_value, defid_value)) in &interp.expecting_value { + if def == defid.clone() { + if let Some(value) = self.get_value(def, varid) { + self.add_value(def, varid, value.clone()); + } + } + } + if let Some(value) = self.get_value(def, varid) { + interp.return_value = Some((value.clone(), def)); + } + } _ => {} } } + state } fn add_variable>( &mut self, interp: &Interp<'cx, C>, - defid: &DefId, + varid: &VarId, + def: DefId, rvalue: &Rvalue, ) { match rvalue { Rvalue::Read(operand) => { - if let Some(values) = self.variables_from_defid.get(&defid) { - match values { - Value::Const(const_value) => { - let prev_value = vec![const_value.clone()]; - self.insert_value(operand, defid, interp, Some(prev_value.clone())); - } - Value::Phi(phi_value) => { - self.insert_value(operand, defid, interp, Some(phi_value.clone())); - } - _ => {} - } + if let Some(value) = get_prev_value(self.get_value(def, *varid)) { + self.insert_value2(operand, varid, def, interp, Some(value)); } else { - self.insert_value(operand, defid, interp, None) + self.insert_value2(operand, varid, def, interp, None); } } Rvalue::Template(template) => { - // self.insert_value(operand, defid, interp, None); - let quasis_joined = template.quasis.join(""); let mut all_potential_values = vec![quasis_joined]; for expr in &template.exprs { - if let Some(varid) = resolve_var_from_operand(&expr) { - if let Some(varkind) = interp.curr_body.get().unwrap().vars.get(varid) { - let defid = get_varid_from_defid(&varkind); - if let Some(defid) = defid { - if let Some(value) = self.variables_from_defid.get(&defid) { - match value { - Value::Const(const_value) => { - let mut new_all_values = vec![]; - if let Const::Literal(literal_string) = const_value { - for values in &all_potential_values { - new_all_values - .push(values.clone() + literal_string); - } - } - all_potential_values = new_all_values; - } - Value::Phi(phi_value) => { - let mut new_all_values = vec![]; - for constant in phi_value { - if let Const::Literal(literal_string) = constant { - for values in &all_potential_values { - new_all_values - .push(values.clone() + literal_string); - } - } - } - all_potential_values = new_all_values; + if let Some(value) = self.get_value(def, *varid) { + match value { + // TODO: get values from all of the operands and add them if they do have a value + Value::Const(const_value) => { + let mut new_all_values = vec![]; + if let Const::Literal(literal_string) = const_value { + for values in &all_potential_values { + new_all_values.push(values.clone() + literal_string); + } + } + all_potential_values = new_all_values; + } + Value::Phi(phi_value) => { + let mut new_all_values = vec![]; + for constant in phi_value { + if let Const::Literal(literal_string) = constant { + for values in &all_potential_values { + new_all_values.push(values.clone() + literal_string); } - _ => {} } } + all_potential_values = new_all_values; } + _ => {} } - } else if let Some(literal) = resolve_literal_from_operand(&expr) { - println!("literal {literal:?}"); } } @@ -794,22 +904,56 @@ impl<'cx> Dataflow<'cx> for PermissionDataflow { .map(|value| Const::Literal(value.clone())) .collect::>(); let value = Value::Phi(consts); - self.variables_from_defid.insert(*defid, value.clone()); + self.add_value(def, *varid, value.clone()); } else if all_potential_values.len() == 1 { - self.variables_from_defid.insert( - *defid, + self.add_value( + def, + *varid, Value::Const(Const::Literal(all_potential_values.get(0).unwrap().clone())), ); } } + Rvalue::Bin(binop, op1, op2) => { + if binop == &BinOp::Add { + let val1 = if let Some(val) = get_str_from_operand(op1) { + Some(Value::Const(Const::Literal(val))) + } else { + self.get_values_from_operand(interp, def, op1).cloned() + }; + let val2 = if let Some(val) = get_str_from_operand(op2) { + Some(Value::Const(Const::Literal(val))) + } else { + self.get_values_from_operand(interp, def, op2).cloned() + }; + let mut new_vals = vec![]; + if let (Some(val1), Some(val2)) = (val1.clone(), val2.clone()) { + match val1 { + Value::Const(const_val) => { + add_const_to_val_vec(&val2, &const_val, &mut new_vals) + } + Value::Phi(phi_val) => phi_val + .iter() + .for_each(|val1| add_const_to_val_vec(&val2, &val1, &mut new_vals)), + _ => {} + } + self.varid_to_value + .insert((def, *varid), return_value_from_string(new_vals)); + } else if let Some(val1) = val1 { + self.add_value(def, *varid, val1); + } else if let Some(val2) = val2 { + self.add_value(def, *varid, val2); + } + } + } _ => {} } } - fn insert_value>( + fn insert_value2>( &mut self, operand: &Operand, - defid: &DefId, + varid: &VarId, + def: DefId, interp: &Interp<'cx, C>, prev_values: Option>, ) { @@ -821,39 +965,39 @@ impl<'cx> Dataflow<'cx> for PermissionDataflow { let mut all_values = prev_values.clone(); all_values.push(const_value); let value = Value::Phi(all_values); - self.variables_from_defid.insert(*defid, value.clone()); + self.add_value(def, *varid, value); } } else { if let Some(lit_value) = convert_operand_to_raw(operand) { let value = Value::Const(Const::Literal(lit_value)); - self.variables_from_defid.insert(*defid, value.clone()); + self.add_value(def, *varid, value); } } } Operand::Var(var) => { - match var.base { - Base::Var(var_id) => { - let varkind = &interp.curr_body.get().unwrap().vars[var_id]; - if let VarKind::LocalDef(local_defid) = varkind { - if let Some(class) = - self.read_class_from_object(interp, local_defid.clone()) - { - if let Some(prev_values) = prev_values { - let const_value = Const::Object(class.clone()); - let mut all_values = prev_values.clone(); - all_values.push(const_value); - let value = Value::Phi(all_values); - self.variables_from_defid.insert(*defid, value.clone()); - } else { - let value = Value::Const(Const::Object(class.clone())); - self.variables_from_defid.insert(*defid, value.clone()); - } - } else if let Some(value) = self.variables_from_defid.get(defid) { - println!("found value {:?}", value) + if let Base::Var(prev_varid) = var.base { + let potential_varkind = &interp.curr_body.get().unwrap().vars.get(prev_varid); + if let Some(VarKind::LocalDef(local_defid)) = potential_varkind { + if let Some(class) = + self.read_class_from_object(interp, local_defid.clone()) + { + if let Some(prev_values) = prev_values { + let const_value = Const::Object(class.clone()); + let mut all_values = prev_values.clone(); + all_values.push(const_value); + let value = Value::Phi(all_values); + self.add_value(def, *varid, value); + } else { + let value = Value::Const(Const::Object(class)); + self.add_value(def, *varid, value); } } + } else { + if let Some(potential_value) = self.get_value(def, prev_varid) { + self.varid_to_value + .insert((def, *varid), potential_value.clone()); + } } - _ => {} } } } @@ -861,20 +1005,21 @@ impl<'cx> Dataflow<'cx> for PermissionDataflow { fn join_term>( &mut self, - interp: &Interp<'cx, C>, + interp: &mut Interp<'cx, C>, def: DefId, block: &'cx BasicBlock, state: Self::State, - worklist: &mut WorkList, + worklist: &mut WorkList, ) { self.super_join_term(interp, def, block, state, worklist); - for (def, arguments) in self.needs_call.drain(..) { - worklist.push_front_blocks(interp.env(), def, arguments); + for (def, arguments, values) in self.needs_call.drain(..) { + worklist.push_front_blocks(interp.env(), def); + interp.callstack_arguments.push(values.clone()); } } } -fn resolve_var_from_operand(operand: &Operand) -> Option { +pub(crate) fn resolve_var_from_operand(operand: &Operand) -> Option { if let Operand::Var(var) = operand { if let Base::Var(varid) = var.base { return Some(varid); @@ -890,11 +1035,28 @@ fn resolve_literal_from_operand(operand: &Operand) -> Option { None } -fn get_varid_from_defid(varkind: &VarKind) -> Option { +fn add_const_to_val_vec(val: &Value, const_val: &Const, vals: &mut Vec) { + match val { + Value::Const(Const::Literal(lit)) => { + if let Const::Literal(lit2) = const_val { + vals.push(lit.to_owned() + &lit2); + } + } + Value::Phi(phi_val2) => phi_val2.iter().for_each(|val2| { + if let (Const::Literal(lit1), Const::Literal(lit2)) = (&const_val, val2) { + vals.push(lit1.to_owned() + lit2); + } + }), + _ => {} + } +} + +pub(crate) fn get_defid_from_varkind(varkind: &VarKind) -> Option { match varkind { VarKind::GlobalRef(defid) => Some(defid.clone()), VarKind::LocalDef(defid) => Some(defid.clone()), VarKind::Arg(defid) => Some(defid.clone()), + VarKind::AnonClosure(defid) => Some(defid.clone()), VarKind::Temp { parent } => parent.clone(), _ => None, } @@ -926,6 +1088,15 @@ fn find_member_of_obj(member: &str, obj: &Class) -> Option { None } +fn get_str_from_operand(operand: &Operand) -> Option { + if let Operand::Lit(lit) = operand { + if let Literal::Str(str) = lit { + return Some(str.to_string()); + } + } + None +} + fn add_elements_to_intrinsic_struct(value: &Value, args: &mut Option>) { match value { Value::Const(const_value) => { @@ -944,9 +1115,35 @@ fn add_elements_to_intrinsic_struct(value: &Value, args: &mut Option } } +fn get_prev_value(value: Option<&Value>) -> Option> { + if let Some(value) = value { + return match value { + Value::Const(const_value) => Some(vec![const_value.clone()]), + Value::Phi(phi_value) => Some(phi_value.clone()), + _ => None, + }; + } + None +} + +fn return_value_from_string(values: Vec) -> Value { + assert!(values.len() > 0); + if values.len() == 1 { + return Value::Const(Const::Literal(values.get(0).unwrap().clone())); + } else { + return Value::Phi( + values + .iter() + .map(|val_string| Const::Literal(val_string.clone())) + .collect_vec(), + ); + } +} + pub struct PermissionChecker { pub vulns: Vec, pub declared_permissions: HashSet, + pub used_permissions: HashSet, } impl PermissionChecker { @@ -954,10 +1151,17 @@ impl PermissionChecker { Self { vulns: vec![], declared_permissions, + used_permissions: HashSet::default(), } } pub fn into_vulns(self) -> impl IntoIterator { + if self.declared_permissions.len() > 0 { + return Vec::from([PermissionVuln { + unused_permissions: self.declared_permissions.clone(), + }]) + .into_iter(); + } self.vulns.into_iter() } } @@ -1006,9 +1210,9 @@ impl<'cx> Checker<'cx> for PermissionChecker { state: &Self::State, operands: Option>, ) -> ControlFlow<(), Self::State> { - println!("visitng intrsinsic"); for permission in &interp.permissions { self.declared_permissions.remove(permission); + self.used_permissions.insert(permission.clone()); } ControlFlow::Continue(*state) } @@ -1030,11 +1234,14 @@ impl IntoVuln for PermissionVuln { Vulnerability { check_name: format!("Least-Privilege"), description: format!( - "Unused permissions listed in manifest file:.", - // self.unused_permissions.into_iter().join(", ") + "Unused permissions listed in manifest file: {:?}", + self.unused_permissions ), recommendation: "Remove permissions in manifest file that are not needed.", - proof: format!("Unused permissions found in manifest.yml"), + proof: format!( + "Unused permissions found in manifest.yml: {:?}", + self.unused_permissions + ), severity: Severity::Low, app_key: reporter.app_key().to_string(), app_name: reporter.app_name().to_string(), diff --git a/crates/forge_analyzer/src/definitions.rs b/crates/forge_analyzer/src/definitions.rs index 8557e653..e73d5bad 100644 --- a/crates/forge_analyzer/src/definitions.rs +++ b/crates/forge_analyzer/src/definitions.rs @@ -484,6 +484,13 @@ enum LowerStage { Create, } +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum IntrinsicName { + RequestConfluence, + RequestJira, + Other, +} + struct Lowerer<'cx> { res: &'cx mut Environment, curr_mod: ModId, @@ -797,17 +804,22 @@ impl<'cx> FunctionAnalyzer<'cx> { && Some(&ImportKind::Default) == self.res.as_foreign_import(def, "@forge/api") => { + let function_name = if *last == String::from("requestJira") { + IntrinsicName::RequestJira + } else { + IntrinsicName::RequestConfluence + }; let first_arg = first_arg?; match classify_api_call(first_arg) { ApiCallKind::Unknown => { if authn.first() == Some(&PropPath::MemberCall("asApp".into())) { - Some(Intrinsic::ApiCall(last.to_string())) + Some(Intrinsic::ApiCall(function_name)) } else { - Some(Intrinsic::SafeCall(last.to_string())) + Some(Intrinsic::SafeCall(function_name)) } } - ApiCallKind::Trivial => Some(Intrinsic::SafeCall(last.to_string())), - ApiCallKind::Authorize => Some(Intrinsic::Authorize(last.to_string())), + ApiCallKind::Trivial => Some(Intrinsic::SafeCall(function_name)), + ApiCallKind::Authorize => Some(Intrinsic::Authorize(function_name)), } } [PropPath::Def(def), PropPath::Static(ref s), ..] if is_storage_read(s) => { @@ -820,7 +832,7 @@ impl<'cx> FunctionAnalyzer<'cx> { } [PropPath::Def(def), ..] => match self.res.as_foreign_import(def, "@forge/api") { Some(ImportKind::Named(ref name)) if *name == *"authorize" => { - Some(Intrinsic::Authorize(String::from(""))) + Some(Intrinsic::Authorize(IntrinsicName::Other)) } _ => None, }, @@ -977,9 +989,11 @@ impl<'cx> FunctionAnalyzer<'cx> { .iter() .enumerate() .map(|(i, arg)| { - let defid = - self.res - .add_anonymous(i.to_string() + "test", AnonType::Unknown, self.module); + let defid = self.res.add_anonymous( + i.to_string() + "argument", + AnonType::Unknown, + self.module, + ); self.lower_expr(&arg.expr, Some(defid)) }) .collect(); @@ -993,6 +1007,7 @@ impl<'cx> FunctionAnalyzer<'cx> { Some(int) => Rvalue::Intrinsic(int, lowered_args), None => Rvalue::Call(callee, lowered_args), }; + let res = self.body.push_tmp(self.block, call, None); Operand::with_var(res) } @@ -1242,6 +1257,7 @@ impl<'cx> FunctionAnalyzer<'cx> { }) => { let left = self.lower_expr(left, None); let right = self.lower_expr(right, None); + let tmp = self .body .push_tmp(self.block, Rvalue::Bin(op.into(), left, right), None); diff --git a/crates/forge_analyzer/src/interp.rs b/crates/forge_analyzer/src/interp.rs index 31c4a588..6fd8dc31 100644 --- a/crates/forge_analyzer/src/interp.rs +++ b/crates/forge_analyzer/src/interp.rs @@ -1,7 +1,7 @@ use std::{ borrow::BorrowMut, cell::{Cell, RefCell, RefMut}, - collections::BTreeMap, + collections::{BTreeMap, VecDeque}, fmt::{self, Display}, io::{self, Write}, iter, @@ -17,10 +17,11 @@ use swc_core::ecma::atoms::JsWord; use tracing::{debug, info, instrument, warn}; use crate::{ + checkers::{get_defid_from_varkind, resolve_var_from_operand, IntrinsicArguments}, definitions::{Class, Const, DefId, Environment, Value}, ir::{ Base, BasicBlock, BasicBlockId, Body, Inst, Intrinsic, Location, Operand, Rvalue, - Successors, STARTING_BLOCK, + Successors, VarId, STARTING_BLOCK, }, worklist::WorkList, }; @@ -141,7 +142,7 @@ pub trait Dataflow<'cx>: Sized { bb: BasicBlockId, block: &'cx BasicBlock, initial_state: Self::State, - arguments: Option>, + arguments: Option>, ) -> Self::State { let mut state = initial_state; for (stmt, inst) in block.iter().enumerate() { @@ -154,15 +155,17 @@ pub trait Dataflow<'cx>: Sized { fn add_variable>( &mut self, interp: &Interp<'cx, C>, - defid: &DefId, + varid: &VarId, + def: DefId, rvalue: &Rvalue, ) { } - fn insert_value>( + fn insert_value2>( &mut self, operand: &Operand, - defid: &DefId, + varid: &VarId, + def: DefId, interp: &Interp<'cx, C>, prev_values: Option>, ) { @@ -170,22 +173,22 @@ pub trait Dataflow<'cx>: Sized { fn join_term>( &mut self, - interp: &Interp<'cx, C>, + interp: &mut Interp<'cx, C>, def: DefId, block: &'cx BasicBlock, state: Self::State, - worklist: &mut WorkList, + worklist: &mut WorkList, ) { - self.super_join_term(interp, def, block, state, worklist); + self.super_join_term(interp.borrow_mut(), def, block, state, worklist); } fn super_join_term>( &mut self, - interp: &Interp<'cx, C>, + interp: &mut Interp<'cx, C>, def: DefId, block: &'cx BasicBlock, state: Self::State, - worklist: &mut WorkList, + worklist: &mut WorkList, ) { match block.successors() { Successors::Return => { @@ -199,7 +202,7 @@ pub trait Dataflow<'cx>: Sized { debug!("{name} {def:?} is called from {calls:?}"); for &(def, loc) in calls { if worklist.visited(&def) { - worklist.push_back_force(def, loc.block, vec![]); + worklist.push_back_force(def, loc.block); } } } @@ -207,15 +210,15 @@ pub trait Dataflow<'cx>: Sized { Successors::One(succ) => { let mut succ_state = interp.block_state_mut(def, succ); if succ_state.join_changed(&state) { - worklist.push_back(def, succ, vec![]); + worklist.push_back(def, succ); } } Successors::Two(succ1, succ2) => { if interp.block_state_mut(def, succ1).join_changed(&state) { - worklist.push_back(def, succ1, vec![]); + worklist.push_back(def, succ1); } if interp.block_state_mut(def, succ2).join_changed(&state) { - worklist.push_back(def, succ2, vec![]); + worklist.push_back(def, succ2); } } } @@ -236,6 +239,85 @@ pub trait Dataflow<'cx>: Sized { ) -> Option { None } + + fn try_read_mem_from_object>( + &self, + _interp: &Interp<'cx, C>, + _def: DefId, + const_var: Const, + ) -> Option<&Value> { + None + } + + fn get_defid_from_operand>( + &self, + _interp: &Interp<'cx, C>, + operand: &Operand, + ) -> Option<(DefId, VarId)> { + if let Some(varid) = resolve_var_from_operand(operand) { + if let Some(varkind) = _interp.body().vars.get(varid) { + if let Some(defid) = get_defid_from_varkind(varkind) { + return Some((defid, varid)); + } + } + } + None + } + + fn insert_with_existing_value>( + &mut self, + operand: &Operand, + value: &Value, + varid: &VarId, + def: DefId, + interp: &Interp<'cx, C>, + ) { + } + + fn read_mem_from_object>( + &self, + _interp: &Interp<'cx, C>, + _def: DefId, + obj: Class, + ) -> Option<&Value> { + None + } + + fn def_to_class_property>( + &self, + _interp: &Interp<'cx, C>, + _def: DefId, + defid: DefId, + ) -> Option<&Value> { + None + } + + fn get_values_from_operand>( + &self, + _interp: &Interp<'cx, C>, + _def: DefId, + operand: &Operand, + ) -> Option<&Value> { + None + } + + fn try_insert>( + &self, + _interp: &Interp<'cx, C>, + _def: DefId, + const_var: Const, + intrinsic_argument: &mut IntrinsicArguments, + ) { + } + + fn handle_second_arg>( + &self, + _interp: &Interp<'cx, C>, + operand: &Operand, + _def: DefId, + intrinsic_argument: &mut IntrinsicArguments, + ) { + } } pub trait Checker<'cx>: Sized { @@ -371,6 +453,7 @@ pub struct Interp<'cx, C: Checker<'cx>> { // We can probably get rid of these RefCells by refactoring the Interp and Checker into // two fields in another struct. call_graph: CallGraph, + pub return_value: Option<(Value, DefId)>, entry: EntryPoint, func_state: RefCell>, pub curr_body: Cell>, @@ -378,8 +461,10 @@ pub struct Interp<'cx, C: Checker<'cx>> { dataflow_visited: FxHashSet, checker_visited: RefCell>, callstack: RefCell>, + pub callstack_arguments: Vec>, vulns: RefCell>, pub permissions: Vec, + pub expecting_value: VecDeque<(DefId, (VarId, DefId))>, _checker: PhantomData, } @@ -444,11 +529,14 @@ impl<'cx, C: Checker<'cx>> Interp<'cx, C> { env, call_graph, entry: Default::default(), + return_value: None, func_state: RefCell::new(FxHashMap::default()), curr_body: Cell::new(None), states: RefCell::new(BTreeMap::new()), dataflow_visited: FxHashSet::default(), checker_visited: RefCell::new(FxHashSet::default()), + callstack_arguments: Vec::new(), + expecting_value: VecDeque::default(), callstack: RefCell::new(Vec::new()), vulns: RefCell::new(Vec::new()), permissions: Vec::new(), @@ -549,9 +637,10 @@ impl<'cx, C: Checker<'cx>> Interp<'cx, C> { self.dataflow_visited.insert(func_def); let mut dataflow = C::Dataflow::with_interp(self); let mut worklist = WorkList::new(); - worklist.push_front_blocks(self.env, func_def, vec![]); + worklist.push_front_blocks(self.env, func_def); let old_body = self.curr_body.get(); - while let Some((def, block_id, args)) = worklist.pop_front() { + while let Some((def, block_id)) = worklist.pop_front() { + let arguments = self.callstack_arguments.pop(); let name = self.env.def_name(def); debug!("Dataflow: {name} - {block_id}"); self.dataflow_visited.insert(def); @@ -564,7 +653,7 @@ impl<'cx, C: Checker<'cx>> Interp<'cx, C> { before_state = before_state.join(&self.block_state(def, pred)); } let state = - dataflow.transfer_block(self, def, block_id, block, before_state, Some(args)); + dataflow.transfer_block(self, def, block_id, block, before_state, arguments); dataflow.join_term(self, def, block, state, &mut worklist); } self.curr_body.set(old_body); diff --git a/crates/forge_analyzer/src/ir.rs b/crates/forge_analyzer/src/ir.rs index 148331e6..3793c732 100644 --- a/crates/forge_analyzer/src/ir.rs +++ b/crates/forge_analyzer/src/ir.rs @@ -33,6 +33,7 @@ use crate::ctx::ModId; use crate::definitions::DefId; use crate::definitions::DefKind; use crate::definitions::Environment; +use crate::definitions::IntrinsicName; use crate::definitions::Value; pub const STARTING_BLOCK: BasicBlockId = BasicBlockId(0); @@ -66,10 +67,10 @@ pub enum Terminator { #[derive(Clone, Debug, PartialEq, Eq)] pub enum Intrinsic { - Authorize(String), + Authorize(IntrinsicName), Fetch, - ApiCall(String), - SafeCall(String), + ApiCall(IntrinsicName), + SafeCall(IntrinsicName), EnvRead, StorageRead, } diff --git a/crates/forge_analyzer/src/permissionclassifier.rs b/crates/forge_analyzer/src/permissionclassifier.rs index af755b78..2ab45477 100644 --- a/crates/forge_analyzer/src/permissionclassifier.rs +++ b/crates/forge_analyzer/src/permissionclassifier.rs @@ -1,4 +1,4 @@ -use crate::checkers::IntrinsicName; +use crate::definitions::IntrinsicName; use forge_loader::forgepermissions::ForgePermissions; pub(crate) fn check_permission_used( @@ -8,9 +8,9 @@ pub(crate) fn check_permission_used( ) -> Vec { let mut used_permissions: Vec = Vec::new(); - let post_call = second_arg.unwrap_or(&String::from("")).contains("POST"); - let delete_call = second_arg.unwrap_or(&String::from("")).contains("DELTE"); - let put_call = second_arg.unwrap_or(&String::from("")).contains("PUT"); + let post_call = second_arg.unwrap_or("").contains("POST"); + let delete_call = second_arg.unwrap_or("").contains("DELTE"); + let put_call = second_arg.unwrap_or("").contains("PUT"); let contains_audit = first_arg.contains("audit"); let contains_issue = first_arg.contains("issue"); diff --git a/crates/forge_analyzer/src/worklist.rs b/crates/forge_analyzer/src/worklist.rs index fdf0a6b3..e4ce0a4a 100644 --- a/crates/forge_analyzer/src/worklist.rs +++ b/crates/forge_analyzer/src/worklist.rs @@ -9,12 +9,12 @@ use crate::{ }; #[derive(Debug, Clone)] -pub struct WorkList { - worklist: VecDeque<(V, W, Vec)>, +pub struct WorkList { + worklist: VecDeque<(V, W)>, visited: FxHashSet, } -impl WorkList +impl WorkList where V: Eq + Hash, { @@ -27,7 +27,7 @@ where } #[inline] - pub fn pop_front(&mut self) -> (Option<(V, W, Vec)>) { + pub fn pop_front(&mut self) -> Option<(V, W)> { self.worklist.pop_front() } @@ -56,31 +56,26 @@ where } } -impl WorkList +impl WorkList where V: Eq + Hash + Copy, { #[inline] - pub fn push_back(&mut self, v: V, w: W, args: Vec) { + pub fn push_back(&mut self, v: V, w: W) { if self.visited.insert(v) { - self.worklist.push_back((v, w, args)); + self.worklist.push_back((v, w)); } } #[inline] - pub fn push_back_force(&mut self, v: V, w: W, args: Vec) { - self.worklist.push_back((v, w, args)); + pub fn push_back_force(&mut self, v: V, w: W) { + self.worklist.push_back((v, w)); } } -impl WorkList { +impl WorkList { #[inline] - pub(crate) fn push_front_blocks( - &mut self, - env: &Environment, - def: DefId, - arguments: Vec, - ) -> bool { + pub(crate) fn push_front_blocks(&mut self, env: &Environment, def: DefId) -> bool { if self.visited.insert(def) { debug!("adding function: {}", env.def_name(def)); let body = env.def_ref(def).expect_body(); @@ -88,8 +83,7 @@ impl WorkList { self.worklist.reserve(blocks.len()); for work in blocks { debug!(?work, "push_front_blocks"); - self.worklist - .push_front((work.0, work.1, arguments.clone())); + self.worklist.push_front((work.0, work.1)); } return true; } @@ -97,12 +91,12 @@ impl WorkList { } } -impl Extend<(V, W, Vec)> for WorkList +impl Extend<(V, W)> for WorkList where V: Eq + Hash, { #[inline] - fn extend)>>(&mut self, iter: T) { + fn extend>(&mut self, iter: T) { self.worklist.extend(iter); } } diff --git a/crates/fsrt/src/main.rs b/crates/fsrt/src/main.rs index 988c5149..9639eeac 100644 --- a/crates/fsrt/src/main.rs +++ b/crates/fsrt/src/main.rs @@ -25,7 +25,7 @@ use tracing_subscriber::{prelude::*, EnvFilter}; use tracing_tree::HierarchicalLayer; use forge_analyzer::{ - checkers::{AuthZChecker, AuthenticateChecker, PermissionChecker}, + checkers::{AuthZChecker, AuthenticateChecker, PermissionChecker, PermissionVuln}, ctx::{AppCtx, ModId}, definitions::{run_resolver, DefId, Environment}, interp::Interp, @@ -136,6 +136,7 @@ impl ForgeProject { fn add_funcs<'a, I: IntoIterator>>(&mut self, iter: I) { self.funcs.extend(iter.into_iter().flat_map(|ftype| { ftype.sequence(|(func_name, path)| { + println!("adding functions {func_name}"); let modid = self.ctx.modid_from_path(&path)?; let func = self.env.module_export(modid, func_name)?; Some((func_name.to_owned(), path, modid, func)) @@ -188,8 +189,11 @@ fn scan_directory(dir: PathBuf, function: Option<&str>, opts: Opts) -> Result, opts: Opts) -> Result, opts: Opts) -> Result 0 { - // checker2.vulns.push(value); - } - reporter.add_vulnerabilities(checker2.into_vulns()); + all_used_permissions.extend(checker2.used_permissions); } FunctionTy::WebTrigger((ref func, ref path, _, def)) => { let mut checker = AuthenticateChecker::new(); @@ -228,12 +229,28 @@ fn scan_directory(dir: PathBuf, function: Option<&str>, opts: Opts) -> Result::from_iter( + unused_permissions.cloned().into_iter(), + ))] + .into_iter(), + ); let report = serde_json::to_string(&reporter.into_report()).into_diagnostic()?; debug!("Writing Report"); diff --git a/test-apps/jira-damn-vulnerable-forge-app/src/GlobalPageApp.jsx b/test-apps/jira-damn-vulnerable-forge-app/src/GlobalPageApp.jsx index d085377d..1c39ec7b 100755 --- a/test-apps/jira-damn-vulnerable-forge-app/src/GlobalPageApp.jsx +++ b/test-apps/jira-damn-vulnerable-forge-app/src/GlobalPageApp.jsx @@ -1,6 +1,7 @@ import { GlobalPage } from "@forge/ui"; import { fetchIssueSummary } from "./utils"; +/* never called can ignore */ const GlobalPageApp = () => { const issue = await fetchIssueSummary('SEC-1'); return ( diff --git a/test-apps/jira-damn-vulnerable-forge-app/src/index.jsx b/test-apps/jira-damn-vulnerable-forge-app/src/index.jsx index 5107f2bf..fe6572a0 100755 --- a/test-apps/jira-damn-vulnerable-forge-app/src/index.jsx +++ b/test-apps/jira-damn-vulnerable-forge-app/src/index.jsx @@ -96,7 +96,8 @@ function SecureGlance() { return ''; } const [flagVal] = useState(async () => { - const issueData = await fetchIssueSummary(platformContext.issueKey, "/rest/api/3/issue/${issueIdOrKey}?fields=summary"); + const issueData = await fetchIssueSummary(platformContext.issueKey, value); + const test = await writeComment("test", "test"); return JSON.stringify(issueData); }); @@ -112,7 +113,7 @@ function SecureGlance() { export const glance = render( - + ); diff --git a/test-apps/jira-damn-vulnerable-forge-app/src/utils.js b/test-apps/jira-damn-vulnerable-forge-app/src/utils.js index 62bf788e..c28dc32b 100755 --- a/test-apps/jira-damn-vulnerable-forge-app/src/utils.js +++ b/test-apps/jira-damn-vulnerable-forge-app/src/utils.js @@ -3,27 +3,33 @@ import api, { route } from '@forge/api'; export async function fetchIssueSummary(issueIdOrKey, url) { let obj = { - method: 'POST', + method: 'GET', headers: { Accept: 'application/json', }, }; + let val = "grapefruit"; + + val = "peach"; + + let pre_url = "/rest/api/3/issue/" + val; + let a_url = route`/rest/api/3/issue/${issueIdOrKey}?fields=summary`; const resp = await api .asApp() - .requestJira(url, { - method: 'POST', - headers: { - Accept: 'application/json', - }, - }); + .requestJira( "/rest/api/3/issue/" + val, obj); const data = await resp.json(); console.log(JSON.stringify(data)); return data['fields']['summary']; } +function get_route(url) { + //return a_url = route`/rest/api/3/issue/${issueIdOrKey}?fields=summary`; + return url; +} + export async function writeComment(issueIdOrKey, comment) { /* const api = require('@forge/api'); */ const resp = await api From c8ca4c1770addbc04b299b6507a50e5469277e18 Mon Sep 17 00:00:00 2001 From: James Gersbach Date: Sat, 8 Jul 2023 17:52:01 -0700 Subject: [PATCH 086/517] wip --- crates/forge_analyzer/src/checkers.rs | 61 ++++++++++++++++--- crates/forge_analyzer/src/definitions.rs | 5 ++ crates/forge_analyzer/src/interp.rs | 4 ++ crates/forge_loader/src/manifest.rs | 2 + crates/fsrt/src/main.rs | 8 ++- .../src/GlobalPageApp.jsx | 3 + .../src/index.jsx | 6 +- .../src/utils.js | 14 ++--- 8 files changed, 83 insertions(+), 20 deletions(-) diff --git a/crates/forge_analyzer/src/checkers.rs b/crates/forge_analyzer/src/checkers.rs index 4023a9f4..d0700fae 100644 --- a/crates/forge_analyzer/src/checkers.rs +++ b/crates/forge_analyzer/src/checkers.rs @@ -119,6 +119,7 @@ impl<'cx> Dataflow<'cx> for AuthorizeDataflow { worklist: &mut WorkList, ) { self.super_join_term(interp, def, block, state, worklist); + //println!("worklist - adding function {}", interp.env().def_name(def)); for def in self.needs_call.drain(..) { worklist.push_front_blocks(interp.env(), def); } @@ -225,6 +226,9 @@ impl<'cx> Checker<'cx> for AuthZChecker { state: &Self::State, operands: Option>, ) -> ControlFlow<(), Self::State> { + + // println!("visitng intrinsic"); + match *intrinsic { Intrinsic::Authorize(_) => { debug!("authorize intrinsic found"); @@ -541,12 +545,26 @@ impl<'cx> Dataflow<'cx> for PermissionDataflow { _def: DefId, intrinsic_argument: &mut IntrinsicArguments, ) { + println!("operand {:?}", operand); + + println!(); + println!("all vars {:?}", self.varid_to_value); + println!(); + if let Some((defid, varid)) = self.get_defid_from_operand(_interp, operand) { - if let Some(val) = self.def_to_class_property(_interp, _def, defid) { - intrinsic_argument.second_arg = Some(vec![]); - add_elements_to_intrinsic_struct(val, &mut intrinsic_argument.second_arg); - } + + // getting number 29 here + + println!("defid -varid {defid:?}. {varid:?}"); + + println!("varid ~~~ values {:?}", _interp.body().vars.get(varid)); + + + if let Some(val) = self.get_value(_def, varid) { + + println!("this is the value {val:?}"); + match val { Value::Const(const_var) => { self.try_insert(_interp, _def, const_var.clone(), &mut *intrinsic_argument); @@ -563,7 +581,15 @@ impl<'cx> Dataflow<'cx> for PermissionDataflow { } _ => {} } - } + } else if let Some(val) = self.def_to_class_property(_interp, _def, defid) { + /* looks like a misclassification here */ + println!("within class to property {val:?} "); + + println!("this is the value {val:?}"); + intrinsic_argument.second_arg = Some(vec![]); + add_elements_to_intrinsic_struct(val, &mut intrinsic_argument.second_arg); + println!("{:?} intrinsic arg second", intrinsic_argument.second_arg); + } } else { // println!("found other arg {var:?}") } @@ -579,8 +605,9 @@ impl<'cx> Dataflow<'cx> for PermissionDataflow { initial_state: Self::State, operands: SmallVec<[crate::ir::Operand; 4]>, ) -> Self::State { + let mut intrinsic_argument = IntrinsicArguments::default(); - println!("transferring intrinsic"); + println!("transferring intrinsic {:?}", _interp.env().def_name(_def) ); if let Intrinsic::ApiCall(name) | Intrinsic::SafeCall(name) | Intrinsic::Authorize(name) = intrinsic { @@ -671,10 +698,25 @@ impl<'cx> Dataflow<'cx> for PermissionDataflow { .filter(|(mem, _)| mem == "method") .map(|(_, defid)| defid) .collect_vec(); + + // println!("deifd methods {defid_method:?} {:?}", obj.pub_members); + // /* everything here is correct */ + + + println!("defid to varid {:?}", _interp.body().def_id_to_vars); + println!(); + println!("deifd to varid {:?}", _interp.body().vars); + println!(); + println!("projections"); + println!(); + + if let Some(_alt_defid) = defid_method.get(0) { for (varid_new, varkind) in _interp.body().vars.clone().into_iter_enumerated() { + // println!("varid {varid_new} : varkind {varkind:?}"); if let Some(defid) = get_defid_from_varkind(&varkind) { if &&defid == _alt_defid { + println!("incorrect value - misinterpreting here {_alt_defid:?} {defid:?} {:?}", self.get_value(_def, varid_new)); return self.get_value(_def, varid_new); } } @@ -690,7 +732,11 @@ impl<'cx> Dataflow<'cx> for PermissionDataflow { defid: DefId, ) -> Option<&Value> { if let Some(DefKind::GlobalObj(objid)) = _interp.env().defs.defs.get(defid) { + println!(""); if let Some(class) = _interp.env().defs.classes.get(objid.clone()) { + + println!("class~~ {class:?}"); + return self.read_mem_from_object(_interp, _def, class.clone()); } } @@ -1127,7 +1173,7 @@ fn get_prev_value(value: Option<&Value>) -> Option> { } fn return_value_from_string(values: Vec) -> Value { - assert!(values.len() > 0); + // assert!(values.len() > 0); if values.len() == 1 { return Value::Const(Const::Literal(values.get(0).unwrap().clone())); } else { @@ -1210,6 +1256,7 @@ impl<'cx> Checker<'cx> for PermissionChecker { state: &Self::State, operands: Option>, ) -> ControlFlow<(), Self::State> { + //println!("visiting _intrinsic"); for permission in &interp.permissions { self.declared_permissions.remove(permission); self.used_permissions.insert(permission.clone()); diff --git a/crates/forge_analyzer/src/definitions.rs b/crates/forge_analyzer/src/definitions.rs index e73d5bad..083d5640 100644 --- a/crates/forge_analyzer/src/definitions.rs +++ b/crates/forge_analyzer/src/definitions.rs @@ -796,6 +796,8 @@ impl<'cx> FunctionAnalyzer<'cx> { *prop == *"get" || *prop == *"getSecret" || *prop == *"query" } + //println!("as intrinsic "); + match *callee { [PropPath::Unknown((ref name, ..))] if *name == *"fetch" => Some(Intrinsic::Fetch), [PropPath::Def(def), ref authn @ .., PropPath::Static(ref last)] @@ -809,6 +811,7 @@ impl<'cx> FunctionAnalyzer<'cx> { } else { IntrinsicName::RequestConfluence }; + //println!("here within the intrinsic"); let first_arg = first_arg?; match classify_api_call(first_arg) { ApiCallKind::Unknown => { @@ -956,6 +959,7 @@ impl<'cx> FunctionAnalyzer<'cx> { } fn lower_call(&mut self, callee: CalleeRef<'_>, args: &[ExprOrSpread]) -> Operand { + //println!("lower call"); let props = normalize_callee_expr(callee, self.res, self.module); if let Some(&PropPath::Def(id)) = props.first() { if self.res.as_foreign_import(id, "@forge/ui").map_or( @@ -1002,6 +1006,7 @@ impl<'cx> FunctionAnalyzer<'cx> { CalleeRef::Import => Operand::UNDEF, CalleeRef::Expr(expr) => self.lower_expr(expr, None), }; + //println!("callee {callee}"); let first_arg = args.first().map(|expr| &*expr.expr); let call = match self.as_intrinsic(&props, first_arg) { Some(int) => Rvalue::Intrinsic(int, lowered_args), diff --git a/crates/forge_analyzer/src/interp.rs b/crates/forge_analyzer/src/interp.rs index 6fd8dc31..a16637b4 100644 --- a/crates/forge_analyzer/src/interp.rs +++ b/crates/forge_analyzer/src/interp.rs @@ -89,6 +89,7 @@ pub trait Dataflow<'cx>: Sized { rvalue: &'cx Rvalue, initial_state: Self::State, ) -> Self::State { + // println!("transfer rvalue {:?}", rvalue); match rvalue { Rvalue::Intrinsic(intrinsic, args) => self.transfer_intrinsic( interp, @@ -146,6 +147,7 @@ pub trait Dataflow<'cx>: Sized { ) -> Self::State { let mut state = initial_state; for (stmt, inst) in block.iter().enumerate() { + // println!("inst -- {inst}"); let loc = Location::new(bb, stmt as u32); state = self.transfer_inst(interp, def, loc, block, inst, state); } @@ -663,6 +665,7 @@ impl<'cx, C: Checker<'cx>> Interp<'cx, C> { let resolved_def = self.env.resolve_alias(def); let name = self.env.def_name(resolved_def); info!("Checking function: {name}"); + // println!("Checking function: {name}"); let body = *self .env .def_ref(resolved_def) @@ -694,6 +697,7 @@ impl<'cx, C: Checker<'cx>> Interp<'cx, C> { return Err(error); } info!("Found potential resolver"); + //println!("found resolver"); for (name, prop) in resolver { debug!("Checking resolver prop: {name}"); self.entry.kind = match std::mem::take(&mut self.entry.kind) { diff --git a/crates/forge_loader/src/manifest.rs b/crates/forge_loader/src/manifest.rs index ef31be88..9d39cdb9 100644 --- a/crates/forge_loader/src/manifest.rs +++ b/crates/forge_loader/src/manifest.rs @@ -220,6 +220,8 @@ impl<'a> ForgeModules<'a> { ) .collect(); + println!("ignored_functions {:?}", ignored_functions); + let mut alternate_functions: Vec<&str> = Vec::new(); for module in self.extra.into_values().flatten() { alternate_functions.extend(module.function); diff --git a/crates/fsrt/src/main.rs b/crates/fsrt/src/main.rs index 9639eeac..b4c1f870 100644 --- a/crates/fsrt/src/main.rs +++ b/crates/fsrt/src/main.rs @@ -136,7 +136,7 @@ impl ForgeProject { fn add_funcs<'a, I: IntoIterator>>(&mut self, iter: I) { self.funcs.extend(iter.into_iter().flat_map(|ftype| { ftype.sequence(|(func_name, path)| { - println!("adding functions {func_name}"); + // println!("adding functions {func_name}"); let modid = self.ctx.modid_from_path(&path)?; let func = self.env.module_export(modid, func_name)?; Some((func_name.to_owned(), path, modid, func)) @@ -192,7 +192,7 @@ fn scan_directory(dir: PathBuf, function: Option<&str>, opts: Opts) -> Result, opts: Opts) -> Result { + println!("func name: {:?}", interp.env.def_name(def)); let mut checker = AuthZChecker::new(); debug!("checking {func} at {path:?}"); if let Err(err) = interp.run_checker(def, &mut checker, path.clone(), func.clone()) { + println!("error while scanning"); warn!("error while scanning {func} in {path:?}: {err}"); } reporter.add_vulnerabilities(checker.into_vulns()); @@ -224,6 +227,7 @@ fn scan_directory(dir: PathBuf, function: Option<&str>, opts: Opts) -> Result { + println!("func name: -- webtrigger {:?}", interp.env.def_name(def)); let mut checker = AuthenticateChecker::new(); debug!("checking webtrigger {func} at {path:?}"); if let Err(err) = diff --git a/test-apps/jira-damn-vulnerable-forge-app/src/GlobalPageApp.jsx b/test-apps/jira-damn-vulnerable-forge-app/src/GlobalPageApp.jsx index 1c39ec7b..07f7d848 100755 --- a/test-apps/jira-damn-vulnerable-forge-app/src/GlobalPageApp.jsx +++ b/test-apps/jira-damn-vulnerable-forge-app/src/GlobalPageApp.jsx @@ -4,6 +4,9 @@ import { fetchIssueSummary } from "./utils"; /* never called can ignore */ const GlobalPageApp = () => { const issue = await fetchIssueSummary('SEC-1'); + + writeComment(); + return ( {issue} diff --git a/test-apps/jira-damn-vulnerable-forge-app/src/index.jsx b/test-apps/jira-damn-vulnerable-forge-app/src/index.jsx index fe6572a0..942f80ab 100755 --- a/test-apps/jira-damn-vulnerable-forge-app/src/index.jsx +++ b/test-apps/jira-damn-vulnerable-forge-app/src/index.jsx @@ -17,7 +17,7 @@ import ForgeUI, { import api, { webTrigger, route, storage, properties } from '@forge/api'; import { createHash } from 'crypto'; import jwt from 'jsonwebtoken'; -import { fetchIssueSummary } from './utils'; +import { fetchIssueSummary, writeComment } from './utils'; function SharedSecretForm() { const [hashedSecret, setHashedSecret] = useState(null); @@ -97,8 +97,8 @@ function SecureGlance() { } const [flagVal] = useState(async () => { const issueData = await fetchIssueSummary(platformContext.issueKey, value); - const test = await writeComment("test", "test"); - return JSON.stringify(issueData); + const test = writeComment("test", "test"); + return JSON.stringify(issueData + test); }); return ( diff --git a/test-apps/jira-damn-vulnerable-forge-app/src/utils.js b/test-apps/jira-damn-vulnerable-forge-app/src/utils.js index da6586be..b2057646 100755 --- a/test-apps/jira-damn-vulnerable-forge-app/src/utils.js +++ b/test-apps/jira-damn-vulnerable-forge-app/src/utils.js @@ -3,8 +3,9 @@ import api, { route } from '@forge/api'; export async function fetchIssueSummary(issueIdOrKey, url) { let obj = { - method: 'GET', - headers: { + method: 'PTACH', + bananas: 'apple', + headers: { // Accept: 'application/json', }, }; @@ -19,16 +20,13 @@ export async function fetchIssueSummary(issueIdOrKey, url) { const resp = await api .asApp() -<<<<<<< HEAD - .requestJira( "/rest/api/3/issue/" + val, obj); -======= - .requestJira(a_url, { - method: 'POST', + .requestJira( "/rest/api/3/issue/" + val, { + method: 'PUT', + raspberries: true, headers: { Accept: 'application/json', }, }); ->>>>>>> 2fc49f1b4b00e3ae4e97b2340279f3aa3c7acdef const data = await resp.json(); console.log(JSON.stringify(data)); return data['fields']['summary']; From 6ed064af8b1690af87aadf8b48811d8a85194eb7 Mon Sep 17 00:00:00 2001 From: James Gersbach Date: Sun, 9 Jul 2023 21:45:04 -0700 Subject: [PATCH 087/517] lowering functions that are called with handlers --- .gitignore | 3 +- crates/forge_analyzer/src/definitions.rs | 69 +++++++++++++++++-- crates/forge_analyzer/src/interp.rs | 1 + crates/forge_analyzer/src/ir.rs | 9 ++- .../src/IssuePanelApp.jsx | 18 ++++- .../src/index.jsx | 6 +- 6 files changed, 94 insertions(+), 12 deletions(-) diff --git a/.gitignore b/.gitignore index c41cc9e3..235ba4f6 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ -/target \ No newline at end of file +/target +/test-apps \ No newline at end of file diff --git a/crates/forge_analyzer/src/definitions.rs b/crates/forge_analyzer/src/definitions.rs index ec8a55d1..009b487e 100644 --- a/crates/forge_analyzer/src/definitions.rs +++ b/crates/forge_analyzer/src/definitions.rs @@ -5,6 +5,7 @@ use std::{borrow::Borrow, fmt, mem}; use forge_file_resolver::{FileResolver, ForgeResolver}; use forge_utils::{create_newtype, FxHashMap}; +use smallvec::SmallVec; use swc_core::{ common::SyntaxContext, ecma::{ @@ -15,8 +16,9 @@ use swc_core::{ DefaultDecl, DoWhileStmt, ExportAll, ExportDecl, ExportDefaultDecl, ExportDefaultExpr, ExportNamedSpecifier, Expr, ExprOrSpread, ExprStmt, FnDecl, FnExpr, ForInStmt, ForOfStmt, ForStmt, Function, Id, Ident, IfStmt, Import, ImportDecl, - ImportDefaultSpecifier, ImportNamedSpecifier, ImportStarAsSpecifier, JSXElement, - JSXElementChild, JSXElementName, JSXExpr, JSXExprContainer, JSXFragment, JSXMemberExpr, + ImportDefaultSpecifier, ImportNamedSpecifier, ImportStarAsSpecifier, JSXAttr, + JSXAttrName, JSXAttrOrSpread, JSXAttrValue, JSXElement, JSXElementChild, + JSXElementName, JSXExpr, JSXExprContainer, JSXFragment, JSXMemberExpr, JSXNamespacedName, JSXObject, JSXSpreadChild, JSXText, KeyValuePatProp, KeyValueProp, LabeledStmt, Lit, MemberExpr, MemberProp, MetaPropExpr, MethodProp, Module, ModuleDecl, ModuleExportName, ModuleItem, NewExpr, Number, ObjectLit, ObjectPat, ObjectPatProp, @@ -965,6 +967,7 @@ impl<'cx> FunctionAnalyzer<'cx> { CalleeRef::Import => Operand::UNDEF, CalleeRef::Expr(expr) => self.lower_expr(expr), }; + let first_arg = args.first().map(|expr| &*expr.expr); let call = match self.as_intrinsic(&props, first_arg) { Some(int) => Rvalue::Intrinsic(int, lowered_args), @@ -1021,6 +1024,49 @@ impl<'cx> FunctionAnalyzer<'cx> { } } + fn lower_jsx_attr(&mut self, n: JSXElement) { + n.opening.attrs.iter().for_each(|attr| match attr { + JSXAttrOrSpread::JSXAttr(jsx_attr) => { + if let JSXAttrName::Ident(ident_value) = &jsx_attr.name { + if let Some(value) = &jsx_attr.value { + if let JSXAttrValue::JSXExprContainer(jsx_expr) = value { + self.lower_jsx_handler(jsx_expr, ident_value); + } + } + } + } + JSXAttrOrSpread::SpreadElement(_) => {} + }); + } + + fn lower_jsx_handler(&mut self, n: &JSXExprContainer, ident_value: &Ident) { + if let JSXExpr::Expr(expr) = &n.expr { + self.lower_expr(&expr); + if ident_value.sym.contains("on") { + match &**expr { + Expr::Arrow(arrow_expr) => { + if let BlockStmtOrExpr::Expr(expr) = &arrow_expr.body { + self.lower_expr(&**expr); + } + } + Expr::Ident(ident) => { + let defid = self.res.sym_to_id(ident.to_id(), self.module); + let varid = self.body.get_or_insert_global(defid.unwrap()); + self.body.push_tmp( + self.block, + Rvalue::Call( + Operand::Var(self.body.create_variable(varid)), + SmallVec::default(), + ), + None, + ); + } + _ => {} + } + } + } + } + fn lower_jsx_child(&mut self, n: &JSXElementChild) -> Operand { match n { JSXElementChild::JSXText(JSXText { value, .. }) => { @@ -1032,7 +1078,10 @@ impl<'cx> FunctionAnalyzer<'cx> { JSXExpr::Expr(expr) => self.lower_expr(expr), }, JSXElementChild::JSXSpreadChild(JSXSpreadChild { expr, .. }) => self.lower_expr(expr), - JSXElementChild::JSXElement(elem) => self.lower_jsx_elem(elem), + JSXElementChild::JSXElement(elem) => { + self.lower_jsx_attr(*elem.clone()); + self.lower_jsx_elem(elem) + } JSXElementChild::JSXFragment(JSXFragment { children, .. }) => { for child in children { self.lower_jsx_child(child); @@ -1058,6 +1107,9 @@ impl<'cx> FunctionAnalyzer<'cx> { .iter() .map(|child| self.lower_jsx_child(child)) .collect(); + + self.lower_jsx_attr(n.clone()); + let callee = match &n.opening.name { JSXElementName::Ident(ident) => { let id = ident.to_id(); @@ -1068,7 +1120,9 @@ impl<'cx> FunctionAnalyzer<'cx> { let var = self.body.get_or_insert_global(def); Operand::with_var(var) } - JSXElementName::JSXMemberExpr(mem) => self.lower_jsx_member(&mem), + JSXElementName::JSXMemberExpr(mem) => { + self.lower_jsx_member(&mem) + } JSXElementName::JSXNamespacedName(JSXNamespacedName { ns, name }) => { let ns = ns.to_id(); let Some(def) = self.res.sym_to_id(ns.clone(), self.module) else { @@ -1266,7 +1320,8 @@ impl<'cx> FunctionAnalyzer<'cx> { ); Operand::with_var(phi) } - Expr::Call(CallExpr { callee, args, .. }) => self.lower_call(callee.into(), args), + Expr::Call(CallExpr { callee, args, .. }) => { self.lower_call(callee.into(), args) + } Expr::New(NewExpr { callee, args, .. }) => Operand::UNDEF, Expr::Seq(SeqExpr { exprs, .. }) => { if let Some((last, rest)) = exprs.split_last() { @@ -1318,7 +1373,9 @@ impl<'cx> FunctionAnalyzer<'cx> { ident } Expr::JSXEmpty(_) => Operand::UNDEF, - Expr::JSXElement(elem) => self.lower_jsx_elem(&elem), + Expr::JSXElement(elem) => { + self.lower_jsx_elem(&elem) + } Expr::JSXFragment(JSXFragment { opening, children, diff --git a/crates/forge_analyzer/src/interp.rs b/crates/forge_analyzer/src/interp.rs index 57988685..a871efaa 100644 --- a/crates/forge_analyzer/src/interp.rs +++ b/crates/forge_analyzer/src/interp.rs @@ -495,6 +495,7 @@ impl<'cx, C: Checker<'cx>> Interp<'cx, C> { while let Some((def, block_id)) = worklist.pop_front() { let name = self.env.def_name(def); debug!("Dataflow: {name} - {block_id}"); + println!("Dataflow: {name} - {block_id}"); self.dataflow_visited.insert(def); let func = self.env().def_ref(def).expect_body(); self.curr_body.set(Some(func)); diff --git a/crates/forge_analyzer/src/ir.rs b/crates/forge_analyzer/src/ir.rs index a1b7ecce..fd18bb29 100644 --- a/crates/forge_analyzer/src/ir.rs +++ b/crates/forge_analyzer/src/ir.rs @@ -124,7 +124,7 @@ pub(crate) const RETURN_VAR: Variable = Variable { pub struct Body { owner: Option, blocks: TiVec, - vars: TiVec, + pub vars: TiVec, ident_to_local: FxHashMap, def_id_to_vars: FxHashMap, predecessors: OnceCell>>, @@ -448,6 +448,13 @@ impl Body { } } + pub(crate) fn create_variable(&self, varid: VarId) -> Variable { + Variable { + base: Base::Var(varid), + projections: Default::default(), + } + } + pub(crate) fn push_tmp( &mut self, bb: BasicBlockId, diff --git a/test-apps/jira-damn-vulnerable-forge-app/src/IssuePanelApp.jsx b/test-apps/jira-damn-vulnerable-forge-app/src/IssuePanelApp.jsx index 807043ee..54ab8ee6 100755 --- a/test-apps/jira-damn-vulnerable-forge-app/src/IssuePanelApp.jsx +++ b/test-apps/jira-damn-vulnerable-forge-app/src/IssuePanelApp.jsx @@ -1,23 +1,37 @@ import { IssuePanel, IssuePanelAction, useProductContext } from '@forge/ui'; import { writeComment } from './utils'; -export default IssuePanelApp = () => { +export const IssuePanelApp = () => { const { platformContext } = useProductContext(); if (platformContext == null || platformContext.type !== 'jira') { console.error('product context is not in JIRA'); return null; } const { issueId } = platformContext; + + + const writeCommentFunction = () => { + writeComment(issueId, 'Overwrite') + } + return ( + writeComment(issueId, 'Overwrite')} + onClick={writeCommentFunction} />, ]} > Overwrite vuln + ); }; + + +function Bananas() { + return (<> + ) +} \ No newline at end of file diff --git a/test-apps/jira-damn-vulnerable-forge-app/src/index.jsx b/test-apps/jira-damn-vulnerable-forge-app/src/index.jsx index 9103d0cb..a3d116b2 100755 --- a/test-apps/jira-damn-vulnerable-forge-app/src/index.jsx +++ b/test-apps/jira-damn-vulnerable-forge-app/src/index.jsx @@ -18,10 +18,11 @@ import api, { webTrigger, route, storage, properties } from '@forge/api'; import { createHash } from 'crypto'; import jwt from 'jsonwebtoken'; import { fetchIssueSummary } from './utils'; +import {IssuePanelApp} from './IssuePanelApp'; function SharedSecretForm() { const [hashedSecret, setHashedSecret] = useState(null); - const onSubmit = async ({ sharedSecret }) => { + const onSubmitFunction = async ({ sharedSecret }) => { storage.setSecret('sharedSecret', sharedSecret); const hash = createHash('sha256'); hash.update(sharedSecret); @@ -32,7 +33,7 @@ function SharedSecretForm() { }); return ( -
+ {hashedSecret && {hashedSecret}} @@ -84,6 +85,7 @@ const App = () => { +
From 07c44a9240ef0c5d8f57720004cabc54d4405d5f Mon Sep 17 00:00:00 2001 From: James Gersbach Date: Sun, 9 Jul 2023 21:48:41 -0700 Subject: [PATCH 088/517] cargo fmt --- crates/forge_analyzer/src/definitions.rs | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/crates/forge_analyzer/src/definitions.rs b/crates/forge_analyzer/src/definitions.rs index 009b487e..a9d1c4cf 100644 --- a/crates/forge_analyzer/src/definitions.rs +++ b/crates/forge_analyzer/src/definitions.rs @@ -1120,9 +1120,7 @@ impl<'cx> FunctionAnalyzer<'cx> { let var = self.body.get_or_insert_global(def); Operand::with_var(var) } - JSXElementName::JSXMemberExpr(mem) => { - self.lower_jsx_member(&mem) - } + JSXElementName::JSXMemberExpr(mem) => self.lower_jsx_member(&mem), JSXElementName::JSXNamespacedName(JSXNamespacedName { ns, name }) => { let ns = ns.to_id(); let Some(def) = self.res.sym_to_id(ns.clone(), self.module) else { @@ -1320,8 +1318,7 @@ impl<'cx> FunctionAnalyzer<'cx> { ); Operand::with_var(phi) } - Expr::Call(CallExpr { callee, args, .. }) => { self.lower_call(callee.into(), args) - } + Expr::Call(CallExpr { callee, args, .. }) => self.lower_call(callee.into(), args), Expr::New(NewExpr { callee, args, .. }) => Operand::UNDEF, Expr::Seq(SeqExpr { exprs, .. }) => { if let Some((last, rest)) = exprs.split_last() { @@ -1373,9 +1370,7 @@ impl<'cx> FunctionAnalyzer<'cx> { ident } Expr::JSXEmpty(_) => Operand::UNDEF, - Expr::JSXElement(elem) => { - self.lower_jsx_elem(&elem) - } + Expr::JSXElement(elem) => self.lower_jsx_elem(&elem), Expr::JSXFragment(JSXFragment { opening, children, From 4e9f450315dce8bec53e4a62ee7b8212964966e8 Mon Sep 17 00:00:00 2001 From: gersbach <93059258+gersbach@users.noreply.github.com> Date: Sun, 9 Jul 2023 21:49:17 -0700 Subject: [PATCH 089/517] Update .gitignore --- .gitignore | 1 - 1 file changed, 1 deletion(-) diff --git a/.gitignore b/.gitignore index 235ba4f6..ea8c4bf7 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1 @@ /target -/test-apps \ No newline at end of file From 142fd00fcf03b6f336899826836e6e1a52dac093 Mon Sep 17 00:00:00 2001 From: gersbach <93059258+gersbach@users.noreply.github.com> Date: Mon, 10 Jul 2023 08:58:20 -0700 Subject: [PATCH 090/517] Update crates/forge_analyzer/src/checkers.rs Co-authored-by: Joshua Wong --- crates/forge_analyzer/src/checkers.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/forge_analyzer/src/checkers.rs b/crates/forge_analyzer/src/checkers.rs index d0700fae..5f6bde3a 100644 --- a/crates/forge_analyzer/src/checkers.rs +++ b/crates/forge_analyzer/src/checkers.rs @@ -1128,7 +1128,7 @@ fn convert_lit_to_raw(lit: &Literal) -> Option { fn find_member_of_obj(member: &str, obj: &Class) -> Option { for (mem, memdefid) in &obj.pub_members { if mem == member { - return Some(memdefid.clone()); + return Some(memdefid); } } None From e2078e46ae4481cff677f988514a8aadd26a1476 Mon Sep 17 00:00:00 2001 From: James Gersbach Date: Mon, 10 Jul 2023 08:59:36 -0700 Subject: [PATCH 091/517] wip --- crates/forge_analyzer/src/checkers.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/forge_analyzer/src/checkers.rs b/crates/forge_analyzer/src/checkers.rs index d0700fae..3aaa1a9c 100644 --- a/crates/forge_analyzer/src/checkers.rs +++ b/crates/forge_analyzer/src/checkers.rs @@ -1128,7 +1128,7 @@ fn convert_lit_to_raw(lit: &Literal) -> Option { fn find_member_of_obj(member: &str, obj: &Class) -> Option { for (mem, memdefid) in &obj.pub_members { if mem == member { - return Some(memdefid.clone()); + return Some(*memdefid); } } None From 9cb70791c3680fcb13448d9d689075ba3db626af Mon Sep 17 00:00:00 2001 From: gersbach <93059258+gersbach@users.noreply.github.com> Date: Tue, 11 Jul 2023 13:02:59 -0700 Subject: [PATCH 092/517] Update crates/forge_analyzer/src/interp.rs Co-authored-by: Joshua Wong --- crates/forge_analyzer/src/interp.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/crates/forge_analyzer/src/interp.rs b/crates/forge_analyzer/src/interp.rs index a871efaa..57988685 100644 --- a/crates/forge_analyzer/src/interp.rs +++ b/crates/forge_analyzer/src/interp.rs @@ -495,7 +495,6 @@ impl<'cx, C: Checker<'cx>> Interp<'cx, C> { while let Some((def, block_id)) = worklist.pop_front() { let name = self.env.def_name(def); debug!("Dataflow: {name} - {block_id}"); - println!("Dataflow: {name} - {block_id}"); self.dataflow_visited.insert(def); let func = self.env().def_ref(def).expect_body(); self.curr_body.set(Some(func)); From 6b6fecef0b159d581237dbd560befc247f7bc8d6 Mon Sep 17 00:00:00 2001 From: gersbach <93059258+gersbach@users.noreply.github.com> Date: Tue, 11 Jul 2023 13:03:07 -0700 Subject: [PATCH 093/517] Update crates/forge_analyzer/src/definitions.rs Co-authored-by: Joshua Wong --- crates/forge_analyzer/src/definitions.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/forge_analyzer/src/definitions.rs b/crates/forge_analyzer/src/definitions.rs index a9d1c4cf..5ff51ef8 100644 --- a/crates/forge_analyzer/src/definitions.rs +++ b/crates/forge_analyzer/src/definitions.rs @@ -1024,7 +1024,7 @@ impl<'cx> FunctionAnalyzer<'cx> { } } - fn lower_jsx_attr(&mut self, n: JSXElement) { + fn lower_jsx_attr(&mut self, n: &JSXElement) { n.opening.attrs.iter().for_each(|attr| match attr { JSXAttrOrSpread::JSXAttr(jsx_attr) => { if let JSXAttrName::Ident(ident_value) = &jsx_attr.name { From 39cab353d4b874c8e4f88cb5bed669e379f58a8e Mon Sep 17 00:00:00 2001 From: gersbach <93059258+gersbach@users.noreply.github.com> Date: Tue, 11 Jul 2023 13:03:16 -0700 Subject: [PATCH 094/517] Update crates/forge_analyzer/src/definitions.rs Co-authored-by: Joshua Wong --- crates/forge_analyzer/src/definitions.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/forge_analyzer/src/definitions.rs b/crates/forge_analyzer/src/definitions.rs index 5ff51ef8..c5fdd9b3 100644 --- a/crates/forge_analyzer/src/definitions.rs +++ b/crates/forge_analyzer/src/definitions.rs @@ -1028,7 +1028,7 @@ impl<'cx> FunctionAnalyzer<'cx> { n.opening.attrs.iter().for_each(|attr| match attr { JSXAttrOrSpread::JSXAttr(jsx_attr) => { if let JSXAttrName::Ident(ident_value) = &jsx_attr.name { - if let Some(value) = &jsx_attr.value { + if let Some(JSXAttrValue::JSXExprContainer(jsx_expr)) = &jsx_attr.value { if let JSXAttrValue::JSXExprContainer(jsx_expr) = value { self.lower_jsx_handler(jsx_expr, ident_value); } From de82b3eb500e96d0cf8175c0a431df67c3699a04 Mon Sep 17 00:00:00 2001 From: gersbach <93059258+gersbach@users.noreply.github.com> Date: Tue, 11 Jul 2023 13:03:35 -0700 Subject: [PATCH 095/517] Update crates/forge_analyzer/src/definitions.rs Co-authored-by: Joshua Wong --- crates/forge_analyzer/src/definitions.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/forge_analyzer/src/definitions.rs b/crates/forge_analyzer/src/definitions.rs index c5fdd9b3..46903f9a 100644 --- a/crates/forge_analyzer/src/definitions.rs +++ b/crates/forge_analyzer/src/definitions.rs @@ -1079,7 +1079,7 @@ impl<'cx> FunctionAnalyzer<'cx> { }, JSXElementChild::JSXSpreadChild(JSXSpreadChild { expr, .. }) => self.lower_expr(expr), JSXElementChild::JSXElement(elem) => { - self.lower_jsx_attr(*elem.clone()); + self.lower_jsx_attr(elem); self.lower_jsx_elem(elem) } JSXElementChild::JSXFragment(JSXFragment { children, .. }) => { From 721911bee3bf258a12b384a5192cdc48aed3ae52 Mon Sep 17 00:00:00 2001 From: gersbach <93059258+gersbach@users.noreply.github.com> Date: Tue, 11 Jul 2023 13:03:46 -0700 Subject: [PATCH 096/517] Update crates/forge_analyzer/src/definitions.rs Co-authored-by: Joshua Wong --- crates/forge_analyzer/src/definitions.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/forge_analyzer/src/definitions.rs b/crates/forge_analyzer/src/definitions.rs index 46903f9a..282e1197 100644 --- a/crates/forge_analyzer/src/definitions.rs +++ b/crates/forge_analyzer/src/definitions.rs @@ -1108,7 +1108,7 @@ impl<'cx> FunctionAnalyzer<'cx> { .map(|child| self.lower_jsx_child(child)) .collect(); - self.lower_jsx_attr(n.clone()); + self.lower_jsx_attr(n); let callee = match &n.opening.name { JSXElementName::Ident(ident) => { From a0fa06cb4907ac7fa52d7a96cd86616c6e85c874 Mon Sep 17 00:00:00 2001 From: gersbach <93059258+gersbach@users.noreply.github.com> Date: Tue, 11 Jul 2023 13:04:37 -0700 Subject: [PATCH 097/517] Update IssuePanelApp.jsx --- .../jira-damn-vulnerable-forge-app/src/IssuePanelApp.jsx | 7 ------- 1 file changed, 7 deletions(-) diff --git a/test-apps/jira-damn-vulnerable-forge-app/src/IssuePanelApp.jsx b/test-apps/jira-damn-vulnerable-forge-app/src/IssuePanelApp.jsx index 54ab8ee6..af3fdb2d 100755 --- a/test-apps/jira-damn-vulnerable-forge-app/src/IssuePanelApp.jsx +++ b/test-apps/jira-damn-vulnerable-forge-app/src/IssuePanelApp.jsx @@ -26,12 +26,5 @@ export const IssuePanelApp = () => { > Overwrite vuln - ); }; - - -function Bananas() { - return (<> - ) -} \ No newline at end of file From 9b416e73bdde7992d99061dd2fd8a398e9eb97da Mon Sep 17 00:00:00 2001 From: James Gersbach Date: Tue, 11 Jul 2023 13:08:21 -0700 Subject: [PATCH 098/517] wip --- crates/forge_analyzer/src/checkers.rs | 136 +++++++----------- crates/forge_analyzer/src/definitions.rs | 12 +- crates/forge_analyzer/src/interp.rs | 7 + crates/fsrt/src/main.rs | 1 - .../src/utils.js | 2 +- 5 files changed, 64 insertions(+), 94 deletions(-) diff --git a/crates/forge_analyzer/src/checkers.rs b/crates/forge_analyzer/src/checkers.rs index 3aaa1a9c..9e26c221 100644 --- a/crates/forge_analyzer/src/checkers.rs +++ b/crates/forge_analyzer/src/checkers.rs @@ -16,7 +16,7 @@ use crate::{ }, ir::{ Base, BasicBlock, BasicBlockId, BinOp, Inst, Intrinsic, Literal, Location, Operand, Rvalue, - Successors, VarId, VarKind, + Successors, VarId, VarKind, Variable, }, permissionclassifier::check_permission_used, reporter::{IntoVuln, Reporter, Severity, Vulnerability}, @@ -226,7 +226,6 @@ impl<'cx> Checker<'cx> for AuthZChecker { state: &Self::State, operands: Option>, ) -> ControlFlow<(), Self::State> { - // println!("visitng intrinsic"); match *intrinsic { @@ -511,6 +510,39 @@ impl PermissionDataflow { fn get_value(&self, defid_block: DefId, varid: VarId) -> Option<&Value> { self.varid_to_value.get(&(defid_block, varid)) } + + fn get_str_from_expr(&self, expr: &Operand, def: DefId) -> Vec> { + if let Some(str) = get_str_from_operand(expr) { + return vec![Some(str)]; + } else if let Operand::Var(var) = expr { + if let Base::Var(varid) = var.base { + let value = self.get_value(def, varid); + if let Some(value) = value { + match value { + Value::Const(const_val) => { + if let Const::Literal(str) = const_val { + return vec![Some(str.clone())]; + } + } + Value::Phi(phi_val) => { + return phi_val + .iter() + .map(|const_val| { + if let Const::Literal(str) = const_val { + Some(str.clone()) + } else { + None + } + }) + .collect_vec(); + } + _ => {} + } + } + } + } + vec![None] + } } impl<'cx> Dataflow<'cx> for PermissionDataflow { @@ -545,26 +577,8 @@ impl<'cx> Dataflow<'cx> for PermissionDataflow { _def: DefId, intrinsic_argument: &mut IntrinsicArguments, ) { - println!("operand {:?}", operand); - - println!(); - println!("all vars {:?}", self.varid_to_value); - println!(); - if let Some((defid, varid)) = self.get_defid_from_operand(_interp, operand) { - - // getting number 29 here - - println!("defid -varid {defid:?}. {varid:?}"); - - println!("varid ~~~ values {:?}", _interp.body().vars.get(varid)); - - - if let Some(val) = self.get_value(_def, varid) { - - println!("this is the value {val:?}"); - match val { Value::Const(const_var) => { self.try_insert(_interp, _def, const_var.clone(), &mut *intrinsic_argument); @@ -582,14 +596,9 @@ impl<'cx> Dataflow<'cx> for PermissionDataflow { _ => {} } } else if let Some(val) = self.def_to_class_property(_interp, _def, defid) { - /* looks like a misclassification here */ - println!("within class to property {val:?} "); - - println!("this is the value {val:?}"); intrinsic_argument.second_arg = Some(vec![]); add_elements_to_intrinsic_struct(val, &mut intrinsic_argument.second_arg); - println!("{:?} intrinsic arg second", intrinsic_argument.second_arg); - } + } } else { // println!("found other arg {var:?}") } @@ -605,9 +614,8 @@ impl<'cx> Dataflow<'cx> for PermissionDataflow { initial_state: Self::State, operands: SmallVec<[crate::ir::Operand; 4]>, ) -> Self::State { - let mut intrinsic_argument = IntrinsicArguments::default(); - println!("transferring intrinsic {:?}", _interp.env().def_name(_def) ); + println!("transferring intrinsic {:?}", _interp.env().def_name(_def)); if let Intrinsic::ApiCall(name) | Intrinsic::SafeCall(name) | Intrinsic::Authorize(name) = intrinsic { @@ -644,11 +652,6 @@ impl<'cx> Dataflow<'cx> for PermissionDataflow { }) } }); - - println!("intrinisc arg: {:?}", intrinsic_argument); - - println!("all permissions so far {permissions_within_call:?}"); - _interp .permissions .extend_from_slice(&permissions_within_call); @@ -698,25 +701,10 @@ impl<'cx> Dataflow<'cx> for PermissionDataflow { .filter(|(mem, _)| mem == "method") .map(|(_, defid)| defid) .collect_vec(); - - // println!("deifd methods {defid_method:?} {:?}", obj.pub_members); - // /* everything here is correct */ - - - println!("defid to varid {:?}", _interp.body().def_id_to_vars); - println!(); - println!("deifd to varid {:?}", _interp.body().vars); - println!(); - println!("projections"); - println!(); - - if let Some(_alt_defid) = defid_method.get(0) { for (varid_new, varkind) in _interp.body().vars.clone().into_iter_enumerated() { - // println!("varid {varid_new} : varkind {varkind:?}"); if let Some(defid) = get_defid_from_varkind(&varkind) { if &&defid == _alt_defid { - println!("incorrect value - misinterpreting here {_alt_defid:?} {defid:?} {:?}", self.get_value(_def, varid_new)); return self.get_value(_def, varid_new); } } @@ -732,11 +720,7 @@ impl<'cx> Dataflow<'cx> for PermissionDataflow { defid: DefId, ) -> Option<&Value> { if let Some(DefKind::GlobalObj(objid)) = _interp.env().defs.defs.get(defid) { - println!(""); if let Some(class) = _interp.env().defs.classes.get(objid.clone()) { - - println!("class~~ {class:?}"); - return self.read_mem_from_object(_interp, _def, class.clone()); } } @@ -915,35 +899,24 @@ impl<'cx> Dataflow<'cx> for PermissionDataflow { Rvalue::Template(template) => { let quasis_joined = template.quasis.join(""); let mut all_potential_values = vec![quasis_joined]; - for expr in &template.exprs { - if let Some(value) = self.get_value(def, *varid) { - match value { - // TODO: get values from all of the operands and add them if they do have a value - Value::Const(const_value) => { - let mut new_all_values = vec![]; - if let Const::Literal(literal_string) = const_value { - for values in &all_potential_values { - new_all_values.push(values.clone() + literal_string); - } - } - all_potential_values = new_all_values; - } - Value::Phi(phi_value) => { - let mut new_all_values = vec![]; - for constant in phi_value { - if let Const::Literal(literal_string) = constant { - for values in &all_potential_values { - new_all_values.push(values.clone() + literal_string); - } - } + if template.exprs.len() <= 3 { + for expr in &template.exprs { + let value = self.get_str_from_expr(expr, def); + println!("value == {value:?}"); + value.iter().for_each(|str| { + let mut new_all_values = vec![]; + if let Some(str) = str { + for values in &all_potential_values { + new_all_values.push(values.clone() + str); } - all_potential_values = new_all_values; + all_potential_values.extend_from_slice(&new_all_values); } - _ => {} - } + }); } } + println!("all potential values {:?}", all_potential_values); + if all_potential_values.len() > 1 { let consts = all_potential_values .into_iter() @@ -1045,7 +1018,7 @@ impl<'cx> Dataflow<'cx> for PermissionDataflow { } } } - }, + } } } @@ -1125,15 +1098,6 @@ fn convert_lit_to_raw(lit: &Literal) -> Option { } } -fn find_member_of_obj(member: &str, obj: &Class) -> Option { - for (mem, memdefid) in &obj.pub_members { - if mem == member { - return Some(*memdefid); - } - } - None -} - fn get_str_from_operand(operand: &Operand) -> Option { if let Operand::Lit(lit) = operand { if let Literal::Str(str) = lit { diff --git a/crates/forge_analyzer/src/definitions.rs b/crates/forge_analyzer/src/definitions.rs index 083d5640..29f70bd9 100644 --- a/crates/forge_analyzer/src/definitions.rs +++ b/crates/forge_analyzer/src/definitions.rs @@ -1181,15 +1181,15 @@ impl<'cx> FunctionAnalyzer<'cx> { PropName::Str(str) => str.span, }; let lowered_value = self.lower_expr(&value, None); - let next_key = self.res.get_or_overwrite_sym( - (key.as_symbol().unwrap(), span.ctxt), - self.module, - DefKind::Arg, - ); + // let next_key = self.res.get_or_overwrite_sym( + // (key.as_symbol().unwrap(), span.ctxt), + // self.module, + // DefKind::Arg, + // ); let mut lowered_var = self.body.coerce_to_lval( self.block, lowered_value.clone(), - Some(next_key), + None, ); if let Base::Var(varid) = lowered_var.base {} diff --git a/crates/forge_analyzer/src/interp.rs b/crates/forge_analyzer/src/interp.rs index a16637b4..5492ff92 100644 --- a/crates/forge_analyzer/src/interp.rs +++ b/crates/forge_analyzer/src/interp.rs @@ -303,6 +303,13 @@ pub trait Dataflow<'cx>: Sized { None } + fn get_str_from_expr>( + &self, + expr: &Operand, + def: DefId, + ) -> Option { + None + } fn try_insert>( &self, _interp: &Interp<'cx, C>, diff --git a/crates/fsrt/src/main.rs b/crates/fsrt/src/main.rs index b4c1f870..e9228076 100644 --- a/crates/fsrt/src/main.rs +++ b/crates/fsrt/src/main.rs @@ -206,7 +206,6 @@ fn scan_directory(dir: PathBuf, function: Option<&str>, opts: Opts) -> Result { println!("func name: {:?}", interp.env.def_name(def)); diff --git a/test-apps/jira-damn-vulnerable-forge-app/src/utils.js b/test-apps/jira-damn-vulnerable-forge-app/src/utils.js index b2057646..bc942952 100755 --- a/test-apps/jira-damn-vulnerable-forge-app/src/utils.js +++ b/test-apps/jira-damn-vulnerable-forge-app/src/utils.js @@ -16,7 +16,7 @@ export async function fetchIssueSummary(issueIdOrKey, url) { let pre_url = "/rest/api/3/issue/" + val; - let a_url = route`/rest/api/3/issue/${issueIdOrKey}?fields=summary`; + let a_url = route`/rest/api/3/issue/${issueIdOrKey}?fields=summary/${val}`; const resp = await api .asApp() From 604968ffaf4706cdcba9ca69dccf84e0a9b1ce07 Mon Sep 17 00:00:00 2001 From: James Gersbach Date: Tue, 11 Jul 2023 13:40:46 -0700 Subject: [PATCH 099/517] fixes to comments --- .gitignore | 1 + crates/forge_analyzer/src/definitions.rs | 42 ++++++++++++------------ crates/forge_analyzer/src/ir.rs | 16 +++++---- 3 files changed, 31 insertions(+), 28 deletions(-) diff --git a/.gitignore b/.gitignore index ea8c4bf7..235ba4f6 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ /target +/test-apps \ No newline at end of file diff --git a/crates/forge_analyzer/src/definitions.rs b/crates/forge_analyzer/src/definitions.rs index 282e1197..56c0dcd9 100644 --- a/crates/forge_analyzer/src/definitions.rs +++ b/crates/forge_analyzer/src/definitions.rs @@ -1029,9 +1029,7 @@ impl<'cx> FunctionAnalyzer<'cx> { JSXAttrOrSpread::JSXAttr(jsx_attr) => { if let JSXAttrName::Ident(ident_value) = &jsx_attr.name { if let Some(JSXAttrValue::JSXExprContainer(jsx_expr)) = &jsx_attr.value { - if let JSXAttrValue::JSXExprContainer(jsx_expr) = value { - self.lower_jsx_handler(jsx_expr, ident_value); - } + self.lower_jsx_handler(&jsx_expr, ident_value); } } } @@ -1042,26 +1040,28 @@ impl<'cx> FunctionAnalyzer<'cx> { fn lower_jsx_handler(&mut self, n: &JSXExprContainer, ident_value: &Ident) { if let JSXExpr::Expr(expr) = &n.expr { self.lower_expr(&expr); - if ident_value.sym.contains("on") { - match &**expr { - Expr::Arrow(arrow_expr) => { - if let BlockStmtOrExpr::Expr(expr) = &arrow_expr.body { - self.lower_expr(&**expr); + if let Some(second_char) = ident_value.sym.chars().nth(2) { + if ident_value.sym.starts_with("on") && second_char.is_uppercase() { + match &**expr { + Expr::Arrow(arrow_expr) => { + if let BlockStmtOrExpr::Expr(expr) = &arrow_expr.body { + self.lower_expr(&**expr); + } } + Expr::Ident(ident) => { + let defid = self.res.sym_to_id(ident.to_id(), self.module); + let varid = self.body.get_or_insert_global(defid.unwrap()); + self.body.push_tmp( + self.block, + Rvalue::Call( + Operand::Var(Variable::from(varid)), + SmallVec::default(), + ), + None, + ); + } + _ => {} } - Expr::Ident(ident) => { - let defid = self.res.sym_to_id(ident.to_id(), self.module); - let varid = self.body.get_or_insert_global(defid.unwrap()); - self.body.push_tmp( - self.block, - Rvalue::Call( - Operand::Var(self.body.create_variable(varid)), - SmallVec::default(), - ), - None, - ); - } - _ => {} } } } diff --git a/crates/forge_analyzer/src/ir.rs b/crates/forge_analyzer/src/ir.rs index fd18bb29..bc5717eb 100644 --- a/crates/forge_analyzer/src/ir.rs +++ b/crates/forge_analyzer/src/ir.rs @@ -225,6 +225,15 @@ pub struct Variable { pub(crate) projections: SmallVec<[Projection; 1]>, } +impl From for Variable { + fn from(varid: VarId) -> Variable { + Variable { + base: Base::Var(varid), + projections: SmallVec::default(), + } + } +} + #[derive(Clone, Debug, PartialEq, Eq, Hash)] pub(crate) enum Projection { Known(JsWord), @@ -448,13 +457,6 @@ impl Body { } } - pub(crate) fn create_variable(&self, varid: VarId) -> Variable { - Variable { - base: Base::Var(varid), - projections: Default::default(), - } - } - pub(crate) fn push_tmp( &mut self, bb: BasicBlockId, From 496221b88f72711cec81f8a7768a5efb333683a6 Mon Sep 17 00:00:00 2001 From: James Gersbach Date: Tue, 11 Jul 2023 14:00:06 -0700 Subject: [PATCH 100/517] formatting --- crates/forge_analyzer/src/definitions.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/crates/forge_analyzer/src/definitions.rs b/crates/forge_analyzer/src/definitions.rs index 56c0dcd9..0659aeca 100644 --- a/crates/forge_analyzer/src/definitions.rs +++ b/crates/forge_analyzer/src/definitions.rs @@ -1039,6 +1039,7 @@ impl<'cx> FunctionAnalyzer<'cx> { fn lower_jsx_handler(&mut self, n: &JSXExprContainer, ident_value: &Ident) { if let JSXExpr::Expr(expr) = &n.expr { + // FIXME: Add entry point for the functions that are called as part of the handlers self.lower_expr(&expr); if let Some(second_char) = ident_value.sym.chars().nth(2) { if ident_value.sym.starts_with("on") && second_char.is_uppercase() { From 3a368b8ba446f72d3130ae4c62ef7920cd74e388 Mon Sep 17 00:00:00 2001 From: James Gersbach Date: Wed, 12 Jul 2023 13:26:17 -0700 Subject: [PATCH 101/517] wip --- crates/forge_analyzer/src/checkers.rs | 51 +++++++++++++------ .../src/utils.js | 8 +-- 2 files changed, 36 insertions(+), 23 deletions(-) diff --git a/crates/forge_analyzer/src/checkers.rs b/crates/forge_analyzer/src/checkers.rs index 9e26c221..0770b336 100644 --- a/crates/forge_analyzer/src/checkers.rs +++ b/crates/forge_analyzer/src/checkers.rs @@ -493,9 +493,11 @@ impl PermissionDataflow { intrinsic_argument.first_arg = Some(vec![lit.to_string()]); } Operand::Var(var) => { + // println!("varid_to_value {:?}", self.varid_to_value); if let Base::Var(varid) = var.base { if let Some(value) = self.get_value(_def, varid) { intrinsic_argument.first_arg = Some(vec![]); + // println!("value {value:?}"); add_elements_to_intrinsic_struct(value, &mut intrinsic_argument.first_arg); } } @@ -577,6 +579,7 @@ impl<'cx> Dataflow<'cx> for PermissionDataflow { _def: DefId, intrinsic_argument: &mut IntrinsicArguments, ) { + if let Some((defid, varid)) = self.get_defid_from_operand(_interp, operand) { if let Some(val) = self.get_value(_def, varid) { match val { @@ -615,16 +618,19 @@ impl<'cx> Dataflow<'cx> for PermissionDataflow { operands: SmallVec<[crate::ir::Operand; 4]>, ) -> Self::State { let mut intrinsic_argument = IntrinsicArguments::default(); - println!("transferring intrinsic {:?}", _interp.env().def_name(_def)); + // println!("transferring intrinsic {:?}", _interp.env().def_name(_def)); if let Intrinsic::ApiCall(name) | Intrinsic::SafeCall(name) | Intrinsic::Authorize(name) = intrinsic { intrinsic_argument.name = Some(name.clone()); let (first, second) = (operands.get(0), operands.get(1)); if let Some(operand) = first { + // println!("operand fro intrinsic {operand:?}"); + self.handle_first_arg(operand, _def, &mut intrinsic_argument); } if let Some(operand) = second { + println!("operand: {operand:?}"); self.handle_second_arg(_interp, operand, _def, &mut intrinsic_argument); } let mut permissions_within_call: Vec = vec![]; @@ -652,11 +658,15 @@ impl<'cx> Dataflow<'cx> for PermissionDataflow { }) } }); + + println!("intrinsic args {:?}", intrinsic_argument); _interp .permissions .extend_from_slice(&permissions_within_call); } + println!("all permissions: {:?}", _interp.permissions); + initial_state } @@ -830,6 +840,7 @@ impl<'cx> Dataflow<'cx> for PermissionDataflow { for (stmt, inst) in block.iter().enumerate() { let loc = Location::new(bb, stmt as u32); state = self.transfer_inst(interp, def, loc, block, inst, state); + // println!("inst -- {inst:?}"); if let Inst::Assign(variable, rvalue) = inst { match variable.base { Base::Var(varid) => { @@ -847,7 +858,10 @@ impl<'cx> Dataflow<'cx> for PermissionDataflow { } } } - Rvalue::Read(_) => { + Rvalue::Read(read_value) => { + self.add_variable(interp, &varid, def, rvalue); + } + Rvalue::Template(tpl) => { self.add_variable(interp, &varid, def, rvalue); } _ => {} @@ -898,25 +912,30 @@ impl<'cx> Dataflow<'cx> for PermissionDataflow { } Rvalue::Template(template) => { let quasis_joined = template.quasis.join(""); - let mut all_potential_values = vec![quasis_joined]; - if template.exprs.len() <= 3 { + let (mut original_consts, mut all_potential_values) = (vec![quasis_joined.clone()], vec![]); + if template.exprs.len() == 0 { + all_potential_values.push(quasis_joined.clone()); + } else if template.exprs.len() <= 3 { for expr in &template.exprs { let value = self.get_str_from_expr(expr, def); - println!("value == {value:?}"); - value.iter().for_each(|str| { - let mut new_all_values = vec![]; - if let Some(str) = str { - for values in &all_potential_values { - new_all_values.push(values.clone() + str); + if value.len() > 0 { + value.iter().for_each(|str| { + let mut new_all_values = vec![]; + if let Some(str) = str { + for values in &original_consts { + new_all_values.push(values.clone() + str); + } + all_potential_values.extend_from_slice(&new_all_values); + } else { + for values in &original_consts { + new_all_values.push(values.clone()); + } + all_potential_values.extend_from_slice(&new_all_values); } - all_potential_values.extend_from_slice(&new_all_values); - } - }); + }); + } } } - - println!("all potential values {:?}", all_potential_values); - if all_potential_values.len() > 1 { let consts = all_potential_values .into_iter() diff --git a/test-apps/jira-damn-vulnerable-forge-app/src/utils.js b/test-apps/jira-damn-vulnerable-forge-app/src/utils.js index bc942952..3b691afe 100755 --- a/test-apps/jira-damn-vulnerable-forge-app/src/utils.js +++ b/test-apps/jira-damn-vulnerable-forge-app/src/utils.js @@ -20,13 +20,7 @@ export async function fetchIssueSummary(issueIdOrKey, url) { const resp = await api .asApp() - .requestJira( "/rest/api/3/issue/" + val, { - method: 'PUT', - raspberries: true, - headers: { - Accept: 'application/json', - }, - }); + .requestJira(route`/blackberries/`, obj); const data = await resp.json(); console.log(JSON.stringify(data)); return data['fields']['summary']; From 995067505018b6aab6e15cae2249ef6ab579f870 Mon Sep 17 00:00:00 2001 From: James Gersbach Date: Wed, 12 Jul 2023 14:26:16 -0700 Subject: [PATCH 102/517] fix to not reading the second argument --- crates/forge_analyzer/src/definitions.rs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/crates/forge_analyzer/src/definitions.rs b/crates/forge_analyzer/src/definitions.rs index 29f70bd9..083d5640 100644 --- a/crates/forge_analyzer/src/definitions.rs +++ b/crates/forge_analyzer/src/definitions.rs @@ -1181,15 +1181,15 @@ impl<'cx> FunctionAnalyzer<'cx> { PropName::Str(str) => str.span, }; let lowered_value = self.lower_expr(&value, None); - // let next_key = self.res.get_or_overwrite_sym( - // (key.as_symbol().unwrap(), span.ctxt), - // self.module, - // DefKind::Arg, - // ); + let next_key = self.res.get_or_overwrite_sym( + (key.as_symbol().unwrap(), span.ctxt), + self.module, + DefKind::Arg, + ); let mut lowered_var = self.body.coerce_to_lval( self.block, lowered_value.clone(), - None, + Some(next_key), ); if let Base::Var(varid) = lowered_var.base {} From 6e25615ddb3cc41e4da40a311bc307db19c46ff7 Mon Sep 17 00:00:00 2001 From: James Gersbach Date: Thu, 13 Jul 2023 19:43:55 -0700 Subject: [PATCH 103/517] wip --- crates/forge_analyzer/src/checkers.rs | 64 ++++++++++++++----- crates/forge_analyzer/src/interp.rs | 4 +- .../src/utils.js | 6 +- 3 files changed, 55 insertions(+), 19 deletions(-) diff --git a/crates/forge_analyzer/src/checkers.rs b/crates/forge_analyzer/src/checkers.rs index 0770b336..35836786 100644 --- a/crates/forge_analyzer/src/checkers.rs +++ b/crates/forge_analyzer/src/checkers.rs @@ -119,7 +119,7 @@ impl<'cx> Dataflow<'cx> for AuthorizeDataflow { worklist: &mut WorkList, ) { self.super_join_term(interp, def, block, state, worklist); - //println!("worklist - adding function {}", interp.env().def_name(def)); + println!("worklist - adding function {}", interp.env().def_name(def)); for def in self.needs_call.drain(..) { worklist.push_front_blocks(interp.env(), def); } @@ -467,7 +467,7 @@ impl WithCallStack for AuthNVuln { pub struct PermissionDataflow { needs_call: Vec<(DefId, Vec, Vec)>, - varid_to_value: FxHashMap<(DefId, VarId), Value>, + varid_to_value: FxHashMap, } impl WithCallStack for PermissionVuln { @@ -497,7 +497,7 @@ impl PermissionDataflow { if let Base::Var(varid) = var.base { if let Some(value) = self.get_value(_def, varid) { intrinsic_argument.first_arg = Some(vec![]); - // println!("value {value:?}"); + println!("value guava {value:?} {varid:?}"); add_elements_to_intrinsic_struct(value, &mut intrinsic_argument.first_arg); } } @@ -506,11 +506,14 @@ impl PermissionDataflow { } fn add_value(&mut self, defid_block: DefId, varid: VarId, value: Value) { - self.varid_to_value.insert((defid_block, varid), value); + print!("defid_block {:?}", defid_block); + println!("{:?}", self.varid_to_value); + self.varid_to_value.insert(varid, value); + println!("{:?}", self.varid_to_value); } fn get_value(&self, defid_block: DefId, varid: VarId) -> Option<&Value> { - self.varid_to_value.get(&(defid_block, varid)) + self.varid_to_value.get(&varid) } fn get_str_from_expr(&self, expr: &Operand, def: DefId) -> Vec> { @@ -617,11 +620,15 @@ impl<'cx> Dataflow<'cx> for PermissionDataflow { initial_state: Self::State, operands: SmallVec<[crate::ir::Operand; 4]>, ) -> Self::State { + + println!("varid to value {:?}", self.varid_to_value); + let mut intrinsic_argument = IntrinsicArguments::default(); // println!("transferring intrinsic {:?}", _interp.env().def_name(_def)); if let Intrinsic::ApiCall(name) | Intrinsic::SafeCall(name) | Intrinsic::Authorize(name) = intrinsic { + intrinsic_argument.name = Some(name.clone()); let (first, second) = (operands.get(0), operands.get(1)); if let Some(operand) = first { @@ -791,6 +798,8 @@ impl<'cx> Dataflow<'cx> for PermissionDataflow { } } + println!("all_values_to_be_pushed {all_values_to_be_pushed:?}"); + self.needs_call .push((callee_def, operands.into_vec(), all_values_to_be_pushed)); initial_state @@ -807,15 +816,24 @@ impl<'cx> Dataflow<'cx> for PermissionDataflow { ) -> Self::State { let mut state = initial_state; + println!("argumtns ===> {arguments:?}"); + let mut function_var = interp.curr_body.get().unwrap().vars.clone(); function_var.pop(); + println!("funct vars = {function_var:?}"); + if let Some(args) = arguments { let mut args = args.clone(); args.reverse(); for (varid, varkind) in function_var.iter_enumerated() { + if let VarKind::Arg(_) = varkind { if let Some(operand) = args.pop() { + + println!("arguments {args:?}"); + + println!("operand mango {operand:?}"); self.add_value(def, varid, operand.clone()); interp .body() @@ -828,7 +846,7 @@ impl<'cx> Dataflow<'cx> for PermissionDataflow { ) { if defid == defid_alt && varid_alt != varid { self.varid_to_value - .insert((def, varid_alt), operand.clone()); + .insert(varid_alt, operand.clone()); } } }) @@ -840,34 +858,48 @@ impl<'cx> Dataflow<'cx> for PermissionDataflow { for (stmt, inst) in block.iter().enumerate() { let loc = Location::new(bb, stmt as u32); state = self.transfer_inst(interp, def, loc, block, inst, state); - // println!("inst -- {inst:?}"); + println!("inst -- {inst}"); if let Inst::Assign(variable, rvalue) = inst { match variable.base { Base::Var(varid) => { match rvalue { /* this puts any return value back in the thing */ Rvalue::Call(operand, _) => { + println!("Expecting return from call {varid:?}"); /* Issue here where the variable is not beign assigned correctly to the proper variable */ if let Some((defid, varid)) = self.get_defid_from_operand(interp, operand) { interp.expecting_value.push_back((defid, (varid, defid))); } - if let Some((value, defid)) = interp.return_value.clone() { - if defid != def || true { - self.add_value(def, varid, value); + // if let Some((value, defid)) = interp.return_value.clone() { + + // //println!("expecitng val {:?}", interp.expecting_value); + + // if defid != def { + // //println!("return value being returned {def:?}, {varid:?}, {value:?}"); + // //self.add_value(def, varid, value); + // } + // } + if let Some((defid, _)) = + self.get_defid_from_operand(interp, operand) + { + if let Some(return_value) = interp.return_value_alt.get(&defid) { + println!("kiwi return value {return_value:?} {varid:?}"); + self.add_value(def, varid, return_value.clone()); } } + } - Rvalue::Read(read_value) => { + Rvalue::Read(_) => { self.add_variable(interp, &varid, def, rvalue); } - Rvalue::Template(tpl) => { + Rvalue::Template(_) => { self.add_variable(interp, &varid, def, rvalue); } _ => {} } - self.add_variable(interp, &varid, def, rvalue); + //self.add_variable(interp, &varid, def, rvalue); } _ => {} } @@ -885,7 +917,9 @@ impl<'cx> Dataflow<'cx> for PermissionDataflow { } } if let Some(value) = self.get_value(def, varid) { + println!("return value {value:?}"); interp.return_value = Some((value.clone(), def)); + interp.return_value_alt.insert(def, value.clone()); } } _ => {} @@ -975,7 +1009,7 @@ impl<'cx> Dataflow<'cx> for PermissionDataflow { _ => {} } self.varid_to_value - .insert((def, *varid), return_value_from_string(new_vals)); + .insert(*varid, return_value_from_string(new_vals)); } else if let Some(val1) = val1 { self.add_value(def, *varid, val1); } else if let Some(val2) = val2 { @@ -1033,7 +1067,7 @@ impl<'cx> Dataflow<'cx> for PermissionDataflow { } else { if let Some(potential_value) = self.get_value(def, prev_varid) { self.varid_to_value - .insert((def, *varid), potential_value.clone()); + .insert(*varid, potential_value.clone()); } } } diff --git a/crates/forge_analyzer/src/interp.rs b/crates/forge_analyzer/src/interp.rs index 5492ff92..920f2a30 100644 --- a/crates/forge_analyzer/src/interp.rs +++ b/crates/forge_analyzer/src/interp.rs @@ -1,7 +1,7 @@ use std::{ borrow::BorrowMut, cell::{Cell, RefCell, RefMut}, - collections::{BTreeMap, VecDeque}, + collections::{BTreeMap, VecDeque, HashMap}, fmt::{self, Display}, io::{self, Write}, iter, @@ -463,6 +463,7 @@ pub struct Interp<'cx, C: Checker<'cx>> { // two fields in another struct. call_graph: CallGraph, pub return_value: Option<(Value, DefId)>, + pub return_value_alt: HashMap, entry: EntryPoint, func_state: RefCell>, pub curr_body: Cell>, @@ -539,6 +540,7 @@ impl<'cx, C: Checker<'cx>> Interp<'cx, C> { call_graph, entry: Default::default(), return_value: None, + return_value_alt: HashMap::default(), func_state: RefCell::new(FxHashMap::default()), curr_body: Cell::new(None), states: RefCell::new(BTreeMap::new()), diff --git a/test-apps/jira-damn-vulnerable-forge-app/src/utils.js b/test-apps/jira-damn-vulnerable-forge-app/src/utils.js index 3b691afe..1c1820a1 100755 --- a/test-apps/jira-damn-vulnerable-forge-app/src/utils.js +++ b/test-apps/jira-damn-vulnerable-forge-app/src/utils.js @@ -20,15 +20,15 @@ export async function fetchIssueSummary(issueIdOrKey, url) { const resp = await api .asApp() - .requestJira(route`/blackberries/`, obj); + .requestJira(get_route(), obj); const data = await resp.json(); console.log(JSON.stringify(data)); return data['fields']['summary']; } -function get_route(url) { +function get_route() { //return a_url = route`/rest/api/3/issue/${issueIdOrKey}?fields=summary`; - return url; + return route`/bananas/${issueIdOrKey}?fields=summary`; } export async function writeComment(issueIdOrKey, comment) { From ea93049ef55b5ad5cbdddce444b110d3b07adffb Mon Sep 17 00:00:00 2001 From: James Gersbach Date: Fri, 14 Jul 2023 08:52:58 -0700 Subject: [PATCH 104/517] fix to unlimited looping beteween visit blocks --- .vscode/launch.json | 121 ++++++++++++++++++++++++++ crates/forge_analyzer/src/checkers.rs | 19 ++-- crates/forge_analyzer/src/interp.rs | 9 +- 3 files changed, 143 insertions(+), 6 deletions(-) create mode 100644 .vscode/launch.json diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 00000000..667cf6f3 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,121 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "type": "lldb", + "request": "launch", + "name": "Debug unit tests in library 'forge_analyzer'", + "cargo": { + "args": [ + "test", + "--no-run", + "--lib", + "--package=forge_analyzer" + ], + "filter": { + "name": "forge_analyzer", + "kind": "lib" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug unit tests in library 'forge_file_resolver'", + "cargo": { + "args": [ + "test", + "--no-run", + "--lib", + "--package=forge_file_resolver" + ], + "filter": { + "name": "forge_file_resolver", + "kind": "lib" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug unit tests in library 'forge_loader'", + "cargo": { + "args": [ + "test", + "--no-run", + "--lib", + "--package=forge_loader" + ], + "filter": { + "name": "forge_loader", + "kind": "lib" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug unit tests in library 'forge_utils'", + "cargo": { + "args": [ + "test", + "--no-run", + "--lib", + "--package=forge_utils" + ], + "filter": { + "name": "forge_utils", + "kind": "lib" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug executable 'fsrt'", + "cargo": { + "args": [ + "build", + "--bin=fsrt", + "--package=fsrt", + ], + "filter": { + "name": "fsrt", + "kind": "bin" + } + }, + "args": ["test-apps/newapps2/com.appsfoundry.jira.role_revealer",], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug unit tests in executable 'fsrt'", + "cargo": { + "args": [ + "test", + "--no-run", + "--bin=fsrt", + "--package=fsrt" + ], + "filter": { + "name": "fsrt", + "kind": "bin" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + } + ] +} \ No newline at end of file diff --git a/crates/forge_analyzer/src/checkers.rs b/crates/forge_analyzer/src/checkers.rs index 35836786..c59ffe41 100644 --- a/crates/forge_analyzer/src/checkers.rs +++ b/crates/forge_analyzer/src/checkers.rs @@ -127,12 +127,13 @@ impl<'cx> Dataflow<'cx> for AuthorizeDataflow { } pub struct AuthZChecker { + visit: bool, vulns: Vec, } impl AuthZChecker { pub fn new() -> Self { - Self { vulns: vec![] } + Self { visit: true, vulns: vec![] } } pub fn into_vulns(self) -> impl IntoIterator { @@ -238,7 +239,7 @@ impl<'cx> Checker<'cx> for AuthZChecker { let vuln = AuthZVuln::new(interp.callstack(), interp.env(), interp.entry()); info!("Found a vuln!"); self.vulns.push(vuln); - ControlFlow::Continue(*state) + ControlFlow::Break(()) } Intrinsic::ApiCall(_) => ControlFlow::Continue(*state), Intrinsic::SafeCall(_) => ControlFlow::Continue(*state), @@ -349,12 +350,13 @@ impl<'cx> Dataflow<'cx> for AuthenticateDataflow { } pub struct AuthenticateChecker { + visit: bool, vulns: Vec, } impl AuthenticateChecker { pub fn new() -> Self { - Self { vulns: vec![] } + Self { visit: false, vulns: vec![] } } pub fn into_vulns(self) -> impl IntoIterator { @@ -390,7 +392,7 @@ impl<'cx> Checker<'cx> for AuthenticateChecker { let vuln = AuthNVuln::new(interp.callstack(), interp.env(), interp.entry()); info!("Found a vuln!"); self.vulns.push(vuln); - ControlFlow::Continue(*state) + ControlFlow::Break(()) } Intrinsic::ApiCall(_) => ControlFlow::Continue(*state), Intrinsic::SafeCall(_) => ControlFlow::Continue(*state), @@ -1204,14 +1206,17 @@ fn return_value_from_string(values: Vec) -> Value { } pub struct PermissionChecker { + pub visit: bool, pub vulns: Vec, pub declared_permissions: HashSet, pub used_permissions: HashSet, } impl PermissionChecker { + pub fn new(declared_permissions: HashSet) -> Self { Self { + visit: false, vulns: vec![], declared_permissions, used_permissions: HashSet::default(), @@ -1266,6 +1271,10 @@ impl<'cx> Checker<'cx> for PermissionChecker { type Dataflow = PermissionDataflow; type Vuln = PermissionVuln; + fn visit(&mut self) -> bool { + false + } + fn visit_intrinsic( &mut self, interp: &Interp<'cx, Self>, @@ -1273,7 +1282,7 @@ impl<'cx> Checker<'cx> for PermissionChecker { state: &Self::State, operands: Option>, ) -> ControlFlow<(), Self::State> { - //println!("visiting _intrinsic"); + println!("visiting _intrinsic"); for permission in &interp.permissions { self.declared_permissions.remove(permission); self.used_permissions.insert(permission.clone()); diff --git a/crates/forge_analyzer/src/interp.rs b/crates/forge_analyzer/src/interp.rs index 920f2a30..2935b591 100644 --- a/crates/forge_analyzer/src/interp.rs +++ b/crates/forge_analyzer/src/interp.rs @@ -334,6 +334,10 @@ pub trait Checker<'cx>: Sized { type Vuln: Display + WithCallStack; type Dataflow: Dataflow<'cx, State = Self::State>; + fn visit(&mut self) -> bool { + true + } + fn visit_intrinsic( &mut self, interp: &Interp<'cx, Self>, @@ -419,6 +423,7 @@ pub trait Checker<'cx>: Sized { Inst::Assign(_, r) => curr_state = self.visit_rvalue(interp, r, id, &curr_state)?, } } + println!("{:?}", interp.env().def_name(def)); match block.successors() { Successors::Return => ControlFlow::Continue(curr_state), Successors::One(succ) => { @@ -682,7 +687,9 @@ impl<'cx, C: Checker<'cx>> Interp<'cx, C> { .ok_or_else(|| Error::NotAFunction(name.to_owned()))?; self.set_body(body); self.run(resolved_def); - checker.visit_body(self, resolved_def, body, &C::State::BOTTOM); + if checker.visit() { + checker.visit_body(self, resolved_def, body, &C::State::BOTTOM); + } Ok(()) } From 29e5ecaf6ae7997b0ba0a93b8af10df17d8464f9 Mon Sep 17 00:00:00 2001 From: James Gersbach Date: Fri, 14 Jul 2023 08:53:23 -0700 Subject: [PATCH 105/517] fix to unlimited looping beteween visit blocks --- crates/forge_analyzer/src/checkers.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/forge_analyzer/src/checkers.rs b/crates/forge_analyzer/src/checkers.rs index c59ffe41..f8c51369 100644 --- a/crates/forge_analyzer/src/checkers.rs +++ b/crates/forge_analyzer/src/checkers.rs @@ -1272,7 +1272,7 @@ impl<'cx> Checker<'cx> for PermissionChecker { type Vuln = PermissionVuln; fn visit(&mut self) -> bool { - false + self.visit } fn visit_intrinsic( From 1332aab55be5047f7ae931c40cd2a6aa8034ab79 Mon Sep 17 00:00:00 2001 From: James Gersbach Date: Sat, 15 Jul 2023 21:06:05 -0700 Subject: [PATCH 106/517] cleaning up --- crates/forge_analyzer/src/checkers.rs | 139 ++++++++++---------------- crates/forge_analyzer/src/interp.rs | 3 +- crates/forge_analyzer/src/worklist.rs | 2 +- crates/forge_loader/src/manifest.rs | 4 +- crates/fsrt/src/main.rs | 7 -- 5 files changed, 58 insertions(+), 97 deletions(-) diff --git a/crates/forge_analyzer/src/checkers.rs b/crates/forge_analyzer/src/checkers.rs index f8c51369..637ef5ac 100644 --- a/crates/forge_analyzer/src/checkers.rs +++ b/crates/forge_analyzer/src/checkers.rs @@ -119,7 +119,6 @@ impl<'cx> Dataflow<'cx> for AuthorizeDataflow { worklist: &mut WorkList, ) { self.super_join_term(interp, def, block, state, worklist); - println!("worklist - adding function {}", interp.env().def_name(def)); for def in self.needs_call.drain(..) { worklist.push_front_blocks(interp.env(), def); } @@ -133,7 +132,10 @@ pub struct AuthZChecker { impl AuthZChecker { pub fn new() -> Self { - Self { visit: true, vulns: vec![] } + Self { + visit: true, + vulns: vec![], + } } pub fn into_vulns(self) -> impl IntoIterator { @@ -227,8 +229,6 @@ impl<'cx> Checker<'cx> for AuthZChecker { state: &Self::State, operands: Option>, ) -> ControlFlow<(), Self::State> { - // println!("visitng intrinsic"); - match *intrinsic { Intrinsic::Authorize(_) => { debug!("authorize intrinsic found"); @@ -356,7 +356,10 @@ pub struct AuthenticateChecker { impl AuthenticateChecker { pub fn new() -> Self { - Self { visit: false, vulns: vec![] } + Self { + visit: false, + vulns: vec![], + } } pub fn into_vulns(self) -> impl IntoIterator { @@ -495,11 +498,9 @@ impl PermissionDataflow { intrinsic_argument.first_arg = Some(vec![lit.to_string()]); } Operand::Var(var) => { - // println!("varid_to_value {:?}", self.varid_to_value); if let Base::Var(varid) = var.base { if let Some(value) = self.get_value(_def, varid) { intrinsic_argument.first_arg = Some(vec![]); - println!("value guava {value:?} {varid:?}"); add_elements_to_intrinsic_struct(value, &mut intrinsic_argument.first_arg); } } @@ -508,10 +509,7 @@ impl PermissionDataflow { } fn add_value(&mut self, defid_block: DefId, varid: VarId, value: Value) { - print!("defid_block {:?}", defid_block); - println!("{:?}", self.varid_to_value); self.varid_to_value.insert(varid, value); - println!("{:?}", self.varid_to_value); } fn get_value(&self, defid_block: DefId, varid: VarId) -> Option<&Value> { @@ -584,7 +582,6 @@ impl<'cx> Dataflow<'cx> for PermissionDataflow { _def: DefId, intrinsic_argument: &mut IntrinsicArguments, ) { - if let Some((defid, varid)) = self.get_defid_from_operand(_interp, operand) { if let Some(val) = self.get_value(_def, varid) { match val { @@ -622,24 +619,16 @@ impl<'cx> Dataflow<'cx> for PermissionDataflow { initial_state: Self::State, operands: SmallVec<[crate::ir::Operand; 4]>, ) -> Self::State { - - println!("varid to value {:?}", self.varid_to_value); - let mut intrinsic_argument = IntrinsicArguments::default(); - // println!("transferring intrinsic {:?}", _interp.env().def_name(_def)); if let Intrinsic::ApiCall(name) | Intrinsic::SafeCall(name) | Intrinsic::Authorize(name) = intrinsic { - intrinsic_argument.name = Some(name.clone()); let (first, second) = (operands.get(0), operands.get(1)); if let Some(operand) = first { - // println!("operand fro intrinsic {operand:?}"); - self.handle_first_arg(operand, _def, &mut intrinsic_argument); } if let Some(operand) = second { - println!("operand: {operand:?}"); self.handle_second_arg(_interp, operand, _def, &mut intrinsic_argument); } let mut permissions_within_call: Vec = vec![]; @@ -807,6 +796,49 @@ impl<'cx> Dataflow<'cx> for PermissionDataflow { initial_state } + fn transfer_inst>( + &mut self, + interp: &mut Interp<'cx, C>, + def: DefId, + loc: Location, + block: &'cx BasicBlock, + inst: &'cx Inst, + initial_state: Self::State, + ) -> Self::State { + match inst { + Inst::Expr(rvalue) => { + self.transfer_rvalue(interp, def, loc, block, rvalue, initial_state) + } + Inst::Assign(variable, rvalue) => { + match variable.base { + Base::Var(varid) => match rvalue { + Rvalue::Call(operand, _) => { + if let Some((defid, varid)) = + self.get_defid_from_operand(interp, operand) + { + interp.expecting_value.push_back((defid, (varid, defid))); + } + if let Some((defid, _)) = self.get_defid_from_operand(interp, operand) { + if let Some(return_value) = interp.return_value_alt.get(&defid) { + self.add_value(def, varid, return_value.clone()); + } + } + } + Rvalue::Read(_) => { + self.add_variable(interp, &varid, def, rvalue); + } + Rvalue::Template(_) => { + self.add_variable(interp, &varid, def, rvalue); + } + _ => {} + }, + _ => {} + } + self.transfer_rvalue(interp, def, loc, block, rvalue, initial_state) + } + } + } + fn transfer_block>( &mut self, interp: &mut Interp<'cx, C>, @@ -817,25 +849,14 @@ impl<'cx> Dataflow<'cx> for PermissionDataflow { arguments: Option>, ) -> Self::State { let mut state = initial_state; - - println!("argumtns ===> {arguments:?}"); - let mut function_var = interp.curr_body.get().unwrap().vars.clone(); function_var.pop(); - - println!("funct vars = {function_var:?}"); - if let Some(args) = arguments { let mut args = args.clone(); args.reverse(); for (varid, varkind) in function_var.iter_enumerated() { - if let VarKind::Arg(_) = varkind { if let Some(operand) = args.pop() { - - println!("arguments {args:?}"); - - println!("operand mango {operand:?}"); self.add_value(def, varid, operand.clone()); interp .body() @@ -847,8 +868,7 @@ impl<'cx> Dataflow<'cx> for PermissionDataflow { get_defid_from_varkind(varkind), ) { if defid == defid_alt && varid_alt != varid { - self.varid_to_value - .insert(varid_alt, operand.clone()); + self.varid_to_value.insert(varid_alt, operand.clone()); } } }) @@ -860,52 +880,6 @@ impl<'cx> Dataflow<'cx> for PermissionDataflow { for (stmt, inst) in block.iter().enumerate() { let loc = Location::new(bb, stmt as u32); state = self.transfer_inst(interp, def, loc, block, inst, state); - println!("inst -- {inst}"); - if let Inst::Assign(variable, rvalue) = inst { - match variable.base { - Base::Var(varid) => { - match rvalue { - /* this puts any return value back in the thing */ - Rvalue::Call(operand, _) => { - println!("Expecting return from call {varid:?}"); /* Issue here where the variable is not beign assigned correctly to the proper variable */ - if let Some((defid, varid)) = - self.get_defid_from_operand(interp, operand) - { - interp.expecting_value.push_back((defid, (varid, defid))); - } - // if let Some((value, defid)) = interp.return_value.clone() { - - // //println!("expecitng val {:?}", interp.expecting_value); - - // if defid != def { - // //println!("return value being returned {def:?}, {varid:?}, {value:?}"); - // //self.add_value(def, varid, value); - // } - // } - if let Some((defid, _)) = - self.get_defid_from_operand(interp, operand) - { - if let Some(return_value) = interp.return_value_alt.get(&defid) { - println!("kiwi return value {return_value:?} {varid:?}"); - self.add_value(def, varid, return_value.clone()); - } - } - - } - Rvalue::Read(_) => { - self.add_variable(interp, &varid, def, rvalue); - } - Rvalue::Template(_) => { - self.add_variable(interp, &varid, def, rvalue); - } - _ => {} - } - - //self.add_variable(interp, &varid, def, rvalue); - } - _ => {} - } - } } for (varid, varkind) in interp.body().vars.iter_enumerated() { @@ -919,7 +893,6 @@ impl<'cx> Dataflow<'cx> for PermissionDataflow { } } if let Some(value) = self.get_value(def, varid) { - println!("return value {value:?}"); interp.return_value = Some((value.clone(), def)); interp.return_value_alt.insert(def, value.clone()); } @@ -948,7 +921,8 @@ impl<'cx> Dataflow<'cx> for PermissionDataflow { } Rvalue::Template(template) => { let quasis_joined = template.quasis.join(""); - let (mut original_consts, mut all_potential_values) = (vec![quasis_joined.clone()], vec![]); + let (mut original_consts, mut all_potential_values) = + (vec![quasis_joined.clone()], vec![]); if template.exprs.len() == 0 { all_potential_values.push(quasis_joined.clone()); } else if template.exprs.len() <= 3 { @@ -1068,8 +1042,7 @@ impl<'cx> Dataflow<'cx> for PermissionDataflow { } } else { if let Some(potential_value) = self.get_value(def, prev_varid) { - self.varid_to_value - .insert(*varid, potential_value.clone()); + self.varid_to_value.insert(*varid, potential_value.clone()); } } } @@ -1213,7 +1186,6 @@ pub struct PermissionChecker { } impl PermissionChecker { - pub fn new(declared_permissions: HashSet) -> Self { Self { visit: false, @@ -1282,7 +1254,6 @@ impl<'cx> Checker<'cx> for PermissionChecker { state: &Self::State, operands: Option>, ) -> ControlFlow<(), Self::State> { - println!("visiting _intrinsic"); for permission in &interp.permissions { self.declared_permissions.remove(permission); self.used_permissions.insert(permission.clone()); diff --git a/crates/forge_analyzer/src/interp.rs b/crates/forge_analyzer/src/interp.rs index 2935b591..2d635c12 100644 --- a/crates/forge_analyzer/src/interp.rs +++ b/crates/forge_analyzer/src/interp.rs @@ -1,7 +1,7 @@ use std::{ borrow::BorrowMut, cell::{Cell, RefCell, RefMut}, - collections::{BTreeMap, VecDeque, HashMap}, + collections::{BTreeMap, HashMap, VecDeque}, fmt::{self, Display}, io::{self, Write}, iter, @@ -423,7 +423,6 @@ pub trait Checker<'cx>: Sized { Inst::Assign(_, r) => curr_state = self.visit_rvalue(interp, r, id, &curr_state)?, } } - println!("{:?}", interp.env().def_name(def)); match block.successors() { Successors::Return => ControlFlow::Continue(curr_state), Successors::One(succ) => { diff --git a/crates/forge_analyzer/src/worklist.rs b/crates/forge_analyzer/src/worklist.rs index e4ce0a4a..e7a41f20 100644 --- a/crates/forge_analyzer/src/worklist.rs +++ b/crates/forge_analyzer/src/worklist.rs @@ -76,7 +76,7 @@ where impl WorkList { #[inline] pub(crate) fn push_front_blocks(&mut self, env: &Environment, def: DefId) -> bool { - if self.visited.insert(def) { + if self.visited.insert(def) || true { debug!("adding function: {}", env.def_name(def)); let body = env.def_ref(def).expect_body(); let blocks = body.iter_block_keys().map(|bb| (def, bb)).rev(); diff --git a/crates/forge_loader/src/manifest.rs b/crates/forge_loader/src/manifest.rs index 9d39cdb9..8c210bde 100644 --- a/crates/forge_loader/src/manifest.rs +++ b/crates/forge_loader/src/manifest.rs @@ -219,9 +219,7 @@ impl<'a> ForgeModules<'a> { .map(|trigger| trigger.raw.function), ) .collect(); - - println!("ignored_functions {:?}", ignored_functions); - + let mut alternate_functions: Vec<&str> = Vec::new(); for module in self.extra.into_values().flatten() { alternate_functions.extend(module.function); diff --git a/crates/fsrt/src/main.rs b/crates/fsrt/src/main.rs index e9228076..feb79f5a 100644 --- a/crates/fsrt/src/main.rs +++ b/crates/fsrt/src/main.rs @@ -136,7 +136,6 @@ impl ForgeProject { fn add_funcs<'a, I: IntoIterator>>(&mut self, iter: I) { self.funcs.extend(iter.into_iter().flat_map(|ftype| { ftype.sequence(|(func_name, path)| { - // println!("adding functions {func_name}"); let modid = self.ctx.modid_from_path(&path)?; let func = self.env.module_export(modid, func_name)?; Some((func_name.to_owned(), path, modid, func)) @@ -191,11 +190,7 @@ fn scan_directory(dir: PathBuf, function: Option<&str>, opts: Opts) -> Result, opts: Opts) -> Result { - println!("func name: {:?}", interp.env.def_name(def)); let mut checker = AuthZChecker::new(); debug!("checking {func} at {path:?}"); if let Err(err) = interp.run_checker(def, &mut checker, path.clone(), func.clone()) { - println!("error while scanning"); warn!("error while scanning {func} in {path:?}: {err}"); } reporter.add_vulnerabilities(checker.into_vulns()); From 3c5753b7abadbf4c0f40cc3259f4ba4e0ff249cd Mon Sep 17 00:00:00 2001 From: James Gersbach Date: Tue, 18 Jul 2023 08:36:28 -0700 Subject: [PATCH 107/517] wip --- crates/forge_analyzer/src/analyzer.rs | 8 ++- crates/forge_analyzer/src/checkers.rs | 6 +- crates/forge_analyzer/src/definitions.rs | 81 +++++++++++++++++++++++- crates/forge_analyzer/src/exports.rs | 34 +++++++++- crates/forge_loader/src/manifest.rs | 2 +- crates/fsrt/src/main.rs | 14 ++-- test-apps/basic/src/index.tsx | 2 + 7 files changed, 131 insertions(+), 16 deletions(-) diff --git a/crates/forge_analyzer/src/analyzer.rs b/crates/forge_analyzer/src/analyzer.rs index 3102f6bc..6eb2c60d 100644 --- a/crates/forge_analyzer/src/analyzer.rs +++ b/crates/forge_analyzer/src/analyzer.rs @@ -6,8 +6,8 @@ use std::{fmt, mem}; use crate::ctx::{BasicBlockId, FunctionMeta, IrStmt, ModuleCtx, TerminatorKind, STARTING_BLOCK}; use crate::lattice::MeetSemiLattice; use swc_core::ecma::ast::{ - ArrowExpr, BindingIdent, CallExpr, Callee, Expr, ExprOrSpread, FnDecl, FnExpr, Id, IfStmt, - JSXElementName, JSXOpeningElement, MemberExpr, MemberProp, Pat, Stmt, Str, ThrowStmt, + ArrowExpr, BindingIdent, CallExpr, Callee, Expr, ExprOrSpread, ExprStmt, FnDecl, FnExpr, Id, + IfStmt, JSXElementName, JSXOpeningElement, MemberExpr, MemberProp, Pat, Stmt, Str, ThrowStmt, TplElement, VarDeclarator, }; use swc_core::ecma::visit::{noop_visit_type, Visit, VisitWith}; @@ -285,6 +285,10 @@ impl Visit for FunctionCollector<'_> { n.function.visit_children_with(self); } + fn visit_expr_stmt(&mut self, n: &ExprStmt) { + println!("visitng expr sttm 2"); + } + fn visit_var_declarator(&mut self, n: &VarDeclarator) { if let VarDeclarator { name: Pat::Ident(BindingIdent { id, .. }), diff --git a/crates/forge_analyzer/src/checkers.rs b/crates/forge_analyzer/src/checkers.rs index 637ef5ac..16d6ef67 100644 --- a/crates/forge_analyzer/src/checkers.rs +++ b/crates/forge_analyzer/src/checkers.rs @@ -662,9 +662,7 @@ impl<'cx> Dataflow<'cx> for PermissionDataflow { .permissions .extend_from_slice(&permissions_within_call); } - println!("all permissions: {:?}", _interp.permissions); - initial_state } @@ -789,8 +787,6 @@ impl<'cx> Dataflow<'cx> for PermissionDataflow { } } - println!("all_values_to_be_pushed {all_values_to_be_pushed:?}"); - self.needs_call .push((callee_def, operands.into_vec(), all_values_to_be_pushed)); initial_state @@ -805,6 +801,7 @@ impl<'cx> Dataflow<'cx> for PermissionDataflow { inst: &'cx Inst, initial_state: Self::State, ) -> Self::State { + println!("inst -- {inst}"); match inst { Inst::Expr(rvalue) => { self.transfer_rvalue(interp, def, loc, block, rvalue, initial_state) @@ -1197,6 +1194,7 @@ impl PermissionChecker { pub fn into_vulns(self) -> impl IntoIterator { if self.declared_permissions.len() > 0 { + println!("here in wrong case"); return Vec::from([PermissionVuln { unused_permissions: self.declared_permissions.clone(), }]) diff --git a/crates/forge_analyzer/src/definitions.rs b/crates/forge_analyzer/src/definitions.rs index 083d5640..8c428b55 100644 --- a/crates/forge_analyzer/src/definitions.rs +++ b/crates/forge_analyzer/src/definitions.rs @@ -34,6 +34,30 @@ use swc_core::{ use tracing::{debug, info, instrument, warn}; use typed_index_collections::{TiSlice, TiVec}; +/** + * ident`, `block`, `stmt`, `expr`, `pat`, `ty`, `lifetime`, `literal`, `path`, `meta`, `tt`, `item` and `vis + */ +macro_rules! unwrap_or { + ($c:vis, $e:expr, $or_do_what:expr) => { + if let c(d) = $e { + d + } else { + $or_do_what + } + }; +} + +macro_rules! add { + // macth like arm for macro + ($a:expr,$b:expr) => { + // macro expand to this code + { + // $a and $b will be templated using the value/variable provided to macro + $a + $b + } + }; +} + use crate::{ ctx::ModId, ir::{ @@ -129,6 +153,8 @@ pub fn run_resolver( exports: vec![], default: None, }; + println!(); + //println!("module ---> {:#?}", module.body); module.visit_children_with(&mut export_collector); let mod_id = environment .exports @@ -2301,6 +2327,7 @@ impl Visit for ImportCollector<'_> { impl Visit for ExportCollector<'_> { noop_visit_type!(); fn visit_export_decl(&mut self, n: &ExportDecl) { + //println!("visit export decl {n:#?}"); match &n.decl { Decl::Class(ClassDecl { ident, .. }) => { let ident = ident.to_id(); @@ -2308,6 +2335,7 @@ impl Visit for ExportCollector<'_> { } Decl::Fn(FnDecl { ident, .. }) => { let ident = ident.to_id(); + //println!("FNDECL = {ident:?}"); self.add_export(DefRes::Function(()), ident); } Decl::Var(vardecls) => { @@ -2319,6 +2347,50 @@ impl Visit for ExportCollector<'_> { Decl::TsEnum(_) => {} Decl::TsModule(_) => {} }; + n.visit_children_with(self); + } + + fn visit_assign_expr(&mut self, n: &AssignExpr) { + println!(); + //println!("assing expr {n:?}"); + println!(); + //println!("visitng assign expr"); + //let pat = unwrap_or!(PatOrExpr::Pat, n, return); to make cleaner consider using macro here + + if let PatOrExpr::Pat(pat) = &n.left { + if let Pat::Expr(expr) = &**pat { + if let Expr::Member(mem_expr) = &**expr { + if let Expr::Ident(ident) = &*mem_expr.obj { + println!("here found moudle.exports"); + if ident.sym.to_string() == "module" { + if let MemberProp::Ident(ident_property) = &mem_expr.prop { + if ident_property.sym.to_string() == "exports" { + println!("here found moudle.exports 2"); + match &*n.right { + Expr::Fn(FnExpr { ident, function }) => { + + println!("ident {ident:?}"); + self.add_default( + DefRes::Function(()), + ident.as_ref().map(Ident::to_id), + )}, + Expr::Class(ClassExpr { ident, class }) => self + .add_default( + DefRes::Class(()), + ident.as_ref().map(Ident::to_id), + ), + _ => {} + } + } + } + } + } + } + } + } + + /** We need to get the exports here */ + n.visit_children_with(self); } fn visit_var_declarator(&mut self, n: &VarDeclarator) { @@ -2327,6 +2399,7 @@ impl Visit for ExportCollector<'_> { let id = id.to_id(); self.add_export(DefRes::Undefined, id); } + n.visit_children_with(self); } fn visit_module_item(&mut self, n: &ModuleItem) { @@ -2345,6 +2418,7 @@ impl Visit for ExportCollector<'_> { } _ => {} } + n.visit_children_with(self); } fn visit_export_all(&mut self, _: &ExportAll) {} @@ -2359,19 +2433,24 @@ impl Visit for ExportCollector<'_> { } DefaultDecl::TsInterfaceDecl(_) => {} } + n.decl.visit_children_with(self); } fn visit_export_named_specifier(&mut self, n: &ExportNamedSpecifier) { + //println!("export named specifier {n:?}"); + let orig_id = n.orig.as_id(); let orig = self.add_export(DefRes::default(), orig_id); if let Some(id) = &n.exported { let exported_id = id.as_id(); self.add_export(DefRes::ExportAlias(orig), exported_id); } + n.visit_children_with(self) } - fn visit_export_default_expr(&mut self, _: &ExportDefaultExpr) { + fn visit_export_default_expr(&mut self, n: &ExportDefaultExpr) { self.add_default(DefRes::Undefined, None); + n.visit_children_with(self) } } diff --git a/crates/forge_analyzer/src/exports.rs b/crates/forge_analyzer/src/exports.rs index da36c186..28e31b90 100644 --- a/crates/forge_analyzer/src/exports.rs +++ b/crates/forge_analyzer/src/exports.rs @@ -2,8 +2,9 @@ use crate::Exports; use forge_utils::FxHashMap; use swc_core::ecma::{ ast::{ - BindingIdent, Decl, ExportAll, ExportDecl, ExportDefaultDecl, ExportDefaultExpr, FnDecl, - ModuleDecl, ModuleItem, Pat, VarDecl, VarDeclarator, + AssignExpr, BindingIdent, Decl, ExportAll, ExportDecl, ExportDefaultDecl, + ExportDefaultExpr, Expr, FnDecl, MemberProp, ModuleDecl, ModuleExportName, ModuleItem, Pat, + PatOrExpr, VarDecl, VarDeclarator, }, visit::{noop_visit_type, Visit, VisitWith}, }; @@ -24,6 +25,9 @@ impl ExportCollector { impl Visit for ExportCollector { noop_visit_type!(); fn visit_export_decl(&mut self, n: &ExportDecl) { + //println!("export collector {n:?}"); + println!(); + match &n.decl { Decl::Class(_) => {} Decl::Fn(FnDecl { ident, .. }) => { @@ -33,10 +37,12 @@ impl Visit for ExportCollector { let ident = ident.to_id(); debug!(?ident, "function export"); // TODO: redo export layout to avoid clones + println!("exported ident {ident:?}"); export_ids.add_named(ident.clone(), ident); } Decl::Var(vardecls) => { let VarDecl { decls, .. } = &**vardecls; + //println!("exported vardecls {vardecls:?}"); decls.iter().for_each(|var| self.visit_var_declarator(var)); } Decl::TsInterface(_) => {} @@ -46,6 +52,30 @@ impl Visit for ExportCollector { }; } + fn visit_module_export_name(&mut self, n: &ModuleExportName) { + //println!("visiting module_export name"); + //println!("{n:?}"); + } + + fn visit_assign_expr(&mut self, n: &AssignExpr) { + // clean this before pushing + if let PatOrExpr::Expr(expr) = &n.left { + println!("here found moudle.exports"); + if let Expr::Member(mem_expr) = &**expr { + if let Expr::Ident(ident) = &*mem_expr.obj { + println!("here found moudle.exports"); + if ident.sym.to_string() == "module" { + if let MemberProp::Ident(ident_property) = &mem_expr.prop { + if ident_property.sym.to_string() == "export" { + println!("here found moudle.exports") + } + } + } + } + } + } + } + fn visit_var_declarator(&mut self, n: &VarDeclarator) { if let Pat::Ident(BindingIdent { id, .. }) = &n.name { let export_ids = self diff --git a/crates/forge_loader/src/manifest.rs b/crates/forge_loader/src/manifest.rs index 8c210bde..ef31be88 100644 --- a/crates/forge_loader/src/manifest.rs +++ b/crates/forge_loader/src/manifest.rs @@ -219,7 +219,7 @@ impl<'a> ForgeModules<'a> { .map(|trigger| trigger.raw.function), ) .collect(); - + let mut alternate_functions: Vec<&str> = Vec::new(); for module in self.extra.into_values().flatten() { alternate_functions.extend(module.function); diff --git a/crates/fsrt/src/main.rs b/crates/fsrt/src/main.rs index feb79f5a..ae6e2afa 100644 --- a/crates/fsrt/src/main.rs +++ b/crates/fsrt/src/main.rs @@ -241,12 +241,14 @@ fn scan_directory(dir: PathBuf, function: Option<&str>, opts: Opts) -> Result::from_iter( - unused_permissions.cloned().into_iter(), - ))] - .into_iter(), - ); + if unused_permissions.clone().count() > 0 { + reporter.add_vulnerabilities( + vec![PermissionVuln::new(HashSet::::from_iter( + unused_permissions.cloned().into_iter(), + ))] + .into_iter(), + ); + } let report = serde_json::to_string(&reporter.into_report()).into_diagnostic()?; debug!("Writing Report"); diff --git a/test-apps/basic/src/index.tsx b/test-apps/basic/src/index.tsx index b3f910fd..31abfe31 100644 --- a/test-apps/basic/src/index.tsx +++ b/test-apps/basic/src/index.tsx @@ -1,5 +1,6 @@ import ForgeUI, { render, Fragment, Macro, Text } from '@forge/ui'; import api, { route } from '@forge/api'; +import { testFn } from './test'; const foo = () => { const res = api.asApp().requestConfluence(route`/rest/api/3/test`); @@ -15,6 +16,7 @@ let test_function = (word) => { const App = () => { foo(); test_function("test_word"); + testFn(); return ( Hello world! From 9f4aaef3b583bda0993ef2a041df2374a111aae1 Mon Sep 17 00:00:00 2001 From: James Gersbach Date: Wed, 19 Jul 2023 15:33:35 -0700 Subject: [PATCH 108/517] fix to the issue of not resolving default exports --- crates/forge_analyzer/src/analyzer.rs | 4 +- crates/forge_analyzer/src/checkers.rs | 6 +- crates/forge_analyzer/src/definitions.rs | 169 +++++++++++++----- crates/forge_analyzer/src/exports.rs | 29 --- crates/forge_analyzer/src/interp.rs | 4 - crates/forge_analyzer/src/worklist.rs | 2 + crates/fsrt/src/main.rs | 1 - .../src/utils.js | 5 + 8 files changed, 131 insertions(+), 89 deletions(-) diff --git a/crates/forge_analyzer/src/analyzer.rs b/crates/forge_analyzer/src/analyzer.rs index 6eb2c60d..0aec70b0 100644 --- a/crates/forge_analyzer/src/analyzer.rs +++ b/crates/forge_analyzer/src/analyzer.rs @@ -285,9 +285,7 @@ impl Visit for FunctionCollector<'_> { n.function.visit_children_with(self); } - fn visit_expr_stmt(&mut self, n: &ExprStmt) { - println!("visitng expr sttm 2"); - } + // fn visit_asign_expr() fn visit_var_declarator(&mut self, n: &VarDeclarator) { if let VarDeclarator { diff --git a/crates/forge_analyzer/src/checkers.rs b/crates/forge_analyzer/src/checkers.rs index 16d6ef67..0283432c 100644 --- a/crates/forge_analyzer/src/checkers.rs +++ b/crates/forge_analyzer/src/checkers.rs @@ -120,6 +120,8 @@ impl<'cx> Dataflow<'cx> for AuthorizeDataflow { ) { self.super_join_term(interp, def, block, state, worklist); for def in self.needs_call.drain(..) { + // we can try and resolve the cll back to the original one here, but that seems more difficult + worklist.push_front_blocks(interp.env(), def); } } @@ -657,12 +659,10 @@ impl<'cx> Dataflow<'cx> for PermissionDataflow { } }); - println!("intrinsic args {:?}", intrinsic_argument); _interp .permissions .extend_from_slice(&permissions_within_call); } - println!("all permissions: {:?}", _interp.permissions); initial_state } @@ -801,7 +801,6 @@ impl<'cx> Dataflow<'cx> for PermissionDataflow { inst: &'cx Inst, initial_state: Self::State, ) -> Self::State { - println!("inst -- {inst}"); match inst { Inst::Expr(rvalue) => { self.transfer_rvalue(interp, def, loc, block, rvalue, initial_state) @@ -1194,7 +1193,6 @@ impl PermissionChecker { pub fn into_vulns(self) -> impl IntoIterator { if self.declared_permissions.len() > 0 { - println!("here in wrong case"); return Vec::from([PermissionVuln { unused_permissions: self.declared_permissions.clone(), }]) diff --git a/crates/forge_analyzer/src/definitions.rs b/crates/forge_analyzer/src/definitions.rs index 8c428b55..2dbcfe53 100644 --- a/crates/forge_analyzer/src/definitions.rs +++ b/crates/forge_analyzer/src/definitions.rs @@ -153,8 +153,6 @@ pub fn run_resolver( exports: vec![], default: None, }; - println!(); - //println!("module ---> {:#?}", module.body); module.visit_children_with(&mut export_collector); let mod_id = environment .exports @@ -683,6 +681,11 @@ impl ResolverTable { .unwrap_or_else(|| self.reserve_symbol(id, module)) } + #[inline] + fn get_sym(&mut self, id: Id, module: ModId) -> Option { + self.sym_id(id.clone(), module) + } + fn reserve_def(&mut self, name: JsWord, module: ModId) -> DefId { self.defs.push_and_get_key(DefRes::default()); self.names.push_and_get_key(name); @@ -821,9 +824,6 @@ impl<'cx> FunctionAnalyzer<'cx> { fn is_storage_read(prop: &JsWord) -> bool { *prop == *"get" || *prop == *"getSecret" || *prop == *"query" } - - //println!("as intrinsic "); - match *callee { [PropPath::Unknown((ref name, ..))] if *name == *"fetch" => Some(Intrinsic::Fetch), [PropPath::Def(def), ref authn @ .., PropPath::Static(ref last)] @@ -837,7 +837,6 @@ impl<'cx> FunctionAnalyzer<'cx> { } else { IntrinsicName::RequestConfluence }; - //println!("here within the intrinsic"); let first_arg = first_arg?; match classify_api_call(first_arg) { ApiCallKind::Unknown => { @@ -985,7 +984,6 @@ impl<'cx> FunctionAnalyzer<'cx> { } fn lower_call(&mut self, callee: CalleeRef<'_>, args: &[ExprOrSpread]) -> Operand { - //println!("lower call"); let props = normalize_callee_expr(callee, self.res, self.module); if let Some(&PropPath::Def(id)) = props.first() { if self.res.as_foreign_import(id, "@forge/ui").map_or( @@ -1032,7 +1030,6 @@ impl<'cx> FunctionAnalyzer<'cx> { CalleeRef::Import => Operand::UNDEF, CalleeRef::Expr(expr) => self.lower_expr(expr, None), }; - //println!("callee {callee}"); let first_arg = args.first().map(|expr| &*expr.expr); let call = match self.as_intrinsic(&props, first_arg) { Some(int) => Rvalue::Intrinsic(int, lowered_args), @@ -1047,8 +1044,11 @@ impl<'cx> FunctionAnalyzer<'cx> { match n { Pat::Ident(BindingIdent { id, .. }) => { let id = id.to_id(); - let def = self.res.get_or_insert_sym(id, self.module); + let def = self.res.get_or_insert_sym(id.clone(), self.module); let var = self.body.get_or_insert_global(def); + + // issue is here, where it may be getting the wrong def + self.push_curr_inst(Inst::Assign(Variable::new(var), val)); } Pat::Array(ArrayPat { elems, .. }) => { @@ -1721,10 +1721,15 @@ impl Visit for LocalDefiner<'_> { fn visit_fn_decl(&mut self, _: &FnDecl) {} } -impl Visit for FunctionCollector<'_> { - fn visit_function(&mut self, n: &Function) { - n.visit_children_with(self); +impl FunctionCollector<'_> { + fn handle_function(&mut self, n: &Function, owner: Option) { let owner = self.parent.unwrap_or_else(|| { + if let Some(defid) = owner { + return defid; + } + if let Some(defid) = self.res.default_export(self.module) { + return defid; + } self.res .add_anonymous("__UNKNOWN", AnonType::Closure, self.module) }); @@ -1744,6 +1749,7 @@ impl Visit for FunctionCollector<'_> { }; n.body.visit_children_with(&mut localdef); let body = localdef.body; + // wrong defid passed in as the current def within the funciton analyzer let mut analyzer = FunctionAnalyzer { res: self.res, module: self.module, @@ -1761,6 +1767,41 @@ impl Visit for FunctionCollector<'_> { *self.res.def_mut(owner).expect_body() = body; } } +} + +impl Visit for FunctionCollector<'_> { + fn visit_assign_expr(&mut self, n: &AssignExpr) { + if let PatOrExpr::Pat(pat) = &n.left { + if let Pat::Expr(expr) = &**pat { + if let Expr::Member(mem_expr) = &**expr { + if let Expr::Ident(ident) = &*mem_expr.obj { + if ident.sym.to_string() == "exports" { + if let MemberProp::Ident(ident_property) = &mem_expr.prop { + match &*n.right { + Expr::Fn(FnExpr { ident, function }) => { + if let Some(defid) = + self.res.get_sym(ident_property.to_id(), self.module) + { + self.handle_function(&**function, Some(defid)); + } + } + _ => {} + } + } + } + } + } + } + } + + n.visit_children_with(self) + } + + fn visit_function(&mut self, n: &Function) { + // likley an issue where we are adding anon instead of using the actual value + n.visit_children_with(self); + self.handle_function(n, None); + } fn visit_arrow_expr( &mut self, @@ -2192,8 +2233,16 @@ impl ExportCollector<'_> { defid } + // some issue here -------- + + /** + * + * the body that we are getting is not getting the insts :( + * We think it may be an issue with the way we are adding the default + */ fn add_default(&mut self, def: DefRes, id: Option) { let defid = match id { + // ehck here, this may be hte issue Some(id) => self.res_table.add_sym(def, id, self.curr_mod), None => { self.res_table.names.push("default".into()); @@ -2327,7 +2376,6 @@ impl Visit for ImportCollector<'_> { impl Visit for ExportCollector<'_> { noop_visit_type!(); fn visit_export_decl(&mut self, n: &ExportDecl) { - //println!("visit export decl {n:#?}"); match &n.decl { Decl::Class(ClassDecl { ident, .. }) => { let ident = ident.to_id(); @@ -2335,12 +2383,11 @@ impl Visit for ExportCollector<'_> { } Decl::Fn(FnDecl { ident, .. }) => { let ident = ident.to_id(); - //println!("FNDECL = {ident:?}"); self.add_export(DefRes::Function(()), ident); } Decl::Var(vardecls) => { let VarDecl { decls, .. } = &**vardecls; - decls.iter().for_each(|var| self.visit_var_declarator(var)); + //decls.iter().for_each(|var| self.visit_var_declarator(var)); } Decl::TsInterface(_) => {} Decl::TsTypeAlias(_) => {} @@ -2351,40 +2398,44 @@ impl Visit for ExportCollector<'_> { } fn visit_assign_expr(&mut self, n: &AssignExpr) { - println!(); - //println!("assing expr {n:?}"); - println!(); - //println!("visitng assign expr"); - //let pat = unwrap_or!(PatOrExpr::Pat, n, return); to make cleaner consider using macro here + if let Some(ident) = ident_from_assign_expr(n) { + if ident.sym.to_string() == "module" { + if let Some(mem_expr) = mem_expr_from_assign(n.clone()) { + if let MemberProp::Ident(ident_property) = &mem_expr.prop { + if ident_property.sym.to_string() == "exports" { + match &*n.right { + Expr::Fn(FnExpr { ident, function }) => self.add_default( + DefRes::Function(()), + ident.as_ref().map(Ident::to_id), + ), + Expr::Class(ClassExpr { ident, class }) => self.add_default( + DefRes::Class(()), + ident.as_ref().map(Ident::to_id), + ), + _ => {} + } + } + } + } + } else if ident.sym.to_string() == "exports" { + if let Some(mem_expr) = mem_expr_from_assign(n.clone()) { + if let MemberProp::Ident(ident_property) = &mem_expr.prop { + //self.add_export(DefRes::Undefined, ident_property.to_id()); + match &*n.right { + Expr::Fn(FnExpr { ident, function }) => { + self.add_export(DefRes::Function(()), ident_property.to_id()); + } + _ => {} + } + } + } + } + } if let PatOrExpr::Pat(pat) = &n.left { if let Pat::Expr(expr) = &**pat { if let Expr::Member(mem_expr) = &**expr { - if let Expr::Ident(ident) = &*mem_expr.obj { - println!("here found moudle.exports"); - if ident.sym.to_string() == "module" { - if let MemberProp::Ident(ident_property) = &mem_expr.prop { - if ident_property.sym.to_string() == "exports" { - println!("here found moudle.exports 2"); - match &*n.right { - Expr::Fn(FnExpr { ident, function }) => { - - println!("ident {ident:?}"); - self.add_default( - DefRes::Function(()), - ident.as_ref().map(Ident::to_id), - )}, - Expr::Class(ClassExpr { ident, class }) => self - .add_default( - DefRes::Class(()), - ident.as_ref().map(Ident::to_id), - ), - _ => {} - } - } - } - } - } + if let Expr::Ident(ident) = &*mem_expr.obj {} } } } @@ -2414,7 +2465,7 @@ impl Visit for ExportCollector<'_> { | ModuleDecl::ExportNamed(_) ) => { - decl.visit_children_with(self) + //decl.visit_children_with(self) } _ => {} } @@ -2437,8 +2488,6 @@ impl Visit for ExportCollector<'_> { } fn visit_export_named_specifier(&mut self, n: &ExportNamedSpecifier) { - //println!("export named specifier {n:?}"); - let orig_id = n.orig.as_id(); let orig = self.add_export(DefRes::default(), orig_id); if let Some(id) = &n.exported { @@ -2454,6 +2503,26 @@ impl Visit for ExportCollector<'_> { } } +fn ident_from_assign_expr(n: &AssignExpr) -> Option { + if let Some(mem_expr) = mem_expr_from_assign(n.clone()) { + if let Expr::Ident(ident) = &*mem_expr.obj { + return Some(ident.clone()); + } + } + None +} + +fn mem_expr_from_assign(n: AssignExpr) -> Option { + if let PatOrExpr::Pat(pat) = &n.left { + if let Pat::Expr(expr) = &**pat { + if let Expr::Member(mem_expr) = &**expr { + return Some(mem_expr.clone()); + } + } + } + None +} + impl Environment { #[inline] fn new() -> Self { @@ -2496,6 +2565,10 @@ impl Environment { def_id } + fn get_sym(&mut self, id: Id, module: ModId) -> Option { + self.resolver.get_sym(id, module) + } + fn new_key_from_res(&mut self, id: DefId, res: DefRes) -> DefKey { match res { DefKind::Arg => DefKind::Arg, diff --git a/crates/forge_analyzer/src/exports.rs b/crates/forge_analyzer/src/exports.rs index 28e31b90..be225b3f 100644 --- a/crates/forge_analyzer/src/exports.rs +++ b/crates/forge_analyzer/src/exports.rs @@ -25,9 +25,6 @@ impl ExportCollector { impl Visit for ExportCollector { noop_visit_type!(); fn visit_export_decl(&mut self, n: &ExportDecl) { - //println!("export collector {n:?}"); - println!(); - match &n.decl { Decl::Class(_) => {} Decl::Fn(FnDecl { ident, .. }) => { @@ -37,12 +34,10 @@ impl Visit for ExportCollector { let ident = ident.to_id(); debug!(?ident, "function export"); // TODO: redo export layout to avoid clones - println!("exported ident {ident:?}"); export_ids.add_named(ident.clone(), ident); } Decl::Var(vardecls) => { let VarDecl { decls, .. } = &**vardecls; - //println!("exported vardecls {vardecls:?}"); decls.iter().for_each(|var| self.visit_var_declarator(var)); } Decl::TsInterface(_) => {} @@ -52,30 +47,6 @@ impl Visit for ExportCollector { }; } - fn visit_module_export_name(&mut self, n: &ModuleExportName) { - //println!("visiting module_export name"); - //println!("{n:?}"); - } - - fn visit_assign_expr(&mut self, n: &AssignExpr) { - // clean this before pushing - if let PatOrExpr::Expr(expr) = &n.left { - println!("here found moudle.exports"); - if let Expr::Member(mem_expr) = &**expr { - if let Expr::Ident(ident) = &*mem_expr.obj { - println!("here found moudle.exports"); - if ident.sym.to_string() == "module" { - if let MemberProp::Ident(ident_property) = &mem_expr.prop { - if ident_property.sym.to_string() == "export" { - println!("here found moudle.exports") - } - } - } - } - } - } - } - fn visit_var_declarator(&mut self, n: &VarDeclarator) { if let Pat::Ident(BindingIdent { id, .. }) = &n.name { let export_ids = self diff --git a/crates/forge_analyzer/src/interp.rs b/crates/forge_analyzer/src/interp.rs index 2d635c12..3c765ae8 100644 --- a/crates/forge_analyzer/src/interp.rs +++ b/crates/forge_analyzer/src/interp.rs @@ -89,7 +89,6 @@ pub trait Dataflow<'cx>: Sized { rvalue: &'cx Rvalue, initial_state: Self::State, ) -> Self::State { - // println!("transfer rvalue {:?}", rvalue); match rvalue { Rvalue::Intrinsic(intrinsic, args) => self.transfer_intrinsic( interp, @@ -147,7 +146,6 @@ pub trait Dataflow<'cx>: Sized { ) -> Self::State { let mut state = initial_state; for (stmt, inst) in block.iter().enumerate() { - // println!("inst -- {inst}"); let loc = Location::new(bb, stmt as u32); state = self.transfer_inst(interp, def, loc, block, inst, state); } @@ -678,7 +676,6 @@ impl<'cx, C: Checker<'cx>> Interp<'cx, C> { let resolved_def = self.env.resolve_alias(def); let name = self.env.def_name(resolved_def); info!("Checking function: {name}"); - // println!("Checking function: {name}"); let body = *self .env .def_ref(resolved_def) @@ -712,7 +709,6 @@ impl<'cx, C: Checker<'cx>> Interp<'cx, C> { return Err(error); } info!("Found potential resolver"); - //println!("found resolver"); for (name, prop) in resolver { debug!("Checking resolver prop: {name}"); self.entry.kind = match std::mem::take(&mut self.entry.kind) { diff --git a/crates/forge_analyzer/src/worklist.rs b/crates/forge_analyzer/src/worklist.rs index e7a41f20..bfaf6865 100644 --- a/crates/forge_analyzer/src/worklist.rs +++ b/crates/forge_analyzer/src/worklist.rs @@ -76,6 +76,8 @@ where impl WorkList { #[inline] pub(crate) fn push_front_blocks(&mut self, env: &Environment, def: DefId) -> bool { + // try and get the original defid here + if self.visited.insert(def) || true { debug!("adding function: {}", env.def_name(def)); let body = env.def_ref(def).expect_body(); diff --git a/crates/fsrt/src/main.rs b/crates/fsrt/src/main.rs index ae6e2afa..1fac0278 100644 --- a/crates/fsrt/src/main.rs +++ b/crates/fsrt/src/main.rs @@ -219,7 +219,6 @@ fn scan_directory(dir: PathBuf, function: Option<&str>, opts: Opts) -> Result { - println!("func name: -- webtrigger {:?}", interp.env.def_name(def)); let mut checker = AuthenticateChecker::new(); debug!("checking webtrigger {func} at {path:?}"); if let Err(err) = diff --git a/test-apps/jira-damn-vulnerable-forge-app/src/utils.js b/test-apps/jira-damn-vulnerable-forge-app/src/utils.js index 1c1820a1..674263d5 100755 --- a/test-apps/jira-damn-vulnerable-forge-app/src/utils.js +++ b/test-apps/jira-damn-vulnerable-forge-app/src/utils.js @@ -1,4 +1,5 @@ import api, { route } from '@forge/api'; +import {testFunctionFromTestFile} from './testfile'; export async function fetchIssueSummary(issueIdOrKey, url) { @@ -33,6 +34,10 @@ function get_route() { export async function writeComment(issueIdOrKey, comment) { /* const api = require('@forge/api'); */ + + + testFunctionFromTestFile("test_value_param") + const resp = await api .asApp() .requestJira(route`/rest/api/3/issue/${issueIdOrKey}/comment`, { From 097224d968228d0bb9ebd27bbedba2b0bac035cd Mon Sep 17 00:00:00 2001 From: James Gersbach Date: Wed, 19 Jul 2023 16:04:21 -0700 Subject: [PATCH 109/517] cleaning up code --- .gitignore | 3 ++- crates/forge_analyzer/src/engine.rs | 1 - crates/forge_analyzer/src/interp.rs | 2 -- 3 files changed, 2 insertions(+), 4 deletions(-) diff --git a/.gitignore b/.gitignore index c41cc9e3..235ba4f6 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ -/target \ No newline at end of file +/target +/test-apps \ No newline at end of file diff --git a/crates/forge_analyzer/src/engine.rs b/crates/forge_analyzer/src/engine.rs index 43204fba..319a3d4f 100644 --- a/crates/forge_analyzer/src/engine.rs +++ b/crates/forge_analyzer/src/engine.rs @@ -117,7 +117,6 @@ impl<'ctx> Machine<'ctx> { Inst::Step(next_block, next_stmt) => { debug!(?next_block, ?next_stmt, "stepping into"); self.eip = (next_block, next_stmt); - // we need to return if possible if let Some(val) = self.transfer() { if let Some(ret) = self.callstack.pop() { debug!(?ret, "returning"); diff --git a/crates/forge_analyzer/src/interp.rs b/crates/forge_analyzer/src/interp.rs index 57988685..3b95604c 100644 --- a/crates/forge_analyzer/src/interp.rs +++ b/crates/forge_analyzer/src/interp.rs @@ -312,8 +312,6 @@ pub(crate) struct EntryPoint { pub struct Interp<'cx, C: Checker<'cx>> { env: &'cx Environment, - // We can probably get rid of these RefCells by refactoring the Interp and Checker into - // two fields in another struct. call_graph: CallGraph, entry: EntryPoint, func_state: RefCell>, From 8b097b44881c4971320cfbb1fc20e413c94289bc Mon Sep 17 00:00:00 2001 From: James Gersbach Date: Wed, 19 Jul 2023 16:09:08 -0700 Subject: [PATCH 110/517] removing unneeded comments --- crates/forge_analyzer/src/checkers.rs | 2 -- crates/forge_analyzer/src/definitions.rs | 12 ------------ crates/forge_analyzer/src/worklist.rs | 2 -- 3 files changed, 16 deletions(-) diff --git a/crates/forge_analyzer/src/checkers.rs b/crates/forge_analyzer/src/checkers.rs index 0283432c..04ad2192 100644 --- a/crates/forge_analyzer/src/checkers.rs +++ b/crates/forge_analyzer/src/checkers.rs @@ -120,8 +120,6 @@ impl<'cx> Dataflow<'cx> for AuthorizeDataflow { ) { self.super_join_term(interp, def, block, state, worklist); for def in self.needs_call.drain(..) { - // we can try and resolve the cll back to the original one here, but that seems more difficult - worklist.push_front_blocks(interp.env(), def); } } diff --git a/crates/forge_analyzer/src/definitions.rs b/crates/forge_analyzer/src/definitions.rs index 2dbcfe53..7030cb95 100644 --- a/crates/forge_analyzer/src/definitions.rs +++ b/crates/forge_analyzer/src/definitions.rs @@ -1046,9 +1046,6 @@ impl<'cx> FunctionAnalyzer<'cx> { let id = id.to_id(); let def = self.res.get_or_insert_sym(id.clone(), self.module); let var = self.body.get_or_insert_global(def); - - // issue is here, where it may be getting the wrong def - self.push_curr_inst(Inst::Assign(Variable::new(var), val)); } Pat::Array(ArrayPat { elems, .. }) => { @@ -1749,7 +1746,6 @@ impl FunctionCollector<'_> { }; n.body.visit_children_with(&mut localdef); let body = localdef.body; - // wrong defid passed in as the current def within the funciton analyzer let mut analyzer = FunctionAnalyzer { res: self.res, module: self.module, @@ -2233,16 +2229,8 @@ impl ExportCollector<'_> { defid } - // some issue here -------- - - /** - * - * the body that we are getting is not getting the insts :( - * We think it may be an issue with the way we are adding the default - */ fn add_default(&mut self, def: DefRes, id: Option) { let defid = match id { - // ehck here, this may be hte issue Some(id) => self.res_table.add_sym(def, id, self.curr_mod), None => { self.res_table.names.push("default".into()); diff --git a/crates/forge_analyzer/src/worklist.rs b/crates/forge_analyzer/src/worklist.rs index bfaf6865..e7a41f20 100644 --- a/crates/forge_analyzer/src/worklist.rs +++ b/crates/forge_analyzer/src/worklist.rs @@ -76,8 +76,6 @@ where impl WorkList { #[inline] pub(crate) fn push_front_blocks(&mut self, env: &Environment, def: DefId) -> bool { - // try and get the original defid here - if self.visited.insert(def) || true { debug!("adding function: {}", env.def_name(def)); let body = env.def_ref(def).expect_body(); From 3090ff9385e7773e68088655eaad9c7cb6c92624 Mon Sep 17 00:00:00 2001 From: James Gersbach Date: Thu, 20 Jul 2023 08:51:07 -0700 Subject: [PATCH 111/517] handling default and nodejs exports --- .gitignore | 3 +- crates/forge_analyzer/src/definitions.rs | 78 +++++++++++++++++++++++- 2 files changed, 77 insertions(+), 4 deletions(-) diff --git a/.gitignore b/.gitignore index c41cc9e3..235ba4f6 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ -/target \ No newline at end of file +/target +/test-apps \ No newline at end of file diff --git a/crates/forge_analyzer/src/definitions.rs b/crates/forge_analyzer/src/definitions.rs index 241c98b3..49321be9 100644 --- a/crates/forge_analyzer/src/definitions.rs +++ b/crates/forge_analyzer/src/definitions.rs @@ -636,6 +636,11 @@ impl ResolverTable { .unwrap_or_else(|| self.reserve_symbol(id, module)) } + #[inline] + fn get_sym(&mut self, id: Id, module: ModId) -> Option { + self.sym_id(id.clone(), module) + } + fn reserve_def(&mut self, name: JsWord, module: ModId) -> DefId { self.defs.push_and_get_key(DefRes::default()); self.names.push_and_get_key(name); @@ -1540,10 +1545,15 @@ impl Visit for LocalDefiner<'_> { fn visit_fn_decl(&mut self, _: &FnDecl) {} } -impl Visit for FunctionCollector<'_> { - fn visit_function(&mut self, n: &Function) { - n.visit_children_with(self); +impl FunctionCollector<'_> { + fn handle_function(&mut self, n: &Function, owner: Option) { let owner = self.parent.unwrap_or_else(|| { + if let Some(defid) = owner { + return defid; + } + if let Some(defid) = self.res.default_export(self.module) { + return defid; + } self.res .add_anonymous("__UNKNOWN", AnonType::Closure, self.module) }); @@ -1563,6 +1573,7 @@ impl Visit for FunctionCollector<'_> { }; n.body.visit_children_with(&mut localdef); let body = localdef.body; + // wrong defid passed in as the current def within the funciton analyzer let mut analyzer = FunctionAnalyzer { res: self.res, module: self.module, @@ -1579,6 +1590,42 @@ impl Visit for FunctionCollector<'_> { *self.res.def_mut(owner).expect_body() = body; } } +} + +impl Visit for FunctionCollector<'_> { + fn visit_assign_expr(&mut self, n: &AssignExpr) { + if let PatOrExpr::Pat(pat) = &n.left { + if let Pat::Expr(expr) = &**pat { + if let Expr::Member(mem_expr) = &**expr { + if let Expr::Ident(ident) = &*mem_expr.obj { + if ident.sym.to_string() == "exports" { + if let MemberProp::Ident(ident_property) = &mem_expr.prop { + match &*n.right { + Expr::Fn(FnExpr { ident, function }) => { + if let Some(defid) = + self.res.get_sym(ident_property.to_id(), self.module) + { + self.handle_function(&**function, Some(defid)); + } + } + _ => {} + } + } + } + } + } + } + } + + n.visit_children_with(self) + } + + fn visit_function(&mut self, n: &Function) { + // likley an issue where we are adding anon instead of using the actual value + n.visit_children_with(self); + self.handle_function(n, None); + } + fn visit_arrow_expr( &mut self, @@ -2219,6 +2266,26 @@ impl Visit for ExportCollector<'_> { } } +fn ident_from_assign_expr(n: &AssignExpr) -> Option { + if let Some(mem_expr) = mem_expr_from_assign(n.clone()) { + if let Expr::Ident(ident) = &*mem_expr.obj { + return Some(ident.clone()); + } + } + None +} + +fn mem_expr_from_assign(n: AssignExpr) -> Option { + if let PatOrExpr::Pat(pat) = &n.left { + if let Pat::Expr(expr) = &**pat { + if let Expr::Member(mem_expr) = &**expr { + return Some(mem_expr.clone()); + } + } + } + None +} + impl Environment { #[inline] fn new() -> Self { @@ -2261,6 +2328,11 @@ impl Environment { def_id } + #[inline] + fn get_sym(&mut self, id: Id, module: ModId) -> Option { + self.resolver.get_sym(id, module) + } + fn new_key_from_res(&mut self, id: DefId, res: DefRes) -> DefKey { match res { DefKind::Arg => DefKind::Arg, From 40175e25ab3a120fc5e7e4a2e8b5568b787ed0ad Mon Sep 17 00:00:00 2001 From: James Gersbach Date: Thu, 20 Jul 2023 08:54:31 -0700 Subject: [PATCH 112/517] formatting --- crates/forge_analyzer/src/definitions.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/crates/forge_analyzer/src/definitions.rs b/crates/forge_analyzer/src/definitions.rs index 49321be9..b4be518c 100644 --- a/crates/forge_analyzer/src/definitions.rs +++ b/crates/forge_analyzer/src/definitions.rs @@ -1626,7 +1626,6 @@ impl Visit for FunctionCollector<'_> { self.handle_function(n, None); } - fn visit_arrow_expr( &mut self, ArrowExpr { From 7baacfb8fdaf73376c3890f9a11b55d1cd6d5094 Mon Sep 17 00:00:00 2001 From: James Gersbach Date: Thu, 20 Jul 2023 14:24:09 -0700 Subject: [PATCH 113/517] cleaning up --- crates/forge_analyzer/src/definitions.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/crates/forge_analyzer/src/definitions.rs b/crates/forge_analyzer/src/definitions.rs index b4be518c..1c06261d 100644 --- a/crates/forge_analyzer/src/definitions.rs +++ b/crates/forge_analyzer/src/definitions.rs @@ -1573,7 +1573,6 @@ impl FunctionCollector<'_> { }; n.body.visit_children_with(&mut localdef); let body = localdef.body; - // wrong defid passed in as the current def within the funciton analyzer let mut analyzer = FunctionAnalyzer { res: self.res, module: self.module, @@ -1621,7 +1620,6 @@ impl Visit for FunctionCollector<'_> { } fn visit_function(&mut self, n: &Function) { - // likley an issue where we are adding anon instead of using the actual value n.visit_children_with(self); self.handle_function(n, None); } From b9ebaa07656acf0eaa7b3a52390d70aff031e8b6 Mon Sep 17 00:00:00 2001 From: James Gersbach Date: Thu, 20 Jul 2023 14:34:28 -0700 Subject: [PATCH 114/517] more code --- crates/forge_analyzer/src/definitions.rs | 38 ++++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/crates/forge_analyzer/src/definitions.rs b/crates/forge_analyzer/src/definitions.rs index 1c06261d..925cdd11 100644 --- a/crates/forge_analyzer/src/definitions.rs +++ b/crates/forge_analyzer/src/definitions.rs @@ -2209,6 +2209,44 @@ impl Visit for ExportCollector<'_> { }; } + + fn visit_assign_expr(&mut self, n: &AssignExpr) { + if let Some(ident) = ident_from_assign_expr(n) { + if ident.sym.to_string() == "module" { + if let Some(mem_expr) = mem_expr_from_assign(n.clone()) { + if let MemberProp::Ident(ident_property) = &mem_expr.prop { + if ident_property.sym.to_string() == "exports" { + match &*n.right { + Expr::Fn(FnExpr { ident, function }) => self.add_default( + DefRes::Function(()), + ident.as_ref().map(Ident::to_id), + ), + Expr::Class(ClassExpr { ident, class }) => self.add_default( + DefRes::Class(()), + ident.as_ref().map(Ident::to_id), + ), + _ => {} + } + } + } + } + } else if ident.sym.to_string() == "exports" { + if let Some(mem_expr) = mem_expr_from_assign(n.clone()) { + if let MemberProp::Ident(ident_property) = &mem_expr.prop { + //self.add_export(DefRes::Undefined, ident_property.to_id()); + match &*n.right { + Expr::Fn(FnExpr { ident, function }) => { + self.add_export(DefRes::Function(()), ident_property.to_id()); + } + _ => {} + } + } + } + } + } + n.visit_children_with(self); + } + fn visit_var_declarator(&mut self, n: &VarDeclarator) { // TODO: handle other kinds of destructuring patterns if let Pat::Ident(BindingIdent { id, .. }) = &n.name { From d812d40553009962140499268b968f9eea4bf548 Mon Sep 17 00:00:00 2001 From: James Gersbach Date: Thu, 20 Jul 2023 14:36:46 -0700 Subject: [PATCH 115/517] formatitng --- crates/forge_analyzer/src/definitions.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/crates/forge_analyzer/src/definitions.rs b/crates/forge_analyzer/src/definitions.rs index 925cdd11..b2f2bf65 100644 --- a/crates/forge_analyzer/src/definitions.rs +++ b/crates/forge_analyzer/src/definitions.rs @@ -2209,7 +2209,6 @@ impl Visit for ExportCollector<'_> { }; } - fn visit_assign_expr(&mut self, n: &AssignExpr) { if let Some(ident) = ident_from_assign_expr(n) { if ident.sym.to_string() == "module" { From a6b20ab163be8291c020463507283ef5ef0b01e8 Mon Sep 17 00:00:00 2001 From: gersbach <93059258+gersbach@users.noreply.github.com> Date: Thu, 20 Jul 2023 14:37:35 -0700 Subject: [PATCH 116/517] Update .gitignore --- .gitignore | 1 - 1 file changed, 1 deletion(-) diff --git a/.gitignore b/.gitignore index 235ba4f6..ea8c4bf7 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1 @@ /target -/test-apps \ No newline at end of file From 5412bd52f76d71042dd49b3ba88055f1ad350301 Mon Sep 17 00:00:00 2001 From: James Gersbach Date: Thu, 20 Jul 2023 16:13:04 -0700 Subject: [PATCH 117/517] handling lowering of useEffect functions --- crates/forge_analyzer/src/definitions.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/crates/forge_analyzer/src/definitions.rs b/crates/forge_analyzer/src/definitions.rs index 241c98b3..81146b84 100644 --- a/crates/forge_analyzer/src/definitions.rs +++ b/crates/forge_analyzer/src/definitions.rs @@ -934,7 +934,7 @@ impl<'cx> FunctionAnalyzer<'cx> { if let Some(&PropPath::Def(id)) = props.first() { if self.res.as_foreign_import(id, "@forge/ui").map_or( false, - |imp| matches!(imp, ImportKind::Named(s) if *s == *"useState"), + |imp| matches!(imp, ImportKind::Named(s) if *s == *"useState" || *s == *"useEffect"), ) { if let [ExprOrSpread { expr, .. }] = args { debug!("found useState"); @@ -1834,6 +1834,8 @@ impl Visit for Lowerer<'_> { class.pub_members.push((fname, new_def)); } } + } else if let Expr::Ident(ident) = &**expr { + //ident.sym } } } @@ -2027,6 +2029,8 @@ impl Visit for ImportCollector<'_> { noop_visit_type!(); fn visit_import_decl(&mut self, n: &ImportDecl) { + println!("visitng import decl {n:?}"); + let Str { value, .. } = &*n.src; let old_import = mem::replace(&mut self.current_import, value.clone()); n.visit_children_with(self); From ce6d642eb974b1793452207dc8f5b2e3aaec3591 Mon Sep 17 00:00:00 2001 From: James Gersbach Date: Thu, 20 Jul 2023 16:14:16 -0700 Subject: [PATCH 118/517] cleaning up --- crates/forge_analyzer/src/definitions.rs | 4 ---- 1 file changed, 4 deletions(-) diff --git a/crates/forge_analyzer/src/definitions.rs b/crates/forge_analyzer/src/definitions.rs index 81146b84..92867ce8 100644 --- a/crates/forge_analyzer/src/definitions.rs +++ b/crates/forge_analyzer/src/definitions.rs @@ -1834,8 +1834,6 @@ impl Visit for Lowerer<'_> { class.pub_members.push((fname, new_def)); } } - } else if let Expr::Ident(ident) = &**expr { - //ident.sym } } } @@ -2029,8 +2027,6 @@ impl Visit for ImportCollector<'_> { noop_visit_type!(); fn visit_import_decl(&mut self, n: &ImportDecl) { - println!("visitng import decl {n:?}"); - let Str { value, .. } = &*n.src; let old_import = mem::replace(&mut self.current_import, value.clone()); n.visit_children_with(self); From 8fa8cdaccb45d6de225d66bc2bed29ffee922fc0 Mon Sep 17 00:00:00 2001 From: James Gersbach Date: Mon, 24 Jul 2023 08:58:09 -0700 Subject: [PATCH 119/517] lowering then and array functions --- crates/forge_analyzer/src/definitions.rs | 23 +++++++++++++++++++++++ crates/forge_analyzer/src/lib.rs | 1 + 2 files changed, 24 insertions(+) diff --git a/crates/forge_analyzer/src/definitions.rs b/crates/forge_analyzer/src/definitions.rs index 241c98b3..45c3b7a4 100644 --- a/crates/forge_analyzer/src/definitions.rs +++ b/crates/forge_analyzer/src/definitions.rs @@ -2,6 +2,7 @@ use std::{borrow::Borrow, fmt, mem}; +use crate::utils::call_func_with_name; use forge_file_resolver::{FileResolver, ForgeResolver}; use forge_utils::{create_newtype, FxHashMap}; @@ -1540,6 +1541,27 @@ impl Visit for LocalDefiner<'_> { fn visit_fn_decl(&mut self, _: &FnDecl) {} } +impl Visit for FunctionAnalyzer<'_> { + fn visit_call_expr(&mut self, n: &CallExpr) { + if call_func_with_name(n, "then") + || call_func_with_name(n, "map") + || call_func_with_name(n, "foreach") + || call_func_with_name(n, "filter") + { + if let Some(lambda_function) = n.args.get(0) { + if let Expr::Arrow(arrow) = &*lambda_function.expr { + if let BlockStmtOrExpr::BlockStmt(block_stmt) = &arrow.body { + block_stmt + .stmts + .iter() + .for_each(|stmt| self.lower_stmt(stmt)); + } + } + } + } + } +} + impl Visit for FunctionCollector<'_> { fn visit_function(&mut self, n: &Function) { n.visit_children_with(self); @@ -1573,6 +1595,7 @@ impl Visit for FunctionCollector<'_> { operand_stack: vec![], in_lhs: false, }; + n.body.visit_children_with(&mut analyzer); if let Some(BlockStmt { stmts, .. }) = &n.body { analyzer.lower_stmts(stmts); let body = analyzer.body; diff --git a/crates/forge_analyzer/src/lib.rs b/crates/forge_analyzer/src/lib.rs index 58f004a9..00bf765f 100644 --- a/crates/forge_analyzer/src/lib.rs +++ b/crates/forge_analyzer/src/lib.rs @@ -10,6 +10,7 @@ pub mod lattice; pub mod pretty; pub mod reporter; pub mod resolver; +pub mod utils; pub mod worklist; use ctx::ModuleCtx; From 238c860437dfbb518e55b19c1d6d0ce640cb65fd Mon Sep 17 00:00:00 2001 From: James Gersbach Date: Mon, 24 Jul 2023 08:58:59 -0700 Subject: [PATCH 120/517] adding utils file --- crates/forge_analyzer/src/utils.rs | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 crates/forge_analyzer/src/utils.rs diff --git a/crates/forge_analyzer/src/utils.rs b/crates/forge_analyzer/src/utils.rs new file mode 100644 index 00000000..354d5fe1 --- /dev/null +++ b/crates/forge_analyzer/src/utils.rs @@ -0,0 +1,14 @@ +use swc_core::ecma::ast::{CallExpr, Callee, Expr, MemberProp}; + +pub fn call_func_with_name(n: &CallExpr, name: &str) -> bool { + if let Callee::Expr(expr) = &n.callee { + if let Expr::Member(mem) = &**expr { + if let MemberProp::Ident(ident) = &mem.prop { + if ident.sym.to_string() == name { + return true; + } + } + } + } + false +} From d8b1c0bbe810f0a32190233b35905cd23417fe68 Mon Sep 17 00:00:00 2001 From: James Gersbach Date: Mon, 24 Jul 2023 12:43:37 -0700 Subject: [PATCH 121/517] prevents scan if the code is transplied --- crates/fsrt/src/main.rs | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/crates/fsrt/src/main.rs b/crates/fsrt/src/main.rs index d1df4f5e..3ba3c84e 100644 --- a/crates/fsrt/src/main.rs +++ b/crates/fsrt/src/main.rs @@ -3,6 +3,7 @@ use std::{ collections::HashSet, convert::TryFrom, fs, + io::{self, BufReader}, os::unix::prelude::OsStrExt, path::{Path, PathBuf}, sync::Arc, @@ -175,6 +176,17 @@ fn scan_directory(dir: PathBuf, function: Option<&str>, opts: Opts) -> Result>(); + + let transpiled_async = paths + .iter() + .map(|path| { + if let Ok(data) = fs::read_to_string(path) { + return data.contains("use strict"); + } + false + }) + .any(|path| path); + let funcrefs = manifest.modules.into_analyzable_functions().flat_map(|f| { f.sequence(|fmod| { let resolved_func = FunctionRef::try_from(fmod)?.try_resolve(&paths, &dir)?; @@ -183,6 +195,10 @@ fn scan_directory(dir: PathBuf, function: Option<&str>, opts: Opts) -> Result Date: Tue, 25 Jul 2023 10:29:30 -0700 Subject: [PATCH 122/517] Update crates/forge_analyzer/src/definitions.rs Co-authored-by: Joshua Wong --- crates/forge_analyzer/src/definitions.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/forge_analyzer/src/definitions.rs b/crates/forge_analyzer/src/definitions.rs index b2f2bf65..786a9e6f 100644 --- a/crates/forge_analyzer/src/definitions.rs +++ b/crates/forge_analyzer/src/definitions.rs @@ -2301,7 +2301,7 @@ impl Visit for ExportCollector<'_> { } fn ident_from_assign_expr(n: &AssignExpr) -> Option { - if let Some(mem_expr) = mem_expr_from_assign(n.clone()) { + mem_expr_from_assign(n).map(Expr::as_ident).cloned() if let Expr::Ident(ident) = &*mem_expr.obj { return Some(ident.clone()); } From 908528db21b676ec1024155753613733b1b422ef Mon Sep 17 00:00:00 2001 From: James Gersbach Date: Tue, 25 Jul 2023 10:44:14 -0700 Subject: [PATCH 123/517] fixes to comments on pr --- crates/forge_analyzer/src/definitions.rs | 26 ++++++++++-------------- 1 file changed, 11 insertions(+), 15 deletions(-) diff --git a/crates/forge_analyzer/src/definitions.rs b/crates/forge_analyzer/src/definitions.rs index b2f2bf65..750ad58c 100644 --- a/crates/forge_analyzer/src/definitions.rs +++ b/crates/forge_analyzer/src/definitions.rs @@ -636,11 +636,6 @@ impl ResolverTable { .unwrap_or_else(|| self.reserve_symbol(id, module)) } - #[inline] - fn get_sym(&mut self, id: Id, module: ModId) -> Option { - self.sym_id(id.clone(), module) - } - fn reserve_def(&mut self, name: JsWord, module: ModId) -> DefId { self.defs.push_and_get_key(DefRes::default()); self.names.push_and_get_key(name); @@ -1597,7 +1592,7 @@ impl Visit for FunctionCollector<'_> { if let Pat::Expr(expr) = &**pat { if let Expr::Member(mem_expr) = &**expr { if let Expr::Ident(ident) = &*mem_expr.obj { - if ident.sym.to_string() == "exports" { + if &ident.sym == "exports" { if let MemberProp::Ident(ident_property) = &mem_expr.prop { match &*n.right { Expr::Fn(FnExpr { ident, function }) => { @@ -2211,8 +2206,8 @@ impl Visit for ExportCollector<'_> { fn visit_assign_expr(&mut self, n: &AssignExpr) { if let Some(ident) = ident_from_assign_expr(n) { - if ident.sym.to_string() == "module" { - if let Some(mem_expr) = mem_expr_from_assign(n.clone()) { + if &ident.sym == "module" { + if let Some(mem_expr) = mem_expr_from_assign(n) { if let MemberProp::Ident(ident_property) = &mem_expr.prop { if ident_property.sym.to_string() == "exports" { match &*n.right { @@ -2229,10 +2224,11 @@ impl Visit for ExportCollector<'_> { } } } - } else if ident.sym.to_string() == "exports" { - if let Some(mem_expr) = mem_expr_from_assign(n.clone()) { + } else if &ident.sym == "exports" { + if let Some(mem_expr) = mem_expr_from_assign(n) { if let MemberProp::Ident(ident_property) = &mem_expr.prop { //self.add_export(DefRes::Undefined, ident_property.to_id()); + // TODO: handling aliases match &*n.right { Expr::Fn(FnExpr { ident, function }) => { self.add_export(DefRes::Function(()), ident_property.to_id()); @@ -2301,7 +2297,7 @@ impl Visit for ExportCollector<'_> { } fn ident_from_assign_expr(n: &AssignExpr) -> Option { - if let Some(mem_expr) = mem_expr_from_assign(n.clone()) { + if let Some(mem_expr) = mem_expr_from_assign(n) { if let Expr::Ident(ident) = &*mem_expr.obj { return Some(ident.clone()); } @@ -2309,11 +2305,11 @@ fn ident_from_assign_expr(n: &AssignExpr) -> Option { None } -fn mem_expr_from_assign(n: AssignExpr) -> Option { +fn mem_expr_from_assign(n: &AssignExpr) -> Option<&MemberExpr> { if let PatOrExpr::Pat(pat) = &n.left { if let Pat::Expr(expr) = &**pat { if let Expr::Member(mem_expr) = &**expr { - return Some(mem_expr.clone()); + return Some(mem_expr); } } } @@ -2363,8 +2359,8 @@ impl Environment { } #[inline] - fn get_sym(&mut self, id: Id, module: ModId) -> Option { - self.resolver.get_sym(id, module) + fn get_sym(&self, id: Id, module: ModId) -> Option { + self.resolver.sym_id(id, module) } fn new_key_from_res(&mut self, id: DefId, res: DefRes) -> DefKey { From 71c5071a549801963016daecd5b6f30aa087fd0e Mon Sep 17 00:00:00 2001 From: James Gersbach Date: Tue, 25 Jul 2023 10:47:58 -0700 Subject: [PATCH 124/517] formatting --- crates/forge_analyzer/src/definitions.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/forge_analyzer/src/definitions.rs b/crates/forge_analyzer/src/definitions.rs index 750ad58c..56c631ae 100644 --- a/crates/forge_analyzer/src/definitions.rs +++ b/crates/forge_analyzer/src/definitions.rs @@ -2209,7 +2209,7 @@ impl Visit for ExportCollector<'_> { if &ident.sym == "module" { if let Some(mem_expr) = mem_expr_from_assign(n) { if let MemberProp::Ident(ident_property) = &mem_expr.prop { - if ident_property.sym.to_string() == "exports" { + if &ident_property.sym == "exports" { match &*n.right { Expr::Fn(FnExpr { ident, function }) => self.add_default( DefRes::Function(()), @@ -2228,7 +2228,7 @@ impl Visit for ExportCollector<'_> { if let Some(mem_expr) = mem_expr_from_assign(n) { if let MemberProp::Ident(ident_property) = &mem_expr.prop { //self.add_export(DefRes::Undefined, ident_property.to_id()); - // TODO: handling aliases + // TODO: handling aliases match &*n.right { Expr::Fn(FnExpr { ident, function }) => { self.add_export(DefRes::Function(()), ident_property.to_id()); From 4c59d396fd48eaa446e43060f145cb96094f6a0b Mon Sep 17 00:00:00 2001 From: James Gersbach Date: Tue, 25 Jul 2023 10:51:44 -0700 Subject: [PATCH 125/517] fixes to pr --- crates/forge_analyzer/src/definitions.rs | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/crates/forge_analyzer/src/definitions.rs b/crates/forge_analyzer/src/definitions.rs index 56c631ae..7f70654c 100644 --- a/crates/forge_analyzer/src/definitions.rs +++ b/crates/forge_analyzer/src/definitions.rs @@ -2306,12 +2306,8 @@ fn ident_from_assign_expr(n: &AssignExpr) -> Option { } fn mem_expr_from_assign(n: &AssignExpr) -> Option<&MemberExpr> { - if let PatOrExpr::Pat(pat) = &n.left { - if let Pat::Expr(expr) = &**pat { - if let Expr::Member(mem_expr) = &**expr { - return Some(mem_expr); - } - } + if let Some(Expr::Member(mem_expr)) = n.left.as_expr() { + return Some(mem_expr); } None } From 6cfda3c4c9ce96d31ada3657d45d804b7cf77f71 Mon Sep 17 00:00:00 2001 From: James Gersbach Date: Thu, 27 Jul 2023 11:01:38 -0700 Subject: [PATCH 126/517] updates to pr --- crates/forge_analyzer/src/definitions.rs | 43 +++++++++++------------- crates/forge_analyzer/src/utils.rs | 5 +-- 2 files changed, 23 insertions(+), 25 deletions(-) diff --git a/crates/forge_analyzer/src/definitions.rs b/crates/forge_analyzer/src/definitions.rs index 45c3b7a4..7944d266 100644 --- a/crates/forge_analyzer/src/definitions.rs +++ b/crates/forge_analyzer/src/definitions.rs @@ -696,7 +696,7 @@ struct FunctionAnalyzer<'cx> { } #[derive(Debug, Clone, Copy, PartialEq, Eq)] -enum CalleeRef<'a> { +pub enum CalleeRef<'a> { Expr(&'a Expr), Import, Super, @@ -960,6 +960,25 @@ impl<'cx> FunctionAnalyzer<'cx> { } } } + + if call_func_with_name(&callee, "then") + || call_func_with_name(&callee, "map") + || call_func_with_name(&callee, "foreach") + || call_func_with_name(&callee, "filter") + { + if let Some(lambda_function) = args.get(0) { + if let Expr::Arrow(arrow) = &*lambda_function.expr { + if let BlockStmtOrExpr::BlockStmt(block_stmt) = &arrow.body { + block_stmt + .stmts + .iter() + .for_each(|stmt| self.lower_stmt(stmt)); + return Operand::UNDEF; + } + } + } + } + let lowered_args = args.iter().map(|arg| self.lower_expr(&arg.expr)).collect(); let callee = match callee { CalleeRef::Super => Operand::Var(Variable::SUPER), @@ -1541,27 +1560,6 @@ impl Visit for LocalDefiner<'_> { fn visit_fn_decl(&mut self, _: &FnDecl) {} } -impl Visit for FunctionAnalyzer<'_> { - fn visit_call_expr(&mut self, n: &CallExpr) { - if call_func_with_name(n, "then") - || call_func_with_name(n, "map") - || call_func_with_name(n, "foreach") - || call_func_with_name(n, "filter") - { - if let Some(lambda_function) = n.args.get(0) { - if let Expr::Arrow(arrow) = &*lambda_function.expr { - if let BlockStmtOrExpr::BlockStmt(block_stmt) = &arrow.body { - block_stmt - .stmts - .iter() - .for_each(|stmt| self.lower_stmt(stmt)); - } - } - } - } - } -} - impl Visit for FunctionCollector<'_> { fn visit_function(&mut self, n: &Function) { n.visit_children_with(self); @@ -1595,7 +1593,6 @@ impl Visit for FunctionCollector<'_> { operand_stack: vec![], in_lhs: false, }; - n.body.visit_children_with(&mut analyzer); if let Some(BlockStmt { stmts, .. }) = &n.body { analyzer.lower_stmts(stmts); let body = analyzer.body; diff --git a/crates/forge_analyzer/src/utils.rs b/crates/forge_analyzer/src/utils.rs index 354d5fe1..5a01fe65 100644 --- a/crates/forge_analyzer/src/utils.rs +++ b/crates/forge_analyzer/src/utils.rs @@ -1,7 +1,8 @@ +use crate::definitions::CalleeRef; use swc_core::ecma::ast::{CallExpr, Callee, Expr, MemberProp}; -pub fn call_func_with_name(n: &CallExpr, name: &str) -> bool { - if let Callee::Expr(expr) = &n.callee { +pub fn call_func_with_name(n: &CalleeRef, name: &str) -> bool { + if let CalleeRef::Expr(expr) = &n { if let Expr::Member(mem) = &**expr { if let MemberProp::Ident(ident) = &mem.prop { if ident.sym.to_string() == name { From 56b1870879c2e204fe627d550fb2eceebc80bffb Mon Sep 17 00:00:00 2001 From: James Gersbach Date: Thu, 27 Jul 2023 13:23:55 -0700 Subject: [PATCH 127/517] updates for pr --- crates/forge_analyzer/src/definitions.rs | 28 ++++++------------------ crates/forge_analyzer/src/utils.rs | 14 +++++------- 2 files changed, 12 insertions(+), 30 deletions(-) diff --git a/crates/forge_analyzer/src/definitions.rs b/crates/forge_analyzer/src/definitions.rs index 7944d266..1f24b30b 100644 --- a/crates/forge_analyzer/src/definitions.rs +++ b/crates/forge_analyzer/src/definitions.rs @@ -2,7 +2,7 @@ use std::{borrow::Borrow, fmt, mem}; -use crate::utils::call_func_with_name; +use crate::utils::calls_method; use forge_file_resolver::{FileResolver, ForgeResolver}; use forge_utils::{create_newtype, FxHashMap}; @@ -936,9 +936,13 @@ impl<'cx> FunctionAnalyzer<'cx> { if self.res.as_foreign_import(id, "@forge/ui").map_or( false, |imp| matches!(imp, ImportKind::Named(s) if *s == *"useState"), - ) { + ) || calls_method(callee, "then") + || calls_method(callee, "map") + || calls_method(callee, "foreach") + || calls_method(callee, "filter") + { if let [ExprOrSpread { expr, .. }] = args { - debug!("found useState"); + debug!("found useState/then/map/foreach/filter"); match &**expr { Expr::Arrow(ArrowExpr { body, .. }) => match body { BlockStmtOrExpr::BlockStmt(stmt) => { @@ -961,24 +965,6 @@ impl<'cx> FunctionAnalyzer<'cx> { } } - if call_func_with_name(&callee, "then") - || call_func_with_name(&callee, "map") - || call_func_with_name(&callee, "foreach") - || call_func_with_name(&callee, "filter") - { - if let Some(lambda_function) = args.get(0) { - if let Expr::Arrow(arrow) = &*lambda_function.expr { - if let BlockStmtOrExpr::BlockStmt(block_stmt) = &arrow.body { - block_stmt - .stmts - .iter() - .for_each(|stmt| self.lower_stmt(stmt)); - return Operand::UNDEF; - } - } - } - } - let lowered_args = args.iter().map(|arg| self.lower_expr(&arg.expr)).collect(); let callee = match callee { CalleeRef::Super => Operand::Var(Variable::SUPER), diff --git a/crates/forge_analyzer/src/utils.rs b/crates/forge_analyzer/src/utils.rs index 5a01fe65..897622e0 100644 --- a/crates/forge_analyzer/src/utils.rs +++ b/crates/forge_analyzer/src/utils.rs @@ -1,14 +1,10 @@ use crate::definitions::CalleeRef; -use swc_core::ecma::ast::{CallExpr, Callee, Expr, MemberProp}; +use swc_core::ecma::ast::{Expr, MemberProp}; -pub fn call_func_with_name(n: &CalleeRef, name: &str) -> bool { - if let CalleeRef::Expr(expr) = &n { - if let Expr::Member(mem) = &**expr { - if let MemberProp::Ident(ident) = &mem.prop { - if ident.sym.to_string() == name { - return true; - } - } +pub fn calls_method(n: CalleeRef<'_>, name: &str) -> bool { + if let CalleeRef::Expr(Expr::Member(mem)) = &n { + if let MemberProp::Ident(ident) = &mem.prop { + return ident.sym == *name; } } false From 24726c548a1affa9ac4035f4b3df86e736bedfd5 Mon Sep 17 00:00:00 2001 From: James Gersbach Date: Thu, 27 Jul 2023 14:57:23 -0700 Subject: [PATCH 128/517] fix to usestrict --- crates/fsrt/src/main.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/crates/fsrt/src/main.rs b/crates/fsrt/src/main.rs index 3ba3c84e..0802e427 100644 --- a/crates/fsrt/src/main.rs +++ b/crates/fsrt/src/main.rs @@ -181,7 +181,9 @@ fn scan_directory(dir: PathBuf, function: Option<&str>, opts: Opts) -> Result Date: Fri, 28 Jul 2023 16:08:09 -0700 Subject: [PATCH 129/517] updates for pr review --- Dockerfile | 2 +- crates/fsrt/src/main.rs | 20 +++++++++----------- 2 files changed, 10 insertions(+), 12 deletions(-) diff --git a/Dockerfile b/Dockerfile index 3fd194a5..fa58fe64 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM rust:1.66.0 as build-env +FROM rust:1.70.0 as build-env WORKDIR /app COPY . /app RUN cargo build --release diff --git a/crates/fsrt/src/main.rs b/crates/fsrt/src/main.rs index 0802e427..ae8078fd 100644 --- a/crates/fsrt/src/main.rs +++ b/crates/fsrt/src/main.rs @@ -177,17 +177,15 @@ fn scan_directory(dir: PathBuf, function: Option<&str>, opts: Opts) -> Result>(); - let transpiled_async = paths - .iter() - .map(|path| { - if let Ok(data) = fs::read_to_string(path) { - if let Some(line) = data.lines().nth(0) { - return line.contains("\"use strict\";"); - } - } - false - }) - .any(|path| path); + let transpiled_async = paths.iter().any(|path| { + if let Ok(data) = fs::read_to_string(path) { + return data + .lines() + .next() + .is_some_and(|data| data == "\"use strict\";" || data == "'use strict';"); + } + false + }); let funcrefs = manifest.modules.into_analyzable_functions().flat_map(|f| { f.sequence(|fmod| { From a5797631c048937d8100b6a96e552b7d1b49651a Mon Sep 17 00:00:00 2001 From: James Gersbach Date: Mon, 31 Jul 2023 12:26:41 -0700 Subject: [PATCH 130/517] lowering classes --- crates/forge_analyzer/src/definitions.rs | 282 +++++++++++++++++++++-- crates/forge_analyzer/src/ir.rs | 4 + 2 files changed, 269 insertions(+), 17 deletions(-) diff --git a/crates/forge_analyzer/src/definitions.rs b/crates/forge_analyzer/src/definitions.rs index ec8a55d1..956a6ec2 100644 --- a/crates/forge_analyzer/src/definitions.rs +++ b/crates/forge_analyzer/src/definitions.rs @@ -11,21 +11,22 @@ use swc_core::{ ast::{ ArrayLit, ArrayPat, ArrowExpr, AssignExpr, AssignOp, AssignPat, AssignPatProp, AssignProp, AwaitExpr, BinExpr, BindingIdent, BlockStmt, BlockStmtOrExpr, BreakStmt, - CallExpr, Callee, ClassDecl, ClassExpr, ComputedPropName, CondExpr, ContinueStmt, Decl, - DefaultDecl, DoWhileStmt, ExportAll, ExportDecl, ExportDefaultDecl, ExportDefaultExpr, - ExportNamedSpecifier, Expr, ExprOrSpread, ExprStmt, FnDecl, FnExpr, ForInStmt, - ForOfStmt, ForStmt, Function, Id, Ident, IfStmt, Import, ImportDecl, - ImportDefaultSpecifier, ImportNamedSpecifier, ImportStarAsSpecifier, JSXElement, - JSXElementChild, JSXElementName, JSXExpr, JSXExprContainer, JSXFragment, JSXMemberExpr, - JSXNamespacedName, JSXObject, JSXSpreadChild, JSXText, KeyValuePatProp, KeyValueProp, - LabeledStmt, Lit, MemberExpr, MemberProp, MetaPropExpr, MethodProp, Module, ModuleDecl, - ModuleExportName, ModuleItem, NewExpr, Number, ObjectLit, ObjectPat, ObjectPatProp, - OptCall, OptChainBase, OptChainExpr, ParenExpr, Pat, PatOrExpr, PrivateName, Prop, - PropName, PropOrSpread, RestPat, ReturnStmt, SeqExpr, Stmt, Str, Super, SuperProp, - SuperPropExpr, SwitchStmt, TaggedTpl, ThisExpr, ThrowStmt, Tpl, TplElement, TryStmt, - TsAsExpr, TsConstAssertion, TsInstantiation, TsNonNullExpr, TsSatisfiesExpr, - TsTypeAssertion, UnaryExpr, UpdateExpr, VarDecl, VarDeclOrExpr, VarDeclOrPat, - VarDeclarator, WhileStmt, WithStmt, YieldExpr, + CallExpr, Callee, ClassDecl, ClassExpr, ClassMethod, ComputedPropName, CondExpr, + Constructor, ContinueStmt, Decl, DefaultDecl, DoWhileStmt, ExportAll, ExportDecl, + ExportDefaultDecl, ExportDefaultExpr, ExportNamedSpecifier, Expr, ExprOrSpread, + ExprStmt, FnDecl, FnExpr, ForInStmt, ForOfStmt, ForStmt, Function, Id, Ident, IfStmt, + Import, ImportDecl, ImportDefaultSpecifier, ImportNamedSpecifier, + ImportStarAsSpecifier, JSXElement, JSXElementChild, JSXElementName, JSXExpr, + JSXExprContainer, JSXFragment, JSXMemberExpr, JSXNamespacedName, JSXObject, + JSXSpreadChild, JSXText, KeyValuePatProp, KeyValueProp, LabeledStmt, Lit, MemberExpr, + MemberProp, MetaPropExpr, MethodProp, Module, ModuleDecl, ModuleExportName, ModuleItem, + NewExpr, Number, ObjectLit, ObjectPat, ObjectPatProp, OptCall, OptChainBase, + OptChainExpr, ParenExpr, Pat, PatOrExpr, PrivateName, Prop, PropName, PropOrSpread, + RestPat, ReturnStmt, SeqExpr, Stmt, Str, Super, SuperProp, SuperPropExpr, SwitchStmt, + TaggedTpl, ThisExpr, ThrowStmt, Tpl, TplElement, TryStmt, TsAsExpr, TsConstAssertion, + TsInstantiation, TsNonNullExpr, TsSatisfiesExpr, TsTypeAssertion, UnaryExpr, + UpdateExpr, VarDecl, VarDeclOrExpr, VarDeclOrPat, VarDeclarator, WhileStmt, WithStmt, + YieldExpr, }, atoms::{Atom, JsWord}, visit::{noop_visit_type, Visit, VisitWith}, @@ -152,6 +153,7 @@ pub fn run_resolver( let mut lowerer = Lowerer { res: &mut environment, curr_mod, + curr_class: None, parents: vec![], curr_def: None, }; @@ -161,6 +163,7 @@ pub fn run_resolver( for (curr_mod, module) in modules.iter_enumerated() { let mut collector = FunctionCollector { res: &mut environment, + curr_class: None, module: curr_mod, parent: None, }; @@ -473,6 +476,7 @@ enum LowerStage { struct Lowerer<'cx> { res: &'cx mut Environment, curr_mod: ModId, + curr_class: Option, parents: Vec, curr_def: Option, } @@ -680,6 +684,7 @@ impl ResolverTable { struct FunctionCollector<'cx> { res: &'cx mut Environment, module: ModId, + curr_class: Option, parent: Option, } @@ -929,6 +934,63 @@ impl<'cx> FunctionAnalyzer<'cx> { var } + fn resolve_class_prop(&self, obj: &Expr) -> Option<&Class> { + if let Expr::Ident(ident) = &*obj { + if let Some(defid) = self.res.sym_to_id(ident.to_id(), self.module) { + if let Some(potential_class) = self.body.class_instantiations.get(&defid) { + if let Some(DefKind::Class(class_id)) = + self.res.defs.defs.get(potential_class.clone()) + { + return self.res.defs.classes.get(class_id.clone()); + } + } + } + } + None + } + + fn get_operand_for_call(&mut self, expr: &Expr) -> Operand { + if let Expr::Ident(ident) = expr { + if let Some(DefKind::Class(class)) = self.res.sym_to_def(ident.to_id(), self.module) { + let id = ident.to_id(); + let Some(def) = self.res.sym_to_id(id.clone(), self.module) else { + return Literal::Undef.into(); + }; + if let Some((_, def_constructor)) = + class.pub_members.iter().find(|(name, defid)| { + if name == "constructor" { + return true; + } else { + false + } + }) + { + let var = self.body.get_or_insert_global(def_constructor.clone()); + return Operand::with_var(var); + } + } + } + if let Expr::Member(MemberExpr { obj, prop, .. }) = expr { + if let Some(class) = self.resolve_class_prop(obj) { + if let MemberProp::Ident(ident) = prop { + if let Some((_, def_constructor)) = + class.pub_members.iter().find(|(name, defid)| { + if name == &ident.sym { + return true; + } else { + false + } + }) + { + let var = self.body.get_or_insert_global(def_constructor.clone()); + return Operand::with_var(var); + } + } + } + } + self.lower_expr(expr) + } + fn lower_call(&mut self, callee: CalleeRef<'_>, args: &[ExprOrSpread]) -> Operand { let props = normalize_callee_expr(callee, self.res, self.module); if let Some(&PropPath::Def(id)) = props.first() { @@ -959,11 +1021,12 @@ impl<'cx> FunctionAnalyzer<'cx> { } } } + let lowered_args = args.iter().map(|arg| self.lower_expr(&arg.expr)).collect(); let callee = match callee { CalleeRef::Super => Operand::Var(Variable::SUPER), CalleeRef::Import => Operand::UNDEF, - CalleeRef::Expr(expr) => self.lower_expr(expr), + CalleeRef::Expr(expr) => self.get_operand_for_call(expr), }; let first_arg = args.first().map(|expr| &*expr.expr); let call = match self.as_intrinsic(&props, first_arg) { @@ -1267,7 +1330,18 @@ impl<'cx> FunctionAnalyzer<'cx> { Operand::with_var(phi) } Expr::Call(CallExpr { callee, args, .. }) => self.lower_call(callee.into(), args), - Expr::New(NewExpr { callee, args, .. }) => Operand::UNDEF, + Expr::New(NewExpr { callee, args, .. }) => { + if let Expr::Ident(ident) = &**callee { + let someething = self.res.sym_to_def(ident.to_id(), self.module); + // remove the clone + return self.lower_call( + CalleeRef::Expr(callee), + args.clone().unwrap_or_default().as_slice(), + ); + } + + Operand::UNDEF + } Expr::Seq(SeqExpr { exprs, .. }) => { if let Some((last, rest)) = exprs.split_last() { for expr in rest { @@ -1598,6 +1672,27 @@ impl Visit for LocalDefiner<'_> { fn visit_var_declarator(&mut self, n: &VarDeclarator) { n.name.visit_with(self); + if let Some(init) = &n.init { + if let Expr::New(new_expr) = &**init { + if let Pat::Ident(ident) = &n.name { + if let Some(defid_variable) = self.res.sym_to_id(ident.to_id(), self.module) { + if let Expr::Ident(ident) = &*new_expr.callee { + if let Some(DefKind::Class(class)) = + self.res.sym_to_def(ident.to_id(), self.module) + { + if let Some(defid_class) = + self.res.sym_to_id(ident.to_id(), self.module) + { + self.body + .class_instantiations + .insert(defid_variable, defid_class); + } + } + } + } + } + } + } } fn visit_decl(&mut self, n: &Decl) { @@ -1616,6 +1711,108 @@ impl Visit for LocalDefiner<'_> { } impl Visit for FunctionCollector<'_> { + fn visit_class_decl(&mut self, n: &ClassDecl) { + if let Some(DefKind::Class(class)) = self.res.sym_to_def(n.ident.to_id(), self.module) { + self.curr_class = Some(class.def); // sets the current class so that mehtods are assigned to the proper class + } + n.visit_children_with(self); + } + + fn visit_constructor(&mut self, n: &Constructor) { + n.visit_children_with(self); + if let Some(class_def) = self.curr_class { + if let DefKind::Class(class) = self.res.clone().def_ref(class_def) { + if let Some((_, owner)) = &class.pub_members.iter().find(|(name, defid)| { + if name == "constructor" { + return true; + } else { + false + } + }) { + let mut argdef = ArgDefiner { + res: self.res, + module: self.module, + func: owner.clone(), + body: Body::with_owner(owner.clone()), + }; + n.params.visit_children_with(&mut argdef); + let body = argdef.body; + let mut localdef = LocalDefiner { + res: self.res, + module: self.module, + func: owner.clone(), + body, + }; + n.body.visit_children_with(&mut localdef); + let body = localdef.body; + let mut analyzer = FunctionAnalyzer { + res: self.res, + module: self.module, + current_def: owner.clone(), + assigning_to: None, + body, + block: BasicBlockId::default(), + operand_stack: vec![], + in_lhs: false, + }; + if let Some(BlockStmt { stmts, .. }) = &n.body { + analyzer.lower_stmts(stmts); + let body = analyzer.body; + *self.res.def_mut(owner.clone()).expect_body() = body; + } + } + } + } + } + + fn visit_class_method(&mut self, n: &ClassMethod) { + n.visit_children_with(self); + if let Some(class_def) = self.curr_class { + if let DefKind::Class(class) = self.res.clone().def_ref(class_def) { + if let Some((_, owner)) = &class.pub_members.iter().find(|(name, defid)| { + if let PropName::Ident(ident) = &n.key { + if name == &ident.sym { + return true; + } + } + false + }) { + let mut argdef = ArgDefiner { + res: self.res, + module: self.module, + func: owner.clone(), + body: Body::with_owner(owner.clone()), + }; + n.function.params.visit_children_with(&mut argdef); + let body = argdef.body; + let mut localdef = LocalDefiner { + res: self.res, + module: self.module, + func: owner.clone(), + body, + }; + n.function.body.visit_children_with(&mut localdef); + let body = localdef.body; + let mut analyzer = FunctionAnalyzer { + res: self.res, + module: self.module, + current_def: owner.clone(), + assigning_to: None, + body, + block: BasicBlockId::default(), + operand_stack: vec![], + in_lhs: false, + }; + if let Some(BlockStmt { stmts, .. }) = &n.function.body { + analyzer.lower_stmts(stmts); + let body = analyzer.body; + *self.res.def_mut(owner.clone()).expect_body() = body; + } + } + } + } + } + fn visit_function(&mut self, n: &Function) { n.visit_children_with(self); let owner = self.parent.unwrap_or_else(|| { @@ -1895,6 +2092,43 @@ fn as_resolver_def<'a>( impl Visit for Lowerer<'_> { noop_visit_type!(); + fn visit_class_decl(&mut self, n: &ClassDecl) { + if let Some(DefKind::Class(class)) = self.res.sym_to_def(n.ident.to_id(), self.curr_mod) { + self.curr_class = Some(class.def); // sets the curr class so that mehtods are assigned to the proper class + } + n.visit_children_with(self); + } + + fn visit_constructor(&mut self, n: &Constructor) { + n.visit_children_with(self); + if let Some(class_def) = self.curr_class { + if let DefKind::Class(class) = self.res.def_mut(class_def) { + if let PropName::Ident(ident) = &n.key { + let owner = + self.res + .add_anonymous("__CONSTRUCTOR", AnonType::Closure, self.curr_mod); + self.res + .add_class_mehtod(&ident.sym, n.key.clone(), class_def, owner); + } + } + } + } + + fn visit_class_method(&mut self, n: &ClassMethod) { + n.visit_children_with(self); + if let Some(class_def) = self.curr_class { + if let DefKind::Class(class) = self.res.def_mut(class_def) { + if let PropName::Ident(ident) = &n.key { + let owner = + self.res + .add_anonymous("__CLASSMETHOD", AnonType::Closure, self.curr_mod); + self.res + .add_class_mehtod(&ident.sym, n.key.clone(), class_def, owner); + } + } + } + } + fn visit_call_expr(&mut self, n: &CallExpr) { if let Some(expr) = n.callee.as_expr() { if let Some((objid, ResolverDef::FnDef)) = as_resolver(expr, self.res, self.curr_mod) { @@ -2368,6 +2602,20 @@ impl Environment { } } + fn add_class_mehtod( + &mut self, + name: impl Into, + n: PropName, + class_def: DefId, + owner: DefId, + ) { + if let DefKind::Class(class) = self.def_mut(class_def) { + if let PropName::Ident(ident) = &n { + class.pub_members.push((ident.sym.to_owned(), owner)); + } + } + } + fn add_anonymous(&mut self, name: impl Into, kind: AnonType, module: ModId) -> DefId { match kind { AnonType::Obj => { diff --git a/crates/forge_analyzer/src/ir.rs b/crates/forge_analyzer/src/ir.rs index a1b7ecce..3c412781 100644 --- a/crates/forge_analyzer/src/ir.rs +++ b/crates/forge_analyzer/src/ir.rs @@ -6,6 +6,7 @@ use core::fmt; use std::array; use std::collections::BTreeSet; +use std::collections::HashMap; use std::hash; use std::hash::Hash; use std::mem; @@ -30,6 +31,7 @@ use swc_core::ecma::{ast::Id, atoms::Atom}; use typed_index_collections::TiVec; use crate::ctx::ModId; +use crate::definitions::Class; use crate::definitions::DefId; use crate::definitions::DefKind; use crate::definitions::Environment; @@ -126,6 +128,7 @@ pub struct Body { blocks: TiVec, vars: TiVec, ident_to_local: FxHashMap, + pub class_instantiations: HashMap, // maps defids of variables to defids of classes def_id_to_vars: FxHashMap, predecessors: OnceCell>>, } @@ -269,6 +272,7 @@ impl Body { vars: local_vars, owner: None, blocks: vec![BasicBlock::default()].into(), + class_instantiations: Default::default(), ident_to_local: Default::default(), def_id_to_vars: Default::default(), predecessors: Default::default(), From e83bd268ad7b9f26cce54a17b17778583375ae69 Mon Sep 17 00:00:00 2001 From: gersbach <93059258+gersbach@users.noreply.github.com> Date: Mon, 31 Jul 2023 12:29:19 -0700 Subject: [PATCH 131/517] Update .gitignore --- .gitignore | 1 - 1 file changed, 1 deletion(-) diff --git a/.gitignore b/.gitignore index 235ba4f6..ea8c4bf7 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1 @@ /target -/test-apps \ No newline at end of file From 5bad5cc7c00bc85f7ee580f66d0deb1f32fb97cd Mon Sep 17 00:00:00 2001 From: Joshua Wong Date: Wed, 2 Aug 2023 11:50:49 -0500 Subject: [PATCH 132/517] feat: generate check name based on stacktrace If there was a false positive in the scanner, a similar ticket will be created if the partner pushes an update, since the artifact id is included in the path. We remove the artifact id by starting the hash at the src directory and include the stacktrace, so that the ticket will remain the same. --- crates/forge_analyzer/src/checkers.rs | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/crates/forge_analyzer/src/checkers.rs b/crates/forge_analyzer/src/checkers.rs index 89d64312..8d9a1d18 100644 --- a/crates/forge_analyzer/src/checkers.rs +++ b/crates/forge_analyzer/src/checkers.rs @@ -183,8 +183,12 @@ impl IntoVuln for AuthZVuln { use std::hash::{Hash, Hasher}; let mut hasher = DefaultHasher::new(); - self.file.hash(&mut hasher); + self.file + .iter() + .skip_while(|comp| *comp != "src") + .for_each(|comp| comp.hash(&mut hasher)); self.entry_func.hash(&mut hasher); + self.stack.hash(&mut hasher); Vulnerability { check_name: format!("Custom-Check-Authorization-{}", hasher.finish()), description: format!("Authorization bypass detected through {} in {:?}.", self.entry_func, self.file), @@ -428,8 +432,12 @@ impl IntoVuln for AuthNVuln { use std::hash::{Hash, Hasher}; let mut hasher = DefaultHasher::new(); - self.file.hash(&mut hasher); + self.file + .iter() + .skip_while(|comp| *comp != "src") + .for_each(|comp| comp.hash(&mut hasher)); self.entry_func.hash(&mut hasher); + self.stack.hash(&mut hasher); Vulnerability { check_name: format!("Custom-Check-Authentication-{}", hasher.finish()), description: format!("Insufficient Authentication through webhook {} in {:?}.", self.entry_func, self.file), From 0b96d9a26a94f0b6ce03bb3f7ebb91d1bfcb690d Mon Sep 17 00:00:00 2001 From: James Gersbach Date: Wed, 2 Aug 2023 12:06:04 -0700 Subject: [PATCH 133/517] small updates --- crates/forge_analyzer/src/analyzer.rs | 7 ++----- crates/forge_analyzer/src/checkers.rs | 2 ++ crates/forge_analyzer/src/definitions.rs | 10 ---------- 3 files changed, 4 insertions(+), 15 deletions(-) diff --git a/crates/forge_analyzer/src/analyzer.rs b/crates/forge_analyzer/src/analyzer.rs index 0aec70b0..3e9ba2aa 100644 --- a/crates/forge_analyzer/src/analyzer.rs +++ b/crates/forge_analyzer/src/analyzer.rs @@ -6,8 +6,8 @@ use std::{fmt, mem}; use crate::ctx::{BasicBlockId, FunctionMeta, IrStmt, ModuleCtx, TerminatorKind, STARTING_BLOCK}; use crate::lattice::MeetSemiLattice; use swc_core::ecma::ast::{ - ArrowExpr, BindingIdent, CallExpr, Callee, Expr, ExprOrSpread, ExprStmt, FnDecl, FnExpr, Id, - IfStmt, JSXElementName, JSXOpeningElement, MemberExpr, MemberProp, Pat, Stmt, Str, ThrowStmt, + ArrowExpr, BindingIdent, CallExpr, Callee, Expr, ExprOrSpread, FnDecl, FnExpr, Id, IfStmt, + JSXElementName, JSXOpeningElement, MemberExpr, MemberProp, Pat, Stmt, Str, ThrowStmt, TplElement, VarDeclarator, }; use swc_core::ecma::visit::{noop_visit_type, Visit, VisitWith}; @@ -237,7 +237,6 @@ impl Visit for FunctionAnalyzer<'_> { _ => {} } } - // we don't need to add this to the IR, since we know it's useless return; } else { IrStmt::Call(id.into()) @@ -285,8 +284,6 @@ impl Visit for FunctionCollector<'_> { n.function.visit_children_with(self); } - // fn visit_asign_expr() - fn visit_var_declarator(&mut self, n: &VarDeclarator) { if let VarDeclarator { name: Pat::Ident(BindingIdent { id, .. }), diff --git a/crates/forge_analyzer/src/checkers.rs b/crates/forge_analyzer/src/checkers.rs index 04ad2192..7ae5246c 100644 --- a/crates/forge_analyzer/src/checkers.rs +++ b/crates/forge_analyzer/src/checkers.rs @@ -799,6 +799,8 @@ impl<'cx> Dataflow<'cx> for PermissionDataflow { inst: &'cx Inst, initial_state: Self::State, ) -> Self::State { + + println!("inst {inst}"); match inst { Inst::Expr(rvalue) => { self.transfer_rvalue(interp, def, loc, block, rvalue, initial_state) diff --git a/crates/forge_analyzer/src/definitions.rs b/crates/forge_analyzer/src/definitions.rs index 7030cb95..a42ac6ef 100644 --- a/crates/forge_analyzer/src/definitions.rs +++ b/crates/forge_analyzer/src/definitions.rs @@ -2419,16 +2419,6 @@ impl Visit for ExportCollector<'_> { } } } - - if let PatOrExpr::Pat(pat) = &n.left { - if let Pat::Expr(expr) = &**pat { - if let Expr::Member(mem_expr) = &**expr { - if let Expr::Ident(ident) = &*mem_expr.obj {} - } - } - } - - /** We need to get the exports here */ n.visit_children_with(self); } From 159c1f81b24468b03b7e2ffc3da61a34f3abb419 Mon Sep 17 00:00:00 2001 From: James Gersbach Date: Thu, 3 Aug 2023 08:59:52 -0700 Subject: [PATCH 134/517] final permission resolver --- Cargo.lock | 654 ++++++++++++++++-- Cargo.toml | 1 + crates/forge_analyzer/Cargo.toml | 1 + crates/forge_analyzer/src/checkers.rs | 1 - crates/forge_permission_resolver/Cargo.toml | 19 + crates/forge_permission_resolver/src/lib.rs | 2 + .../src/permissions_resolver.rs | 224 ++++++ crates/forge_permission_resolver/src/test.rs | 74 ++ 8 files changed, 921 insertions(+), 55 deletions(-) create mode 100644 crates/forge_permission_resolver/Cargo.toml create mode 100644 crates/forge_permission_resolver/src/lib.rs create mode 100644 crates/forge_permission_resolver/src/permissions_resolver.rs create mode 100644 crates/forge_permission_resolver/src/test.rs diff --git a/Cargo.lock b/Cargo.lock index a0412ab5..c272f41b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -80,7 +80,18 @@ dependencies = [ "proc-macro2", "quote", "swc_macros_common", - "syn", + "syn 1.0.107", +] + +[[package]] +name = "async-channel" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81953c529336010edd6d8e358f886d9581267795c61b19475b71314bffa46d35" +dependencies = [ + "concurrent-queue", + "event-listener", + "futures-core", ] [[package]] @@ -103,7 +114,7 @@ dependencies = [ "proc-macro-error", "proc-macro2", "quote", - "syn", + "syn 1.0.107", ] [[package]] @@ -122,7 +133,7 @@ dependencies = [ "cc", "cfg-if", "libc", - "miniz_oxide", + "miniz_oxide 0.6.2", "object", "rustc-demangle", ] @@ -133,6 +144,12 @@ version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" +[[package]] +name = "base64" +version = "0.21.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "604178f6c5c21f02dc555784810edfb88d34ac2c73b2eae109655649ee73ce3d" + [[package]] name = "better_scoped_tls" version = "0.1.0" @@ -187,6 +204,18 @@ version = "3.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "572f695136211188308f16ad2ca5c851a712c464060ae6974944458eb83880ba" +[[package]] +name = "bytes" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89b2fd2a0dcf38d7971e2194b6b6eebab45ae01067456a7fd93d5547a61b70be" + +[[package]] +name = "castaway" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2698f953def977c68f935bb0dfa959375ad4638570e969e2f1e9f433cbf1af6" + [[package]] name = "cc" version = "1.0.78" @@ -240,7 +269,7 @@ dependencies = [ "proc-macro-error", "proc-macro2", "quote", - "syn", + "syn 1.0.107", ] [[package]] @@ -262,6 +291,15 @@ dependencies = [ "unicode-width", ] +[[package]] +name = "concurrent-queue" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62ec6771ecfa0762d24683ee5a32ad78487a3d3afdc0fb8cae19d2c5deb50b7c" +dependencies = [ + "crossbeam-utils", +] + [[package]] name = "core-foundation-sys" version = "0.8.3" @@ -277,6 +315,15 @@ dependencies = [ "libc", ] +[[package]] +name = "crc32fast" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d" +dependencies = [ + "cfg-if", +] + [[package]] name = "crossbeam-channel" version = "0.5.6" @@ -337,7 +384,38 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6d2301688392eb071b0bf1a37be05c469d3cc4dbbd95df672fe28ab021e6a096" dependencies = [ "quote", - "syn", + "syn 1.0.107", +] + +[[package]] +name = "curl" +version = "0.4.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "509bd11746c7ac09ebd19f0b17782eae80aadee26237658a6b4808afb5c11a22" +dependencies = [ + "curl-sys", + "libc", + "openssl-probe", + "openssl-sys", + "schannel", + "socket2", + "winapi", +] + +[[package]] +name = "curl-sys" +version = "0.4.65+curl-8.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "961ba061c9ef2fe34bbd12b807152d96f0badd2bebe7b90ce6c8c8b7572a0986" +dependencies = [ + "cc", + "libc", + "libnghttp2-sys", + "libz-sys", + "openssl-sys", + "pkg-config", + "vcpkg", + "winapi", ] [[package]] @@ -364,7 +442,7 @@ dependencies = [ "proc-macro2", "quote", "scratch", - "syn", + "syn 1.0.107", ] [[package]] @@ -381,7 +459,7 @@ checksum = "39e61fda7e62115119469c7b3591fd913ecca96fb766cfd3f2e2502ab7bc87a5" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.107", ] [[package]] @@ -405,7 +483,7 @@ dependencies = [ "proc-macro2", "quote", "strsim", - "syn", + "syn 1.0.107", ] [[package]] @@ -416,7 +494,7 @@ checksum = "9c972679f83bdf9c42bd905396b6c3588a843a17f0f16dfcfa3e2c5d57441835" dependencies = [ "darling_core", "quote", - "syn", + "syn 1.0.107", ] [[package]] @@ -454,6 +532,15 @@ version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "90e5c1c8368803113bf0c9584fc495a58b86dc8a29edbf8fe877d21d9507e797" +[[package]] +name = "encoding_rs" +version = "0.8.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071a31f4ee85403370b58aca746f01041ede6f0da2730960ad001edc2b71b394" +dependencies = [ + "cfg-if", +] + [[package]] name = "enum-iterator" version = "1.2.0" @@ -471,7 +558,7 @@ checksum = "828de45d0ca18782232dfb8f3ea9cc428e8ced380eb26a520baaacfc70de39ce" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.107", ] [[package]] @@ -483,7 +570,7 @@ dependencies = [ "pmutil", "proc-macro2", "swc_macros_common", - "syn", + "syn 1.0.107", ] [[package]] @@ -507,12 +594,37 @@ dependencies = [ "libc", ] +[[package]] +name = "event-listener" +version = "2.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0" + +[[package]] +name = "fastrand" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e51093e27b0797c359783294ca4f0a911c270184cb10f85783b118614a1501be" +dependencies = [ + "instant", +] + [[package]] name = "fixedbitset" version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80" +[[package]] +name = "flate2" +version = "1.0.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b9429470923de8e8cbd4d2dc513535400b4b3fef0319fb5c4e1f520a7bef743" +dependencies = [ + "crc32fast", + "miniz_oxide 0.7.1", +] + [[package]] name = "fnv" version = "1.0.7" @@ -525,6 +637,7 @@ version = "0.1.0" dependencies = [ "fixedbitset", "forge_file_resolver", + "forge_permission_resolver", "forge_utils", "itertools", "num-bigint", @@ -560,6 +673,19 @@ dependencies = [ "tracing", ] +[[package]] +name = "forge_permission_resolver" +version = "0.1.0" +dependencies = [ + "isahc", + "pretty_assertions", + "regex", + "serde", + "serde_json", + "tracing", + "ureq", +] + [[package]] name = "forge_utils" version = "0.1.0" @@ -586,7 +712,7 @@ dependencies = [ "pmutil", "proc-macro2", "swc_macros_common", - "syn", + "syn 1.0.107", ] [[package]] @@ -609,6 +735,33 @@ dependencies = [ "walkdir", ] +[[package]] +name = "futures-core" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4bca583b7e26f571124fe5b7561d49cb2868d79116cfa0eefce955557c6fee8c" + +[[package]] +name = "futures-io" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fff74096e71ed47f8e023204cfd0aa1289cd54ae5430a9523be060cdb849964" + +[[package]] +name = "futures-lite" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49a9d51ce47660b1e808d3c990b4709f2f415d928835a17dfd16991515c46bce" +dependencies = [ + "fastrand", + "futures-core", + "futures-io", + "memchr", + "parking", + "pin-project-lite", + "waker-fn", +] + [[package]] name = "generic-array" version = "0.14.6" @@ -639,7 +792,7 @@ dependencies = [ "proc-macro-error", "proc-macro2", "quote", - "syn", + "syn 1.0.107", ] [[package]] @@ -681,6 +834,17 @@ dependencies = [ "libc", ] +[[package]] +name = "http" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd6effc99afb63425aff9b05836f029929e345a6148a14b7ecd5ab67af944482" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + [[package]] name = "iana-time-zone" version = "0.1.53" @@ -738,6 +902,15 @@ dependencies = [ "serde", ] +[[package]] +name = "instant" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" +dependencies = [ + "cfg-if", +] + [[package]] name = "io-lifetimes" version = "1.0.4" @@ -745,7 +918,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e7d6c6f8c91b4b9ed43484ad1a938e393caf35960fce7f82a040497207bd8e9e" dependencies = [ "libc", - "windows-sys", + "windows-sys 0.42.0", ] [[package]] @@ -758,7 +931,7 @@ dependencies = [ "pmutil", "proc-macro2", "quote", - "syn", + "syn 1.0.107", ] [[package]] @@ -770,7 +943,7 @@ dependencies = [ "hermit-abi 0.2.6", "io-lifetimes", "rustix", - "windows-sys", + "windows-sys 0.42.0", ] [[package]] @@ -779,6 +952,33 @@ version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "616cde7c720bb2bb5824a224687d8f77bfd38922027f01d825cd7453be5099fb" +[[package]] +name = "isahc" +version = "1.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "334e04b4d781f436dc315cb1e7515bd96826426345d498149e4bde36b67f8ee9" +dependencies = [ + "async-channel", + "castaway", + "crossbeam-utils", + "curl", + "curl-sys", + "encoding_rs", + "event-listener", + "futures-lite", + "http", + "log", + "mime", + "once_cell", + "polling", + "slab", + "sluice", + "tracing", + "tracing-futures", + "url", + "waker-fn", +] + [[package]] name = "itertools" version = "0.10.5" @@ -893,9 +1093,31 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.139" +version = "0.2.147" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4668fb0ea861c1df094127ac5f1da3409a82116a4ba74fca2e58ef927159bb3" + +[[package]] +name = "libnghttp2-sys" +version = "0.1.7+1.45.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57ed28aba195b38d5ff02b9170cbff627e336a20925e43b4945390401c5dc93f" +dependencies = [ + "cc", + "libc", +] + +[[package]] +name = "libz-sys" +version = "1.1.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "201de327520df007757c1f0adce6e827fe8562fbc28bfd9c15571c66ca1f5f79" +checksum = "d97137b25e321a73eef1418d1d5d2eda4d77e12813f8e6dead84bc52c5870a7b" +dependencies = [ + "cc", + "libc", + "pkg-config", + "vcpkg", +] [[package]] name = "link-cplusplus" @@ -1012,7 +1234,7 @@ checksum = "6b5bc45b761bcf1b5e6e6c4128cd93b84c218721a8d9b894aa0aff4ed180174c" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.107", ] [[package]] @@ -1023,9 +1245,15 @@ checksum = "97c2401ab7ac5282ca5c8b518a87635b1a93762b0b90b9990c509888eeccba29" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.107", ] +[[package]] +name = "mime" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" + [[package]] name = "minimal-lexical" version = "0.2.1" @@ -1041,6 +1269,15 @@ dependencies = [ "adler", ] +[[package]] +name = "miniz_oxide" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7810e0be55b428ada41041c41f32c9f1a42817901b4ccf45fa3d4b6561e74c7" +dependencies = [ + "adler", +] + [[package]] name = "new_debug_unreachable" version = "1.0.4" @@ -1141,6 +1378,24 @@ version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6f61fba1741ea2b3d6a1e3178721804bb716a68a6aeba1149b5d52e3d464ea66" +[[package]] +name = "openssl-probe" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" + +[[package]] +name = "openssl-sys" +version = "0.9.90" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "374533b0e45f3a7ced10fcaeccca020e66656bc03dac384f852e4e5a7a8104a6" +dependencies = [ + "cc", + "libc", + "pkg-config", + "vcpkg", +] + [[package]] name = "os_str_bytes" version = "6.4.1" @@ -1168,6 +1423,12 @@ version = "3.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c1b04fb49957986fdce4d6ee7a65027d55d4b6d2265e5848bbb507b58ccfdb6f" +[[package]] +name = "parking" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14f2252c834a40ed9bb5422029649578e63aa341ac401f74e719dd1afda8394e" + [[package]] name = "parking_lot" version = "0.12.1" @@ -1188,7 +1449,7 @@ dependencies = [ "libc", "redox_syscall", "smallvec", - "windows-sys", + "windows-sys 0.42.0", ] [[package]] @@ -1251,7 +1512,7 @@ dependencies = [ "proc-macro-hack", "proc-macro2", "quote", - "syn", + "syn 1.0.107", ] [[package]] @@ -1263,12 +1524,38 @@ dependencies = [ "siphasher", ] +[[package]] +name = "pin-project" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "030ad2bc4db10a8944cb0d837f158bdfec4d4a4873ab701a95046770d11f8842" +dependencies = [ + "pin-project-internal", +] + +[[package]] +name = "pin-project-internal" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec2e072ecce94ec471b13398d5402c188e76ac03cf74dd1a975161b23a3f6d9c" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.28", +] + [[package]] name = "pin-project-lite" version = "0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116" +[[package]] +name = "pkg-config" +version = "0.3.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26072860ba924cbfa98ea39c8c19b4dd6a4a25423dbdf219c1eca91aa0cf6964" + [[package]] name = "pmutil" version = "0.5.3" @@ -1277,7 +1564,23 @@ checksum = "3894e5d549cccbe44afecf72922f277f603cd4bb0219c8342631ef18fffbe004" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.107", +] + +[[package]] +name = "polling" +version = "2.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b2d323e8ca7996b3e23126511a523f7e62924d93ecd5ae73b333815b0eb3dce" +dependencies = [ + "autocfg", + "bitflags", + "cfg-if", + "concurrent-queue", + "libc", + "log", + "pin-project-lite", + "windows-sys 0.48.0", ] [[package]] @@ -1331,7 +1634,7 @@ dependencies = [ "proc-macro-error-attr", "proc-macro2", "quote", - "syn", + "syn 1.0.107", "version_check", ] @@ -1354,18 +1657,18 @@ checksum = "dc375e1527247fe1a97d8b7156678dfe7c1af2fc075c9a4db3690ecd2a148068" [[package]] name = "proc-macro2" -version = "1.0.50" +version = "1.0.66" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ef7d57beacfaf2d8aee5937dab7b7f28de3cb8b1828479bb5de2a7106f2bae2" +checksum = "18fb31db3f9bddb2ea821cde30a9f70117e3f119938b5ee630b7403aa6e2ead9" dependencies = [ "unicode-ident", ] [[package]] name = "quote" -version = "1.0.23" +version = "1.0.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8856d8364d252a14d474036ea1358d63c9e6965c8e5c1885c18f73d70bff9c7b" +checksum = "50f3b39ccfb720540debaa0164757101c08ecb8d326b15358ce76a62c7e85965" dependencies = [ "proc-macro2", ] @@ -1469,6 +1772,21 @@ version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4389f1d5789befaf6029ebd9f7dac4af7f7e3d61b69d4f30e2ac02b57e7712b0" +[[package]] +name = "ring" +version = "0.16.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3053cf52e236a3ed746dfc745aa9cacf1b791d846bdaf412f60a8d7d6e17c8fc" +dependencies = [ + "cc", + "libc", + "once_cell", + "spin", + "untrusted", + "web-sys", + "winapi", +] + [[package]] name = "rustc-demangle" version = "0.1.21" @@ -1501,7 +1819,39 @@ dependencies = [ "io-lifetimes", "libc", "linux-raw-sys", - "windows-sys", + "windows-sys 0.42.0", +] + +[[package]] +name = "rustls" +version = "0.21.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d1feddffcfcc0b33f5c6ce9a29e341e4cd59c3f78e7ee45f4a40c038b1d6cbb" +dependencies = [ + "log", + "ring", + "rustls-webpki 0.101.2", + "sct", +] + +[[package]] +name = "rustls-webpki" +version = "0.100.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6207cd5ed3d8dca7816f8f3725513a34609c0c765bf652b8c3cb4cfd87db46b" +dependencies = [ + "ring", + "untrusted", +] + +[[package]] +name = "rustls-webpki" +version = "0.101.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "513722fd73ad80a71f72b61009ea1b584bcfa1483ca93949c8f290298837fa59" +dependencies = [ + "ring", + "untrusted", ] [[package]] @@ -1525,6 +1875,15 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "schannel" +version = "0.1.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c3733bf4cf7ea0880754e19cb5a462007c4a8c1914bff372ccc95b464f1df88" +dependencies = [ + "windows-sys 0.48.0", +] + [[package]] name = "scoped-tls" version = "1.0.1" @@ -1543,6 +1902,16 @@ version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ddccb15bcce173023b3fedd9436f882a0739b8dfb45e4f6b6002bee5929f61b2" +[[package]] +name = "sct" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d53dcdb7c9f8158937a7981b48accfd39a43af418591a5d008c7b22b5e1b7ca4" +dependencies = [ + "ring", + "untrusted", +] + [[package]] name = "semver" version = "0.9.0" @@ -1595,7 +1964,7 @@ checksum = "af487d118eecd09402d70a5d72551860e788df87b464af30e5ea6a38c75c541e" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.107", ] [[package]] @@ -1648,6 +2017,26 @@ version = "0.3.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7bd3e3206899af3f8b12af284fafc038cc1dc2b41d1b89dd17297221c5d225de" +[[package]] +name = "slab" +version = "0.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6528351c9bc8ab22353f9d776db39a20288e8d6c37ef8cfe3317cf875eecfc2d" +dependencies = [ + "autocfg", +] + +[[package]] +name = "sluice" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d7400c0eff44aa2fcb5e31a5f24ba9716ed90138769e4977a2ba6014ae63eb5" +dependencies = [ + "async-channel", + "futures-core", + "futures-io", +] + [[package]] name = "smallvec" version = "1.10.0" @@ -1660,13 +2049,23 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f67ad224767faa3c7d8b6d91985b78e70a1324408abcb1cfcc2be4c06bc06043" +[[package]] +name = "socket2" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64a4a911eed85daf18834cfaa86a79b7d266ff93ff5ba14005426219480ed662" +dependencies = [ + "libc", + "winapi", +] + [[package]] name = "sourcemap" version = "6.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "aebe057d110ddba043708da3fb010bf562ff6e9d4d60c9ee92860527bcbeccd6" dependencies = [ - "base64", + "base64 0.13.1", "if_chain", "rustc_version", "serde", @@ -1675,6 +2074,12 @@ dependencies = [ "url", ] +[[package]] +name = "spin" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" + [[package]] name = "st-map" version = "0.1.6" @@ -1700,7 +2105,7 @@ dependencies = [ "pmutil", "proc-macro2", "quote", - "syn", + "syn 1.0.107", ] [[package]] @@ -1745,7 +2150,7 @@ dependencies = [ "proc-macro2", "quote", "swc_macros_common", - "syn", + "syn 1.0.107", ] [[package]] @@ -1790,7 +2195,7 @@ checksum = "a68788142778ec4fb76367b7ae2883b6829763e139d2531a8dce17df63659242" dependencies = [ "ahash", "anyhow", - "base64", + "base64 0.13.1", "dashmap", "either", "indexmap", @@ -1909,7 +2314,7 @@ dependencies = [ "proc-macro2", "quote", "swc_macros_common", - "syn", + "syn 1.0.107", ] [[package]] @@ -1977,7 +2382,7 @@ dependencies = [ "proc-macro2", "quote", "swc_macros_common", - "syn", + "syn 1.0.107", ] [[package]] @@ -2208,7 +2613,7 @@ dependencies = [ "proc-macro2", "quote", "swc_macros_common", - "syn", + "syn 1.0.107", ] [[package]] @@ -2290,7 +2695,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1c3499f6c71eeb443a3cdcb0a82a8dcb70d83ce589f5be652fc0ff25ff68344b" dependencies = [ "ahash", - "base64", + "base64 0.13.1", "dashmap", "indexmap", "once_cell", @@ -2384,7 +2789,7 @@ dependencies = [ "pmutil", "proc-macro2", "quote", - "syn", + "syn 1.0.107", ] [[package]] @@ -2421,7 +2826,7 @@ dependencies = [ "pmutil", "proc-macro2", "quote", - "syn", + "syn 1.0.107", ] [[package]] @@ -2453,7 +2858,7 @@ checksum = "a4795c8d23e0de62eef9cac0a20ae52429ee2ffc719768e838490f195b7d7267" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.107", ] [[package]] @@ -2477,7 +2882,7 @@ dependencies = [ "proc-macro2", "quote", "swc_macros_common", - "syn", + "syn 1.0.107", ] [[package]] @@ -2491,6 +2896,17 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "syn" +version = "2.0.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04361975b3f5e348b2189d8dc55bc942f278b2d482a6a0365de5bdd62d351567" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + [[package]] name = "termcolor" version = "1.2.0" @@ -2517,7 +2933,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cb20089a8ba2b69debd491f8d2d023761cbf196e999218c591fa1e7e15a21907" dependencies = [ "rustix", - "windows-sys", + "windows-sys 0.42.0", ] [[package]] @@ -2548,7 +2964,7 @@ checksum = "1fb327af4685e4d03fa8cbcf1716380da910eeb2bb8be417e7f9fd3fb164f36f" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.107", ] [[package]] @@ -2622,6 +3038,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ce8c33a8d48bd45d624a6e523445fd21ec13d3653cd51f681abf67418f54eb8" dependencies = [ "cfg-if", + "log", "pin-project-lite", "tracing-attributes", "tracing-core", @@ -2635,7 +3052,7 @@ checksum = "4017f8f45139870ca7e672686113917c71c7a6e02d4924eda67186083c03081a" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.107", ] [[package]] @@ -2648,6 +3065,16 @@ dependencies = [ "valuable", ] +[[package]] +name = "tracing-futures" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97d095ae15e245a057c8e8451bab9b3ee1e1f68e9ba2b4fbc18d0ac5237835f2" +dependencies = [ + "pin-project", + "tracing", +] + [[package]] name = "tracing-log" version = "0.1.3" @@ -2767,6 +3194,28 @@ version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bc7ed8ba44ca06be78ea1ad2c3682a43349126c8818054231ee6f4748012aed2" +[[package]] +name = "untrusted" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" + +[[package]] +name = "ureq" +version = "2.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b11c96ac7ee530603dcdf68ed1557050f374ce55a5a07193ebf8cbc9f8927e9" +dependencies = [ + "base64 0.21.2", + "flate2", + "log", + "once_cell", + "rustls", + "rustls-webpki 0.100.1", + "url", + "webpki-roots", +] + [[package]] name = "url" version = "2.3.1" @@ -2784,6 +3233,12 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" +[[package]] +name = "vcpkg" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" + [[package]] name = "vergen" version = "7.5.0" @@ -2805,6 +3260,12 @@ version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" +[[package]] +name = "waker-fn" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d5b2c62b4012a3e1eca5a7e077d13b3bf498c4073e33ccd58626607748ceeca" + [[package]] name = "walkdir" version = "2.3.2" @@ -2849,7 +3310,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn", + "syn 1.0.107", "wasm-bindgen-shared", ] @@ -2871,7 +3332,7 @@ checksum = "07bc0c051dc5f23e307b13285f9d75df86bfdf816c5721e573dec1f9b8aa193c" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.107", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -2882,6 +3343,25 @@ version = "0.2.83" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1c38c045535d93ec4f0b4defec448e4291638ee608530863b1e2ba115d4fff7f" +[[package]] +name = "web-sys" +version = "0.3.60" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bcda906d8be16e728fd5adc5b729afad4e444e106ab28cd1c7256e54fa61510f" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "webpki-roots" +version = "0.23.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b03058f88386e5ff5310d9111d53f48b17d732b401aeb83a8d5190f2ac459338" +dependencies = [ + "rustls-webpki 0.100.1", +] + [[package]] name = "winapi" version = "0.3.9" @@ -2919,13 +3399,37 @@ version = "0.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7" dependencies = [ - "windows_aarch64_gnullvm", - "windows_aarch64_msvc", - "windows_i686_gnu", - "windows_i686_msvc", - "windows_x86_64_gnu", - "windows_x86_64_gnullvm", - "windows_x86_64_msvc", + "windows_aarch64_gnullvm 0.42.0", + "windows_aarch64_msvc 0.42.0", + "windows_i686_gnu 0.42.0", + "windows_i686_msvc 0.42.0", + "windows_x86_64_gnu 0.42.0", + "windows_x86_64_gnullvm 0.42.0", + "windows_x86_64_msvc 0.42.0", +] + +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-targets" +version = "0.48.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05d4b17490f70499f20b9e791dcf6a299785ce8af4d709018206dc5b4953e95f" +dependencies = [ + "windows_aarch64_gnullvm 0.48.0", + "windows_aarch64_msvc 0.48.0", + "windows_i686_gnu 0.48.0", + "windows_i686_msvc 0.48.0", + "windows_x86_64_gnu 0.48.0", + "windows_x86_64_gnullvm 0.48.0", + "windows_x86_64_msvc 0.48.0", ] [[package]] @@ -2934,42 +3438,84 @@ version = "0.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "41d2aa71f6f0cbe00ae5167d90ef3cfe66527d6f613ca78ac8024c3ccab9a19e" +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91ae572e1b79dba883e0d315474df7305d12f569b400fcf90581b06062f7e1bc" + [[package]] name = "windows_aarch64_msvc" version = "0.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dd0f252f5a35cac83d6311b2e795981f5ee6e67eb1f9a7f64eb4500fbc4dcdb4" +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2ef27e0d7bdfcfc7b868b317c1d32c641a6fe4629c171b8928c7b08d98d7cf3" + [[package]] name = "windows_i686_gnu" version = "0.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fbeae19f6716841636c28d695375df17562ca208b2b7d0dc47635a50ae6c5de7" +[[package]] +name = "windows_i686_gnu" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "622a1962a7db830d6fd0a69683c80a18fda201879f0f447f065a3b7467daa241" + [[package]] name = "windows_i686_msvc" version = "0.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "84c12f65daa39dd2babe6e442988fc329d6243fdce47d7d2d155b8d874862246" +[[package]] +name = "windows_i686_msvc" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4542c6e364ce21bf45d69fdd2a8e455fa38d316158cfd43b3ac1c5b1b19f8e00" + [[package]] name = "windows_x86_64_gnu" version = "0.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bf7b1b21b5362cbc318f686150e5bcea75ecedc74dd157d874d754a2ca44b0ed" +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca2b8a661f7628cbd23440e50b05d705db3686f894fc9580820623656af974b1" + [[package]] name = "windows_x86_64_gnullvm" version = "0.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09d525d2ba30eeb3297665bd434a54297e4170c7f1a44cad4ef58095b4cd2028" +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7896dbc1f41e08872e9d5e8f8baa8fdd2677f29468c4e156210174edc7f7b953" + [[package]] name = "windows_x86_64_msvc" version = "0.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f40009d85759725a34da6d89a94e63d7bdc50a862acf0dbc7c8e488f1edcb6f5" +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a" + [[package]] name = "yansi" version = "0.5.1" diff --git a/Cargo.toml b/Cargo.toml index e333fbf5..e55c6a6b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -35,3 +35,4 @@ forge_loader = { path = "crates/forge_loader" } forge_analyzer = { path = "crates/forge_analyzer" } forge_file_resolver = { path = "crates/forge_file_resolver" } forge_utils = { path = "crates/forge_utils" } +forge_permission_resolver = { path = "crates/forge_permission_resolver" } diff --git a/crates/forge_analyzer/Cargo.toml b/crates/forge_analyzer/Cargo.toml index 130f7fae..1f8d123c 100644 --- a/crates/forge_analyzer/Cargo.toml +++ b/crates/forge_analyzer/Cargo.toml @@ -11,6 +11,7 @@ license.workspace = true fixedbitset.workspace = true forge_file_resolver.workspace = true forge_utils.workspace = true +forge_permission_resolver.workspace = true itertools.workspace = true num-bigint.workspace = true once_cell.workspace = true diff --git a/crates/forge_analyzer/src/checkers.rs b/crates/forge_analyzer/src/checkers.rs index 89d64312..c0c2e867 100644 --- a/crates/forge_analyzer/src/checkers.rs +++ b/crates/forge_analyzer/src/checkers.rs @@ -355,7 +355,6 @@ impl<'cx> Checker<'cx> for AuthenticateChecker { type State = Authenticated; type Dataflow = AuthenticateDataflow; type Vuln = AuthNVuln; - fn visit_intrinsic( &mut self, interp: &Interp<'cx, Self>, diff --git a/crates/forge_permission_resolver/Cargo.toml b/crates/forge_permission_resolver/Cargo.toml new file mode 100644 index 00000000..ce30eaf2 --- /dev/null +++ b/crates/forge_permission_resolver/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "forge_permission_resolver" +authors.workspace = true +version.workspace = true +edition.workspace = true +license.workspace = true + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +isahc = "1.7.2" +serde.workspace = true +serde_json.workspace = true +tracing.workspace = true +regex.workspace = true +ureq = "2.7.1" + +[dev-dependencies] +pretty_assertions.workspace = true diff --git a/crates/forge_permission_resolver/src/lib.rs b/crates/forge_permission_resolver/src/lib.rs new file mode 100644 index 00000000..fb793cf6 --- /dev/null +++ b/crates/forge_permission_resolver/src/lib.rs @@ -0,0 +1,2 @@ +pub mod permissions_resolver; +pub mod test; diff --git a/crates/forge_permission_resolver/src/permissions_resolver.rs b/crates/forge_permission_resolver/src/permissions_resolver.rs new file mode 100644 index 00000000..d321c151 --- /dev/null +++ b/crates/forge_permission_resolver/src/permissions_resolver.rs @@ -0,0 +1,224 @@ +use regex::Regex; +use serde::Deserialize; +use serde_json; +use std::{collections::HashMap, hash::Hash, vec}; +use tracing::warn; +use ureq; + +type PermissionHashMap = HashMap<(String, RequestType), Vec>; + +#[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] +struct SwaggerReponse<'a> { + #[serde(default, borrow)] + paths: HashMap<&'a str, Endpoint>, +} + +#[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] +struct Endpoint { + #[serde(default)] + get: Option, + #[serde(default)] + put: Option, + #[serde(default)] + patch: Option, + #[serde(default)] + post: Option, + #[serde(default)] + delete: Option, +} + +#[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] +struct RequestDetails { + #[serde( + rename( + deserialize = "x-atlassian-oauth2-scopes", + deserialize = "x-atlassian-oauth2-scopes" + ), + default + )] + permission: Vec, +} + +#[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] +struct PermissionData { + // TODO: Replace these with the ForgePermissionEnum once it is merged in + #[serde(default)] + scopes: Vec, +} + +#[derive(Hash, PartialEq, Eq, Debug)] +pub enum RequestType { + Get, + Patch, + Post, + Put, + Delete, +} + +#[derive(Copy, Clone)] +pub enum PermissionType { + Classic = 0, + Granular = 1, +} + +pub fn check_url_for_permissions( + permission_map: &PermissionHashMap, + endpoint_regex: &HashMap, + request: RequestType, + url: &str, +) -> Vec { + let mut length_of_regex = Vec::new(); + + // sort by the length of regex + for (string, regex) in endpoint_regex.iter() { + length_of_regex.push((regex.as_str().len(), string)) + } + + length_of_regex.sort_by_key(|k| k.0); + length_of_regex.reverse(); + + for (_, endpoint) in length_of_regex { + let regex = endpoint_regex.get(endpoint).unwrap(); + if regex.is_match(&(url.to_owned() + "-")) { + return permission_map + .get(&(endpoint.clone(), request)) + .unwrap_or(&vec![]) + .clone(); + } + } + return vec![]; +} + +pub fn get_permission_resolver() -> (PermissionHashMap, HashMap) { + let jira_url = "https://developer.atlassian.com/cloud/jira/platform/swagger-v3.v3.json"; + let confluence_url = "https://developer.atlassian.com/cloud/confluence/swagger.v3.json"; + + let mut endpoint_map: PermissionHashMap = HashMap::default(); + let mut endpoint_regex: HashMap = HashMap::default(); + + get_permisions_for(jira_url, &mut endpoint_map, &mut endpoint_regex); + get_permisions_for(confluence_url, &mut endpoint_map, &mut endpoint_regex); + + return (endpoint_map, endpoint_regex); +} + +pub fn get_permisions_for( + url: &str, + endpoint_map_classic: &mut PermissionHashMap, + endpoint_regex: &mut HashMap, +) { + if let Result::Ok(repsonse) = ureq::get(url).call() { + if let Result::Ok(data) = repsonse.into_string() { + let data: SwaggerReponse = serde_json::from_str(&data).unwrap(); + for (key, endpoint_data) in data.paths.iter() { + let endpoint_data = get_request_type(endpoint_data, key); + endpoint_data + .into_iter() + .for_each(|(key, request, permissions)| { + let regex = Regex::new(&find_regex_for_endpoint(&key)).unwrap(); + + endpoint_regex.insert(key.clone(), regex); + endpoint_map_classic.insert((key, request), permissions); + }); + } + } + } else { + warn!("Failed to retreive the permission json"); + } +} + +pub fn find_regex_for_endpoint(key: &str) -> String { + let mut regex_str = String::new(); + let mut prev_index = 0; + for (i, char) in key.chars().enumerate() { + if char == '{' { + regex_str += &key[prev_index..i]; + } else if char == '}' { + regex_str += ".*"; + prev_index = i + 1; + } else if i == key.len() { + regex_str += &key[prev_index..i] + } + } + + if prev_index < key.len() { + regex_str += &key[prev_index..key.len()]; + } + + return String::from(regex_str + "-"); +} + +fn get_request_type( + endpoint_data: &Endpoint, + key: &str, +) -> Vec<(String, RequestType, Vec)> { + let mut all_methods = Vec::new(); + + if let Some(endpoint_data) = &endpoint_data.delete { + all_methods.push(( + key.to_string(), + RequestType::Delete, + endpoint_data + .permission + .clone() + .into_iter() + .map(|data| data.scopes) + .flatten() + .collect(), + )); + } + if let Some(endpoint_data) = &endpoint_data.patch { + all_methods.push(( + key.to_string(), + RequestType::Patch, + endpoint_data + .permission + .clone() + .into_iter() + .map(|data| data.scopes) + .flatten() + .collect(), + )); + } + if let Some(endpoint_data) = &endpoint_data.post { + all_methods.push(( + key.to_string(), + RequestType::Post, + endpoint_data + .permission + .clone() + .into_iter() + .map(|data| data.scopes) + .flatten() + .collect(), + )); + } + if let Some(endpoint_data) = &endpoint_data.put { + all_methods.push(( + key.to_string(), + RequestType::Put, + endpoint_data + .permission + .clone() + .into_iter() + .map(|data| data.scopes) + .flatten() + .collect(), + )); + } + if let Some(endpoint_data) = &endpoint_data.get { + all_methods.push(( + key.to_string(), + RequestType::Get, + endpoint_data + .permission + .clone() + .into_iter() + .map(|data| data.scopes) + .flatten() + .collect(), + )); + } + + return all_methods; +} diff --git a/crates/forge_permission_resolver/src/test.rs b/crates/forge_permission_resolver/src/test.rs new file mode 100644 index 00000000..e87e6056 --- /dev/null +++ b/crates/forge_permission_resolver/src/test.rs @@ -0,0 +1,74 @@ +use crate::permissions_resolver::{ + check_url_for_permissions, find_regex_for_endpoint, get_permission_resolver, RequestType, +}; + +mod tests { + use super::*; + + #[test] + fn test_simple_url_with_end_var() { + let result = find_regex_for_endpoint("/rest/api/3/version/{id}/mergeto/{moveIssuesTo}"); + assert_eq!(result, "/rest/api/3/version/.*/mergeto/.*-"); + } + + #[test] + fn test_simple_url_with_middle_var() { + let result = find_regex_for_endpoint("/rest/api/3/issue/{issueIdOrKey}/comment"); + assert_eq!(result, "/rest/api/3/issue/.*/comment-"); + } + + #[test] + fn test_simple_url_with_no_var() { + let result = find_regex_for_endpoint("/rest/api/3/fieldconfigurationscheme/project"); + assert_eq!(result, "/rest/api/3/fieldconfigurationscheme/project-"); + } + + #[test] + fn test_resolving_permssion_basic() { + let (permission_map, regex_map) = get_permission_resolver(); + let url = "/rest/api/3/issue/27/attachments"; + let request_type = RequestType::Post; + let result = check_url_for_permissions(&permission_map, ®ex_map, request_type, url); + + let expected_permission: Vec = vec![ + String::from("write:jira-work"), + String::from("read:user:jira"), + String::from("write:attachment:jira"), + String::from("read:attachment:jira"), + String::from("read:avatar:jira"), + ]; + + assert_eq!(result, expected_permission); + } + + #[test] + fn test_resolving_permssion_end_var() { + let (permission_map, regex_map) = get_permission_resolver(); + let url = "/wiki/rest/api/relation/1/from/1/2/to/3/4"; + let request_type = RequestType::Get; + let result = check_url_for_permissions(&permission_map, ®ex_map, request_type, url); + + let expected_permission: Vec = vec![ + String::from("read:confluence-content.summary"), + String::from("read:relation:confluence"), + String::from("read:content-details:confluence"), + ]; + + assert_eq!(result, expected_permission); + } + + #[test] + fn test_resolving_permssion_no_var() { + let (permission_map, regex_map) = get_permission_resolver(); + let url = "/rest/api/3/issue/archive"; + let request_type = RequestType::Post; + let result = check_url_for_permissions(&permission_map, ®ex_map, request_type, url); + + let expected_permission: Vec = vec![ + String::from("write:jira-work"), + String::from("write:issue:jira"), + ]; + + assert_eq!(result, expected_permission); + } +} From 425edcad8a2deb8803256b02c35555e6753d6d1f Mon Sep 17 00:00:00 2001 From: James Gersbach Date: Thu, 3 Aug 2023 09:12:40 -0700 Subject: [PATCH 135/517] cleaning up --- .../src/permissions_resolver.rs | 58 +++++-------------- 1 file changed, 15 insertions(+), 43 deletions(-) diff --git a/crates/forge_permission_resolver/src/permissions_resolver.rs b/crates/forge_permission_resolver/src/permissions_resolver.rs index d321c151..dfa8869d 100644 --- a/crates/forge_permission_resolver/src/permissions_resolver.rs +++ b/crates/forge_permission_resolver/src/permissions_resolver.rs @@ -158,67 +158,39 @@ fn get_request_type( all_methods.push(( key.to_string(), RequestType::Delete, - endpoint_data - .permission - .clone() - .into_iter() - .map(|data| data.scopes) - .flatten() - .collect(), + get_scopes(endpoint_data), )); } if let Some(endpoint_data) = &endpoint_data.patch { all_methods.push(( key.to_string(), RequestType::Patch, - endpoint_data - .permission - .clone() - .into_iter() - .map(|data| data.scopes) - .flatten() - .collect(), + get_scopes(endpoint_data), )); } if let Some(endpoint_data) = &endpoint_data.post { all_methods.push(( key.to_string(), RequestType::Post, - endpoint_data - .permission - .clone() - .into_iter() - .map(|data| data.scopes) - .flatten() - .collect(), + get_scopes(endpoint_data), )); } if let Some(endpoint_data) = &endpoint_data.put { - all_methods.push(( - key.to_string(), - RequestType::Put, - endpoint_data - .permission - .clone() - .into_iter() - .map(|data| data.scopes) - .flatten() - .collect(), - )); + all_methods.push((key.to_string(), RequestType::Put, get_scopes(endpoint_data))); } if let Some(endpoint_data) = &endpoint_data.get { - all_methods.push(( - key.to_string(), - RequestType::Get, - endpoint_data - .permission - .clone() - .into_iter() - .map(|data| data.scopes) - .flatten() - .collect(), - )); + all_methods.push((key.to_string(), RequestType::Get, get_scopes(endpoint_data))); } return all_methods; } + +fn get_scopes(endpoint_data: &RequestDetails) -> Vec { + return endpoint_data + .permission + .clone() + .into_iter() + .map(|data| data.scopes) + .flatten() + .collect(); +} From 3307867e6dc6bf0fe2613fca8aa4db6d121b28cc Mon Sep 17 00:00:00 2001 From: James Gersbach Date: Thu, 3 Aug 2023 14:33:17 -0700 Subject: [PATCH 136/517] resloving comments on PR --- crates/forge_analyzer/src/definitions.rs | 58 +++++++++++------------- 1 file changed, 26 insertions(+), 32 deletions(-) diff --git a/crates/forge_analyzer/src/definitions.rs b/crates/forge_analyzer/src/definitions.rs index 956a6ec2..e2a9f573 100644 --- a/crates/forge_analyzer/src/definitions.rs +++ b/crates/forge_analyzer/src/definitions.rs @@ -1672,21 +1672,19 @@ impl Visit for LocalDefiner<'_> { fn visit_var_declarator(&mut self, n: &VarDeclarator) { n.name.visit_with(self); - if let Some(init) = &n.init { - if let Expr::New(new_expr) = &**init { - if let Pat::Ident(ident) = &n.name { - if let Some(defid_variable) = self.res.sym_to_id(ident.to_id(), self.module) { - if let Expr::Ident(ident) = &*new_expr.callee { - if let Some(DefKind::Class(class)) = - self.res.sym_to_def(ident.to_id(), self.module) + if let Some(Expr::New(new_expr)) = n.init.as_deref() { + if let Pat::Ident(ident) = &n.name { + if let Some(defid_variable) = self.res.sym_to_id(ident.to_id(), self.module) { + if let Expr::Ident(ident) = &*new_expr.callee { + if let Some(DefKind::Class(class)) = + self.res.sym_to_def(ident.to_id(), self.module) + { + if let Some(defid_class) = + self.res.sym_to_id(ident.to_id(), self.module) { - if let Some(defid_class) = - self.res.sym_to_id(ident.to_id(), self.module) - { - self.body - .class_instantiations - .insert(defid_variable, defid_class); - } + self.body + .class_instantiations + .insert(defid_variable, defid_class); } } } @@ -1723,24 +1721,20 @@ impl Visit for FunctionCollector<'_> { if let Some(class_def) = self.curr_class { if let DefKind::Class(class) = self.res.clone().def_ref(class_def) { if let Some((_, owner)) = &class.pub_members.iter().find(|(name, defid)| { - if name == "constructor" { - return true; - } else { - false - } + name == "constructor" }) { let mut argdef = ArgDefiner { res: self.res, module: self.module, - func: owner.clone(), - body: Body::with_owner(owner.clone()), + func: *owner, + body: Body::with_owner(*owner), }; n.params.visit_children_with(&mut argdef); let body = argdef.body; let mut localdef = LocalDefiner { res: self.res, module: self.module, - func: owner.clone(), + func: *owner, body, }; n.body.visit_children_with(&mut localdef); @@ -1748,7 +1742,7 @@ impl Visit for FunctionCollector<'_> { let mut analyzer = FunctionAnalyzer { res: self.res, module: self.module, - current_def: owner.clone(), + current_def: *owner, assigning_to: None, body, block: BasicBlockId::default(), @@ -1758,7 +1752,7 @@ impl Visit for FunctionCollector<'_> { if let Some(BlockStmt { stmts, .. }) = &n.body { analyzer.lower_stmts(stmts); let body = analyzer.body; - *self.res.def_mut(owner.clone()).expect_body() = body; + *self.res.def_mut(*owner).expect_body() = body; } } } @@ -1780,15 +1774,15 @@ impl Visit for FunctionCollector<'_> { let mut argdef = ArgDefiner { res: self.res, module: self.module, - func: owner.clone(), - body: Body::with_owner(owner.clone()), + func: *owner, + body: Body::with_owner(*owner), }; n.function.params.visit_children_with(&mut argdef); let body = argdef.body; let mut localdef = LocalDefiner { res: self.res, module: self.module, - func: owner.clone(), + func: *owner, body, }; n.function.body.visit_children_with(&mut localdef); @@ -1796,7 +1790,7 @@ impl Visit for FunctionCollector<'_> { let mut analyzer = FunctionAnalyzer { res: self.res, module: self.module, - current_def: owner.clone(), + current_def: *owner, assigning_to: None, body, block: BasicBlockId::default(), @@ -1806,7 +1800,7 @@ impl Visit for FunctionCollector<'_> { if let Some(BlockStmt { stmts, .. }) = &n.function.body { analyzer.lower_stmts(stmts); let body = analyzer.body; - *self.res.def_mut(owner.clone()).expect_body() = body; + *self.res.def_mut(*owner).expect_body() = body; } } } @@ -2108,7 +2102,7 @@ impl Visit for Lowerer<'_> { self.res .add_anonymous("__CONSTRUCTOR", AnonType::Closure, self.curr_mod); self.res - .add_class_mehtod(&ident.sym, n.key.clone(), class_def, owner); + .add_class_method(&ident.sym, n.key.clone(), class_def, owner); } } } @@ -2123,7 +2117,7 @@ impl Visit for Lowerer<'_> { self.res .add_anonymous("__CLASSMETHOD", AnonType::Closure, self.curr_mod); self.res - .add_class_mehtod(&ident.sym, n.key.clone(), class_def, owner); + .add_class_method(&ident.sym, n.key.clone(), class_def, owner); } } } @@ -2602,7 +2596,7 @@ impl Environment { } } - fn add_class_mehtod( + fn add_class_method( &mut self, name: impl Into, n: PropName, From ce7453aedfbadd1b389aefa7b554b153319c99ad Mon Sep 17 00:00:00 2001 From: James Gersbach Date: Fri, 4 Aug 2023 12:27:39 -0700 Subject: [PATCH 137/517] removing unneeded file --- .vscode/launch.json | 121 -------------------------- crates/forge_analyzer/src/checkers.rs | 1 - crates/forge_analyzer/src/exports.rs | 5 +- crates/forge_analyzer/src/reporter.rs | 1 - crates/forge_analyzer/src/worklist.rs | 2 +- 5 files changed, 3 insertions(+), 127 deletions(-) delete mode 100644 .vscode/launch.json diff --git a/.vscode/launch.json b/.vscode/launch.json deleted file mode 100644 index 667cf6f3..00000000 --- a/.vscode/launch.json +++ /dev/null @@ -1,121 +0,0 @@ -{ - // Use IntelliSense to learn about possible attributes. - // Hover to view descriptions of existing attributes. - // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 - "version": "0.2.0", - "configurations": [ - { - "type": "lldb", - "request": "launch", - "name": "Debug unit tests in library 'forge_analyzer'", - "cargo": { - "args": [ - "test", - "--no-run", - "--lib", - "--package=forge_analyzer" - ], - "filter": { - "name": "forge_analyzer", - "kind": "lib" - } - }, - "args": [], - "cwd": "${workspaceFolder}" - }, - { - "type": "lldb", - "request": "launch", - "name": "Debug unit tests in library 'forge_file_resolver'", - "cargo": { - "args": [ - "test", - "--no-run", - "--lib", - "--package=forge_file_resolver" - ], - "filter": { - "name": "forge_file_resolver", - "kind": "lib" - } - }, - "args": [], - "cwd": "${workspaceFolder}" - }, - { - "type": "lldb", - "request": "launch", - "name": "Debug unit tests in library 'forge_loader'", - "cargo": { - "args": [ - "test", - "--no-run", - "--lib", - "--package=forge_loader" - ], - "filter": { - "name": "forge_loader", - "kind": "lib" - } - }, - "args": [], - "cwd": "${workspaceFolder}" - }, - { - "type": "lldb", - "request": "launch", - "name": "Debug unit tests in library 'forge_utils'", - "cargo": { - "args": [ - "test", - "--no-run", - "--lib", - "--package=forge_utils" - ], - "filter": { - "name": "forge_utils", - "kind": "lib" - } - }, - "args": [], - "cwd": "${workspaceFolder}" - }, - { - "type": "lldb", - "request": "launch", - "name": "Debug executable 'fsrt'", - "cargo": { - "args": [ - "build", - "--bin=fsrt", - "--package=fsrt", - ], - "filter": { - "name": "fsrt", - "kind": "bin" - } - }, - "args": ["test-apps/newapps2/com.appsfoundry.jira.role_revealer",], - "cwd": "${workspaceFolder}" - }, - { - "type": "lldb", - "request": "launch", - "name": "Debug unit tests in executable 'fsrt'", - "cargo": { - "args": [ - "test", - "--no-run", - "--bin=fsrt", - "--package=fsrt" - ], - "filter": { - "name": "fsrt", - "kind": "bin" - } - }, - "args": [], - "cwd": "${workspaceFolder}" - } - ] -} \ No newline at end of file diff --git a/crates/forge_analyzer/src/checkers.rs b/crates/forge_analyzer/src/checkers.rs index 7ae5246c..2b107f86 100644 --- a/crates/forge_analyzer/src/checkers.rs +++ b/crates/forge_analyzer/src/checkers.rs @@ -799,7 +799,6 @@ impl<'cx> Dataflow<'cx> for PermissionDataflow { inst: &'cx Inst, initial_state: Self::State, ) -> Self::State { - println!("inst {inst}"); match inst { Inst::Expr(rvalue) => { diff --git a/crates/forge_analyzer/src/exports.rs b/crates/forge_analyzer/src/exports.rs index be225b3f..da36c186 100644 --- a/crates/forge_analyzer/src/exports.rs +++ b/crates/forge_analyzer/src/exports.rs @@ -2,9 +2,8 @@ use crate::Exports; use forge_utils::FxHashMap; use swc_core::ecma::{ ast::{ - AssignExpr, BindingIdent, Decl, ExportAll, ExportDecl, ExportDefaultDecl, - ExportDefaultExpr, Expr, FnDecl, MemberProp, ModuleDecl, ModuleExportName, ModuleItem, Pat, - PatOrExpr, VarDecl, VarDeclarator, + BindingIdent, Decl, ExportAll, ExportDecl, ExportDefaultDecl, ExportDefaultExpr, FnDecl, + ModuleDecl, ModuleItem, Pat, VarDecl, VarDeclarator, }, visit::{noop_visit_type, Visit, VisitWith}, }; diff --git a/crates/forge_analyzer/src/reporter.rs b/crates/forge_analyzer/src/reporter.rs index 1b993d7c..f5cd6441 100644 --- a/crates/forge_analyzer/src/reporter.rs +++ b/crates/forge_analyzer/src/reporter.rs @@ -1,6 +1,5 @@ use forge_loader::forgepermissions::ForgePermissions; use serde::Serialize; -use std::collections::HashSet; use time::{Date, OffsetDateTime}; #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)] diff --git a/crates/forge_analyzer/src/worklist.rs b/crates/forge_analyzer/src/worklist.rs index e7a41f20..78691f67 100644 --- a/crates/forge_analyzer/src/worklist.rs +++ b/crates/forge_analyzer/src/worklist.rs @@ -5,7 +5,7 @@ use tracing::debug; use crate::{ definitions::{DefId, Environment}, - ir::{BasicBlockId, Operand}, + ir::BasicBlockId, }; #[derive(Debug, Clone)] From 59b66762b3ae69922622a2ab8ea8e48eeef6b0dc Mon Sep 17 00:00:00 2001 From: James Gersbach Date: Fri, 4 Aug 2023 14:37:20 -0700 Subject: [PATCH 138/517] wip --- crates/forge_analyzer/src/checkers.rs | 5 ++++- crates/forge_analyzer/src/definitions.rs | 1 + test-apps/jira-damn-vulnerable-forge-app/src/utils.js | 10 +++++++++- 3 files changed, 14 insertions(+), 2 deletions(-) diff --git a/crates/forge_analyzer/src/checkers.rs b/crates/forge_analyzer/src/checkers.rs index bc44a8d7..7c95b07f 100644 --- a/crates/forge_analyzer/src/checkers.rs +++ b/crates/forge_analyzer/src/checkers.rs @@ -647,6 +647,7 @@ impl<'cx> Dataflow<'cx> for PermissionDataflow { .for_each(|first_arg_vec| { if let Some(second_arg_vec) = intrinsic_argument.second_arg.clone() { first_arg_vec.iter().for_each(|first_arg| { + println!("first arg ===> {first_arg:?}"); second_arg_vec.iter().for_each(|second_arg| { let permissions = check_permission_used( intrinsic_func_type, @@ -658,6 +659,7 @@ impl<'cx> Dataflow<'cx> for PermissionDataflow { }) } else { first_arg_vec.iter().for_each(|first_arg| { + println!("first arg ===> {first_arg:?}"); let permissions = check_permission_used(intrinsic_func_type, first_arg, None); permissions_within_call.extend_from_slice(&permissions); @@ -665,6 +667,8 @@ impl<'cx> Dataflow<'cx> for PermissionDataflow { } }); + println!("permissions within call {permissions_within_call:?}"); + _interp .permissions .extend_from_slice(&permissions_within_call); @@ -792,7 +796,6 @@ impl<'cx> Dataflow<'cx> for PermissionDataflow { }, } } - self.needs_call .push((callee_def, operands.into_vec(), all_values_to_be_pushed)); initial_state diff --git a/crates/forge_analyzer/src/definitions.rs b/crates/forge_analyzer/src/definitions.rs index 20d3a2c5..de06c8af 100644 --- a/crates/forge_analyzer/src/definitions.rs +++ b/crates/forge_analyzer/src/definitions.rs @@ -2865,6 +2865,7 @@ impl Environment { ) { if let DefKind::Class(class) = self.def_mut(class_def) { if let PropName::Ident(ident) = &n { + println!("adding class method --"); class.pub_members.push((ident.sym.to_owned(), owner)); } } diff --git a/test-apps/jira-damn-vulnerable-forge-app/src/utils.js b/test-apps/jira-damn-vulnerable-forge-app/src/utils.js index 674263d5..caf62bac 100755 --- a/test-apps/jira-damn-vulnerable-forge-app/src/utils.js +++ b/test-apps/jira-damn-vulnerable-forge-app/src/utils.js @@ -1,5 +1,6 @@ import api, { route } from '@forge/api'; import {testFunctionFromTestFile} from './testfile'; +import UselessClass from './uselessClass'; export async function fetchIssueSummary(issueIdOrKey, url) { @@ -11,6 +12,8 @@ export async function fetchIssueSummary(issueIdOrKey, url) { }, }; + testFunctionFromTestFile(); + let val = "grapefruit"; val = "peach"; @@ -36,7 +39,12 @@ export async function writeComment(issueIdOrKey, comment) { /* const api = require('@forge/api'); */ - testFunctionFromTestFile("test_value_param") + // ERROR, even if this is not assigned anything then it will assign the param to the issueIdOrKey var + let issueIdOrKey = testFunctionFromTestFile("test_value_param") + + let my_class = new UselessClass(); + + my_class.test_function(); const resp = await api .asApp() From 361c4da03d38e6c39c798a6576736dcf42c3b6d0 Mon Sep 17 00:00:00 2001 From: James Gersbach Date: Fri, 4 Aug 2023 15:30:54 -0700 Subject: [PATCH 139/517] fixed to pr --- Cargo.lock | 416 +----------------- Cargo.toml | 2 +- crates/forge_analyzer/src/engine.rs | 1 + crates/forge_analyzer/src/interp.rs | 2 + crates/forge_permission_resolver/Cargo.toml | 5 +- .../src/permissions_resolver.rs | 58 ++- 6 files changed, 56 insertions(+), 428 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index c272f41b..8f05dca8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -83,17 +83,6 @@ dependencies = [ "syn 1.0.107", ] -[[package]] -name = "async-channel" -version = "1.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81953c529336010edd6d8e358f886d9581267795c61b19475b71314bffa46d35" -dependencies = [ - "concurrent-queue", - "event-listener", - "futures-core", -] - [[package]] name = "atty" version = "0.2.14" @@ -204,18 +193,6 @@ version = "3.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "572f695136211188308f16ad2ca5c851a712c464060ae6974944458eb83880ba" -[[package]] -name = "bytes" -version = "1.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89b2fd2a0dcf38d7971e2194b6b6eebab45ae01067456a7fd93d5547a61b70be" - -[[package]] -name = "castaway" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2698f953def977c68f935bb0dfa959375ad4638570e969e2f1e9f433cbf1af6" - [[package]] name = "cc" version = "1.0.78" @@ -291,15 +268,6 @@ dependencies = [ "unicode-width", ] -[[package]] -name = "concurrent-queue" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62ec6771ecfa0762d24683ee5a32ad78487a3d3afdc0fb8cae19d2c5deb50b7c" -dependencies = [ - "crossbeam-utils", -] - [[package]] name = "core-foundation-sys" version = "0.8.3" @@ -387,37 +355,6 @@ dependencies = [ "syn 1.0.107", ] -[[package]] -name = "curl" -version = "0.4.44" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "509bd11746c7ac09ebd19f0b17782eae80aadee26237658a6b4808afb5c11a22" -dependencies = [ - "curl-sys", - "libc", - "openssl-probe", - "openssl-sys", - "schannel", - "socket2", - "winapi", -] - -[[package]] -name = "curl-sys" -version = "0.4.65+curl-8.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "961ba061c9ef2fe34bbd12b807152d96f0badd2bebe7b90ce6c8c8b7572a0986" -dependencies = [ - "cc", - "libc", - "libnghttp2-sys", - "libz-sys", - "openssl-sys", - "pkg-config", - "vcpkg", - "winapi", -] - [[package]] name = "cxx" version = "1.0.86" @@ -594,21 +531,6 @@ dependencies = [ "libc", ] -[[package]] -name = "event-listener" -version = "2.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0" - -[[package]] -name = "fastrand" -version = "1.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e51093e27b0797c359783294ca4f0a911c270184cb10f85783b118614a1501be" -dependencies = [ - "instant", -] - [[package]] name = "fixedbitset" version = "0.4.2" @@ -677,7 +599,6 @@ dependencies = [ name = "forge_permission_resolver" version = "0.1.0" dependencies = [ - "isahc", "pretty_assertions", "regex", "serde", @@ -735,33 +656,6 @@ dependencies = [ "walkdir", ] -[[package]] -name = "futures-core" -version = "0.3.28" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4bca583b7e26f571124fe5b7561d49cb2868d79116cfa0eefce955557c6fee8c" - -[[package]] -name = "futures-io" -version = "0.3.28" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fff74096e71ed47f8e023204cfd0aa1289cd54ae5430a9523be060cdb849964" - -[[package]] -name = "futures-lite" -version = "1.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49a9d51ce47660b1e808d3c990b4709f2f415d928835a17dfd16991515c46bce" -dependencies = [ - "fastrand", - "futures-core", - "futures-io", - "memchr", - "parking", - "pin-project-lite", - "waker-fn", -] - [[package]] name = "generic-array" version = "0.14.6" @@ -834,17 +728,6 @@ dependencies = [ "libc", ] -[[package]] -name = "http" -version = "0.2.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd6effc99afb63425aff9b05836f029929e345a6148a14b7ecd5ab67af944482" -dependencies = [ - "bytes", - "fnv", - "itoa", -] - [[package]] name = "iana-time-zone" version = "0.1.53" @@ -902,15 +785,6 @@ dependencies = [ "serde", ] -[[package]] -name = "instant" -version = "0.1.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" -dependencies = [ - "cfg-if", -] - [[package]] name = "io-lifetimes" version = "1.0.4" @@ -918,7 +792,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e7d6c6f8c91b4b9ed43484ad1a938e393caf35960fce7f82a040497207bd8e9e" dependencies = [ "libc", - "windows-sys 0.42.0", + "windows-sys", ] [[package]] @@ -943,7 +817,7 @@ dependencies = [ "hermit-abi 0.2.6", "io-lifetimes", "rustix", - "windows-sys 0.42.0", + "windows-sys", ] [[package]] @@ -952,33 +826,6 @@ version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "616cde7c720bb2bb5824a224687d8f77bfd38922027f01d825cd7453be5099fb" -[[package]] -name = "isahc" -version = "1.7.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "334e04b4d781f436dc315cb1e7515bd96826426345d498149e4bde36b67f8ee9" -dependencies = [ - "async-channel", - "castaway", - "crossbeam-utils", - "curl", - "curl-sys", - "encoding_rs", - "event-listener", - "futures-lite", - "http", - "log", - "mime", - "once_cell", - "polling", - "slab", - "sluice", - "tracing", - "tracing-futures", - "url", - "waker-fn", -] - [[package]] name = "itertools" version = "0.10.5" @@ -1097,28 +944,6 @@ version = "0.2.147" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b4668fb0ea861c1df094127ac5f1da3409a82116a4ba74fca2e58ef927159bb3" -[[package]] -name = "libnghttp2-sys" -version = "0.1.7+1.45.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57ed28aba195b38d5ff02b9170cbff627e336a20925e43b4945390401c5dc93f" -dependencies = [ - "cc", - "libc", -] - -[[package]] -name = "libz-sys" -version = "1.1.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d97137b25e321a73eef1418d1d5d2eda4d77e12813f8e6dead84bc52c5870a7b" -dependencies = [ - "cc", - "libc", - "pkg-config", - "vcpkg", -] - [[package]] name = "link-cplusplus" version = "1.0.8" @@ -1248,12 +1073,6 @@ dependencies = [ "syn 1.0.107", ] -[[package]] -name = "mime" -version = "0.3.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" - [[package]] name = "minimal-lexical" version = "0.2.1" @@ -1378,24 +1197,6 @@ version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6f61fba1741ea2b3d6a1e3178721804bb716a68a6aeba1149b5d52e3d464ea66" -[[package]] -name = "openssl-probe" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" - -[[package]] -name = "openssl-sys" -version = "0.9.90" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "374533b0e45f3a7ced10fcaeccca020e66656bc03dac384f852e4e5a7a8104a6" -dependencies = [ - "cc", - "libc", - "pkg-config", - "vcpkg", -] - [[package]] name = "os_str_bytes" version = "6.4.1" @@ -1423,12 +1224,6 @@ version = "3.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c1b04fb49957986fdce4d6ee7a65027d55d4b6d2265e5848bbb507b58ccfdb6f" -[[package]] -name = "parking" -version = "2.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14f2252c834a40ed9bb5422029649578e63aa341ac401f74e719dd1afda8394e" - [[package]] name = "parking_lot" version = "0.12.1" @@ -1449,7 +1244,7 @@ dependencies = [ "libc", "redox_syscall", "smallvec", - "windows-sys 0.42.0", + "windows-sys", ] [[package]] @@ -1524,38 +1319,12 @@ dependencies = [ "siphasher", ] -[[package]] -name = "pin-project" -version = "1.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "030ad2bc4db10a8944cb0d837f158bdfec4d4a4873ab701a95046770d11f8842" -dependencies = [ - "pin-project-internal", -] - -[[package]] -name = "pin-project-internal" -version = "1.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec2e072ecce94ec471b13398d5402c188e76ac03cf74dd1a975161b23a3f6d9c" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.28", -] - [[package]] name = "pin-project-lite" version = "0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116" -[[package]] -name = "pkg-config" -version = "0.3.27" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26072860ba924cbfa98ea39c8c19b4dd6a4a25423dbdf219c1eca91aa0cf6964" - [[package]] name = "pmutil" version = "0.5.3" @@ -1567,22 +1336,6 @@ dependencies = [ "syn 1.0.107", ] -[[package]] -name = "polling" -version = "2.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b2d323e8ca7996b3e23126511a523f7e62924d93ecd5ae73b333815b0eb3dce" -dependencies = [ - "autocfg", - "bitflags", - "cfg-if", - "concurrent-queue", - "libc", - "log", - "pin-project-lite", - "windows-sys 0.48.0", -] - [[package]] name = "ppv-lite86" version = "0.2.17" @@ -1819,7 +1572,7 @@ dependencies = [ "io-lifetimes", "libc", "linux-raw-sys", - "windows-sys 0.42.0", + "windows-sys", ] [[package]] @@ -1875,15 +1628,6 @@ dependencies = [ "winapi-util", ] -[[package]] -name = "schannel" -version = "0.1.22" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c3733bf4cf7ea0880754e19cb5a462007c4a8c1914bff372ccc95b464f1df88" -dependencies = [ - "windows-sys 0.48.0", -] - [[package]] name = "scoped-tls" version = "1.0.1" @@ -1938,9 +1682,9 @@ checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" [[package]] name = "serde" -version = "1.0.152" +version = "1.0.181" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb7d1f0d3021d347a83e556fc4683dea2ea09d87bccdf88ff5c12545d89d5efb" +checksum = "6d3e73c93c3240c0bda063c239298e633114c69a888c3e37ca8bb33f343e9890" dependencies = [ "serde_derive", ] @@ -1958,20 +1702,20 @@ dependencies = [ [[package]] name = "serde_derive" -version = "1.0.152" +version = "1.0.181" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af487d118eecd09402d70a5d72551860e788df87b464af30e5ea6a38c75c541e" +checksum = "be02f6cb0cd3a5ec20bbcfbcbd749f57daddb1a0882dc2e46a6c236c90b977ed" dependencies = [ "proc-macro2", "quote", - "syn 1.0.107", + "syn 2.0.28", ] [[package]] name = "serde_json" -version = "1.0.91" +version = "1.0.104" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "877c235533714907a8c2464236f5c4b2a17262ef1bd71f38f35ea592c8da6883" +checksum = "076066c5f1078eac5b722a31827a8832fe108bed65dfa75e233c89f8206e976c" dependencies = [ "itoa", "ryu", @@ -2017,26 +1761,6 @@ version = "0.3.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7bd3e3206899af3f8b12af284fafc038cc1dc2b41d1b89dd17297221c5d225de" -[[package]] -name = "slab" -version = "0.4.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6528351c9bc8ab22353f9d776db39a20288e8d6c37ef8cfe3317cf875eecfc2d" -dependencies = [ - "autocfg", -] - -[[package]] -name = "sluice" -version = "0.5.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d7400c0eff44aa2fcb5e31a5f24ba9716ed90138769e4977a2ba6014ae63eb5" -dependencies = [ - "async-channel", - "futures-core", - "futures-io", -] - [[package]] name = "smallvec" version = "1.10.0" @@ -2049,16 +1773,6 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f67ad224767faa3c7d8b6d91985b78e70a1324408abcb1cfcc2be4c06bc06043" -[[package]] -name = "socket2" -version = "0.4.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64a4a911eed85daf18834cfaa86a79b7d266ff93ff5ba14005426219480ed662" -dependencies = [ - "libc", - "winapi", -] - [[package]] name = "sourcemap" version = "6.2.1" @@ -2933,7 +2647,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cb20089a8ba2b69debd491f8d2d023761cbf196e999218c591fa1e7e15a21907" dependencies = [ "rustix", - "windows-sys 0.42.0", + "windows-sys", ] [[package]] @@ -3038,7 +2752,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ce8c33a8d48bd45d624a6e523445fd21ec13d3653cd51f681abf67418f54eb8" dependencies = [ "cfg-if", - "log", "pin-project-lite", "tracing-attributes", "tracing-core", @@ -3065,16 +2778,6 @@ dependencies = [ "valuable", ] -[[package]] -name = "tracing-futures" -version = "0.2.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97d095ae15e245a057c8e8451bab9b3ee1e1f68e9ba2b4fbc18d0ac5237835f2" -dependencies = [ - "pin-project", - "tracing", -] - [[package]] name = "tracing-log" version = "0.1.3" @@ -3207,11 +2910,14 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b11c96ac7ee530603dcdf68ed1557050f374ce55a5a07193ebf8cbc9f8927e9" dependencies = [ "base64 0.21.2", + "encoding_rs", "flate2", "log", "once_cell", "rustls", "rustls-webpki 0.100.1", + "serde", + "serde_json", "url", "webpki-roots", ] @@ -3233,12 +2939,6 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" -[[package]] -name = "vcpkg" -version = "0.2.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" - [[package]] name = "vergen" version = "7.5.0" @@ -3260,12 +2960,6 @@ version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" -[[package]] -name = "waker-fn" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d5b2c62b4012a3e1eca5a7e077d13b3bf498c4073e33ccd58626607748ceeca" - [[package]] name = "walkdir" version = "2.3.2" @@ -3399,37 +3093,13 @@ version = "0.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7" dependencies = [ - "windows_aarch64_gnullvm 0.42.0", - "windows_aarch64_msvc 0.42.0", - "windows_i686_gnu 0.42.0", - "windows_i686_msvc 0.42.0", - "windows_x86_64_gnu 0.42.0", - "windows_x86_64_gnullvm 0.42.0", - "windows_x86_64_msvc 0.42.0", -] - -[[package]] -name = "windows-sys" -version = "0.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" -dependencies = [ - "windows-targets", -] - -[[package]] -name = "windows-targets" -version = "0.48.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05d4b17490f70499f20b9e791dcf6a299785ce8af4d709018206dc5b4953e95f" -dependencies = [ - "windows_aarch64_gnullvm 0.48.0", - "windows_aarch64_msvc 0.48.0", - "windows_i686_gnu 0.48.0", - "windows_i686_msvc 0.48.0", - "windows_x86_64_gnu 0.48.0", - "windows_x86_64_gnullvm 0.48.0", - "windows_x86_64_msvc 0.48.0", + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", ] [[package]] @@ -3438,84 +3108,42 @@ version = "0.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "41d2aa71f6f0cbe00ae5167d90ef3cfe66527d6f613ca78ac8024c3ccab9a19e" -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91ae572e1b79dba883e0d315474df7305d12f569b400fcf90581b06062f7e1bc" - [[package]] name = "windows_aarch64_msvc" version = "0.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dd0f252f5a35cac83d6311b2e795981f5ee6e67eb1f9a7f64eb4500fbc4dcdb4" -[[package]] -name = "windows_aarch64_msvc" -version = "0.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2ef27e0d7bdfcfc7b868b317c1d32c641a6fe4629c171b8928c7b08d98d7cf3" - [[package]] name = "windows_i686_gnu" version = "0.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fbeae19f6716841636c28d695375df17562ca208b2b7d0dc47635a50ae6c5de7" -[[package]] -name = "windows_i686_gnu" -version = "0.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "622a1962a7db830d6fd0a69683c80a18fda201879f0f447f065a3b7467daa241" - [[package]] name = "windows_i686_msvc" version = "0.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "84c12f65daa39dd2babe6e442988fc329d6243fdce47d7d2d155b8d874862246" -[[package]] -name = "windows_i686_msvc" -version = "0.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4542c6e364ce21bf45d69fdd2a8e455fa38d316158cfd43b3ac1c5b1b19f8e00" - [[package]] name = "windows_x86_64_gnu" version = "0.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bf7b1b21b5362cbc318f686150e5bcea75ecedc74dd157d874d754a2ca44b0ed" -[[package]] -name = "windows_x86_64_gnu" -version = "0.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca2b8a661f7628cbd23440e50b05d705db3686f894fc9580820623656af974b1" - [[package]] name = "windows_x86_64_gnullvm" version = "0.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09d525d2ba30eeb3297665bd434a54297e4170c7f1a44cad4ef58095b4cd2028" -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7896dbc1f41e08872e9d5e8f8baa8fdd2677f29468c4e156210174edc7f7b953" - [[package]] name = "windows_x86_64_msvc" version = "0.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f40009d85759725a34da6d89a94e63d7bdc50a862acf0dbc7c8e488f1edcb6f5" -[[package]] -name = "windows_x86_64_msvc" -version = "0.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a" - [[package]] name = "yansi" version = "0.5.1" diff --git a/Cargo.toml b/Cargo.toml index e55c6a6b..2d22a89e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,7 +14,7 @@ itertools = "0.10.5" miette = { version = "5.5.0", features = ["fancy"] } num-bigint = { version = "0.4.3" } serde = { version = "1.0.152", features = ["derive"] } -serde_json = "1.0.91" +serde_json = "1.0.97" serde_yaml = "0.9.17" petgraph = "0.6.2" pretty_assertions = "1.3.0" diff --git a/crates/forge_analyzer/src/engine.rs b/crates/forge_analyzer/src/engine.rs index 319a3d4f..43204fba 100644 --- a/crates/forge_analyzer/src/engine.rs +++ b/crates/forge_analyzer/src/engine.rs @@ -117,6 +117,7 @@ impl<'ctx> Machine<'ctx> { Inst::Step(next_block, next_stmt) => { debug!(?next_block, ?next_stmt, "stepping into"); self.eip = (next_block, next_stmt); + // we need to return if possible if let Some(val) = self.transfer() { if let Some(ret) = self.callstack.pop() { debug!(?ret, "returning"); diff --git a/crates/forge_analyzer/src/interp.rs b/crates/forge_analyzer/src/interp.rs index 3b95604c..57988685 100644 --- a/crates/forge_analyzer/src/interp.rs +++ b/crates/forge_analyzer/src/interp.rs @@ -312,6 +312,8 @@ pub(crate) struct EntryPoint { pub struct Interp<'cx, C: Checker<'cx>> { env: &'cx Environment, + // We can probably get rid of these RefCells by refactoring the Interp and Checker into + // two fields in another struct. call_graph: CallGraph, entry: EntryPoint, func_state: RefCell>, diff --git a/crates/forge_permission_resolver/Cargo.toml b/crates/forge_permission_resolver/Cargo.toml index ce30eaf2..e3a75e00 100644 --- a/crates/forge_permission_resolver/Cargo.toml +++ b/crates/forge_permission_resolver/Cargo.toml @@ -8,12 +8,13 @@ license.workspace = true # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -isahc = "1.7.2" serde.workspace = true serde_json.workspace = true tracing.workspace = true regex.workspace = true -ureq = "2.7.1" +ureq = { version = "2.7.1", features = ["json", "charset"] } + + [dev-dependencies] pretty_assertions.workspace = true diff --git a/crates/forge_permission_resolver/src/permissions_resolver.rs b/crates/forge_permission_resolver/src/permissions_resolver.rs index dfa8869d..874dd64b 100644 --- a/crates/forge_permission_resolver/src/permissions_resolver.rs +++ b/crates/forge_permission_resolver/src/permissions_resolver.rs @@ -1,16 +1,16 @@ use regex::Regex; use serde::Deserialize; use serde_json; -use std::{collections::HashMap, hash::Hash, vec}; +use std::{cmp::Reverse, collections::HashMap, hash::Hash, vec}; use tracing::warn; use ureq; type PermissionHashMap = HashMap<(String, RequestType), Vec>; #[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] -struct SwaggerReponse<'a> { - #[serde(default, borrow)] - paths: HashMap<&'a str, Endpoint>, +struct SwaggerReponse { + #[serde(default)] + paths: HashMap, } #[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] @@ -67,26 +67,23 @@ pub fn check_url_for_permissions( request: RequestType, url: &str, ) -> Vec { - let mut length_of_regex = Vec::new(); - // sort by the length of regex - for (string, regex) in endpoint_regex.iter() { - length_of_regex.push((regex.as_str().len(), string)) - } - - length_of_regex.sort_by_key(|k| k.0); - length_of_regex.reverse(); + let mut length_of_regex = endpoint_regex + .iter() + .map(|(string, regex)| (regex.as_str().len(), &*string)) + .collect::>(); + length_of_regex.sort_by_key(|k| Reverse(k.0)); for (_, endpoint) in length_of_regex { let regex = endpoint_regex.get(endpoint).unwrap(); if regex.is_match(&(url.to_owned() + "-")) { return permission_map - .get(&(endpoint.clone(), request)) - .unwrap_or(&vec![]) - .clone(); + .get(&(endpoint.to_owned(), request)) + .cloned() + .unwrap_or_default(); } } - return vec![]; + vec![] } pub fn get_permission_resolver() -> (PermissionHashMap, HashMap) { @@ -107,20 +104,18 @@ pub fn get_permisions_for( endpoint_map_classic: &mut PermissionHashMap, endpoint_regex: &mut HashMap, ) { - if let Result::Ok(repsonse) = ureq::get(url).call() { - if let Result::Ok(data) = repsonse.into_string() { - let data: SwaggerReponse = serde_json::from_str(&data).unwrap(); - for (key, endpoint_data) in data.paths.iter() { - let endpoint_data = get_request_type(endpoint_data, key); - endpoint_data - .into_iter() - .for_each(|(key, request, permissions)| { - let regex = Regex::new(&find_regex_for_endpoint(&key)).unwrap(); - - endpoint_regex.insert(key.clone(), regex); - endpoint_map_classic.insert((key, request), permissions); - }); - } + if let Result::Ok(response) = ureq::get(url).call() { + let data: SwaggerReponse = response.into_json().unwrap(); + for (key, endpoint_data) in &data.paths { + let endpoint_data = get_request_type(endpoint_data, key); + endpoint_data + .into_iter() + .for_each(|(key, request, permissions)| { + let regex = Regex::new(&find_regex_for_endpoint(&key)).unwrap(); + + endpoint_regex.insert(key.clone(), regex); + endpoint_map_classic.insert((key, request), permissions); + }); } } else { warn!("Failed to retreive the permission json"); @@ -145,7 +140,8 @@ pub fn find_regex_for_endpoint(key: &str) -> String { regex_str += &key[prev_index..key.len()]; } - return String::from(regex_str + "-"); + regex_str.push('-'); + regex_str } fn get_request_type( From eb1cce446f8bc8800a827773ed4c23cbf18a9c1b Mon Sep 17 00:00:00 2001 From: James Gersbach Date: Sat, 5 Aug 2023 16:28:55 -0700 Subject: [PATCH 140/517] erros with defintion analysis -- correct intermixing of exprs and quasis --- crates/forge_analyzer/src/checkers.rs | 79 +++++++++++++++---- .../src/GlobalPageApp.jsx | 2 +- 2 files changed, 63 insertions(+), 18 deletions(-) diff --git a/crates/forge_analyzer/src/checkers.rs b/crates/forge_analyzer/src/checkers.rs index 9e81b5af..605ecc93 100644 --- a/crates/forge_analyzer/src/checkers.rs +++ b/crates/forge_analyzer/src/checkers.rs @@ -925,37 +925,82 @@ impl<'cx> Dataflow<'cx> for PermissionDataflow { } } Rvalue::Template(template) => { + + // trying to get the template stirngs in order .... + let quasis_joined = template.quasis.join(""); let (mut original_consts, mut all_potential_values) = - (vec![quasis_joined.clone()], vec![]); + (vec![""], vec![String::from("")]); if template.exprs.len() == 0 { all_potential_values.push(quasis_joined.clone()); } else if template.exprs.len() <= 3 { - for expr in &template.exprs { - let value = self.get_str_from_expr(expr, def); - if value.len() > 0 { - value.iter().for_each(|str| { - let mut new_all_values = vec![]; - if let Some(str) = str { - for values in &original_consts { - new_all_values.push(values.clone() + str); - } - all_potential_values.extend_from_slice(&new_all_values); - } else { - for values in &original_consts { - new_all_values.push(values.clone()); - } - all_potential_values.extend_from_slice(&new_all_values); + + + let mut all_values = vec![String::from("")]; + + for (i, expr) in template.exprs.iter().enumerate() { + + if let Some(quasis) = template.quasis.get(i) { + all_values = all_values.iter().map(|value| value.to_owned() + &quasis.to_string()).collect(); + + } + + // abc abd + + let mut new_values__ = vec![]; + + let values = self.get_str_from_expr(expr, def); + if values.len() > 0 { + for str_value in values { + for value in &all_values { + if let Some(str) = &str_value { + new_values__.push(value.clone() + &str) + } } - }); + } + + all_values = new_values__ } + + // for value in &all_potential_values { + // if let Some(quasis) = template.quasis.get(i) { + // let newstr = value.clone().to_owned() + &quasis.to_string(); + // all_potential_values.push(newstr) + // } + // } + + // let value = self.get_str_from_expr(expr, def); + // if value.len() > 0 { + // value.iter().for_each(|str| { + // let mut new_all_values = vec![]; + // if let Some(str) = str { + // for values in &all_potential_values { + + // println!("values == {values:?} {str:?}"); + + // new_all_values.push(values.clone().to_owned() + str); + // } + // all_potential_values.extend_from_slice(&new_all_values); + // } else { + // for values in &all_potential_values { + // new_all_values.push(values.to_string().clone()); + // } + // all_potential_values.extend_from_slice(&new_all_values); + // } + // }); + // } } + + println!("all potential values {all_potential_values:?} {all_values:?}"); + all_potential_values = all_values; } if all_potential_values.len() > 1 { let consts = all_potential_values .into_iter() .map(|value| Const::Literal(value.clone())) .collect::>(); + + println!("consts == {consts:?}"); let value = Value::Phi(consts); self.add_value(def, *varid, value.clone()); } else if all_potential_values.len() == 1 { diff --git a/test-apps/jira-damn-vulnerable-forge-app/src/GlobalPageApp.jsx b/test-apps/jira-damn-vulnerable-forge-app/src/GlobalPageApp.jsx index 07f7d848..364fb310 100755 --- a/test-apps/jira-damn-vulnerable-forge-app/src/GlobalPageApp.jsx +++ b/test-apps/jira-damn-vulnerable-forge-app/src/GlobalPageApp.jsx @@ -3,7 +3,7 @@ import { fetchIssueSummary } from "./utils"; /* never called can ignore */ const GlobalPageApp = () => { - const issue = await fetchIssueSummary('SEC-1'); + const issue = await fetchIssueSummary('SEC-1', "orangechiken"); writeComment(); From 83119fbbd3abfd87a0633df2568673425bc6d061 Mon Sep 17 00:00:00 2001 From: James Gersbach Date: Sun, 6 Aug 2023 11:51:51 -0700 Subject: [PATCH 141/517] working module exports --- crates/forge_analyzer/src/checkers.rs | 117 ++++---- crates/forge_analyzer/src/definitions.rs | 156 +++++++--- crates/forge_analyzer/src/interp.rs | 20 +- crates/forge_analyzer/src/ir.rs | 1 + crates/forge_analyzer/src/lib.rs | 1 - .../src/permissionclassifier.rs | 280 ------------------ crates/forge_analyzer/src/reporter.rs | 1 - crates/forge_loader/src/manifest.rs | 2 +- .../src/permissions_resolver.rs | 14 +- crates/forge_permission_resolver/src/test.rs | 10 +- crates/fsrt/src/main.rs | 17 +- .../src/utils.js | 21 +- 12 files changed, 242 insertions(+), 398 deletions(-) delete mode 100644 crates/forge_analyzer/src/permissionclassifier.rs diff --git a/crates/forge_analyzer/src/checkers.rs b/crates/forge_analyzer/src/checkers.rs index 605ecc93..2bbe12f4 100644 --- a/crates/forge_analyzer/src/checkers.rs +++ b/crates/forge_analyzer/src/checkers.rs @@ -1,10 +1,21 @@ use core::fmt; use forge_loader::forgepermissions::ForgePermissions; +use forge_permission_resolver::permissions_resolver::{ + check_url_for_permissions, get_permission_resolver_confluence, get_permission_resolver_jira, + PermissionHashMap, RequestType, +}; use forge_utils::FxHashMap; use itertools::Itertools; +use regex::Regex; use serde::de::value; use smallvec::SmallVec; -use std::{cmp::max, collections::HashSet, iter, mem, ops::ControlFlow, path::PathBuf}; +use std::{ + cmp::max, + collections::{HashMap, HashSet}, + iter, mem, + ops::ControlFlow, + path::PathBuf, +}; use swc_core::ecma::transforms::base::perf::Check; use tracing::{debug, info, warn}; @@ -18,7 +29,6 @@ use crate::{ Base, BasicBlock, BasicBlockId, BinOp, Inst, Intrinsic, Literal, Location, Operand, Rvalue, Successors, VarId, VarKind, Variable, }, - permissionclassifier::check_permission_used, reporter::{IntoVuln, Reporter, Severity, Vulnerability}, worklist::WorkList, }; @@ -480,6 +490,7 @@ impl WithCallStack for AuthNVuln { pub struct PermissionDataflow { needs_call: Vec<(DefId, Vec, Vec)>, varid_to_value: FxHashMap, + } impl WithCallStack for PermissionVuln { @@ -638,7 +649,10 @@ impl<'cx> Dataflow<'cx> for PermissionDataflow { if let Some(operand) = second { self.handle_second_arg(_interp, operand, _def, &mut intrinsic_argument); } - let mut permissions_within_call: Vec = vec![]; + + // CLEAN UP + + let mut permissions_within_call: Vec = vec![]; let intrinsic_func_type = intrinsic_argument.name.unwrap(); intrinsic_argument .first_arg @@ -648,20 +662,29 @@ impl<'cx> Dataflow<'cx> for PermissionDataflow { first_arg_vec.iter().for_each(|first_arg| { println!("first arg ===> {first_arg:?}"); second_arg_vec.iter().for_each(|second_arg| { - let permissions = check_permission_used( - intrinsic_func_type, - first_arg, - Some(second_arg), - ); - permissions_within_call.extend_from_slice(&permissions); + if intrinsic_func_type == IntrinsicName::RequestConfluence { + let permissions = check_url_for_permissions(&_interp.confluence_permission_resolver, &_interp.confluence_regex_map, trnaslate_request_type(Some(second_arg)), &first_arg); + permissions_within_call.extend_from_slice(&permissions) + // change this to seoiimthinf abdonaofoadinadsjoklj + } else if intrinsic_func_type == IntrinsicName::RequestJira { + let permissions = check_url_for_permissions(&_interp.jira_permission_resolver, &_interp.jira_regex_map, trnaslate_request_type(Some(second_arg)), &first_arg); + permissions_within_call.extend_from_slice(&permissions) + } }) }) } else { first_arg_vec.iter().for_each(|first_arg| { println!("first arg ===> {first_arg:?}"); - let permissions = - check_permission_used(intrinsic_func_type, first_arg, None); - permissions_within_call.extend_from_slice(&permissions); + if intrinsic_func_type == IntrinsicName::RequestConfluence { + let permissions = check_url_for_permissions(&_interp.confluence_permission_resolver, &_interp.confluence_regex_map, RequestType::Get, &first_arg); + permissions_within_call.extend_from_slice(&permissions) + // change this to seoiimthinf abdonaofoadinadsjoklj + } else if intrinsic_func_type == IntrinsicName::RequestJira { + let permissions = check_url_for_permissions(&_interp.jira_permission_resolver, &_interp.jira_regex_map, RequestType::Get, &first_arg); + permissions_within_call.extend_from_slice(&permissions) + } + + //HERE }) } }); @@ -671,6 +694,8 @@ impl<'cx> Dataflow<'cx> for PermissionDataflow { _interp .permissions .extend_from_slice(&permissions_within_call); + + println!("_interp permisisions {:?}", _interp.permissions); } initial_state } @@ -809,7 +834,7 @@ impl<'cx> Dataflow<'cx> for PermissionDataflow { inst: &'cx Inst, initial_state: Self::State, ) -> Self::State { - println!("inst {inst}"); + println!("\tinst {inst}"); match inst { Inst::Expr(rvalue) => { self.transfer_rvalue(interp, def, loc, block, rvalue, initial_state) @@ -925,7 +950,6 @@ impl<'cx> Dataflow<'cx> for PermissionDataflow { } } Rvalue::Template(template) => { - // trying to get the template stirngs in order .... let quasis_joined = template.quasis.join(""); @@ -934,15 +958,14 @@ impl<'cx> Dataflow<'cx> for PermissionDataflow { if template.exprs.len() == 0 { all_potential_values.push(quasis_joined.clone()); } else if template.exprs.len() <= 3 { - - let mut all_values = vec![String::from("")]; for (i, expr) in template.exprs.iter().enumerate() { - if let Some(quasis) = template.quasis.get(i) { - all_values = all_values.iter().map(|value| value.to_owned() + &quasis.to_string()).collect(); - + all_values = all_values + .iter() + .map(|value| value.to_owned() + &quasis.to_string()) + .collect(); } // abc abd @@ -955,40 +978,12 @@ impl<'cx> Dataflow<'cx> for PermissionDataflow { for value in &all_values { if let Some(str) = &str_value { new_values__.push(value.clone() + &str) - } + } } } all_values = new_values__ } - - // for value in &all_potential_values { - // if let Some(quasis) = template.quasis.get(i) { - // let newstr = value.clone().to_owned() + &quasis.to_string(); - // all_potential_values.push(newstr) - // } - // } - - // let value = self.get_str_from_expr(expr, def); - // if value.len() > 0 { - // value.iter().for_each(|str| { - // let mut new_all_values = vec![]; - // if let Some(str) = str { - // for values in &all_potential_values { - - // println!("values == {values:?} {str:?}"); - - // new_all_values.push(values.clone().to_owned() + str); - // } - // all_potential_values.extend_from_slice(&new_all_values); - // } else { - // for values in &all_potential_values { - // new_all_values.push(values.to_string().clone()); - // } - // all_potential_values.extend_from_slice(&new_all_values); - // } - // }); - // } } println!("all potential values {all_potential_values:?} {all_values:?}"); @@ -1228,15 +1223,31 @@ fn return_value_from_string(values: Vec) -> Value { } } +fn trnaslate_request_type(request_type: Option<&str>) -> RequestType { + if let Some(request_type) = request_type { + match request_type { + "PATCH" => RequestType::Patch, + "PUT" => RequestType::Put, + "DELETE" => RequestType::Delete, + "POST" => RequestType::Post, + _ => RequestType::Get, + } + } else { + return RequestType::Get; + } + +} + pub struct PermissionChecker { pub visit: bool, pub vulns: Vec, - pub declared_permissions: HashSet, - pub used_permissions: HashSet, + pub declared_permissions: HashSet, + pub used_permissions: HashSet, } impl PermissionChecker { - pub fn new(declared_permissions: HashSet) -> Self { + pub fn new(declared_permissions: HashSet) -> Self { + Self { visit: false, vulns: vec![], @@ -1314,11 +1325,11 @@ impl<'cx> Checker<'cx> for PermissionChecker { #[derive(Debug)] pub struct PermissionVuln { - unused_permissions: HashSet, + unused_permissions: HashSet, } impl PermissionVuln { - pub fn new(unused_permissions: HashSet) -> PermissionVuln { + pub fn new(unused_permissions: HashSet) -> PermissionVuln { PermissionVuln { unused_permissions } } } diff --git a/crates/forge_analyzer/src/definitions.rs b/crates/forge_analyzer/src/definitions.rs index de06c8af..ae740f60 100644 --- a/crates/forge_analyzer/src/definitions.rs +++ b/crates/forge_analyzer/src/definitions.rs @@ -123,6 +123,8 @@ struct SymbolExport { pub struct ResolverTable { defs: TiVec, pub names: TiVec, + recent_names: FxHashMap<(JsWord, ModId), DefId>, + exported_names: FxHashMap<(JsWord, ModId), DefId>, symbol_to_id: FxHashMap, parent: FxHashMap, owning_module: TiVec, @@ -204,6 +206,7 @@ pub fn run_resolver( let mut collector = FunctionCollector { res: &mut environment, curr_class: None, + curr_function: None, module: curr_mod, parent: None, }; @@ -670,6 +673,23 @@ impl ResolverTable { self.symbol_to_id.get(&Symbol { module, id }).copied() } + #[inline] + fn recent_sym(&self, sym: JsWord, module: ModId) -> Option { + self.recent_names.get(&(sym, module )).copied() + } + + + // this won't work becasue there may be names that are the same acrosss modules ... + #[inline] + fn get_default(&self, module: ModId) -> Option { + for (defid, sym) in self.names.iter_enumerated() { + if &sym.to_string() == "default" && self.owning_module.contains(&module) { + return Some(defid); + } + } + None + } + #[inline] fn sym_kind(&self, id: Id, module: ModId) -> Option { let def = self.sym_id(id, module)?; @@ -683,8 +703,10 @@ impl ResolverTable { #[inline] fn get_or_insert_sym(&mut self, id: Id, module: ModId) -> DefId { - self.sym_id(id.clone(), module) - .unwrap_or_else(|| self.reserve_symbol(id, module)) + let defid = self.sym_id(id.clone(), module) + .unwrap_or_else(|| self.reserve_symbol(id.clone(), module)); + self.recent_names.insert((id.0, module), defid); + defid } #[inline] @@ -722,6 +744,7 @@ impl ResolverTable { debug_assert_eq!(defid, defid2, "inconsistent state while inserting {}", id.0); let sym = id.0.clone(); self.symbol_to_id.insert(Symbol { id, module }, defid2); + self.recent_names.insert((sym.clone(), module), defid2); let defid3 = self.names.push_and_get_key(sym); debug_assert_eq!( defid, @@ -737,6 +760,7 @@ struct FunctionCollector<'cx> { res: &'cx mut Environment, module: ModId, curr_class: Option, + curr_function: Option, parent: Option, } @@ -1057,6 +1081,7 @@ impl<'cx> FunctionAnalyzer<'cx> { || calls_method(callee, "map") || calls_method(callee, "foreach") || calls_method(callee, "filter") + || calls_method(callee, "catch") { if let [ExprOrSpread { expr, .. }] = args { debug!("found useState/then/map/foreach/filter"); @@ -1210,7 +1235,9 @@ impl<'cx> FunctionAnalyzer<'cx> { JSXExpr::JSXEmptyExpr(_) => Operand::UNDEF, JSXExpr::Expr(expr) => self.lower_expr(expr, None), }, - JSXElementChild::JSXSpreadChild(JSXSpreadChild { expr, .. }) => self.lower_expr(expr, None), + JSXElementChild::JSXSpreadChild(JSXSpreadChild { expr, .. }) => { + self.lower_expr(expr, None) + } JSXElementChild::JSXElement(elem) => { self.lower_jsx_attr(elem); self.lower_jsx_elem(elem) @@ -1322,7 +1349,7 @@ impl<'cx> FunctionAnalyzer<'cx> { }; let lowered_value = self.lower_expr(&value, None); let next_key = self.res.get_or_overwrite_sym( - (key.as_symbol().unwrap(), span.ctxt), + (key.as_symbol().unwrap_or_default(), span.ctxt), self.module, DefKind::Arg, ); @@ -1865,6 +1892,53 @@ impl Visit for LocalDefiner<'_> { } impl Visit for FunctionCollector<'_> { + + fn visit_export_default_decl(&mut self, n: &ExportDefaultDecl) { + if let Some(defid) = self.res.default_export(self.module) { + if let Some(defid) = self.res.resolver.get_default(self.module) { + // self.res.defs.defs.insert(defid, DefRes::Function(())); ask josh about this + } + + self.curr_function = Some(defid); + } + n.visit_children_with(self); + + self.curr_function = None; + } + + fn visit_assign_expr(&mut self, n: &AssignExpr) { + if let Some(ident) = ident_from_assign_expr(n) { + if ident.sym.to_string() == "module" { + if let Some(mem_expr) = mem_expr_from_assign(n) { + if let MemberProp::Ident(ident_property) = &mem_expr.prop { + if ident_property.sym.to_string() == "exports" { + match &*n.right { + Expr::Fn(FnExpr { ident, function }) => self.handle_function(&**&function, self.res.default_export(self.module)), + Expr::Class(ClassExpr { ident, class }) => {}, + _ => {} + } + } + } + } + } + else if ident.sym.to_string() == "exports" { + if let Some(mem_expr) = mem_expr_from_assign(n) { + if let MemberProp::Ident(ident_property) = &mem_expr.prop { + match &*n.right { + Expr::Fn(n) => { + if let Some(defid) = self.res.get_sym(ident_property.to_id(), self.module) { + self.handle_function(&**&n.function, Some(defid)); + } + } + _ => {} + } + } + } + } + } + n.visit_children_with(self); + } + fn visit_class_decl(&mut self, n: &ClassDecl) { if let Some(DefKind::Class(class)) = self.res.sym_to_def(n.ident.to_id(), self.module) { self.curr_class = Some(class.def); // sets the current class so that mehtods are assigned to the proper class @@ -1965,31 +2039,6 @@ impl Visit for FunctionCollector<'_> { } } - fn visit_assign_expr(&mut self, n: &AssignExpr) { - if let Some(expr) = &n.left.as_expr() { - if let Expr::Member(mem_expr) = &**expr { - if let Expr::Ident(ident) = &*mem_expr.obj { - if &ident.sym == "exports" { - if let MemberProp::Ident(ident_property) = &mem_expr.prop { - match &*n.right { - Expr::Fn(FnExpr { ident, function }) => { - if let Some(defid) = - self.res.get_sym(ident_property.to_id(), self.module) - { - self.handle_function(&**function, Some(defid)); - } - } - _ => {} - } - } - } - } - } - } - - n.visit_children_with(self) - } - fn visit_arrow_expr( &mut self, ArrowExpr { @@ -2136,12 +2185,16 @@ impl Visit for FunctionCollector<'_> { fn visit_fn_decl(&mut self, n: &FnDecl) { let id = n.ident.to_id(); - let def = self - .res - .get_or_overwrite_sym(id, self.module, DefKind::Function(())); - self.parent = Some(def); - n.function.visit_with(self); - self.parent = None; + if let Some(defid) = self.res.resolver.exported_names.get(&(n.ident.clone().sym, self.module)) { + self.handle_function(&*n.function, Some(defid.clone())); + } else { + let def = self + .res + .get_or_overwrite_sym(id, self.module, DefKind::Function(())); + self.parent = Some(def); + n.function.visit_with(self); + self.parent = None; + } } fn visit_function(&mut self, n: &Function) { @@ -2150,18 +2203,17 @@ impl Visit for FunctionCollector<'_> { } } - impl FunctionCollector<'_> { fn handle_function(&mut self, n: &Function, owner: Option) { let owner = self.parent.unwrap_or_else(|| { if let Some(defid) = owner { - return defid; - } - if let Some(defid) = self.res.default_export(self.module) { - return defid; - } - self.res + defid + } else if let Some(defid ) = self.curr_function { + defid + } else { + self.res .add_anonymous("__UNKNOWN", AnonType::Closure, self.module) + } }); let mut argdef = ArgDefiner { res: self.res, @@ -2543,7 +2595,6 @@ impl Visit for ImportCollector<'_> { let import_name = imported .as_ref() .map_or_else(|| local.0.clone(), export_name_to_jsword); - match self .file_resolver .resolve_import(self.curr_mod.into(), &*self.current_import) @@ -2688,10 +2739,13 @@ impl Visit for ExportCollector<'_> { } else if ident.sym.to_string() == "exports" { if let Some(mem_expr) = mem_expr_from_assign(n) { if let MemberProp::Ident(ident_property) = &mem_expr.prop { - //self.add_export(DefRes::Undefined, ident_property.to_id()); match &*n.right { Expr::Fn(FnExpr { ident, function }) => { self.add_export(DefRes::Function(()), ident_property.to_id()); + }, + Expr::Ident(ident) => { + let export_defid = self.add_export(DefRes::Function(()), ident_property.to_id()); + self.res_table.exported_names.insert((ident.sym.clone(), self.curr_mod), export_defid); } _ => {} } @@ -2756,8 +2810,13 @@ impl Visit for ExportCollector<'_> { } fn visit_export_default_expr(&mut self, n: &ExportDefaultExpr) { - self.add_default(DefRes::Undefined, None); - n.visit_children_with(self) + if let Expr::Ident(ident) = &*n.expr { + + self.add_default(DefRes::Function(()), None); + + self.res_table.exported_names.insert((ident.sym.clone(), self.curr_mod), self.default.unwrap()); + } + //n.visit_children_with(self) } } @@ -2824,6 +2883,11 @@ impl Environment { self.resolver.sym_id(id, module) } + #[inline] + fn recent_sym(&self, sym: JsWord, module: ModId) -> Option { + self.resolver.recent_sym(sym, module) + } + fn new_key_from_res(&mut self, id: DefId, res: DefRes) -> DefKey { match res { DefKind::Arg => DefKind::Arg, diff --git a/crates/forge_analyzer/src/interp.rs b/crates/forge_analyzer/src/interp.rs index 3c765ae8..498ce8cb 100644 --- a/crates/forge_analyzer/src/interp.rs +++ b/crates/forge_analyzer/src/interp.rs @@ -10,8 +10,13 @@ use std::{ path::PathBuf, }; +use forge_permission_resolver::permissions_resolver::{ + check_url_for_permissions, get_permission_resolver_confluence, get_permission_resolver_jira, + PermissionHashMap, +}; use forge_loader::forgepermissions::ForgePermissions; use forge_utils::{FxHashMap, FxHashSet}; +use regex::Regex; use smallvec::SmallVec; use swc_core::ecma::atoms::JsWord; use tracing::{debug, info, instrument, warn}; @@ -475,8 +480,12 @@ pub struct Interp<'cx, C: Checker<'cx>> { callstack: RefCell>, pub callstack_arguments: Vec>, vulns: RefCell>, - pub permissions: Vec, + pub permissions: Vec, pub expecting_value: VecDeque<(DefId, (VarId, DefId))>, + pub jira_permission_resolver: PermissionHashMap, + pub confluence_permission_resolver: PermissionHashMap, + pub jira_regex_map: HashMap, + pub confluence_regex_map: HashMap, _checker: PhantomData, } @@ -536,6 +545,10 @@ impl std::error::Error for Error {} impl<'cx, C: Checker<'cx>> Interp<'cx, C> { pub fn new(env: &'cx Environment) -> Self { + let (jira_permission_resolver, jira_regex_map) = get_permission_resolver_jira(); + let (confluence_permission_resolver, confluence_regex_map) = + get_permission_resolver_confluence(); + let call_graph = CallGraph::new(env); Self { env, @@ -553,6 +566,10 @@ impl<'cx, C: Checker<'cx>> Interp<'cx, C> { callstack: RefCell::new(Vec::new()), vulns: RefCell::new(Vec::new()), permissions: Vec::new(), + jira_permission_resolver, + confluence_permission_resolver, + jira_regex_map, + confluence_regex_map, _checker: PhantomData, } } @@ -656,6 +673,7 @@ impl<'cx, C: Checker<'cx>> Interp<'cx, C> { let arguments = self.callstack_arguments.pop(); let name = self.env.def_name(def); debug!("Dataflow: {name} - {block_id}"); + println!("Dataflow: {def:?} {name} - {block_id}"); self.dataflow_visited.insert(def); let func = self.env().def_ref(def).expect_body(); self.curr_body.set(Some(func)); diff --git a/crates/forge_analyzer/src/ir.rs b/crates/forge_analyzer/src/ir.rs index 57a64af3..0706e689 100644 --- a/crates/forge_analyzer/src/ir.rs +++ b/crates/forge_analyzer/src/ir.rs @@ -395,6 +395,7 @@ impl Body { #[inline] pub(crate) fn push_inst(&mut self, bb: BasicBlockId, inst: Inst) { + println!("{:?} {inst}", self.owner); self.blocks[bb].insts.push(inst); } diff --git a/crates/forge_analyzer/src/lib.rs b/crates/forge_analyzer/src/lib.rs index f9a56e97..00bf765f 100644 --- a/crates/forge_analyzer/src/lib.rs +++ b/crates/forge_analyzer/src/lib.rs @@ -7,7 +7,6 @@ pub mod exports; pub mod interp; pub mod ir; pub mod lattice; -pub mod permissionclassifier; pub mod pretty; pub mod reporter; pub mod resolver; diff --git a/crates/forge_analyzer/src/permissionclassifier.rs b/crates/forge_analyzer/src/permissionclassifier.rs deleted file mode 100644 index 2ab45477..00000000 --- a/crates/forge_analyzer/src/permissionclassifier.rs +++ /dev/null @@ -1,280 +0,0 @@ -use crate::definitions::IntrinsicName; -use forge_loader::forgepermissions::ForgePermissions; - -pub(crate) fn check_permission_used( - function_name: IntrinsicName, - first_arg: &str, - second_arg: Option<&str>, -) -> Vec { - let mut used_permissions: Vec = Vec::new(); - - let post_call = second_arg.unwrap_or("").contains("POST"); - let delete_call = second_arg.unwrap_or("").contains("DELTE"); - let put_call = second_arg.unwrap_or("").contains("PUT"); - - let contains_audit = first_arg.contains("audit"); - let contains_issue = first_arg.contains("issue"); - let contains_content = first_arg.contains("content"); - let contains_user = first_arg.contains("user"); - let contains_theme = first_arg.contains("theme"); - let contains_template = first_arg.contains("template"); - let contains_space = first_arg.contains("space"); - let contains_analytics = first_arg.contains("analytics"); - let contains_cql = first_arg.contains("cql"); - let contains_attachment = first_arg.contains("attachment"); - let contains_contentbody = first_arg.contains("contentbody"); - let contians_permissions = first_arg.contains("permissions"); - let contains_property = first_arg.contains("property"); - let contains_page_tree = first_arg.contains("pageTree"); - let contains_group = first_arg.contains("group"); - let contains_inlinetasks = first_arg.contains("inlinetasks"); - let contains_relation = first_arg.contains("relation"); - let contains_settings = first_arg.contains("settings"); - let contains_permission = first_arg.contains("permission"); - let contains_download = first_arg.contains("download"); - let contains_descendants = first_arg.contains("descendants"); - let contains_comment = first_arg.contains("comment"); - let contains_label = first_arg.contains("contains_label"); - let contains_search = first_arg.contains("contains_search"); - let contains_longtask = first_arg.contains("contains_longtask"); - let contains_notification = first_arg.contains("notification"); - let contains_watch = first_arg.contains("watch"); - let contains_version = first_arg.contains("version"); - let contains_state = first_arg.contains("contains_state"); - let contains_available = first_arg.contains("available"); - let contains_announcement_banner = first_arg.contains("announcementBanner"); - let contains_avatar = first_arg.contains("avatar"); - let contains_size = first_arg.contains("size"); - let contains_dashboard = first_arg.contains("dashboard"); - let contains_gadget = first_arg.contains("gadget"); - let contains_filter = first_arg.contains("filter"); - let contains_tracking = first_arg.contains("tracking"); - let contains_groupuserpicker = first_arg.contains("groupuserpicker"); - let contains_workflow = first_arg.contains("workflow"); - let contains_status = first_arg.contains("status"); - let contains_task = first_arg.contains("task"); - let contains_screen = first_arg.contains("screen"); - let non_get_call = post_call || delete_call || put_call; - let contains_webhook = first_arg.contains("webhook"); - let contains_project = first_arg.contains("project"); - let contains_actor = first_arg.contains("actor"); - let contains_role = first_arg.contains("contains_role"); - let contains_project_validate = first_arg.contains("projectvalidate"); - let contains_email = first_arg.contains("email"); - let contains_notification_scheme = first_arg.contains("notificationscheme"); - let contains_priority = first_arg.contains("priority"); - let contains_properties = first_arg.contains("properties"); - let contains_remote_link = first_arg.contains("remotelink"); - let contains_resolution = first_arg.contains("resolution"); - let contains_security_level = first_arg.contains("securitylevel"); - let contains_issue_security_schemes = first_arg.contains("issuesecurityschemes"); - let contains_issue_type = first_arg.contains("issuetype"); - let contains_issue_type_schemes = first_arg.contains("issuetypescheme"); - let contains_votes = first_arg.contains("contains_votes"); - let contains_worklog = first_arg.contains("worklog"); - let contains_expression = first_arg.contains("expression"); - let contains_configuration = first_arg.contains("configuration"); - let contains_application_properties = first_arg.contains("application-properties"); - - match function_name { - IntrinsicName::RequestJira => { - if (contains_dashboard && non_get_call) - || (contains_user && non_get_call) - || contains_task - { - used_permissions.push(ForgePermissions::WriteJiraWork); - if contains_gadget { - used_permissions.push(ForgePermissions::ReadJiraWork) - } - } else if contains_expression { - used_permissions.push(ForgePermissions::ReadJiraUser); - used_permissions.push(ForgePermissions::ReadJiraUser) - } else if (contains_avatar && contains_size) - || contains_dashboard - || contains_status - || contains_groupuserpicker - { - used_permissions.push(ForgePermissions::ReadJiraWork) - } else if (!non_get_call && contains_user) || contains_configuration { - used_permissions.push(ForgePermissions::ReadJiraUser) - } else if contains_webhook { - used_permissions.push(ForgePermissions::ManageJiraWebhook); - used_permissions.push(ForgePermissions::ReadJiraWork) - } else if (contains_remote_link && non_get_call) - || (contains_issue && contains_votes && non_get_call) - || (contains_worklog && non_get_call) - { - used_permissions.push(ForgePermissions::WriteJiraWork) - } else if (contains_issue_type && non_get_call) - || (contains_issue_type && non_get_call) - || (contains_project && non_get_call) - || (contains_project && contains_actor) - || (contains_project && contains_role) - || (contains_project && contains_email) - || (contains_priority && (non_get_call || contains_search)) - || (contains_properties && contains_issue && non_get_call) - || (contains_resolution && non_get_call) - || contains_audit - || contains_avatar - || contains_workflow - || contains_tracking - || contains_status - || contains_screen - || contains_notification_scheme - || contains_security_level - || contains_issue_security_schemes - || contains_issue_type_schemes - || contains_announcement_banner - || contains_application_properties - { - used_permissions.push(ForgePermissions::ManageJiraConfiguration) - } else if contains_filter { - if non_get_call { - used_permissions.push(ForgePermissions::WriteJiraWork) - } else { - used_permissions.push(ForgePermissions::ReadJiraWork) - } - } else if contains_project - || contains_project_validate - || contains_priority - || contains_search - || contains_issue_type - || (contains_issue && contains_votes) - || (contains_properties && contains_issue) - || (contains_remote_link && !non_get_call) - || (contains_resolution && !non_get_call) - || contains_worklog - { - used_permissions.push(ForgePermissions::ReadJiraWork) - } else if post_call { - if contains_issue { - used_permissions.push(ForgePermissions::WriteJiraWork); - } else { - used_permissions.push(ForgePermissions::Unknown); - } - } else { - if contains_issue { - used_permissions.push(ForgePermissions::ReadJiraWork); - } else { - used_permissions.push(ForgePermissions::Unknown); - } - } - } - IntrinsicName::RequestConfluence => { - if non_get_call { - if contains_content { - used_permissions.push(ForgePermissions::WriteConfluenceContent); - } else if contains_audit { - used_permissions.push(ForgePermissions::WriteAuditLogsConfluence); - if post_call { - used_permissions.push(ForgePermissions::ReadAuditLogsConfluence); - } - } else if contains_content && contains_attachment { - if put_call { - // review this more specifically - // /wiki/rest/api/content/{id}/child/attachment/{attachmentId}`, - used_permissions.push(ForgePermissions::WriteConfluenceFile); - used_permissions.push(ForgePermissions::WriteConfluenceProps) - } else { - used_permissions.push(ForgePermissions::WriteConfluenceFile) - } - } else if contains_contentbody { - used_permissions.push(ForgePermissions::ReadConfluenceContentAll) - } else if contains_content && contians_permissions { - used_permissions.push(ForgePermissions::ReadConfluenceContentPermission) - } else if contains_property { - used_permissions.push(ForgePermissions::WriteConfluenceProps) - } else if contains_content - || contains_page_tree - || contains_relation - || contains_template - { - used_permissions.push(ForgePermissions::WriteConfluenceContent) - } else if contains_group { - used_permissions.push(ForgePermissions::WriteConfluenceGroups) - } else if contains_settings { - used_permissions.push(ForgePermissions::ManageConfluenceConfiguration) - } else if contains_space && contains_permission { - if !delete_call { - used_permissions.push(ForgePermissions::ReadSpacePermissionConfluence); - } - used_permissions.push(ForgePermissions::WriteSpacePermissionsConfluence) - } else if contains_space || contains_theme { - used_permissions.push(ForgePermissions::WriteConfluenceSpace); - } else if contains_inlinetasks { - used_permissions.push(ForgePermissions::WriteInlineTaskConfluence) - } else if contains_user && contains_property { - used_permissions.push(ForgePermissions::WriteUserPropertyConfluence); - } else { - used_permissions.push(ForgePermissions::Unknown); - } - } else { - if contains_issue { - used_permissions.push(ForgePermissions::ReadJiraWork); - } else if contains_audit { - used_permissions.push(ForgePermissions::ReadAuditLogsConfluence) - } else if contains_cql { - if contains_user { - used_permissions.push(ForgePermissions::ReadContentDetailsConfluence); - } else { - used_permissions.push(ForgePermissions::SearchConfluence); - } - } else if contains_attachment && contains_download { - used_permissions.push(ForgePermissions::ReadOnlyContentAttachmentConfluence) - } else if contains_longtask { - used_permissions.push(ForgePermissions::ReadContentMetadataConfluence); - used_permissions.push(ForgePermissions::ReadConfluenceSpaceSummary) - } else if contains_content && contains_property { - used_permissions.push(ForgePermissions::ReadConfluenceProps); - } else if contains_template - || contains_relation - || (contains_content - && (contains_comment || contains_descendants || contains_label)) - { - used_permissions.push(ForgePermissions::ReadConfluenceContentSummary) - } else if contains_space && contains_settings { - used_permissions.push(ForgePermissions::ReadConfluenceSpaceSummary) - } else if contains_space && contains_theme { - used_permissions.push(ForgePermissions::ManageConfluenceConfiguration) - } else if contains_space && contains_content && contains_state { - used_permissions.push(ForgePermissions::ReadConfluenceContentAll) - } else if contains_space && contains_content { - used_permissions.push(ForgePermissions::ReadConfluenceContentSummary) - } else if contains_state && contains_content && contains_available { - used_permissions.push(ForgePermissions::WriteConfluenceContent) - } else if contains_content - && (contains_notification - || contains_watch - || contains_version - || contains_state) - { - used_permissions.push(ForgePermissions::ReadConfluenceContentSummary) - } else if contains_space { - used_permissions.push(ForgePermissions::ReadConfluenceProps) - } else if contains_content || contains_analytics { - used_permissions.push(ForgePermissions::ReadConfluenceContentAll) - } else if contains_user && contains_property { - used_permissions.push(ForgePermissions::WriteUserPropertyConfluence) - } else if contains_settings { - used_permissions.push(ForgePermissions::ManageConfluenceConfiguration) - } else if contains_search { - used_permissions.push(ForgePermissions::ReadContentDetailsConfluence) - } else if contains_space { - used_permissions.push(ForgePermissions::ReadConfluenceSpaceSummary) - } else if contains_user { - used_permissions.push(ForgePermissions::ReadConfluenceUser) - } else if contains_label { - used_permissions.push(ForgePermissions::ReadConfluenceContentSummary) - } else if contains_inlinetasks { - used_permissions.push(ForgePermissions::ReadConfluenceContentAll); - } else { - used_permissions.push(ForgePermissions::Unknown); - } - } - } - _ => { - used_permissions.push(ForgePermissions::Unknown); - } - } - used_permissions -} diff --git a/crates/forge_analyzer/src/reporter.rs b/crates/forge_analyzer/src/reporter.rs index f5cd6441..0fb853a4 100644 --- a/crates/forge_analyzer/src/reporter.rs +++ b/crates/forge_analyzer/src/reporter.rs @@ -1,4 +1,3 @@ -use forge_loader::forgepermissions::ForgePermissions; use serde::Serialize; use time::{Date, OffsetDateTime}; diff --git a/crates/forge_loader/src/manifest.rs b/crates/forge_loader/src/manifest.rs index ef31be88..eaed924b 100644 --- a/crates/forge_loader/src/manifest.rs +++ b/crates/forge_loader/src/manifest.rs @@ -109,7 +109,7 @@ struct Content<'a> { #[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] pub struct Perms<'a> { #[serde(default)] - pub scopes: Vec, + pub scopes: Vec, #[serde(default, borrow)] content: Content<'a>, } diff --git a/crates/forge_permission_resolver/src/permissions_resolver.rs b/crates/forge_permission_resolver/src/permissions_resolver.rs index 874dd64b..d13fc7a9 100644 --- a/crates/forge_permission_resolver/src/permissions_resolver.rs +++ b/crates/forge_permission_resolver/src/permissions_resolver.rs @@ -5,7 +5,7 @@ use std::{cmp::Reverse, collections::HashMap, hash::Hash, vec}; use tracing::warn; use ureq; -type PermissionHashMap = HashMap<(String, RequestType), Vec>; +pub type PermissionHashMap = HashMap<(String, RequestType), Vec>; #[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] struct SwaggerReponse { @@ -86,15 +86,21 @@ pub fn check_url_for_permissions( vec![] } -pub fn get_permission_resolver() -> (PermissionHashMap, HashMap) { +pub fn get_permission_resolver_jira() -> (PermissionHashMap, HashMap) { let jira_url = "https://developer.atlassian.com/cloud/jira/platform/swagger-v3.v3.json"; + return get_permission_resolver(jira_url); +} + +pub fn get_permission_resolver_confluence() -> (PermissionHashMap, HashMap) { let confluence_url = "https://developer.atlassian.com/cloud/confluence/swagger.v3.json"; + return get_permission_resolver(confluence_url); +} +pub fn get_permission_resolver(url: &str) -> (PermissionHashMap, HashMap) { let mut endpoint_map: PermissionHashMap = HashMap::default(); let mut endpoint_regex: HashMap = HashMap::default(); - get_permisions_for(jira_url, &mut endpoint_map, &mut endpoint_regex); - get_permisions_for(confluence_url, &mut endpoint_map, &mut endpoint_regex); + get_permisions_for(url, &mut endpoint_map, &mut endpoint_regex); return (endpoint_map, endpoint_regex); } diff --git a/crates/forge_permission_resolver/src/test.rs b/crates/forge_permission_resolver/src/test.rs index e87e6056..9a1a91c5 100644 --- a/crates/forge_permission_resolver/src/test.rs +++ b/crates/forge_permission_resolver/src/test.rs @@ -3,6 +3,10 @@ use crate::permissions_resolver::{ }; mod tests { + use crate::permissions_resolver::{ + get_permission_resolver_confluence, get_permission_resolver_jira, + }; + use super::*; #[test] @@ -25,7 +29,7 @@ mod tests { #[test] fn test_resolving_permssion_basic() { - let (permission_map, regex_map) = get_permission_resolver(); + let (permission_map, regex_map) = get_permission_resolver_jira(); let url = "/rest/api/3/issue/27/attachments"; let request_type = RequestType::Post; let result = check_url_for_permissions(&permission_map, ®ex_map, request_type, url); @@ -43,7 +47,7 @@ mod tests { #[test] fn test_resolving_permssion_end_var() { - let (permission_map, regex_map) = get_permission_resolver(); + let (permission_map, regex_map) = get_permission_resolver_confluence(); let url = "/wiki/rest/api/relation/1/from/1/2/to/3/4"; let request_type = RequestType::Get; let result = check_url_for_permissions(&permission_map, ®ex_map, request_type, url); @@ -59,7 +63,7 @@ mod tests { #[test] fn test_resolving_permssion_no_var() { - let (permission_map, regex_map) = get_permission_resolver(); + let (permission_map, regex_map) = get_permission_resolver_jira(); let url = "/rest/api/3/issue/archive"; let request_type = RequestType::Post; let result = check_url_for_permissions(&permission_map, ®ex_map, request_type, url); diff --git a/crates/fsrt/src/main.rs b/crates/fsrt/src/main.rs index b0b1955a..2c811119 100644 --- a/crates/fsrt/src/main.rs +++ b/crates/fsrt/src/main.rs @@ -178,8 +178,11 @@ fn scan_directory(dir: PathBuf, function: Option<&str>, opts: Opts) -> Result = - HashSet::from_iter(permission_scopes.iter().cloned()); + let permissions_declared: HashSet = + HashSet::from_iter(permission_scopes.iter().map(|s| s.replace("\"", "") + )); + + println!("permission scopes: {permission_scopes:?}"); let paths = collect_sourcefiles(dir.join("src/")).collect::>(); @@ -232,7 +235,9 @@ fn scan_directory(dir: PathBuf, function: Option<&str>, opts: Opts) -> Result { let mut checker = AuthenticateChecker::new(); @@ -251,14 +256,16 @@ fn scan_directory(dir: PathBuf, function: Option<&str>, opts: Opts) -> Result 0 { reporter.add_vulnerabilities( - vec![PermissionVuln::new(HashSet::::from_iter( + vec![PermissionVuln::new(HashSet::::from_iter( unused_permissions.cloned().into_iter(), ))] .into_iter(), diff --git a/test-apps/jira-damn-vulnerable-forge-app/src/utils.js b/test-apps/jira-damn-vulnerable-forge-app/src/utils.js index caf62bac..063558d7 100755 --- a/test-apps/jira-damn-vulnerable-forge-app/src/utils.js +++ b/test-apps/jira-damn-vulnerable-forge-app/src/utils.js @@ -1,18 +1,33 @@ import api, { route } from '@forge/api'; import {testFunctionFromTestFile} from './testfile'; -import UselessClass from './uselessClass'; +import module_exports_func from './moduleex.js' +import {func_from_exports, diffunc} from "./exportse.js" +import {another_export, newExport} from './newexports.js' +import func_defult from './export_default'; +import my_function from "./export_default2.js" export async function fetchIssueSummary(issueIdOrKey, url) { let obj = { - method: 'PTACH', + method: 'POST', bananas: 'apple', headers: { // Accept: 'application/json', }, }; - testFunctionFromTestFile(); + module_exports_func() + func_from_exports() + another_export() + newExport() + func_defult() + my_function() + + // testFunctionFromTestFile(); + + diffunc() + + different_function(); let val = "grapefruit"; From 9aa746d1cbaa85d722a0bf12ea9a72ae9304f6b3 Mon Sep 17 00:00:00 2001 From: James Gersbach Date: Sun, 6 Aug 2023 14:06:08 -0700 Subject: [PATCH 142/517] formatting --- crates/forge_analyzer/src/checkers.rs | 45 ++++++++++++++--------- crates/forge_analyzer/src/definitions.rs | 46 +++++++++++++++--------- crates/forge_analyzer/src/interp.rs | 2 +- crates/forge_analyzer/src/ir.rs | 1 - crates/fsrt/src/main.rs | 4 +-- 5 files changed, 59 insertions(+), 39 deletions(-) diff --git a/crates/forge_analyzer/src/checkers.rs b/crates/forge_analyzer/src/checkers.rs index 2bbe12f4..3e11490e 100644 --- a/crates/forge_analyzer/src/checkers.rs +++ b/crates/forge_analyzer/src/checkers.rs @@ -490,7 +490,6 @@ impl WithCallStack for AuthNVuln { pub struct PermissionDataflow { needs_call: Vec<(DefId, Vec, Vec)>, varid_to_value: FxHashMap, - } impl WithCallStack for PermissionVuln { @@ -663,27 +662,45 @@ impl<'cx> Dataflow<'cx> for PermissionDataflow { println!("first arg ===> {first_arg:?}"); second_arg_vec.iter().for_each(|second_arg| { if intrinsic_func_type == IntrinsicName::RequestConfluence { - let permissions = check_url_for_permissions(&_interp.confluence_permission_resolver, &_interp.confluence_regex_map, trnaslate_request_type(Some(second_arg)), &first_arg); + let permissions = check_url_for_permissions( + &_interp.confluence_permission_resolver, + &_interp.confluence_regex_map, + trnaslate_request_type(Some(second_arg)), + &first_arg, + ); permissions_within_call.extend_from_slice(&permissions) - // change this to seoiimthinf abdonaofoadinadsjoklj } else if intrinsic_func_type == IntrinsicName::RequestJira { - let permissions = check_url_for_permissions(&_interp.jira_permission_resolver, &_interp.jira_regex_map, trnaslate_request_type(Some(second_arg)), &first_arg); + let permissions = check_url_for_permissions( + &_interp.jira_permission_resolver, + &_interp.jira_regex_map, + trnaslate_request_type(Some(second_arg)), + &first_arg, + ); permissions_within_call.extend_from_slice(&permissions) - } + } }) }) } else { first_arg_vec.iter().for_each(|first_arg| { println!("first arg ===> {first_arg:?}"); if intrinsic_func_type == IntrinsicName::RequestConfluence { - let permissions = check_url_for_permissions(&_interp.confluence_permission_resolver, &_interp.confluence_regex_map, RequestType::Get, &first_arg); + let permissions = check_url_for_permissions( + &_interp.confluence_permission_resolver, + &_interp.confluence_regex_map, + RequestType::Get, + &first_arg, + ); permissions_within_call.extend_from_slice(&permissions) - // change this to seoiimthinf abdonaofoadinadsjoklj } else if intrinsic_func_type == IntrinsicName::RequestJira { - let permissions = check_url_for_permissions(&_interp.jira_permission_resolver, &_interp.jira_regex_map, RequestType::Get, &first_arg); + let permissions = check_url_for_permissions( + &_interp.jira_permission_resolver, + &_interp.jira_regex_map, + RequestType::Get, + &first_arg, + ); permissions_within_call.extend_from_slice(&permissions) - } - + } + //HERE }) } @@ -950,8 +967,6 @@ impl<'cx> Dataflow<'cx> for PermissionDataflow { } } Rvalue::Template(template) => { - // trying to get the template stirngs in order .... - let quasis_joined = template.quasis.join(""); let (mut original_consts, mut all_potential_values) = (vec![""], vec![String::from("")]); @@ -968,8 +983,6 @@ impl<'cx> Dataflow<'cx> for PermissionDataflow { .collect(); } - // abc abd - let mut new_values__ = vec![]; let values = self.get_str_from_expr(expr, def); @@ -1235,8 +1248,7 @@ fn trnaslate_request_type(request_type: Option<&str>) -> RequestType { } else { return RequestType::Get; } - -} +} pub struct PermissionChecker { pub visit: bool, @@ -1247,7 +1259,6 @@ pub struct PermissionChecker { impl PermissionChecker { pub fn new(declared_permissions: HashSet) -> Self { - Self { visit: false, vulns: vec![], diff --git a/crates/forge_analyzer/src/definitions.rs b/crates/forge_analyzer/src/definitions.rs index ae740f60..4e3f5ac4 100644 --- a/crates/forge_analyzer/src/definitions.rs +++ b/crates/forge_analyzer/src/definitions.rs @@ -675,10 +675,9 @@ impl ResolverTable { #[inline] fn recent_sym(&self, sym: JsWord, module: ModId) -> Option { - self.recent_names.get(&(sym, module )).copied() + self.recent_names.get(&(sym, module)).copied() } - // this won't work becasue there may be names that are the same acrosss modules ... #[inline] fn get_default(&self, module: ModId) -> Option { @@ -703,7 +702,8 @@ impl ResolverTable { #[inline] fn get_or_insert_sym(&mut self, id: Id, module: ModId) -> DefId { - let defid = self.sym_id(id.clone(), module) + let defid = self + .sym_id(id.clone(), module) .unwrap_or_else(|| self.reserve_symbol(id.clone(), module)); self.recent_names.insert((id.0, module), defid); defid @@ -1892,7 +1892,6 @@ impl Visit for LocalDefiner<'_> { } impl Visit for FunctionCollector<'_> { - fn visit_export_default_decl(&mut self, n: &ExportDefaultDecl) { if let Some(defid) = self.res.default_export(self.module) { if let Some(defid) = self.res.resolver.get_default(self.module) { @@ -1913,20 +1912,24 @@ impl Visit for FunctionCollector<'_> { if let MemberProp::Ident(ident_property) = &mem_expr.prop { if ident_property.sym.to_string() == "exports" { match &*n.right { - Expr::Fn(FnExpr { ident, function }) => self.handle_function(&**&function, self.res.default_export(self.module)), - Expr::Class(ClassExpr { ident, class }) => {}, + Expr::Fn(FnExpr { ident, function }) => self.handle_function( + &**&function, + self.res.default_export(self.module), + ), + Expr::Class(ClassExpr { ident, class }) => {} _ => {} } } } } - } - else if ident.sym.to_string() == "exports" { + } else if ident.sym.to_string() == "exports" { if let Some(mem_expr) = mem_expr_from_assign(n) { if let MemberProp::Ident(ident_property) = &mem_expr.prop { match &*n.right { Expr::Fn(n) => { - if let Some(defid) = self.res.get_sym(ident_property.to_id(), self.module) { + if let Some(defid) = + self.res.get_sym(ident_property.to_id(), self.module) + { self.handle_function(&**&n.function, Some(defid)); } } @@ -2185,7 +2188,12 @@ impl Visit for FunctionCollector<'_> { fn visit_fn_decl(&mut self, n: &FnDecl) { let id = n.ident.to_id(); - if let Some(defid) = self.res.resolver.exported_names.get(&(n.ident.clone().sym, self.module)) { + if let Some(defid) = self + .res + .resolver + .exported_names + .get(&(n.ident.clone().sym, self.module)) + { self.handle_function(&*n.function, Some(defid.clone())); } else { let def = self @@ -2208,11 +2216,11 @@ impl FunctionCollector<'_> { let owner = self.parent.unwrap_or_else(|| { if let Some(defid) = owner { defid - } else if let Some(defid ) = self.curr_function { + } else if let Some(defid) = self.curr_function { defid } else { self.res - .add_anonymous("__UNKNOWN", AnonType::Closure, self.module) + .add_anonymous("__UNKNOWN", AnonType::Closure, self.module) } }); let mut argdef = ArgDefiner { @@ -2742,10 +2750,13 @@ impl Visit for ExportCollector<'_> { match &*n.right { Expr::Fn(FnExpr { ident, function }) => { self.add_export(DefRes::Function(()), ident_property.to_id()); - }, + } Expr::Ident(ident) => { - let export_defid = self.add_export(DefRes::Function(()), ident_property.to_id()); - self.res_table.exported_names.insert((ident.sym.clone(), self.curr_mod), export_defid); + let export_defid = + self.add_export(DefRes::Function(()), ident_property.to_id()); + self.res_table + .exported_names + .insert((ident.sym.clone(), self.curr_mod), export_defid); } _ => {} } @@ -2811,10 +2822,11 @@ impl Visit for ExportCollector<'_> { fn visit_export_default_expr(&mut self, n: &ExportDefaultExpr) { if let Expr::Ident(ident) = &*n.expr { - self.add_default(DefRes::Function(()), None); - self.res_table.exported_names.insert((ident.sym.clone(), self.curr_mod), self.default.unwrap()); + self.res_table + .exported_names + .insert((ident.sym.clone(), self.curr_mod), self.default.unwrap()); } //n.visit_children_with(self) } diff --git a/crates/forge_analyzer/src/interp.rs b/crates/forge_analyzer/src/interp.rs index 498ce8cb..44dcaf14 100644 --- a/crates/forge_analyzer/src/interp.rs +++ b/crates/forge_analyzer/src/interp.rs @@ -10,11 +10,11 @@ use std::{ path::PathBuf, }; +use forge_loader::forgepermissions::ForgePermissions; use forge_permission_resolver::permissions_resolver::{ check_url_for_permissions, get_permission_resolver_confluence, get_permission_resolver_jira, PermissionHashMap, }; -use forge_loader::forgepermissions::ForgePermissions; use forge_utils::{FxHashMap, FxHashSet}; use regex::Regex; use smallvec::SmallVec; diff --git a/crates/forge_analyzer/src/ir.rs b/crates/forge_analyzer/src/ir.rs index 0706e689..57a64af3 100644 --- a/crates/forge_analyzer/src/ir.rs +++ b/crates/forge_analyzer/src/ir.rs @@ -395,7 +395,6 @@ impl Body { #[inline] pub(crate) fn push_inst(&mut self, bb: BasicBlockId, inst: Inst) { - println!("{:?} {inst}", self.owner); self.blocks[bb].insts.push(inst); } diff --git a/crates/fsrt/src/main.rs b/crates/fsrt/src/main.rs index 2c811119..86309e5f 100644 --- a/crates/fsrt/src/main.rs +++ b/crates/fsrt/src/main.rs @@ -179,8 +179,7 @@ fn scan_directory(dir: PathBuf, function: Option<&str>, opts: Opts) -> Result = - HashSet::from_iter(permission_scopes.iter().map(|s| s.replace("\"", "") - )); + HashSet::from_iter(permission_scopes.iter().map(|s| s.replace("\"", ""))); println!("permission scopes: {permission_scopes:?}"); @@ -237,7 +236,6 @@ fn scan_directory(dir: PathBuf, function: Option<&str>, opts: Opts) -> Result { let mut checker = AuthenticateChecker::new(); From 8155c244b94fd00e8540da95d7e2da0f91ce3fa2 Mon Sep 17 00:00:00 2001 From: James Gersbach Date: Sun, 6 Aug 2023 19:46:05 -0700 Subject: [PATCH 143/517] working body definition analysis --- crates/forge_analyzer/src/checkers.rs | 296 ++++++++---------- crates/forge_analyzer/src/definitions.rs | 18 +- crates/forge_analyzer/src/interp.rs | 21 +- crates/fsrt/src/main.rs | 60 ++-- .../src/utils.js | 2 +- 5 files changed, 167 insertions(+), 230 deletions(-) diff --git a/crates/forge_analyzer/src/checkers.rs b/crates/forge_analyzer/src/checkers.rs index 3e11490e..8cdd4c42 100644 --- a/crates/forge_analyzer/src/checkers.rs +++ b/crates/forge_analyzer/src/checkers.rs @@ -16,7 +16,7 @@ use std::{ ops::ControlFlow, path::PathBuf, }; -use swc_core::ecma::transforms::base::perf::Check; +use swc_core::ecma::{transforms::base::perf::Check, utils::Value::Known}; use tracing::{debug, info, warn}; @@ -26,8 +26,8 @@ use crate::{ Checker, Dataflow, EntryKind, EntryPoint, Frame, Interp, JoinSemiLattice, WithCallStack, }, ir::{ - Base, BasicBlock, BasicBlockId, BinOp, Inst, Intrinsic, Literal, Location, Operand, Rvalue, - Successors, VarId, VarKind, Variable, + Base, BasicBlock, BasicBlockId, BinOp, Inst, Intrinsic, Literal, Location, Operand, + Projection, Rvalue, Successors, VarId, VarKind, Variable, }, reporter::{IntoVuln, Reporter, Severity, Vulnerability}, worklist::WorkList, @@ -489,7 +489,7 @@ impl WithCallStack for AuthNVuln { pub struct PermissionDataflow { needs_call: Vec<(DefId, Vec, Vec)>, - varid_to_value: FxHashMap, + varid_to_value: FxHashMap<(DefId, VarId, Option), Value>, } impl WithCallStack for PermissionVuln { @@ -516,7 +516,7 @@ impl PermissionDataflow { } Operand::Var(var) => { if let Base::Var(varid) = var.base { - if let Some(value) = self.get_value(_def, varid) { + if let Some(value) = self.get_value(_def, varid, None) { intrinsic_argument.first_arg = Some(vec![]); add_elements_to_intrinsic_struct(value, &mut intrinsic_argument.first_arg); } @@ -525,12 +525,25 @@ impl PermissionDataflow { } } - fn add_value(&mut self, defid_block: DefId, varid: VarId, value: Value) { - self.varid_to_value.insert(varid, value); + fn add_value( + &mut self, + defid_block: DefId, + varid: VarId, + value: Value, + projection: Option, + ) { + println!("varid from {varid:?} -- {projection:?} -- {value:?}"); + self.varid_to_value + .insert((defid_block, varid, projection), value); } - fn get_value(&self, defid_block: DefId, varid: VarId) -> Option<&Value> { - self.varid_to_value.get(&varid) + fn get_value( + &self, + defid_block: DefId, + varid: VarId, + projection: Option, + ) -> Option<&Value> { + self.varid_to_value.get(&(defid_block, varid, projection)) } fn get_str_from_expr(&self, expr: &Operand, def: DefId) -> Vec> { @@ -538,7 +551,7 @@ impl PermissionDataflow { return vec![Some(str)]; } else if let Operand::Var(var) = expr { if let Base::Var(varid) = var.base { - let value = self.get_value(def, varid); + let value = self.get_value(def, varid, None); if let Some(value) = value { match value { Value::Const(const_val) => { @@ -579,19 +592,6 @@ impl<'cx> Dataflow<'cx> for PermissionDataflow { } } - fn try_insert>( - &self, - _interp: &Interp<'cx, C>, - _def: DefId, - const_var: Const, - intrinsic_argument: &mut IntrinsicArguments, - ) { - if let Some(val) = self.try_read_mem_from_object(_interp, _def.clone(), const_var.clone()) { - intrinsic_argument.second_arg = Some(vec![]); - add_elements_to_intrinsic_struct(val, &mut intrinsic_argument.second_arg); - } - } - fn handle_second_arg>( &self, _interp: &Interp<'cx, C>, @@ -599,30 +599,37 @@ impl<'cx> Dataflow<'cx> for PermissionDataflow { _def: DefId, intrinsic_argument: &mut IntrinsicArguments, ) { - if let Some((defid, varid)) = self.get_defid_from_operand(_interp, operand) { - if let Some(val) = self.get_value(_def, varid) { - match val { - Value::Const(const_var) => { - self.try_insert(_interp, _def, const_var.clone(), &mut *intrinsic_argument); - } - Value::Phi(phi_var) => { - phi_var.iter().for_each(|const_val| { - self.try_insert( - _interp, - _def, - const_val.clone(), - &mut *intrinsic_argument, - ); - }); + println!("operand from the second arg {operand:?}"); + + if let Operand::Var(variable) = operand { + if let Base::Var(varid) = variable.base { + if let Some(value) = + self.get_value(_def, varid, Some(Projection::Known("method".into()))) + { + match value { + Value::Const(Const::Literal(lit)) => { + intrinsic_argument.second_arg = Some(vec![lit.to_string()]); + } + Value::Phi(phi_val) => { + intrinsic_argument.second_arg = Some( + phi_val + .iter() + .map(|data| { + if let Const::Literal(lit) = data { + return Some(lit.to_string()); + } else { + None + } + }) + .filter(|const_val| const_val != &None) + .map(|f| f.unwrap()) + .collect_vec(), + ) + } + _ => {} } - _ => {} } - } else if let Some(val) = self.def_to_class_property(_interp, _def, defid) { - intrinsic_argument.second_arg = Some(vec![]); - add_elements_to_intrinsic_struct(val, &mut intrinsic_argument.second_arg); } - } else { - // println!("found other arg {var:?}") } } @@ -649,8 +656,6 @@ impl<'cx> Dataflow<'cx> for PermissionDataflow { self.handle_second_arg(_interp, operand, _def, &mut intrinsic_argument); } - // CLEAN UP - let mut permissions_within_call: Vec = vec![]; let intrinsic_func_type = intrinsic_argument.name.unwrap(); intrinsic_argument @@ -659,7 +664,6 @@ impl<'cx> Dataflow<'cx> for PermissionDataflow { .for_each(|first_arg_vec| { if let Some(second_arg_vec) = intrinsic_argument.second_arg.clone() { first_arg_vec.iter().for_each(|first_arg| { - println!("first arg ===> {first_arg:?}"); second_arg_vec.iter().for_each(|second_arg| { if intrinsic_func_type == IntrinsicName::RequestConfluence { let permissions = check_url_for_permissions( @@ -682,7 +686,6 @@ impl<'cx> Dataflow<'cx> for PermissionDataflow { }) } else { first_arg_vec.iter().for_each(|first_arg| { - println!("first arg ===> {first_arg:?}"); if intrinsic_func_type == IntrinsicName::RequestConfluence { let permissions = check_url_for_permissions( &_interp.confluence_permission_resolver, @@ -700,98 +703,25 @@ impl<'cx> Dataflow<'cx> for PermissionDataflow { ); permissions_within_call.extend_from_slice(&permissions) } - - //HERE }) } }); - println!("permissions within call {permissions_within_call:?}"); - _interp .permissions .extend_from_slice(&permissions_within_call); - - println!("_interp permisisions {:?}", _interp.permissions); } initial_state } - fn read_class_from_object>( - &mut self, - _interp: &Interp<'cx, C>, - defid: DefId, - ) -> Option { - let def_kind = _interp.env().defs.defs.get(defid); - if let Some(id) = def_kind { - if let DefKind::GlobalObj(obj_id) = id { - let class = _interp.env().defs.classes.get(obj_id.clone()); - if let Some(class) = class { - return Some(class.clone()); - } - } - } - None - } - - fn try_read_mem_from_object>( - &self, - _interp: &Interp<'cx, C>, - _def: DefId, - const_var: Const, - ) -> Option<&Value> { - if let Const::Object(obj) = const_var { - return self.read_mem_from_object(_interp, _def, obj); - } - None - } - - fn read_mem_from_object>( - &self, - _interp: &Interp<'cx, C>, - _def: DefId, - obj: Class, - ) -> Option<&Value> { - let defid_method = obj - .pub_members - .iter() - .filter(|(mem, _)| mem == "method") - .map(|(_, defid)| defid) - .collect_vec(); - if let Some(_alt_defid) = defid_method.get(0) { - for (varid_new, varkind) in _interp.body().vars.clone().into_iter_enumerated() { - if let Some(defid) = get_defid_from_varkind(&varkind) { - if &&defid == _alt_defid { - return self.get_value(_def, varid_new); - } - } - } - } - None - } - - fn def_to_class_property>( - &self, - _interp: &Interp<'cx, C>, - _def: DefId, - defid: DefId, - ) -> Option<&Value> { - if let Some(DefKind::GlobalObj(objid)) = _interp.env().defs.defs.get(defid) { - if let Some(class) = _interp.env().defs.classes.get(objid.clone()) { - return self.read_mem_from_object(_interp, _def, class.clone()); - } - } - None - } - fn get_values_from_operand>( &self, _interp: &Interp<'cx, C>, _def: DefId, operand: &Operand, ) -> Option<&Value> { - if let Some((_, varid)) = self.get_defid_from_operand(_interp, operand) { - return self.get_value(_def, varid); + if let Some((var, varid)) = resolve_var_from_operand(operand) { + return self.get_value(_def, varid, var.projections.get(0).cloned()); } None } @@ -827,7 +757,9 @@ impl<'cx> Dataflow<'cx> for PermissionDataflow { } Operand::Var(var) => match var.base { Base::Var(varid) => { - if let Some(value) = self.get_value(def, varid) { + if let Some(value) = + self.get_value(def, varid, var.projections.get(0).cloned()) + { all_values_to_be_pushed.push(value.clone()); } else { all_values_to_be_pushed.push(Value::Unknown) @@ -856,26 +788,25 @@ impl<'cx> Dataflow<'cx> for PermissionDataflow { Inst::Expr(rvalue) => { self.transfer_rvalue(interp, def, loc, block, rvalue, initial_state) } - Inst::Assign(variable, rvalue) => { - match variable.base { + Inst::Assign(var, rvalue) => { + match var.base { Base::Var(varid) => match rvalue { Rvalue::Call(operand, _) => { - if let Some((defid, varid)) = - self.get_defid_from_operand(interp, operand) - { - interp.expecting_value.push_back((defid, (varid, defid))); + if let Some((defid, varid)) = resolve_var_from_operand(operand) { + // interp.expecting_value.push_back((defid, (varid, defid))); } - if let Some((defid, _)) = self.get_defid_from_operand(interp, operand) { - if let Some(return_value) = interp.return_value_alt.get(&defid) { - self.add_value(def, varid, return_value.clone()); - } + if let Some((var, varid)) = resolve_var_from_operand(operand) { + // if let Some(return_value) = interp.return_value_alt.get(&defid) { + // self.add_value(def, varid, return_value.clone()); + // } } } Rvalue::Read(_) => { - self.add_variable(interp, &varid, def, rvalue); + println!("\t\t within read"); + self.add_variable(interp, var, &varid, def, rvalue); } Rvalue::Template(_) => { - self.add_variable(interp, &varid, def, rvalue); + self.add_variable(interp, var, &varid, def, rvalue); } _ => {} }, @@ -904,7 +835,7 @@ impl<'cx> Dataflow<'cx> for PermissionDataflow { for (varid, varkind) in function_var.iter_enumerated() { if let VarKind::Arg(_) = varkind { if let Some(operand) = args.pop() { - self.add_value(def, varid, operand.clone()); + self.add_value(def, varid, operand.clone(), None); interp .body() .vars @@ -915,7 +846,8 @@ impl<'cx> Dataflow<'cx> for PermissionDataflow { get_defid_from_varkind(varkind), ) { if defid == defid_alt && varid_alt != varid { - self.varid_to_value.insert(varid_alt, operand.clone()); + self.varid_to_value + .insert((def, varid_alt, None), operand.clone()); } } }) @@ -934,12 +866,12 @@ impl<'cx> Dataflow<'cx> for PermissionDataflow { VarKind::Ret => { for (defid, (varid_value, defid_value)) in &interp.expecting_value { if def == defid.clone() { - if let Some(value) = self.get_value(def, varid) { - self.add_value(def, varid, value.clone()); + if let Some(value) = self.get_value(def, varid, None) { + self.add_value(def, varid, value.clone(), None); } } } - if let Some(value) = self.get_value(def, varid) { + if let Some(value) = self.get_value(def, varid, None) { interp.return_value = Some((value.clone(), def)); interp.return_value_alt.insert(def, value.clone()); } @@ -954,22 +886,39 @@ impl<'cx> Dataflow<'cx> for PermissionDataflow { fn add_variable>( &mut self, interp: &Interp<'cx, C>, + lval: &Variable, varid: &VarId, def: DefId, rvalue: &Rvalue, ) { match rvalue { Rvalue::Read(operand) => { - if let Some(value) = get_prev_value(self.get_value(def, *varid)) { - self.insert_value2(operand, varid, def, interp, Some(value)); + // transfer all of the variables + if let Operand::Var(variable) = operand { + if let Base::Var(varid_rval) = variable.base { + self.varid_to_value.clone().iter().for_each( + |((defid, varid_rval_potential, projection), value)| { + if varid_rval_potential == &varid_rval { + self.add_value(def, *varid, value.clone(), projection.clone()) + } + }, + ); + } } else { - self.insert_value2(operand, varid, def, interp, None); + if let Some(value) = get_prev_value(self.get_value( + def, + *varid, + lval.projections.get(0).cloned(), + )) { + self.insert_value(operand, lval, varid, def, interp, Some(value)); + } else { + self.insert_value(operand, lval, varid, def, interp, None); + } } } Rvalue::Template(template) => { let quasis_joined = template.quasis.join(""); - let (mut original_consts, mut all_potential_values) = - (vec![""], vec![String::from("")]); + let mut all_potential_values = vec![String::from("")]; if template.exprs.len() == 0 { all_potential_values.push(quasis_joined.clone()); } else if template.exprs.len() <= 3 { @@ -999,7 +948,6 @@ impl<'cx> Dataflow<'cx> for PermissionDataflow { } } - println!("all potential values {all_potential_values:?} {all_values:?}"); all_potential_values = all_values; } if all_potential_values.len() > 1 { @@ -1007,15 +955,14 @@ impl<'cx> Dataflow<'cx> for PermissionDataflow { .into_iter() .map(|value| Const::Literal(value.clone())) .collect::>(); - - println!("consts == {consts:?}"); let value = Value::Phi(consts); - self.add_value(def, *varid, value.clone()); + self.add_value(def, *varid, value.clone(), None); } else if all_potential_values.len() == 1 { self.add_value( def, *varid, Value::Const(Const::Literal(all_potential_values.get(0).unwrap().clone())), + None, ); } } @@ -1043,11 +990,11 @@ impl<'cx> Dataflow<'cx> for PermissionDataflow { _ => {} } self.varid_to_value - .insert(*varid, return_value_from_string(new_vals)); + .insert((def, *varid, None), return_value_from_string(new_vals)); } else if let Some(val1) = val1 { - self.add_value(def, *varid, val1); + self.add_value(def, *varid, val1, None); } else if let Some(val2) = val2 { - self.add_value(def, *varid, val2); + self.add_value(def, *varid, val2, None); } } } @@ -1055,9 +1002,10 @@ impl<'cx> Dataflow<'cx> for PermissionDataflow { } } - fn insert_value2>( + fn insert_value>( &mut self, operand: &Operand, + lval: &Variable, varid: &VarId, def: DefId, interp: &Interp<'cx, C>, @@ -1071,12 +1019,12 @@ impl<'cx> Dataflow<'cx> for PermissionDataflow { let mut all_values = prev_values.clone(); all_values.push(const_value); let value = Value::Phi(all_values); - self.add_value(def, *varid, value); + self.add_value(def, *varid, value, lval.projections.get(0).cloned()); } } else { if let Some(lit_value) = convert_operand_to_raw(operand) { let value = Value::Const(Const::Literal(lit_value)); - self.add_value(def, *varid, value); + self.add_value(def, *varid, value, lval.projections.get(0).cloned()); } } } @@ -1092,15 +1040,28 @@ impl<'cx> Dataflow<'cx> for PermissionDataflow { let mut all_values = prev_values.clone(); all_values.push(const_value); let value = Value::Phi(all_values); - self.add_value(def, *varid, value); + self.add_value( + def, + *varid, + value, + lval.projections.get(0).cloned(), + ); } else { let value = Value::Const(Const::Object(class)); - self.add_value(def, *varid, value); + self.add_value( + def, + *varid, + value, + lval.projections.get(0).cloned(), + ); } } } else { - if let Some(potential_value) = self.get_value(def, prev_varid) { - self.varid_to_value.insert(*varid, potential_value.clone()); + if let Some(potential_value) = self.get_value(def, prev_varid, None) { + self.varid_to_value.insert( + (def, *varid, var.projections.get(0).cloned()), + potential_value.clone(), + ); } } } @@ -1124,22 +1085,15 @@ impl<'cx> Dataflow<'cx> for PermissionDataflow { } } -pub(crate) fn resolve_var_from_operand(operand: &Operand) -> Option { +pub(crate) fn resolve_var_from_operand(operand: &Operand) -> Option<(Variable, VarId)> { if let Operand::Var(var) = operand { if let Base::Var(varid) = var.base { - return Some(varid); + return Some((var.clone(), varid)); } } None } -fn resolve_literal_from_operand(operand: &Operand) -> Option { - if let Operand::Lit(lit) = operand { - return Some(lit.clone()); - } - None -} - fn add_const_to_val_vec(val: &Value, const_val: &Const, vals: &mut Vec) { match val { Value::Const(Const::Literal(lit)) => { @@ -1326,10 +1280,6 @@ impl<'cx> Checker<'cx> for PermissionChecker { state: &Self::State, operands: Option>, ) -> ControlFlow<(), Self::State> { - for permission in &interp.permissions { - self.declared_permissions.remove(permission); - self.used_permissions.insert(permission.clone()); - } ControlFlow::Continue(*state) } } diff --git a/crates/forge_analyzer/src/definitions.rs b/crates/forge_analyzer/src/definitions.rs index 4e3f5ac4..ac86d8bb 100644 --- a/crates/forge_analyzer/src/definitions.rs +++ b/crates/forge_analyzer/src/definitions.rs @@ -1340,25 +1340,12 @@ impl<'cx> FunctionAnalyzer<'cx> { ); } Prop::KeyValue(KeyValueProp { key, value }) => { - let span = match key { - PropName::BigInt(bigint) => bigint.span, - PropName::Computed(computed) => computed.span, - PropName::Ident(ident) => ident.span, - PropName::Num(num) => num.span, - PropName::Str(str) => str.span, - }; let lowered_value = self.lower_expr(&value, None); - let next_key = self.res.get_or_overwrite_sym( - (key.as_symbol().unwrap_or_default(), span.ctxt), - self.module, - DefKind::Arg, - ); - let mut lowered_var = self.body.coerce_to_lval( + let lowered_var = self.body.coerce_to_lval( self.block, lowered_value.clone(), - Some(next_key), + None, ); - if let Base::Var(varid) = lowered_var.base {} let rval = Rvalue::Read(lowered_value); match lowered_var.base { @@ -2941,7 +2928,6 @@ impl Environment { ) { if let DefKind::Class(class) = self.def_mut(class_def) { if let PropName::Ident(ident) = &n { - println!("adding class method --"); class.pub_members.push((ident.sym.to_owned(), owner)); } } diff --git a/crates/forge_analyzer/src/interp.rs b/crates/forge_analyzer/src/interp.rs index 44dcaf14..c3991ef1 100644 --- a/crates/forge_analyzer/src/interp.rs +++ b/crates/forge_analyzer/src/interp.rs @@ -26,7 +26,7 @@ use crate::{ definitions::{Class, Const, DefId, Environment, Value}, ir::{ Base, BasicBlock, BasicBlockId, Body, Inst, Intrinsic, Location, Operand, Rvalue, - Successors, VarId, STARTING_BLOCK, + Successors, VarId, Variable, STARTING_BLOCK, }, worklist::WorkList, }; @@ -160,15 +160,17 @@ pub trait Dataflow<'cx>: Sized { fn add_variable>( &mut self, interp: &Interp<'cx, C>, + lval: &Variable, varid: &VarId, def: DefId, rvalue: &Rvalue, ) { } - fn insert_value2>( + fn insert_value>( &mut self, operand: &Operand, + lval: &Variable, varid: &VarId, def: DefId, interp: &Interp<'cx, C>, @@ -254,21 +256,6 @@ pub trait Dataflow<'cx>: Sized { None } - fn get_defid_from_operand>( - &self, - _interp: &Interp<'cx, C>, - operand: &Operand, - ) -> Option<(DefId, VarId)> { - if let Some(varid) = resolve_var_from_operand(operand) { - if let Some(varkind) = _interp.body().vars.get(varid) { - if let Some(defid) = get_defid_from_varkind(varkind) { - return Some((defid, varid)); - } - } - } - None - } - fn insert_with_existing_value>( &mut self, operand: &Operand, diff --git a/crates/fsrt/src/main.rs b/crates/fsrt/src/main.rs index 86309e5f..0581879a 100644 --- a/crates/fsrt/src/main.rs +++ b/crates/fsrt/src/main.rs @@ -63,6 +63,10 @@ struct Args { #[arg(short, long)] out: Option, + // Run the permission checker + #[arg(short, long)] + check_permissions: bool, + /// The directory to scan. Assumes there is a `manifest.ya?ml` file in the top level /// directory, and that the source code is located in `src/` #[arg(name = "DIRS", value_hint = ValueHint::DirPath)] @@ -73,6 +77,7 @@ struct Args { struct Opts { dump_cfg: bool, dump_callgraph: bool, + check_permissions: bool, appkey: Option, out: Option, } @@ -195,6 +200,10 @@ fn scan_directory(dir: PathBuf, function: Option<&str>, opts: Opts) -> Result, opts: Opts) -> Result, opts: Opts) -> Result { let mut checker = AuthenticateChecker::new(); @@ -247,27 +256,31 @@ fn scan_directory(dir: PathBuf, function: Option<&str>, opts: Opts) -> Result 0 { - reporter.add_vulnerabilities( - vec![PermissionVuln::new(HashSet::::from_iter( - unused_permissions.cloned().into_iter(), - ))] - .into_iter(), - ); + if run_permission_checker { + let unused_permissions = permissions_declared.difference(&all_used_permissions); + if unused_permissions.clone().count() > 0 { + reporter.add_vulnerabilities( + vec![PermissionVuln::new(HashSet::::from_iter( + unused_permissions.cloned().into_iter(), + ))] + .into_iter(), + ); + } } let report = serde_json::to_string(&reporter.into_report()).into_diagnostic()?; @@ -291,6 +304,7 @@ fn main() -> Result<()> { let opts = Opts { dump_callgraph: args.callgraph, dump_cfg: args.cfg, + check_permissions: args.check_permissions, out: args.out, appkey: args.appkey, }; diff --git a/test-apps/jira-damn-vulnerable-forge-app/src/utils.js b/test-apps/jira-damn-vulnerable-forge-app/src/utils.js index 063558d7..db56e172 100755 --- a/test-apps/jira-damn-vulnerable-forge-app/src/utils.js +++ b/test-apps/jira-damn-vulnerable-forge-app/src/utils.js @@ -9,7 +9,7 @@ import my_function from "./export_default2.js" export async function fetchIssueSummary(issueIdOrKey, url) { let obj = { - method: 'POST', + method: 'DELETE', bananas: 'apple', headers: { // Accept: 'application/json', From cdff5c8236b00a407d97a708a5204e411be0bbbc Mon Sep 17 00:00:00 2001 From: James Gersbach Date: Mon, 7 Aug 2023 14:02:32 -0700 Subject: [PATCH 144/517] cleaning up codebase --- crates/forge_analyzer/src/checkers.rs | 227 ++++++------------ crates/forge_analyzer/src/definitions.rs | 16 ++ crates/forge_analyzer/src/interp.rs | 11 +- crates/forge_analyzer/src/ir.rs | 3 + crates/forge_analyzer/src/utils.rs | 126 +++++++++- .../src/permissions_resolver.rs | 3 +- .../src/IssuePanelApp.jsx | 1 + .../src/utils.js | 11 +- 8 files changed, 225 insertions(+), 173 deletions(-) diff --git a/crates/forge_analyzer/src/checkers.rs b/crates/forge_analyzer/src/checkers.rs index 8cdd4c42..0852e982 100644 --- a/crates/forge_analyzer/src/checkers.rs +++ b/crates/forge_analyzer/src/checkers.rs @@ -30,6 +30,10 @@ use crate::{ Projection, Rvalue, Successors, VarId, VarKind, Variable, }, reporter::{IntoVuln, Reporter, Severity, Vulnerability}, + utils::{ + add_const_to_val_vec, add_elements_to_intrinsic_struct, convert_operand_to_raw, + get_defid_from_varkind, get_prev_value, get_str_from_operand, resolve_var_from_operand, + }, worklist::WorkList, }; @@ -82,6 +86,7 @@ impl<'cx> Dataflow<'cx> for AuthorizeDataflow { debug!("authorize intrinsic found"); AuthorizeState::Yes } + Intrinsic::JWTSign(_) => initial_state, Intrinsic::Fetch => initial_state, Intrinsic::ApiCall(_) => initial_state, Intrinsic::SafeCall(_) => initial_state, @@ -255,6 +260,7 @@ impl<'cx> Checker<'cx> for AuthZChecker { self.vulns.push(vuln); ControlFlow::Break(()) } + Intrinsic::JWTSign(_) => ControlFlow::Continue(*state), Intrinsic::ApiCall(_) => ControlFlow::Continue(*state), Intrinsic::SafeCall(_) => ControlFlow::Continue(*state), Intrinsic::EnvRead => ControlFlow::Continue(*state), @@ -313,6 +319,7 @@ impl<'cx> Dataflow<'cx> for AuthenticateDataflow { debug!("authenticated"); Authenticated::Yes } + Intrinsic::JWTSign(_) => initial_state, Intrinsic::ApiCall(_) => initial_state, Intrinsic::SafeCall(_) => initial_state, } @@ -410,6 +417,7 @@ impl<'cx> Checker<'cx> for AuthenticateChecker { self.vulns.push(vuln); ControlFlow::Break(()) } + Intrinsic::JWTSign(_) => ControlFlow::Continue(*state), Intrinsic::ApiCall(_) => ControlFlow::Continue(*state), Intrinsic::SafeCall(_) => ControlFlow::Continue(*state), } @@ -525,6 +533,44 @@ impl PermissionDataflow { } } + fn handle_second_arg( + &self, + operand: &Operand, + _def: DefId, + intrinsic_argument: &mut IntrinsicArguments, + ) { + if let Operand::Var(variable) = operand { + if let Base::Var(varid) = variable.base { + if let Some(value) = + self.get_value(_def, varid, Some(Projection::Known("method".into()))) + { + match value { + Value::Const(Const::Literal(lit)) => { + intrinsic_argument.second_arg = Some(vec![lit.to_string()]); + } + Value::Phi(phi_val) => { + intrinsic_argument.second_arg = Some( + phi_val + .iter() + .map(|data| { + if let Const::Literal(lit) = data { + return Some(lit.to_string()); + } else { + None + } + }) + .filter(|const_val| const_val != &None) + .map(|f| f.unwrap()) + .collect_vec(), + ) + } + _ => {} + } + } + } + } + } + fn add_value( &mut self, defid_block: DefId, @@ -532,7 +578,7 @@ impl PermissionDataflow { value: Value, projection: Option, ) { - println!("varid from {varid:?} -- {projection:?} -- {value:?}"); + // println!("varid from {varid:?} -- {projection:?} -- {value:?}"); self.varid_to_value .insert((defid_block, varid, projection), value); } @@ -592,47 +638,6 @@ impl<'cx> Dataflow<'cx> for PermissionDataflow { } } - fn handle_second_arg>( - &self, - _interp: &Interp<'cx, C>, - operand: &Operand, - _def: DefId, - intrinsic_argument: &mut IntrinsicArguments, - ) { - println!("operand from the second arg {operand:?}"); - - if let Operand::Var(variable) = operand { - if let Base::Var(varid) = variable.base { - if let Some(value) = - self.get_value(_def, varid, Some(Projection::Known("method".into()))) - { - match value { - Value::Const(Const::Literal(lit)) => { - intrinsic_argument.second_arg = Some(vec![lit.to_string()]); - } - Value::Phi(phi_val) => { - intrinsic_argument.second_arg = Some( - phi_val - .iter() - .map(|data| { - if let Const::Literal(lit) = data { - return Some(lit.to_string()); - } else { - None - } - }) - .filter(|const_val| const_val != &None) - .map(|f| f.unwrap()) - .collect_vec(), - ) - } - _ => {} - } - } - } - } - } - fn transfer_intrinsic>( &mut self, _interp: &mut Interp<'cx, C>, @@ -653,9 +658,11 @@ impl<'cx> Dataflow<'cx> for PermissionDataflow { self.handle_first_arg(operand, _def, &mut intrinsic_argument); } if let Some(operand) = second { - self.handle_second_arg(_interp, operand, _def, &mut intrinsic_argument); + self.handle_second_arg(operand, _def, &mut intrinsic_argument); } + println!("intrinsic argument {intrinsic_argument:?}"); + let mut permissions_within_call: Vec = vec![]; let intrinsic_func_type = intrinsic_argument.name.unwrap(); intrinsic_argument @@ -707,6 +714,8 @@ impl<'cx> Dataflow<'cx> for PermissionDataflow { } }); + println!("permissions found {permissions_within_call:?}"); + _interp .permissions .extend_from_slice(&permissions_within_call); @@ -802,7 +811,6 @@ impl<'cx> Dataflow<'cx> for PermissionDataflow { } } Rvalue::Read(_) => { - println!("\t\t within read"); self.add_variable(interp, var, &varid, def, rvalue); } Rvalue::Template(_) => { @@ -931,27 +939,37 @@ impl<'cx> Dataflow<'cx> for PermissionDataflow { .map(|value| value.to_owned() + &quasis.to_string()) .collect(); } - - let mut new_values__ = vec![]; + let mut new_values = vec![]; let values = self.get_str_from_expr(expr, def); if values.len() > 0 { for str_value in values { for value in &all_values { if let Some(str) = &str_value { - new_values__.push(value.clone() + &str) + new_values.push(value.clone() + &str) + } else { + new_values.push(value.clone()) } } } - - all_values = new_values__ + all_values = new_values } } + if template.quasis.len() > template.exprs.len() { + all_values = all_values + .iter() + .map(|value| { + value.to_owned() + &template.quasis.last().unwrap().to_string() + }) + .collect(); + } + all_potential_values = all_values; } if all_potential_values.len() > 1 { let consts = all_potential_values + .clone() .into_iter() .map(|value| Const::Literal(value.clone())) .collect::>(); @@ -1085,97 +1103,6 @@ impl<'cx> Dataflow<'cx> for PermissionDataflow { } } -pub(crate) fn resolve_var_from_operand(operand: &Operand) -> Option<(Variable, VarId)> { - if let Operand::Var(var) = operand { - if let Base::Var(varid) = var.base { - return Some((var.clone(), varid)); - } - } - None -} - -fn add_const_to_val_vec(val: &Value, const_val: &Const, vals: &mut Vec) { - match val { - Value::Const(Const::Literal(lit)) => { - if let Const::Literal(lit2) = const_val { - vals.push(lit.to_owned() + &lit2); - } - } - Value::Phi(phi_val2) => phi_val2.iter().for_each(|val2| { - if let (Const::Literal(lit1), Const::Literal(lit2)) = (&const_val, val2) { - vals.push(lit1.to_owned() + lit2); - } - }), - _ => {} - } -} - -pub(crate) fn get_defid_from_varkind(varkind: &VarKind) -> Option { - match varkind { - VarKind::GlobalRef(defid) => Some(defid.clone()), - VarKind::LocalDef(defid) => Some(defid.clone()), - VarKind::Arg(defid) => Some(defid.clone()), - VarKind::AnonClosure(defid) => Some(defid.clone()), - VarKind::Temp { parent } => parent.clone(), - _ => None, - } -} - -fn convert_operand_to_raw(operand: &Operand) -> Option { - if let Operand::Lit(lit) = operand { - convert_lit_to_raw(lit) - } else { - None - } -} - -fn convert_lit_to_raw(lit: &Literal) -> Option { - match lit { - Literal::BigInt(bigint) => Some(bigint.to_string()), - Literal::Number(num) => Some(num.to_string()), - Literal::Str(str) => Some(str.to_string()), - _ => None, - } -} - -fn get_str_from_operand(operand: &Operand) -> Option { - if let Operand::Lit(lit) = operand { - if let Literal::Str(str) = lit { - return Some(str.to_string()); - } - } - None -} - -fn add_elements_to_intrinsic_struct(value: &Value, args: &mut Option>) { - match value { - Value::Const(const_value) => { - if let Const::Literal(literal) = const_value { - args.as_mut().unwrap().push(literal.clone()); - } - } - Value::Phi(phi_value) => { - for value in phi_value { - if let Const::Literal(literal) = value { - args.as_mut().unwrap().push(literal.clone()); - } - } - } - _ => {} - } -} - -fn get_prev_value(value: Option<&Value>) -> Option> { - if let Some(value) = value { - return match value { - Value::Const(const_value) => Some(vec![const_value.clone()]), - Value::Phi(phi_value) => Some(phi_value.clone()), - _ => None, - }; - } - None -} - fn return_value_from_string(values: Vec) -> Value { // assert!(values.len() > 0); if values.len() == 1 { @@ -1264,26 +1191,6 @@ impl JoinSemiLattice for PermissionTest { } } -impl<'cx> Checker<'cx> for PermissionChecker { - type State = PermissionTest; - type Dataflow = PermissionDataflow; - type Vuln = PermissionVuln; - - fn visit(&mut self) -> bool { - self.visit - } - - fn visit_intrinsic( - &mut self, - interp: &Interp<'cx, Self>, - intrinsic: &'cx Intrinsic, - state: &Self::State, - operands: Option>, - ) -> ControlFlow<(), Self::State> { - ControlFlow::Continue(*state) - } -} - #[derive(Debug)] pub struct PermissionVuln { unused_permissions: HashSet, diff --git a/crates/forge_analyzer/src/definitions.rs b/crates/forge_analyzer/src/definitions.rs index ac86d8bb..d2684e2d 100644 --- a/crates/forge_analyzer/src/definitions.rs +++ b/crates/forge_analyzer/src/definitions.rs @@ -523,6 +523,13 @@ pub enum IntrinsicName { Other, } +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct JWTSigningDetails { + package: String, + signing_function: String, + position_arg: i8, +} + struct Lowerer<'cx> { res: &'cx mut Environment, curr_mod: ModId, @@ -881,6 +888,15 @@ impl<'cx> FunctionAnalyzer<'cx> { ApiCallKind::Authorize => Some(Intrinsic::Authorize(function_name)), } } + [PropPath::Def(def), ref authn @ .., PropPath::Static(ref last)] + if *last == *"sign" + || *last == *"requestConfluence" + && Some(&ImportKind::Default) + == self.res.as_foreign_import(def, "jsonwebtoken") => + { + println!("found jwt "); + Some(Intrinsic::Authorize(IntrinsicName::RequestJira)) + } [PropPath::Def(def), PropPath::Static(ref s), ..] if is_storage_read(s) => { match self.res.as_foreign_import(def, "@forge/api") { Some(ImportKind::Named(ref name)) if *name == *"storage" => { diff --git a/crates/forge_analyzer/src/interp.rs b/crates/forge_analyzer/src/interp.rs index c3991ef1..d68f7b27 100644 --- a/crates/forge_analyzer/src/interp.rs +++ b/crates/forge_analyzer/src/interp.rs @@ -22,12 +22,13 @@ use swc_core::ecma::atoms::JsWord; use tracing::{debug, info, instrument, warn}; use crate::{ - checkers::{get_defid_from_varkind, resolve_var_from_operand, IntrinsicArguments}, + checkers::IntrinsicArguments, definitions::{Class, Const, DefId, Environment, Value}, ir::{ Base, BasicBlock, BasicBlockId, Body, Inst, Intrinsic, Location, Operand, Rvalue, Successors, VarId, Variable, STARTING_BLOCK, }, + utils::{get_defid_from_varkind, resolve_var_from_operand}, worklist::WorkList, }; @@ -324,10 +325,6 @@ pub trait Checker<'cx>: Sized { type Vuln: Display + WithCallStack; type Dataflow: Dataflow<'cx, State = Self::State>; - fn visit(&mut self) -> bool { - true - } - fn visit_intrinsic( &mut self, interp: &Interp<'cx, Self>, @@ -688,9 +685,7 @@ impl<'cx, C: Checker<'cx>> Interp<'cx, C> { .ok_or_else(|| Error::NotAFunction(name.to_owned()))?; self.set_body(body); self.run(resolved_def); - if checker.visit() { - checker.visit_body(self, resolved_def, body, &C::State::BOTTOM); - } + checker.visit_body(self, resolved_def, body, &C::State::BOTTOM); Ok(()) } diff --git a/crates/forge_analyzer/src/ir.rs b/crates/forge_analyzer/src/ir.rs index 57a64af3..2f82075c 100644 --- a/crates/forge_analyzer/src/ir.rs +++ b/crates/forge_analyzer/src/ir.rs @@ -36,6 +36,7 @@ use crate::definitions::DefId; use crate::definitions::DefKind; use crate::definitions::Environment; use crate::definitions::IntrinsicName; +use crate::definitions::JWTSigningDetails; use crate::definitions::Value; pub const STARTING_BLOCK: BasicBlockId = BasicBlockId(0); @@ -73,6 +74,7 @@ pub enum Intrinsic { Fetch, ApiCall(IntrinsicName), SafeCall(IntrinsicName), + JWTSign(JWTSigningDetails), EnvRead, StorageRead, } @@ -759,6 +761,7 @@ impl fmt::Display for Intrinsic { match *self { Intrinsic::Fetch => write!(f, "fetch"), Intrinsic::Authorize(_) => write!(f, "authorize"), + Intrinsic::JWTSign(_) => write!(f, "jwt sign"), Intrinsic::ApiCall(_) => write!(f, "api call"), Intrinsic::SafeCall(_) => write!(f, "safe api call"), Intrinsic::EnvRead => write!(f, "env read"), diff --git a/crates/forge_analyzer/src/utils.rs b/crates/forge_analyzer/src/utils.rs index 897622e0..23051592 100644 --- a/crates/forge_analyzer/src/utils.rs +++ b/crates/forge_analyzer/src/utils.rs @@ -1,4 +1,9 @@ -use crate::definitions::CalleeRef; +use crate::{ + definitions::{CalleeRef, Const, DefId, Value}, + ir::{Base, Literal, Operand, VarId, VarKind, Variable}, +}; +use forge_permission_resolver::permissions_resolver::RequestType; +use itertools::Itertools; use swc_core::ecma::ast::{Expr, MemberProp}; pub fn calls_method(n: CalleeRef<'_>, name: &str) -> bool { @@ -9,3 +14,122 @@ pub fn calls_method(n: CalleeRef<'_>, name: &str) -> bool { } false } + +pub fn resolve_var_from_operand(operand: &Operand) -> Option<(Variable, VarId)> { + if let Operand::Var(var) = operand { + if let Base::Var(varid) = var.base { + return Some((var.clone(), varid)); + } + } + None +} + +pub fn add_const_to_val_vec(val: &Value, const_val: &Const, vals: &mut Vec) { + match val { + Value::Const(Const::Literal(lit)) => { + if let Const::Literal(lit2) = const_val { + vals.push(lit.to_owned() + &lit2); + } + } + Value::Phi(phi_val2) => phi_val2.iter().for_each(|val2| { + if let (Const::Literal(lit1), Const::Literal(lit2)) = (&const_val, val2) { + vals.push(lit1.to_owned() + lit2); + } + }), + _ => {} + } +} + +pub fn get_defid_from_varkind(varkind: &VarKind) -> Option { + match varkind { + VarKind::GlobalRef(defid) => Some(defid.clone()), + VarKind::LocalDef(defid) => Some(defid.clone()), + VarKind::Arg(defid) => Some(defid.clone()), + VarKind::AnonClosure(defid) => Some(defid.clone()), + VarKind::Temp { parent } => parent.clone(), + _ => None, + } +} + +pub fn convert_operand_to_raw(operand: &Operand) -> Option { + if let Operand::Lit(lit) = operand { + convert_lit_to_raw(lit) + } else { + None + } +} + +pub fn convert_lit_to_raw(lit: &Literal) -> Option { + match lit { + Literal::BigInt(bigint) => Some(bigint.to_string()), + Literal::Number(num) => Some(num.to_string()), + Literal::Str(str) => Some(str.to_string()), + _ => None, + } +} + +pub fn get_str_from_operand(operand: &Operand) -> Option { + if let Operand::Lit(lit) = operand { + if let Literal::Str(str) = lit { + return Some(str.to_string()); + } + } + None +} + +pub fn add_elements_to_intrinsic_struct(value: &Value, args: &mut Option>) { + match value { + Value::Const(const_value) => { + if let Const::Literal(literal) = const_value { + args.as_mut().unwrap().push(literal.clone()); + } + } + Value::Phi(phi_value) => { + for value in phi_value { + if let Const::Literal(literal) = value { + args.as_mut().unwrap().push(literal.clone()); + } + } + } + _ => {} + } +} + +pub fn get_prev_value(value: Option<&Value>) -> Option> { + if let Some(value) = value { + return match value { + Value::Const(const_value) => Some(vec![const_value.clone()]), + Value::Phi(phi_value) => Some(phi_value.clone()), + _ => None, + }; + } + None +} + +pub fn return_value_from_string(values: Vec) -> Value { + // assert!(values.len() > 0); + if values.len() == 1 { + return Value::Const(Const::Literal(values.get(0).unwrap().clone())); + } else { + return Value::Phi( + values + .iter() + .map(|val_string| Const::Literal(val_string.clone())) + .collect_vec(), + ); + } +} + +pub fn trnaslate_request_type(request_type: Option<&str>) -> RequestType { + if let Some(request_type) = request_type { + match request_type { + "PATCH" => RequestType::Patch, + "PUT" => RequestType::Put, + "DELETE" => RequestType::Delete, + "POST" => RequestType::Post, + _ => RequestType::Get, + } + } else { + return RequestType::Get; + } +} diff --git a/crates/forge_permission_resolver/src/permissions_resolver.rs b/crates/forge_permission_resolver/src/permissions_resolver.rs index d13fc7a9..1aa28fa3 100644 --- a/crates/forge_permission_resolver/src/permissions_resolver.rs +++ b/crates/forge_permission_resolver/src/permissions_resolver.rs @@ -67,7 +67,8 @@ pub fn check_url_for_permissions( request: RequestType, url: &str, ) -> Vec { - // sort by the length of regex + println!("check_url_for_permissions {request:?} {url:?}"); + let mut length_of_regex = endpoint_regex .iter() .map(|(string, regex)| (regex.as_str().len(), &*string)) diff --git a/test-apps/jira-damn-vulnerable-forge-app/src/IssuePanelApp.jsx b/test-apps/jira-damn-vulnerable-forge-app/src/IssuePanelApp.jsx index 9e9b8cbe..553d69c6 100755 --- a/test-apps/jira-damn-vulnerable-forge-app/src/IssuePanelApp.jsx +++ b/test-apps/jira-damn-vulnerable-forge-app/src/IssuePanelApp.jsx @@ -9,6 +9,7 @@ export const IssuePanelApp = () => { } const { issueId } = platformContext; + writeComment(); const writeCommentFunction = () => { writeComment(issueId, 'Overwrite') diff --git a/test-apps/jira-damn-vulnerable-forge-app/src/utils.js b/test-apps/jira-damn-vulnerable-forge-app/src/utils.js index db56e172..bcc27c90 100755 --- a/test-apps/jira-damn-vulnerable-forge-app/src/utils.js +++ b/test-apps/jira-damn-vulnerable-forge-app/src/utils.js @@ -1,4 +1,6 @@ import api, { route } from '@forge/api'; +import jwt from "jsonwebtoken"; + import {testFunctionFromTestFile} from './testfile'; import module_exports_func from './moduleex.js' import {func_from_exports, diffunc} from "./exportse.js" @@ -9,13 +11,16 @@ import my_function from "./export_default2.js" export async function fetchIssueSummary(issueIdOrKey, url) { let obj = { - method: 'DELETE', + method: 'POST', bananas: 'apple', headers: { // Accept: 'application/json', }, }; + var token = jwt.sign({ foo: 'bar' }, 'secret_token'); + + module_exports_func() func_from_exports() another_export() @@ -39,7 +44,7 @@ export async function fetchIssueSummary(issueIdOrKey, url) { const resp = await api .asApp() - .requestJira(get_route(), obj); + .requestJira(a_url, obj); const data = await resp.json(); console.log(JSON.stringify(data)); return data['fields']['summary']; @@ -64,7 +69,7 @@ export async function writeComment(issueIdOrKey, comment) { const resp = await api .asApp() .requestJira(route`/rest/api/3/issue/${issueIdOrKey}/comment`, { - method: 'POST', + method: 'DELETE', headers: { Accept: 'application/json', 'Content-Type': 'application/json', From 2e0db66eea22138a100906007ec28b02a47e4369 Mon Sep 17 00:00:00 2001 From: James Gersbach Date: Mon, 7 Aug 2023 14:52:55 -0700 Subject: [PATCH 145/517] failed attemp to put the definition analysis into the interp --- crates/forge_analyzer/src/checkers.rs | 293 ++++++++++++-------------- crates/forge_analyzer/src/interp.rs | 71 +++++-- crates/forge_analyzer/src/ir.rs | 4 +- 3 files changed, 190 insertions(+), 178 deletions(-) diff --git a/crates/forge_analyzer/src/checkers.rs b/crates/forge_analyzer/src/checkers.rs index 0852e982..66c5ba97 100644 --- a/crates/forge_analyzer/src/checkers.rs +++ b/crates/forge_analyzer/src/checkers.rs @@ -32,7 +32,7 @@ use crate::{ reporter::{IntoVuln, Reporter, Severity, Vulnerability}, utils::{ add_const_to_val_vec, add_elements_to_intrinsic_struct, convert_operand_to_raw, - get_defid_from_varkind, get_prev_value, get_str_from_operand, resolve_var_from_operand, + get_defid_from_varkind, get_prev_value, get_str_from_operand, resolve_var_from_operand, return_value_from_string, }, worklist::WorkList, }; @@ -511,121 +511,6 @@ pub struct IntrinsicArguments { second_arg: Option>, } -impl PermissionDataflow { - fn handle_first_arg( - &self, - operand: &Operand, - _def: DefId, - intrinsic_argument: &mut IntrinsicArguments, - ) { - match operand { - Operand::Lit(lit) => { - intrinsic_argument.first_arg = Some(vec![lit.to_string()]); - } - Operand::Var(var) => { - if let Base::Var(varid) = var.base { - if let Some(value) = self.get_value(_def, varid, None) { - intrinsic_argument.first_arg = Some(vec![]); - add_elements_to_intrinsic_struct(value, &mut intrinsic_argument.first_arg); - } - } - } - } - } - - fn handle_second_arg( - &self, - operand: &Operand, - _def: DefId, - intrinsic_argument: &mut IntrinsicArguments, - ) { - if let Operand::Var(variable) = operand { - if let Base::Var(varid) = variable.base { - if let Some(value) = - self.get_value(_def, varid, Some(Projection::Known("method".into()))) - { - match value { - Value::Const(Const::Literal(lit)) => { - intrinsic_argument.second_arg = Some(vec![lit.to_string()]); - } - Value::Phi(phi_val) => { - intrinsic_argument.second_arg = Some( - phi_val - .iter() - .map(|data| { - if let Const::Literal(lit) = data { - return Some(lit.to_string()); - } else { - None - } - }) - .filter(|const_val| const_val != &None) - .map(|f| f.unwrap()) - .collect_vec(), - ) - } - _ => {} - } - } - } - } - } - - fn add_value( - &mut self, - defid_block: DefId, - varid: VarId, - value: Value, - projection: Option, - ) { - // println!("varid from {varid:?} -- {projection:?} -- {value:?}"); - self.varid_to_value - .insert((defid_block, varid, projection), value); - } - - fn get_value( - &self, - defid_block: DefId, - varid: VarId, - projection: Option, - ) -> Option<&Value> { - self.varid_to_value.get(&(defid_block, varid, projection)) - } - - fn get_str_from_expr(&self, expr: &Operand, def: DefId) -> Vec> { - if let Some(str) = get_str_from_operand(expr) { - return vec![Some(str)]; - } else if let Operand::Var(var) = expr { - if let Base::Var(varid) = var.base { - let value = self.get_value(def, varid, None); - if let Some(value) = value { - match value { - Value::Const(const_val) => { - if let Const::Literal(str) = const_val { - return vec![Some(str.clone())]; - } - } - Value::Phi(phi_val) => { - return phi_val - .iter() - .map(|const_val| { - if let Const::Literal(str) = const_val { - Some(str.clone()) - } else { - None - } - }) - .collect_vec(); - } - _ => {} - } - } - } - } - vec![None] - } -} - impl<'cx> Dataflow<'cx> for PermissionDataflow { type State = PermissionTest; @@ -655,10 +540,10 @@ impl<'cx> Dataflow<'cx> for PermissionDataflow { intrinsic_argument.name = Some(name.clone()); let (first, second) = (operands.get(0), operands.get(1)); if let Some(operand) = first { - self.handle_first_arg(operand, _def, &mut intrinsic_argument); + self.handle_first_arg(_interp, operand, _def, &mut intrinsic_argument); } if let Some(operand) = second { - self.handle_second_arg(operand, _def, &mut intrinsic_argument); + self.handle_second_arg(_interp, operand, _def, &mut intrinsic_argument); } println!("intrinsic argument {intrinsic_argument:?}"); @@ -725,12 +610,12 @@ impl<'cx> Dataflow<'cx> for PermissionDataflow { fn get_values_from_operand>( &self, - _interp: &Interp<'cx, C>, + _interp: &mut Interp<'cx, C>, _def: DefId, operand: &Operand, ) -> Option<&Value> { if let Some((var, varid)) = resolve_var_from_operand(operand) { - return self.get_value(_def, varid, var.projections.get(0).cloned()); + return None // return _interp.get_value(_def, varid, var.projections.get(0).cloned()); } None } @@ -767,7 +652,7 @@ impl<'cx> Dataflow<'cx> for PermissionDataflow { Operand::Var(var) => match var.base { Base::Var(varid) => { if let Some(value) = - self.get_value(def, varid, var.projections.get(0).cloned()) + interp.get_value(def, varid, var.projections.get(0).cloned()) { all_values_to_be_pushed.push(value.clone()); } else { @@ -843,7 +728,7 @@ impl<'cx> Dataflow<'cx> for PermissionDataflow { for (varid, varkind) in function_var.iter_enumerated() { if let VarKind::Arg(_) = varkind { if let Some(operand) = args.pop() { - self.add_value(def, varid, operand.clone(), None); + interp.add_value(def, varid, operand.clone(), None); interp .body() .vars @@ -869,19 +754,19 @@ impl<'cx> Dataflow<'cx> for PermissionDataflow { state = self.transfer_inst(interp, def, loc, block, inst, state); } - for (varid, varkind) in interp.body().vars.iter_enumerated() { + for (varid, varkind) in interp.body().vars.clone().iter_enumerated() { match varkind { VarKind::Ret => { for (defid, (varid_value, defid_value)) in &interp.expecting_value { if def == defid.clone() { - if let Some(value) = self.get_value(def, varid, None) { - self.add_value(def, varid, value.clone(), None); + if let Some(value) = interp.get_value(def, varid, None).clone() { + interp.add_value(def, varid, value.clone(), None); } } } - if let Some(value) = self.get_value(def, varid, None) { + if let Some(value) = interp.get_value(def, varid, None) { interp.return_value = Some((value.clone(), def)); - interp.return_value_alt.insert(def, value.clone()); + //interp.return_value_alt.insert(def, value.clone()); } } _ => {} @@ -893,7 +778,7 @@ impl<'cx> Dataflow<'cx> for PermissionDataflow { fn add_variable>( &mut self, - interp: &Interp<'cx, C>, + interp: &mut Interp<'cx, C>, lval: &Variable, varid: &VarId, def: DefId, @@ -904,23 +789,23 @@ impl<'cx> Dataflow<'cx> for PermissionDataflow { // transfer all of the variables if let Operand::Var(variable) = operand { if let Base::Var(varid_rval) = variable.base { - self.varid_to_value.clone().iter().for_each( + interp.varid_to_value.clone().iter().for_each( |((defid, varid_rval_potential, projection), value)| { if varid_rval_potential == &varid_rval { - self.add_value(def, *varid, value.clone(), projection.clone()) + interp.add_value(def, *varid, value.clone(), projection.clone()) } }, ); } } else { - if let Some(value) = get_prev_value(self.get_value( + if let Some(value) = get_prev_value(interp.get_value( def, *varid, lval.projections.get(0).cloned(), )) { - self.insert_value(operand, lval, varid, def, interp, Some(value)); + self.insert_value(interp, operand, lval, varid, def, Some(value)); } else { - self.insert_value(operand, lval, varid, def, interp, None); + self.insert_value(interp, operand, lval, varid, def, None); } } } @@ -941,7 +826,7 @@ impl<'cx> Dataflow<'cx> for PermissionDataflow { } let mut new_values = vec![]; - let values = self.get_str_from_expr(expr, def); + let values = self.get_str_from_expr(interp, expr, def); if values.len() > 0 { for str_value in values { for value in &all_values { @@ -974,9 +859,9 @@ impl<'cx> Dataflow<'cx> for PermissionDataflow { .map(|value| Const::Literal(value.clone())) .collect::>(); let value = Value::Phi(consts); - self.add_value(def, *varid, value.clone(), None); + interp.add_value(def, *varid, value.clone(), None); } else if all_potential_values.len() == 1 { - self.add_value( + interp.add_value( def, *varid, Value::Const(Const::Literal(all_potential_values.get(0).unwrap().clone())), @@ -1010,9 +895,9 @@ impl<'cx> Dataflow<'cx> for PermissionDataflow { self.varid_to_value .insert((def, *varid, None), return_value_from_string(new_vals)); } else if let Some(val1) = val1 { - self.add_value(def, *varid, val1, None); + interp.add_value(def, *varid, val1, None); } else if let Some(val2) = val2 { - self.add_value(def, *varid, val2, None); + interp.add_value(def, *varid, val2, None); } } } @@ -1022,11 +907,11 @@ impl<'cx> Dataflow<'cx> for PermissionDataflow { fn insert_value>( &mut self, + interp: &mut Interp<'cx, C>, operand: &Operand, lval: &Variable, varid: &VarId, def: DefId, - interp: &Interp<'cx, C>, prev_values: Option>, ) { match operand { @@ -1037,12 +922,12 @@ impl<'cx> Dataflow<'cx> for PermissionDataflow { let mut all_values = prev_values.clone(); all_values.push(const_value); let value = Value::Phi(all_values); - self.add_value(def, *varid, value, lval.projections.get(0).cloned()); + interp.add_value(def, *varid, value, lval.projections.get(0).cloned()); } } else { if let Some(lit_value) = convert_operand_to_raw(operand) { let value = Value::Const(Const::Literal(lit_value)); - self.add_value(def, *varid, value, lval.projections.get(0).cloned()); + interp.add_value(def, *varid, value, lval.projections.get(0).cloned()); } } } @@ -1058,7 +943,7 @@ impl<'cx> Dataflow<'cx> for PermissionDataflow { let mut all_values = prev_values.clone(); all_values.push(const_value); let value = Value::Phi(all_values); - self.add_value( + interp.add_value( def, *varid, value, @@ -1066,7 +951,7 @@ impl<'cx> Dataflow<'cx> for PermissionDataflow { ); } else { let value = Value::Const(Const::Object(class)); - self.add_value( + interp.add_value( def, *varid, value, @@ -1075,7 +960,7 @@ impl<'cx> Dataflow<'cx> for PermissionDataflow { } } } else { - if let Some(potential_value) = self.get_value(def, prev_varid, None) { + if let Some(potential_value) = interp.get_value(def, prev_varid, None) { self.varid_to_value.insert( (def, *varid, var.projections.get(0).cloned()), potential_value.clone(), @@ -1101,19 +986,99 @@ impl<'cx> Dataflow<'cx> for PermissionDataflow { interp.callstack_arguments.push(values.clone()); } } -} -fn return_value_from_string(values: Vec) -> Value { - // assert!(values.len() > 0); - if values.len() == 1 { - return Value::Const(Const::Literal(values.get(0).unwrap().clone())); - } else { - return Value::Phi( - values - .iter() - .map(|val_string| Const::Literal(val_string.clone())) - .collect_vec(), - ); + fn handle_first_arg>( + &self, + interp: &Interp<'cx, C>, + operand: &Operand, + _def: DefId, + intrinsic_argument: &mut IntrinsicArguments, + ) { + match operand { + Operand::Lit(lit) => { + intrinsic_argument.first_arg = Some(vec![lit.to_string()]); + } + Operand::Var(var) => { + if let Base::Var(varid) = var.base { + if let Some(value) = interp.get_value(_def, varid, None) { + intrinsic_argument.first_arg = Some(vec![]); + add_elements_to_intrinsic_struct(value, &mut intrinsic_argument.first_arg); + } + } + } + } + } + + fn handle_second_arg>( + &self, + interp: &Interp<'cx, C>, + operand: &Operand, + _def: DefId, + intrinsic_argument: &mut IntrinsicArguments, + ) { + if let Operand::Var(variable) = operand { + if let Base::Var(varid) = variable.base { + if let Some(value) = + interp.get_value(_def, varid, Some(Projection::Known("method".into()))) + { + match value { + Value::Const(Const::Literal(lit)) => { + intrinsic_argument.second_arg = Some(vec![lit.to_string()]); + } + Value::Phi(phi_val) => { + intrinsic_argument.second_arg = Some( + phi_val + .iter() + .map(|data| { + if let Const::Literal(lit) = data { + return Some(lit.to_string()); + } else { + None + } + }) + .filter(|const_val| const_val != &None) + .map(|f| f.unwrap()) + .collect_vec(), + ) + } + _ => {} + } + } + } + } + } + + fn get_str_from_expr>(&self, _interp: &Interp<'cx, C>, expr: &Operand, def: DefId) -> Vec> { + if let Some(str) = get_str_from_operand(expr) { + return vec![Some(str)]; + } else if let Operand::Var(var) = expr { + if let Base::Var(varid) = var.base { + let value = _interp.get_value(def, varid, None); + if let Some(value) = value { + match value { + Value::Const(const_val) => { + if let Const::Literal(str) = const_val { + return vec![Some(str.clone())]; + } + } + Value::Phi(phi_val) => { + return phi_val + .iter() + .map(|const_val| { + if let Const::Literal(str) = const_val { + Some(str.clone()) + } else { + None + } + }) + .collect_vec(); + } + _ => {} + } + } + } + } + vec![None] } } @@ -1202,6 +1167,22 @@ impl PermissionVuln { } } +impl<'cx> Checker<'cx> for PermissionChecker { + type State = PermissionTest; + type Dataflow = PermissionDataflow; + type Vuln = PermissionVuln; + + fn visit_intrinsic( + &mut self, + interp: &Interp<'cx, Self>, + intrinsic: &'cx Intrinsic, + state: &Self::State, + operands: Option>, + ) -> ControlFlow<(), Self::State> { + ControlFlow::Continue(*state) + } +} + impl IntoVuln for PermissionVuln { fn into_vuln(self, reporter: &Reporter) -> Vulnerability { Vulnerability { diff --git a/crates/forge_analyzer/src/interp.rs b/crates/forge_analyzer/src/interp.rs index d68f7b27..270830cf 100644 --- a/crates/forge_analyzer/src/interp.rs +++ b/crates/forge_analyzer/src/interp.rs @@ -26,12 +26,14 @@ use crate::{ definitions::{Class, Const, DefId, Environment, Value}, ir::{ Base, BasicBlock, BasicBlockId, Body, Inst, Intrinsic, Location, Operand, Rvalue, - Successors, VarId, Variable, STARTING_BLOCK, + Successors, VarId, Variable, STARTING_BLOCK, Projection, }, utils::{get_defid_from_varkind, resolve_var_from_operand}, worklist::WorkList, }; +pub type DefinitionAnalysisMap = FxHashMap<(DefId, VarId, Option), Value>; + pub trait JoinSemiLattice: Sized + Ord { const BOTTOM: Self; @@ -160,7 +162,7 @@ pub trait Dataflow<'cx>: Sized { fn add_variable>( &mut self, - interp: &Interp<'cx, C>, + interp: &mut Interp<'cx, C>, lval: &Variable, varid: &VarId, def: DefId, @@ -170,11 +172,11 @@ pub trait Dataflow<'cx>: Sized { fn insert_value>( &mut self, + interp: &mut Interp<'cx, C>, operand: &Operand, lval: &Variable, varid: &VarId, def: DefId, - interp: &Interp<'cx, C>, prev_values: Option>, ) { } @@ -267,6 +269,14 @@ pub trait Dataflow<'cx>: Sized { ) { } + fn handle_first_arg>( + &self, + _interp: &Interp<'cx, C>, + operand: &Operand, + _def: DefId, + intrinsic_argument: &mut IntrinsicArguments, + ) {} + fn read_mem_from_object>( &self, _interp: &Interp<'cx, C>, @@ -276,6 +286,18 @@ pub trait Dataflow<'cx>: Sized { None } + fn handle_second_arg>( + &self, + _interp: &Interp<'cx, C>, + operand: &Operand, + _def: DefId, + intrinsic_argument: &mut IntrinsicArguments, + ) {} + + fn get_str_from_expr>(&self, _interp: &Interp<'cx, C>, expr: &Operand, def: DefId) -> Vec> { + vec![None] + } + fn def_to_class_property>( &self, _interp: &Interp<'cx, C>, @@ -287,20 +309,13 @@ pub trait Dataflow<'cx>: Sized { fn get_values_from_operand>( &self, - _interp: &Interp<'cx, C>, + _interp: &mut Interp<'cx, C>, _def: DefId, operand: &Operand, ) -> Option<&Value> { None } - fn get_str_from_expr>( - &self, - expr: &Operand, - def: DefId, - ) -> Option { - None - } fn try_insert>( &self, _interp: &Interp<'cx, C>, @@ -309,15 +324,6 @@ pub trait Dataflow<'cx>: Sized { intrinsic_argument: &mut IntrinsicArguments, ) { } - - fn handle_second_arg>( - &self, - _interp: &Interp<'cx, C>, - operand: &Operand, - _def: DefId, - intrinsic_argument: &mut IntrinsicArguments, - ) { - } } pub trait Checker<'cx>: Sized { @@ -470,6 +476,7 @@ pub struct Interp<'cx, C: Checker<'cx>> { pub confluence_permission_resolver: PermissionHashMap, pub jira_regex_map: HashMap, pub confluence_regex_map: HashMap, + pub varid_to_value: DefinitionAnalysisMap, _checker: PhantomData, } @@ -550,6 +557,7 @@ impl<'cx, C: Checker<'cx>> Interp<'cx, C> { callstack: RefCell::new(Vec::new()), vulns: RefCell::new(Vec::new()), permissions: Vec::new(), + varid_to_value: DefinitionAnalysisMap::default(), jira_permission_resolver, confluence_permission_resolver, jira_regex_map, @@ -578,10 +586,33 @@ impl<'cx, C: Checker<'cx>> Interp<'cx, C> { (*self.callstack.borrow()).clone() } + #[inline] pub(crate) fn checker_visit(&self, def: DefId) -> bool { self.checker_visited.borrow_mut().insert(def) } + #[inline] + pub(crate) fn add_value( + &mut self, + defid_block: DefId, + varid: VarId, + value: Value, + projection: Option, + ) { + // println!("varid from {varid:?} -- {projection:?} -- {value:?}"); + self.varid_to_value + .insert((defid_block, varid, projection), value); + } + #[inline] + pub(crate) fn get_value( + &self, + defid_block: DefId, + varid: VarId, + projection: Option, + ) -> Option<&Value> { + self.varid_to_value.get(&(defid_block, varid, projection)) + } + #[inline] fn called_from(&self, def: DefId) -> &[(DefId, Location)] { self.call_graph.called_from.get(&def).map_or(&[], |v| v) diff --git a/crates/forge_analyzer/src/ir.rs b/crates/forge_analyzer/src/ir.rs index 2f82075c..1c04a1a2 100644 --- a/crates/forge_analyzer/src/ir.rs +++ b/crates/forge_analyzer/src/ir.rs @@ -213,7 +213,7 @@ pub enum Operand { } #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] -pub(crate) enum Base { +pub enum Base { This, Super, Var(VarId), @@ -243,7 +243,7 @@ impl From for Variable { } #[derive(Clone, Debug, PartialEq, Eq, Hash)] -pub(crate) enum Projection { +pub enum Projection { Known(JsWord), Computed(Base), } From 00e93c66875f014848de8a4f572fc479f37d3d04 Mon Sep 17 00:00:00 2001 From: James Gersbach Date: Mon, 7 Aug 2023 21:03:09 -0700 Subject: [PATCH 146/517] working definition analysis seperated analysis and permission checker --- Cargo.lock | 105 +++++ crates/forge_analyzer/src/checkers.rs | 555 +++++++++++++++----------- crates/forge_analyzer/src/interp.rs | 149 ++++--- crates/forge_analyzer/src/runners.rs | 0 crates/forge_analyzer/src/utils.rs | 14 + crates/fsrt/Cargo.toml | 1 + crates/fsrt/src/main.rs | 36 +- 7 files changed, 574 insertions(+), 286 deletions(-) create mode 100644 crates/forge_analyzer/src/runners.rs diff --git a/Cargo.lock b/Cargo.lock index e752a0a8..839b63f7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -645,6 +645,7 @@ dependencies = [ "forge_analyzer", "forge_file_resolver", "forge_loader", + "futures", "miette 5.5.0", "rustc-hash", "serde", @@ -657,6 +658,95 @@ dependencies = [ "walkdir", ] +[[package]] +name = "futures" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23342abe12aba583913b2e62f22225ff9c950774065e4bfb61a19cd9770fec40" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "955518d47e09b25bbebc7a18df10b81f0c766eaf4c4f1cccef2fca5f2a4fb5f2" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4bca583b7e26f571124fe5b7561d49cb2868d79116cfa0eefce955557c6fee8c" + +[[package]] +name = "futures-executor" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccecee823288125bd88b4d7f565c9e58e41858e47ab72e8ea2d64e93624386e0" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-io" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fff74096e71ed47f8e023204cfd0aa1289cd54ae5430a9523be060cdb849964" + +[[package]] +name = "futures-macro" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89ca545a94061b6365f2c7355b4b32bd20df3ff95f02da9329b34ccc3bd6ee72" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.28", +] + +[[package]] +name = "futures-sink" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f43be4fe21a13b9781a69afa4985b0f6ee0e1afab2c6f454a8cf30e2b2237b6e" + +[[package]] +name = "futures-task" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76d3d132be6c0e6aa1534069c705a74a5997a356c0dc2f86a47765e5617c5b65" + +[[package]] +name = "futures-util" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26b01e40b772d54cf6c6d721c1d1abd0647a0106a12ecaa1c186273392a69533" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "pin-utils", + "slab", +] + [[package]] name = "generic-array" version = "0.14.6" @@ -1326,6 +1416,12 @@ version = "0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116" +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + [[package]] name = "pmutil" version = "0.5.3" @@ -1762,6 +1858,15 @@ version = "0.3.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7bd3e3206899af3f8b12af284fafc038cc1dc2b41d1b89dd17297221c5d225de" +[[package]] +name = "slab" +version = "0.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6528351c9bc8ab22353f9d776db39a20288e8d6c37ef8cfe3317cf875eecfc2d" +dependencies = [ + "autocfg", +] + [[package]] name = "smallvec" version = "1.10.0" diff --git a/crates/forge_analyzer/src/checkers.rs b/crates/forge_analyzer/src/checkers.rs index 66c5ba97..3d8d2392 100644 --- a/crates/forge_analyzer/src/checkers.rs +++ b/crates/forge_analyzer/src/checkers.rs @@ -23,7 +23,8 @@ use tracing::{debug, info, warn}; use crate::{ definitions::{Class, Const, DefId, DefKind, Environment, IntrinsicName, Value}, interp::{ - Checker, Dataflow, EntryKind, EntryPoint, Frame, Interp, JoinSemiLattice, WithCallStack, + Checker, Dataflow, EntryKind, EntryPoint, Frame, Interp, JoinSemiLattice, Runner, + WithCallStack, }, ir::{ Base, BasicBlock, BasicBlockId, BinOp, Inst, Intrinsic, Literal, Location, Operand, @@ -32,7 +33,8 @@ use crate::{ reporter::{IntoVuln, Reporter, Severity, Vulnerability}, utils::{ add_const_to_val_vec, add_elements_to_intrinsic_struct, convert_operand_to_raw, - get_defid_from_varkind, get_prev_value, get_str_from_operand, resolve_var_from_operand, return_value_from_string, + get_defid_from_varkind, get_prev_value, get_str_from_operand, resolve_var_from_operand, + return_value_from_string, translate_request_type, }, worklist::WorkList, }; @@ -65,13 +67,13 @@ impl JoinSemiLattice for AuthorizeState { impl<'cx> Dataflow<'cx> for AuthorizeDataflow { type State = AuthorizeState; - fn with_interp>( + fn with_interp>( _interp: &Interp<'cx, C>, ) -> Self { Self { needs_call: vec![] } } - fn transfer_intrinsic>( + fn transfer_intrinsic>( &mut self, _interp: &mut Interp<'cx, C>, _def: DefId, @@ -95,7 +97,7 @@ impl<'cx> Dataflow<'cx> for AuthorizeDataflow { } } - fn transfer_call>( + fn transfer_call>( &mut self, interp: &Interp<'cx, C>, def: DefId, @@ -125,7 +127,7 @@ impl<'cx> Dataflow<'cx> for AuthorizeDataflow { } } - fn join_term>( + fn join_term>( &mut self, interp: &mut Interp<'cx, C>, def: DefId, @@ -236,10 +238,9 @@ impl WithCallStack for AuthZVuln { fn add_call_stack(&mut self, _stack: Vec) {} } -impl<'cx> Checker<'cx> for AuthZChecker { +impl<'cx> Runner<'cx> for AuthZChecker { type State = AuthorizeState; type Dataflow = AuthorizeDataflow; - type Vuln = AuthZVuln; fn visit_intrinsic( &mut self, @@ -269,6 +270,10 @@ impl<'cx> Checker<'cx> for AuthZChecker { } } +impl<'cx> Checker<'cx> for AuthZChecker { + type Vuln = AuthZVuln; +} + #[derive(Debug, PartialEq, Eq, Clone, Copy, PartialOrd, Ord)] pub enum Authenticated { No, @@ -297,13 +302,13 @@ pub struct AuthenticateDataflow { impl<'cx> Dataflow<'cx> for AuthenticateDataflow { type State = Authenticated; - fn with_interp>( + fn with_interp>( _interp: &Interp<'cx, C>, ) -> Self { Self { needs_call: vec![] } } - fn transfer_intrinsic>( + fn transfer_intrinsic>( &mut self, _interp: &mut Interp<'cx, C>, _def: DefId, @@ -325,7 +330,7 @@ impl<'cx> Dataflow<'cx> for AuthenticateDataflow { } } - fn transfer_call>( + fn transfer_call>( &mut self, interp: &Interp<'cx, C>, def: DefId, @@ -355,7 +360,7 @@ impl<'cx> Dataflow<'cx> for AuthenticateDataflow { } } - fn join_term>( + fn join_term>( &mut self, interp: &mut Interp<'cx, C>, def: DefId, @@ -394,10 +399,10 @@ impl Default for AuthenticateChecker { } } -impl<'cx> Checker<'cx> for AuthenticateChecker { +impl<'cx> Runner<'cx> for AuthenticateChecker { type State = Authenticated; type Dataflow = AuthenticateDataflow; - type Vuln = AuthNVuln; + fn visit_intrinsic( &mut self, interp: &Interp<'cx, Self>, @@ -424,6 +429,10 @@ impl<'cx> Checker<'cx> for AuthenticateChecker { } } +impl<'cx> Checker<'cx> for AuthenticateChecker { + type Vuln = AuthNVuln; +} + #[derive(Debug)] pub struct AuthNVuln { stack: String, @@ -496,8 +505,35 @@ impl WithCallStack for AuthNVuln { } pub struct PermissionDataflow { - needs_call: Vec<(DefId, Vec, Vec)>, - varid_to_value: FxHashMap<(DefId, VarId, Option), Value>, + needs_call: Vec<(DefId, Vec)>, + pub varid_to_value: FxHashMap<(DefId, VarId, Option), Value>, +} + +impl PermissionDataflow { + fn handle_second_arg(&self, value: &Value, intrinsic_argument: &mut IntrinsicArguments) { + match value { + Value::Const(Const::Literal(lit)) => { + intrinsic_argument.second_arg = Some(vec![lit.to_string()]); + } + Value::Phi(phi_val) => { + intrinsic_argument.second_arg = Some( + phi_val + .iter() + .map(|data| { + if let Const::Literal(lit) = data { + return Some(lit.to_string()); + } else { + None + } + }) + .filter(|const_val| const_val != &None) + .map(|f| f.unwrap()) + .collect_vec(), + ) + } + _ => {} + } + } } impl WithCallStack for PermissionVuln { @@ -514,7 +550,7 @@ pub struct IntrinsicArguments { impl<'cx> Dataflow<'cx> for PermissionDataflow { type State = PermissionTest; - fn with_interp>( + fn with_interp>( _interp: &Interp<'cx, C>, ) -> Self { Self { @@ -523,7 +559,7 @@ impl<'cx> Dataflow<'cx> for PermissionDataflow { } } - fn transfer_intrinsic>( + fn transfer_intrinsic>( &mut self, _interp: &mut Interp<'cx, C>, _def: DefId, @@ -540,10 +576,33 @@ impl<'cx> Dataflow<'cx> for PermissionDataflow { intrinsic_argument.name = Some(name.clone()); let (first, second) = (operands.get(0), operands.get(1)); if let Some(operand) = first { - self.handle_first_arg(_interp, operand, _def, &mut intrinsic_argument); + match operand { + Operand::Lit(lit) => { + intrinsic_argument.first_arg = Some(vec![lit.to_string()]); + } + Operand::Var(var) => { + if let Base::Var(varid) = var.base { + if let Some(value) = _interp.get_value(_def, varid, None) { + intrinsic_argument.first_arg = Some(vec![]); + add_elements_to_intrinsic_struct( + value, + &mut intrinsic_argument.first_arg, + ); + } + } + } + } } if let Some(operand) = second { - self.handle_second_arg(_interp, operand, _def, &mut intrinsic_argument); + if let Operand::Var(variable) = operand { + if let Base::Var(varid) = variable.base { + if let Some(value) = + _interp.get_value(_def, varid, Some(Projection::Known("method".into()))) + { + self.handle_second_arg(value, &mut intrinsic_argument); + } + } + } } println!("intrinsic argument {intrinsic_argument:?}"); @@ -561,7 +620,7 @@ impl<'cx> Dataflow<'cx> for PermissionDataflow { let permissions = check_url_for_permissions( &_interp.confluence_permission_resolver, &_interp.confluence_regex_map, - trnaslate_request_type(Some(second_arg)), + translate_request_type(Some(second_arg)), &first_arg, ); permissions_within_call.extend_from_slice(&permissions) @@ -569,7 +628,7 @@ impl<'cx> Dataflow<'cx> for PermissionDataflow { let permissions = check_url_for_permissions( &_interp.jira_permission_resolver, &_interp.jira_regex_map, - trnaslate_request_type(Some(second_arg)), + translate_request_type(Some(second_arg)), &first_arg, ); permissions_within_call.extend_from_slice(&permissions) @@ -608,19 +667,241 @@ impl<'cx> Dataflow<'cx> for PermissionDataflow { initial_state } - fn get_values_from_operand>( - &self, + fn transfer_call>( + &mut self, + interp: &Interp<'cx, C>, + def: DefId, + loc: Location, + _block: &'cx BasicBlock, + callee: &'cx crate::ir::Operand, + initial_state: Self::State, + operands: SmallVec<[crate::ir::Operand; 4]>, + ) -> Self::State { + let Some((callee_def, _body)) = self.resolve_call(interp, callee) else { + return initial_state; + }; + + let callee_name = interp.env().def_name(callee_def); + let caller_name = interp.env().def_name(def); + debug!("Found call to {callee_name} at {def:?} {caller_name}"); + self.needs_call.push((callee_def, operands.into_vec())); + initial_state + } + + fn transfer_inst>( + &mut self, + interp: &mut Interp<'cx, C>, + def: DefId, + loc: Location, + block: &'cx BasicBlock, + inst: &'cx Inst, + initial_state: Self::State, + ) -> Self::State { + println!("\tinst {inst}"); + match inst { + Inst::Expr(rvalue) => { + self.transfer_rvalue(interp, def, loc, block, rvalue, initial_state) + } + Inst::Assign(var, rvalue) => { + self.transfer_rvalue(interp, def, loc, block, rvalue, initial_state) + } + } + } + + fn transfer_block>( + &mut self, + interp: &mut Interp<'cx, C>, + def: DefId, + bb: BasicBlockId, + block: &'cx BasicBlock, + initial_state: Self::State, + arguments: Option>, + ) -> Self::State { + let mut state = initial_state; + + for (stmt, inst) in block.iter().enumerate() { + let loc = Location::new(bb, stmt as u32); + state = self.transfer_inst(interp, def, loc, block, inst, state); + } + state + } + + fn join_term>( + &mut self, + interp: &mut Interp<'cx, C>, + def: DefId, + block: &'cx BasicBlock, + state: Self::State, + worklist: &mut WorkList, + ) { + self.super_join_term(interp, def, block, state, worklist); + for (def, arguments) in self.needs_call.drain(..) { + worklist.push_front_blocks(interp.env(), def); + } + } +} + +pub struct PermissionChecker { + pub visit: bool, + pub vulns: Vec, + pub declared_permissions: HashSet, + pub used_permissions: HashSet, +} + +impl PermissionChecker { + pub fn new(declared_permissions: HashSet) -> Self { + Self { + visit: false, + vulns: vec![], + declared_permissions, + used_permissions: HashSet::default(), + } + } + + pub fn into_vulns(self) -> impl IntoIterator { + if self.declared_permissions.len() > 0 { + return Vec::from([PermissionVuln { + unused_permissions: self.declared_permissions.clone(), + }]) + .into_iter(); + } + self.vulns.into_iter() + } +} + +impl Default for PermissionChecker { + fn default() -> Self { + Self::new(HashSet::new()) + } +} + +impl fmt::Display for PermissionVuln { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "Authentication vulnerability") + } +} + +#[derive(Debug, PartialEq, Eq, Clone, Copy, PartialOrd, Ord)] +pub enum PermissionTest { + Yes, +} + +impl JoinSemiLattice for PermissionTest { + const BOTTOM: Self = Self::Yes; + + #[inline] + fn join_changed(&mut self, other: &Self) -> bool { + let old = mem::replace(self, max(*other, *self)); + old == *self + } + + #[inline] + fn join(&self, other: &Self) -> Self { + max(*other, *self) + } +} + +#[derive(Debug)] +pub struct PermissionVuln { + unused_permissions: HashSet, +} + +impl PermissionVuln { + pub fn new(unused_permissions: HashSet) -> PermissionVuln { + PermissionVuln { unused_permissions } + } +} + +pub struct DefintionAnalysisRunner { + pub needs_call: Vec<(DefId, Vec, Vec)>, +} + +impl<'cx> Runner<'cx> for PermissionChecker { + type State = PermissionTest; + type Dataflow = PermissionDataflow; + + fn visit_intrinsic( + &mut self, + interp: &Interp<'cx, Self>, + intrinsic: &'cx Intrinsic, + state: &Self::State, + operands: Option>, + ) -> ControlFlow<(), Self::State> { + ControlFlow::Continue(*state) + } +} + +impl<'cx> Checker<'cx> for PermissionChecker { + type Vuln = PermissionVuln; +} + +impl IntoVuln for PermissionVuln { + fn into_vuln(self, reporter: &Reporter) -> Vulnerability { + Vulnerability { + check_name: format!("Least-Privilege"), + description: format!( + "Unused permissions listed in manifest file: {:?}", + self.unused_permissions + ), + recommendation: "Remove permissions in manifest file that are not needed.", + proof: format!( + "Unused permissions found in manifest.yml: {:?}", + self.unused_permissions + ), + severity: Severity::Low, + app_key: reporter.app_key().to_string(), + app_name: reporter.app_name().to_string(), + date: reporter.current_date(), + } + } +} + +impl<'cx> Runner<'cx> for DefintionAnalysisRunner { + type State = PermissionTest; + type Dataflow = DefintionAnalysisRunner; + + fn visit_intrinsic( + &mut self, + interp: &Interp<'cx, Self>, + intrinsic: &'cx Intrinsic, + state: &Self::State, + operands: Option>, + ) -> ControlFlow<(), Self::State> { + ControlFlow::Continue(*state) + } +} + +impl DefintionAnalysisRunner { + pub fn new() -> Self { + Self { + needs_call: Vec::default(), + } + } +} + +impl<'cx> Dataflow<'cx> for DefintionAnalysisRunner { + type State = PermissionTest; + + fn with_interp>( + _interp: &Interp<'cx, C>, + ) -> Self { + Self { needs_call: vec![] } + } + + fn transfer_intrinsic>( + &mut self, _interp: &mut Interp<'cx, C>, _def: DefId, - operand: &Operand, - ) -> Option<&Value> { - if let Some((var, varid)) = resolve_var_from_operand(operand) { - return None // return _interp.get_value(_def, varid, var.projections.get(0).cloned()); - } - None + _loc: Location, + _block: &'cx BasicBlock, + intrinsic: &'cx Intrinsic, + initial_state: Self::State, + operands: SmallVec<[crate::ir::Operand; 4]>, + ) -> Self::State { + initial_state } - fn transfer_call>( + fn transfer_call>( &mut self, interp: &Interp<'cx, C>, def: DefId, @@ -668,7 +949,7 @@ impl<'cx> Dataflow<'cx> for PermissionDataflow { initial_state } - fn transfer_inst>( + fn transfer_inst>( &mut self, interp: &mut Interp<'cx, C>, def: DefId, @@ -710,7 +991,7 @@ impl<'cx> Dataflow<'cx> for PermissionDataflow { } } - fn transfer_block>( + fn transfer_block>( &mut self, interp: &mut Interp<'cx, C>, def: DefId, @@ -739,7 +1020,8 @@ impl<'cx> Dataflow<'cx> for PermissionDataflow { get_defid_from_varkind(varkind), ) { if defid == defid_alt && varid_alt != varid { - self.varid_to_value + interp + .varid_to_value .insert((def, varid_alt, None), operand.clone()); } } @@ -757,7 +1039,7 @@ impl<'cx> Dataflow<'cx> for PermissionDataflow { for (varid, varkind) in interp.body().vars.clone().iter_enumerated() { match varkind { VarKind::Ret => { - for (defid, (varid_value, defid_value)) in &interp.expecting_value { + for (defid, (varid_value, defid_value)) in interp.expecting_value.clone() { if def == defid.clone() { if let Some(value) = interp.get_value(def, varid, None).clone() { interp.add_value(def, varid, value.clone(), None); @@ -776,7 +1058,7 @@ impl<'cx> Dataflow<'cx> for PermissionDataflow { state } - fn add_variable>( + fn add_variable>( &mut self, interp: &mut Interp<'cx, C>, lval: &Variable, @@ -874,12 +1156,12 @@ impl<'cx> Dataflow<'cx> for PermissionDataflow { let val1 = if let Some(val) = get_str_from_operand(op1) { Some(Value::Const(Const::Literal(val))) } else { - self.get_values_from_operand(interp, def, op1).cloned() + self.get_values_from_operand(interp, def, op2) }; let val2 = if let Some(val) = get_str_from_operand(op2) { Some(Value::Const(Const::Literal(val))) } else { - self.get_values_from_operand(interp, def, op2).cloned() + self.get_values_from_operand(interp, def, op2) }; let mut new_vals = vec![]; if let (Some(val1), Some(val2)) = (val1.clone(), val2.clone()) { @@ -892,7 +1174,8 @@ impl<'cx> Dataflow<'cx> for PermissionDataflow { .for_each(|val1| add_const_to_val_vec(&val2, &val1, &mut new_vals)), _ => {} } - self.varid_to_value + interp + .varid_to_value .insert((def, *varid, None), return_value_from_string(new_vals)); } else if let Some(val1) = val1 { interp.add_value(def, *varid, val1, None); @@ -905,7 +1188,7 @@ impl<'cx> Dataflow<'cx> for PermissionDataflow { } } - fn insert_value>( + fn insert_value>( &mut self, interp: &mut Interp<'cx, C>, operand: &Operand, @@ -961,7 +1244,7 @@ impl<'cx> Dataflow<'cx> for PermissionDataflow { } } else { if let Some(potential_value) = interp.get_value(def, prev_varid, None) { - self.varid_to_value.insert( + interp.varid_to_value.insert( (def, *varid, var.projections.get(0).cloned()), potential_value.clone(), ); @@ -972,7 +1255,7 @@ impl<'cx> Dataflow<'cx> for PermissionDataflow { } } - fn join_term>( + fn join_term>( &mut self, interp: &mut Interp<'cx, C>, def: DefId, @@ -987,68 +1270,12 @@ impl<'cx> Dataflow<'cx> for PermissionDataflow { } } - fn handle_first_arg>( + fn get_str_from_expr>( &self, - interp: &Interp<'cx, C>, - operand: &Operand, - _def: DefId, - intrinsic_argument: &mut IntrinsicArguments, - ) { - match operand { - Operand::Lit(lit) => { - intrinsic_argument.first_arg = Some(vec![lit.to_string()]); - } - Operand::Var(var) => { - if let Base::Var(varid) = var.base { - if let Some(value) = interp.get_value(_def, varid, None) { - intrinsic_argument.first_arg = Some(vec![]); - add_elements_to_intrinsic_struct(value, &mut intrinsic_argument.first_arg); - } - } - } - } - } - - fn handle_second_arg>( - &self, - interp: &Interp<'cx, C>, - operand: &Operand, - _def: DefId, - intrinsic_argument: &mut IntrinsicArguments, - ) { - if let Operand::Var(variable) = operand { - if let Base::Var(varid) = variable.base { - if let Some(value) = - interp.get_value(_def, varid, Some(Projection::Known("method".into()))) - { - match value { - Value::Const(Const::Literal(lit)) => { - intrinsic_argument.second_arg = Some(vec![lit.to_string()]); - } - Value::Phi(phi_val) => { - intrinsic_argument.second_arg = Some( - phi_val - .iter() - .map(|data| { - if let Const::Literal(lit) = data { - return Some(lit.to_string()); - } else { - None - } - }) - .filter(|const_val| const_val != &None) - .map(|f| f.unwrap()) - .collect_vec(), - ) - } - _ => {} - } - } - } - } - } - - fn get_str_from_expr>(&self, _interp: &Interp<'cx, C>, expr: &Operand, def: DefId) -> Vec> { + _interp: &Interp<'cx, C>, + expr: &Operand, + def: DefId, + ) -> Vec> { if let Some(str) = get_str_from_operand(expr) { return vec![Some(str)]; } else if let Operand::Var(var) = expr { @@ -1081,125 +1308,3 @@ impl<'cx> Dataflow<'cx> for PermissionDataflow { vec![None] } } - -fn trnaslate_request_type(request_type: Option<&str>) -> RequestType { - if let Some(request_type) = request_type { - match request_type { - "PATCH" => RequestType::Patch, - "PUT" => RequestType::Put, - "DELETE" => RequestType::Delete, - "POST" => RequestType::Post, - _ => RequestType::Get, - } - } else { - return RequestType::Get; - } -} - -pub struct PermissionChecker { - pub visit: bool, - pub vulns: Vec, - pub declared_permissions: HashSet, - pub used_permissions: HashSet, -} - -impl PermissionChecker { - pub fn new(declared_permissions: HashSet) -> Self { - Self { - visit: false, - vulns: vec![], - declared_permissions, - used_permissions: HashSet::default(), - } - } - - pub fn into_vulns(self) -> impl IntoIterator { - if self.declared_permissions.len() > 0 { - return Vec::from([PermissionVuln { - unused_permissions: self.declared_permissions.clone(), - }]) - .into_iter(); - } - self.vulns.into_iter() - } -} - -impl Default for PermissionChecker { - fn default() -> Self { - Self::new(HashSet::new()) - } -} - -impl fmt::Display for PermissionVuln { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "Authentication vulnerability") - } -} - -#[derive(Debug, PartialEq, Eq, Clone, Copy, PartialOrd, Ord)] -pub enum PermissionTest { - Yes, -} - -impl JoinSemiLattice for PermissionTest { - const BOTTOM: Self = Self::Yes; - - #[inline] - fn join_changed(&mut self, other: &Self) -> bool { - let old = mem::replace(self, max(*other, *self)); - old == *self - } - - #[inline] - fn join(&self, other: &Self) -> Self { - max(*other, *self) - } -} - -#[derive(Debug)] -pub struct PermissionVuln { - unused_permissions: HashSet, -} - -impl PermissionVuln { - pub fn new(unused_permissions: HashSet) -> PermissionVuln { - PermissionVuln { unused_permissions } - } -} - -impl<'cx> Checker<'cx> for PermissionChecker { - type State = PermissionTest; - type Dataflow = PermissionDataflow; - type Vuln = PermissionVuln; - - fn visit_intrinsic( - &mut self, - interp: &Interp<'cx, Self>, - intrinsic: &'cx Intrinsic, - state: &Self::State, - operands: Option>, - ) -> ControlFlow<(), Self::State> { - ControlFlow::Continue(*state) - } -} - -impl IntoVuln for PermissionVuln { - fn into_vuln(self, reporter: &Reporter) -> Vulnerability { - Vulnerability { - check_name: format!("Least-Privilege"), - description: format!( - "Unused permissions listed in manifest file: {:?}", - self.unused_permissions - ), - recommendation: "Remove permissions in manifest file that are not needed.", - proof: format!( - "Unused permissions found in manifest.yml: {:?}", - self.unused_permissions - ), - severity: Severity::Low, - app_key: reporter.app_key().to_string(), - app_name: reporter.app_name().to_string(), - date: reporter.current_date(), - } - } -} diff --git a/crates/forge_analyzer/src/interp.rs b/crates/forge_analyzer/src/interp.rs index 270830cf..4dd907be 100644 --- a/crates/forge_analyzer/src/interp.rs +++ b/crates/forge_analyzer/src/interp.rs @@ -16,6 +16,7 @@ use forge_permission_resolver::permissions_resolver::{ PermissionHashMap, }; use forge_utils::{FxHashMap, FxHashSet}; +use itertools::Itertools; use regex::Regex; use smallvec::SmallVec; use swc_core::ecma::atoms::JsWord; @@ -25,10 +26,10 @@ use crate::{ checkers::IntrinsicArguments, definitions::{Class, Const, DefId, Environment, Value}, ir::{ - Base, BasicBlock, BasicBlockId, Body, Inst, Intrinsic, Location, Operand, Rvalue, - Successors, VarId, Variable, STARTING_BLOCK, Projection, + Base, BasicBlock, BasicBlockId, Body, Inst, Intrinsic, Location, Operand, Projection, + Rvalue, Successors, VarId, Variable, STARTING_BLOCK, }, - utils::{get_defid_from_varkind, resolve_var_from_operand}, + utils::{get_defid_from_varkind, get_str_from_operand, resolve_var_from_operand}, worklist::WorkList, }; @@ -54,10 +55,10 @@ pub trait WithCallStack { pub trait Dataflow<'cx>: Sized { type State: JoinSemiLattice + Clone; - fn with_interp>(interp: &Interp<'cx, C>) -> Self; + fn with_interp>(interp: &Interp<'cx, C>) -> Self; #[inline] - fn resolve_call>( + fn resolve_call>( &mut self, interp: &Interp<'cx, C>, callee: &Operand, @@ -65,7 +66,7 @@ pub trait Dataflow<'cx>: Sized { interp.body().resolve_call(interp.env(), callee) } - fn transfer_intrinsic>( + fn transfer_intrinsic>( &mut self, interp: &mut Interp<'cx, C>, def: DefId, @@ -76,7 +77,7 @@ pub trait Dataflow<'cx>: Sized { operands: SmallVec<[crate::ir::Operand; 4]>, ) -> Self::State; - fn transfer_call>( + fn transfer_call>( &mut self, interp: &Interp<'cx, C>, def: DefId, @@ -88,7 +89,7 @@ pub trait Dataflow<'cx>: Sized { oprands: SmallVec<[crate::ir::Operand; 4]>, ) -> Self::State; - fn transfer_rvalue>( + fn transfer_rvalue>( &mut self, interp: &mut Interp<'cx, C>, def: DefId, @@ -124,7 +125,7 @@ pub trait Dataflow<'cx>: Sized { } } - fn transfer_inst>( + fn transfer_inst>( &mut self, interp: &mut Interp<'cx, C>, def: DefId, @@ -143,7 +144,7 @@ pub trait Dataflow<'cx>: Sized { } } - fn transfer_block>( + fn transfer_block>( &mut self, interp: &mut Interp<'cx, C>, def: DefId, @@ -160,7 +161,7 @@ pub trait Dataflow<'cx>: Sized { state } - fn add_variable>( + fn add_variable>( &mut self, interp: &mut Interp<'cx, C>, lval: &Variable, @@ -170,7 +171,7 @@ pub trait Dataflow<'cx>: Sized { ) { } - fn insert_value>( + fn insert_value>( &mut self, interp: &mut Interp<'cx, C>, operand: &Operand, @@ -181,7 +182,7 @@ pub trait Dataflow<'cx>: Sized { ) { } - fn join_term>( + fn join_term>( &mut self, interp: &mut Interp<'cx, C>, def: DefId, @@ -192,7 +193,7 @@ pub trait Dataflow<'cx>: Sized { self.super_join_term(interp.borrow_mut(), def, block, state, worklist); } - fn super_join_term>( + fn super_join_term>( &mut self, interp: &mut Interp<'cx, C>, def: DefId, @@ -234,7 +235,7 @@ pub trait Dataflow<'cx>: Sized { } } - fn read_class_from_variable>( + fn read_class_from_variable>( &mut self, _interp: &Interp<'cx, C>, defid: DefId, @@ -242,7 +243,7 @@ pub trait Dataflow<'cx>: Sized { None } - fn read_class_from_object>( + fn read_class_from_object>( &mut self, _interp: &Interp<'cx, C>, defid: DefId, @@ -250,7 +251,7 @@ pub trait Dataflow<'cx>: Sized { None } - fn try_read_mem_from_object>( + fn try_read_mem_from_object>( &self, _interp: &Interp<'cx, C>, _def: DefId, @@ -259,7 +260,7 @@ pub trait Dataflow<'cx>: Sized { None } - fn insert_with_existing_value>( + fn insert_with_existing_value>( &mut self, operand: &Operand, value: &Value, @@ -269,15 +270,7 @@ pub trait Dataflow<'cx>: Sized { ) { } - fn handle_first_arg>( - &self, - _interp: &Interp<'cx, C>, - operand: &Operand, - _def: DefId, - intrinsic_argument: &mut IntrinsicArguments, - ) {} - - fn read_mem_from_object>( + fn read_mem_from_object>( &self, _interp: &Interp<'cx, C>, _def: DefId, @@ -286,19 +279,45 @@ pub trait Dataflow<'cx>: Sized { None } - fn handle_second_arg>( + fn get_str_from_expr>( &self, _interp: &Interp<'cx, C>, - operand: &Operand, - _def: DefId, - intrinsic_argument: &mut IntrinsicArguments, - ) {} - - fn get_str_from_expr>(&self, _interp: &Interp<'cx, C>, expr: &Operand, def: DefId) -> Vec> { + expr: &Operand, + def: DefId, + ) -> Vec> { + if let Some(str) = get_str_from_operand(expr) { + return vec![Some(str)]; + } else if let Operand::Var(var) = expr { + if let Base::Var(varid) = var.base { + let value = _interp.get_value(def, varid, None); + if let Some(value) = value { + match value { + Value::Const(const_val) => { + if let Const::Literal(str) = const_val { + return vec![Some(str.clone())]; + } + } + Value::Phi(phi_val) => { + return phi_val + .iter() + .map(|const_val| { + if let Const::Literal(str) = const_val { + Some(str.clone()) + } else { + None + } + }) + .collect_vec(); + } + _ => {} + } + } + } + } vec![None] } - fn def_to_class_property>( + fn def_to_class_property>( &self, _interp: &Interp<'cx, C>, _def: DefId, @@ -307,16 +326,22 @@ pub trait Dataflow<'cx>: Sized { None } - fn get_values_from_operand>( + #[inline] + fn get_values_from_operand>( &self, _interp: &mut Interp<'cx, C>, _def: DefId, operand: &Operand, - ) -> Option<&Value> { + ) -> Option { + if let Some((var, varid)) = resolve_var_from_operand(operand) { + return _interp + .get_value(_def, varid, var.projections.get(0).cloned()) + .cloned(); + } None } - fn try_insert>( + fn try_insert>( &self, _interp: &Interp<'cx, C>, _def: DefId, @@ -326,9 +351,8 @@ pub trait Dataflow<'cx>: Sized { } } -pub trait Checker<'cx>: Sized { +pub trait Runner<'cx>: Sized { type State: JoinSemiLattice + Clone + fmt::Debug; - type Vuln: Display + WithCallStack; type Dataflow: Dataflow<'cx, State = Self::State>; fn visit_intrinsic( @@ -432,6 +456,10 @@ pub trait Checker<'cx>: Sized { } } +pub trait Checker<'cx>: Sized + Runner<'cx> { + type Vuln: Display + WithCallStack; +} + #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub(crate) struct Frame { pub(crate) calling_function: DefId, @@ -454,7 +482,7 @@ pub(crate) struct EntryPoint { } #[derive(Debug)] -pub struct Interp<'cx, C: Checker<'cx>> { +pub struct Interp<'cx, C: Runner<'cx>> { pub env: &'cx Environment, // We can probably get rid of these RefCells by refactoring the Interp and Checker into // two fields in another struct. @@ -469,14 +497,14 @@ pub struct Interp<'cx, C: Checker<'cx>> { checker_visited: RefCell>, callstack: RefCell>, pub callstack_arguments: Vec>, - vulns: RefCell>, + // vulns: RefCell>, pub permissions: Vec, pub expecting_value: VecDeque<(DefId, (VarId, DefId))>, pub jira_permission_resolver: PermissionHashMap, pub confluence_permission_resolver: PermissionHashMap, pub jira_regex_map: HashMap, pub confluence_regex_map: HashMap, - pub varid_to_value: DefinitionAnalysisMap, + pub varid_to_value: FxHashMap<(DefId, VarId, Option), Value>, _checker: PhantomData, } @@ -534,7 +562,7 @@ impl fmt::Display for Error { impl std::error::Error for Error {} -impl<'cx, C: Checker<'cx>> Interp<'cx, C> { +impl<'cx, C: Runner<'cx>> Interp<'cx, C> { pub fn new(env: &'cx Environment) -> Self { let (jira_permission_resolver, jira_regex_map) = get_permission_resolver_jira(); let (confluence_permission_resolver, confluence_regex_map) = @@ -555,7 +583,7 @@ impl<'cx, C: Checker<'cx>> Interp<'cx, C> { callstack_arguments: Vec::new(), expecting_value: VecDeque::default(), callstack: RefCell::new(Vec::new()), - vulns: RefCell::new(Vec::new()), + // vulns: RefCell::new(Vec::new()), permissions: Vec::new(), varid_to_value: DefinitionAnalysisMap::default(), jira_permission_resolver, @@ -566,6 +594,11 @@ impl<'cx, C: Checker<'cx>> Interp<'cx, C> { } } + #[inline] + pub fn get_defs(&self) -> DefinitionAnalysisMap { + self.varid_to_value.clone() + } + #[inline] pub(crate) fn env(&self) -> &'cx Environment { self.env @@ -599,10 +632,10 @@ impl<'cx, C: Checker<'cx>> Interp<'cx, C> { value: Value, projection: Option, ) { - // println!("varid from {varid:?} -- {projection:?} -- {value:?}"); self.varid_to_value .insert((defid_block, varid, projection), value); } + #[inline] pub(crate) fn get_value( &self, @@ -754,16 +787,16 @@ impl<'cx, C: Checker<'cx>> Interp<'cx, C> { Ok(()) } - pub fn dump_results(&self, out: &mut dyn Write) -> io::Result<()> { - let vulns = &**self.vulns.borrow(); - if vulns.is_empty() { - writeln!(out, "No vulnerabilities found") - } else { - writeln!(out, "Found {} vulnerabilities", vulns.len())?; - for vuln in vulns { - writeln!(out, "{vuln}")?; - } - Ok(()) - } - } + // pub fn dump_results(&self, out: &mut dyn Write) -> io::Result<()> { + // let vulns = &**self.vulns.borrow(); + // if vulns.is_empty() { + // writeln!(out, "No vulnerabilities found") + // } else { + // writeln!(out, "Found {} vulnerabilities", vulns.len())?; + // for vuln in vulns { + // writeln!(out, "{vuln}")?; + // } + // Ok(()) + // } + // } } diff --git a/crates/forge_analyzer/src/runners.rs b/crates/forge_analyzer/src/runners.rs new file mode 100644 index 00000000..e69de29b diff --git a/crates/forge_analyzer/src/utils.rs b/crates/forge_analyzer/src/utils.rs index 23051592..4c75ca08 100644 --- a/crates/forge_analyzer/src/utils.rs +++ b/crates/forge_analyzer/src/utils.rs @@ -68,6 +68,20 @@ pub fn convert_lit_to_raw(lit: &Literal) -> Option { } } +pub fn translate_request_type(request_type: Option<&str>) -> RequestType { + if let Some(request_type) = request_type { + match request_type { + "PATCH" => RequestType::Patch, + "PUT" => RequestType::Put, + "DELETE" => RequestType::Delete, + "POST" => RequestType::Post, + _ => RequestType::Get, + } + } else { + return RequestType::Get; + } +} + pub fn get_str_from_operand(operand: &Operand) -> Option { if let Operand::Lit(lit) = operand { if let Literal::Str(str) = lit { diff --git a/crates/fsrt/Cargo.toml b/crates/fsrt/Cargo.toml index fb344a97..dc69be96 100644 --- a/crates/fsrt/Cargo.toml +++ b/crates/fsrt/Cargo.toml @@ -22,3 +22,4 @@ tracing-subscriber.workspace = true tracing-tree.workspace = true tracing.workspace = true walkdir.workspace = true +futures = "0.3.28" diff --git a/crates/fsrt/src/main.rs b/crates/fsrt/src/main.rs index 0581879a..706c69f4 100644 --- a/crates/fsrt/src/main.rs +++ b/crates/fsrt/src/main.rs @@ -1,6 +1,7 @@ #![allow(clippy::type_complexity)] use clap::{Parser, ValueHint}; use forge_loader::forgepermissions::ForgePermissions; +use futures::executor::block_on; use miette::{IntoDiagnostic, Result}; use std::{ collections::HashSet, @@ -26,7 +27,10 @@ use tracing_subscriber::{prelude::*, EnvFilter}; use tracing_tree::HierarchicalLayer; use forge_analyzer::{ - checkers::{AuthZChecker, AuthenticateChecker, PermissionChecker, PermissionVuln}, + checkers::{ + AuthZChecker, AuthenticateChecker, DefintionAnalysisRunner, PermissionChecker, + PermissionVuln, + }, ctx::{AppCtx, ModId}, definitions::{run_resolver, DefId, Environment}, interp::Interp, @@ -219,6 +223,8 @@ fn scan_directory(dir: PathBuf, function: Option<&str>, opts: Opts) -> Result, opts: Opts) -> Result { + let mut runner = DefintionAnalysisRunner::new(); + debug!("checking {func} at {path:?}"); + if let Err(err) = defintion_analysis_interp.run_checker( + def, + &mut runner, + path.clone(), + func.clone(), + ) { + warn!("error while getting definition analysis {func} in {path:?}: {err}"); + } let mut checker = AuthZChecker::new(); debug!("checking {func} at {path:?}"); if let Err(err) = interp.run_checker(def, &mut checker, path.clone(), func.clone()) @@ -236,8 +252,10 @@ fn scan_directory(dir: PathBuf, function: Option<&str>, opts: Opts) -> Result, opts: Opts) -> Result { + let mut runner = DefintionAnalysisRunner::new(); + debug!("checking {func} at {path:?}"); + if let Err(err) = defintion_analysis_interp.run_checker( + def, + &mut runner, + path.clone(), + func.clone(), + ) { + warn!("error while getting definition analysis {func} in {path:?}: {err}"); + } + let mut checker = AuthenticateChecker::new(); debug!("checking webtrigger {func} at {path:?}"); if let Err(err) = authn_interp.run_checker(def, &mut checker, path.clone(), func.clone()) { - println!("error while scanning {func} in {path:?}: {err}"); warn!("error while scanning {func} in {path:?}: {err}"); } reporter.add_vulnerabilities(checker.into_vulns()); + if run_permission_checker { + perm_interp.varid_to_value = defintion_analysis_interp.get_defs(); let mut checker2 = PermissionChecker::new(permissions_declared.clone()); if let Err(err) = perm_interp.run_checker(def, &mut checker2, path.clone(), func.clone()) { - println!("error while scanning {func} in {path:?}: {err}"); warn!("error while scanning {func} in {path:?}: {err}"); } all_used_permissions.extend(perm_interp.permissions.clone()); @@ -291,6 +320,7 @@ fn scan_directory(dir: PathBuf, function: Option<&str>, opts: Opts) -> Result println!("{report}"), } + Ok(proj) } From 89556ca97c78cabb966f3f9ba618e0e44f117032 Mon Sep 17 00:00:00 2001 From: James Gersbach Date: Mon, 7 Aug 2023 23:08:42 -0700 Subject: [PATCH 147/517] finalize secret scanner --- Cargo.lock | 105 -------- crates/forge_analyzer/src/checkers.rs | 246 +++++++++++++++++- crates/forge_analyzer/src/definitions.rs | 44 +++- crates/forge_analyzer/src/interp.rs | 11 +- crates/forge_analyzer/src/ir.rs | 4 +- .../src/permissions_resolver.rs | 2 - crates/fsrt/Cargo.toml | 1 - crates/fsrt/src/main.rs | 49 +++- secretdata.yaml | 6 + .../src/utils.js | 4 +- 10 files changed, 327 insertions(+), 145 deletions(-) create mode 100644 secretdata.yaml diff --git a/Cargo.lock b/Cargo.lock index 839b63f7..e752a0a8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -645,7 +645,6 @@ dependencies = [ "forge_analyzer", "forge_file_resolver", "forge_loader", - "futures", "miette 5.5.0", "rustc-hash", "serde", @@ -658,95 +657,6 @@ dependencies = [ "walkdir", ] -[[package]] -name = "futures" -version = "0.3.28" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23342abe12aba583913b2e62f22225ff9c950774065e4bfb61a19cd9770fec40" -dependencies = [ - "futures-channel", - "futures-core", - "futures-executor", - "futures-io", - "futures-sink", - "futures-task", - "futures-util", -] - -[[package]] -name = "futures-channel" -version = "0.3.28" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "955518d47e09b25bbebc7a18df10b81f0c766eaf4c4f1cccef2fca5f2a4fb5f2" -dependencies = [ - "futures-core", - "futures-sink", -] - -[[package]] -name = "futures-core" -version = "0.3.28" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4bca583b7e26f571124fe5b7561d49cb2868d79116cfa0eefce955557c6fee8c" - -[[package]] -name = "futures-executor" -version = "0.3.28" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ccecee823288125bd88b4d7f565c9e58e41858e47ab72e8ea2d64e93624386e0" -dependencies = [ - "futures-core", - "futures-task", - "futures-util", -] - -[[package]] -name = "futures-io" -version = "0.3.28" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fff74096e71ed47f8e023204cfd0aa1289cd54ae5430a9523be060cdb849964" - -[[package]] -name = "futures-macro" -version = "0.3.28" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89ca545a94061b6365f2c7355b4b32bd20df3ff95f02da9329b34ccc3bd6ee72" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.28", -] - -[[package]] -name = "futures-sink" -version = "0.3.28" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f43be4fe21a13b9781a69afa4985b0f6ee0e1afab2c6f454a8cf30e2b2237b6e" - -[[package]] -name = "futures-task" -version = "0.3.28" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76d3d132be6c0e6aa1534069c705a74a5997a356c0dc2f86a47765e5617c5b65" - -[[package]] -name = "futures-util" -version = "0.3.28" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26b01e40b772d54cf6c6d721c1d1abd0647a0106a12ecaa1c186273392a69533" -dependencies = [ - "futures-channel", - "futures-core", - "futures-io", - "futures-macro", - "futures-sink", - "futures-task", - "memchr", - "pin-project-lite", - "pin-utils", - "slab", -] - [[package]] name = "generic-array" version = "0.14.6" @@ -1416,12 +1326,6 @@ version = "0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116" -[[package]] -name = "pin-utils" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" - [[package]] name = "pmutil" version = "0.5.3" @@ -1858,15 +1762,6 @@ version = "0.3.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7bd3e3206899af3f8b12af284fafc038cc1dc2b41d1b89dd17297221c5d225de" -[[package]] -name = "slab" -version = "0.4.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6528351c9bc8ab22353f9d776db39a20288e8d6c37ef8cfe3317cf875eecfc2d" -dependencies = [ - "autocfg", -] - [[package]] name = "smallvec" version = "1.10.0" diff --git a/crates/forge_analyzer/src/checkers.rs b/crates/forge_analyzer/src/checkers.rs index 3d8d2392..5ccc55bc 100644 --- a/crates/forge_analyzer/src/checkers.rs +++ b/crates/forge_analyzer/src/checkers.rs @@ -21,7 +21,7 @@ use swc_core::ecma::{transforms::base::perf::Check, utils::Value::Known}; use tracing::{debug, info, warn}; use crate::{ - definitions::{Class, Const, DefId, DefKind, Environment, IntrinsicName, Value}, + definitions::{Class, Const, DefId, DefKind, Environment, IntrinsicName, PackageData, Value}, interp::{ Checker, Dataflow, EntryKind, EntryPoint, Frame, Interp, JoinSemiLattice, Runner, WithCallStack, @@ -246,6 +246,7 @@ impl<'cx> Runner<'cx> for AuthZChecker { &mut self, interp: &Interp<'cx, Self>, intrinsic: &'cx Intrinsic, + def: DefId, state: &Self::State, operands: Option>, ) -> ControlFlow<(), Self::State> { @@ -407,6 +408,7 @@ impl<'cx> Runner<'cx> for AuthenticateChecker { &mut self, interp: &Interp<'cx, Self>, intrinsic: &'cx Intrinsic, + def: DefId, state: &Self::State, operands: Option>, ) -> ControlFlow<(), Self::State> { @@ -504,6 +506,240 @@ impl WithCallStack for AuthNVuln { fn add_call_stack(&mut self, _stack: Vec) {} } +pub struct SecretDataflow { + needs_call: Vec, +} + +#[derive(Debug, PartialEq, Eq, Clone, Copy, PartialOrd, Ord)] +pub enum SecretState { + ALL, +} + +impl JoinSemiLattice for SecretState { + const BOTTOM: Self = Self::ALL; + + #[inline] + fn join_changed(&mut self, other: &Self) -> bool { + let old = mem::replace(self, max(*other, *self)); + old == *self + } + + #[inline] + fn join(&self, other: &Self) -> Self { + max(*other, *self) + } +} + +impl<'cx> Dataflow<'cx> for SecretDataflow { + type State = SecretState; + + fn with_interp>( + _interp: &Interp<'cx, C>, + ) -> Self { + Self { needs_call: vec![] } + } + + fn transfer_intrinsic>( + &mut self, + _interp: &mut Interp<'cx, C>, + _def: DefId, + _loc: Location, + _block: &'cx BasicBlock, + intrinsic: &'cx Intrinsic, + initial_state: Self::State, + operands: SmallVec<[crate::ir::Operand; 4]>, + ) -> Self::State { + initial_state + } + + fn transfer_call>( + &mut self, + interp: &Interp<'cx, C>, + def: DefId, + loc: Location, + _block: &'cx BasicBlock, + callee: &'cx crate::ir::Operand, + initial_state: Self::State, + operands: SmallVec<[crate::ir::Operand; 4]>, + ) -> Self::State { + let Some((callee_def, _body)) = self.resolve_call(interp, callee) else { + return initial_state; + }; + self.needs_call.push(callee_def); + SecretState::ALL + } + + fn join_term>( + &mut self, + interp: &mut Interp<'cx, C>, + def: DefId, + block: &'cx BasicBlock, + state: Self::State, + worklist: &mut WorkList, + ) { + self.super_join_term(interp, def, block, state, worklist); + for def in self.needs_call.drain(..) { + worklist.push_front_blocks(interp.env(), def); + } + } +} + +pub struct SecretChecker { + visit: bool, + vulns: Vec, +} + +impl SecretChecker { + pub fn new() -> Self { + Self { + visit: true, + vulns: vec![], + } + } + + pub fn into_vulns(self) -> impl IntoIterator { + // TODO: make this an associated function on the Checker trait. + self.vulns.into_iter() + } +} + +impl Default for SecretChecker { + fn default() -> Self { + Self::new() + } +} + +#[derive(Debug)] +pub struct SecretVuln { + stack: String, + entry_func: String, + file: PathBuf, +} + +impl SecretVuln { + fn new(callstack: Vec, env: &Environment, entry: &EntryPoint) -> Self { + let entry_func = match &entry.kind { + EntryKind::Function(func) => func.clone(), + EntryKind::Resolver(res, prop) => format!("{res}.{prop}"), + EntryKind::Empty => { + warn!("empty function"); + String::new() + } + }; + let file = entry.file.clone(); + let stack = Itertools::intersperse( + iter::once(&*entry_func).chain( + callstack + .into_iter() + .rev() + .map(|frame| env.def_name(frame.calling_function)), + ), + " -> ", + ) + .collect(); + Self { + stack, + entry_func, + file, + } + } +} + +impl fmt::Display for SecretVuln { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "Hardcoded secret vulnerability") + } +} + +impl IntoVuln for SecretVuln { + fn into_vuln(self, reporter: &Reporter) -> Vulnerability { + use std::collections::hash_map::DefaultHasher; + use std::hash::{Hash, Hasher}; + + let mut hasher = DefaultHasher::new(); + self.file + .iter() + .skip_while(|comp| *comp != "src") + .for_each(|comp| comp.hash(&mut hasher)); + self.entry_func.hash(&mut hasher); + self.stack.hash(&mut hasher); + Vulnerability { + check_name: format!("Secret-{}", hasher.finish()), + description: format!( + "Hardcoded secret found within codebase {} in {:?}.", + self.entry_func, self.file + ), + recommendation: "Use secrets as enviornment variables instead of hardcoding them.", + proof: format!("Hardcoded secret found in found via {}", self.stack), + severity: Severity::High, + app_key: reporter.app_key().to_owned(), + app_name: reporter.app_name().to_owned(), + date: reporter.current_date(), + } + } +} + +impl WithCallStack for SecretVuln { + fn add_call_stack(&mut self, _stack: Vec) {} +} + +impl<'cx> Runner<'cx> for SecretChecker { + type State = SecretState; + type Dataflow = SecretDataflow; + + fn visit_intrinsic( + &mut self, + interp: &Interp<'cx, Self>, + intrinsic: &'cx Intrinsic, + def: DefId, + state: &Self::State, + operands: Option>, + ) -> ControlFlow<(), Self::State> { + if let Intrinsic::JWTSign(package_data) = intrinsic { + if let Some(operand) = operands + .unwrap_or_default() + .get((package_data.secret_position - 1) as usize) + { + match operand { + Operand::Lit(lit) => { + let vuln = + SecretVuln::new(interp.callstack(), interp.env(), interp.entry()); + if let Literal::Str(_) = lit { + info!("Found a vuln!"); + self.vulns.push(vuln); + } + } + Operand::Var(var) => { + if let Base::Var(varid) = var.base { + if let Some(value) = + interp.get_value(def, varid, var.projections.get(0).cloned()) + { + match value { + Value::Const(_) | Value::Phi(_) => { + let vuln = SecretVuln::new( + interp.callstack(), + interp.env(), + interp.entry(), + ); + info!("Found a vuln!"); + self.vulns.push(vuln); + } + _ => {} + } + } + } + } + } + } + } + ControlFlow::Continue(*state) + } +} + +impl<'cx> Checker<'cx> for SecretChecker { + type Vuln = SecretVuln; +} + pub struct PermissionDataflow { needs_call: Vec<(DefId, Vec)>, pub varid_to_value: FxHashMap<(DefId, VarId, Option), Value>, @@ -605,8 +841,6 @@ impl<'cx> Dataflow<'cx> for PermissionDataflow { } } - println!("intrinsic argument {intrinsic_argument:?}"); - let mut permissions_within_call: Vec = vec![]; let intrinsic_func_type = intrinsic_argument.name.unwrap(); intrinsic_argument @@ -658,8 +892,6 @@ impl<'cx> Dataflow<'cx> for PermissionDataflow { } }); - println!("permissions found {permissions_within_call:?}"); - _interp .permissions .extend_from_slice(&permissions_within_call); @@ -697,7 +929,6 @@ impl<'cx> Dataflow<'cx> for PermissionDataflow { inst: &'cx Inst, initial_state: Self::State, ) -> Self::State { - println!("\tinst {inst}"); match inst { Inst::Expr(rvalue) => { self.transfer_rvalue(interp, def, loc, block, rvalue, initial_state) @@ -824,6 +1055,7 @@ impl<'cx> Runner<'cx> for PermissionChecker { &mut self, interp: &Interp<'cx, Self>, intrinsic: &'cx Intrinsic, + def: DefId, state: &Self::State, operands: Option>, ) -> ControlFlow<(), Self::State> { @@ -864,6 +1096,7 @@ impl<'cx> Runner<'cx> for DefintionAnalysisRunner { &mut self, interp: &Interp<'cx, Self>, intrinsic: &'cx Intrinsic, + def: DefId, state: &Self::State, operands: Option>, ) -> ControlFlow<(), Self::State> { @@ -958,7 +1191,6 @@ impl<'cx> Dataflow<'cx> for DefintionAnalysisRunner { inst: &'cx Inst, initial_state: Self::State, ) -> Self::State { - println!("\tinst {inst}"); match inst { Inst::Expr(rvalue) => { self.transfer_rvalue(interp, def, loc, block, rvalue, initial_state) diff --git a/crates/forge_analyzer/src/definitions.rs b/crates/forge_analyzer/src/definitions.rs index d2684e2d..7fde46b3 100644 --- a/crates/forge_analyzer/src/definitions.rs +++ b/crates/forge_analyzer/src/definitions.rs @@ -6,6 +6,8 @@ use crate::utils::calls_method; use forge_file_resolver::{FileResolver, ForgeResolver}; use forge_utils::{create_newtype, FxHashMap}; +use itertools::Itertools; +use serde::{Deserialize, Serialize}; use smallvec::SmallVec; use swc_core::{ common::SyntaxContext, @@ -149,6 +151,7 @@ struct ModuleDefs { pub fn run_resolver( modules: &TiSlice, file_resolver: &ForgeResolver, + secret_packages: Vec, ) -> Environment { let mut environment = Environment::new(); for (curr_mod, module) in modules.iter_enumerated() { @@ -207,6 +210,7 @@ pub fn run_resolver( res: &mut environment, curr_class: None, curr_function: None, + secret_packages: secret_packages.clone(), // remove the clone module: curr_mod, parent: None, }; @@ -523,11 +527,11 @@ pub enum IntrinsicName { Other, } -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct JWTSigningDetails { - package: String, - signing_function: String, - position_arg: i8, +#[derive(Serialize, Deserialize, PartialEq, Debug, Clone, Eq)] +pub struct PackageData { + pub package_name: String, + pub function_name: String, + pub secret_position: u8, } struct Lowerer<'cx> { @@ -768,6 +772,7 @@ struct FunctionCollector<'cx> { module: ModId, curr_class: Option, curr_function: Option, + secret_packages: Vec, parent: Option, } @@ -778,6 +783,7 @@ struct FunctionAnalyzer<'cx> { assigning_to: Option, pub body: Body, block: BasicBlockId, + secret_packages: Vec, operand_stack: Vec, in_lhs: bool, } @@ -889,13 +895,26 @@ impl<'cx> FunctionAnalyzer<'cx> { } } [PropPath::Def(def), ref authn @ .., PropPath::Static(ref last)] - if *last == *"sign" - || *last == *"requestConfluence" + if self.secret_packages.iter().any(|package_data| { + return *package_data.function_name == *last && Some(&ImportKind::Default) - == self.res.as_foreign_import(def, "jsonwebtoken") => + == self.res.as_foreign_import(def, &package_data.package_name); + }) => { - println!("found jwt "); - Some(Intrinsic::Authorize(IntrinsicName::RequestJira)) + Some(Intrinsic::JWTSign( + self.secret_packages + .iter() + .cloned() + .filter(|package| { + *package.function_name == *last + && Some(&ImportKind::Default) + == self.res.as_foreign_import(def, &package.package_name) + }) + .collect_vec() + .get(0) + .unwrap() + .clone(), + )) } [PropPath::Def(def), PropPath::Static(ref s), ..] if is_storage_read(s) => { match self.res.as_foreign_import(def, "@forge/api") { @@ -1982,6 +2001,7 @@ impl Visit for FunctionCollector<'_> { module: self.module, current_def: *owner, assigning_to: None, + secret_packages: self.secret_packages.clone(), body, block: BasicBlockId::default(), operand_stack: vec![], @@ -2029,6 +2049,7 @@ impl Visit for FunctionCollector<'_> { res: self.res, module: self.module, current_def: *owner, + secret_packages: self.secret_packages.clone(), assigning_to: None, body, block: BasicBlockId::default(), @@ -2080,6 +2101,7 @@ impl Visit for FunctionCollector<'_> { module: self.module, current_def: owner, assigning_to: None, + secret_packages: self.secret_packages.clone(), body, block: BasicBlockId::default(), operand_stack: vec![], @@ -2145,6 +2167,7 @@ impl Visit for FunctionCollector<'_> { module: self.module, current_def: owner, assigning_to: None, + secret_packages: self.secret_packages.clone(), body: Body::with_owner(owner), block: BasicBlockId::default(), operand_stack: vec![], @@ -2248,6 +2271,7 @@ impl FunctionCollector<'_> { current_def: owner, assigning_to: None, body, + secret_packages: self.secret_packages.clone(), block: BasicBlockId::default(), operand_stack: vec![], in_lhs: false, diff --git a/crates/forge_analyzer/src/interp.rs b/crates/forge_analyzer/src/interp.rs index 4dd907be..5c244518 100644 --- a/crates/forge_analyzer/src/interp.rs +++ b/crates/forge_analyzer/src/interp.rs @@ -359,6 +359,7 @@ pub trait Runner<'cx>: Sized { &mut self, interp: &Interp<'cx, Self>, intrinsic: &'cx Intrinsic, + def: DefId, state: &Self::State, operands: Option>, ) -> ControlFlow<(), Self::State>; @@ -407,13 +408,14 @@ pub trait Runner<'cx>: Sized { &mut self, interp: &Interp<'cx, Self>, rvalue: &'cx Rvalue, + def: DefId, id: BasicBlockId, curr_state: &Self::State, ) -> ControlFlow<(), Self::State> { debug!("visiting rvalue {rvalue:?} with {curr_state:?}"); match rvalue { Rvalue::Intrinsic(intrinsic, operands) => { - self.visit_intrinsic(interp, intrinsic, curr_state, Some(operands.clone())) + self.visit_intrinsic(interp, intrinsic, def, curr_state, Some(operands.clone())) } Rvalue::Call(callee, args) => self.visit_call(interp, callee, args, id, curr_state), Rvalue::Unary(_, _) @@ -436,8 +438,10 @@ pub trait Runner<'cx>: Sized { let mut curr_state = interp.block_state(def, id).join(curr_state); for stmt in block { match stmt { - Inst::Expr(r) => curr_state = self.visit_rvalue(interp, r, id, &curr_state)?, - Inst::Assign(_, r) => curr_state = self.visit_rvalue(interp, r, id, &curr_state)?, + Inst::Expr(r) => curr_state = self.visit_rvalue(interp, r, def, id, &curr_state)?, + Inst::Assign(_, r) => { + curr_state = self.visit_rvalue(interp, r, def, id, &curr_state)? + } } } match block.successors() { @@ -721,7 +725,6 @@ impl<'cx, C: Runner<'cx>> Interp<'cx, C> { let arguments = self.callstack_arguments.pop(); let name = self.env.def_name(def); debug!("Dataflow: {name} - {block_id}"); - println!("Dataflow: {def:?} {name} - {block_id}"); self.dataflow_visited.insert(def); let func = self.env().def_ref(def).expect_body(); self.curr_body.set(Some(func)); diff --git a/crates/forge_analyzer/src/ir.rs b/crates/forge_analyzer/src/ir.rs index 1c04a1a2..8ed9bf8b 100644 --- a/crates/forge_analyzer/src/ir.rs +++ b/crates/forge_analyzer/src/ir.rs @@ -36,7 +36,7 @@ use crate::definitions::DefId; use crate::definitions::DefKind; use crate::definitions::Environment; use crate::definitions::IntrinsicName; -use crate::definitions::JWTSigningDetails; +use crate::definitions::PackageData; use crate::definitions::Value; pub const STARTING_BLOCK: BasicBlockId = BasicBlockId(0); @@ -74,7 +74,7 @@ pub enum Intrinsic { Fetch, ApiCall(IntrinsicName), SafeCall(IntrinsicName), - JWTSign(JWTSigningDetails), + JWTSign(PackageData), EnvRead, StorageRead, } diff --git a/crates/forge_permission_resolver/src/permissions_resolver.rs b/crates/forge_permission_resolver/src/permissions_resolver.rs index 1aa28fa3..e4118d6d 100644 --- a/crates/forge_permission_resolver/src/permissions_resolver.rs +++ b/crates/forge_permission_resolver/src/permissions_resolver.rs @@ -67,8 +67,6 @@ pub fn check_url_for_permissions( request: RequestType, url: &str, ) -> Vec { - println!("check_url_for_permissions {request:?} {url:?}"); - let mut length_of_regex = endpoint_regex .iter() .map(|(string, regex)| (regex.as_str().len(), &*string)) diff --git a/crates/fsrt/Cargo.toml b/crates/fsrt/Cargo.toml index dc69be96..fb344a97 100644 --- a/crates/fsrt/Cargo.toml +++ b/crates/fsrt/Cargo.toml @@ -22,4 +22,3 @@ tracing-subscriber.workspace = true tracing-tree.workspace = true tracing.workspace = true walkdir.workspace = true -futures = "0.3.28" diff --git a/crates/fsrt/src/main.rs b/crates/fsrt/src/main.rs index 706c69f4..d06df67e 100644 --- a/crates/fsrt/src/main.rs +++ b/crates/fsrt/src/main.rs @@ -1,7 +1,6 @@ #![allow(clippy::type_complexity)] use clap::{Parser, ValueHint}; use forge_loader::forgepermissions::ForgePermissions; -use futures::executor::block_on; use miette::{IntoDiagnostic, Result}; use std::{ collections::HashSet, @@ -29,10 +28,10 @@ use tracing_tree::HierarchicalLayer; use forge_analyzer::{ checkers::{ AuthZChecker, AuthenticateChecker, DefintionAnalysisRunner, PermissionChecker, - PermissionVuln, + PermissionVuln, SecretChecker, }, ctx::{AppCtx, ModId}, - definitions::{run_resolver, DefId, Environment}, + definitions::{run_resolver, DefId, Environment, PackageData}, interp::Interp, reporter::Reporter, resolver::resolve_calls, @@ -100,6 +99,7 @@ impl ForgeProject { fn with_files_and_sourceroot, I: IntoIterator>( src: P, iter: I, + secret_packages: Vec, ) -> Self { let sm = Arc::::default(); let target = EsVersion::latest(); @@ -133,7 +133,7 @@ impl ForgeProject { }); let keys = ctx.module_ids().collect::>(); debug!(?keys); - let env = run_resolver(ctx.modules(), ctx.file_resolver()); + let env = run_resolver(ctx.modules(), ctx.file_resolver(), secret_packages); Self { sm, ctx, @@ -190,7 +190,14 @@ fn scan_directory(dir: PathBuf, function: Option<&str>, opts: Opts) -> Result = HashSet::from_iter(permission_scopes.iter().map(|s| s.replace("\"", ""))); - println!("permission scopes: {permission_scopes:?}"); + let secret_packages: Vec = + if let Result::Ok(f) = std::fs::File::open("secretdata.yaml") { + let scrape_config: Vec = + serde_yaml::from_reader(f).expect("Failed to deserialize package"); + scrape_config + } else { + vec![] + }; let paths = collect_sourcefiles(dir.join("src/")).collect::>(); @@ -203,11 +210,8 @@ fn scan_directory(dir: PathBuf, function: Option<&str>, opts: Opts) -> Result, opts: Opts) -> Result, opts: Opts) -> Result, opts: Opts) -> Result, opts: Opts) -> Result, opts: Opts) -> Result Date: Tue, 8 Aug 2023 10:13:43 -0700 Subject: [PATCH 148/517] patch to definition analysis --- crates/forge_analyzer/src/checkers.rs | 82 ++++++++----------- crates/forge_analyzer/src/interp.rs | 4 +- .../src/index.jsx | 2 +- .../src/utils.js | 10 +-- 4 files changed, 41 insertions(+), 57 deletions(-) diff --git a/crates/forge_analyzer/src/checkers.rs b/crates/forge_analyzer/src/checkers.rs index 5ccc55bc..c6e889b9 100644 --- a/crates/forge_analyzer/src/checkers.rs +++ b/crates/forge_analyzer/src/checkers.rs @@ -829,14 +829,12 @@ impl<'cx> Dataflow<'cx> for PermissionDataflow { } } } - if let Some(operand) = second { - if let Operand::Var(variable) = operand { - if let Base::Var(varid) = variable.base { - if let Some(value) = - _interp.get_value(_def, varid, Some(Projection::Known("method".into()))) - { - self.handle_second_arg(value, &mut intrinsic_argument); - } + if let Some(Operand::Var(variable)) = second { + if let Base::Var(varid) = variable.base { + if let Some(value) = + _interp.get_value(_def, varid, Some(Projection::Known("method".into()))) + { + self.handle_second_arg(value, &mut intrinsic_argument); } } } @@ -892,6 +890,8 @@ impl<'cx> Dataflow<'cx> for PermissionDataflow { } }); + println!("intrinsic argument {intrinsic_argument:?}"); + _interp .permissions .extend_from_slice(&permissions_within_call); @@ -920,25 +920,6 @@ impl<'cx> Dataflow<'cx> for PermissionDataflow { initial_state } - fn transfer_inst>( - &mut self, - interp: &mut Interp<'cx, C>, - def: DefId, - loc: Location, - block: &'cx BasicBlock, - inst: &'cx Inst, - initial_state: Self::State, - ) -> Self::State { - match inst { - Inst::Expr(rvalue) => { - self.transfer_rvalue(interp, def, loc, block, rvalue, initial_state) - } - Inst::Assign(var, rvalue) => { - self.transfer_rvalue(interp, def, loc, block, rvalue, initial_state) - } - } - } - fn transfer_block>( &mut self, interp: &mut Interp<'cx, C>, @@ -1199,13 +1180,18 @@ impl<'cx> Dataflow<'cx> for DefintionAnalysisRunner { match var.base { Base::Var(varid) => match rvalue { Rvalue::Call(operand, _) => { - if let Some((defid, varid)) = resolve_var_from_operand(operand) { - // interp.expecting_value.push_back((defid, (varid, defid))); - } - if let Some((var, varid)) = resolve_var_from_operand(operand) { - // if let Some(return_value) = interp.return_value_alt.get(&defid) { - // self.add_value(def, varid, return_value.clone()); - // } + if let Operand::Var(variable) = operand { + if let Base::Var(varid) = variable.base { + if let Some(VarKind::GlobalRef(defid)) = + interp.body().vars.get(varid) + { + if let Base::Var(varid_to_assign) = var.base { + interp + .expected_return_values + .insert(*defid, (def, varid_to_assign)); + } + } + } } } Rvalue::Read(_) => { @@ -1269,21 +1255,19 @@ impl<'cx> Dataflow<'cx> for DefintionAnalysisRunner { } for (varid, varkind) in interp.body().vars.clone().iter_enumerated() { - match varkind { - VarKind::Ret => { - for (defid, (varid_value, defid_value)) in interp.expecting_value.clone() { - if def == defid.clone() { - if let Some(value) = interp.get_value(def, varid, None).clone() { - interp.add_value(def, varid, value.clone(), None); - } - } - } + if &VarKind::Ret == varkind { + if let Some((defid_calling_func, varid_calling_func)) = + interp.expected_return_values.get(&def) + { if let Some(value) = interp.get_value(def, varid, None) { - interp.return_value = Some((value.clone(), def)); - //interp.return_value_alt.insert(def, value.clone()); + interp.add_value( + *defid_calling_func, + *varid_calling_func, + value.clone(), + None, + ); } } - _ => {} } } @@ -1515,10 +1499,8 @@ impl<'cx> Dataflow<'cx> for DefintionAnalysisRunner { let value = _interp.get_value(def, varid, None); if let Some(value) = value { match value { - Value::Const(const_val) => { - if let Const::Literal(str) = const_val { - return vec![Some(str.clone())]; - } + Value::Const(Const::Literal(str)) => { + return vec![Some(str.clone())]; } Value::Phi(phi_val) => { return phi_val diff --git a/crates/forge_analyzer/src/interp.rs b/crates/forge_analyzer/src/interp.rs index 5c244518..ae12bf43 100644 --- a/crates/forge_analyzer/src/interp.rs +++ b/crates/forge_analyzer/src/interp.rs @@ -3,6 +3,7 @@ use std::{ cell::{Cell, RefCell, RefMut}, collections::{BTreeMap, HashMap, VecDeque}, fmt::{self, Display}, + hash::Hash, io::{self, Write}, iter, marker::PhantomData, @@ -502,6 +503,7 @@ pub struct Interp<'cx, C: Runner<'cx>> { callstack: RefCell>, pub callstack_arguments: Vec>, // vulns: RefCell>, + pub expected_return_values: HashMap, pub permissions: Vec, pub expecting_value: VecDeque<(DefId, (VarId, DefId))>, pub jira_permission_resolver: PermissionHashMap, @@ -588,6 +590,7 @@ impl<'cx, C: Runner<'cx>> Interp<'cx, C> { expecting_value: VecDeque::default(), callstack: RefCell::new(Vec::new()), // vulns: RefCell::new(Vec::new()), + expected_return_values: HashMap::default(), permissions: Vec::new(), varid_to_value: DefinitionAnalysisMap::default(), jira_permission_resolver, @@ -728,7 +731,6 @@ impl<'cx, C: Runner<'cx>> Interp<'cx, C> { self.dataflow_visited.insert(def); let func = self.env().def_ref(def).expect_body(); self.curr_body.set(Some(func)); - let block = func.block(block_id); let mut before_state = self.block_state(def, block_id); let block = func.block(block_id); for &pred in func.predecessors(block_id) { diff --git a/test-apps/jira-damn-vulnerable-forge-app/src/index.jsx b/test-apps/jira-damn-vulnerable-forge-app/src/index.jsx index 4d47ef45..fca8fbde 100755 --- a/test-apps/jira-damn-vulnerable-forge-app/src/index.jsx +++ b/test-apps/jira-damn-vulnerable-forge-app/src/index.jsx @@ -98,7 +98,7 @@ function SecureGlance() { return ''; } const [flagVal] = useState(async () => { - const issueData = await fetchIssueSummary(platformContext.issueKey, value); + const issueData = await fetchIssueSummary(platformContext.issueKey, "test_value_passed_in_as_argument"); const test = writeComment("test", "test"); return JSON.stringify(issueData + test); }); diff --git a/test-apps/jira-damn-vulnerable-forge-app/src/utils.js b/test-apps/jira-damn-vulnerable-forge-app/src/utils.js index f8969fc7..882320ad 100755 --- a/test-apps/jira-damn-vulnerable-forge-app/src/utils.js +++ b/test-apps/jira-damn-vulnerable-forge-app/src/utils.js @@ -8,7 +8,7 @@ import {another_export, newExport} from './newexports.js' import func_defult from './export_default'; import my_function from "./export_default2.js" -export async function fetchIssueSummary(issueIdOrKey, url) { +export async function fetchIssueSummary(issueIdOrKey, test_value) { let obj = { method: 'POST', @@ -18,7 +18,7 @@ export async function fetchIssueSummary(issueIdOrKey, url) { }, }; - var token = jwt.sign({ foo: 'bar' }, "process.env.SECRET"); + var token = jwt.sign({ foo: 'bar' }, process.env.SECRET); module_exports_func() @@ -44,15 +44,15 @@ export async function fetchIssueSummary(issueIdOrKey, url) { const resp = await api .asApp() - .requestJira(a_url, obj); + .requestJira(get_random_string(), obj); const data = await resp.json(); console.log(JSON.stringify(data)); return data['fields']['summary']; } -function get_route() { +function get_random_string() { //return a_url = route`/rest/api/3/issue/${issueIdOrKey}?fields=summary`; - return route`/bananas/${issueIdOrKey}?fields=summary`; + return "test_string_from_get_random_string"; } export async function writeComment(issueIdOrKey, comment) { From 5227f9f84422a72943004c00153cd0182db31cf7 Mon Sep 17 00:00:00 2001 From: James Gersbach Date: Tue, 8 Aug 2023 10:30:25 -0700 Subject: [PATCH 149/517] formatting --- crates/forge_permission_resolver/src/permissions_resolver.rs | 1 - crates/forge_permission_resolver/src/test.rs | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/crates/forge_permission_resolver/src/permissions_resolver.rs b/crates/forge_permission_resolver/src/permissions_resolver.rs index aff06067..d13fc7a9 100644 --- a/crates/forge_permission_resolver/src/permissions_resolver.rs +++ b/crates/forge_permission_resolver/src/permissions_resolver.rs @@ -102,7 +102,6 @@ pub fn get_permission_resolver(url: &str) -> (PermissionHashMap, HashMap Date: Tue, 8 Aug 2023 17:01:56 -0700 Subject: [PATCH 150/517] cleaning up --- crates/forge_analyzer/src/analyzer.rs | 1 + crates/forge_analyzer/src/checkers.rs | 2 ++ crates/forge_analyzer/src/definitions.rs | 15 ++++++++++----- crates/forge_analyzer/src/ir.rs | 1 - secretdata.yaml | 2 +- .../jira-damn-vulnerable-forge-app/src/utils.js | 8 ++++++-- 6 files changed, 20 insertions(+), 9 deletions(-) diff --git a/crates/forge_analyzer/src/analyzer.rs b/crates/forge_analyzer/src/analyzer.rs index 3e9ba2aa..3102f6bc 100644 --- a/crates/forge_analyzer/src/analyzer.rs +++ b/crates/forge_analyzer/src/analyzer.rs @@ -237,6 +237,7 @@ impl Visit for FunctionAnalyzer<'_> { _ => {} } } + // we don't need to add this to the IR, since we know it's useless return; } else { IrStmt::Call(id.into()) diff --git a/crates/forge_analyzer/src/checkers.rs b/crates/forge_analyzer/src/checkers.rs index c6e889b9..aa9c0eff 100644 --- a/crates/forge_analyzer/src/checkers.rs +++ b/crates/forge_analyzer/src/checkers.rs @@ -1172,6 +1172,8 @@ impl<'cx> Dataflow<'cx> for DefintionAnalysisRunner { inst: &'cx Inst, initial_state: Self::State, ) -> Self::State { + + println!("\t inst {inst}"); match inst { Inst::Expr(rvalue) => { self.transfer_rvalue(interp, def, loc, block, rvalue, initial_state) diff --git a/crates/forge_analyzer/src/definitions.rs b/crates/forge_analyzer/src/definitions.rs index 7fde46b3..507f1fc2 100644 --- a/crates/forge_analyzer/src/definitions.rs +++ b/crates/forge_analyzer/src/definitions.rs @@ -1156,7 +1156,7 @@ impl<'cx> FunctionAnalyzer<'cx> { let callee = match callee { CalleeRef::Super => Operand::Var(Variable::SUPER), CalleeRef::Import => Operand::UNDEF, - CalleeRef::Expr(expr) => self.lower_expr(expr, None), + CalleeRef::Expr(expr) => self.get_operand_for_call(expr), }; let first_arg = args.first().map(|expr| &*expr.expr); @@ -1521,7 +1521,6 @@ impl<'cx> FunctionAnalyzer<'cx> { Expr::Call(CallExpr { callee, args, .. }) => self.lower_call(callee.into(), args), Expr::New(NewExpr { callee, args, .. }) => { if let Expr::Ident(ident) = &**callee { - let someething = self.res.sym_to_def(ident.to_id(), self.module); // remove the clone return self.lower_call( CalleeRef::Expr(callee), @@ -1938,7 +1937,9 @@ impl Visit for FunctionCollector<'_> { &**&function, self.res.default_export(self.module), ), - Expr::Class(ClassExpr { ident, class }) => {} + Expr::Class(ClassExpr { ident, class }) => { + + } _ => {} } } @@ -1972,7 +1973,7 @@ impl Visit for FunctionCollector<'_> { } fn visit_constructor(&mut self, n: &Constructor) { - n.visit_children_with(self); + //n.visit_children_with(self); if let Some(class_def) = self.curr_class { if let DefKind::Class(class) = self.res.clone().def_ref(class_def) { if let Some((_, owner)) = &class @@ -2018,7 +2019,7 @@ impl Visit for FunctionCollector<'_> { } fn visit_class_method(&mut self, n: &ClassMethod) { - n.visit_children_with(self); + //n.visit_children_with(self); if let Some(class_def) = self.curr_class { if let DefKind::Class(class) = self.res.clone().def_ref(class_def) { if let Some((_, owner)) = &class.pub_members.iter().find(|(name, defid)| { @@ -2682,6 +2683,8 @@ impl Visit for ImportCollector<'_> { } None => warn!("unable to find default import for {}", &self.current_import), } + + // POI } Err(_) => { let foreign_id = self.foreign_defs.push_and_get_key(ForeignItem { @@ -2837,6 +2840,8 @@ impl Visit for ExportCollector<'_> { n.decl.visit_children_with(self); } + // POI + fn visit_export_named_specifier(&mut self, n: &ExportNamedSpecifier) { let orig_id = n.orig.as_id(); let orig = self.add_export(DefRes::default(), orig_id); diff --git a/crates/forge_analyzer/src/ir.rs b/crates/forge_analyzer/src/ir.rs index 8ed9bf8b..281bc260 100644 --- a/crates/forge_analyzer/src/ir.rs +++ b/crates/forge_analyzer/src/ir.rs @@ -415,7 +415,6 @@ impl Body { env: &'cx Environment, callee: &Operand, ) -> Option<(DefId, &'cx Body)> { - /* callee is the name of the function that is being called */ match callee { Operand::Var(Variable { diff --git a/secretdata.yaml b/secretdata.yaml index ce6eafce..11865b0b 100644 --- a/secretdata.yaml +++ b/secretdata.yaml @@ -1,4 +1,4 @@ -- package_name: jwt +- package_name: jsonwebtoken function_name: sign secret_position: 2 - package_name: jwt-simple diff --git a/test-apps/jira-damn-vulnerable-forge-app/src/utils.js b/test-apps/jira-damn-vulnerable-forge-app/src/utils.js index 882320ad..29ee6136 100755 --- a/test-apps/jira-damn-vulnerable-forge-app/src/utils.js +++ b/test-apps/jira-damn-vulnerable-forge-app/src/utils.js @@ -1,12 +1,13 @@ import api, { route } from '@forge/api'; -import jwt from "jwt"; +import jwt from "jsonwebtoken"; import {testFunctionFromTestFile} from './testfile'; import module_exports_func from './moduleex.js' import {func_from_exports, diffunc} from "./exportse.js" import {another_export, newExport} from './newexports.js' import func_defult from './export_default'; -import my_function from "./export_default2.js" +import my_function from "./export_default2.js"; +import ANewClass from './anewclass'; export async function fetchIssueSummary(issueIdOrKey, test_value) { @@ -18,6 +19,9 @@ export async function fetchIssueSummary(issueIdOrKey, test_value) { }, }; + + let a_class = new ANewClass(); + a_class.function_a_new_class(); var token = jwt.sign({ foo: 'bar' }, process.env.SECRET); From ba4c9d84dd092a948b567bc6c8d99757b5bef62c Mon Sep 17 00:00:00 2001 From: James Gersbach Date: Wed, 9 Aug 2023 20:10:52 -0700 Subject: [PATCH 151/517] functioning definition export --- crates/forge_analyzer/src/checkers.rs | 1 - crates/forge_analyzer/src/definitions.rs | 96 +++++++++++++++---- crates/forge_analyzer/src/interp.rs | 3 + crates/forge_analyzer/src/ir.rs | 1 - .../src/permissions_resolver.rs | 3 + .../src/utils.js | 9 +- 6 files changed, 86 insertions(+), 27 deletions(-) diff --git a/crates/forge_analyzer/src/checkers.rs b/crates/forge_analyzer/src/checkers.rs index aa9c0eff..83f244c6 100644 --- a/crates/forge_analyzer/src/checkers.rs +++ b/crates/forge_analyzer/src/checkers.rs @@ -1172,7 +1172,6 @@ impl<'cx> Dataflow<'cx> for DefintionAnalysisRunner { inst: &'cx Inst, initial_state: Self::State, ) -> Self::State { - println!("\t inst {inst}"); match inst { Inst::Expr(rvalue) => { diff --git a/crates/forge_analyzer/src/definitions.rs b/crates/forge_analyzer/src/definitions.rs index 507f1fc2..13885356 100644 --- a/crates/forge_analyzer/src/definitions.rs +++ b/crates/forge_analyzer/src/definitions.rs @@ -689,7 +689,6 @@ impl ResolverTable { self.recent_names.get(&(sym, module)).copied() } - // this won't work becasue there may be names that are the same acrosss modules ... #[inline] fn get_default(&self, module: ModId) -> Option { for (defid, sym) in self.names.iter_enumerated() { @@ -1065,6 +1064,8 @@ impl<'cx> FunctionAnalyzer<'cx> { } fn get_operand_for_call(&mut self, expr: &Expr) -> Operand { + println!("expr {expr:?}"); + if let Expr::Ident(ident) = expr { if let Some(DefKind::Class(class)) = self.res.sym_to_def(ident.to_id(), self.module) { let id = ident.to_id(); @@ -1073,11 +1074,8 @@ impl<'cx> FunctionAnalyzer<'cx> { }; if let Some((_, def_constructor)) = class.pub_members.iter().find(|(name, defid)| { - if name == "constructor" { - return true; - } else { - false - } + println!("found a constructor method"); + return name == "constructor"; }) { let var = self.body.get_or_insert_global(def_constructor.clone()); @@ -1090,11 +1088,7 @@ impl<'cx> FunctionAnalyzer<'cx> { if let MemberProp::Ident(ident) = prop { if let Some((_, def_constructor)) = class.pub_members.iter().find(|(name, defid)| { - if name == &ident.sym { - return true; - } else { - false - } + return name == &ident.sym; }) { let var = self.body.get_or_insert_global(def_constructor.clone()); @@ -1915,14 +1909,18 @@ impl Visit for LocalDefiner<'_> { impl Visit for FunctionCollector<'_> { fn visit_export_default_decl(&mut self, n: &ExportDefaultDecl) { if let Some(defid) = self.res.default_export(self.module) { - if let Some(defid) = self.res.resolver.get_default(self.module) { - // self.res.defs.defs.insert(defid, DefRes::Function(())); ask josh about this - } + // we don't need to check that it is either a class or a function because + // it will get caught by the respective methods. + println!("visit_export_default_decl {defid:?} {:?}", self.module); + + println!("all classes {:?}", self.res.defs.classes); + self.curr_class = Some(defid); self.curr_function = Some(defid); } n.visit_children_with(self); + self.curr_class = None; self.curr_function = None; } @@ -1938,7 +1936,7 @@ impl Visit for FunctionCollector<'_> { self.res.default_export(self.module), ), Expr::Class(ClassExpr { ident, class }) => { - + self.curr_class = self.res.default_export(self.module) } _ => {} } @@ -1953,9 +1951,19 @@ impl Visit for FunctionCollector<'_> { if let Some(defid) = self.res.get_sym(ident_property.to_id(), self.module) { + println!("found def func ... {defid:?}"); self.handle_function(&**&n.function, Some(defid)); } } + Expr::Class(class) => { + println!("class ~~~ {ident_property:?}"); + if let Some(defid) = + self.res.get_sym(ident_property.to_id(), self.module) + { + println!("found def ... {defid:?}"); + self.curr_class = Some(defid); + } + } _ => {} } } @@ -1969,6 +1977,9 @@ impl Visit for FunctionCollector<'_> { if let Some(DefKind::Class(class)) = self.res.sym_to_def(n.ident.to_id(), self.module) { self.curr_class = Some(class.def); // sets the current class so that mehtods are assigned to the proper class } + + // need a way to get the curr class without using the sym ... + n.visit_children_with(self); } @@ -2020,13 +2031,14 @@ impl Visit for FunctionCollector<'_> { fn visit_class_method(&mut self, n: &ClassMethod) { //n.visit_children_with(self); + println!("visitng class method {:?}", self.curr_class); if let Some(class_def) = self.curr_class { + println!("1 ~ {n:?}"); if let DefKind::Class(class) = self.res.clone().def_ref(class_def) { + println!("2 ~ {n:?}"); if let Some((_, owner)) = &class.pub_members.iter().find(|(name, defid)| { if let PropName::Ident(ident) = &n.key { - if name == &ident.sym { - return true; - } + return name == &ident.sym; } false }) { @@ -2370,6 +2382,35 @@ fn as_resolver_def<'a>( impl Visit for Lowerer<'_> { noop_visit_type!(); + fn visit_export_default_decl(&mut self, n: &ExportDefaultDecl) { + if let Some(defid) = self.res.default_export(self.curr_mod) { + self.curr_class = Some(defid); + } + n.visit_children_with(self); + self.curr_class = None; + } + + fn visit_assign_expr(&mut self, n: &AssignExpr) { + if let Some(ident) = ident_from_assign_expr(n) { + if ident.sym.to_string() == "module" { + if let Some(mem_expr) = mem_expr_from_assign(n) { + if let MemberProp::Ident(ident_property) = &mem_expr.prop { + if ident_property.sym.to_string() == "exports" { + if let Expr::Class(ClassExpr { ident, class }) = &*n.right { + if let Some(defid) = self.res.default_export(self.curr_mod) { + self.curr_class = Some(defid); + } + n.visit_children_with(self); + self.curr_class = None; + } + } + } + } + } + } + n.visit_children_with(self); + } + fn visit_class_decl(&mut self, n: &ClassDecl) { if let Some(DefKind::Class(class)) = self.res.sym_to_def(n.ident.to_id(), self.curr_mod) { self.curr_class = Some(class.def); // sets the curr class so that mehtods are assigned to the proper class @@ -2394,9 +2435,12 @@ impl Visit for Lowerer<'_> { fn visit_class_method(&mut self, n: &ClassMethod) { n.visit_children_with(self); + println!("adding class mehtod .... "); if let Some(class_def) = self.curr_class { if let DefKind::Class(class) = self.res.def_mut(class_def) { if let PropName::Ident(ident) = &n.key { + println!("adding class mehtod .... "); + let owner = self.res .add_anonymous("__CLASSMETHOD", AnonType::Closure, self.curr_mod); @@ -2608,6 +2652,12 @@ impl ExportCollector<'_> { }; self.default = Some(defid); } + + fn add_default_with_id(&mut self, def: DefRes, defid: DefId) { + self.res_table.names.push("default".into()); + self.res_table.owning_module.push(self.curr_mod); + self.default = Some(defid); + } } impl Visit for ImportCollector<'_> { @@ -2673,6 +2723,8 @@ impl Visit for ImportCollector<'_> { debug_assert_ne!(self.curr_mod, mod_id); match self.resolver.default_export(mod_id) { Some(def) => { + println!("def -- {def:?} {:?} {:?}", mod_id, self.curr_mod); + self.resolver.resolver.symbol_to_id.insert( Symbol { module: self.curr_mod, @@ -2681,7 +2733,7 @@ impl Visit for ImportCollector<'_> { def, ); } - None => warn!("unable to find default import for {}", &self.current_import), + None => println!("unable to find default import for {}", &self.current_import), } // POI @@ -2781,6 +2833,9 @@ impl Visit for ExportCollector<'_> { Expr::Fn(FnExpr { ident, function }) => { self.add_export(DefRes::Function(()), ident_property.to_id()); } + Expr::Class(_) => { + self.add_export(DefRes::Class(()), ident_property.to_id()); + } Expr::Ident(ident) => { let export_defid = self.add_export(DefRes::Function(()), ident_property.to_id()); @@ -2855,12 +2910,10 @@ impl Visit for ExportCollector<'_> { fn visit_export_default_expr(&mut self, n: &ExportDefaultExpr) { if let Expr::Ident(ident) = &*n.expr { self.add_default(DefRes::Function(()), None); - self.res_table .exported_names .insert((ident.sym.clone(), self.curr_mod), self.default.unwrap()); } - //n.visit_children_with(self) } } @@ -3237,6 +3290,7 @@ impl Definitions { } DefKind::Class(_) => { let objid = classes.push_and_get_key(Class::new(id)); + println!("pusing class 1"); DefKind::Class(objid) } DefKind::Foreign(d) => DefKind::Foreign(d), diff --git a/crates/forge_analyzer/src/interp.rs b/crates/forge_analyzer/src/interp.rs index ae12bf43..ef1eac6c 100644 --- a/crates/forge_analyzer/src/interp.rs +++ b/crates/forge_analyzer/src/interp.rs @@ -725,10 +725,13 @@ impl<'cx, C: Runner<'cx>> Interp<'cx, C> { worklist.push_front_blocks(self.env, func_def); let old_body = self.curr_body.get(); while let Some((def, block_id)) = worklist.pop_front() { + println!("def from popping {def:?} block id from popping {block_id:?}"); + let arguments = self.callstack_arguments.pop(); let name = self.env.def_name(def); debug!("Dataflow: {name} - {block_id}"); self.dataflow_visited.insert(def); + println!("expect body 8"); let func = self.env().def_ref(def).expect_body(); self.curr_body.set(Some(func)); let mut before_state = self.block_state(def, block_id); diff --git a/crates/forge_analyzer/src/ir.rs b/crates/forge_analyzer/src/ir.rs index 281bc260..b745a494 100644 --- a/crates/forge_analyzer/src/ir.rs +++ b/crates/forge_analyzer/src/ir.rs @@ -415,7 +415,6 @@ impl Body { env: &'cx Environment, callee: &Operand, ) -> Option<(DefId, &'cx Body)> { - match callee { Operand::Var(Variable { base: Base::Var(var), diff --git a/crates/forge_permission_resolver/src/permissions_resolver.rs b/crates/forge_permission_resolver/src/permissions_resolver.rs index d13fc7a9..32143130 100644 --- a/crates/forge_permission_resolver/src/permissions_resolver.rs +++ b/crates/forge_permission_resolver/src/permissions_resolver.rs @@ -110,8 +110,11 @@ pub fn get_permisions_for( endpoint_map_classic: &mut PermissionHashMap, endpoint_regex: &mut HashMap, ) { + println!("prev to call"); if let Result::Ok(response) = ureq::get(url).call() { + println!("prev to parsing"); let data: SwaggerReponse = response.into_json().unwrap(); + println!("finish parsing"); for (key, endpoint_data) in &data.paths { let endpoint_data = get_request_type(endpoint_data, key); endpoint_data diff --git a/test-apps/jira-damn-vulnerable-forge-app/src/utils.js b/test-apps/jira-damn-vulnerable-forge-app/src/utils.js index 29ee6136..9ac25eb2 100755 --- a/test-apps/jira-damn-vulnerable-forge-app/src/utils.js +++ b/test-apps/jira-damn-vulnerable-forge-app/src/utils.js @@ -7,7 +7,7 @@ import {func_from_exports, diffunc} from "./exportse.js" import {another_export, newExport} from './newexports.js' import func_defult from './export_default'; import my_function from "./export_default2.js"; -import ANewClass from './anewclass'; +import {AClassNew} from './anewclass'; export async function fetchIssueSummary(issueIdOrKey, test_value) { @@ -20,8 +20,7 @@ export async function fetchIssueSummary(issueIdOrKey, test_value) { }; - let a_class = new ANewClass(); - a_class.function_a_new_class(); + var token = jwt.sign({ foo: 'bar' }, process.env.SECRET); @@ -36,7 +35,9 @@ export async function fetchIssueSummary(issueIdOrKey, test_value) { diffunc() - different_function(); + // different_function(); + let a_class = new AClassNew(); + a_class.function_a_new_class(); let val = "grapefruit"; From 1ed4ce39cb9c4990e2dc0f09bb58257742ce1e2e Mon Sep 17 00:00:00 2001 From: James Gersbach Date: Thu, 10 Aug 2023 10:51:13 -0700 Subject: [PATCH 152/517] fix bug for exporting default classes --- crates/forge_analyzer/src/checkers.rs | 2 - crates/forge_analyzer/src/definitions.rs | 100 +++++++++++------- crates/forge_analyzer/src/interp.rs | 3 - .../src/permissions_resolver.rs | 3 - .../src/utils.js | 4 +- 5 files changed, 62 insertions(+), 50 deletions(-) diff --git a/crates/forge_analyzer/src/checkers.rs b/crates/forge_analyzer/src/checkers.rs index 83f244c6..d1197ebf 100644 --- a/crates/forge_analyzer/src/checkers.rs +++ b/crates/forge_analyzer/src/checkers.rs @@ -890,7 +890,6 @@ impl<'cx> Dataflow<'cx> for PermissionDataflow { } }); - println!("intrinsic argument {intrinsic_argument:?}"); _interp .permissions @@ -1172,7 +1171,6 @@ impl<'cx> Dataflow<'cx> for DefintionAnalysisRunner { inst: &'cx Inst, initial_state: Self::State, ) -> Self::State { - println!("\t inst {inst}"); match inst { Inst::Expr(rvalue) => { self.transfer_rvalue(interp, def, loc, block, rvalue, initial_state) diff --git a/crates/forge_analyzer/src/definitions.rs b/crates/forge_analyzer/src/definitions.rs index 13885356..681e4a4b 100644 --- a/crates/forge_analyzer/src/definitions.rs +++ b/crates/forge_analyzer/src/definitions.rs @@ -127,6 +127,7 @@ pub struct ResolverTable { pub names: TiVec, recent_names: FxHashMap<(JsWord, ModId), DefId>, exported_names: FxHashMap<(JsWord, ModId), DefId>, + default_export_names: FxHashMap<(JsWord, ModId), DefId>, symbol_to_id: FxHashMap, parent: FxHashMap, owning_module: TiVec, @@ -735,14 +736,14 @@ impl ResolverTable { let defid2 = self.owning_module.push_and_get_key(module); debug_assert_eq!( defid, defid2, - "inconsistent state while inserting {}", + "inconsistent state while inserting 1 {}", &*name ); let defid3 = self.names.push_and_get_key(name); debug_assert_eq!( defid, defid3, - "inconsistent state while inserting {}", + "inconsistent state while inserting 2 {}", self.names.last().unwrap() ); defid @@ -751,7 +752,11 @@ impl ResolverTable { fn add_sym(&mut self, def: DefRes, id: Id, module: ModId) -> DefId { let defid = self.defs.push_and_get_key(def); let defid2 = self.owning_module.push_and_get_key(module); - debug_assert_eq!(defid, defid2, "inconsistent state while inserting {}", id.0); + debug_assert_eq!( + defid, defid2, + "inconsistent state while inserting 3 {}", + id.0 + ); let sym = id.0.clone(); self.symbol_to_id.insert(Symbol { id, module }, defid2); self.recent_names.insert((sym.clone(), module), defid2); @@ -759,7 +764,7 @@ impl ResolverTable { debug_assert_eq!( defid, defid3, - "inconsistent state while inserting {}", + "inconsistent state while inserting 4 {}", self.names.last().unwrap() ); defid @@ -1064,17 +1069,14 @@ impl<'cx> FunctionAnalyzer<'cx> { } fn get_operand_for_call(&mut self, expr: &Expr) -> Operand { - println!("expr {expr:?}"); - if let Expr::Ident(ident) = expr { if let Some(DefKind::Class(class)) = self.res.sym_to_def(ident.to_id(), self.module) { let id = ident.to_id(); - let Some(def) = self.res.sym_to_id(id.clone(), self.module) else { - return Literal::Undef.into(); - }; + // let Some(def) = self.res.sym_to_id(id.clone(), self.module) else { + // return Literal::Undef.into(); + // }; if let Some((_, def_constructor)) = class.pub_members.iter().find(|(name, defid)| { - println!("found a constructor method"); return name == "constructor"; }) { @@ -1911,10 +1913,6 @@ impl Visit for FunctionCollector<'_> { if let Some(defid) = self.res.default_export(self.module) { // we don't need to check that it is either a class or a function because // it will get caught by the respective methods. - println!("visit_export_default_decl {defid:?} {:?}", self.module); - - println!("all classes {:?}", self.res.defs.classes); - self.curr_class = Some(defid); self.curr_function = Some(defid); } @@ -1938,6 +1936,7 @@ impl Visit for FunctionCollector<'_> { Expr::Class(ClassExpr { ident, class }) => { self.curr_class = self.res.default_export(self.module) } + // do not worry about idents here ... _ => {} } } @@ -1951,16 +1950,13 @@ impl Visit for FunctionCollector<'_> { if let Some(defid) = self.res.get_sym(ident_property.to_id(), self.module) { - println!("found def func ... {defid:?}"); - self.handle_function(&**&n.function, Some(defid)); + self.handle_function(&*n.function, Some(defid)); } } Expr::Class(class) => { - println!("class ~~~ {ident_property:?}"); if let Some(defid) = self.res.get_sym(ident_property.to_id(), self.module) { - println!("found def ... {defid:?}"); self.curr_class = Some(defid); } } @@ -1974,12 +1970,19 @@ impl Visit for FunctionCollector<'_> { } fn visit_class_decl(&mut self, n: &ClassDecl) { - if let Some(DefKind::Class(class)) = self.res.sym_to_def(n.ident.to_id(), self.module) { + if let Some(defid) = self + .res + .resolver + .exported_names + .get(&(n.ident.clone().sym, self.module)) + .cloned() + { + self.curr_class = Some(defid); + } else if let Some(DefKind::Class(class)) = + self.res.sym_to_def(n.ident.to_id(), self.module) + { self.curr_class = Some(class.def); // sets the current class so that mehtods are assigned to the proper class } - - // need a way to get the curr class without using the sym ... - n.visit_children_with(self); } @@ -2031,11 +2034,8 @@ impl Visit for FunctionCollector<'_> { fn visit_class_method(&mut self, n: &ClassMethod) { //n.visit_children_with(self); - println!("visitng class method {:?}", self.curr_class); if let Some(class_def) = self.curr_class { - println!("1 ~ {n:?}"); if let DefKind::Class(class) = self.res.clone().def_ref(class_def) { - println!("2 ~ {n:?}"); if let Some((_, owner)) = &class.pub_members.iter().find(|(name, defid)| { if let PropName::Ident(ident) = &n.key { return name == &ident.sym; @@ -2136,7 +2136,7 @@ impl Visit for FunctionCollector<'_> { } fn visit_var_declarator(&mut self, n: &VarDeclarator) { - n.visit_children_with(self); + n.visit_children_with(self); let Some(BindingIdent{id, ..}) = n.name.as_ident() else { return; }; @@ -2232,7 +2232,11 @@ impl Visit for FunctionCollector<'_> { .resolver .exported_names .get(&(n.ident.clone().sym, self.module)) + .cloned() { + self.res.resolver.defs[defid] = DefRes::Function(()); + self.res.overwrite_def(defid, DefRes::Function(())); + self.curr_function = Some(defid); self.handle_function(&*n.function, Some(defid.clone())); } else { let def = self @@ -2412,8 +2416,21 @@ impl Visit for Lowerer<'_> { } fn visit_class_decl(&mut self, n: &ClassDecl) { - if let Some(DefKind::Class(class)) = self.res.sym_to_def(n.ident.to_id(), self.curr_mod) { - self.curr_class = Some(class.def); // sets the curr class so that mehtods are assigned to the proper class + if let Some(defid) = self + .res + .resolver + .exported_names + .get(&(n.ident.clone().sym, self.curr_mod)) + .cloned() + { + self.res.resolver.defs[defid] = DefRes::Class(()); + self.res.overwrite_def(defid, DefRes::Class(())); + self.curr_class = Some(defid); + } else if let Some(DefKind::Class(class)) = + self.res.sym_to_def(n.ident.to_id(), self.curr_mod) + { + // sets the current class so that mehtods are assigned to the proper class + self.curr_class = Some(class.def); } n.visit_children_with(self); } @@ -2435,12 +2452,9 @@ impl Visit for Lowerer<'_> { fn visit_class_method(&mut self, n: &ClassMethod) { n.visit_children_with(self); - println!("adding class mehtod .... "); if let Some(class_def) = self.curr_class { if let DefKind::Class(class) = self.res.def_mut(class_def) { if let PropName::Ident(ident) = &n.key { - println!("adding class mehtod .... "); - let owner = self.res .add_anonymous("__CLASSMETHOD", AnonType::Closure, self.curr_mod); @@ -2723,7 +2737,10 @@ impl Visit for ImportCollector<'_> { debug_assert_ne!(self.curr_mod, mod_id); match self.resolver.default_export(mod_id) { Some(def) => { - println!("def -- {def:?} {:?} {:?}", mod_id, self.curr_mod); + println!( + "def -- {:?} {def:?} {:?} {:?}", + n.local, mod_id, self.curr_mod + ); self.resolver.resolver.symbol_to_id.insert( Symbol { @@ -2733,10 +2750,8 @@ impl Visit for ImportCollector<'_> { def, ); } - None => println!("unable to find default import for {}", &self.current_import), + None => warn!("unable to find default import for {}", &self.current_import), } - - // POI } Err(_) => { let foreign_id = self.foreign_defs.push_and_get_key(ForeignItem { @@ -2821,6 +2836,13 @@ impl Visit for ExportCollector<'_> { DefRes::Class(()), ident.as_ref().map(Ident::to_id), ), + Expr::Ident(ident) => { + self.add_default(DefRes::Undefined, None); + self.res_table.default_export_names.insert( + (ident.sym.clone(), self.curr_mod), + self.default.unwrap(), + ); + } _ => {} } } @@ -2838,7 +2860,7 @@ impl Visit for ExportCollector<'_> { } Expr::Ident(ident) => { let export_defid = - self.add_export(DefRes::Function(()), ident_property.to_id()); + self.add_export(DefRes::Undefined, ident_property.to_id()); self.res_table .exported_names .insert((ident.sym.clone(), self.curr_mod), export_defid); @@ -2895,8 +2917,6 @@ impl Visit for ExportCollector<'_> { n.decl.visit_children_with(self); } - // POI - fn visit_export_named_specifier(&mut self, n: &ExportNamedSpecifier) { let orig_id = n.orig.as_id(); let orig = self.add_export(DefRes::default(), orig_id); @@ -2909,7 +2929,8 @@ impl Visit for ExportCollector<'_> { fn visit_export_default_expr(&mut self, n: &ExportDefaultExpr) { if let Expr::Ident(ident) = &*n.expr { - self.add_default(DefRes::Function(()), None); + self.add_default(DefRes::Undefined, None); + // adding the default export, so we can resolve it during the lowering self.res_table .exported_names .insert((ident.sym.clone(), self.curr_mod), self.default.unwrap()); @@ -3290,7 +3311,6 @@ impl Definitions { } DefKind::Class(_) => { let objid = classes.push_and_get_key(Class::new(id)); - println!("pusing class 1"); DefKind::Class(objid) } DefKind::Foreign(d) => DefKind::Foreign(d), diff --git a/crates/forge_analyzer/src/interp.rs b/crates/forge_analyzer/src/interp.rs index ef1eac6c..ae12bf43 100644 --- a/crates/forge_analyzer/src/interp.rs +++ b/crates/forge_analyzer/src/interp.rs @@ -725,13 +725,10 @@ impl<'cx, C: Runner<'cx>> Interp<'cx, C> { worklist.push_front_blocks(self.env, func_def); let old_body = self.curr_body.get(); while let Some((def, block_id)) = worklist.pop_front() { - println!("def from popping {def:?} block id from popping {block_id:?}"); - let arguments = self.callstack_arguments.pop(); let name = self.env.def_name(def); debug!("Dataflow: {name} - {block_id}"); self.dataflow_visited.insert(def); - println!("expect body 8"); let func = self.env().def_ref(def).expect_body(); self.curr_body.set(Some(func)); let mut before_state = self.block_state(def, block_id); diff --git a/crates/forge_permission_resolver/src/permissions_resolver.rs b/crates/forge_permission_resolver/src/permissions_resolver.rs index 32143130..d13fc7a9 100644 --- a/crates/forge_permission_resolver/src/permissions_resolver.rs +++ b/crates/forge_permission_resolver/src/permissions_resolver.rs @@ -110,11 +110,8 @@ pub fn get_permisions_for( endpoint_map_classic: &mut PermissionHashMap, endpoint_regex: &mut HashMap, ) { - println!("prev to call"); if let Result::Ok(response) = ureq::get(url).call() { - println!("prev to parsing"); let data: SwaggerReponse = response.into_json().unwrap(); - println!("finish parsing"); for (key, endpoint_data) in &data.paths { let endpoint_data = get_request_type(endpoint_data, key); endpoint_data diff --git a/test-apps/jira-damn-vulnerable-forge-app/src/utils.js b/test-apps/jira-damn-vulnerable-forge-app/src/utils.js index 9ac25eb2..1cd10d95 100755 --- a/test-apps/jira-damn-vulnerable-forge-app/src/utils.js +++ b/test-apps/jira-damn-vulnerable-forge-app/src/utils.js @@ -7,7 +7,7 @@ import {func_from_exports, diffunc} from "./exportse.js" import {another_export, newExport} from './newexports.js' import func_defult from './export_default'; import my_function from "./export_default2.js"; -import {AClassNew} from './anewclass'; +import ANewClass from './anewclass'; export async function fetchIssueSummary(issueIdOrKey, test_value) { @@ -36,7 +36,7 @@ export async function fetchIssueSummary(issueIdOrKey, test_value) { diffunc() // different_function(); - let a_class = new AClassNew(); + let a_class = new ANewClass(); a_class.function_a_new_class(); let val = "grapefruit"; From 36b6059a9006c44c076286474ef413b7ba2eeb6e Mon Sep 17 00:00:00 2001 From: James Gersbach Date: Thu, 10 Aug 2023 11:18:23 -0700 Subject: [PATCH 153/517] formatting --- crates/forge_analyzer/src/checkers.rs | 1 - crates/forge_analyzer/src/definitions.rs | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/crates/forge_analyzer/src/checkers.rs b/crates/forge_analyzer/src/checkers.rs index d1197ebf..475ce8ee 100644 --- a/crates/forge_analyzer/src/checkers.rs +++ b/crates/forge_analyzer/src/checkers.rs @@ -890,7 +890,6 @@ impl<'cx> Dataflow<'cx> for PermissionDataflow { } }); - _interp .permissions .extend_from_slice(&permissions_within_call); diff --git a/crates/forge_analyzer/src/definitions.rs b/crates/forge_analyzer/src/definitions.rs index 681e4a4b..9189afc0 100644 --- a/crates/forge_analyzer/src/definitions.rs +++ b/crates/forge_analyzer/src/definitions.rs @@ -2136,7 +2136,7 @@ impl Visit for FunctionCollector<'_> { } fn visit_var_declarator(&mut self, n: &VarDeclarator) { - n.visit_children_with(self); + n.visit_children_with(self); let Some(BindingIdent{id, ..}) = n.name.as_ident() else { return; }; From 1355733413cf704d9968ab0395bbaa0539fcccab Mon Sep 17 00:00:00 2001 From: James Gersbach Date: Thu, 10 Aug 2023 13:55:43 -0700 Subject: [PATCH 154/517] improvments to performance --- Cargo.lock | 1 + crates/forge_analyzer/src/checkers.rs | 13 +++++++---- crates/forge_analyzer/src/definitions.rs | 23 +++++++++++-------- crates/forge_analyzer/src/interp.rs | 21 +++++++++-------- crates/forge_analyzer/src/worklist.rs | 4 ++-- .../src/permissions_resolver.rs | 3 +++ crates/fsrt/Cargo.toml | 1 + crates/fsrt/src/main.rs | 15 ++++++++---- .../src/utils.js | 4 ++-- 9 files changed, 52 insertions(+), 33 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e752a0a8..1ad7087b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -645,6 +645,7 @@ dependencies = [ "forge_analyzer", "forge_file_resolver", "forge_loader", + "forge_permission_resolver", "miette 5.5.0", "rustc-hash", "serde", diff --git a/crates/forge_analyzer/src/checkers.rs b/crates/forge_analyzer/src/checkers.rs index 475ce8ee..5df39897 100644 --- a/crates/forge_analyzer/src/checkers.rs +++ b/crates/forge_analyzer/src/checkers.rs @@ -137,7 +137,7 @@ impl<'cx> Dataflow<'cx> for AuthorizeDataflow { ) { self.super_join_term(interp, def, block, state, worklist); for def in self.needs_call.drain(..) { - worklist.push_front_blocks(interp.env(), def); + worklist.push_front_blocks(interp.env(), def, interp.call_all); } } } @@ -371,7 +371,7 @@ impl<'cx> Dataflow<'cx> for AuthenticateDataflow { ) { self.super_join_term(interp, def, block, state, worklist); for def in self.needs_call.drain(..) { - worklist.push_front_blocks(interp.env(), def); + worklist.push_front_blocks(interp.env(), def, interp.call_all); } } } @@ -579,7 +579,7 @@ impl<'cx> Dataflow<'cx> for SecretDataflow { ) { self.super_join_term(interp, def, block, state, worklist); for def in self.needs_call.drain(..) { - worklist.push_front_blocks(interp.env(), def); + worklist.push_front_blocks(interp.env(), def, interp.call_all); } } } @@ -946,7 +946,7 @@ impl<'cx> Dataflow<'cx> for PermissionDataflow { ) { self.super_join_term(interp, def, block, state, worklist); for (def, arguments) in self.needs_call.drain(..) { - worklist.push_front_blocks(interp.env(), def); + worklist.push_front_blocks(interp.env(), def, interp.call_all); } } } @@ -1170,6 +1170,9 @@ impl<'cx> Dataflow<'cx> for DefintionAnalysisRunner { inst: &'cx Inst, initial_state: Self::State, ) -> Self::State { + + println!("\t inst {inst}"); + match inst { Inst::Expr(rvalue) => { self.transfer_rvalue(interp, def, loc, block, rvalue, initial_state) @@ -1479,7 +1482,7 @@ impl<'cx> Dataflow<'cx> for DefintionAnalysisRunner { ) { self.super_join_term(interp, def, block, state, worklist); for (def, arguments, values) in self.needs_call.drain(..) { - worklist.push_front_blocks(interp.env(), def); + worklist.push_front_blocks(interp.env(), def, interp.call_all); interp.callstack_arguments.push(values.clone()); } } diff --git a/crates/forge_analyzer/src/definitions.rs b/crates/forge_analyzer/src/definitions.rs index 9189afc0..0a03882f 100644 --- a/crates/forge_analyzer/src/definitions.rs +++ b/crates/forge_analyzer/src/definitions.rs @@ -2737,11 +2737,6 @@ impl Visit for ImportCollector<'_> { debug_assert_ne!(self.curr_mod, mod_id); match self.resolver.default_export(mod_id) { Some(def) => { - println!( - "def -- {:?} {def:?} {:?} {:?}", - n.local, mod_id, self.curr_mod - ); - self.resolver.resolver.symbol_to_id.insert( Symbol { module: self.curr_mod, @@ -2838,10 +2833,11 @@ impl Visit for ExportCollector<'_> { ), Expr::Ident(ident) => { self.add_default(DefRes::Undefined, None); - self.res_table.default_export_names.insert( - (ident.sym.clone(), self.curr_mod), - self.default.unwrap(), - ); + // adding the default export, so we can resolve it during the lowering + self.res_table + .exported_names + .insert((ident.sym.clone(), self.curr_mod), self.default.unwrap()); + } _ => {} } @@ -2918,6 +2914,14 @@ impl Visit for ExportCollector<'_> { } fn visit_export_named_specifier(&mut self, n: &ExportNamedSpecifier) { + if let ModuleExportName::Ident(ident) = &n.orig { + let export_defid = + self.add_export(DefRes::Undefined, ident.to_id()); + self.res_table + .exported_names + .insert((ident.sym.clone(), self.curr_mod), export_defid); + } else { + let orig_id = n.orig.as_id(); let orig = self.add_export(DefRes::default(), orig_id); if let Some(id) = &n.exported { @@ -2926,6 +2930,7 @@ impl Visit for ExportCollector<'_> { } n.visit_children_with(self) } + } fn visit_export_default_expr(&mut self, n: &ExportDefaultExpr) { if let Expr::Ident(ident) = &*n.expr { diff --git a/crates/forge_analyzer/src/interp.rs b/crates/forge_analyzer/src/interp.rs index ae12bf43..83acd825 100644 --- a/crates/forge_analyzer/src/interp.rs +++ b/crates/forge_analyzer/src/interp.rs @@ -14,7 +14,7 @@ use std::{ use forge_loader::forgepermissions::ForgePermissions; use forge_permission_resolver::permissions_resolver::{ check_url_for_permissions, get_permission_resolver_confluence, get_permission_resolver_jira, - PermissionHashMap, + PermissionHashMap, RequestType, }; use forge_utils::{FxHashMap, FxHashSet}; use itertools::Itertools; @@ -356,6 +356,8 @@ pub trait Runner<'cx>: Sized { type State: JoinSemiLattice + Clone + fmt::Debug; type Dataflow: Dataflow<'cx, State = Self::State>; + const visit_all: bool = true; + fn visit_intrinsic( &mut self, interp: &Interp<'cx, Self>, @@ -491,6 +493,7 @@ pub struct Interp<'cx, C: Runner<'cx>> { pub env: &'cx Environment, // We can probably get rid of these RefCells by refactoring the Interp and Checker into // two fields in another struct. + pub call_all: bool, call_graph: CallGraph, pub return_value: Option<(Value, DefId)>, pub return_value_alt: HashMap, @@ -506,10 +509,10 @@ pub struct Interp<'cx, C: Runner<'cx>> { pub expected_return_values: HashMap, pub permissions: Vec, pub expecting_value: VecDeque<(DefId, (VarId, DefId))>, - pub jira_permission_resolver: PermissionHashMap, - pub confluence_permission_resolver: PermissionHashMap, - pub jira_regex_map: HashMap, - pub confluence_regex_map: HashMap, + pub jira_permission_resolver: &'cx PermissionHashMap, + pub confluence_permission_resolver: &'cx PermissionHashMap, + pub jira_regex_map: &'cx HashMap, + pub confluence_regex_map: &'cx HashMap, pub varid_to_value: FxHashMap<(DefId, VarId, Option), Value>, _checker: PhantomData, } @@ -569,15 +572,13 @@ impl fmt::Display for Error { impl std::error::Error for Error {} impl<'cx, C: Runner<'cx>> Interp<'cx, C> { - pub fn new(env: &'cx Environment) -> Self { - let (jira_permission_resolver, jira_regex_map) = get_permission_resolver_jira(); - let (confluence_permission_resolver, confluence_regex_map) = - get_permission_resolver_confluence(); + pub fn new(env: &'cx Environment, call_all: bool, jira_permission_resolver: &'cx PermissionHashMap, jira_regex_map: &'cx HashMap, confluence_permission_resolver: &'cx PermissionHashMap, confluence_regex_map: &'cx HashMap) -> Self { let call_graph = CallGraph::new(env); Self { env, call_graph, + call_all, entry: Default::default(), return_value: None, return_value_alt: HashMap::default(), @@ -722,7 +723,7 @@ impl<'cx, C: Runner<'cx>> Interp<'cx, C> { self.dataflow_visited.insert(func_def); let mut dataflow = C::Dataflow::with_interp(self); let mut worklist = WorkList::new(); - worklist.push_front_blocks(self.env, func_def); + worklist.push_front_blocks(self.env, func_def, self.call_all); let old_body = self.curr_body.get(); while let Some((def, block_id)) = worklist.pop_front() { let arguments = self.callstack_arguments.pop(); diff --git a/crates/forge_analyzer/src/worklist.rs b/crates/forge_analyzer/src/worklist.rs index 78691f67..d5a8e145 100644 --- a/crates/forge_analyzer/src/worklist.rs +++ b/crates/forge_analyzer/src/worklist.rs @@ -75,8 +75,8 @@ where impl WorkList { #[inline] - pub(crate) fn push_front_blocks(&mut self, env: &Environment, def: DefId) -> bool { - if self.visited.insert(def) || true { + pub(crate) fn push_front_blocks(&mut self, env: &Environment, def: DefId, visit_all: bool) -> bool { + if self.visited.insert(def) || visit_all { debug!("adding function: {}", env.def_name(def)); let body = env.def_ref(def).expect_body(); let blocks = body.iter_block_keys().map(|bb| (def, bb)).rev(); diff --git a/crates/forge_permission_resolver/src/permissions_resolver.rs b/crates/forge_permission_resolver/src/permissions_resolver.rs index d13fc7a9..8473b3aa 100644 --- a/crates/forge_permission_resolver/src/permissions_resolver.rs +++ b/crates/forge_permission_resolver/src/permissions_resolver.rs @@ -110,8 +110,11 @@ pub fn get_permisions_for( endpoint_map_classic: &mut PermissionHashMap, endpoint_regex: &mut HashMap, ) { + println!("pre api call"); if let Result::Ok(response) = ureq::get(url).call() { + println!("post api call"); let data: SwaggerReponse = response.into_json().unwrap(); + println!("post swagger parse"); for (key, endpoint_data) in &data.paths { let endpoint_data = get_request_type(endpoint_data, key); endpoint_data diff --git a/crates/fsrt/Cargo.toml b/crates/fsrt/Cargo.toml index fb344a97..3d1220ba 100644 --- a/crates/fsrt/Cargo.toml +++ b/crates/fsrt/Cargo.toml @@ -12,6 +12,7 @@ clap.workspace = true forge_analyzer.workspace = true forge_file_resolver.workspace = true forge_loader.workspace = true +forge_permission_resolver.workspace = true miette.workspace = true rustc-hash.workspace = true serde.workspace = true diff --git a/crates/fsrt/src/main.rs b/crates/fsrt/src/main.rs index d06df67e..e4057c94 100644 --- a/crates/fsrt/src/main.rs +++ b/crates/fsrt/src/main.rs @@ -1,6 +1,7 @@ #![allow(clippy::type_complexity)] use clap::{Parser, ValueHint}; use forge_loader::forgepermissions::ForgePermissions; +use forge_permission_resolver::permissions_resolver::{get_permission_resolver_confluence, get_permission_resolver_jira}; use miette::{IntoDiagnostic, Result}; use std::{ collections::HashSet, @@ -228,13 +229,17 @@ fn scan_directory(dir: PathBuf, function: Option<&str>, opts: Opts) -> Result Date: Fri, 11 Aug 2023 09:39:17 -0700 Subject: [PATCH 155/517] working global lowerer --- crates/forge_analyzer/src/checkers.rs | 146 +++++++++------ crates/forge_analyzer/src/definitions.rs | 172 +++++++++++++++--- crates/forge_analyzer/src/interp.rs | 66 ++++++- crates/forge_analyzer/src/worklist.rs | 7 +- crates/fsrt/src/main.rs | 86 +++++++-- .../src/utils.js | 8 +- 6 files changed, 375 insertions(+), 110 deletions(-) diff --git a/crates/forge_analyzer/src/checkers.rs b/crates/forge_analyzer/src/checkers.rs index 5df39897..a4c95cdc 100644 --- a/crates/forge_analyzer/src/checkers.rs +++ b/crates/forge_analyzer/src/checkers.rs @@ -814,7 +814,9 @@ impl<'cx> Dataflow<'cx> for PermissionDataflow { if let Some(operand) = first { match operand { Operand::Lit(lit) => { - intrinsic_argument.first_arg = Some(vec![lit.to_string()]); + if &Literal::Undef != lit { + intrinsic_argument.first_arg = Some(vec![lit.to_string()]); + } } Operand::Var(var) => { if let Base::Var(varid) = var.base { @@ -841,18 +843,58 @@ impl<'cx> Dataflow<'cx> for PermissionDataflow { let mut permissions_within_call: Vec = vec![]; let intrinsic_func_type = intrinsic_argument.name.unwrap(); - intrinsic_argument - .first_arg - .iter() - .for_each(|first_arg_vec| { - if let Some(second_arg_vec) = intrinsic_argument.second_arg.clone() { - first_arg_vec.iter().for_each(|first_arg| { - second_arg_vec.iter().for_each(|second_arg| { + + //println!("intrinsic_argument {:?}", intrinsic_argument); + + if intrinsic_argument.first_arg == None { + println!("interp permissions {:?}", _interp.permissions); + println!("found none ...."); + _interp.permissions.drain(..); + println!("interp permissions {:?}", _interp.permissions); + } else { + intrinsic_argument + .first_arg + .iter() + .for_each(|first_arg_vec| { + if let Some(second_arg_vec) = intrinsic_argument.second_arg.clone() { + first_arg_vec.iter().for_each(|first_arg| { + println!("first arg before {first_arg:?}"); + let first_arg = first_arg.replace(&['\"'][..], ""); + + println!("first arg after {first_arg:?}"); + + second_arg_vec.iter().for_each(|second_arg| { + if intrinsic_func_type == IntrinsicName::RequestConfluence { + let permissions = check_url_for_permissions( + &_interp.confluence_permission_resolver, + &_interp.confluence_regex_map, + translate_request_type(Some(second_arg)), + &first_arg, + ); + permissions_within_call.extend_from_slice(&permissions) + } else if intrinsic_func_type == IntrinsicName::RequestJira { + let permissions = check_url_for_permissions( + &_interp.jira_permission_resolver, + &_interp.jira_regex_map, + translate_request_type(Some(second_arg)), + &first_arg, + ); + permissions_within_call.extend_from_slice(&permissions) + } + }) + }) + } else { + first_arg_vec.iter().for_each(|first_arg| { + println!("first arg before {first_arg:?}"); + let first_arg = first_arg.replace(&['\"'][..], ""); + + println!("first arg after {first_arg:?}"); + if intrinsic_func_type == IntrinsicName::RequestConfluence { let permissions = check_url_for_permissions( &_interp.confluence_permission_resolver, &_interp.confluence_regex_map, - translate_request_type(Some(second_arg)), + RequestType::Get, &first_arg, ); permissions_within_call.extend_from_slice(&permissions) @@ -860,39 +902,26 @@ impl<'cx> Dataflow<'cx> for PermissionDataflow { let permissions = check_url_for_permissions( &_interp.jira_permission_resolver, &_interp.jira_regex_map, - translate_request_type(Some(second_arg)), + RequestType::Get, &first_arg, ); permissions_within_call.extend_from_slice(&permissions) } }) - }) - } else { - first_arg_vec.iter().for_each(|first_arg| { - if intrinsic_func_type == IntrinsicName::RequestConfluence { - let permissions = check_url_for_permissions( - &_interp.confluence_permission_resolver, - &_interp.confluence_regex_map, - RequestType::Get, - &first_arg, - ); - permissions_within_call.extend_from_slice(&permissions) - } else if intrinsic_func_type == IntrinsicName::RequestJira { - let permissions = check_url_for_permissions( - &_interp.jira_permission_resolver, - &_interp.jira_regex_map, - RequestType::Get, - &first_arg, - ); - permissions_within_call.extend_from_slice(&permissions) - } - }) - } - }); + } + }); + + println!("permissions within call {permissions_within_call:?}"); + + _interp.permissions = _interp + .permissions + .iter() + .cloned() + .filter(|permissions| !permissions_within_call.contains(permissions)) + .collect_vec(); + } - _interp - .permissions - .extend_from_slice(&permissions_within_call); + // remvove all permissions that it finds } initial_state } @@ -954,24 +983,20 @@ impl<'cx> Dataflow<'cx> for PermissionDataflow { pub struct PermissionChecker { pub visit: bool, pub vulns: Vec, - pub declared_permissions: HashSet, - pub used_permissions: HashSet, } impl PermissionChecker { - pub fn new(declared_permissions: HashSet) -> Self { + pub fn new() -> Self { Self { visit: false, vulns: vec![], - declared_permissions, - used_permissions: HashSet::default(), } } - pub fn into_vulns(self) -> impl IntoIterator { - if self.declared_permissions.len() > 0 { + pub fn into_vulns(self, permissions: Vec) -> impl IntoIterator { + if permissions.len() > 0 { return Vec::from([PermissionVuln { - unused_permissions: self.declared_permissions.clone(), + unused_permissions: permissions.clone(), }]) .into_iter(); } @@ -981,7 +1006,7 @@ impl PermissionChecker { impl Default for PermissionChecker { fn default() -> Self { - Self::new(HashSet::new()) + PermissionChecker::new() } } @@ -1013,11 +1038,11 @@ impl JoinSemiLattice for PermissionTest { #[derive(Debug)] pub struct PermissionVuln { - unused_permissions: HashSet, + unused_permissions: Vec, } impl PermissionVuln { - pub fn new(unused_permissions: HashSet) -> PermissionVuln { + pub fn new(unused_permissions: Vec) -> PermissionVuln { PermissionVuln { unused_permissions } } } @@ -1170,7 +1195,6 @@ impl<'cx> Dataflow<'cx> for DefintionAnalysisRunner { inst: &'cx Inst, initial_state: Self::State, ) -> Self::State { - println!("\t inst {inst}"); match inst { @@ -1416,18 +1440,20 @@ impl<'cx> Dataflow<'cx> for DefintionAnalysisRunner { ) { match operand { Operand::Lit(_lit) => { - if let Some(prev_values) = prev_values { - if let Some(lit_value) = convert_operand_to_raw(operand) { - let const_value = Const::Literal(lit_value); - let mut all_values = prev_values.clone(); - all_values.push(const_value); - let value = Value::Phi(all_values); - interp.add_value(def, *varid, value, lval.projections.get(0).cloned()); - } - } else { - if let Some(lit_value) = convert_operand_to_raw(operand) { - let value = Value::Const(Const::Literal(lit_value)); - interp.add_value(def, *varid, value, lval.projections.get(0).cloned()); + if &Literal::Undef != _lit { + if let Some(prev_values) = prev_values { + if let Some(lit_value) = convert_operand_to_raw(operand) { + let const_value = Const::Literal(lit_value); + let mut all_values = prev_values.clone(); + all_values.push(const_value); + let value = Value::Phi(all_values); + interp.add_value(def, *varid, value, lval.projections.get(0).cloned()); + } + } else { + if let Some(lit_value) = convert_operand_to_raw(operand) { + let value = Value::Const(Const::Literal(lit_value)); + interp.add_value(def, *varid, value, lval.projections.get(0).cloned()); + } } } } diff --git a/crates/forge_analyzer/src/definitions.rs b/crates/forge_analyzer/src/definitions.rs index 0a03882f..48f9b0fb 100644 --- a/crates/forge_analyzer/src/definitions.rs +++ b/crates/forge_analyzer/src/definitions.rs @@ -15,11 +15,11 @@ use swc_core::{ ast::{ ArrayLit, ArrayPat, ArrowExpr, AssignExpr, AssignOp, AssignPat, AssignPatProp, AssignProp, AwaitExpr, BinExpr, BindingIdent, BlockStmt, BlockStmtOrExpr, BreakStmt, - CallExpr, Callee, ClassDecl, ClassExpr, ClassMethod, ComputedPropName, CondExpr, - Constructor, ContinueStmt, Decl, DefaultDecl, DoWhileStmt, ExportAll, ExportDecl, - ExportDefaultDecl, ExportDefaultExpr, ExportNamedSpecifier, Expr, ExprOrSpread, - ExprStmt, FnDecl, FnExpr, ForInStmt, ForOfStmt, ForStmt, Function, Id, Ident, IfStmt, - Import, ImportDecl, ImportDefaultSpecifier, ImportNamedSpecifier, + CallExpr, Callee, Class as Clss, ClassDecl, ClassExpr, ClassMethod, ComputedPropName, + CondExpr, Constructor, ContinueStmt, Decl, DefaultDecl, DoWhileStmt, ExportAll, + ExportDecl, ExportDefaultDecl, ExportDefaultExpr, ExportNamedSpecifier, Expr, + ExprOrSpread, ExprStmt, FnDecl, FnExpr, ForInStmt, ForOfStmt, ForStmt, Function, Id, + Ident, IfStmt, Import, ImportDecl, ImportDefaultSpecifier, ImportNamedSpecifier, ImportStarAsSpecifier, JSXAttrName, JSXAttrOrSpread, JSXAttrValue, JSXElement, JSXElementChild, JSXElementName, JSXExpr, JSXExprContainer, JSXFragment, JSXMemberExpr, JSXNamespacedName, JSXObject, JSXSpreadChild, JSXText, KeyValuePatProp, KeyValueProp, @@ -125,10 +125,12 @@ struct SymbolExport { pub struct ResolverTable { defs: TiVec, pub names: TiVec, + pub global: TiVec, recent_names: FxHashMap<(JsWord, ModId), DefId>, exported_names: FxHashMap<(JsWord, ModId), DefId>, default_export_names: FxHashMap<(JsWord, ModId), DefId>, symbol_to_id: FxHashMap, + global_defid_to_value: FxHashMap, parent: FxHashMap, owning_module: TiVec, } @@ -155,6 +157,7 @@ pub fn run_resolver( secret_packages: Vec, ) -> Environment { let mut environment = Environment::new(); + for (curr_mod, module) in modules.iter_enumerated() { let mut export_collector = ExportCollector { res_table: &mut environment.resolver, @@ -186,6 +189,19 @@ pub fn run_resolver( module.visit_with(&mut import_collector); } + let mut foreign = TiVec::default(); + for (curr_mod, module) in modules.iter_enumerated() { + let mut import_collector = ImportCollector { + resolver: &mut environment, + file_resolver, + foreign_defs: &mut foreign, + curr_mod, + current_import: Default::default(), + in_foreign_import: false, + }; + module.visit_with(&mut import_collector); + } + let defs = Definitions::new( environment .resolver @@ -206,6 +222,20 @@ pub fn run_resolver( module.visit_with(&mut lowerer); } + for (curr_mod, module) in modules.iter_enumerated() { + + let global_id = environment.get_or_reserve_global_scope(curr_mod); + + let mut global_collector = GlobalCollector { + res: &mut environment, + global_id, + secret_packages: secret_packages.clone(), // remove the clone + module: curr_mod, + parent: None, + }; + module.visit_with(&mut global_collector); + } + for (curr_mod, module) in modules.iter_enumerated() { let mut collector = FunctionCollector { res: &mut environment, @@ -494,11 +524,14 @@ pub struct Definitions { #[derive(Debug, Clone, Default)] pub struct Environment { exports: TiVec>, + pub global: TiVec, pub defs: Definitions, default_exports: FxHashMap, pub resolver: ResolverTable, } +// POI + struct ImportCollector<'cx> { resolver: &'cx mut Environment, file_resolver: &'cx ForgeResolver, @@ -515,6 +548,14 @@ struct ExportCollector<'cx> { default: Option, } +struct GlobalCollector<'cx> { + res: &'cx mut Environment, + module: ModId, + global_id: DefId, + secret_packages: Vec, + parent: Option, +} + #[derive(Debug, Clone, Copy, PartialEq, Eq)] enum LowerStage { Collect, @@ -1546,7 +1587,11 @@ impl<'cx> FunctionAnalyzer<'cx> { let var = self.body.get_or_insert_global(def); Operand::with_var(var) } - Expr::Lit(lit) => lit.clone().into(), + Expr::Lit(lit) => { + println!("lowering literal {lit:?}"); + + lit.clone().into() + } Expr::Tpl(tpl) => { let tpl = self.lower_tpl(tpl); Operand::with_var( @@ -2492,6 +2537,7 @@ impl Visit for Lowerer<'_> { { let id = id.to_id(); match &**expr { + Expr::Lit(lit) => {} Expr::Arrow(expr) => { let def_id = self.def_function(id); let old_def = self.curr_def.replace(def_id); @@ -2792,6 +2838,55 @@ impl Visit for ImportCollector<'_> { } } +impl Visit for GlobalCollector<'_> { + fn visit_function(&mut self, _: &Function) {} + + fn visit_class(&mut self, _: &Clss) {} + + + fn visit_module(&mut self, n: &Module) { + + // we encouter 2 statements, so the defid gets reassigned every time + + let owner = self.global_id; + let mut localdef = LocalDefiner { + res: self.res, + module: self.module, + func: owner, + body: Body::with_owner(owner), + }; + n.visit_children_with(&mut localdef); + let body = localdef.body; + let mut analyzer = FunctionAnalyzer { + res: self.res, + module: self.module, + current_def: owner, + assigning_to: None, + secret_packages: self.secret_packages.clone(), + body, + block: BasicBlockId::default(), + operand_stack: vec![], + in_lhs: false, + }; + + let mut all_module_items = Vec::new(); + + for item in &n.body { + if let ModuleItem::Stmt(stmt) = item { + all_module_items.push(stmt.clone()); + } + } + analyzer.lower_stmts(all_module_items.as_slice()); + println!("analyzer body !` {owner:?} {:?}", analyzer.body.clone()); + + let body = analyzer.body; + + + *self.res.def_mut(owner).expect_body() = body; + } + +} + impl Visit for ExportCollector<'_> { noop_visit_type!(); fn visit_export_decl(&mut self, n: &ExportDecl) { @@ -2834,10 +2929,10 @@ impl Visit for ExportCollector<'_> { Expr::Ident(ident) => { self.add_default(DefRes::Undefined, None); // adding the default export, so we can resolve it during the lowering - self.res_table - .exported_names - .insert((ident.sym.clone(), self.curr_mod), self.default.unwrap()); - + self.res_table.exported_names.insert( + (ident.sym.clone(), self.curr_mod), + self.default.unwrap(), + ); } _ => {} } @@ -2914,22 +3009,20 @@ impl Visit for ExportCollector<'_> { } fn visit_export_named_specifier(&mut self, n: &ExportNamedSpecifier) { - if let ModuleExportName::Ident(ident) = &n.orig { - let export_defid = - self.add_export(DefRes::Undefined, ident.to_id()); - self.res_table - .exported_names - .insert((ident.sym.clone(), self.curr_mod), export_defid); + if let ModuleExportName::Ident(ident) = &n.orig { + let export_defid = self.add_export(DefRes::Undefined, ident.to_id()); + self.res_table + .exported_names + .insert((ident.sym.clone(), self.curr_mod), export_defid); } else { - - let orig_id = n.orig.as_id(); - let orig = self.add_export(DefRes::default(), orig_id); - if let Some(id) = &n.exported { - let exported_id = id.as_id(); - self.add_export(DefRes::ExportAlias(orig), exported_id); + let orig_id = n.orig.as_id(); + let orig = self.add_export(DefRes::default(), orig_id); + if let Some(id) = &n.exported { + let exported_id = id.as_id(); + self.add_export(DefRes::ExportAlias(orig), exported_id); + } + n.visit_children_with(self) } - n.visit_children_with(self) - } } fn visit_export_default_expr(&mut self, n: &ExportDefaultExpr) { @@ -3011,6 +3104,27 @@ impl Environment { self.resolver.recent_sym(sym, module) } + #[inline] + pub fn get_all_functions(&self) -> Vec { + self.defs.get_all_functions() + } + + #[inline] + fn reserve_global_scope(&mut self, module: ModId) -> DefId { + let defid = self.add_anonymous("__GLOBAL", AnonType::Closure, module); + self.global.insert(module, defid); + defid + } + + #[inline] + fn get_or_reserve_global_scope(&mut self, module: ModId) -> DefId { + if let Some(defid) = self.global.get(module) { + return defid.clone() + } + self.reserve_global_scope(module) + } + + fn new_key_from_res(&mut self, id: DefId, res: DefRes) -> DefKey { match res { DefKind::Arg => DefKind::Arg, @@ -3069,6 +3183,8 @@ impl Environment { id } AnonType::Closure => { + let name = name.into(); + println!("name --> {name:?}"); let id = self .resolver .add_anon(DefRes::Closure(()), name.into(), module); @@ -3340,6 +3456,14 @@ impl Definitions { foreign, } } + + pub fn get_all_functions(&self) -> Vec { + self.defs + .iter_enumerated() + .filter(|(defid, defkind)| defkind == &&DefKind::Function(())) + .map(|(defid, defkind)| defid) + .collect_vec() + } } trait Database { diff --git a/crates/forge_analyzer/src/interp.rs b/crates/forge_analyzer/src/interp.rs index 83acd825..5662fd53 100644 --- a/crates/forge_analyzer/src/interp.rs +++ b/crates/forge_analyzer/src/interp.rs @@ -494,6 +494,7 @@ pub struct Interp<'cx, C: Runner<'cx>> { // We can probably get rid of these RefCells by refactoring the Interp and Checker into // two fields in another struct. pub call_all: bool, + pub call_uncalled: bool, call_graph: CallGraph, pub return_value: Option<(Value, DefId)>, pub return_value_alt: HashMap, @@ -572,13 +573,22 @@ impl fmt::Display for Error { impl std::error::Error for Error {} impl<'cx, C: Runner<'cx>> Interp<'cx, C> { - pub fn new(env: &'cx Environment, call_all: bool, jira_permission_resolver: &'cx PermissionHashMap, jira_regex_map: &'cx HashMap, confluence_permission_resolver: &'cx PermissionHashMap, confluence_regex_map: &'cx HashMap) -> Self { - + pub fn new( + env: &'cx Environment, + call_all: bool, + call_uncalled: bool, + permissions: Vec, + jira_permission_resolver: &'cx PermissionHashMap, + jira_regex_map: &'cx HashMap, + confluence_permission_resolver: &'cx PermissionHashMap, + confluence_regex_map: &'cx HashMap, + ) -> Self { let call_graph = CallGraph::new(env); Self { env, call_graph, call_all, + call_uncalled, entry: Default::default(), return_value: None, return_value_alt: HashMap::default(), @@ -592,7 +602,7 @@ impl<'cx, C: Runner<'cx>> Interp<'cx, C> { callstack: RefCell::new(Vec::new()), // vulns: RefCell::new(Vec::new()), expected_return_values: HashMap::default(), - permissions: Vec::new(), + permissions, varid_to_value: DefinitionAnalysisMap::default(), jira_permission_resolver, confluence_permission_resolver, @@ -723,12 +733,27 @@ impl<'cx, C: Runner<'cx>> Interp<'cx, C> { self.dataflow_visited.insert(func_def); let mut dataflow = C::Dataflow::with_interp(self); let mut worklist = WorkList::new(); + + // all all of the global state prior to running + + println!("all global {:?}", &self.env().global); + + for global_def in &self.env().global { + println!("global_def {global_def:?}"); + + + let func = self.env().def_ref(*global_def).expect_body(); + println!("\tfunc {func:?}"); + worklist.push_front_blocks(self.env, *global_def, self.call_all); + } + worklist.push_front_blocks(self.env, func_def, self.call_all); let old_body = self.curr_body.get(); while let Some((def, block_id)) = worklist.pop_front() { let arguments = self.callstack_arguments.pop(); let name = self.env.def_name(def); debug!("Dataflow: {name} - {block_id}"); + println!("Dataflow: {name} - {block_id}"); self.dataflow_visited.insert(def); let func = self.env().def_ref(def).expect_body(); self.curr_body.set(Some(func)); @@ -741,6 +766,41 @@ impl<'cx, C: Runner<'cx>> Interp<'cx, C> { dataflow.transfer_block(self, def, block_id, block, before_state, arguments); dataflow.join_term(self, def, block, state, &mut worklist); } + + if self.call_uncalled { + let all_functions = self.env.get_all_functions(); + let all_functions_set = FxHashSet::from_iter(all_functions.iter()); + + for def in all_functions_set { + if !worklist.visited(def) { + let body = self.env.def_ref(*def).expect_body(); + let blocks = body.iter_block_keys().map(|bb| (def, bb)).rev(); + worklist.reserve(blocks.len()); + for work in blocks { + debug!(?work, "push_front_blocks"); + worklist.push_back_force(*work.0, work.1); + } + } + } + + while let Some((def, block_id)) = worklist.pop_front() { + let arguments = self.callstack_arguments.pop(); + let name = self.env.def_name(def); + debug!("Dataflow: {name} - {block_id}"); + self.dataflow_visited.insert(def); + let func = self.env().def_ref(def).expect_body(); + self.curr_body.set(Some(func)); + let mut before_state = self.block_state(def, block_id); + let block = func.block(block_id); + for &pred in func.predecessors(block_id) { + before_state = before_state.join(&self.block_state(def, pred)); + } + let state = + dataflow.transfer_block(self, def, block_id, block, before_state, arguments); + dataflow.join_term(self, def, block, state, &mut worklist); + } + } + self.curr_body.set(old_body); } diff --git a/crates/forge_analyzer/src/worklist.rs b/crates/forge_analyzer/src/worklist.rs index d5a8e145..3e946126 100644 --- a/crates/forge_analyzer/src/worklist.rs +++ b/crates/forge_analyzer/src/worklist.rs @@ -75,7 +75,12 @@ where impl WorkList { #[inline] - pub(crate) fn push_front_blocks(&mut self, env: &Environment, def: DefId, visit_all: bool) -> bool { + pub(crate) fn push_front_blocks( + &mut self, + env: &Environment, + def: DefId, + visit_all: bool, + ) -> bool { if self.visited.insert(def) || visit_all { debug!("adding function: {}", env.def_name(def)); let body = env.def_ref(def).expect_body(); diff --git a/crates/fsrt/src/main.rs b/crates/fsrt/src/main.rs index e4057c94..0a746ae7 100644 --- a/crates/fsrt/src/main.rs +++ b/crates/fsrt/src/main.rs @@ -1,7 +1,9 @@ #![allow(clippy::type_complexity)] use clap::{Parser, ValueHint}; use forge_loader::forgepermissions::ForgePermissions; -use forge_permission_resolver::permissions_resolver::{get_permission_resolver_confluence, get_permission_resolver_jira}; +use forge_permission_resolver::permissions_resolver::{ + get_permission_resolver_confluence, get_permission_resolver_jira, +}; use miette::{IntoDiagnostic, Result}; use std::{ collections::HashSet, @@ -229,19 +231,68 @@ fn scan_directory(dir: PathBuf, function: Option<&str>, opts: Opts) -> Result, opts: Opts) -> Result { @@ -318,26 +368,24 @@ fn scan_directory(dir: PathBuf, function: Option<&str>, opts: Opts) -> Result 0 { + if perm_interp.permissions.len() > 0 { + println!("here :))"); reporter.add_vulnerabilities( - vec![PermissionVuln::new(HashSet::::from_iter( - unused_permissions.cloned().into_iter(), - ))] - .into_iter(), + vec![PermissionVuln::new(perm_interp.permissions)].into_iter(), ); } } diff --git a/test-apps/jira-damn-vulnerable-forge-app/src/utils.js b/test-apps/jira-damn-vulnerable-forge-app/src/utils.js index 946ca142..f4dc2192 100755 --- a/test-apps/jira-damn-vulnerable-forge-app/src/utils.js +++ b/test-apps/jira-damn-vulnerable-forge-app/src/utils.js @@ -9,6 +9,8 @@ import func_defult from './export_default'; import my_function from "./export_default2.js"; import {classone} from './anewclass'; +let global = "global__flag___test" + export async function fetchIssueSummary(issueIdOrKey, test_value) { let obj = { @@ -36,7 +38,7 @@ export async function fetchIssueSummary(issueIdOrKey, test_value) { diffunc() // different_function(); - let a_class = new classone(); + let a_class = new ANewClass(); a_class.function_a_new_class(); let val = "grapefruit"; @@ -49,7 +51,7 @@ export async function fetchIssueSummary(issueIdOrKey, test_value) { const resp = await api .asApp() - .requestJira(get_random_string(), obj); + .requestJira("/rest/api/3/issue/27/attachments", obj); const data = await resp.json(); console.log(JSON.stringify(data)); return data['fields']['summary']; @@ -74,7 +76,7 @@ export async function writeComment(issueIdOrKey, comment) { const resp = await api .asApp() .requestJira(route`/rest/api/3/issue/${issueIdOrKey}/comment`, { - method: 'DELETE', + method: 'POST', headers: { Accept: 'application/json', 'Content-Type': 'application/json', From 8b87b5f8de2b69f4758cc078476434e6d435bcb4 Mon Sep 17 00:00:00 2001 From: James Gersbach Date: Fri, 11 Aug 2023 10:53:56 -0700 Subject: [PATCH 156/517] fix to permission checker --- crates/forge_analyzer/src/checkers.rs | 53 ++++++++++++++++++- crates/forge_analyzer/src/definitions.rs | 5 -- crates/forge_analyzer/src/interp.rs | 11 +--- crates/fsrt/src/main.rs | 4 ++ .../src/utils.js | 2 +- 5 files changed, 58 insertions(+), 17 deletions(-) diff --git a/crates/forge_analyzer/src/checkers.rs b/crates/forge_analyzer/src/checkers.rs index a4c95cdc..09df0287 100644 --- a/crates/forge_analyzer/src/checkers.rs +++ b/crates/forge_analyzer/src/checkers.rs @@ -6,6 +6,7 @@ use forge_permission_resolver::permissions_resolver::{ }; use forge_utils::FxHashMap; use itertools::Itertools; +use petgraph::operator; use regex::Regex; use serde::de::value; use smallvec::SmallVec; @@ -726,6 +727,25 @@ impl<'cx> Runner<'cx> for SecretChecker { } _ => {} } + } else if let Some(value) = interp.body().vars.get(varid) { + if let VarKind::GlobalRef(def) = value { + if let Some(value) = interp.defid_to_value.get(def) { + println!("value [] {value:?}"); + match value { + Value::Const(_) | Value::Phi(_) => { + let vuln = SecretVuln::new( + interp.callstack(), + interp.env(), + interp.entry(), + ); + info!("Found a vuln!"); + self.vulns.push(vuln); + } + _ => {} + } + } + + } } } } @@ -743,6 +763,7 @@ impl<'cx> Checker<'cx> for SecretChecker { pub struct PermissionDataflow { needs_call: Vec<(DefId, Vec)>, pub varid_to_value: FxHashMap<(DefId, VarId, Option), Value>, + pub defid_to_value: FxHashMap, } impl PermissionDataflow { @@ -792,6 +813,7 @@ impl<'cx> Dataflow<'cx> for PermissionDataflow { Self { needs_call: vec![], varid_to_value: FxHashMap::default(), + defid_to_value: FxHashMap::default(), } } @@ -826,6 +848,17 @@ impl<'cx> Dataflow<'cx> for PermissionDataflow { value, &mut intrinsic_argument.first_arg, ); + } else if let Some(value) = _interp.body().vars.get(varid) { + if let VarKind::GlobalRef(def) = value { + if let Some(Value::Const(value)) = _interp.defid_to_value.get(def) { + intrinsic_argument.first_arg = Some(vec![]); + add_elements_to_intrinsic_struct( + &Value::Const(value.clone()), + &mut intrinsic_argument.first_arg, + ); + } + + } } } } @@ -844,7 +877,7 @@ impl<'cx> Dataflow<'cx> for PermissionDataflow { let mut permissions_within_call: Vec = vec![]; let intrinsic_func_type = intrinsic_argument.name.unwrap(); - //println!("intrinsic_argument {:?}", intrinsic_argument); + println!("intrinsic_argument {:?}", intrinsic_argument); if intrinsic_argument.first_arg == None { println!("interp permissions {:?}", _interp.permissions); @@ -1219,7 +1252,23 @@ impl<'cx> Dataflow<'cx> for DefintionAnalysisRunner { } } } - Rvalue::Read(_) => { + Rvalue::Read(operand) => { + // test example exclusively for demo, needs to be fully implemented + + if let Rvalue::Read(read) = rvalue { + if let Operand::Lit(lit) = read { + if let Literal::Str(str) = lit { + if let Base::Var(varid) = var.base { + if let Some(VarKind::GlobalRef(def)) = interp.body().vars.get(varid) { + println!("inserting ---> {def:?} {str:?}"); + interp.defid_to_value.insert(*def, Value::Const(Const::Literal(str.to_string()))); + } + } + } + } + } + /* should be expanded to include all of the cases ... */ + self.add_variable(interp, var, &varid, def, rvalue); } Rvalue::Template(_) => { diff --git a/crates/forge_analyzer/src/definitions.rs b/crates/forge_analyzer/src/definitions.rs index 48f9b0fb..26623b77 100644 --- a/crates/forge_analyzer/src/definitions.rs +++ b/crates/forge_analyzer/src/definitions.rs @@ -1588,8 +1588,6 @@ impl<'cx> FunctionAnalyzer<'cx> { Operand::with_var(var) } Expr::Lit(lit) => { - println!("lowering literal {lit:?}"); - lit.clone().into() } Expr::Tpl(tpl) => { @@ -2877,8 +2875,6 @@ impl Visit for GlobalCollector<'_> { } } analyzer.lower_stmts(all_module_items.as_slice()); - println!("analyzer body !` {owner:?} {:?}", analyzer.body.clone()); - let body = analyzer.body; @@ -3184,7 +3180,6 @@ impl Environment { } AnonType::Closure => { let name = name.into(); - println!("name --> {name:?}"); let id = self .resolver .add_anon(DefRes::Closure(()), name.into(), module); diff --git a/crates/forge_analyzer/src/interp.rs b/crates/forge_analyzer/src/interp.rs index 5662fd53..c9f9a9db 100644 --- a/crates/forge_analyzer/src/interp.rs +++ b/crates/forge_analyzer/src/interp.rs @@ -515,6 +515,7 @@ pub struct Interp<'cx, C: Runner<'cx>> { pub jira_regex_map: &'cx HashMap, pub confluence_regex_map: &'cx HashMap, pub varid_to_value: FxHashMap<(DefId, VarId, Option), Value>, + pub defid_to_value: FxHashMap, _checker: PhantomData, } @@ -604,6 +605,7 @@ impl<'cx, C: Runner<'cx>> Interp<'cx, C> { expected_return_values: HashMap::default(), permissions, varid_to_value: DefinitionAnalysisMap::default(), + defid_to_value: FxHashMap::default(), jira_permission_resolver, confluence_permission_resolver, jira_regex_map, @@ -734,16 +736,7 @@ impl<'cx, C: Runner<'cx>> Interp<'cx, C> { let mut dataflow = C::Dataflow::with_interp(self); let mut worklist = WorkList::new(); - // all all of the global state prior to running - - println!("all global {:?}", &self.env().global); - for global_def in &self.env().global { - println!("global_def {global_def:?}"); - - - let func = self.env().def_ref(*global_def).expect_body(); - println!("\tfunc {func:?}"); worklist.push_front_blocks(self.env, *global_def, self.call_all); } diff --git a/crates/fsrt/src/main.rs b/crates/fsrt/src/main.rs index 0a746ae7..21592d7c 100644 --- a/crates/fsrt/src/main.rs +++ b/crates/fsrt/src/main.rs @@ -317,6 +317,7 @@ fn scan_directory(dir: PathBuf, function: Option<&str>, opts: Opts) -> Result, opts: Opts) -> Result, opts: Opts) -> Result, opts: Opts) -> Result Date: Fri, 11 Aug 2023 10:54:32 -0700 Subject: [PATCH 157/517] formatting --- crates/forge_analyzer/src/checkers.rs | 17 +++++++++++------ crates/forge_analyzer/src/definitions.rs | 12 ++---------- 2 files changed, 13 insertions(+), 16 deletions(-) diff --git a/crates/forge_analyzer/src/checkers.rs b/crates/forge_analyzer/src/checkers.rs index 09df0287..88938f62 100644 --- a/crates/forge_analyzer/src/checkers.rs +++ b/crates/forge_analyzer/src/checkers.rs @@ -744,7 +744,6 @@ impl<'cx> Runner<'cx> for SecretChecker { _ => {} } } - } } } @@ -850,14 +849,15 @@ impl<'cx> Dataflow<'cx> for PermissionDataflow { ); } else if let Some(value) = _interp.body().vars.get(varid) { if let VarKind::GlobalRef(def) = value { - if let Some(Value::Const(value)) = _interp.defid_to_value.get(def) { + if let Some(Value::Const(value)) = + _interp.defid_to_value.get(def) + { intrinsic_argument.first_arg = Some(vec![]); add_elements_to_intrinsic_struct( &Value::Const(value.clone()), &mut intrinsic_argument.first_arg, ); } - } } } @@ -1259,11 +1259,16 @@ impl<'cx> Dataflow<'cx> for DefintionAnalysisRunner { if let Operand::Lit(lit) = read { if let Literal::Str(str) = lit { if let Base::Var(varid) = var.base { - if let Some(VarKind::GlobalRef(def)) = interp.body().vars.get(varid) { + if let Some(VarKind::GlobalRef(def)) = + interp.body().vars.get(varid) + { println!("inserting ---> {def:?} {str:?}"); - interp.defid_to_value.insert(*def, Value::Const(Const::Literal(str.to_string()))); + interp.defid_to_value.insert( + *def, + Value::Const(Const::Literal(str.to_string())), + ); } - } + } } } } diff --git a/crates/forge_analyzer/src/definitions.rs b/crates/forge_analyzer/src/definitions.rs index 26623b77..935d47af 100644 --- a/crates/forge_analyzer/src/definitions.rs +++ b/crates/forge_analyzer/src/definitions.rs @@ -223,7 +223,6 @@ pub fn run_resolver( } for (curr_mod, module) in modules.iter_enumerated() { - let global_id = environment.get_or_reserve_global_scope(curr_mod); let mut global_collector = GlobalCollector { @@ -1587,9 +1586,7 @@ impl<'cx> FunctionAnalyzer<'cx> { let var = self.body.get_or_insert_global(def); Operand::with_var(var) } - Expr::Lit(lit) => { - lit.clone().into() - } + Expr::Lit(lit) => lit.clone().into(), Expr::Tpl(tpl) => { let tpl = self.lower_tpl(tpl); Operand::with_var( @@ -2841,9 +2838,7 @@ impl Visit for GlobalCollector<'_> { fn visit_class(&mut self, _: &Clss) {} - fn visit_module(&mut self, n: &Module) { - // we encouter 2 statements, so the defid gets reassigned every time let owner = self.global_id; @@ -2877,10 +2872,8 @@ impl Visit for GlobalCollector<'_> { analyzer.lower_stmts(all_module_items.as_slice()); let body = analyzer.body; - *self.res.def_mut(owner).expect_body() = body; } - } impl Visit for ExportCollector<'_> { @@ -3115,12 +3108,11 @@ impl Environment { #[inline] fn get_or_reserve_global_scope(&mut self, module: ModId) -> DefId { if let Some(defid) = self.global.get(module) { - return defid.clone() + return defid.clone(); } self.reserve_global_scope(module) } - fn new_key_from_res(&mut self, id: DefId, res: DefRes) -> DefKey { match res { DefKind::Arg => DefKind::Arg, From 864c410d7f93839c8d4f1f1e49dcd7c2240b3dd4 Mon Sep 17 00:00:00 2001 From: James Gersbach Date: Fri, 11 Aug 2023 15:18:47 -0700 Subject: [PATCH 158/517] currnet --- .DS_Store | Bin 0 -> 6148 bytes crates/forge_analyzer/src/checkers.rs | 13 ++----------- crates/forge_analyzer/src/interp.rs | 2 +- .../src/permissions_resolver.rs | 3 --- crates/fsrt/src/main.rs | 5 ++--- .../jira-damn-vulnerable-forge-app/manifest.yml | 8 ++------ 6 files changed, 7 insertions(+), 24 deletions(-) create mode 100644 .DS_Store diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..8be8bb9e33b0a65196180c3aa6e75f780e7f5e35 GIT binary patch literal 6148 zcmeHKJ8r^25S>X}IHI9Uxfe*m4OSMMfD44g>5z=%q<6)+I2vz01;|O~H0TYQdAqao z*77SnjEHFW({Du9BGSSQ*LJnwoxF+)P=Uu#z`hR!ZdeniK>u_g_y_=Okaok` zX9-}j1h6Jffylr#sKB6Vju;wrU$5 ds~1IGu{F+X;uPp~ Dataflow<'cx> for PermissionDataflow { let mut permissions_within_call: Vec = vec![]; let intrinsic_func_type = intrinsic_argument.name.unwrap(); - println!("intrinsic_argument {:?}", intrinsic_argument); + //println!("intrinsic_argument {:?}", intrinsic_argument); if intrinsic_argument.first_arg == None { println!("interp permissions {:?}", _interp.permissions); @@ -891,11 +891,7 @@ impl<'cx> Dataflow<'cx> for PermissionDataflow { .for_each(|first_arg_vec| { if let Some(second_arg_vec) = intrinsic_argument.second_arg.clone() { first_arg_vec.iter().for_each(|first_arg| { - println!("first arg before {first_arg:?}"); let first_arg = first_arg.replace(&['\"'][..], ""); - - println!("first arg after {first_arg:?}"); - second_arg_vec.iter().for_each(|second_arg| { if intrinsic_func_type == IntrinsicName::RequestConfluence { let permissions = check_url_for_permissions( @@ -918,11 +914,7 @@ impl<'cx> Dataflow<'cx> for PermissionDataflow { }) } else { first_arg_vec.iter().for_each(|first_arg| { - println!("first arg before {first_arg:?}"); let first_arg = first_arg.replace(&['\"'][..], ""); - - println!("first arg after {first_arg:?}"); - if intrinsic_func_type == IntrinsicName::RequestConfluence { let permissions = check_url_for_permissions( &_interp.confluence_permission_resolver, @@ -944,7 +936,7 @@ impl<'cx> Dataflow<'cx> for PermissionDataflow { } }); - println!("permissions within call {permissions_within_call:?}"); + //println!("permissions within call {permissions_within_call:?}"); _interp.permissions = _interp .permissions @@ -1228,7 +1220,6 @@ impl<'cx> Dataflow<'cx> for DefintionAnalysisRunner { inst: &'cx Inst, initial_state: Self::State, ) -> Self::State { - println!("\t inst {inst}"); match inst { Inst::Expr(rvalue) => { diff --git a/crates/forge_analyzer/src/interp.rs b/crates/forge_analyzer/src/interp.rs index c9f9a9db..5bfb919c 100644 --- a/crates/forge_analyzer/src/interp.rs +++ b/crates/forge_analyzer/src/interp.rs @@ -746,7 +746,7 @@ impl<'cx, C: Runner<'cx>> Interp<'cx, C> { let arguments = self.callstack_arguments.pop(); let name = self.env.def_name(def); debug!("Dataflow: {name} - {block_id}"); - println!("Dataflow: {name} - {block_id}"); + // println!("Dataflow: {name} - {block_id}"); self.dataflow_visited.insert(def); let func = self.env().def_ref(def).expect_body(); self.curr_body.set(Some(func)); diff --git a/crates/forge_permission_resolver/src/permissions_resolver.rs b/crates/forge_permission_resolver/src/permissions_resolver.rs index 8473b3aa..d13fc7a9 100644 --- a/crates/forge_permission_resolver/src/permissions_resolver.rs +++ b/crates/forge_permission_resolver/src/permissions_resolver.rs @@ -110,11 +110,8 @@ pub fn get_permisions_for( endpoint_map_classic: &mut PermissionHashMap, endpoint_regex: &mut HashMap, ) { - println!("pre api call"); if let Result::Ok(response) = ureq::get(url).call() { - println!("post api call"); let data: SwaggerReponse = response.into_json().unwrap(); - println!("post swagger parse"); for (key, endpoint_data) in &data.paths { let endpoint_data = get_request_type(endpoint_data, key); endpoint_data diff --git a/crates/fsrt/src/main.rs b/crates/fsrt/src/main.rs index 21592d7c..de1f2d62 100644 --- a/crates/fsrt/src/main.rs +++ b/crates/fsrt/src/main.rs @@ -231,7 +231,7 @@ fn scan_directory(dir: PathBuf, function: Option<&str>, opts: Opts) -> Result, opts: Opts) -> Result, opts: Opts) -> Result 0 { - println!("here :))"); reporter.add_vulnerabilities( vec![PermissionVuln::new(perm_interp.permissions)].into_iter(), ); diff --git a/test-apps/jira-damn-vulnerable-forge-app/manifest.yml b/test-apps/jira-damn-vulnerable-forge-app/manifest.yml index 33c820cf..97bdcc4b 100755 --- a/test-apps/jira-damn-vulnerable-forge-app/manifest.yml +++ b/test-apps/jira-damn-vulnerable-forge-app/manifest.yml @@ -57,13 +57,9 @@ app: id: ari:cloud:ecosystem::app/22948e9c-8414-4d24-bd45-f0dc7428608f permissions: scopes: - - 'storage:app' - - 'read:jira-user' + - 'read:user:jira' - 'read:jira-work' - - 'write:jira-work' - - 'manage:jira-project' - - 'manage:jira-configuration' - - 'manage:jira-webhook' + external: fetch: client: From 2f5ff452b9afbec00b35da1de369831949bebe76 Mon Sep 17 00:00:00 2001 From: Joshua Wong Date: Thu, 7 Sep 2023 15:13:38 -0500 Subject: [PATCH 159/517] fix: correctly lower environment variable reads --- crates/forge_analyzer/src/definitions.rs | 28 +++++++++++++++++------- 1 file changed, 20 insertions(+), 8 deletions(-) diff --git a/crates/forge_analyzer/src/definitions.rs b/crates/forge_analyzer/src/definitions.rs index 261fea02..8606a618 100644 --- a/crates/forge_analyzer/src/definitions.rs +++ b/crates/forge_analyzer/src/definitions.rs @@ -6,7 +6,7 @@ use crate::utils::calls_method; use forge_file_resolver::{FileResolver, ForgeResolver}; use forge_utils::{create_newtype, FxHashMap}; -use smallvec::SmallVec; +use smallvec::{smallvec, SmallVec}; use swc_core::{ common::SyntaxContext, ecma::{ @@ -750,7 +750,7 @@ fn classify_api_call(expr: &Expr) -> ApiCallKind { fn check(&mut self, name: &str) { if name.contains("permission") { self.kind = self.kind.max(ApiCallKind::Authorize); - } else if TRIVIAL.is_match(name) { + } else if TRIVIAL.is_match(name) || name.contains("group") { self.kind = self.kind.max(ApiCallKind::Trivial); } } @@ -834,6 +834,16 @@ impl<'cx> FunctionAnalyzer<'cx> { } fn lower_member(&mut self, obj: &Expr, prop: &MemberProp) -> Operand { + // FIXME: add support for computed property names, i.e. process['env'] + if obj.as_ident().is_some_and(|ident| *ident.sym == *"process") + && prop.as_ident().is_some_and(|ident| *ident.sym == *"env") + { + self.push_curr_inst(Inst::Expr(Rvalue::Intrinsic( + Intrinsic::EnvRead, + smallvec![], + ))); + } + let obj = self.lower_expr(obj); let Operand::Var(mut var) = obj else { // FIXME: handle literals @@ -1953,7 +1963,7 @@ impl Visit for FunctionCollector<'_> { fn visit_var_declarator(&mut self, n: &VarDeclarator) { n.visit_children_with(self); - let Some(BindingIdent{id, ..}) = n.name.as_ident() else { + let Some(BindingIdent { id, .. }) = n.name.as_ident() else { return; }; let id = id.to_id(); @@ -1989,7 +1999,9 @@ impl Visit for FunctionCollector<'_> { let owner = self.res .get_or_overwrite_sym(id, self.module, DefKind::Function(())); - let Some(ExprOrSpread { expr, .. }) = &args.first() else { return; }; + let Some(ExprOrSpread { expr, .. }) = &args.first() else { + return; + }; let old_parent = self.parent.replace(owner); let mut analyzer = FunctionAnalyzer { res: self.res, @@ -2164,10 +2176,10 @@ fn as_resolver_def<'a>( module: ModId, ) -> Option<(DefId, &'a JsWord, &'a Expr)> { let Some((objid, ResolverDef::FnDef)) = call - .callee - .as_expr() - .and_then(|expr| as_resolver(expr, res, module)) else - { + .callee + .as_expr() + .and_then(|expr| as_resolver(expr, res, module)) + else { return None; }; let [ExprOrSpread { expr: name, .. }, ExprOrSpread { expr: args, .. }] = &*call.args else { From d893f59755087220f6887b187783640f683d2334 Mon Sep 17 00:00:00 2001 From: Joshua Wong Date: Thu, 7 Sep 2023 15:31:47 -0500 Subject: [PATCH 160/517] chore: move test module directly below the permission_resolver module and only build during testing. --- crates/forge_permission_resolver/src/lib.rs | 1 - .../src/permissions_resolver.rs | 73 +++++++++++++++++- crates/forge_permission_resolver/src/test.rs | 74 ------------------- 3 files changed, 72 insertions(+), 76 deletions(-) delete mode 100644 crates/forge_permission_resolver/src/test.rs diff --git a/crates/forge_permission_resolver/src/lib.rs b/crates/forge_permission_resolver/src/lib.rs index fb793cf6..f90e19f4 100644 --- a/crates/forge_permission_resolver/src/lib.rs +++ b/crates/forge_permission_resolver/src/lib.rs @@ -1,2 +1 @@ pub mod permissions_resolver; -pub mod test; diff --git a/crates/forge_permission_resolver/src/permissions_resolver.rs b/crates/forge_permission_resolver/src/permissions_resolver.rs index 874dd64b..fe98ab4b 100644 --- a/crates/forge_permission_resolver/src/permissions_resolver.rs +++ b/crates/forge_permission_resolver/src/permissions_resolver.rs @@ -1,6 +1,5 @@ use regex::Regex; use serde::Deserialize; -use serde_json; use std::{cmp::Reverse, collections::HashMap, hash::Hash, vec}; use tracing::warn; use ureq; @@ -190,3 +189,75 @@ fn get_scopes(endpoint_data: &RequestDetails) -> Vec { .flatten() .collect(); } + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn test_simple_url_with_end_var() { + let result = find_regex_for_endpoint("/rest/api/3/version/{id}/mergeto/{moveIssuesTo}"); + assert_eq!(result, "/rest/api/3/version/.*/mergeto/.*-"); + } + + #[test] + fn test_simple_url_with_middle_var() { + let result = find_regex_for_endpoint("/rest/api/3/issue/{issueIdOrKey}/comment"); + assert_eq!(result, "/rest/api/3/issue/.*/comment-"); + } + + #[test] + fn test_simple_url_with_no_var() { + let result = find_regex_for_endpoint("/rest/api/3/fieldconfigurationscheme/project"); + assert_eq!(result, "/rest/api/3/fieldconfigurationscheme/project-"); + } + + #[test] + fn test_resolving_permssion_basic() { + let (permission_map, regex_map) = get_permission_resolver(); + let url = "/rest/api/3/issue/27/attachments"; + let request_type = RequestType::Post; + let result = check_url_for_permissions(&permission_map, ®ex_map, request_type, url); + + let expected_permission: Vec = vec![ + String::from("write:jira-work"), + String::from("read:user:jira"), + String::from("write:attachment:jira"), + String::from("read:attachment:jira"), + String::from("read:avatar:jira"), + ]; + + assert_eq!(result, expected_permission); + } + + #[test] + fn test_resolving_permssion_end_var() { + let (permission_map, regex_map) = get_permission_resolver(); + let url = "/wiki/rest/api/relation/1/from/1/2/to/3/4"; + let request_type = RequestType::Get; + let result = check_url_for_permissions(&permission_map, ®ex_map, request_type, url); + + let expected_permission: Vec = vec![ + String::from("read:confluence-content.summary"), + String::from("read:relation:confluence"), + String::from("read:content-details:confluence"), + ]; + + assert_eq!(result, expected_permission); + } + + #[test] + fn test_resolving_permssion_no_var() { + let (permission_map, regex_map) = get_permission_resolver(); + let url = "/rest/api/3/issue/archive"; + let request_type = RequestType::Post; + let result = check_url_for_permissions(&permission_map, ®ex_map, request_type, url); + + let expected_permission: Vec = vec![ + String::from("write:jira-work"), + String::from("write:issue:jira"), + ]; + + assert_eq!(result, expected_permission); + } +} diff --git a/crates/forge_permission_resolver/src/test.rs b/crates/forge_permission_resolver/src/test.rs deleted file mode 100644 index e87e6056..00000000 --- a/crates/forge_permission_resolver/src/test.rs +++ /dev/null @@ -1,74 +0,0 @@ -use crate::permissions_resolver::{ - check_url_for_permissions, find_regex_for_endpoint, get_permission_resolver, RequestType, -}; - -mod tests { - use super::*; - - #[test] - fn test_simple_url_with_end_var() { - let result = find_regex_for_endpoint("/rest/api/3/version/{id}/mergeto/{moveIssuesTo}"); - assert_eq!(result, "/rest/api/3/version/.*/mergeto/.*-"); - } - - #[test] - fn test_simple_url_with_middle_var() { - let result = find_regex_for_endpoint("/rest/api/3/issue/{issueIdOrKey}/comment"); - assert_eq!(result, "/rest/api/3/issue/.*/comment-"); - } - - #[test] - fn test_simple_url_with_no_var() { - let result = find_regex_for_endpoint("/rest/api/3/fieldconfigurationscheme/project"); - assert_eq!(result, "/rest/api/3/fieldconfigurationscheme/project-"); - } - - #[test] - fn test_resolving_permssion_basic() { - let (permission_map, regex_map) = get_permission_resolver(); - let url = "/rest/api/3/issue/27/attachments"; - let request_type = RequestType::Post; - let result = check_url_for_permissions(&permission_map, ®ex_map, request_type, url); - - let expected_permission: Vec = vec![ - String::from("write:jira-work"), - String::from("read:user:jira"), - String::from("write:attachment:jira"), - String::from("read:attachment:jira"), - String::from("read:avatar:jira"), - ]; - - assert_eq!(result, expected_permission); - } - - #[test] - fn test_resolving_permssion_end_var() { - let (permission_map, regex_map) = get_permission_resolver(); - let url = "/wiki/rest/api/relation/1/from/1/2/to/3/4"; - let request_type = RequestType::Get; - let result = check_url_for_permissions(&permission_map, ®ex_map, request_type, url); - - let expected_permission: Vec = vec![ - String::from("read:confluence-content.summary"), - String::from("read:relation:confluence"), - String::from("read:content-details:confluence"), - ]; - - assert_eq!(result, expected_permission); - } - - #[test] - fn test_resolving_permssion_no_var() { - let (permission_map, regex_map) = get_permission_resolver(); - let url = "/rest/api/3/issue/archive"; - let request_type = RequestType::Post; - let result = check_url_for_permissions(&permission_map, ®ex_map, request_type, url); - - let expected_permission: Vec = vec![ - String::from("write:jira-work"), - String::from("write:issue:jira"), - ]; - - assert_eq!(result, expected_permission); - } -} From dae77d6b2a36e2bbcf74c8154b072849a8189fec Mon Sep 17 00:00:00 2001 From: Joshua Wong Date: Thu, 7 Sep 2023 15:32:22 -0500 Subject: [PATCH 161/517] fix: correctly lower process['env'] --- crates/forge_analyzer/src/definitions.rs | 7 +++---- crates/forge_analyzer/src/utils.rs | 13 ++++++++++++- 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/crates/forge_analyzer/src/definitions.rs b/crates/forge_analyzer/src/definitions.rs index 8606a618..b49bfd64 100644 --- a/crates/forge_analyzer/src/definitions.rs +++ b/crates/forge_analyzer/src/definitions.rs @@ -2,7 +2,7 @@ use std::{borrow::Borrow, fmt, mem}; -use crate::utils::calls_method; +use crate::utils::{calls_method, eq_prop_name}; use forge_file_resolver::{FileResolver, ForgeResolver}; use forge_utils::{create_newtype, FxHashMap}; @@ -834,10 +834,9 @@ impl<'cx> FunctionAnalyzer<'cx> { } fn lower_member(&mut self, obj: &Expr, prop: &MemberProp) -> Operand { - // FIXME: add support for computed property names, i.e. process['env'] - if obj.as_ident().is_some_and(|ident| *ident.sym == *"process") - && prop.as_ident().is_some_and(|ident| *ident.sym == *"env") + if obj.as_ident().is_some_and(|ident| *ident.sym == *"process") && eq_prop_name(prop, "env") { + // FIXME: Store the exact environment variable in the IR and don't create duplicate IR instructions. self.push_curr_inst(Inst::Expr(Rvalue::Intrinsic( Intrinsic::EnvRead, smallvec![], diff --git a/crates/forge_analyzer/src/utils.rs b/crates/forge_analyzer/src/utils.rs index 897622e0..1d11af3b 100644 --- a/crates/forge_analyzer/src/utils.rs +++ b/crates/forge_analyzer/src/utils.rs @@ -1,5 +1,5 @@ use crate::definitions::CalleeRef; -use swc_core::ecma::ast::{Expr, MemberProp}; +use swc_core::ecma::ast::{Expr, Lit, MemberProp}; pub fn calls_method(n: CalleeRef<'_>, name: &str) -> bool { if let CalleeRef::Expr(Expr::Member(mem)) = &n { @@ -9,3 +9,14 @@ pub fn calls_method(n: CalleeRef<'_>, name: &str) -> bool { } false } + +pub fn eq_prop_name(n: &MemberProp, name: &str) -> bool { + match n { + MemberProp::Ident(ident) => ident.sym == *name, + MemberProp::Computed(expr) => match expr.expr.as_ref() { + Expr::Lit(Lit::Str(lit)) => *lit.value == *name, + _ => false, + }, + _ => false, + } +} From 3946b19b4ac938fff2fba2d79db50bbefb923f7b Mon Sep 17 00:00:00 2001 From: Joshua Wong Date: Thu, 7 Sep 2023 15:34:15 -0500 Subject: [PATCH 162/517] style: run rustfmt --- crates/forge_analyzer/src/checkers.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/forge_analyzer/src/checkers.rs b/crates/forge_analyzer/src/checkers.rs index 48a7b5ca..9de7b349 100644 --- a/crates/forge_analyzer/src/checkers.rs +++ b/crates/forge_analyzer/src/checkers.rs @@ -301,8 +301,8 @@ impl<'cx> Dataflow<'cx> for AuthenticateDataflow { initial_state: Self::State, ) -> Self::State { let Some((callee_def, _body)) = self.resolve_call(interp, callee) else { - return initial_state; - }; + return initial_state; + }; match interp.func_state(callee_def) { Some(state) => { if state == Authenticated::Yes { From 12f9a60ec5607f9ad0c64f64a060570e01c797de Mon Sep 17 00:00:00 2001 From: James Gersbach Date: Sun, 10 Sep 2023 23:04:53 -0400 Subject: [PATCH 163/517] updates to test cases --- .../forge_permission_resolver/src/permissions_resolver.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/crates/forge_permission_resolver/src/permissions_resolver.rs b/crates/forge_permission_resolver/src/permissions_resolver.rs index 7874eecb..5bd3e8a9 100644 --- a/crates/forge_permission_resolver/src/permissions_resolver.rs +++ b/crates/forge_permission_resolver/src/permissions_resolver.rs @@ -220,7 +220,7 @@ mod test { #[test] fn test_resolving_permssion_basic() { - let (permission_map, regex_map) = get_permission_resolver(); + let (permission_map, regex_map) = get_permission_resolver_jira(); let url = "/rest/api/3/issue/27/attachments"; let request_type = RequestType::Post; let result = check_url_for_permissions(&permission_map, ®ex_map, request_type, url); @@ -238,7 +238,7 @@ mod test { #[test] fn test_resolving_permssion_end_var() { - let (permission_map, regex_map) = get_permission_resolver(); + let (permission_map, regex_map) = get_permission_resolver_confluence(); let url = "/wiki/rest/api/relation/1/from/1/2/to/3/4"; let request_type = RequestType::Get; let result = check_url_for_permissions(&permission_map, ®ex_map, request_type, url); @@ -254,7 +254,7 @@ mod test { #[test] fn test_resolving_permssion_no_var() { - let (permission_map, regex_map) = get_permission_resolver(); + let (permission_map, regex_map) = get_permission_resolver_jira(); let url = "/rest/api/3/issue/archive"; let request_type = RequestType::Post; let result = check_url_for_permissions(&permission_map, ®ex_map, request_type, url); From 75985e95e91079f108b1e4dda5b0d606d4797f13 Mon Sep 17 00:00:00 2001 From: awang7 Date: Mon, 11 Sep 2023 15:01:45 -0400 Subject: [PATCH 164/517] added new structs for user non-invokable modules --- crates/forge_loader/src/manifest.rs | 138 +++++++++++++++++++++++++++- 1 file changed, 136 insertions(+), 2 deletions(-) diff --git a/crates/forge_loader/src/manifest.rs b/crates/forge_loader/src/manifest.rs index cd415c53..9add8383 100644 --- a/crates/forge_loader/src/manifest.rs +++ b/crates/forge_loader/src/manifest.rs @@ -66,12 +66,57 @@ struct ScheduledTrigger<'a> { interval: Interval, } +#[derive(Debug, Clone, PartialEq, Eq, Deserialize)] +struct DataProvider<'a> { + key: &'a str, + #[serde(flatten, borrow)] + callback: Callback<'a>, +} + +#[derive(Debug, Clone, PartialEq, Eq, Deserialize)] +pub struct Callback<'a> { + pub function: &'a str, +} + +// Struct for Custom field Module. Check that search suggestion gets read in correctly. +#[derive(Debug, Clone, PartialEq, Eq, Deserialize)] +struct CustomField<'a> { + #[serde(flatten, borrow)] + key: &'a str, + search_suggestion: &'a str, +} + + +#[derive(Debug, Clone, PartialEq, Eq, Deserialize)] +struct UiModificatons<'a> { + #[serde(flatten, borrow)] + key: &'a str, + resolver: Callback<'a>, +} + +#[derive(Debug, Clone, PartialEq, Eq, Deserialize)] +struct WorkflowValidator<'a> { + #[serde(flatten, borrow)] + key: &'a str, + functon: &'a str, + resolver: Callback<'a> +} + +#[derive(Debug, Clone, PartialEq, Eq, Deserialize)] +struct WorkflowPostFunction<'a> { + #[serde(flatten, borrow)] + key: &'a str, + functon: &'a str, +} + +// Add more structs here for deserializing forge modules #[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] pub struct ForgeModules<'a> { #[serde(rename = "macro", default, borrow)] macros: Vec>, #[serde(rename = "function", default, borrow)] pub functions: Vec>, + // deserializing non user-invocable modules #[serde(rename = "webtrigger", default, borrow)] webtriggers: Vec>, #[serde(rename = "trigger", default, borrow)] @@ -80,6 +125,17 @@ pub struct ForgeModules<'a> { scheduled_triggers: Vec>, #[serde(rename = "consumer", default, borrow)] pub consumers: Vec>, + #[serde(rename = "compass:dataProvider", default, borrow)] + pub data_provider: Vec>, + #[serde(rename = "jira:customField", default, borrow)] + pub custom_field: Vec>, + #[serde(rename = "jira:uiModificatons", default, borrow)] + pub ui_modifications: Vec>, + #[serde(rename = "jira:workflowValidator", default, borrow)] + pub workflow_validator: Vec>, + #[serde(rename = "jira:workflowPostFunction", default, borrow)] + pub workflow_post_function: Vec>, + // deserializing user invokable module functions #[serde(flatten)] extra: FxHashMap>>, } @@ -158,12 +214,25 @@ pub struct FunctionRef<'a, S = Unresolved> { status: S, } + +// Add an extra variant to the FunctionTy enum for non user invocable functions +// Indirect: functions indirectly invoked by user :O So kewl. +// TODO: change this to struct with bools #[derive(Debug, Clone, PartialEq, Eq)] pub enum FunctionTy { Invokable(T), WebTrigger(T), } +// Struct used for tracking what scan a funtion requires. +#[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] +pub struct Entrypoints<'a> { + function:Vec>, + invokable: bool, + web_trigger: bool, +} + +// Helper functions that help filter out which functions are what. impl FunctionTy { pub fn map(self, f: impl FnOnce(T) -> O) -> FunctionTy { match self { @@ -199,16 +268,18 @@ impl AsRef for FunctionTy { } } + impl<'a> ForgeModules<'a> { + + pub fn into_analyzable_functions( mut self, ) -> impl Iterator>> { // number of webtriggers are usually low, so it's better to just sort them and reuse - // the vec's storage instead of using a HashSet self.webtriggers .sort_unstable_by_key(|trigger| trigger.function); - // same rational for using a BTreeSet + let mut ignored_functions: BTreeSet<_> = self .scheduled_triggers .into_iter() @@ -220,6 +291,7 @@ impl<'a> ForgeModules<'a> { ) .collect(); + // make alternate_functions all user-invokable functions let mut alternate_functions: Vec<&str> = Vec::new(); for module in self.extra.into_values().flatten() { alternate_functions.extend(module.function); @@ -228,12 +300,19 @@ impl<'a> ForgeModules<'a> { } } + // Iterate over Consumers and check that if consumers isn't in alternate functions, add consumer funtion to be ignored + // assuming that alternate functions already has all user invokable functions. self.consumers.iter().for_each(|consumer| { if !alternate_functions.contains(&consumer.resolver.function) { ignored_functions.insert(consumer.resolver.function); } }); + // TODO: Iterate through all deserialized entrypoints that are represented as a struct when deserialized + // Update Struct values to be true or not. If any part true, then scan. + // This solution fixes the problem that we only check known user invokable modules and also acccounts for non-invokable module entry points + + // return non-user invokable functions self.functions.into_iter().filter_map(move |func| { if ignored_functions.contains(&func.key) { return None; @@ -400,4 +479,59 @@ mod tests { } ); } + + // Modified specific deserialization schemes for modules. Checking that new schemes can deserialize function values. + #[test] + fn test_new_deserialize() { + + let json = r#"{ + "app": { + "name": "My App", + "id": "my-app" + }, + "modules": { + "macro": [ + { + "key": "my-macro", + "title": "My Macro", + "resolver": { + "function": Catch-me-if-you-can1 + }, + "config": { + "function": Catch-me-if-you-can2 + } + } + ], + "function": [ + { + "key": "my-function", + "handler": "my-function-handler", + "providers": { + "auth": ["my-auth-provider"] + } + } + ], + "webtrigger": [ + { + "key": "my-webtrigger", + "function": "my-webtrigger-handler" + } + ] + }, + "permissions": { + "scopes": [ + "my-scope" + ], + "content": { + "scripts": [ + "my-script.js" + ], + "styles": [ + "my-style.css" + ] + } + } + }"#; + + } } From d799e8261d49fcb816cc0b6db466f14c2968bb26 Mon Sep 17 00:00:00 2001 From: awang7 Date: Mon, 11 Sep 2023 15:01:45 -0400 Subject: [PATCH 165/517] added new structs for user non-invokable modules --- crates/forge_loader/src/manifest.rs | 138 +++++++++++++++++++++++++++- 1 file changed, 136 insertions(+), 2 deletions(-) diff --git a/crates/forge_loader/src/manifest.rs b/crates/forge_loader/src/manifest.rs index cd415c53..9add8383 100644 --- a/crates/forge_loader/src/manifest.rs +++ b/crates/forge_loader/src/manifest.rs @@ -66,12 +66,57 @@ struct ScheduledTrigger<'a> { interval: Interval, } +#[derive(Debug, Clone, PartialEq, Eq, Deserialize)] +struct DataProvider<'a> { + key: &'a str, + #[serde(flatten, borrow)] + callback: Callback<'a>, +} + +#[derive(Debug, Clone, PartialEq, Eq, Deserialize)] +pub struct Callback<'a> { + pub function: &'a str, +} + +// Struct for Custom field Module. Check that search suggestion gets read in correctly. +#[derive(Debug, Clone, PartialEq, Eq, Deserialize)] +struct CustomField<'a> { + #[serde(flatten, borrow)] + key: &'a str, + search_suggestion: &'a str, +} + + +#[derive(Debug, Clone, PartialEq, Eq, Deserialize)] +struct UiModificatons<'a> { + #[serde(flatten, borrow)] + key: &'a str, + resolver: Callback<'a>, +} + +#[derive(Debug, Clone, PartialEq, Eq, Deserialize)] +struct WorkflowValidator<'a> { + #[serde(flatten, borrow)] + key: &'a str, + functon: &'a str, + resolver: Callback<'a> +} + +#[derive(Debug, Clone, PartialEq, Eq, Deserialize)] +struct WorkflowPostFunction<'a> { + #[serde(flatten, borrow)] + key: &'a str, + functon: &'a str, +} + +// Add more structs here for deserializing forge modules #[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] pub struct ForgeModules<'a> { #[serde(rename = "macro", default, borrow)] macros: Vec>, #[serde(rename = "function", default, borrow)] pub functions: Vec>, + // deserializing non user-invocable modules #[serde(rename = "webtrigger", default, borrow)] webtriggers: Vec>, #[serde(rename = "trigger", default, borrow)] @@ -80,6 +125,17 @@ pub struct ForgeModules<'a> { scheduled_triggers: Vec>, #[serde(rename = "consumer", default, borrow)] pub consumers: Vec>, + #[serde(rename = "compass:dataProvider", default, borrow)] + pub data_provider: Vec>, + #[serde(rename = "jira:customField", default, borrow)] + pub custom_field: Vec>, + #[serde(rename = "jira:uiModificatons", default, borrow)] + pub ui_modifications: Vec>, + #[serde(rename = "jira:workflowValidator", default, borrow)] + pub workflow_validator: Vec>, + #[serde(rename = "jira:workflowPostFunction", default, borrow)] + pub workflow_post_function: Vec>, + // deserializing user invokable module functions #[serde(flatten)] extra: FxHashMap>>, } @@ -158,12 +214,25 @@ pub struct FunctionRef<'a, S = Unresolved> { status: S, } + +// Add an extra variant to the FunctionTy enum for non user invocable functions +// Indirect: functions indirectly invoked by user :O So kewl. +// TODO: change this to struct with bools #[derive(Debug, Clone, PartialEq, Eq)] pub enum FunctionTy { Invokable(T), WebTrigger(T), } +// Struct used for tracking what scan a funtion requires. +#[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] +pub struct Entrypoints<'a> { + function:Vec>, + invokable: bool, + web_trigger: bool, +} + +// Helper functions that help filter out which functions are what. impl FunctionTy { pub fn map(self, f: impl FnOnce(T) -> O) -> FunctionTy { match self { @@ -199,16 +268,18 @@ impl AsRef for FunctionTy { } } + impl<'a> ForgeModules<'a> { + + pub fn into_analyzable_functions( mut self, ) -> impl Iterator>> { // number of webtriggers are usually low, so it's better to just sort them and reuse - // the vec's storage instead of using a HashSet self.webtriggers .sort_unstable_by_key(|trigger| trigger.function); - // same rational for using a BTreeSet + let mut ignored_functions: BTreeSet<_> = self .scheduled_triggers .into_iter() @@ -220,6 +291,7 @@ impl<'a> ForgeModules<'a> { ) .collect(); + // make alternate_functions all user-invokable functions let mut alternate_functions: Vec<&str> = Vec::new(); for module in self.extra.into_values().flatten() { alternate_functions.extend(module.function); @@ -228,12 +300,19 @@ impl<'a> ForgeModules<'a> { } } + // Iterate over Consumers and check that if consumers isn't in alternate functions, add consumer funtion to be ignored + // assuming that alternate functions already has all user invokable functions. self.consumers.iter().for_each(|consumer| { if !alternate_functions.contains(&consumer.resolver.function) { ignored_functions.insert(consumer.resolver.function); } }); + // TODO: Iterate through all deserialized entrypoints that are represented as a struct when deserialized + // Update Struct values to be true or not. If any part true, then scan. + // This solution fixes the problem that we only check known user invokable modules and also acccounts for non-invokable module entry points + + // return non-user invokable functions self.functions.into_iter().filter_map(move |func| { if ignored_functions.contains(&func.key) { return None; @@ -400,4 +479,59 @@ mod tests { } ); } + + // Modified specific deserialization schemes for modules. Checking that new schemes can deserialize function values. + #[test] + fn test_new_deserialize() { + + let json = r#"{ + "app": { + "name": "My App", + "id": "my-app" + }, + "modules": { + "macro": [ + { + "key": "my-macro", + "title": "My Macro", + "resolver": { + "function": Catch-me-if-you-can1 + }, + "config": { + "function": Catch-me-if-you-can2 + } + } + ], + "function": [ + { + "key": "my-function", + "handler": "my-function-handler", + "providers": { + "auth": ["my-auth-provider"] + } + } + ], + "webtrigger": [ + { + "key": "my-webtrigger", + "function": "my-webtrigger-handler" + } + ] + }, + "permissions": { + "scopes": [ + "my-scope" + ], + "content": { + "scripts": [ + "my-script.js" + ], + "styles": [ + "my-style.css" + ] + } + } + }"#; + + } } From a585f2e66f3a9ba2a163c81a19b9d914b7d4b0cb Mon Sep 17 00:00:00 2001 From: awang7 Date: Mon, 11 Sep 2023 15:50:57 -0400 Subject: [PATCH 166/517] added more todo statements and cleaned up struct definitions --- crates/forge_loader/src/manifest.rs | 21 +++++++++++++++------ crates/fsrt/src/main.rs | 2 ++ 2 files changed, 17 insertions(+), 6 deletions(-) diff --git a/crates/forge_loader/src/manifest.rs b/crates/forge_loader/src/manifest.rs index 9add8383..55b03475 100644 --- a/crates/forge_loader/src/manifest.rs +++ b/crates/forge_loader/src/manifest.rs @@ -16,7 +16,7 @@ struct AuthProviders<'a> { #[serde(borrow)] auth: Vec<&'a str>, } - +// Maps the Functions Module in common Modules #[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] pub struct FunctionMod<'a> { key: &'a str, @@ -25,24 +25,30 @@ pub struct FunctionMod<'a> { providers: Option>, } +// Modified #[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] struct ModInfo<'a> { - key: &'a str, - title: &'a str, + function: &'a str, } #[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] -struct MacroMod<'a> { - #[serde(flatten, borrow)] - info: ModInfo<'a>, +struct MacroMod<'a> { + #[serde(flatten, borrow)] + key: &'a str, + function: &'a str, + resolver: ModInfo<'a>, + config: ModInfo<'a>, + export: ModInfo<'a>, } +// WebTrigger => RawTrigger; WHY IS THIS NAMED DIFFERENTLY !? WHO CHANGED NAMES #[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] struct RawTrigger<'a> { key: &'a str, function: &'a str, } +// Trigger => EventTriger; WHY IS THIS NAMED DIFFERENTLY !? WHO CHANGED NAMES #[derive(Debug, Clone, PartialEq, Eq, Deserialize)] struct EventTrigger<'a> { #[serde(flatten, borrow)] @@ -59,6 +65,7 @@ enum Interval { Week, } +// Thank you to whomeever kept this one the same. T.T #[derive(Debug, Clone, PartialEq, Eq, Deserialize)] struct ScheduledTrigger<'a> { #[serde(flatten, borrow)] @@ -66,6 +73,7 @@ struct ScheduledTrigger<'a> { interval: Interval, } +// compass DataProvider module #[derive(Debug, Clone, PartialEq, Eq, Deserialize)] struct DataProvider<'a> { key: &'a str, @@ -73,6 +81,7 @@ struct DataProvider<'a> { callback: Callback<'a>, } +// Struct for mapping functions defined one more level in whose value is {function: string}. Used to represent resolver types. #[derive(Debug, Clone, PartialEq, Eq, Deserialize)] pub struct Callback<'a> { pub function: &'a str, diff --git a/crates/fsrt/src/main.rs b/crates/fsrt/src/main.rs index ae8078fd..488d915a 100644 --- a/crates/fsrt/src/main.rs +++ b/crates/fsrt/src/main.rs @@ -208,6 +208,8 @@ fn scan_directory(dir: PathBuf, function: Option<&str>, opts: Opts) -> Result { let mut checker = AuthZChecker::new(); From a02b0ba21c00db045c025cc2c4b1cea5111e5990 Mon Sep 17 00:00:00 2001 From: awang7 Date: Mon, 11 Sep 2023 15:50:57 -0400 Subject: [PATCH 167/517] added more todo statements and cleaned up struct definitions --- crates/forge_loader/src/manifest.rs | 21 +++++++++++++++------ crates/fsrt/src/main.rs | 2 ++ 2 files changed, 17 insertions(+), 6 deletions(-) diff --git a/crates/forge_loader/src/manifest.rs b/crates/forge_loader/src/manifest.rs index 9add8383..55b03475 100644 --- a/crates/forge_loader/src/manifest.rs +++ b/crates/forge_loader/src/manifest.rs @@ -16,7 +16,7 @@ struct AuthProviders<'a> { #[serde(borrow)] auth: Vec<&'a str>, } - +// Maps the Functions Module in common Modules #[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] pub struct FunctionMod<'a> { key: &'a str, @@ -25,24 +25,30 @@ pub struct FunctionMod<'a> { providers: Option>, } +// Modified #[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] struct ModInfo<'a> { - key: &'a str, - title: &'a str, + function: &'a str, } #[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] -struct MacroMod<'a> { - #[serde(flatten, borrow)] - info: ModInfo<'a>, +struct MacroMod<'a> { + #[serde(flatten, borrow)] + key: &'a str, + function: &'a str, + resolver: ModInfo<'a>, + config: ModInfo<'a>, + export: ModInfo<'a>, } +// WebTrigger => RawTrigger; WHY IS THIS NAMED DIFFERENTLY !? WHO CHANGED NAMES #[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] struct RawTrigger<'a> { key: &'a str, function: &'a str, } +// Trigger => EventTriger; WHY IS THIS NAMED DIFFERENTLY !? WHO CHANGED NAMES #[derive(Debug, Clone, PartialEq, Eq, Deserialize)] struct EventTrigger<'a> { #[serde(flatten, borrow)] @@ -59,6 +65,7 @@ enum Interval { Week, } +// Thank you to whomeever kept this one the same. T.T #[derive(Debug, Clone, PartialEq, Eq, Deserialize)] struct ScheduledTrigger<'a> { #[serde(flatten, borrow)] @@ -66,6 +73,7 @@ struct ScheduledTrigger<'a> { interval: Interval, } +// compass DataProvider module #[derive(Debug, Clone, PartialEq, Eq, Deserialize)] struct DataProvider<'a> { key: &'a str, @@ -73,6 +81,7 @@ struct DataProvider<'a> { callback: Callback<'a>, } +// Struct for mapping functions defined one more level in whose value is {function: string}. Used to represent resolver types. #[derive(Debug, Clone, PartialEq, Eq, Deserialize)] pub struct Callback<'a> { pub function: &'a str, diff --git a/crates/fsrt/src/main.rs b/crates/fsrt/src/main.rs index ae8078fd..488d915a 100644 --- a/crates/fsrt/src/main.rs +++ b/crates/fsrt/src/main.rs @@ -208,6 +208,8 @@ fn scan_directory(dir: PathBuf, function: Option<&str>, opts: Opts) -> Result { let mut checker = AuthZChecker::new(); From ca0f6ee96bcb72e8ee524ada7eecba80d932412f Mon Sep 17 00:00:00 2001 From: awang7 Date: Tue, 12 Sep 2023 13:42:48 -0400 Subject: [PATCH 168/517] created iterators of all known user non-invokable functions to be scanned. And updated alternate_functions to track using entrypoints struct. Todo: capture additional entrypoints for user invokable functions(like macros), write tests + debug, and change main.rs to work with new data struct --- crates/forge_loader/src/manifest.rs | 196 ++++++++++++++----- crates/forge_permission_resolver/src/test.rs | 7 + 2 files changed, 157 insertions(+), 46 deletions(-) diff --git a/crates/forge_loader/src/manifest.rs b/crates/forge_loader/src/manifest.rs index 55b03475..06a1829e 100644 --- a/crates/forge_loader/src/manifest.rs +++ b/crates/forge_loader/src/manifest.rs @@ -9,6 +9,7 @@ use crate::Error; use forge_utils::FxHashMap; use itertools::{Either, Itertools}; use serde::Deserialize; +use serde_json::map::Entry; use tracing::trace; #[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] @@ -75,7 +76,7 @@ struct ScheduledTrigger<'a> { // compass DataProvider module #[derive(Debug, Clone, PartialEq, Eq, Deserialize)] -struct DataProvider<'a> { +pub struct DataProvider<'a> { key: &'a str, #[serde(flatten, borrow)] callback: Callback<'a>, @@ -89,7 +90,7 @@ pub struct Callback<'a> { // Struct for Custom field Module. Check that search suggestion gets read in correctly. #[derive(Debug, Clone, PartialEq, Eq, Deserialize)] -struct CustomField<'a> { +pub struct CustomField<'a> { #[serde(flatten, borrow)] key: &'a str, search_suggestion: &'a str, @@ -97,14 +98,14 @@ struct CustomField<'a> { #[derive(Debug, Clone, PartialEq, Eq, Deserialize)] -struct UiModificatons<'a> { +pub struct UiModificatons<'a> { #[serde(flatten, borrow)] key: &'a str, resolver: Callback<'a>, } #[derive(Debug, Clone, PartialEq, Eq, Deserialize)] -struct WorkflowValidator<'a> { +pub struct WorkflowValidator<'a> { #[serde(flatten, borrow)] key: &'a str, functon: &'a str, @@ -112,10 +113,10 @@ struct WorkflowValidator<'a> { } #[derive(Debug, Clone, PartialEq, Eq, Deserialize)] -struct WorkflowPostFunction<'a> { +pub struct WorkflowPostFunction<'a> { #[serde(flatten, borrow)] key: &'a str, - functon: &'a str, + function: &'a str, } // Add more structs here for deserializing forge modules @@ -234,9 +235,9 @@ pub enum FunctionTy { } // Struct used for tracking what scan a funtion requires. -#[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] +#[derive(Debug, Clone, PartialEq, Eq, Deserialize)] pub struct Entrypoints<'a> { - function:Vec>, + function: &'a str, invokable: bool, web_trigger: bool, } @@ -280,64 +281,167 @@ impl AsRef for FunctionTy { impl<'a> ForgeModules<'a> { - +// TODO: fix return type whop pub fn into_analyzable_functions( mut self, - ) -> impl Iterator>> { + ) -> impl Iterator> { // number of webtriggers are usually low, so it's better to just sort them and reuse self.webtriggers .sort_unstable_by_key(|trigger| trigger.function); + // Get all the Triggers and represent them as a new struct thing where "webtrigger" attribute is true + // for all trigger things + let web = self.webtriggers.iter().for_each(|webtriggers| { + Entrypoints { + function: webtriggers.function, + invokable: false, + web_trigger: true, + }; + }); + let event = self.event_triggers.iter().for_each(|event_triggers| { + Entrypoints { + function: event_triggers.raw.function, + invokable: false, + web_trigger: true, + }; + }); + let schedule = self.scheduled_triggers.iter().for_each(|schedule_triggers| { + Entrypoints { + function: schedule_triggers.raw.function, + invokable: false, + web_trigger: true, + }; + }); + + // create arrays representing functions that expose user non-invokable functions + let consumer = self.consumers.iter().for_each(|consumers| { + Entrypoints { + function: consumers.resolver.function, + invokable: true, + web_trigger: false, + }; + }); + + let data_provider = self.data_provider.iter().for_each(|dataprovider| { + Entrypoints { + function: dataprovider.callback.function, + invokable: true, + web_trigger: false, + }; + }); + + let custom_field = self.custom_field.iter().for_each(|customfield| { + Entrypoints { + function: customfield.search_suggestion, + invokable: true, + web_trigger: false, + }; + }); + + let ui_mod = self.ui_modifications.iter().for_each(|ui| { + Entrypoints { + function: ui.resolver.function, + invokable: true, + web_trigger: false, + }; + }); - let mut ignored_functions: BTreeSet<_> = self - .scheduled_triggers - .into_iter() - .map(|trigger| trigger.raw.function) - .chain( - self.event_triggers - .into_iter() - .map(|trigger| trigger.raw.function), - ) - .collect(); + let workflow_validator = self.workflow_validator.iter().for_each(|validator| { + Entrypoints { + function: validator.resolver.function, + invokable: true, + web_trigger: false, + }; + }); + + let workflow_post = self.workflow_post_function.iter().for_each(|post_function| { + Entrypoints { + function: post_function.function, + invokable: true, + web_trigger: false, + }; + }); + + // let user_invokable = self.extra.into_values().flatten().into_iter().for_each(|invokable| { + + // if invokable.resolver != None { + // Entrypoints { + // function: invokable.resolver, + // invokable: true, + // web_trigger: false, + // }; + + // } + // Entrypoints { + // function: invokable.function, + // invokable: true, + // web_trigger: false, + // }; + // }); + + // let mut ignored_functions: BTreeSet<_> = self + // .scheduled_triggers + // .into_iter() + // .map(|trigger| trigger.raw.function) + // .chain( + // self.event_triggers + // .into_iter() + // .map(|trigger| trigger.raw.function), + // ) + // .collect(); + + // get array for user invokable module functions // make alternate_functions all user-invokable functions - let mut alternate_functions: Vec<&str> = Vec::new(); + let mut alternate_functions = Vec::new(); for module in self.extra.into_values().flatten() { - alternate_functions.extend(module.function); + if let Some(mod_function) = module.function { + alternate_functions.push(Entrypoints { + function: mod_function, + invokable: true, + web_trigger: false + }); + } + if let Some(resolver) = module.resolver { - alternate_functions.push(resolver.function); + alternate_functions.push(Entrypoints { + function: resolver.function, + invokable: true, + web_trigger: false + }); } } + workflow_post // Iterate over Consumers and check that if consumers isn't in alternate functions, add consumer funtion to be ignored // assuming that alternate functions already has all user invokable functions. - self.consumers.iter().for_each(|consumer| { - if !alternate_functions.contains(&consumer.resolver.function) { - ignored_functions.insert(consumer.resolver.function); - } - }); + // self.consumers.iter().for_each(|consumer| { + // if !alternate_functions.contains(&consumer.resolver.function) { + // ignored_functions.insert(consumer.resolver.function); + // } + // }); // TODO: Iterate through all deserialized entrypoints that are represented as a struct when deserialized // Update Struct values to be true or not. If any part true, then scan. // This solution fixes the problem that we only check known user invokable modules and also acccounts for non-invokable module entry points // return non-user invokable functions - self.functions.into_iter().filter_map(move |func| { - if ignored_functions.contains(&func.key) { - return None; - } - Some( - if self - .webtriggers - .binary_search_by_key(&func.key, |trigger| trigger.function) - .is_ok() - { - FunctionTy::WebTrigger(func) - } else { - FunctionTy::Invokable(func) - }, - ) - }) + // self.functions.into_iter().filter_map(move |func| { + // if ignored_functions.contains(&func.key) { + // return None; + // } + // Some( + // if self + // .webtriggers + // .binary_search_by_key(&func.key, |trigger| trigger.function) + // .is_ok() + // { + // FunctionTy::WebTrigger(func) + // } else { + // FunctionTy::Invokable(func) + // }, + // ) + // }) } } @@ -453,8 +557,8 @@ mod tests { assert_eq!(manifest.app.name, Some("My App")); assert_eq!(manifest.app.id, "my-app"); assert_eq!(manifest.modules.macros.len(), 1); - assert_eq!(manifest.modules.macros[0].info.title, "My Macro"); - assert_eq!(manifest.modules.macros[0].info.key, "my-macro"); + assert_eq!(manifest.modules.macros[0].key, "My Macro"); + assert_eq!(manifest.modules.macros[0].function, "my-macro"); assert_eq!(manifest.modules.functions.len(), 1); assert_eq!( manifest.modules.functions[0], diff --git a/crates/forge_permission_resolver/src/test.rs b/crates/forge_permission_resolver/src/test.rs index e87e6056..5689fbc5 100644 --- a/crates/forge_permission_resolver/src/test.rs +++ b/crates/forge_permission_resolver/src/test.rs @@ -71,4 +71,11 @@ mod tests { assert_eq!(result, expected_permission); } + + + // TODO: Add a test case using a manifest that has a function exposed through both a non user invocable module and a user invocable module + #[test] + fn test_catch_indirect_func_invoke() { + assert_eq!(0, 0); + } } From 9e69bd4bd3f0c2ed5f557aa2764d0a8e9d34fc0a Mon Sep 17 00:00:00 2001 From: awang7 Date: Tue, 12 Sep 2023 13:42:48 -0400 Subject: [PATCH 169/517] created iterators of all known user non-invokable functions to be scanned. And updated alternate_functions to track using entrypoints struct. Todo: capture additional entrypoints for user invokable functions(like macros), write tests + debug, and change main.rs to work with new data struct --- crates/forge_loader/src/manifest.rs | 196 ++++++++++++++----- crates/forge_permission_resolver/src/test.rs | 7 + 2 files changed, 157 insertions(+), 46 deletions(-) diff --git a/crates/forge_loader/src/manifest.rs b/crates/forge_loader/src/manifest.rs index 55b03475..06a1829e 100644 --- a/crates/forge_loader/src/manifest.rs +++ b/crates/forge_loader/src/manifest.rs @@ -9,6 +9,7 @@ use crate::Error; use forge_utils::FxHashMap; use itertools::{Either, Itertools}; use serde::Deserialize; +use serde_json::map::Entry; use tracing::trace; #[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] @@ -75,7 +76,7 @@ struct ScheduledTrigger<'a> { // compass DataProvider module #[derive(Debug, Clone, PartialEq, Eq, Deserialize)] -struct DataProvider<'a> { +pub struct DataProvider<'a> { key: &'a str, #[serde(flatten, borrow)] callback: Callback<'a>, @@ -89,7 +90,7 @@ pub struct Callback<'a> { // Struct for Custom field Module. Check that search suggestion gets read in correctly. #[derive(Debug, Clone, PartialEq, Eq, Deserialize)] -struct CustomField<'a> { +pub struct CustomField<'a> { #[serde(flatten, borrow)] key: &'a str, search_suggestion: &'a str, @@ -97,14 +98,14 @@ struct CustomField<'a> { #[derive(Debug, Clone, PartialEq, Eq, Deserialize)] -struct UiModificatons<'a> { +pub struct UiModificatons<'a> { #[serde(flatten, borrow)] key: &'a str, resolver: Callback<'a>, } #[derive(Debug, Clone, PartialEq, Eq, Deserialize)] -struct WorkflowValidator<'a> { +pub struct WorkflowValidator<'a> { #[serde(flatten, borrow)] key: &'a str, functon: &'a str, @@ -112,10 +113,10 @@ struct WorkflowValidator<'a> { } #[derive(Debug, Clone, PartialEq, Eq, Deserialize)] -struct WorkflowPostFunction<'a> { +pub struct WorkflowPostFunction<'a> { #[serde(flatten, borrow)] key: &'a str, - functon: &'a str, + function: &'a str, } // Add more structs here for deserializing forge modules @@ -234,9 +235,9 @@ pub enum FunctionTy { } // Struct used for tracking what scan a funtion requires. -#[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] +#[derive(Debug, Clone, PartialEq, Eq, Deserialize)] pub struct Entrypoints<'a> { - function:Vec>, + function: &'a str, invokable: bool, web_trigger: bool, } @@ -280,64 +281,167 @@ impl AsRef for FunctionTy { impl<'a> ForgeModules<'a> { - +// TODO: fix return type whop pub fn into_analyzable_functions( mut self, - ) -> impl Iterator>> { + ) -> impl Iterator> { // number of webtriggers are usually low, so it's better to just sort them and reuse self.webtriggers .sort_unstable_by_key(|trigger| trigger.function); + // Get all the Triggers and represent them as a new struct thing where "webtrigger" attribute is true + // for all trigger things + let web = self.webtriggers.iter().for_each(|webtriggers| { + Entrypoints { + function: webtriggers.function, + invokable: false, + web_trigger: true, + }; + }); + let event = self.event_triggers.iter().for_each(|event_triggers| { + Entrypoints { + function: event_triggers.raw.function, + invokable: false, + web_trigger: true, + }; + }); + let schedule = self.scheduled_triggers.iter().for_each(|schedule_triggers| { + Entrypoints { + function: schedule_triggers.raw.function, + invokable: false, + web_trigger: true, + }; + }); + + // create arrays representing functions that expose user non-invokable functions + let consumer = self.consumers.iter().for_each(|consumers| { + Entrypoints { + function: consumers.resolver.function, + invokable: true, + web_trigger: false, + }; + }); + + let data_provider = self.data_provider.iter().for_each(|dataprovider| { + Entrypoints { + function: dataprovider.callback.function, + invokable: true, + web_trigger: false, + }; + }); + + let custom_field = self.custom_field.iter().for_each(|customfield| { + Entrypoints { + function: customfield.search_suggestion, + invokable: true, + web_trigger: false, + }; + }); + + let ui_mod = self.ui_modifications.iter().for_each(|ui| { + Entrypoints { + function: ui.resolver.function, + invokable: true, + web_trigger: false, + }; + }); - let mut ignored_functions: BTreeSet<_> = self - .scheduled_triggers - .into_iter() - .map(|trigger| trigger.raw.function) - .chain( - self.event_triggers - .into_iter() - .map(|trigger| trigger.raw.function), - ) - .collect(); + let workflow_validator = self.workflow_validator.iter().for_each(|validator| { + Entrypoints { + function: validator.resolver.function, + invokable: true, + web_trigger: false, + }; + }); + + let workflow_post = self.workflow_post_function.iter().for_each(|post_function| { + Entrypoints { + function: post_function.function, + invokable: true, + web_trigger: false, + }; + }); + + // let user_invokable = self.extra.into_values().flatten().into_iter().for_each(|invokable| { + + // if invokable.resolver != None { + // Entrypoints { + // function: invokable.resolver, + // invokable: true, + // web_trigger: false, + // }; + + // } + // Entrypoints { + // function: invokable.function, + // invokable: true, + // web_trigger: false, + // }; + // }); + + // let mut ignored_functions: BTreeSet<_> = self + // .scheduled_triggers + // .into_iter() + // .map(|trigger| trigger.raw.function) + // .chain( + // self.event_triggers + // .into_iter() + // .map(|trigger| trigger.raw.function), + // ) + // .collect(); + + // get array for user invokable module functions // make alternate_functions all user-invokable functions - let mut alternate_functions: Vec<&str> = Vec::new(); + let mut alternate_functions = Vec::new(); for module in self.extra.into_values().flatten() { - alternate_functions.extend(module.function); + if let Some(mod_function) = module.function { + alternate_functions.push(Entrypoints { + function: mod_function, + invokable: true, + web_trigger: false + }); + } + if let Some(resolver) = module.resolver { - alternate_functions.push(resolver.function); + alternate_functions.push(Entrypoints { + function: resolver.function, + invokable: true, + web_trigger: false + }); } } + workflow_post // Iterate over Consumers and check that if consumers isn't in alternate functions, add consumer funtion to be ignored // assuming that alternate functions already has all user invokable functions. - self.consumers.iter().for_each(|consumer| { - if !alternate_functions.contains(&consumer.resolver.function) { - ignored_functions.insert(consumer.resolver.function); - } - }); + // self.consumers.iter().for_each(|consumer| { + // if !alternate_functions.contains(&consumer.resolver.function) { + // ignored_functions.insert(consumer.resolver.function); + // } + // }); // TODO: Iterate through all deserialized entrypoints that are represented as a struct when deserialized // Update Struct values to be true or not. If any part true, then scan. // This solution fixes the problem that we only check known user invokable modules and also acccounts for non-invokable module entry points // return non-user invokable functions - self.functions.into_iter().filter_map(move |func| { - if ignored_functions.contains(&func.key) { - return None; - } - Some( - if self - .webtriggers - .binary_search_by_key(&func.key, |trigger| trigger.function) - .is_ok() - { - FunctionTy::WebTrigger(func) - } else { - FunctionTy::Invokable(func) - }, - ) - }) + // self.functions.into_iter().filter_map(move |func| { + // if ignored_functions.contains(&func.key) { + // return None; + // } + // Some( + // if self + // .webtriggers + // .binary_search_by_key(&func.key, |trigger| trigger.function) + // .is_ok() + // { + // FunctionTy::WebTrigger(func) + // } else { + // FunctionTy::Invokable(func) + // }, + // ) + // }) } } @@ -453,8 +557,8 @@ mod tests { assert_eq!(manifest.app.name, Some("My App")); assert_eq!(manifest.app.id, "my-app"); assert_eq!(manifest.modules.macros.len(), 1); - assert_eq!(manifest.modules.macros[0].info.title, "My Macro"); - assert_eq!(manifest.modules.macros[0].info.key, "my-macro"); + assert_eq!(manifest.modules.macros[0].key, "My Macro"); + assert_eq!(manifest.modules.macros[0].function, "my-macro"); assert_eq!(manifest.modules.functions.len(), 1); assert_eq!( manifest.modules.functions[0], diff --git a/crates/forge_permission_resolver/src/test.rs b/crates/forge_permission_resolver/src/test.rs index e87e6056..5689fbc5 100644 --- a/crates/forge_permission_resolver/src/test.rs +++ b/crates/forge_permission_resolver/src/test.rs @@ -71,4 +71,11 @@ mod tests { assert_eq!(result, expected_permission); } + + + // TODO: Add a test case using a manifest that has a function exposed through both a non user invocable module and a user invocable module + #[test] + fn test_catch_indirect_func_invoke() { + assert_eq!(0, 0); + } } From a169dc3b764a91da59827e705b4bdaa418540a3a Mon Sep 17 00:00:00 2001 From: awang7 Date: Wed, 13 Sep 2023 17:22:16 -0400 Subject: [PATCH 170/517] fixed all the red squigglies --- crates/forge_loader/src/manifest.rs | 149 ++++++++++++++++------------ 1 file changed, 85 insertions(+), 64 deletions(-) diff --git a/crates/forge_loader/src/manifest.rs b/crates/forge_loader/src/manifest.rs index 06a1829e..beb21e6e 100644 --- a/crates/forge_loader/src/manifest.rs +++ b/crates/forge_loader/src/manifest.rs @@ -281,85 +281,106 @@ impl AsRef for FunctionTy { impl<'a> ForgeModules<'a> { -// TODO: fix return type whop +// TODO: function returns iterator where each item is some specified type. pub fn into_analyzable_functions( mut self, - ) -> impl Iterator> { + ) { // number of webtriggers are usually low, so it's better to just sort them and reuse - self.webtriggers - .sort_unstable_by_key(|trigger| trigger.function); + // self.webtriggers + // .sort_unstable_by_key(|trigger| trigger.function); // Get all the Triggers and represent them as a new struct thing where "webtrigger" attribute is true // for all trigger things - let web = self.webtriggers.iter().for_each(|webtriggers| { - Entrypoints { - function: webtriggers.function, - invokable: false, - web_trigger: true, - }; + + let mut functions_to_scan = Vec::new(); + self.webtriggers.iter().for_each(|webtriggers| { + functions_to_scan.push( + Entrypoints { + function: webtriggers.function, + invokable: false, + web_trigger: true, + } + ); + }); - let event = self.event_triggers.iter().for_each(|event_triggers| { - Entrypoints { - function: event_triggers.raw.function, - invokable: false, - web_trigger: true, - }; + self.event_triggers.iter().for_each(|event_triggers| { + functions_to_scan.push( + Entrypoints { + function: event_triggers.raw.function, + invokable: false, + web_trigger: true, + } + ); }); - let schedule = self.scheduled_triggers.iter().for_each(|schedule_triggers| { - Entrypoints { - function: schedule_triggers.raw.function, - invokable: false, - web_trigger: true, - }; + self.scheduled_triggers.iter().for_each(|schedule_triggers| { + functions_to_scan.push( + Entrypoints { + function: schedule_triggers.raw.function, + invokable: false, + web_trigger: true, + } + ) }); // create arrays representing functions that expose user non-invokable functions - let consumer = self.consumers.iter().for_each(|consumers| { - Entrypoints { - function: consumers.resolver.function, - invokable: true, - web_trigger: false, - }; + self.consumers.iter().for_each(|consumers| { + functions_to_scan.push( + Entrypoints { + function: consumers.resolver.function, + invokable: true, + web_trigger: false, + } + ) }); - let data_provider = self.data_provider.iter().for_each(|dataprovider| { - Entrypoints { - function: dataprovider.callback.function, - invokable: true, - web_trigger: false, - }; + self.data_provider.iter().for_each(|dataprovider| { + functions_to_scan.push( + Entrypoints { + function: dataprovider.callback.function, + invokable: true, + web_trigger: false, + } + ) }); - let custom_field = self.custom_field.iter().for_each(|customfield| { - Entrypoints { - function: customfield.search_suggestion, - invokable: true, - web_trigger: false, - }; + self.custom_field.iter().for_each(|customfield| { + functions_to_scan.push( + Entrypoints { + function: customfield.search_suggestion, + invokable: true, + web_trigger: false, + } + ) }); - let ui_mod = self.ui_modifications.iter().for_each(|ui| { - Entrypoints { - function: ui.resolver.function, - invokable: true, - web_trigger: false, - }; + self.ui_modifications.iter().for_each(|ui| { + functions_to_scan.push( + Entrypoints { + function: ui.resolver.function, + invokable: true, + web_trigger: false, + } + ) }); - let workflow_validator = self.workflow_validator.iter().for_each(|validator| { - Entrypoints { - function: validator.resolver.function, - invokable: true, - web_trigger: false, - }; + self.workflow_validator.iter().for_each(|validator| { + functions_to_scan.push( + Entrypoints { + function: validator.resolver.function, + invokable: true, + web_trigger: false, + } + ) }); - - let workflow_post = self.workflow_post_function.iter().for_each(|post_function| { - Entrypoints { - function: post_function.function, - invokable: true, - web_trigger: false, - }; + + self.workflow_post_function.iter().for_each(|post_function| { + functions_to_scan.push( + Entrypoints { + function: post_function.function, + invokable: true, + web_trigger: false, + } + ) }); @@ -393,10 +414,10 @@ impl<'a> ForgeModules<'a> { // get array for user invokable module functions // make alternate_functions all user-invokable functions - let mut alternate_functions = Vec::new(); + // let mut alternate_functions = Vec::new(); for module in self.extra.into_values().flatten() { if let Some(mod_function) = module.function { - alternate_functions.push(Entrypoints { + functions_to_scan.push(Entrypoints { function: mod_function, invokable: true, web_trigger: false @@ -404,15 +425,15 @@ impl<'a> ForgeModules<'a> { } if let Some(resolver) = module.resolver { - alternate_functions.push(Entrypoints { + functions_to_scan.push(Entrypoints { function: resolver.function, invokable: true, web_trigger: false }); } } - - workflow_post + functions_to_scan.into_iter(); + // alternate_functions.into_iter(); // Iterate over Consumers and check that if consumers isn't in alternate functions, add consumer funtion to be ignored // assuming that alternate functions already has all user invokable functions. // self.consumers.iter().for_each(|consumer| { From cbb7a5bfcf0c29ec6e6d9474c1a660a502e894f2 Mon Sep 17 00:00:00 2001 From: awang7 Date: Wed, 13 Sep 2023 17:22:16 -0400 Subject: [PATCH 171/517] fixed all the red squigglies --- crates/forge_loader/src/manifest.rs | 149 ++++++++++++++++------------ 1 file changed, 85 insertions(+), 64 deletions(-) diff --git a/crates/forge_loader/src/manifest.rs b/crates/forge_loader/src/manifest.rs index 06a1829e..beb21e6e 100644 --- a/crates/forge_loader/src/manifest.rs +++ b/crates/forge_loader/src/manifest.rs @@ -281,85 +281,106 @@ impl AsRef for FunctionTy { impl<'a> ForgeModules<'a> { -// TODO: fix return type whop +// TODO: function returns iterator where each item is some specified type. pub fn into_analyzable_functions( mut self, - ) -> impl Iterator> { + ) { // number of webtriggers are usually low, so it's better to just sort them and reuse - self.webtriggers - .sort_unstable_by_key(|trigger| trigger.function); + // self.webtriggers + // .sort_unstable_by_key(|trigger| trigger.function); // Get all the Triggers and represent them as a new struct thing where "webtrigger" attribute is true // for all trigger things - let web = self.webtriggers.iter().for_each(|webtriggers| { - Entrypoints { - function: webtriggers.function, - invokable: false, - web_trigger: true, - }; + + let mut functions_to_scan = Vec::new(); + self.webtriggers.iter().for_each(|webtriggers| { + functions_to_scan.push( + Entrypoints { + function: webtriggers.function, + invokable: false, + web_trigger: true, + } + ); + }); - let event = self.event_triggers.iter().for_each(|event_triggers| { - Entrypoints { - function: event_triggers.raw.function, - invokable: false, - web_trigger: true, - }; + self.event_triggers.iter().for_each(|event_triggers| { + functions_to_scan.push( + Entrypoints { + function: event_triggers.raw.function, + invokable: false, + web_trigger: true, + } + ); }); - let schedule = self.scheduled_triggers.iter().for_each(|schedule_triggers| { - Entrypoints { - function: schedule_triggers.raw.function, - invokable: false, - web_trigger: true, - }; + self.scheduled_triggers.iter().for_each(|schedule_triggers| { + functions_to_scan.push( + Entrypoints { + function: schedule_triggers.raw.function, + invokable: false, + web_trigger: true, + } + ) }); // create arrays representing functions that expose user non-invokable functions - let consumer = self.consumers.iter().for_each(|consumers| { - Entrypoints { - function: consumers.resolver.function, - invokable: true, - web_trigger: false, - }; + self.consumers.iter().for_each(|consumers| { + functions_to_scan.push( + Entrypoints { + function: consumers.resolver.function, + invokable: true, + web_trigger: false, + } + ) }); - let data_provider = self.data_provider.iter().for_each(|dataprovider| { - Entrypoints { - function: dataprovider.callback.function, - invokable: true, - web_trigger: false, - }; + self.data_provider.iter().for_each(|dataprovider| { + functions_to_scan.push( + Entrypoints { + function: dataprovider.callback.function, + invokable: true, + web_trigger: false, + } + ) }); - let custom_field = self.custom_field.iter().for_each(|customfield| { - Entrypoints { - function: customfield.search_suggestion, - invokable: true, - web_trigger: false, - }; + self.custom_field.iter().for_each(|customfield| { + functions_to_scan.push( + Entrypoints { + function: customfield.search_suggestion, + invokable: true, + web_trigger: false, + } + ) }); - let ui_mod = self.ui_modifications.iter().for_each(|ui| { - Entrypoints { - function: ui.resolver.function, - invokable: true, - web_trigger: false, - }; + self.ui_modifications.iter().for_each(|ui| { + functions_to_scan.push( + Entrypoints { + function: ui.resolver.function, + invokable: true, + web_trigger: false, + } + ) }); - let workflow_validator = self.workflow_validator.iter().for_each(|validator| { - Entrypoints { - function: validator.resolver.function, - invokable: true, - web_trigger: false, - }; + self.workflow_validator.iter().for_each(|validator| { + functions_to_scan.push( + Entrypoints { + function: validator.resolver.function, + invokable: true, + web_trigger: false, + } + ) }); - - let workflow_post = self.workflow_post_function.iter().for_each(|post_function| { - Entrypoints { - function: post_function.function, - invokable: true, - web_trigger: false, - }; + + self.workflow_post_function.iter().for_each(|post_function| { + functions_to_scan.push( + Entrypoints { + function: post_function.function, + invokable: true, + web_trigger: false, + } + ) }); @@ -393,10 +414,10 @@ impl<'a> ForgeModules<'a> { // get array for user invokable module functions // make alternate_functions all user-invokable functions - let mut alternate_functions = Vec::new(); + // let mut alternate_functions = Vec::new(); for module in self.extra.into_values().flatten() { if let Some(mod_function) = module.function { - alternate_functions.push(Entrypoints { + functions_to_scan.push(Entrypoints { function: mod_function, invokable: true, web_trigger: false @@ -404,15 +425,15 @@ impl<'a> ForgeModules<'a> { } if let Some(resolver) = module.resolver { - alternate_functions.push(Entrypoints { + functions_to_scan.push(Entrypoints { function: resolver.function, invokable: true, web_trigger: false }); } } - - workflow_post + functions_to_scan.into_iter(); + // alternate_functions.into_iter(); // Iterate over Consumers and check that if consumers isn't in alternate functions, add consumer funtion to be ignored // assuming that alternate functions already has all user invokable functions. // self.consumers.iter().for_each(|consumer| { From b4455a15d46d14ae1db77daf1c396d6121c8c3a2 Mon Sep 17 00:00:00 2001 From: awang7 Date: Thu, 14 Sep 2023 16:23:52 -0400 Subject: [PATCH 172/517] added user-invokable module with additional entry points for macros. Adding more modules. Writing tests next? --- crates/forge_loader/src/manifest.rs | 56 ++++++++++++++--------------- 1 file changed, 28 insertions(+), 28 deletions(-) diff --git a/crates/forge_loader/src/manifest.rs b/crates/forge_loader/src/manifest.rs index beb21e6e..ef7277da 100644 --- a/crates/forge_loader/src/manifest.rs +++ b/crates/forge_loader/src/manifest.rs @@ -283,7 +283,7 @@ impl<'a> ForgeModules<'a> { // TODO: function returns iterator where each item is some specified type. pub fn into_analyzable_functions( - mut self, + self, ) { // number of webtriggers are usually low, so it's better to just sort them and reuse // self.webtriggers @@ -383,35 +383,35 @@ impl<'a> ForgeModules<'a> { ) }); - - // let user_invokable = self.extra.into_values().flatten().into_iter().for_each(|invokable| { + // get user invokable modules that have additional exposure endpoints. + // ie macros has config and export fields on top of resolver fields that are functions + for macros in self.macros { + if let Some(resolver)= Some(macros.resolver) { + functions_to_scan.push(Entrypoints { + function: resolver.function, + invokable: true, + web_trigger: false + }) + } - // if invokable.resolver != None { - // Entrypoints { - // function: invokable.resolver, - // invokable: true, - // web_trigger: false, - // }; + if let Some(config)= Some(macros.config) { + functions_to_scan.push(Entrypoints { + function: config.function, + invokable: true, + web_trigger: false + }) + } + if let Some(export)= Some(macros.export) { + functions_to_scan.push(Entrypoints { + function: export.function, + invokable: true, + web_trigger: false + }) + } - // } - // Entrypoints { - // function: invokable.function, - // invokable: true, - // web_trigger: false, - // }; - // }); - - // let mut ignored_functions: BTreeSet<_> = self - // .scheduled_triggers - // .into_iter() - // .map(|trigger| trigger.raw.function) - // .chain( - // self.event_triggers - // .into_iter() - // .map(|trigger| trigger.raw.function), - // ) - // .collect(); + } + // get array for user invokable module functions // make alternate_functions all user-invokable functions // let mut alternate_functions = Vec::new(); @@ -430,7 +430,7 @@ impl<'a> ForgeModules<'a> { invokable: true, web_trigger: false }); - } + } } functions_to_scan.into_iter(); // alternate_functions.into_iter(); From 22b27b9e314f938e65dc15ff745eeb955db65439 Mon Sep 17 00:00:00 2001 From: awang7 Date: Thu, 14 Sep 2023 16:23:52 -0400 Subject: [PATCH 173/517] added user-invokable module with additional entry points for macros. Adding more modules. Writing tests next? --- crates/forge_loader/src/manifest.rs | 56 ++++++++++++++--------------- 1 file changed, 28 insertions(+), 28 deletions(-) diff --git a/crates/forge_loader/src/manifest.rs b/crates/forge_loader/src/manifest.rs index beb21e6e..ef7277da 100644 --- a/crates/forge_loader/src/manifest.rs +++ b/crates/forge_loader/src/manifest.rs @@ -283,7 +283,7 @@ impl<'a> ForgeModules<'a> { // TODO: function returns iterator where each item is some specified type. pub fn into_analyzable_functions( - mut self, + self, ) { // number of webtriggers are usually low, so it's better to just sort them and reuse // self.webtriggers @@ -383,35 +383,35 @@ impl<'a> ForgeModules<'a> { ) }); - - // let user_invokable = self.extra.into_values().flatten().into_iter().for_each(|invokable| { + // get user invokable modules that have additional exposure endpoints. + // ie macros has config and export fields on top of resolver fields that are functions + for macros in self.macros { + if let Some(resolver)= Some(macros.resolver) { + functions_to_scan.push(Entrypoints { + function: resolver.function, + invokable: true, + web_trigger: false + }) + } - // if invokable.resolver != None { - // Entrypoints { - // function: invokable.resolver, - // invokable: true, - // web_trigger: false, - // }; + if let Some(config)= Some(macros.config) { + functions_to_scan.push(Entrypoints { + function: config.function, + invokable: true, + web_trigger: false + }) + } + if let Some(export)= Some(macros.export) { + functions_to_scan.push(Entrypoints { + function: export.function, + invokable: true, + web_trigger: false + }) + } - // } - // Entrypoints { - // function: invokable.function, - // invokable: true, - // web_trigger: false, - // }; - // }); - - // let mut ignored_functions: BTreeSet<_> = self - // .scheduled_triggers - // .into_iter() - // .map(|trigger| trigger.raw.function) - // .chain( - // self.event_triggers - // .into_iter() - // .map(|trigger| trigger.raw.function), - // ) - // .collect(); + } + // get array for user invokable module functions // make alternate_functions all user-invokable functions // let mut alternate_functions = Vec::new(); @@ -430,7 +430,7 @@ impl<'a> ForgeModules<'a> { invokable: true, web_trigger: false }); - } + } } functions_to_scan.into_iter(); // alternate_functions.into_iter(); From d38aa7a8aa1a12b60ee7cec4e52917284b50f515 Mon Sep 17 00:00:00 2001 From: awang7 Date: Fri, 15 Sep 2023 13:37:48 -0400 Subject: [PATCH 174/517] added test to deserialize macros with additional entry points like config and export --- crates/forge_loader/src/manifest.rs | 45 ++++++++++++++++++++++------- 1 file changed, 35 insertions(+), 10 deletions(-) diff --git a/crates/forge_loader/src/manifest.rs b/crates/forge_loader/src/manifest.rs index ef7277da..16e1687a 100644 --- a/crates/forge_loader/src/manifest.rs +++ b/crates/forge_loader/src/manifest.rs @@ -34,12 +34,15 @@ struct ModInfo<'a> { #[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] struct MacroMod<'a> { - #[serde(flatten, borrow)] + // #[serde(flatten, borrow)] key: &'a str, function: &'a str, - resolver: ModInfo<'a>, - config: ModInfo<'a>, - export: ModInfo<'a>, + #[serde(borrow)] + resolver: Option>, + #[serde(borrow)] + config: Option>, + #[serde(borrow)] + export: Option>, } // WebTrigger => RawTrigger; WHY IS THIS NAMED DIFFERENTLY !? WHO CHANGED NAMES @@ -386,7 +389,7 @@ impl<'a> ForgeModules<'a> { // get user invokable modules that have additional exposure endpoints. // ie macros has config and export fields on top of resolver fields that are functions for macros in self.macros { - if let Some(resolver)= Some(macros.resolver) { + if let Some(resolver)= macros.resolver { functions_to_scan.push(Entrypoints { function: resolver.function, invokable: true, @@ -394,14 +397,14 @@ impl<'a> ForgeModules<'a> { }) } - if let Some(config)= Some(macros.config) { + if let Some(config)= macros.config { functions_to_scan.push(Entrypoints { function: config.function, invokable: true, web_trigger: false }) } - if let Some(export)= Some(macros.export) { + if let Some(export)= macros.export { functions_to_scan.push(Entrypoints { function: export.function, invokable: true, @@ -541,7 +544,7 @@ mod tests { "macro": [ { "key": "my-macro", - "title": "My Macro" + "function": "My Macro" } ], "function": [ @@ -628,11 +631,15 @@ mod tests { { "key": "my-macro", "title": "My Macro", + "function": "Catch-me-if-you-can0", "resolver": { - "function": Catch-me-if-you-can1 + "function": "Catch-me-if-you-can1" }, "config": { - "function": Catch-me-if-you-can2 + "function": "Catch-me-if-you-can2" + }, + "export": { + "function": "Catch-me-if-you-can3" } } ], @@ -666,6 +673,24 @@ mod tests { } } }"#; + let manifest: ForgeManifest = serde_json::from_str(json).unwrap(); + assert_eq!(manifest.modules.macros.len(), 1); + assert_eq!(manifest.modules.macros[0].function, "Catch-me-if-you-can0"); + + if let Some(func) = &manifest.modules.macros[0].resolver { + assert_eq!(func.function, "Catch-me-if-you-can1"); + + } + + if let Some(func) = &manifest.modules.macros[0].config { + assert_eq!(func.function, "Catch-me-if-you-can2"); + + } + + if let Some(func) = &manifest.modules.macros[0].export { + assert_eq!(func.function, "Catch-me-if-you-can3"); + + } } } From 760dd8a8e3f0a7bfc97f1ce975cd5a8b53dbb228 Mon Sep 17 00:00:00 2001 From: awang7 Date: Fri, 15 Sep 2023 13:37:48 -0400 Subject: [PATCH 175/517] added test to deserialize macros with additional entry points like config and export --- crates/forge_loader/src/manifest.rs | 45 ++++++++++++++++++++++------- 1 file changed, 35 insertions(+), 10 deletions(-) diff --git a/crates/forge_loader/src/manifest.rs b/crates/forge_loader/src/manifest.rs index ef7277da..16e1687a 100644 --- a/crates/forge_loader/src/manifest.rs +++ b/crates/forge_loader/src/manifest.rs @@ -34,12 +34,15 @@ struct ModInfo<'a> { #[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] struct MacroMod<'a> { - #[serde(flatten, borrow)] + // #[serde(flatten, borrow)] key: &'a str, function: &'a str, - resolver: ModInfo<'a>, - config: ModInfo<'a>, - export: ModInfo<'a>, + #[serde(borrow)] + resolver: Option>, + #[serde(borrow)] + config: Option>, + #[serde(borrow)] + export: Option>, } // WebTrigger => RawTrigger; WHY IS THIS NAMED DIFFERENTLY !? WHO CHANGED NAMES @@ -386,7 +389,7 @@ impl<'a> ForgeModules<'a> { // get user invokable modules that have additional exposure endpoints. // ie macros has config and export fields on top of resolver fields that are functions for macros in self.macros { - if let Some(resolver)= Some(macros.resolver) { + if let Some(resolver)= macros.resolver { functions_to_scan.push(Entrypoints { function: resolver.function, invokable: true, @@ -394,14 +397,14 @@ impl<'a> ForgeModules<'a> { }) } - if let Some(config)= Some(macros.config) { + if let Some(config)= macros.config { functions_to_scan.push(Entrypoints { function: config.function, invokable: true, web_trigger: false }) } - if let Some(export)= Some(macros.export) { + if let Some(export)= macros.export { functions_to_scan.push(Entrypoints { function: export.function, invokable: true, @@ -541,7 +544,7 @@ mod tests { "macro": [ { "key": "my-macro", - "title": "My Macro" + "function": "My Macro" } ], "function": [ @@ -628,11 +631,15 @@ mod tests { { "key": "my-macro", "title": "My Macro", + "function": "Catch-me-if-you-can0", "resolver": { - "function": Catch-me-if-you-can1 + "function": "Catch-me-if-you-can1" }, "config": { - "function": Catch-me-if-you-can2 + "function": "Catch-me-if-you-can2" + }, + "export": { + "function": "Catch-me-if-you-can3" } } ], @@ -666,6 +673,24 @@ mod tests { } } }"#; + let manifest: ForgeManifest = serde_json::from_str(json).unwrap(); + assert_eq!(manifest.modules.macros.len(), 1); + assert_eq!(manifest.modules.macros[0].function, "Catch-me-if-you-can0"); + + if let Some(func) = &manifest.modules.macros[0].resolver { + assert_eq!(func.function, "Catch-me-if-you-can1"); + + } + + if let Some(func) = &manifest.modules.macros[0].config { + assert_eq!(func.function, "Catch-me-if-you-can2"); + + } + + if let Some(func) = &manifest.modules.macros[0].export { + assert_eq!(func.function, "Catch-me-if-you-can3"); + + } } } From cd2ed7bb11a5aff5a4220446d5dd7bd6f66be872 Mon Sep 17 00:00:00 2001 From: awang7 Date: Fri, 15 Sep 2023 17:20:10 -0400 Subject: [PATCH 176/517] edited main to iterate over vector of functions. TODO: edit add_funcs in main.rs and test into_analyzble_function use case in main.rs --- crates/forge_loader/src/manifest.rs | 45 +++------------- crates/fsrt/src/main.rs | 81 +++++++++++++++++++---------- 2 files changed, 60 insertions(+), 66 deletions(-) diff --git a/crates/forge_loader/src/manifest.rs b/crates/forge_loader/src/manifest.rs index 16e1687a..c74fa421 100644 --- a/crates/forge_loader/src/manifest.rs +++ b/crates/forge_loader/src/manifest.rs @@ -240,9 +240,9 @@ pub enum FunctionTy { // Struct used for tracking what scan a funtion requires. #[derive(Debug, Clone, PartialEq, Eq, Deserialize)] pub struct Entrypoints<'a> { - function: &'a str, - invokable: bool, - web_trigger: bool, + pub function: &'a str, + pub invokable: bool, + pub web_trigger: bool, } // Helper functions that help filter out which functions are what. @@ -285,9 +285,9 @@ impl AsRef for FunctionTy { impl<'a> ForgeModules<'a> { // TODO: function returns iterator where each item is some specified type. - pub fn into_analyzable_functions( + pub fn into_analyzable_functions ( self, - ) { + ) -> Vec>{ // number of webtriggers are usually low, so it's better to just sort them and reuse // self.webtriggers // .sort_unstable_by_key(|trigger| trigger.function); @@ -413,11 +413,9 @@ impl<'a> ForgeModules<'a> { } } - // get array for user invokable module functions // make alternate_functions all user-invokable functions - // let mut alternate_functions = Vec::new(); for module in self.extra.into_values().flatten() { if let Some(mod_function) = module.function { functions_to_scan.push(Entrypoints { @@ -435,37 +433,8 @@ impl<'a> ForgeModules<'a> { }); } } - functions_to_scan.into_iter(); - // alternate_functions.into_iter(); - // Iterate over Consumers and check that if consumers isn't in alternate functions, add consumer funtion to be ignored - // assuming that alternate functions already has all user invokable functions. - // self.consumers.iter().for_each(|consumer| { - // if !alternate_functions.contains(&consumer.resolver.function) { - // ignored_functions.insert(consumer.resolver.function); - // } - // }); - - // TODO: Iterate through all deserialized entrypoints that are represented as a struct when deserialized - // Update Struct values to be true or not. If any part true, then scan. - // This solution fixes the problem that we only check known user invokable modules and also acccounts for non-invokable module entry points - - // return non-user invokable functions - // self.functions.into_iter().filter_map(move |func| { - // if ignored_functions.contains(&func.key) { - // return None; - // } - // Some( - // if self - // .webtriggers - // .binary_search_by_key(&func.key, |trigger| trigger.function) - // .is_ok() - // { - // FunctionTy::WebTrigger(func) - // } else { - // FunctionTy::Invokable(func) - // }, - // ) - // }) + + return functions_to_scan; } } diff --git a/crates/fsrt/src/main.rs b/crates/fsrt/src/main.rs index 488d915a..d2729304 100644 --- a/crates/fsrt/src/main.rs +++ b/crates/fsrt/src/main.rs @@ -12,6 +12,7 @@ use std::{ use clap::{Parser, ValueHint}; use miette::{IntoDiagnostic, Result}; +use serde_json::map::Entry; use swc_core::{ common::{Globals, Mark, SourceMap, GLOBALS}, ecma::{ @@ -34,7 +35,7 @@ use forge_analyzer::{ resolver::resolve_calls, }; -use forge_loader::manifest::{ForgeManifest, FunctionRef, FunctionTy}; +use forge_loader::manifest::{ForgeManifest, FunctionRef, FunctionTy, Entrypoints}; use walkdir::WalkDir; #[derive(Parser, Debug)] @@ -82,7 +83,7 @@ struct ForgeProject { sm: Arc, ctx: AppCtx, env: Environment, - funcs: Vec>, + funcs: Vec>, opts: Opts, } @@ -133,8 +134,8 @@ impl ForgeProject { opts: Opts::default(), } } - - fn add_funcs<'a, I: IntoIterator>>(&mut self, iter: I) { +// TODO: edit to work with new iterator that not FUNCTIONTY + fn add_funcs<'a, I: IntoIterator>>(&mut self, iter: I) { self.funcs.extend(iter.into_iter().flat_map(|ftype| { ftype.sequence(|(func_name, path)| { let modid = self.ctx.modid_from_path(&path)?; @@ -187,12 +188,14 @@ fn scan_directory(dir: PathBuf, function: Option<&str>, opts: Opts) -> Result(resolved_func.into_func_path()) - }) - }); + let funcrefs = &manifest.modules.into_analyzable_functions(); + + // .flat_map(|f| { + // f.sequence(|fmod| { + // let resolved_func = FunctionRef::try_from(fmod)?.try_resolve(&paths, &dir)?; + // Ok::<_, forge_loader::Error>(resolved_func.into_func_path()) + // }) + // }); let src_root = dir.join("src"); let mut proj = ForgeProject::with_files_and_sourceroot(src_root, paths.clone()); if transpiled_async { @@ -210,26 +213,48 @@ fn scan_directory(dir: PathBuf, function: Option<&str>, opts: Opts) -> Result { - let mut checker = AuthZChecker::new(); - debug!("checking {func} at {path:?}"); - if let Err(err) = interp.run_checker(def, &mut checker, path.clone(), func.clone()) - { - warn!("error while scanning {func} in {path:?}: {err}"); - } - reporter.add_vulnerabilities(checker.into_vulns()); + // match *func { + // FunctionTy::Invokable((ref func, ref path, _, def)) => { + // let mut checker = AuthZChecker::new(); + // debug!("checking {func} at {path:?}"); + // if let Err(err) = interp.run_checker(def, &mut checker, path.clone(), func.clone()) + // { + // warn!("error while scanning {func} in {path:?}: {err}"); + // } + // reporter.add_vulnerabilities(checker.into_vulns()); + // } + // FunctionTy::WebTrigger((ref func, ref path, _, def)) => { + // let mut checker = AuthenticateChecker::new(); + // debug!("checking webtrigger {func} at {path:?}"); + // if let Err(err) = + // authn_interp.run_checker(def, &mut checker, path.clone(), func.clone()) + // { + // warn!("error while scanning {func} in {path:?}: {err}"); + // } + // reporter.add_vulnerabilities(checker.into_vulns()); + // } + // } + + if func.invokable { + let mut checker = AuthZChecker::new(); + debug!("checking {func} at {path:?}"); + if let Err(err) = interp.run_checker(def, &mut checker, path.clone(), func.clone()) + { + warn!("error while scanning {func} in {path:?}: {err}"); } - FunctionTy::WebTrigger((ref func, ref path, _, def)) => { - let mut checker = AuthenticateChecker::new(); - debug!("checking webtrigger {func} at {path:?}"); - if let Err(err) = - authn_interp.run_checker(def, &mut checker, path.clone(), func.clone()) - { - warn!("error while scanning {func} in {path:?}: {err}"); - } - reporter.add_vulnerabilities(checker.into_vulns()); + reporter.add_vulnerabilities(checker.into_vulns()); + + } else if func.web_trigger { + let mut checker = AuthenticateChecker::new(); + debug!("checking webtrigger {func} at {path:?}"); + if let Err(err) = + authn_interp.run_checker(def, &mut checker, path.clone(), func.clone()) + { + warn!("error while scanning {func} in {path:?}: {err}"); } + reporter.add_vulnerabilities(checker.into_vulns()); + + } } let report = serde_json::to_string(&reporter.into_report()).into_diagnostic()?; From 36b317a3c5c5584752736036c983d2692a8029c8 Mon Sep 17 00:00:00 2001 From: awang7 Date: Fri, 15 Sep 2023 17:20:10 -0400 Subject: [PATCH 177/517] edited main to iterate over vector of functions. TODO: edit add_funcs in main.rs and test into_analyzble_function use case in main.rs --- crates/forge_loader/src/manifest.rs | 45 +++------------- crates/fsrt/src/main.rs | 81 +++++++++++++++++++---------- 2 files changed, 60 insertions(+), 66 deletions(-) diff --git a/crates/forge_loader/src/manifest.rs b/crates/forge_loader/src/manifest.rs index 16e1687a..c74fa421 100644 --- a/crates/forge_loader/src/manifest.rs +++ b/crates/forge_loader/src/manifest.rs @@ -240,9 +240,9 @@ pub enum FunctionTy { // Struct used for tracking what scan a funtion requires. #[derive(Debug, Clone, PartialEq, Eq, Deserialize)] pub struct Entrypoints<'a> { - function: &'a str, - invokable: bool, - web_trigger: bool, + pub function: &'a str, + pub invokable: bool, + pub web_trigger: bool, } // Helper functions that help filter out which functions are what. @@ -285,9 +285,9 @@ impl AsRef for FunctionTy { impl<'a> ForgeModules<'a> { // TODO: function returns iterator where each item is some specified type. - pub fn into_analyzable_functions( + pub fn into_analyzable_functions ( self, - ) { + ) -> Vec>{ // number of webtriggers are usually low, so it's better to just sort them and reuse // self.webtriggers // .sort_unstable_by_key(|trigger| trigger.function); @@ -413,11 +413,9 @@ impl<'a> ForgeModules<'a> { } } - // get array for user invokable module functions // make alternate_functions all user-invokable functions - // let mut alternate_functions = Vec::new(); for module in self.extra.into_values().flatten() { if let Some(mod_function) = module.function { functions_to_scan.push(Entrypoints { @@ -435,37 +433,8 @@ impl<'a> ForgeModules<'a> { }); } } - functions_to_scan.into_iter(); - // alternate_functions.into_iter(); - // Iterate over Consumers and check that if consumers isn't in alternate functions, add consumer funtion to be ignored - // assuming that alternate functions already has all user invokable functions. - // self.consumers.iter().for_each(|consumer| { - // if !alternate_functions.contains(&consumer.resolver.function) { - // ignored_functions.insert(consumer.resolver.function); - // } - // }); - - // TODO: Iterate through all deserialized entrypoints that are represented as a struct when deserialized - // Update Struct values to be true or not. If any part true, then scan. - // This solution fixes the problem that we only check known user invokable modules and also acccounts for non-invokable module entry points - - // return non-user invokable functions - // self.functions.into_iter().filter_map(move |func| { - // if ignored_functions.contains(&func.key) { - // return None; - // } - // Some( - // if self - // .webtriggers - // .binary_search_by_key(&func.key, |trigger| trigger.function) - // .is_ok() - // { - // FunctionTy::WebTrigger(func) - // } else { - // FunctionTy::Invokable(func) - // }, - // ) - // }) + + return functions_to_scan; } } diff --git a/crates/fsrt/src/main.rs b/crates/fsrt/src/main.rs index 488d915a..d2729304 100644 --- a/crates/fsrt/src/main.rs +++ b/crates/fsrt/src/main.rs @@ -12,6 +12,7 @@ use std::{ use clap::{Parser, ValueHint}; use miette::{IntoDiagnostic, Result}; +use serde_json::map::Entry; use swc_core::{ common::{Globals, Mark, SourceMap, GLOBALS}, ecma::{ @@ -34,7 +35,7 @@ use forge_analyzer::{ resolver::resolve_calls, }; -use forge_loader::manifest::{ForgeManifest, FunctionRef, FunctionTy}; +use forge_loader::manifest::{ForgeManifest, FunctionRef, FunctionTy, Entrypoints}; use walkdir::WalkDir; #[derive(Parser, Debug)] @@ -82,7 +83,7 @@ struct ForgeProject { sm: Arc, ctx: AppCtx, env: Environment, - funcs: Vec>, + funcs: Vec>, opts: Opts, } @@ -133,8 +134,8 @@ impl ForgeProject { opts: Opts::default(), } } - - fn add_funcs<'a, I: IntoIterator>>(&mut self, iter: I) { +// TODO: edit to work with new iterator that not FUNCTIONTY + fn add_funcs<'a, I: IntoIterator>>(&mut self, iter: I) { self.funcs.extend(iter.into_iter().flat_map(|ftype| { ftype.sequence(|(func_name, path)| { let modid = self.ctx.modid_from_path(&path)?; @@ -187,12 +188,14 @@ fn scan_directory(dir: PathBuf, function: Option<&str>, opts: Opts) -> Result(resolved_func.into_func_path()) - }) - }); + let funcrefs = &manifest.modules.into_analyzable_functions(); + + // .flat_map(|f| { + // f.sequence(|fmod| { + // let resolved_func = FunctionRef::try_from(fmod)?.try_resolve(&paths, &dir)?; + // Ok::<_, forge_loader::Error>(resolved_func.into_func_path()) + // }) + // }); let src_root = dir.join("src"); let mut proj = ForgeProject::with_files_and_sourceroot(src_root, paths.clone()); if transpiled_async { @@ -210,26 +213,48 @@ fn scan_directory(dir: PathBuf, function: Option<&str>, opts: Opts) -> Result { - let mut checker = AuthZChecker::new(); - debug!("checking {func} at {path:?}"); - if let Err(err) = interp.run_checker(def, &mut checker, path.clone(), func.clone()) - { - warn!("error while scanning {func} in {path:?}: {err}"); - } - reporter.add_vulnerabilities(checker.into_vulns()); + // match *func { + // FunctionTy::Invokable((ref func, ref path, _, def)) => { + // let mut checker = AuthZChecker::new(); + // debug!("checking {func} at {path:?}"); + // if let Err(err) = interp.run_checker(def, &mut checker, path.clone(), func.clone()) + // { + // warn!("error while scanning {func} in {path:?}: {err}"); + // } + // reporter.add_vulnerabilities(checker.into_vulns()); + // } + // FunctionTy::WebTrigger((ref func, ref path, _, def)) => { + // let mut checker = AuthenticateChecker::new(); + // debug!("checking webtrigger {func} at {path:?}"); + // if let Err(err) = + // authn_interp.run_checker(def, &mut checker, path.clone(), func.clone()) + // { + // warn!("error while scanning {func} in {path:?}: {err}"); + // } + // reporter.add_vulnerabilities(checker.into_vulns()); + // } + // } + + if func.invokable { + let mut checker = AuthZChecker::new(); + debug!("checking {func} at {path:?}"); + if let Err(err) = interp.run_checker(def, &mut checker, path.clone(), func.clone()) + { + warn!("error while scanning {func} in {path:?}: {err}"); } - FunctionTy::WebTrigger((ref func, ref path, _, def)) => { - let mut checker = AuthenticateChecker::new(); - debug!("checking webtrigger {func} at {path:?}"); - if let Err(err) = - authn_interp.run_checker(def, &mut checker, path.clone(), func.clone()) - { - warn!("error while scanning {func} in {path:?}: {err}"); - } - reporter.add_vulnerabilities(checker.into_vulns()); + reporter.add_vulnerabilities(checker.into_vulns()); + + } else if func.web_trigger { + let mut checker = AuthenticateChecker::new(); + debug!("checking webtrigger {func} at {path:?}"); + if let Err(err) = + authn_interp.run_checker(def, &mut checker, path.clone(), func.clone()) + { + warn!("error while scanning {func} in {path:?}: {err}"); } + reporter.add_vulnerabilities(checker.into_vulns()); + + } } let report = serde_json::to_string(&reporter.into_report()).into_diagnostic()?; From 31a30481bd3d25f4b4b28fa40ea7314359037786 Mon Sep 17 00:00:00 2001 From: awang7 Date: Mon, 18 Sep 2023 11:09:18 -0400 Subject: [PATCH 178/517] added new modules for additional endpoints in user invokable modules --- crates/forge_loader/src/manifest.rs | 43 ++++++++++++++++++++++++++++- 1 file changed, 42 insertions(+), 1 deletion(-) diff --git a/crates/forge_loader/src/manifest.rs b/crates/forge_loader/src/manifest.rs index c74fa421..1c4794e1 100644 --- a/crates/forge_loader/src/manifest.rs +++ b/crates/forge_loader/src/manifest.rs @@ -34,7 +34,6 @@ struct ModInfo<'a> { #[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] struct MacroMod<'a> { - // #[serde(flatten, borrow)] key: &'a str, function: &'a str, #[serde(borrow)] @@ -45,6 +44,42 @@ struct MacroMod<'a> { export: Option>, } +#[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] +struct ContentByLineItem<'a> { + key: &'a str, + function: &'a str, + #[serde(borrow)] + resolver: Option>, + #[serde(borrow)] + dynamic_properties: Option>, +} + +#[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] +struct IssueGlance<'a> { + key: &'a str, + function: &'a str, + #[serde(borrow)] + resolver: Option>, + #[serde(borrow)] + dynamic_properties: Option>, + +} + +#[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] +struct AccessImportType<'a> { + key: &'a str, + function: &'a str, + #[serde(borrow)] + one_delete_import: Option>, + #[serde(borrow)] + start_import: Option>, + #[serde(borrow)] + stop_import: Option>, + #[serde(borrow)] + import_status: Option>, + +} + // WebTrigger => RawTrigger; WHY IS THIS NAMED DIFFERENTLY !? WHO CHANGED NAMES #[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] struct RawTrigger<'a> { @@ -129,6 +164,12 @@ pub struct ForgeModules<'a> { macros: Vec>, #[serde(rename = "function", default, borrow)] pub functions: Vec>, + #[serde(rename = "contentByLineItem", default, borrow)] + content_by_line_item: Vec>, + #[serde(rename = "jira:issueGlance", default, borrow)] + issue_glance: Vec>, + #[serde(rename = "jira:accessImportType", default, borrow)] + access_import_type: Vec>, // deserializing non user-invocable modules #[serde(rename = "webtrigger", default, borrow)] webtriggers: Vec>, From 1eff9d7f7f40f9b64ad69fe549ae58dfeaff75e0 Mon Sep 17 00:00:00 2001 From: awang7 Date: Mon, 18 Sep 2023 11:09:18 -0400 Subject: [PATCH 179/517] added new modules for additional endpoints in user invokable modules --- crates/forge_loader/src/manifest.rs | 43 ++++++++++++++++++++++++++++- 1 file changed, 42 insertions(+), 1 deletion(-) diff --git a/crates/forge_loader/src/manifest.rs b/crates/forge_loader/src/manifest.rs index c74fa421..1c4794e1 100644 --- a/crates/forge_loader/src/manifest.rs +++ b/crates/forge_loader/src/manifest.rs @@ -34,7 +34,6 @@ struct ModInfo<'a> { #[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] struct MacroMod<'a> { - // #[serde(flatten, borrow)] key: &'a str, function: &'a str, #[serde(borrow)] @@ -45,6 +44,42 @@ struct MacroMod<'a> { export: Option>, } +#[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] +struct ContentByLineItem<'a> { + key: &'a str, + function: &'a str, + #[serde(borrow)] + resolver: Option>, + #[serde(borrow)] + dynamic_properties: Option>, +} + +#[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] +struct IssueGlance<'a> { + key: &'a str, + function: &'a str, + #[serde(borrow)] + resolver: Option>, + #[serde(borrow)] + dynamic_properties: Option>, + +} + +#[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] +struct AccessImportType<'a> { + key: &'a str, + function: &'a str, + #[serde(borrow)] + one_delete_import: Option>, + #[serde(borrow)] + start_import: Option>, + #[serde(borrow)] + stop_import: Option>, + #[serde(borrow)] + import_status: Option>, + +} + // WebTrigger => RawTrigger; WHY IS THIS NAMED DIFFERENTLY !? WHO CHANGED NAMES #[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] struct RawTrigger<'a> { @@ -129,6 +164,12 @@ pub struct ForgeModules<'a> { macros: Vec>, #[serde(rename = "function", default, borrow)] pub functions: Vec>, + #[serde(rename = "contentByLineItem", default, borrow)] + content_by_line_item: Vec>, + #[serde(rename = "jira:issueGlance", default, borrow)] + issue_glance: Vec>, + #[serde(rename = "jira:accessImportType", default, borrow)] + access_import_type: Vec>, // deserializing non user-invocable modules #[serde(rename = "webtrigger", default, borrow)] webtriggers: Vec>, From 559dc5b58288761ba8bf2ebbb6ee2ac384795e24 Mon Sep 17 00:00:00 2001 From: awang7 Date: Mon, 18 Sep 2023 11:20:37 -0400 Subject: [PATCH 180/517] added methods for adding additional user invokable endpoints to vector for scanning. --- crates/forge_loader/src/manifest.rs | 88 +++++++++++++++++++++++++++++ 1 file changed, 88 insertions(+) diff --git a/crates/forge_loader/src/manifest.rs b/crates/forge_loader/src/manifest.rs index 1c4794e1..a76efe43 100644 --- a/crates/forge_loader/src/manifest.rs +++ b/crates/forge_loader/src/manifest.rs @@ -454,6 +454,94 @@ impl<'a> ForgeModules<'a> { } } + + for contentitem in self.content_by_line_item { + functions_to_scan.push(Entrypoints { + function: contentitem.function, + invokable: true, + web_trigger: false + }); + if let Some(resolver)= contentitem.resolver { + functions_to_scan.push(Entrypoints { + function: resolver.function, + invokable: true, + web_trigger: false + }) + } + + if let Some(dynamic_properties)= contentitem.dynamic_properties { + functions_to_scan.push(Entrypoints { + function: dynamic_properties.function, + invokable: true, + web_trigger: false + }) + } + + } + + for issue in self.issue_glance { + functions_to_scan.push(Entrypoints { + function: issue.function, + invokable: true, + web_trigger: false + }); + if let Some(resolver)= issue.resolver { + functions_to_scan.push(Entrypoints { + function: resolver.function, + invokable: true, + web_trigger: false + }) + } + + if let Some(dynamic_properties)= issue.dynamic_properties { + functions_to_scan.push(Entrypoints { + function: dynamic_properties.function, + invokable: true, + web_trigger: false + }) + } + + } + + for access in self.access_import_type { + functions_to_scan.push(Entrypoints { + function: access.function, + invokable: true, + web_trigger: false + }); + if let Some(delete) = access.one_delete_import { + functions_to_scan.push(Entrypoints { + function: delete.function, + invokable: true, + web_trigger: false + }) + } + + if let Some(start)= access.start_import { + functions_to_scan.push(Entrypoints { + function: start.function, + invokable: true, + web_trigger: false + }) + } + + if let Some(stop)= access.stop_import { + functions_to_scan.push(Entrypoints { + function: stop.function, + invokable: true, + web_trigger: false + }) + } + + if let Some(status)= access.import_status { + functions_to_scan.push(Entrypoints { + function: status.function, + invokable: true, + web_trigger: false + }) + } + + } // get array for user invokable module functions // make alternate_functions all user-invokable functions From 1068e6d1b450c9d3600e057c290941c497bc993d Mon Sep 17 00:00:00 2001 From: awang7 Date: Mon, 18 Sep 2023 11:20:37 -0400 Subject: [PATCH 181/517] added methods for adding additional user invokable endpoints to vector for scanning. --- crates/forge_loader/src/manifest.rs | 88 +++++++++++++++++++++++++++++ 1 file changed, 88 insertions(+) diff --git a/crates/forge_loader/src/manifest.rs b/crates/forge_loader/src/manifest.rs index 1c4794e1..a76efe43 100644 --- a/crates/forge_loader/src/manifest.rs +++ b/crates/forge_loader/src/manifest.rs @@ -454,6 +454,94 @@ impl<'a> ForgeModules<'a> { } } + + for contentitem in self.content_by_line_item { + functions_to_scan.push(Entrypoints { + function: contentitem.function, + invokable: true, + web_trigger: false + }); + if let Some(resolver)= contentitem.resolver { + functions_to_scan.push(Entrypoints { + function: resolver.function, + invokable: true, + web_trigger: false + }) + } + + if let Some(dynamic_properties)= contentitem.dynamic_properties { + functions_to_scan.push(Entrypoints { + function: dynamic_properties.function, + invokable: true, + web_trigger: false + }) + } + + } + + for issue in self.issue_glance { + functions_to_scan.push(Entrypoints { + function: issue.function, + invokable: true, + web_trigger: false + }); + if let Some(resolver)= issue.resolver { + functions_to_scan.push(Entrypoints { + function: resolver.function, + invokable: true, + web_trigger: false + }) + } + + if let Some(dynamic_properties)= issue.dynamic_properties { + functions_to_scan.push(Entrypoints { + function: dynamic_properties.function, + invokable: true, + web_trigger: false + }) + } + + } + + for access in self.access_import_type { + functions_to_scan.push(Entrypoints { + function: access.function, + invokable: true, + web_trigger: false + }); + if let Some(delete) = access.one_delete_import { + functions_to_scan.push(Entrypoints { + function: delete.function, + invokable: true, + web_trigger: false + }) + } + + if let Some(start)= access.start_import { + functions_to_scan.push(Entrypoints { + function: start.function, + invokable: true, + web_trigger: false + }) + } + + if let Some(stop)= access.stop_import { + functions_to_scan.push(Entrypoints { + function: stop.function, + invokable: true, + web_trigger: false + }) + } + + if let Some(status)= access.import_status { + functions_to_scan.push(Entrypoints { + function: status.function, + invokable: true, + web_trigger: false + }) + } + + } // get array for user invokable module functions // make alternate_functions all user-invokable functions From 826acccc5dab94838fe69b93c04efe978a7fade5 Mon Sep 17 00:00:00 2001 From: awang7 Date: Mon, 18 Sep 2023 11:50:09 -0400 Subject: [PATCH 182/517] removed callback mod and abstracted structs to use MacroMod to represent {function: str} entry points. Added missing entrypoints on jira:customField and implmented scanning for those --- crates/forge_loader/src/manifest.rs | 53 ++++++++++++++++++++++------- 1 file changed, 41 insertions(+), 12 deletions(-) diff --git a/crates/forge_loader/src/manifest.rs b/crates/forge_loader/src/manifest.rs index a76efe43..10f018a4 100644 --- a/crates/forge_loader/src/manifest.rs +++ b/crates/forge_loader/src/manifest.rs @@ -117,13 +117,7 @@ struct ScheduledTrigger<'a> { pub struct DataProvider<'a> { key: &'a str, #[serde(flatten, borrow)] - callback: Callback<'a>, -} - -// Struct for mapping functions defined one more level in whose value is {function: string}. Used to represent resolver types. -#[derive(Debug, Clone, PartialEq, Eq, Deserialize)] -pub struct Callback<'a> { - pub function: &'a str, + callback: ModInfo<'a>, } // Struct for Custom field Module. Check that search suggestion gets read in correctly. @@ -131,23 +125,28 @@ pub struct Callback<'a> { pub struct CustomField<'a> { #[serde(flatten, borrow)] key: &'a str, + // all attributes below involve function calls + value: &'a str, search_suggestion: &'a str, + function: &'a str, + edit: &'a str, + resolver: ModInfo<'a> } #[derive(Debug, Clone, PartialEq, Eq, Deserialize)] pub struct UiModificatons<'a> { - #[serde(flatten, borrow)] key: &'a str, - resolver: Callback<'a>, + #[serde(flatten, borrow)] + resolver: ModInfo<'a>, } #[derive(Debug, Clone, PartialEq, Eq, Deserialize)] pub struct WorkflowValidator<'a> { - #[serde(flatten, borrow)] key: &'a str, functon: &'a str, - resolver: Callback<'a> + #[serde(flatten, borrow)] + resolver: ModInfo<'a> } #[derive(Debug, Clone, PartialEq, Eq, Deserialize)] @@ -388,13 +387,43 @@ impl<'a> ForgeModules<'a> { }); self.custom_field.iter().for_each(|customfield| { + functions_to_scan.push( + Entrypoints { + function: customfield.value, + invokable: true, + web_trigger: false, + } + ); functions_to_scan.push( Entrypoints { function: customfield.search_suggestion, invokable: true, web_trigger: false, } - ) + ); + functions_to_scan.push( + Entrypoints { + function: customfield.function, + invokable: true, + web_trigger: false, + } + ); + functions_to_scan.push( + Entrypoints { + function: customfield.edit, + invokable: true, + web_trigger: false, + } + ); + + functions_to_scan.push( + Entrypoints { + function: customfield.resolver.function, + invokable: true, + web_trigger: false, + } + ); + }); self.ui_modifications.iter().for_each(|ui| { From 720aef9759b378c0b082850770b4e6758f841a37 Mon Sep 17 00:00:00 2001 From: awang7 Date: Mon, 18 Sep 2023 11:50:09 -0400 Subject: [PATCH 183/517] removed callback mod and abstracted structs to use MacroMod to represent {function: str} entry points. Added missing entrypoints on jira:customField and implmented scanning for those --- crates/forge_loader/src/manifest.rs | 53 ++++++++++++++++++++++------- 1 file changed, 41 insertions(+), 12 deletions(-) diff --git a/crates/forge_loader/src/manifest.rs b/crates/forge_loader/src/manifest.rs index a76efe43..10f018a4 100644 --- a/crates/forge_loader/src/manifest.rs +++ b/crates/forge_loader/src/manifest.rs @@ -117,13 +117,7 @@ struct ScheduledTrigger<'a> { pub struct DataProvider<'a> { key: &'a str, #[serde(flatten, borrow)] - callback: Callback<'a>, -} - -// Struct for mapping functions defined one more level in whose value is {function: string}. Used to represent resolver types. -#[derive(Debug, Clone, PartialEq, Eq, Deserialize)] -pub struct Callback<'a> { - pub function: &'a str, + callback: ModInfo<'a>, } // Struct for Custom field Module. Check that search suggestion gets read in correctly. @@ -131,23 +125,28 @@ pub struct Callback<'a> { pub struct CustomField<'a> { #[serde(flatten, borrow)] key: &'a str, + // all attributes below involve function calls + value: &'a str, search_suggestion: &'a str, + function: &'a str, + edit: &'a str, + resolver: ModInfo<'a> } #[derive(Debug, Clone, PartialEq, Eq, Deserialize)] pub struct UiModificatons<'a> { - #[serde(flatten, borrow)] key: &'a str, - resolver: Callback<'a>, + #[serde(flatten, borrow)] + resolver: ModInfo<'a>, } #[derive(Debug, Clone, PartialEq, Eq, Deserialize)] pub struct WorkflowValidator<'a> { - #[serde(flatten, borrow)] key: &'a str, functon: &'a str, - resolver: Callback<'a> + #[serde(flatten, borrow)] + resolver: ModInfo<'a> } #[derive(Debug, Clone, PartialEq, Eq, Deserialize)] @@ -388,13 +387,43 @@ impl<'a> ForgeModules<'a> { }); self.custom_field.iter().for_each(|customfield| { + functions_to_scan.push( + Entrypoints { + function: customfield.value, + invokable: true, + web_trigger: false, + } + ); functions_to_scan.push( Entrypoints { function: customfield.search_suggestion, invokable: true, web_trigger: false, } - ) + ); + functions_to_scan.push( + Entrypoints { + function: customfield.function, + invokable: true, + web_trigger: false, + } + ); + functions_to_scan.push( + Entrypoints { + function: customfield.edit, + invokable: true, + web_trigger: false, + } + ); + + functions_to_scan.push( + Entrypoints { + function: customfield.resolver.function, + invokable: true, + web_trigger: false, + } + ); + }); self.ui_modifications.iter().for_each(|ui| { From b955fa7d23e30eb1e87930b5775221b06601a2e9 Mon Sep 17 00:00:00 2001 From: awang7 Date: Mon, 18 Sep 2023 18:21:10 -0400 Subject: [PATCH 184/517] changed entrypoints to entrypoint. Working on other comments --- crates/forge_loader/src/manifest.rs | 82 ++++++++++++++++------------- crates/fsrt/src/main.rs | 37 +++++++------ 2 files changed, 65 insertions(+), 54 deletions(-) diff --git a/crates/forge_loader/src/manifest.rs b/crates/forge_loader/src/manifest.rs index 10f018a4..9cbc9efd 100644 --- a/crates/forge_loader/src/manifest.rs +++ b/crates/forge_loader/src/manifest.rs @@ -143,9 +143,9 @@ pub struct UiModificatons<'a> { #[derive(Debug, Clone, PartialEq, Eq, Deserialize)] pub struct WorkflowValidator<'a> { - key: &'a str, - functon: &'a str, #[serde(flatten, borrow)] + key: &'a str, + function: &'a str, resolver: ModInfo<'a> } @@ -279,7 +279,7 @@ pub enum FunctionTy { // Struct used for tracking what scan a funtion requires. #[derive(Debug, Clone, PartialEq, Eq, Deserialize)] -pub struct Entrypoints<'a> { +pub struct Entrypoint<'a> { pub function: &'a str, pub invokable: bool, pub web_trigger: bool, @@ -327,10 +327,10 @@ impl<'a> ForgeModules<'a> { // TODO: function returns iterator where each item is some specified type. pub fn into_analyzable_functions ( self, - ) -> Vec>{ + ) -> Vec>{ // number of webtriggers are usually low, so it's better to just sort them and reuse - // self.webtriggers - // .sort_unstable_by_key(|trigger| trigger.function); + self.webtriggers.iter(). + .sort_unstable_by_key(|trigger| trigger.function); // Get all the Triggers and represent them as a new struct thing where "webtrigger" attribute is true // for all trigger things @@ -338,7 +338,7 @@ impl<'a> ForgeModules<'a> { let mut functions_to_scan = Vec::new(); self.webtriggers.iter().for_each(|webtriggers| { functions_to_scan.push( - Entrypoints { + Entrypoint { function: webtriggers.function, invokable: false, web_trigger: true, @@ -348,7 +348,7 @@ impl<'a> ForgeModules<'a> { }); self.event_triggers.iter().for_each(|event_triggers| { functions_to_scan.push( - Entrypoints { + Entrypoint { function: event_triggers.raw.function, invokable: false, web_trigger: true, @@ -357,7 +357,7 @@ impl<'a> ForgeModules<'a> { }); self.scheduled_triggers.iter().for_each(|schedule_triggers| { functions_to_scan.push( - Entrypoints { + Entrypoint { function: schedule_triggers.raw.function, invokable: false, web_trigger: true, @@ -368,7 +368,7 @@ impl<'a> ForgeModules<'a> { // create arrays representing functions that expose user non-invokable functions self.consumers.iter().for_each(|consumers| { functions_to_scan.push( - Entrypoints { + Entrypoint { function: consumers.resolver.function, invokable: true, web_trigger: false, @@ -378,7 +378,7 @@ impl<'a> ForgeModules<'a> { self.data_provider.iter().for_each(|dataprovider| { functions_to_scan.push( - Entrypoints { + Entrypoint { function: dataprovider.callback.function, invokable: true, web_trigger: false, @@ -388,28 +388,28 @@ impl<'a> ForgeModules<'a> { self.custom_field.iter().for_each(|customfield| { functions_to_scan.push( - Entrypoints { + Entrypoint { function: customfield.value, invokable: true, web_trigger: false, } ); functions_to_scan.push( - Entrypoints { + Entrypoint { function: customfield.search_suggestion, invokable: true, web_trigger: false, } ); functions_to_scan.push( - Entrypoints { + Entrypoint { function: customfield.function, invokable: true, web_trigger: false, } ); functions_to_scan.push( - Entrypoints { + Entrypoint { function: customfield.edit, invokable: true, web_trigger: false, @@ -417,7 +417,7 @@ impl<'a> ForgeModules<'a> { ); functions_to_scan.push( - Entrypoints { + Entrypoint { function: customfield.resolver.function, invokable: true, web_trigger: false, @@ -428,7 +428,7 @@ impl<'a> ForgeModules<'a> { self.ui_modifications.iter().for_each(|ui| { functions_to_scan.push( - Entrypoints { + Entrypoint { function: ui.resolver.function, invokable: true, web_trigger: false, @@ -438,17 +438,25 @@ impl<'a> ForgeModules<'a> { self.workflow_validator.iter().for_each(|validator| { functions_to_scan.push( - Entrypoints { + Entrypoint { + function: validator.function, + invokable: true, + web_trigger: false, + } + ); + + functions_to_scan.push( + Entrypoint { function: validator.resolver.function, invokable: true, web_trigger: false, } - ) + ); }); self.workflow_post_function.iter().for_each(|post_function| { functions_to_scan.push( - Entrypoints { + Entrypoint { function: post_function.function, invokable: true, web_trigger: false, @@ -460,7 +468,7 @@ impl<'a> ForgeModules<'a> { // ie macros has config and export fields on top of resolver fields that are functions for macros in self.macros { if let Some(resolver)= macros.resolver { - functions_to_scan.push(Entrypoints { + functions_to_scan.push(Entrypoint { function: resolver.function, invokable: true, web_trigger: false @@ -468,14 +476,14 @@ impl<'a> ForgeModules<'a> { } if let Some(config)= macros.config { - functions_to_scan.push(Entrypoints { + functions_to_scan.push(Entrypoint { function: config.function, invokable: true, web_trigger: false }) } if let Some(export)= macros.export { - functions_to_scan.push(Entrypoints { + functions_to_scan.push(Entrypoint { function: export.function, invokable: true, web_trigger: false @@ -485,13 +493,13 @@ impl<'a> ForgeModules<'a> { } for contentitem in self.content_by_line_item { - functions_to_scan.push(Entrypoints { + functions_to_scan.push(Entrypoint { function: contentitem.function, invokable: true, web_trigger: false }); if let Some(resolver)= contentitem.resolver { - functions_to_scan.push(Entrypoints { + functions_to_scan.push(Entrypoint { function: resolver.function, invokable: true, web_trigger: false @@ -499,7 +507,7 @@ impl<'a> ForgeModules<'a> { } if let Some(dynamic_properties)= contentitem.dynamic_properties { - functions_to_scan.push(Entrypoints { + functions_to_scan.push(Entrypoint { function: dynamic_properties.function, invokable: true, web_trigger: false @@ -509,13 +517,13 @@ impl<'a> ForgeModules<'a> { } for issue in self.issue_glance { - functions_to_scan.push(Entrypoints { + functions_to_scan.push(Entrypoint { function: issue.function, invokable: true, web_trigger: false }); if let Some(resolver)= issue.resolver { - functions_to_scan.push(Entrypoints { + functions_to_scan.push(Entrypoint { function: resolver.function, invokable: true, web_trigger: false @@ -523,7 +531,7 @@ impl<'a> ForgeModules<'a> { } if let Some(dynamic_properties)= issue.dynamic_properties { - functions_to_scan.push(Entrypoints { + functions_to_scan.push(Entrypoint { function: dynamic_properties.function, invokable: true, web_trigger: false @@ -533,13 +541,13 @@ impl<'a> ForgeModules<'a> { } for access in self.access_import_type { - functions_to_scan.push(Entrypoints { + functions_to_scan.push(Entrypoint { function: access.function, invokable: true, web_trigger: false }); if let Some(delete) = access.one_delete_import { - functions_to_scan.push(Entrypoints { + functions_to_scan.push(Entrypoint { function: delete.function, invokable: true, web_trigger: false @@ -547,7 +555,7 @@ impl<'a> ForgeModules<'a> { } if let Some(start)= access.start_import { - functions_to_scan.push(Entrypoints { + functions_to_scan.push(Entrypoint { function: start.function, invokable: true, web_trigger: false @@ -555,7 +563,7 @@ impl<'a> ForgeModules<'a> { } if let Some(stop)= access.stop_import { - functions_to_scan.push(Entrypoints { + functions_to_scan.push(Entrypoint { function: stop.function, invokable: true, web_trigger: false @@ -563,7 +571,7 @@ impl<'a> ForgeModules<'a> { } if let Some(status)= access.import_status { - functions_to_scan.push(Entrypoints { + functions_to_scan.push(Entrypoint { function: status.function, invokable: true, web_trigger: false @@ -576,7 +584,7 @@ impl<'a> ForgeModules<'a> { // make alternate_functions all user-invokable functions for module in self.extra.into_values().flatten() { if let Some(mod_function) = module.function { - functions_to_scan.push(Entrypoints { + functions_to_scan.push(Entrypoint { function: mod_function, invokable: true, web_trigger: false @@ -584,7 +592,7 @@ impl<'a> ForgeModules<'a> { } if let Some(resolver) = module.resolver { - functions_to_scan.push(Entrypoints { + functions_to_scan.push(Entrypoint { function: resolver.function, invokable: true, web_trigger: false @@ -592,7 +600,7 @@ impl<'a> ForgeModules<'a> { } } - return functions_to_scan; + functions_to_scan } } diff --git a/crates/fsrt/src/main.rs b/crates/fsrt/src/main.rs index d2729304..3a5162fe 100644 --- a/crates/fsrt/src/main.rs +++ b/crates/fsrt/src/main.rs @@ -35,7 +35,7 @@ use forge_analyzer::{ resolver::resolve_calls, }; -use forge_loader::manifest::{ForgeManifest, FunctionRef, FunctionTy, Entrypoints}; +use forge_loader::manifest::{ForgeManifest, FunctionRef, FunctionTy, Entrypoint}; use walkdir::WalkDir; #[derive(Parser, Debug)] @@ -83,7 +83,7 @@ struct ForgeProject { sm: Arc, ctx: AppCtx, env: Environment, - funcs: Vec>, + funcs: Vec>, opts: Opts, } @@ -135,17 +135,20 @@ impl ForgeProject { } } // TODO: edit to work with new iterator that not FUNCTIONTY - fn add_funcs<'a, I: IntoIterator>>(&mut self, iter: I) { - self.funcs.extend(iter.into_iter().flat_map(|ftype| { - ftype.sequence(|(func_name, path)| { - let modid = self.ctx.modid_from_path(&path)?; - let func = self.env.module_export(modid, func_name)?; - Some((func_name.to_owned(), path, modid, func)) - }) - })); + fn add_funcs<'a, I: IntoIterator>>(&mut self, iter: I) { + self.funcs.extend(iter); } } + +// self.funcs.extend(iter.into_iter().flat_map(|ftype| { +// ftype.sequence(|(func_name, path)| { +// let modid = self.ctx.modid_from_path(&path)?; +// let func = self.env.module_export(modid, func_name)?; +// Some((func_name.to_owned(), path, modid, func)) +// }) +// })); + fn is_js_file>(path: P) -> bool { matches!( path.as_ref().extension().map(|s| s.as_bytes()), @@ -188,7 +191,7 @@ fn scan_directory(dir: PathBuf, function: Option<&str>, opts: Opts) -> Result, opts: Opts) -> Result Date: Mon, 18 Sep 2023 18:21:10 -0400 Subject: [PATCH 185/517] changed entrypoints to entrypoint. Working on other comments --- crates/forge_loader/src/manifest.rs | 82 ++++++++++++++++------------- crates/fsrt/src/main.rs | 37 +++++++------ 2 files changed, 65 insertions(+), 54 deletions(-) diff --git a/crates/forge_loader/src/manifest.rs b/crates/forge_loader/src/manifest.rs index 10f018a4..9cbc9efd 100644 --- a/crates/forge_loader/src/manifest.rs +++ b/crates/forge_loader/src/manifest.rs @@ -143,9 +143,9 @@ pub struct UiModificatons<'a> { #[derive(Debug, Clone, PartialEq, Eq, Deserialize)] pub struct WorkflowValidator<'a> { - key: &'a str, - functon: &'a str, #[serde(flatten, borrow)] + key: &'a str, + function: &'a str, resolver: ModInfo<'a> } @@ -279,7 +279,7 @@ pub enum FunctionTy { // Struct used for tracking what scan a funtion requires. #[derive(Debug, Clone, PartialEq, Eq, Deserialize)] -pub struct Entrypoints<'a> { +pub struct Entrypoint<'a> { pub function: &'a str, pub invokable: bool, pub web_trigger: bool, @@ -327,10 +327,10 @@ impl<'a> ForgeModules<'a> { // TODO: function returns iterator where each item is some specified type. pub fn into_analyzable_functions ( self, - ) -> Vec>{ + ) -> Vec>{ // number of webtriggers are usually low, so it's better to just sort them and reuse - // self.webtriggers - // .sort_unstable_by_key(|trigger| trigger.function); + self.webtriggers.iter(). + .sort_unstable_by_key(|trigger| trigger.function); // Get all the Triggers and represent them as a new struct thing where "webtrigger" attribute is true // for all trigger things @@ -338,7 +338,7 @@ impl<'a> ForgeModules<'a> { let mut functions_to_scan = Vec::new(); self.webtriggers.iter().for_each(|webtriggers| { functions_to_scan.push( - Entrypoints { + Entrypoint { function: webtriggers.function, invokable: false, web_trigger: true, @@ -348,7 +348,7 @@ impl<'a> ForgeModules<'a> { }); self.event_triggers.iter().for_each(|event_triggers| { functions_to_scan.push( - Entrypoints { + Entrypoint { function: event_triggers.raw.function, invokable: false, web_trigger: true, @@ -357,7 +357,7 @@ impl<'a> ForgeModules<'a> { }); self.scheduled_triggers.iter().for_each(|schedule_triggers| { functions_to_scan.push( - Entrypoints { + Entrypoint { function: schedule_triggers.raw.function, invokable: false, web_trigger: true, @@ -368,7 +368,7 @@ impl<'a> ForgeModules<'a> { // create arrays representing functions that expose user non-invokable functions self.consumers.iter().for_each(|consumers| { functions_to_scan.push( - Entrypoints { + Entrypoint { function: consumers.resolver.function, invokable: true, web_trigger: false, @@ -378,7 +378,7 @@ impl<'a> ForgeModules<'a> { self.data_provider.iter().for_each(|dataprovider| { functions_to_scan.push( - Entrypoints { + Entrypoint { function: dataprovider.callback.function, invokable: true, web_trigger: false, @@ -388,28 +388,28 @@ impl<'a> ForgeModules<'a> { self.custom_field.iter().for_each(|customfield| { functions_to_scan.push( - Entrypoints { + Entrypoint { function: customfield.value, invokable: true, web_trigger: false, } ); functions_to_scan.push( - Entrypoints { + Entrypoint { function: customfield.search_suggestion, invokable: true, web_trigger: false, } ); functions_to_scan.push( - Entrypoints { + Entrypoint { function: customfield.function, invokable: true, web_trigger: false, } ); functions_to_scan.push( - Entrypoints { + Entrypoint { function: customfield.edit, invokable: true, web_trigger: false, @@ -417,7 +417,7 @@ impl<'a> ForgeModules<'a> { ); functions_to_scan.push( - Entrypoints { + Entrypoint { function: customfield.resolver.function, invokable: true, web_trigger: false, @@ -428,7 +428,7 @@ impl<'a> ForgeModules<'a> { self.ui_modifications.iter().for_each(|ui| { functions_to_scan.push( - Entrypoints { + Entrypoint { function: ui.resolver.function, invokable: true, web_trigger: false, @@ -438,17 +438,25 @@ impl<'a> ForgeModules<'a> { self.workflow_validator.iter().for_each(|validator| { functions_to_scan.push( - Entrypoints { + Entrypoint { + function: validator.function, + invokable: true, + web_trigger: false, + } + ); + + functions_to_scan.push( + Entrypoint { function: validator.resolver.function, invokable: true, web_trigger: false, } - ) + ); }); self.workflow_post_function.iter().for_each(|post_function| { functions_to_scan.push( - Entrypoints { + Entrypoint { function: post_function.function, invokable: true, web_trigger: false, @@ -460,7 +468,7 @@ impl<'a> ForgeModules<'a> { // ie macros has config and export fields on top of resolver fields that are functions for macros in self.macros { if let Some(resolver)= macros.resolver { - functions_to_scan.push(Entrypoints { + functions_to_scan.push(Entrypoint { function: resolver.function, invokable: true, web_trigger: false @@ -468,14 +476,14 @@ impl<'a> ForgeModules<'a> { } if let Some(config)= macros.config { - functions_to_scan.push(Entrypoints { + functions_to_scan.push(Entrypoint { function: config.function, invokable: true, web_trigger: false }) } if let Some(export)= macros.export { - functions_to_scan.push(Entrypoints { + functions_to_scan.push(Entrypoint { function: export.function, invokable: true, web_trigger: false @@ -485,13 +493,13 @@ impl<'a> ForgeModules<'a> { } for contentitem in self.content_by_line_item { - functions_to_scan.push(Entrypoints { + functions_to_scan.push(Entrypoint { function: contentitem.function, invokable: true, web_trigger: false }); if let Some(resolver)= contentitem.resolver { - functions_to_scan.push(Entrypoints { + functions_to_scan.push(Entrypoint { function: resolver.function, invokable: true, web_trigger: false @@ -499,7 +507,7 @@ impl<'a> ForgeModules<'a> { } if let Some(dynamic_properties)= contentitem.dynamic_properties { - functions_to_scan.push(Entrypoints { + functions_to_scan.push(Entrypoint { function: dynamic_properties.function, invokable: true, web_trigger: false @@ -509,13 +517,13 @@ impl<'a> ForgeModules<'a> { } for issue in self.issue_glance { - functions_to_scan.push(Entrypoints { + functions_to_scan.push(Entrypoint { function: issue.function, invokable: true, web_trigger: false }); if let Some(resolver)= issue.resolver { - functions_to_scan.push(Entrypoints { + functions_to_scan.push(Entrypoint { function: resolver.function, invokable: true, web_trigger: false @@ -523,7 +531,7 @@ impl<'a> ForgeModules<'a> { } if let Some(dynamic_properties)= issue.dynamic_properties { - functions_to_scan.push(Entrypoints { + functions_to_scan.push(Entrypoint { function: dynamic_properties.function, invokable: true, web_trigger: false @@ -533,13 +541,13 @@ impl<'a> ForgeModules<'a> { } for access in self.access_import_type { - functions_to_scan.push(Entrypoints { + functions_to_scan.push(Entrypoint { function: access.function, invokable: true, web_trigger: false }); if let Some(delete) = access.one_delete_import { - functions_to_scan.push(Entrypoints { + functions_to_scan.push(Entrypoint { function: delete.function, invokable: true, web_trigger: false @@ -547,7 +555,7 @@ impl<'a> ForgeModules<'a> { } if let Some(start)= access.start_import { - functions_to_scan.push(Entrypoints { + functions_to_scan.push(Entrypoint { function: start.function, invokable: true, web_trigger: false @@ -555,7 +563,7 @@ impl<'a> ForgeModules<'a> { } if let Some(stop)= access.stop_import { - functions_to_scan.push(Entrypoints { + functions_to_scan.push(Entrypoint { function: stop.function, invokable: true, web_trigger: false @@ -563,7 +571,7 @@ impl<'a> ForgeModules<'a> { } if let Some(status)= access.import_status { - functions_to_scan.push(Entrypoints { + functions_to_scan.push(Entrypoint { function: status.function, invokable: true, web_trigger: false @@ -576,7 +584,7 @@ impl<'a> ForgeModules<'a> { // make alternate_functions all user-invokable functions for module in self.extra.into_values().flatten() { if let Some(mod_function) = module.function { - functions_to_scan.push(Entrypoints { + functions_to_scan.push(Entrypoint { function: mod_function, invokable: true, web_trigger: false @@ -584,7 +592,7 @@ impl<'a> ForgeModules<'a> { } if let Some(resolver) = module.resolver { - functions_to_scan.push(Entrypoints { + functions_to_scan.push(Entrypoint { function: resolver.function, invokable: true, web_trigger: false @@ -592,7 +600,7 @@ impl<'a> ForgeModules<'a> { } } - return functions_to_scan; + functions_to_scan } } diff --git a/crates/fsrt/src/main.rs b/crates/fsrt/src/main.rs index d2729304..3a5162fe 100644 --- a/crates/fsrt/src/main.rs +++ b/crates/fsrt/src/main.rs @@ -35,7 +35,7 @@ use forge_analyzer::{ resolver::resolve_calls, }; -use forge_loader::manifest::{ForgeManifest, FunctionRef, FunctionTy, Entrypoints}; +use forge_loader::manifest::{ForgeManifest, FunctionRef, FunctionTy, Entrypoint}; use walkdir::WalkDir; #[derive(Parser, Debug)] @@ -83,7 +83,7 @@ struct ForgeProject { sm: Arc, ctx: AppCtx, env: Environment, - funcs: Vec>, + funcs: Vec>, opts: Opts, } @@ -135,17 +135,20 @@ impl ForgeProject { } } // TODO: edit to work with new iterator that not FUNCTIONTY - fn add_funcs<'a, I: IntoIterator>>(&mut self, iter: I) { - self.funcs.extend(iter.into_iter().flat_map(|ftype| { - ftype.sequence(|(func_name, path)| { - let modid = self.ctx.modid_from_path(&path)?; - let func = self.env.module_export(modid, func_name)?; - Some((func_name.to_owned(), path, modid, func)) - }) - })); + fn add_funcs<'a, I: IntoIterator>>(&mut self, iter: I) { + self.funcs.extend(iter); } } + +// self.funcs.extend(iter.into_iter().flat_map(|ftype| { +// ftype.sequence(|(func_name, path)| { +// let modid = self.ctx.modid_from_path(&path)?; +// let func = self.env.module_export(modid, func_name)?; +// Some((func_name.to_owned(), path, modid, func)) +// }) +// })); + fn is_js_file>(path: P) -> bool { matches!( path.as_ref().extension().map(|s| s.as_bytes()), @@ -188,7 +191,7 @@ fn scan_directory(dir: PathBuf, function: Option<&str>, opts: Opts) -> Result, opts: Opts) -> Result Date: Tue, 19 Sep 2023 11:42:46 -0400 Subject: [PATCH 186/517] updated struct serde features and rust attributes. Updated customField with optional values --- crates/forge_loader/src/manifest.rs | 98 +++++++++++++++++------------ 1 file changed, 57 insertions(+), 41 deletions(-) diff --git a/crates/forge_loader/src/manifest.rs b/crates/forge_loader/src/manifest.rs index 9cbc9efd..d1366765 100644 --- a/crates/forge_loader/src/manifest.rs +++ b/crates/forge_loader/src/manifest.rs @@ -35,7 +35,7 @@ struct ModInfo<'a> { #[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] struct MacroMod<'a> { key: &'a str, - function: &'a str, + function: Option<&'a str>, #[serde(borrow)] resolver: Option>, #[serde(borrow)] @@ -126,11 +126,11 @@ pub struct CustomField<'a> { #[serde(flatten, borrow)] key: &'a str, // all attributes below involve function calls - value: &'a str, - search_suggestion: &'a str, - function: &'a str, - edit: &'a str, - resolver: ModInfo<'a> + value: Option<&'a str>, + search_suggestions: &'a str, + function: Option<&'a str>, + edit: Option<&'a str>, + resolver: Option>, } @@ -151,7 +151,6 @@ pub struct WorkflowValidator<'a> { #[derive(Debug, Clone, PartialEq, Eq, Deserialize)] pub struct WorkflowPostFunction<'a> { - #[serde(flatten, borrow)] key: &'a str, function: &'a str, } @@ -278,7 +277,7 @@ pub enum FunctionTy { } // Struct used for tracking what scan a funtion requires. -#[derive(Debug, Clone, PartialEq, Eq, Deserialize)] +#[derive(Debug, Clone, PartialEq, Eq)] pub struct Entrypoint<'a> { pub function: &'a str, pub invokable: bool, @@ -329,8 +328,8 @@ impl<'a> ForgeModules<'a> { self, ) -> Vec>{ // number of webtriggers are usually low, so it's better to just sort them and reuse - self.webtriggers.iter(). - .sort_unstable_by_key(|trigger| trigger.function); + // self.webtriggers.iter(). + // .sort_unstable_by_key(|trigger| trigger.function); // Get all the Triggers and represent them as a new struct thing where "webtrigger" attribute is true // for all trigger things @@ -387,43 +386,55 @@ impl<'a> ForgeModules<'a> { }); self.custom_field.iter().for_each(|customfield| { - functions_to_scan.push( - Entrypoint { - function: customfield.value, - invokable: true, - web_trigger: false, - } - ); - functions_to_scan.push( - Entrypoint { - function: customfield.search_suggestion, - invokable: true, - web_trigger: false, - } - ); - functions_to_scan.push( - Entrypoint { - function: customfield.function, - invokable: true, - web_trigger: false, - } - ); - functions_to_scan.push( - Entrypoint { - function: customfield.edit, - invokable: true, - web_trigger: false, - } - ); + if let Some(value)= customfield.value { + functions_to_scan.push( + Entrypoint { + function: value, + invokable: true, + web_trigger: false, + } + ); + } + functions_to_scan.push( Entrypoint { - function: customfield.resolver.function, + function: customfield.search_suggestions, invokable: true, web_trigger: false, } ); + if let Some(func)= customfield.function { + functions_to_scan.push( + Entrypoint { + function: func, + invokable: true, + web_trigger: false, + } + ); + } + + if let Some(edit)= customfield.edit{ + functions_to_scan.push( + Entrypoint { + function: edit, + invokable: true, + web_trigger: false, + } + ); + } + + if let Some(resolver)= &customfield.resolver { + functions_to_scan.push( + Entrypoint { + function: resolver.function, + invokable: true, + web_trigger: false, + } + ); + } + }); self.ui_modifications.iter().for_each(|ui| { @@ -717,7 +728,7 @@ mod tests { assert_eq!(manifest.app.id, "my-app"); assert_eq!(manifest.modules.macros.len(), 1); assert_eq!(manifest.modules.macros[0].key, "My Macro"); - assert_eq!(manifest.modules.macros[0].function, "my-macro"); + // assert_eq!(manifest.modules.macros[0].function, "my-macro"); assert_eq!(manifest.modules.functions.len(), 1); assert_eq!( manifest.modules.functions[0], @@ -810,7 +821,12 @@ mod tests { }"#; let manifest: ForgeManifest = serde_json::from_str(json).unwrap(); assert_eq!(manifest.modules.macros.len(), 1); - assert_eq!(manifest.modules.macros[0].function, "Catch-me-if-you-can0"); + + if let Some(func) = manifest.modules.macros[0].function { + assert_eq!(func, "Catch-me-if-you-can0"); + + } + if let Some(func) = &manifest.modules.macros[0].resolver { assert_eq!(func.function, "Catch-me-if-you-can1"); From 8602eecb799fd966b0975e51ed77e76d60c7f8f7 Mon Sep 17 00:00:00 2001 From: awang7 Date: Tue, 19 Sep 2023 11:42:46 -0400 Subject: [PATCH 187/517] updated struct serde features and rust attributes. Updated customField with optional values --- crates/forge_loader/src/manifest.rs | 98 +++++++++++++++++------------ 1 file changed, 57 insertions(+), 41 deletions(-) diff --git a/crates/forge_loader/src/manifest.rs b/crates/forge_loader/src/manifest.rs index 9cbc9efd..d1366765 100644 --- a/crates/forge_loader/src/manifest.rs +++ b/crates/forge_loader/src/manifest.rs @@ -35,7 +35,7 @@ struct ModInfo<'a> { #[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] struct MacroMod<'a> { key: &'a str, - function: &'a str, + function: Option<&'a str>, #[serde(borrow)] resolver: Option>, #[serde(borrow)] @@ -126,11 +126,11 @@ pub struct CustomField<'a> { #[serde(flatten, borrow)] key: &'a str, // all attributes below involve function calls - value: &'a str, - search_suggestion: &'a str, - function: &'a str, - edit: &'a str, - resolver: ModInfo<'a> + value: Option<&'a str>, + search_suggestions: &'a str, + function: Option<&'a str>, + edit: Option<&'a str>, + resolver: Option>, } @@ -151,7 +151,6 @@ pub struct WorkflowValidator<'a> { #[derive(Debug, Clone, PartialEq, Eq, Deserialize)] pub struct WorkflowPostFunction<'a> { - #[serde(flatten, borrow)] key: &'a str, function: &'a str, } @@ -278,7 +277,7 @@ pub enum FunctionTy { } // Struct used for tracking what scan a funtion requires. -#[derive(Debug, Clone, PartialEq, Eq, Deserialize)] +#[derive(Debug, Clone, PartialEq, Eq)] pub struct Entrypoint<'a> { pub function: &'a str, pub invokable: bool, @@ -329,8 +328,8 @@ impl<'a> ForgeModules<'a> { self, ) -> Vec>{ // number of webtriggers are usually low, so it's better to just sort them and reuse - self.webtriggers.iter(). - .sort_unstable_by_key(|trigger| trigger.function); + // self.webtriggers.iter(). + // .sort_unstable_by_key(|trigger| trigger.function); // Get all the Triggers and represent them as a new struct thing where "webtrigger" attribute is true // for all trigger things @@ -387,43 +386,55 @@ impl<'a> ForgeModules<'a> { }); self.custom_field.iter().for_each(|customfield| { - functions_to_scan.push( - Entrypoint { - function: customfield.value, - invokable: true, - web_trigger: false, - } - ); - functions_to_scan.push( - Entrypoint { - function: customfield.search_suggestion, - invokable: true, - web_trigger: false, - } - ); - functions_to_scan.push( - Entrypoint { - function: customfield.function, - invokable: true, - web_trigger: false, - } - ); - functions_to_scan.push( - Entrypoint { - function: customfield.edit, - invokable: true, - web_trigger: false, - } - ); + if let Some(value)= customfield.value { + functions_to_scan.push( + Entrypoint { + function: value, + invokable: true, + web_trigger: false, + } + ); + } + functions_to_scan.push( Entrypoint { - function: customfield.resolver.function, + function: customfield.search_suggestions, invokable: true, web_trigger: false, } ); + if let Some(func)= customfield.function { + functions_to_scan.push( + Entrypoint { + function: func, + invokable: true, + web_trigger: false, + } + ); + } + + if let Some(edit)= customfield.edit{ + functions_to_scan.push( + Entrypoint { + function: edit, + invokable: true, + web_trigger: false, + } + ); + } + + if let Some(resolver)= &customfield.resolver { + functions_to_scan.push( + Entrypoint { + function: resolver.function, + invokable: true, + web_trigger: false, + } + ); + } + }); self.ui_modifications.iter().for_each(|ui| { @@ -717,7 +728,7 @@ mod tests { assert_eq!(manifest.app.id, "my-app"); assert_eq!(manifest.modules.macros.len(), 1); assert_eq!(manifest.modules.macros[0].key, "My Macro"); - assert_eq!(manifest.modules.macros[0].function, "my-macro"); + // assert_eq!(manifest.modules.macros[0].function, "my-macro"); assert_eq!(manifest.modules.functions.len(), 1); assert_eq!( manifest.modules.functions[0], @@ -810,7 +821,12 @@ mod tests { }"#; let manifest: ForgeManifest = serde_json::from_str(json).unwrap(); assert_eq!(manifest.modules.macros.len(), 1); - assert_eq!(manifest.modules.macros[0].function, "Catch-me-if-you-can0"); + + if let Some(func) = manifest.modules.macros[0].function { + assert_eq!(func, "Catch-me-if-you-can0"); + + } + if let Some(func) = &manifest.modules.macros[0].resolver { assert_eq!(func.function, "Catch-me-if-you-can1"); From 7e86f464498428c625740f80f511ec999a367f24 Mon Sep 17 00:00:00 2001 From: awang7 Date: Wed, 20 Sep 2023 12:01:46 -0400 Subject: [PATCH 188/517] abstracted structs to use a CommonKeys struct that holds: key, function, and resolver for less code duplication. Updated into_analayzable method to update values based on functions specified in function mod. TODO: Update rest of non trigger modules to update mapping to functions to scan from function mod --- crates/forge_loader/src/manifest.rs | 138 +++++++++++++--------------- crates/fsrt/src/main.rs | 4 +- 2 files changed, 67 insertions(+), 75 deletions(-) diff --git a/crates/forge_loader/src/manifest.rs b/crates/forge_loader/src/manifest.rs index d1366765..e00273a8 100644 --- a/crates/forge_loader/src/manifest.rs +++ b/crates/forge_loader/src/manifest.rs @@ -1,6 +1,6 @@ use std::{ borrow::Borrow, - collections::{BTreeSet, HashSet}, + collections::{HashSet}, hash::Hash, path::{Path, PathBuf}, }; @@ -9,7 +9,7 @@ use crate::Error; use forge_utils::FxHashMap; use itertools::{Either, Itertools}; use serde::Deserialize; -use serde_json::map::Entry; +use std::collections::BTreeMap; use tracing::trace; #[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] @@ -26,57 +26,46 @@ pub struct FunctionMod<'a> { providers: Option>, } -// Modified +// Abstracting away key, function, and resolver into a single struct for reuse whoo! #[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] -struct ModInfo<'a> { +struct CommonKey<'a> { + key: &'a str, function: &'a str, + resolver: Option<&'a str>, + } #[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] struct MacroMod<'a> { - key: &'a str, - function: Option<&'a str>, - #[serde(borrow)] - resolver: Option>, - #[serde(borrow)] - config: Option>, - #[serde(borrow)] - export: Option>, + #[serde(flatten, borrow)] + common_keys: CommonKey<'a>, + config: Option<&'a str>, + export: Option<&'a str>, } #[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] struct ContentByLineItem<'a> { - key: &'a str, - function: &'a str, - #[serde(borrow)] - resolver: Option>, + #[serde(flatten, borrow)] + common_keys: CommonKey<'a>, #[serde(borrow)] - dynamic_properties: Option>, + dynamic_properties: Option<&'a str>, } #[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] struct IssueGlance<'a> { - key: &'a str, - function: &'a str, - #[serde(borrow)] - resolver: Option>, - #[serde(borrow)] - dynamic_properties: Option>, + #[serde(flatten, borrow)] + common_keys: CommonKey<'a>, + dynamic_properties: Option<&'a str>, } - #[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] struct AccessImportType<'a> { - key: &'a str, - function: &'a str, - #[serde(borrow)] - one_delete_import: Option>, - #[serde(borrow)] - start_import: Option>, - #[serde(borrow)] - stop_import: Option>, - #[serde(borrow)] - import_status: Option>, + #[serde(flatten, borrow)] + common_keys: CommonKey<'a>, + one_delete_import: Option<&'a str>, + start_import: Option<&'a str>, + stop_import: Option<&'a str>, + import_status: Option<&'a str>, } @@ -115,44 +104,39 @@ struct ScheduledTrigger<'a> { // compass DataProvider module #[derive(Debug, Clone, PartialEq, Eq, Deserialize)] pub struct DataProvider<'a> { - key: &'a str, #[serde(flatten, borrow)] - callback: ModInfo<'a>, + key: &'a str, + callback: Option<&'a str>, } // Struct for Custom field Module. Check that search suggestion gets read in correctly. #[derive(Debug, Clone, PartialEq, Eq, Deserialize)] -pub struct CustomField<'a> { - #[serde(flatten, borrow)] - key: &'a str, +pub struct CustomField<'a> { // all attributes below involve function calls + #[serde(flatten, borrow)] + common_keys: CommonKey<'a>, value: Option<&'a str>, search_suggestions: &'a str, - function: Option<&'a str>, edit: Option<&'a str>, - resolver: Option>, } #[derive(Debug, Clone, PartialEq, Eq, Deserialize)] pub struct UiModificatons<'a> { - key: &'a str, #[serde(flatten, borrow)] - resolver: ModInfo<'a>, + common_key: CommonKey<'a> } #[derive(Debug, Clone, PartialEq, Eq, Deserialize)] pub struct WorkflowValidator<'a> { #[serde(flatten, borrow)] - key: &'a str, - function: &'a str, - resolver: ModInfo<'a> + common_keys: CommonKey<'a> } #[derive(Debug, Clone, PartialEq, Eq, Deserialize)] pub struct WorkflowPostFunction<'a> { - key: &'a str, - function: &'a str, + #[serde(flatten, borrow)] + common_keys: CommonKey<'a> } // Add more structs here for deserializing forge modules @@ -284,6 +268,7 @@ pub struct Entrypoint<'a> { pub web_trigger: bool, } + // Helper functions that help filter out which functions are what. impl FunctionTy { pub fn map(self, f: impl FnOnce(T) -> O) -> FunctionTy { @@ -325,43 +310,50 @@ impl<'a> ForgeModules<'a> { // TODO: function returns iterator where each item is some specified type. pub fn into_analyzable_functions ( - self, + &mut self, ) -> Vec>{ // number of webtriggers are usually low, so it's better to just sort them and reuse - // self.webtriggers.iter(). - // .sort_unstable_by_key(|trigger| trigger.function); + self.webtriggers.sort_unstable_by_key(|trigger| trigger.function); // Get all the Triggers and represent them as a new struct thing where "webtrigger" attribute is true // for all trigger things - let mut functions_to_scan = Vec::new(); + let mut functions_to_scan = BTreeMap::new(); + + // Get all functions for module from manifest.yml + self.functions.iter().for_each(|func| { + functions_to_scan.insert(func.handler, Entrypoint { + function: func.handler, + invokable: false, + web_trigger: false, + }); + + }); + + self.webtriggers.iter().for_each(|webtriggers| { - functions_to_scan.push( - Entrypoint { - function: webtriggers.function, - invokable: false, - web_trigger: true, - } - ); + if functions_to_scan.contains_key(webtriggers.function) { + if let Some(entry) = functions_to_scan.get_mut(webtriggers.function) { + entry.web_trigger = true; + } + } }); + + self.event_triggers.iter().for_each(|event_triggers| { - functions_to_scan.push( - Entrypoint { - function: event_triggers.raw.function, - invokable: false, - web_trigger: true, - } - ); + if functions_to_scan.contains_key(event_triggers.raw.function) { + if let Some(entry) = functions_to_scan.get_mut(event_triggers.raw.function) { + entry.web_trigger = true; + } + } }); self.scheduled_triggers.iter().for_each(|schedule_triggers| { - functions_to_scan.push( - Entrypoint { - function: schedule_triggers.raw.function, - invokable: false, - web_trigger: true, - } - ) + if functions_to_scan.contains_key(schedule_triggers.raw.function) { + if let Some(entry) = functions_to_scan.get_mut(schedule_triggers.raw.function) { + entry.web_trigger = true; + } + } }); // create arrays representing functions that expose user non-invokable functions diff --git a/crates/fsrt/src/main.rs b/crates/fsrt/src/main.rs index 3a5162fe..b000b24b 100644 --- a/crates/fsrt/src/main.rs +++ b/crates/fsrt/src/main.rs @@ -78,7 +78,7 @@ struct Opts { out: Option, } -struct ForgeProject { +struct ForgeProject<'a> { #[allow(dead_code)] sm: Arc, ctx: AppCtx, @@ -87,7 +87,7 @@ struct ForgeProject { opts: Opts, } -impl ForgeProject { +impl ForgeProject<'_> { #[instrument(skip(src, iter))] fn with_files_and_sourceroot, I: IntoIterator>( src: P, From 3a530a17f79c3ecc0ba1bb37c682bd9749331ebc Mon Sep 17 00:00:00 2001 From: awang7 Date: Wed, 20 Sep 2023 12:01:46 -0400 Subject: [PATCH 189/517] abstracted structs to use a CommonKeys struct that holds: key, function, and resolver for less code duplication. Updated into_analayzable method to update values based on functions specified in function mod. TODO: Update rest of non trigger modules to update mapping to functions to scan from function mod --- crates/forge_loader/src/manifest.rs | 138 +++++++++++++--------------- crates/fsrt/src/main.rs | 4 +- 2 files changed, 67 insertions(+), 75 deletions(-) diff --git a/crates/forge_loader/src/manifest.rs b/crates/forge_loader/src/manifest.rs index d1366765..e00273a8 100644 --- a/crates/forge_loader/src/manifest.rs +++ b/crates/forge_loader/src/manifest.rs @@ -1,6 +1,6 @@ use std::{ borrow::Borrow, - collections::{BTreeSet, HashSet}, + collections::{HashSet}, hash::Hash, path::{Path, PathBuf}, }; @@ -9,7 +9,7 @@ use crate::Error; use forge_utils::FxHashMap; use itertools::{Either, Itertools}; use serde::Deserialize; -use serde_json::map::Entry; +use std::collections::BTreeMap; use tracing::trace; #[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] @@ -26,57 +26,46 @@ pub struct FunctionMod<'a> { providers: Option>, } -// Modified +// Abstracting away key, function, and resolver into a single struct for reuse whoo! #[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] -struct ModInfo<'a> { +struct CommonKey<'a> { + key: &'a str, function: &'a str, + resolver: Option<&'a str>, + } #[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] struct MacroMod<'a> { - key: &'a str, - function: Option<&'a str>, - #[serde(borrow)] - resolver: Option>, - #[serde(borrow)] - config: Option>, - #[serde(borrow)] - export: Option>, + #[serde(flatten, borrow)] + common_keys: CommonKey<'a>, + config: Option<&'a str>, + export: Option<&'a str>, } #[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] struct ContentByLineItem<'a> { - key: &'a str, - function: &'a str, - #[serde(borrow)] - resolver: Option>, + #[serde(flatten, borrow)] + common_keys: CommonKey<'a>, #[serde(borrow)] - dynamic_properties: Option>, + dynamic_properties: Option<&'a str>, } #[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] struct IssueGlance<'a> { - key: &'a str, - function: &'a str, - #[serde(borrow)] - resolver: Option>, - #[serde(borrow)] - dynamic_properties: Option>, + #[serde(flatten, borrow)] + common_keys: CommonKey<'a>, + dynamic_properties: Option<&'a str>, } - #[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] struct AccessImportType<'a> { - key: &'a str, - function: &'a str, - #[serde(borrow)] - one_delete_import: Option>, - #[serde(borrow)] - start_import: Option>, - #[serde(borrow)] - stop_import: Option>, - #[serde(borrow)] - import_status: Option>, + #[serde(flatten, borrow)] + common_keys: CommonKey<'a>, + one_delete_import: Option<&'a str>, + start_import: Option<&'a str>, + stop_import: Option<&'a str>, + import_status: Option<&'a str>, } @@ -115,44 +104,39 @@ struct ScheduledTrigger<'a> { // compass DataProvider module #[derive(Debug, Clone, PartialEq, Eq, Deserialize)] pub struct DataProvider<'a> { - key: &'a str, #[serde(flatten, borrow)] - callback: ModInfo<'a>, + key: &'a str, + callback: Option<&'a str>, } // Struct for Custom field Module. Check that search suggestion gets read in correctly. #[derive(Debug, Clone, PartialEq, Eq, Deserialize)] -pub struct CustomField<'a> { - #[serde(flatten, borrow)] - key: &'a str, +pub struct CustomField<'a> { // all attributes below involve function calls + #[serde(flatten, borrow)] + common_keys: CommonKey<'a>, value: Option<&'a str>, search_suggestions: &'a str, - function: Option<&'a str>, edit: Option<&'a str>, - resolver: Option>, } #[derive(Debug, Clone, PartialEq, Eq, Deserialize)] pub struct UiModificatons<'a> { - key: &'a str, #[serde(flatten, borrow)] - resolver: ModInfo<'a>, + common_key: CommonKey<'a> } #[derive(Debug, Clone, PartialEq, Eq, Deserialize)] pub struct WorkflowValidator<'a> { #[serde(flatten, borrow)] - key: &'a str, - function: &'a str, - resolver: ModInfo<'a> + common_keys: CommonKey<'a> } #[derive(Debug, Clone, PartialEq, Eq, Deserialize)] pub struct WorkflowPostFunction<'a> { - key: &'a str, - function: &'a str, + #[serde(flatten, borrow)] + common_keys: CommonKey<'a> } // Add more structs here for deserializing forge modules @@ -284,6 +268,7 @@ pub struct Entrypoint<'a> { pub web_trigger: bool, } + // Helper functions that help filter out which functions are what. impl FunctionTy { pub fn map(self, f: impl FnOnce(T) -> O) -> FunctionTy { @@ -325,43 +310,50 @@ impl<'a> ForgeModules<'a> { // TODO: function returns iterator where each item is some specified type. pub fn into_analyzable_functions ( - self, + &mut self, ) -> Vec>{ // number of webtriggers are usually low, so it's better to just sort them and reuse - // self.webtriggers.iter(). - // .sort_unstable_by_key(|trigger| trigger.function); + self.webtriggers.sort_unstable_by_key(|trigger| trigger.function); // Get all the Triggers and represent them as a new struct thing where "webtrigger" attribute is true // for all trigger things - let mut functions_to_scan = Vec::new(); + let mut functions_to_scan = BTreeMap::new(); + + // Get all functions for module from manifest.yml + self.functions.iter().for_each(|func| { + functions_to_scan.insert(func.handler, Entrypoint { + function: func.handler, + invokable: false, + web_trigger: false, + }); + + }); + + self.webtriggers.iter().for_each(|webtriggers| { - functions_to_scan.push( - Entrypoint { - function: webtriggers.function, - invokable: false, - web_trigger: true, - } - ); + if functions_to_scan.contains_key(webtriggers.function) { + if let Some(entry) = functions_to_scan.get_mut(webtriggers.function) { + entry.web_trigger = true; + } + } }); + + self.event_triggers.iter().for_each(|event_triggers| { - functions_to_scan.push( - Entrypoint { - function: event_triggers.raw.function, - invokable: false, - web_trigger: true, - } - ); + if functions_to_scan.contains_key(event_triggers.raw.function) { + if let Some(entry) = functions_to_scan.get_mut(event_triggers.raw.function) { + entry.web_trigger = true; + } + } }); self.scheduled_triggers.iter().for_each(|schedule_triggers| { - functions_to_scan.push( - Entrypoint { - function: schedule_triggers.raw.function, - invokable: false, - web_trigger: true, - } - ) + if functions_to_scan.contains_key(schedule_triggers.raw.function) { + if let Some(entry) = functions_to_scan.get_mut(schedule_triggers.raw.function) { + entry.web_trigger = true; + } + } }); // create arrays representing functions that expose user non-invokable functions diff --git a/crates/fsrt/src/main.rs b/crates/fsrt/src/main.rs index 3a5162fe..b000b24b 100644 --- a/crates/fsrt/src/main.rs +++ b/crates/fsrt/src/main.rs @@ -78,7 +78,7 @@ struct Opts { out: Option, } -struct ForgeProject { +struct ForgeProject<'a> { #[allow(dead_code)] sm: Arc, ctx: AppCtx, @@ -87,7 +87,7 @@ struct ForgeProject { opts: Opts, } -impl ForgeProject { +impl ForgeProject<'_> { #[instrument(skip(src, iter))] fn with_files_and_sourceroot, I: IntoIterator>( src: P, From 24d99b2f1817d662961b31cf9d238e26e8a6b17d Mon Sep 17 00:00:00 2001 From: awang7 Date: Wed, 20 Sep 2023 17:59:39 -0400 Subject: [PATCH 190/517] finished updating methods to check functions_to_scan and update entrypoint struct when needed. TODO: test and toggle function call in main.rs --- crates/forge_loader/src/manifest.rs | 331 ++++++++++++---------------- crates/fsrt/src/main.rs | 4 +- 2 files changed, 142 insertions(+), 193 deletions(-) diff --git a/crates/forge_loader/src/manifest.rs b/crates/forge_loader/src/manifest.rs index e00273a8..060e383b 100644 --- a/crates/forge_loader/src/manifest.rs +++ b/crates/forge_loader/src/manifest.rs @@ -124,7 +124,7 @@ pub struct CustomField<'a> { #[derive(Debug, Clone, PartialEq, Eq, Deserialize)] pub struct UiModificatons<'a> { #[serde(flatten, borrow)] - common_key: CommonKey<'a> + common_keys: CommonKey<'a> } #[derive(Debug, Clone, PartialEq, Eq, Deserialize)] @@ -311,7 +311,7 @@ impl<'a> ForgeModules<'a> { // TODO: function returns iterator where each item is some specified type. pub fn into_analyzable_functions ( &mut self, - ) -> Vec>{ + ) -> BTreeMap<&'a str, Entrypoint<'a>>{ // number of webtriggers are usually low, so it's better to just sort them and reuse self.webtriggers.sort_unstable_by_key(|trigger| trigger.function); @@ -322,8 +322,8 @@ impl<'a> ForgeModules<'a> { // Get all functions for module from manifest.yml self.functions.iter().for_each(|func| { - functions_to_scan.insert(func.handler, Entrypoint { - function: func.handler, + functions_to_scan.insert(func.key, Entrypoint { + function: func.key, invokable: false, web_trigger: false, }); @@ -357,249 +357,202 @@ impl<'a> ForgeModules<'a> { }); // create arrays representing functions that expose user non-invokable functions - self.consumers.iter().for_each(|consumers| { - functions_to_scan.push( - Entrypoint { - function: consumers.resolver.function, - invokable: true, - web_trigger: false, - } - ) + self.consumers.iter().for_each(|consumer| { + if functions_to_scan.contains_key(consumer.resolver.function) { + if let Some(entry) = functions_to_scan.get_mut(consumer.resolver.function) { + entry.invokable = true; + } + } }); self.data_provider.iter().for_each(|dataprovider| { - functions_to_scan.push( - Entrypoint { - function: dataprovider.callback.function, - invokable: true, - web_trigger: false, - } - ) + if let Some(call) = dataprovider.callback { + if let Some(entry) = functions_to_scan.get_mut(call) { + entry.invokable = true; + } + } + }); self.custom_field.iter().for_each(|customfield| { - if let Some(value)= customfield.value { - functions_to_scan.push( - Entrypoint { - function: value, - invokable: true, - web_trigger: false, - } - ); + if let Some(entry) = functions_to_scan.get_mut(value) { + entry.invokable = true; + } } - functions_to_scan.push( - Entrypoint { - function: customfield.search_suggestions, - invokable: true, - web_trigger: false, - } - ); - - if let Some(func)= customfield.function { - functions_to_scan.push( - Entrypoint { - function: func, - invokable: true, - web_trigger: false, - } - ); + if let Some(entry) = functions_to_scan.get_mut(customfield.search_suggestions) { + entry.invokable = true; + } + + if let Some(entry) = functions_to_scan.get_mut(customfield.common_keys.function) { + entry.invokable = true; + } + + if let Some(resolver) = customfield.common_keys.resolver { + if let Some(entry) = functions_to_scan.get_mut(resolver) { + entry.invokable = true; + } } - if let Some(edit)= customfield.edit{ - functions_to_scan.push( - Entrypoint { - function: edit, - invokable: true, - web_trigger: false, - } - ); - } + if let Some(edit) = customfield.edit { + if let Some(entry) = functions_to_scan.get_mut(edit) { + entry.invokable = true; + } - if let Some(resolver)= &customfield.resolver { - functions_to_scan.push( - Entrypoint { - function: resolver.function, - invokable: true, - web_trigger: false, - } - ); } }); self.ui_modifications.iter().for_each(|ui| { - functions_to_scan.push( - Entrypoint { - function: ui.resolver.function, - invokable: true, - web_trigger: false, + if let Some(entry) = functions_to_scan.get_mut(ui.common_keys.function) { + entry.invokable = true; + } + + if let Some(resolver) = ui.common_keys.resolver { + if let Some(entry) = functions_to_scan.get_mut(resolver) { + entry.invokable = true; } - ) + } }); self.workflow_validator.iter().for_each(|validator| { - functions_to_scan.push( - Entrypoint { - function: validator.function, - invokable: true, - web_trigger: false, - } - ); + if let Some(entry) = functions_to_scan.get_mut(validator.common_keys.function) { + entry.invokable = true; + } - functions_to_scan.push( - Entrypoint { - function: validator.resolver.function, - invokable: true, - web_trigger: false, + if let Some(resolver) = validator.common_keys.resolver { + if let Some(entry) = functions_to_scan.get_mut(resolver) { + entry.invokable = true; } - ); + } }); self.workflow_post_function.iter().for_each(|post_function| { - functions_to_scan.push( - Entrypoint { - function: post_function.function, - invokable: true, - web_trigger: false, + if let Some(entry) = functions_to_scan.get_mut(post_function.common_keys.function) { + entry.invokable = true; + } + + if let Some(resolver) = post_function.common_keys.resolver { + if let Some(entry) = functions_to_scan.get_mut(resolver) { + entry.invokable = true; } - ) + } }); // get user invokable modules that have additional exposure endpoints. // ie macros has config and export fields on top of resolver fields that are functions - for macros in self.macros { - if let Some(resolver)= macros.resolver { - functions_to_scan.push(Entrypoint { - function: resolver.function, - invokable: true, - web_trigger: false - }) + self.macros.iter().for_each(|macros| { + if let Some(resolver)= macros.common_keys.resolver { + if let Some(entry) = functions_to_scan.get_mut(resolver) { + entry.invokable = true; + } } if let Some(config)= macros.config { - functions_to_scan.push(Entrypoint { - function: config.function, - invokable: true, - web_trigger: false - }) + if let Some(entry) = functions_to_scan.get_mut(config) { + entry.invokable = true; + } } + if let Some(export)= macros.export { - functions_to_scan.push(Entrypoint { - function: export.function, - invokable: true, - web_trigger: false - }) + if let Some(entry) = functions_to_scan.get_mut(export) { + entry.invokable = true; + } } - } + }); - for contentitem in self.content_by_line_item { - functions_to_scan.push(Entrypoint { - function: contentitem.function, - invokable: true, - web_trigger: false - }); - if let Some(resolver)= contentitem.resolver { - functions_to_scan.push(Entrypoint { - function: resolver.function, - invokable: true, - web_trigger: false - }) + self.content_by_line_item.iter().for_each(|contentitem| { + if let Some(entry) = functions_to_scan.get_mut(contentitem.common_keys.function) { + entry.invokable = true; + } + + + if let Some(resolver)= contentitem.common_keys.resolver { + if let Some(entry) = functions_to_scan.get_mut(resolver) { + entry.invokable = true; + } } if let Some(dynamic_properties)= contentitem.dynamic_properties { - functions_to_scan.push(Entrypoint { - function: dynamic_properties.function, - invokable: true, - web_trigger: false - }) + if let Some(entry) = functions_to_scan.get_mut(dynamic_properties) { + entry.invokable = true; + } } - } + }); - for issue in self.issue_glance { - functions_to_scan.push(Entrypoint { - function: issue.function, - invokable: true, - web_trigger: false - }); - if let Some(resolver)= issue.resolver { - functions_to_scan.push(Entrypoint { - function: resolver.function, - invokable: true, - web_trigger: false - }) + self.issue_glance.iter().for_each(|issue| { + if let Some(entry) = functions_to_scan.get_mut(issue.common_keys.function) { + entry.invokable = true; + } + + + if let Some(resolver)= issue.common_keys.resolver { + if let Some(entry) = functions_to_scan.get_mut(resolver) { + entry.invokable = true; + } } if let Some(dynamic_properties)= issue.dynamic_properties { - functions_to_scan.push(Entrypoint { - function: dynamic_properties.function, - invokable: true, - web_trigger: false - }) + if let Some(entry) = functions_to_scan.get_mut(dynamic_properties) { + entry.invokable = true; + } } - } + }); + + self.access_import_type.iter().for_each(|access| { + if let Some(entry) = functions_to_scan.get_mut(access.common_keys.function) { + entry.invokable = true; + } + + if let Some(resolver) = access.common_keys.resolver { + if let Some(entry) = functions_to_scan.get_mut(resolver) { + entry.invokable = true; + } + } - for access in self.access_import_type { - functions_to_scan.push(Entrypoint { - function: access.function, - invokable: true, - web_trigger: false - }); if let Some(delete) = access.one_delete_import { - functions_to_scan.push(Entrypoint { - function: delete.function, - invokable: true, - web_trigger: false - }) + if let Some(entry) = functions_to_scan.get_mut(delete) { + entry.invokable = true; + } } - if let Some(start)= access.start_import { - functions_to_scan.push(Entrypoint { - function: start.function, - invokable: true, - web_trigger: false - }) + if let Some(start) = access.start_import { + if let Some(entry) = functions_to_scan.get_mut(start) { + entry.invokable = true; + } } - if let Some(stop)= access.stop_import { - functions_to_scan.push(Entrypoint { - function: stop.function, - invokable: true, - web_trigger: false - }) + if let Some(stop) = access.stop_import { + if let Some(entry) = functions_to_scan.get_mut(stop) { + entry.invokable = true; + } } if let Some(status)= access.import_status { - functions_to_scan.push(Entrypoint { - function: status.function, - invokable: true, - web_trigger: false - }) + if let Some(entry) = functions_to_scan.get_mut(status) { + entry.invokable = true; + } } - } + }); // get array for user invokable module functions // make alternate_functions all user-invokable functions - for module in self.extra.into_values().flatten() { + for module in self.extra.clone().into_values().flatten() { if let Some(mod_function) = module.function { - functions_to_scan.push(Entrypoint { - function: mod_function, - invokable: true, - web_trigger: false - }); + if let Some(entry) = functions_to_scan.get_mut(mod_function) { + entry.invokable = true; + } } if let Some(resolver) = module.resolver { - functions_to_scan.push(Entrypoint { - function: resolver.function, - invokable: true, - web_trigger: false - }); + if let Some(entry) = functions_to_scan.get_mut(resolver.function) { + entry.invokable = true; + } } } @@ -719,7 +672,7 @@ mod tests { assert_eq!(manifest.app.name, Some("My App")); assert_eq!(manifest.app.id, "my-app"); assert_eq!(manifest.modules.macros.len(), 1); - assert_eq!(manifest.modules.macros[0].key, "My Macro"); + assert_eq!(manifest.modules.macros[0].common_keys.key, "My Macro"); // assert_eq!(manifest.modules.macros[0].function, "my-macro"); assert_eq!(manifest.modules.functions.len(), 1); assert_eq!( @@ -813,25 +766,21 @@ mod tests { }"#; let manifest: ForgeManifest = serde_json::from_str(json).unwrap(); assert_eq!(manifest.modules.macros.len(), 1); - - if let Some(func) = manifest.modules.macros[0].function { - assert_eq!(func, "Catch-me-if-you-can0"); - - } + assert_eq!(manifest.modules.macros[0].common_keys.function, "Catch-me-if-you-can0"); - if let Some(func) = &manifest.modules.macros[0].resolver { - assert_eq!(func.function, "Catch-me-if-you-can1"); + if let Some(func) = manifest.modules.macros[0].common_keys.resolver { + assert_eq!(func, "Catch-me-if-you-can1"); } - if let Some(func) = &manifest.modules.macros[0].config { - assert_eq!(func.function, "Catch-me-if-you-can2"); + if let Some(func) = manifest.modules.macros[0].config { + assert_eq!(func, "Catch-me-if-you-can2"); } - if let Some(func) = &manifest.modules.macros[0].export { - assert_eq!(func.function, "Catch-me-if-you-can3"); + if let Some(func) = manifest.modules.macros[0].export { + assert_eq!(func, "Catch-me-if-you-can3"); } diff --git a/crates/fsrt/src/main.rs b/crates/fsrt/src/main.rs index b000b24b..fb4e5df0 100644 --- a/crates/fsrt/src/main.rs +++ b/crates/fsrt/src/main.rs @@ -1,6 +1,6 @@ #![allow(clippy::type_complexity)] use std::{ - collections::HashSet, + collections::{HashSet, BTreeMap, hash_map::Entry}, convert::TryFrom, fs, io::{self, BufReader}, @@ -83,7 +83,7 @@ struct ForgeProject<'a> { sm: Arc, ctx: AppCtx, env: Environment, - funcs: Vec>, + funcs:BTreeMap<&'a str, Entrypoint<'a>>, opts: Opts, } From ef373f7e7a1f2be0523485884552ef37681a98f7 Mon Sep 17 00:00:00 2001 From: awang7 Date: Wed, 20 Sep 2023 17:59:39 -0400 Subject: [PATCH 191/517] finished updating methods to check functions_to_scan and update entrypoint struct when needed. TODO: test and toggle function call in main.rs --- crates/forge_loader/src/manifest.rs | 331 ++++++++++++---------------- crates/fsrt/src/main.rs | 4 +- 2 files changed, 142 insertions(+), 193 deletions(-) diff --git a/crates/forge_loader/src/manifest.rs b/crates/forge_loader/src/manifest.rs index e00273a8..060e383b 100644 --- a/crates/forge_loader/src/manifest.rs +++ b/crates/forge_loader/src/manifest.rs @@ -124,7 +124,7 @@ pub struct CustomField<'a> { #[derive(Debug, Clone, PartialEq, Eq, Deserialize)] pub struct UiModificatons<'a> { #[serde(flatten, borrow)] - common_key: CommonKey<'a> + common_keys: CommonKey<'a> } #[derive(Debug, Clone, PartialEq, Eq, Deserialize)] @@ -311,7 +311,7 @@ impl<'a> ForgeModules<'a> { // TODO: function returns iterator where each item is some specified type. pub fn into_analyzable_functions ( &mut self, - ) -> Vec>{ + ) -> BTreeMap<&'a str, Entrypoint<'a>>{ // number of webtriggers are usually low, so it's better to just sort them and reuse self.webtriggers.sort_unstable_by_key(|trigger| trigger.function); @@ -322,8 +322,8 @@ impl<'a> ForgeModules<'a> { // Get all functions for module from manifest.yml self.functions.iter().for_each(|func| { - functions_to_scan.insert(func.handler, Entrypoint { - function: func.handler, + functions_to_scan.insert(func.key, Entrypoint { + function: func.key, invokable: false, web_trigger: false, }); @@ -357,249 +357,202 @@ impl<'a> ForgeModules<'a> { }); // create arrays representing functions that expose user non-invokable functions - self.consumers.iter().for_each(|consumers| { - functions_to_scan.push( - Entrypoint { - function: consumers.resolver.function, - invokable: true, - web_trigger: false, - } - ) + self.consumers.iter().for_each(|consumer| { + if functions_to_scan.contains_key(consumer.resolver.function) { + if let Some(entry) = functions_to_scan.get_mut(consumer.resolver.function) { + entry.invokable = true; + } + } }); self.data_provider.iter().for_each(|dataprovider| { - functions_to_scan.push( - Entrypoint { - function: dataprovider.callback.function, - invokable: true, - web_trigger: false, - } - ) + if let Some(call) = dataprovider.callback { + if let Some(entry) = functions_to_scan.get_mut(call) { + entry.invokable = true; + } + } + }); self.custom_field.iter().for_each(|customfield| { - if let Some(value)= customfield.value { - functions_to_scan.push( - Entrypoint { - function: value, - invokable: true, - web_trigger: false, - } - ); + if let Some(entry) = functions_to_scan.get_mut(value) { + entry.invokable = true; + } } - functions_to_scan.push( - Entrypoint { - function: customfield.search_suggestions, - invokable: true, - web_trigger: false, - } - ); - - if let Some(func)= customfield.function { - functions_to_scan.push( - Entrypoint { - function: func, - invokable: true, - web_trigger: false, - } - ); + if let Some(entry) = functions_to_scan.get_mut(customfield.search_suggestions) { + entry.invokable = true; + } + + if let Some(entry) = functions_to_scan.get_mut(customfield.common_keys.function) { + entry.invokable = true; + } + + if let Some(resolver) = customfield.common_keys.resolver { + if let Some(entry) = functions_to_scan.get_mut(resolver) { + entry.invokable = true; + } } - if let Some(edit)= customfield.edit{ - functions_to_scan.push( - Entrypoint { - function: edit, - invokable: true, - web_trigger: false, - } - ); - } + if let Some(edit) = customfield.edit { + if let Some(entry) = functions_to_scan.get_mut(edit) { + entry.invokable = true; + } - if let Some(resolver)= &customfield.resolver { - functions_to_scan.push( - Entrypoint { - function: resolver.function, - invokable: true, - web_trigger: false, - } - ); } }); self.ui_modifications.iter().for_each(|ui| { - functions_to_scan.push( - Entrypoint { - function: ui.resolver.function, - invokable: true, - web_trigger: false, + if let Some(entry) = functions_to_scan.get_mut(ui.common_keys.function) { + entry.invokable = true; + } + + if let Some(resolver) = ui.common_keys.resolver { + if let Some(entry) = functions_to_scan.get_mut(resolver) { + entry.invokable = true; } - ) + } }); self.workflow_validator.iter().for_each(|validator| { - functions_to_scan.push( - Entrypoint { - function: validator.function, - invokable: true, - web_trigger: false, - } - ); + if let Some(entry) = functions_to_scan.get_mut(validator.common_keys.function) { + entry.invokable = true; + } - functions_to_scan.push( - Entrypoint { - function: validator.resolver.function, - invokable: true, - web_trigger: false, + if let Some(resolver) = validator.common_keys.resolver { + if let Some(entry) = functions_to_scan.get_mut(resolver) { + entry.invokable = true; } - ); + } }); self.workflow_post_function.iter().for_each(|post_function| { - functions_to_scan.push( - Entrypoint { - function: post_function.function, - invokable: true, - web_trigger: false, + if let Some(entry) = functions_to_scan.get_mut(post_function.common_keys.function) { + entry.invokable = true; + } + + if let Some(resolver) = post_function.common_keys.resolver { + if let Some(entry) = functions_to_scan.get_mut(resolver) { + entry.invokable = true; } - ) + } }); // get user invokable modules that have additional exposure endpoints. // ie macros has config and export fields on top of resolver fields that are functions - for macros in self.macros { - if let Some(resolver)= macros.resolver { - functions_to_scan.push(Entrypoint { - function: resolver.function, - invokable: true, - web_trigger: false - }) + self.macros.iter().for_each(|macros| { + if let Some(resolver)= macros.common_keys.resolver { + if let Some(entry) = functions_to_scan.get_mut(resolver) { + entry.invokable = true; + } } if let Some(config)= macros.config { - functions_to_scan.push(Entrypoint { - function: config.function, - invokable: true, - web_trigger: false - }) + if let Some(entry) = functions_to_scan.get_mut(config) { + entry.invokable = true; + } } + if let Some(export)= macros.export { - functions_to_scan.push(Entrypoint { - function: export.function, - invokable: true, - web_trigger: false - }) + if let Some(entry) = functions_to_scan.get_mut(export) { + entry.invokable = true; + } } - } + }); - for contentitem in self.content_by_line_item { - functions_to_scan.push(Entrypoint { - function: contentitem.function, - invokable: true, - web_trigger: false - }); - if let Some(resolver)= contentitem.resolver { - functions_to_scan.push(Entrypoint { - function: resolver.function, - invokable: true, - web_trigger: false - }) + self.content_by_line_item.iter().for_each(|contentitem| { + if let Some(entry) = functions_to_scan.get_mut(contentitem.common_keys.function) { + entry.invokable = true; + } + + + if let Some(resolver)= contentitem.common_keys.resolver { + if let Some(entry) = functions_to_scan.get_mut(resolver) { + entry.invokable = true; + } } if let Some(dynamic_properties)= contentitem.dynamic_properties { - functions_to_scan.push(Entrypoint { - function: dynamic_properties.function, - invokable: true, - web_trigger: false - }) + if let Some(entry) = functions_to_scan.get_mut(dynamic_properties) { + entry.invokable = true; + } } - } + }); - for issue in self.issue_glance { - functions_to_scan.push(Entrypoint { - function: issue.function, - invokable: true, - web_trigger: false - }); - if let Some(resolver)= issue.resolver { - functions_to_scan.push(Entrypoint { - function: resolver.function, - invokable: true, - web_trigger: false - }) + self.issue_glance.iter().for_each(|issue| { + if let Some(entry) = functions_to_scan.get_mut(issue.common_keys.function) { + entry.invokable = true; + } + + + if let Some(resolver)= issue.common_keys.resolver { + if let Some(entry) = functions_to_scan.get_mut(resolver) { + entry.invokable = true; + } } if let Some(dynamic_properties)= issue.dynamic_properties { - functions_to_scan.push(Entrypoint { - function: dynamic_properties.function, - invokable: true, - web_trigger: false - }) + if let Some(entry) = functions_to_scan.get_mut(dynamic_properties) { + entry.invokable = true; + } } - } + }); + + self.access_import_type.iter().for_each(|access| { + if let Some(entry) = functions_to_scan.get_mut(access.common_keys.function) { + entry.invokable = true; + } + + if let Some(resolver) = access.common_keys.resolver { + if let Some(entry) = functions_to_scan.get_mut(resolver) { + entry.invokable = true; + } + } - for access in self.access_import_type { - functions_to_scan.push(Entrypoint { - function: access.function, - invokable: true, - web_trigger: false - }); if let Some(delete) = access.one_delete_import { - functions_to_scan.push(Entrypoint { - function: delete.function, - invokable: true, - web_trigger: false - }) + if let Some(entry) = functions_to_scan.get_mut(delete) { + entry.invokable = true; + } } - if let Some(start)= access.start_import { - functions_to_scan.push(Entrypoint { - function: start.function, - invokable: true, - web_trigger: false - }) + if let Some(start) = access.start_import { + if let Some(entry) = functions_to_scan.get_mut(start) { + entry.invokable = true; + } } - if let Some(stop)= access.stop_import { - functions_to_scan.push(Entrypoint { - function: stop.function, - invokable: true, - web_trigger: false - }) + if let Some(stop) = access.stop_import { + if let Some(entry) = functions_to_scan.get_mut(stop) { + entry.invokable = true; + } } if let Some(status)= access.import_status { - functions_to_scan.push(Entrypoint { - function: status.function, - invokable: true, - web_trigger: false - }) + if let Some(entry) = functions_to_scan.get_mut(status) { + entry.invokable = true; + } } - } + }); // get array for user invokable module functions // make alternate_functions all user-invokable functions - for module in self.extra.into_values().flatten() { + for module in self.extra.clone().into_values().flatten() { if let Some(mod_function) = module.function { - functions_to_scan.push(Entrypoint { - function: mod_function, - invokable: true, - web_trigger: false - }); + if let Some(entry) = functions_to_scan.get_mut(mod_function) { + entry.invokable = true; + } } if let Some(resolver) = module.resolver { - functions_to_scan.push(Entrypoint { - function: resolver.function, - invokable: true, - web_trigger: false - }); + if let Some(entry) = functions_to_scan.get_mut(resolver.function) { + entry.invokable = true; + } } } @@ -719,7 +672,7 @@ mod tests { assert_eq!(manifest.app.name, Some("My App")); assert_eq!(manifest.app.id, "my-app"); assert_eq!(manifest.modules.macros.len(), 1); - assert_eq!(manifest.modules.macros[0].key, "My Macro"); + assert_eq!(manifest.modules.macros[0].common_keys.key, "My Macro"); // assert_eq!(manifest.modules.macros[0].function, "my-macro"); assert_eq!(manifest.modules.functions.len(), 1); assert_eq!( @@ -813,25 +766,21 @@ mod tests { }"#; let manifest: ForgeManifest = serde_json::from_str(json).unwrap(); assert_eq!(manifest.modules.macros.len(), 1); - - if let Some(func) = manifest.modules.macros[0].function { - assert_eq!(func, "Catch-me-if-you-can0"); - - } + assert_eq!(manifest.modules.macros[0].common_keys.function, "Catch-me-if-you-can0"); - if let Some(func) = &manifest.modules.macros[0].resolver { - assert_eq!(func.function, "Catch-me-if-you-can1"); + if let Some(func) = manifest.modules.macros[0].common_keys.resolver { + assert_eq!(func, "Catch-me-if-you-can1"); } - if let Some(func) = &manifest.modules.macros[0].config { - assert_eq!(func.function, "Catch-me-if-you-can2"); + if let Some(func) = manifest.modules.macros[0].config { + assert_eq!(func, "Catch-me-if-you-can2"); } - if let Some(func) = &manifest.modules.macros[0].export { - assert_eq!(func.function, "Catch-me-if-you-can3"); + if let Some(func) = manifest.modules.macros[0].export { + assert_eq!(func, "Catch-me-if-you-can3"); } diff --git a/crates/fsrt/src/main.rs b/crates/fsrt/src/main.rs index b000b24b..fb4e5df0 100644 --- a/crates/fsrt/src/main.rs +++ b/crates/fsrt/src/main.rs @@ -1,6 +1,6 @@ #![allow(clippy::type_complexity)] use std::{ - collections::HashSet, + collections::{HashSet, BTreeMap, hash_map::Entry}, convert::TryFrom, fs, io::{self, BufReader}, @@ -83,7 +83,7 @@ struct ForgeProject<'a> { sm: Arc, ctx: AppCtx, env: Environment, - funcs: Vec>, + funcs:BTreeMap<&'a str, Entrypoint<'a>>, opts: Opts, } From 7f2098cb1764c41841f200af98d954aefa2e7bc7 Mon Sep 17 00:00:00 2001 From: awang7 Date: Thu, 21 Sep 2023 14:54:28 -0400 Subject: [PATCH 192/517] commented out consumer filter --- crates/forge_loader/src/manifest.rs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/crates/forge_loader/src/manifest.rs b/crates/forge_loader/src/manifest.rs index 060e383b..35dac217 100644 --- a/crates/forge_loader/src/manifest.rs +++ b/crates/forge_loader/src/manifest.rs @@ -357,13 +357,13 @@ impl<'a> ForgeModules<'a> { }); // create arrays representing functions that expose user non-invokable functions - self.consumers.iter().for_each(|consumer| { - if functions_to_scan.contains_key(consumer.resolver.function) { - if let Some(entry) = functions_to_scan.get_mut(consumer.resolver.function) { - entry.invokable = true; - } - } - }); + // self.consumers.iter().for_each(|consumer| { + // if functions_to_scan.contains_key(consumer.resolver.function) { + // if let Some(entry) = functions_to_scan.get_mut(consumer.resolver.function) { + // entry.invokable = true; + // } + // } + // }); self.data_provider.iter().for_each(|dataprovider| { if let Some(call) = dataprovider.callback { From c3e09a198857171d30860622f1edc173c01a0961 Mon Sep 17 00:00:00 2001 From: awang7 Date: Thu, 21 Sep 2023 14:54:28 -0400 Subject: [PATCH 193/517] commented out consumer filter --- crates/forge_loader/src/manifest.rs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/crates/forge_loader/src/manifest.rs b/crates/forge_loader/src/manifest.rs index 060e383b..35dac217 100644 --- a/crates/forge_loader/src/manifest.rs +++ b/crates/forge_loader/src/manifest.rs @@ -357,13 +357,13 @@ impl<'a> ForgeModules<'a> { }); // create arrays representing functions that expose user non-invokable functions - self.consumers.iter().for_each(|consumer| { - if functions_to_scan.contains_key(consumer.resolver.function) { - if let Some(entry) = functions_to_scan.get_mut(consumer.resolver.function) { - entry.invokable = true; - } - } - }); + // self.consumers.iter().for_each(|consumer| { + // if functions_to_scan.contains_key(consumer.resolver.function) { + // if let Some(entry) = functions_to_scan.get_mut(consumer.resolver.function) { + // entry.invokable = true; + // } + // } + // }); self.data_provider.iter().for_each(|dataprovider| { if let Some(call) = dataprovider.callback { From 3b3fb38bbb4ec25b48c9d4b0a3ba8a4af18c1228 Mon Sep 17 00:00:00 2001 From: awang7 Date: Thu, 21 Sep 2023 14:57:12 -0400 Subject: [PATCH 194/517] updated search_suggestion in customfield to be an optional value. --- crates/forge_loader/src/manifest.rs | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/crates/forge_loader/src/manifest.rs b/crates/forge_loader/src/manifest.rs index 35dac217..25b2b0a3 100644 --- a/crates/forge_loader/src/manifest.rs +++ b/crates/forge_loader/src/manifest.rs @@ -2,7 +2,7 @@ use std::{ borrow::Borrow, collections::{HashSet}, hash::Hash, - path::{Path, PathBuf}, + path::{Path, PathBuf}, str::pattern::SearchStep, }; use crate::Error; @@ -116,7 +116,7 @@ pub struct CustomField<'a> { #[serde(flatten, borrow)] common_keys: CommonKey<'a>, value: Option<&'a str>, - search_suggestions: &'a str, + search_suggestions: Option<&'a str>, edit: Option<&'a str>, } @@ -381,8 +381,11 @@ impl<'a> ForgeModules<'a> { } } - if let Some(entry) = functions_to_scan.get_mut(customfield.search_suggestions) { - entry.invokable = true; + if let Some(search) = customfield.search_suggestions { + if let Some(entry) = functions_to_scan.get_mut(search) { + entry.invokable = true; + } + } if let Some(entry) = functions_to_scan.get_mut(customfield.common_keys.function) { From 4df84d01496bcfd05ff9ba930a5f5a8136f19439 Mon Sep 17 00:00:00 2001 From: awang7 Date: Thu, 21 Sep 2023 14:57:12 -0400 Subject: [PATCH 195/517] updated search_suggestion in customfield to be an optional value. --- crates/forge_loader/src/manifest.rs | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/crates/forge_loader/src/manifest.rs b/crates/forge_loader/src/manifest.rs index 35dac217..25b2b0a3 100644 --- a/crates/forge_loader/src/manifest.rs +++ b/crates/forge_loader/src/manifest.rs @@ -2,7 +2,7 @@ use std::{ borrow::Borrow, collections::{HashSet}, hash::Hash, - path::{Path, PathBuf}, + path::{Path, PathBuf}, str::pattern::SearchStep, }; use crate::Error; @@ -116,7 +116,7 @@ pub struct CustomField<'a> { #[serde(flatten, borrow)] common_keys: CommonKey<'a>, value: Option<&'a str>, - search_suggestions: &'a str, + search_suggestions: Option<&'a str>, edit: Option<&'a str>, } @@ -381,8 +381,11 @@ impl<'a> ForgeModules<'a> { } } - if let Some(entry) = functions_to_scan.get_mut(customfield.search_suggestions) { - entry.invokable = true; + if let Some(search) = customfield.search_suggestions { + if let Some(entry) = functions_to_scan.get_mut(search) { + entry.invokable = true; + } + } if let Some(entry) = functions_to_scan.get_mut(customfield.common_keys.function) { From 45e644cc6923dd6bcbd4527969d885c7cb0e2ba7 Mon Sep 17 00:00:00 2001 From: Joshua Wong Date: Tue, 26 Sep 2023 11:56:05 -0500 Subject: [PATCH 196/517] feat: recognize manual authorization for custom fields --- Cargo.lock | 23 +++++++++++++ Cargo.toml | 2 ++ crates/forge_analyzer/Cargo.toml | 1 + crates/forge_analyzer/src/checkers.rs | 36 +++++++++++++++------ crates/forge_analyzer/src/definitions.rs | 32 ++++++++++++++---- crates/forge_analyzer/src/interp.rs | 41 +++++++++++++----------- crates/forge_analyzer/src/ir.rs | 6 ++++ crates/forge_analyzer/src/utils.rs | 9 ++++++ crates/forge_loader/src/manifest.rs | 18 ++++++++++- crates/fsrt/src/main.rs | 1 - 10 files changed, 134 insertions(+), 35 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 8f05dca8..71775572 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -570,6 +570,7 @@ dependencies = [ "serde", "serde_json", "smallvec", + "stacker", "swc_core", "time 0.3.17", "tracing", @@ -1417,6 +1418,15 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "psm" +version = "0.1.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5787f7cda34e3033a72192c018bc5883100330f362ef279a8cbccfce8bb4e874" +dependencies = [ + "cc", +] + [[package]] name = "quote" version = "1.0.32" @@ -1810,6 +1820,19 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" +[[package]] +name = "stacker" +version = "0.1.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c886bd4480155fd3ef527d45e9ac8dd7118a898a46530b7b94c3e21866259fce" +dependencies = [ + "cc", + "cfg-if", + "libc", + "psm", + "winapi", +] + [[package]] name = "static-map-macro" version = "0.2.3" diff --git a/Cargo.toml b/Cargo.toml index 2d22a89e..d744ea1c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,5 +1,6 @@ [workspace] members = ["crates/*"] +resolver = "2" [workspace.package] version = "0.1.0" @@ -16,6 +17,7 @@ num-bigint = { version = "0.4.3" } serde = { version = "1.0.152", features = ["derive"] } serde_json = "1.0.97" serde_yaml = "0.9.17" +stacker = "0.1.15" petgraph = "0.6.2" pretty_assertions = "1.3.0" indexmap = { version = "1.9.2", features = ["std"] } diff --git a/crates/forge_analyzer/Cargo.toml b/crates/forge_analyzer/Cargo.toml index 1f8d123c..73f4d1d3 100644 --- a/crates/forge_analyzer/Cargo.toml +++ b/crates/forge_analyzer/Cargo.toml @@ -19,6 +19,7 @@ petgraph.workspace = true regex.workspace = true serde_json.workspace = true serde.workspace = true +stacker.workspace = true smallvec.workspace = true swc_core.workspace = true time.workspace = true diff --git a/crates/forge_analyzer/src/checkers.rs b/crates/forge_analyzer/src/checkers.rs index 9de7b349..0aee50ec 100644 --- a/crates/forge_analyzer/src/checkers.rs +++ b/crates/forge_analyzer/src/checkers.rs @@ -21,6 +21,7 @@ pub struct AuthorizeDataflow { #[derive(Debug, PartialEq, Eq, Clone, Copy, PartialOrd, Ord)] pub enum AuthorizeState { No, + CustomFieldOnly, Yes, } @@ -62,7 +63,12 @@ impl<'cx> Dataflow<'cx> for AuthorizeDataflow { debug!("authorize intrinsic found"); AuthorizeState::Yes } + Intrinsic::UserFieldAccess => { + debug!("user field access found"); + std::cmp::max(AuthorizeState::CustomFieldOnly, initial_state) + } Intrinsic::Fetch => initial_state, + Intrinsic::ApiCustomField => initial_state, Intrinsic::ApiCall => initial_state, Intrinsic::SafeCall => initial_state, Intrinsic::EnvRead => initial_state, @@ -223,16 +229,24 @@ impl<'cx> Checker<'cx> for AuthZChecker { ControlFlow::Continue(AuthorizeState::Yes) } Intrinsic::Fetch => ControlFlow::Continue(*state), - Intrinsic::ApiCall if *state == AuthorizeState::No => { + Intrinsic::ApiCall if *state != AuthorizeState::Yes => { let vuln = AuthZVuln::new(interp.callstack(), interp.env(), interp.entry()); info!("Found a vuln!"); self.vulns.push(vuln); ControlFlow::Break(()) } - Intrinsic::ApiCall => ControlFlow::Continue(*state), - Intrinsic::SafeCall => ControlFlow::Continue(*state), - Intrinsic::EnvRead => ControlFlow::Continue(*state), - Intrinsic::StorageRead => ControlFlow::Continue(*state), + Intrinsic::ApiCustomField if *state < AuthorizeState::CustomFieldOnly => { + let vuln = AuthZVuln::new(interp.callstack(), interp.env(), interp.entry()); + info!("Found a vuln!"); + self.vulns.push(vuln); + ControlFlow::Break(()) + } + Intrinsic::ApiCall + | Intrinsic::SafeCall + | Intrinsic::EnvRead + | Intrinsic::UserFieldAccess + | Intrinsic::ApiCustomField + | Intrinsic::StorageRead => ControlFlow::Continue(*state), } } } @@ -286,8 +300,10 @@ impl<'cx> Dataflow<'cx> for AuthenticateDataflow { debug!("authenticated"); Authenticated::Yes } - Intrinsic::ApiCall => initial_state, - Intrinsic::SafeCall => initial_state, + Intrinsic::ApiCall + | Intrinsic::ApiCustomField + | Intrinsic::UserFieldAccess + | Intrinsic::SafeCall => initial_state, } } @@ -371,13 +387,15 @@ impl<'cx> Checker<'cx> for AuthenticateChecker { debug!("authenticated"); ControlFlow::Continue(Authenticated::Yes) } - Intrinsic::ApiCall if *state == Authenticated::No => { + Intrinsic::ApiCall | Intrinsic::ApiCustomField if *state == Authenticated::No => { let vuln = AuthNVuln::new(interp.callstack(), interp.env(), interp.entry()); info!("Found a vuln!"); self.vulns.push(vuln); ControlFlow::Break(()) } - Intrinsic::ApiCall => ControlFlow::Continue(*state), + Intrinsic::ApiCall | Intrinsic::UserFieldAccess | Intrinsic::ApiCustomField => { + ControlFlow::Continue(*state) + } Intrinsic::SafeCall => ControlFlow::Continue(*state), } } diff --git a/crates/forge_analyzer/src/definitions.rs b/crates/forge_analyzer/src/definitions.rs index b49bfd64..6623792d 100644 --- a/crates/forge_analyzer/src/definitions.rs +++ b/crates/forge_analyzer/src/definitions.rs @@ -728,6 +728,8 @@ impl<'a> From<&'a Expr> for CalleeRef<'a> { enum ApiCallKind { #[default] Unknown, + Fields, + CustomField, Trivial, Authorize, } @@ -752,6 +754,10 @@ fn classify_api_call(expr: &Expr) -> ApiCallKind { self.kind = self.kind.max(ApiCallKind::Authorize); } else if TRIVIAL.is_match(name) || name.contains("group") { self.kind = self.kind.max(ApiCallKind::Trivial); + } else if name == "/rest/api/3/field" || name == "/rest/api/2/field" { + self.kind = ApiCallKind::Fields; + } else if name.contains("field") { + self.kind = self.kind.max(ApiCallKind::CustomField); } } } @@ -785,20 +791,34 @@ impl<'cx> FunctionAnalyzer<'cx> { match *callee { [PropPath::Unknown((ref name, ..))] if *name == *"fetch" => Some(Intrinsic::Fetch), [PropPath::Def(def), ref authn @ .., PropPath::Static(ref last)] - if *last == *"requestJira" - || *last == *"requestConfluence" - && Some(&ImportKind::Default) - == self.res.as_foreign_import(def, "@forge/api") => + if (*last == *"requestJira" || *last == *"requestConfluence") + && Some(&ImportKind::Default) + == self.res.as_foreign_import(def, "@forge/api") => { let first_arg = first_arg?; + let is_as_app = authn.first() == Some(&PropPath::MemberCall("asApp".into())); match classify_api_call(first_arg) { ApiCallKind::Unknown => { - if authn.first() == Some(&PropPath::MemberCall("asApp".into())) { + if is_as_app { Some(Intrinsic::ApiCall) } else { Some(Intrinsic::SafeCall) } } + ApiCallKind::CustomField => { + if is_as_app { + Some(Intrinsic::ApiCustomField) + } else { + Some(Intrinsic::SafeCall) + } + } + ApiCallKind::Fields => { + if is_as_app { + Some(Intrinsic::ApiCustomField) + } else { + Some(Intrinsic::UserFieldAccess) + } + } ApiCallKind::Trivial => Some(Intrinsic::SafeCall), ApiCallKind::Authorize => Some(Intrinsic::Authorize), } @@ -1579,7 +1599,7 @@ impl<'cx> FunctionAnalyzer<'cx> { alt: cont, }); let check = mem::replace(&mut self.block, body_id); - self.lower_stmt(body); + self.set_curr_terminator(Terminator::Goto(check)); self.block = cont; } diff --git a/crates/forge_analyzer/src/interp.rs b/crates/forge_analyzer/src/interp.rs index 3b95604c..af3e56c7 100644 --- a/crates/forge_analyzer/src/interp.rs +++ b/crates/forge_analyzer/src/interp.rs @@ -19,6 +19,7 @@ use crate::{ BasicBlock, BasicBlockId, Body, Inst, Intrinsic, Location, Operand, Rvalue, Successors, STARTING_BLOCK, }, + utils::ensure_sufficient_stack, worklist::WorkList, }; @@ -266,26 +267,30 @@ pub trait Checker<'cx>: Sized { block: &'cx BasicBlock, curr_state: &Self::State, ) -> ControlFlow<(), Self::State> { - let mut curr_state = interp.block_state(def, id).join(curr_state); - for stmt in block { - match stmt { - Inst::Expr(r) => curr_state = self.visit_rvalue(interp, r, id, &curr_state)?, - Inst::Assign(_, r) => curr_state = self.visit_rvalue(interp, r, id, &curr_state)?, - } - } - match block.successors() { - Successors::Return => ControlFlow::Continue(curr_state), - Successors::One(succ) => { - let bb = interp.body().block(id); - self.visit_block(interp, def, succ, bb, &curr_state) + ensure_sufficient_stack(|| { + let mut curr_state = interp.block_state(def, id).join(curr_state); + for stmt in block { + match stmt { + Inst::Expr(r) => curr_state = self.visit_rvalue(interp, r, id, &curr_state)?, + Inst::Assign(_, r) => { + curr_state = self.visit_rvalue(interp, r, id, &curr_state)? + } + } } - Successors::Two(succ1, succ2) => { - let bb = interp.body().block(succ1); - self.visit_block(interp, def, succ1, bb, &curr_state)?; - let bb = interp.body().block(succ2); - self.visit_block(interp, def, succ2, bb, &curr_state) + match block.successors() { + Successors::Return => ControlFlow::Continue(curr_state), + Successors::One(succ) => { + let bb = interp.body().block(id); + self.visit_block(interp, def, succ, bb, &curr_state) + } + Successors::Two(succ1, succ2) => { + let bb = interp.body().block(succ1); + self.visit_block(interp, def, succ1, bb, &curr_state)?; + let bb = interp.body().block(succ2); + self.visit_block(interp, def, succ2, bb, &curr_state) + } } - } + }) } } diff --git a/crates/forge_analyzer/src/ir.rs b/crates/forge_analyzer/src/ir.rs index 67ef7422..866b0235 100644 --- a/crates/forge_analyzer/src/ir.rs +++ b/crates/forge_analyzer/src/ir.rs @@ -65,12 +65,16 @@ pub(crate) enum Terminator { }, } +// FIXME: ideally we should record the API call expression in the IR and the `UserFieldAccess` and `ApiCustomField` variants +// should be removed and the type of the API call should be determined during dataflow. #[derive(Clone, Debug, Copy, PartialEq, Eq)] pub enum Intrinsic { Authorize, Fetch, ApiCall, SafeCall, + UserFieldAccess, + ApiCustomField, EnvRead, StorageRead, } @@ -747,6 +751,8 @@ impl fmt::Display for Intrinsic { Intrinsic::Fetch => write!(f, "fetch"), Intrinsic::Authorize => write!(f, "authorize"), Intrinsic::ApiCall => write!(f, "api call"), + Intrinsic::ApiCustomField => write!(f, "accessing custom field route asApp"), + Intrinsic::UserFieldAccess => write!(f, "accessing which fields a user can access"), Intrinsic::SafeCall => write!(f, "safe api call"), Intrinsic::EnvRead => write!(f, "env read"), Intrinsic::StorageRead => write!(f, "forge storage read"), diff --git a/crates/forge_analyzer/src/utils.rs b/crates/forge_analyzer/src/utils.rs index 1d11af3b..8379c07b 100644 --- a/crates/forge_analyzer/src/utils.rs +++ b/crates/forge_analyzer/src/utils.rs @@ -20,3 +20,12 @@ pub fn eq_prop_name(n: &MemberProp, name: &str) -> bool { _ => false, } } + +const RED_ZONE: usize = 100 * 1024; + +const STACK_SIZE: usize = 1024 * 1024; + +#[inline] +pub(crate) fn ensure_sufficient_stack(f: impl FnOnce() -> T) -> T { + stacker::maybe_grow(RED_ZONE, STACK_SIZE, || f()) +} diff --git a/crates/forge_loader/src/manifest.rs b/crates/forge_loader/src/manifest.rs index cd415c53..65aa39f1 100644 --- a/crates/forge_loader/src/manifest.rs +++ b/crates/forge_loader/src/manifest.rs @@ -66,6 +66,12 @@ struct ScheduledTrigger<'a> { interval: Interval, } +#[derive(Debug, Clone, PartialEq, Eq, Deserialize)] +struct WorkflowPostFunction<'a> { + #[serde(flatten, borrow)] + raw: RawTrigger<'a>, +} + #[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] pub struct ForgeModules<'a> { #[serde(rename = "macro", default, borrow)] @@ -80,6 +86,8 @@ pub struct ForgeModules<'a> { scheduled_triggers: Vec>, #[serde(rename = "consumer", default, borrow)] pub consumers: Vec>, + #[serde(rename = "jira:workflowPostFunction", default, borrow)] + workflow_post_functions: Vec>, #[serde(flatten)] extra: FxHashMap>>, } @@ -203,6 +211,9 @@ impl<'a> ForgeModules<'a> { pub fn into_analyzable_functions( mut self, ) -> impl Iterator>> { + //FIXME: The logic here is incorrect, a function should be in the ignored function IIF it + //is not invoked by a user-invocable module. + // number of webtriggers are usually low, so it's better to just sort them and reuse // the vec's storage instead of using a HashSet self.webtriggers @@ -218,9 +229,14 @@ impl<'a> ForgeModules<'a> { .into_iter() .map(|trigger| trigger.raw.function), ) + .chain( + self.workflow_post_functions + .into_iter() + .map(|pf| pf.raw.function), + ) .collect(); - let mut alternate_functions: Vec<&str> = Vec::new(); + let mut alternate_functions = vec![]; for module in self.extra.into_values().flatten() { alternate_functions.extend(module.function); if let Some(resolver) = module.resolver { diff --git a/crates/fsrt/src/main.rs b/crates/fsrt/src/main.rs index ae8078fd..4564f31e 100644 --- a/crates/fsrt/src/main.rs +++ b/crates/fsrt/src/main.rs @@ -3,7 +3,6 @@ use std::{ collections::HashSet, convert::TryFrom, fs, - io::{self, BufReader}, os::unix::prelude::OsStrExt, path::{Path, PathBuf}, sync::Arc, From 2db15770885ae3382e93524533449484092a077d Mon Sep 17 00:00:00 2001 From: Joshua Wong Date: Tue, 26 Sep 2023 12:13:30 -0500 Subject: [PATCH 197/517] fix: lower while loop body --- crates/forge_analyzer/src/definitions.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/forge_analyzer/src/definitions.rs b/crates/forge_analyzer/src/definitions.rs index 6623792d..71a15ca4 100644 --- a/crates/forge_analyzer/src/definitions.rs +++ b/crates/forge_analyzer/src/definitions.rs @@ -1599,7 +1599,7 @@ impl<'cx> FunctionAnalyzer<'cx> { alt: cont, }); let check = mem::replace(&mut self.block, body_id); - + self.lower_stmt(body); self.set_curr_terminator(Terminator::Goto(check)); self.block = cont; } From a79ffd0681a7e30fef5e4d8f45cd92a4b80957b5 Mon Sep 17 00:00:00 2001 From: Joshua Wong Date: Tue, 26 Sep 2023 12:15:57 -0500 Subject: [PATCH 198/517] fix: remove stacker usage in interp --- crates/forge_analyzer/src/interp.rs | 41 +++++++++++++---------------- crates/forge_analyzer/src/utils.rs | 2 +- 2 files changed, 19 insertions(+), 24 deletions(-) diff --git a/crates/forge_analyzer/src/interp.rs b/crates/forge_analyzer/src/interp.rs index af3e56c7..3b95604c 100644 --- a/crates/forge_analyzer/src/interp.rs +++ b/crates/forge_analyzer/src/interp.rs @@ -19,7 +19,6 @@ use crate::{ BasicBlock, BasicBlockId, Body, Inst, Intrinsic, Location, Operand, Rvalue, Successors, STARTING_BLOCK, }, - utils::ensure_sufficient_stack, worklist::WorkList, }; @@ -267,30 +266,26 @@ pub trait Checker<'cx>: Sized { block: &'cx BasicBlock, curr_state: &Self::State, ) -> ControlFlow<(), Self::State> { - ensure_sufficient_stack(|| { - let mut curr_state = interp.block_state(def, id).join(curr_state); - for stmt in block { - match stmt { - Inst::Expr(r) => curr_state = self.visit_rvalue(interp, r, id, &curr_state)?, - Inst::Assign(_, r) => { - curr_state = self.visit_rvalue(interp, r, id, &curr_state)? - } - } + let mut curr_state = interp.block_state(def, id).join(curr_state); + for stmt in block { + match stmt { + Inst::Expr(r) => curr_state = self.visit_rvalue(interp, r, id, &curr_state)?, + Inst::Assign(_, r) => curr_state = self.visit_rvalue(interp, r, id, &curr_state)?, } - match block.successors() { - Successors::Return => ControlFlow::Continue(curr_state), - Successors::One(succ) => { - let bb = interp.body().block(id); - self.visit_block(interp, def, succ, bb, &curr_state) - } - Successors::Two(succ1, succ2) => { - let bb = interp.body().block(succ1); - self.visit_block(interp, def, succ1, bb, &curr_state)?; - let bb = interp.body().block(succ2); - self.visit_block(interp, def, succ2, bb, &curr_state) - } + } + match block.successors() { + Successors::Return => ControlFlow::Continue(curr_state), + Successors::One(succ) => { + let bb = interp.body().block(id); + self.visit_block(interp, def, succ, bb, &curr_state) } - }) + Successors::Two(succ1, succ2) => { + let bb = interp.body().block(succ1); + self.visit_block(interp, def, succ1, bb, &curr_state)?; + let bb = interp.body().block(succ2); + self.visit_block(interp, def, succ2, bb, &curr_state) + } + } } } diff --git a/crates/forge_analyzer/src/utils.rs b/crates/forge_analyzer/src/utils.rs index 8379c07b..4a0ad371 100644 --- a/crates/forge_analyzer/src/utils.rs +++ b/crates/forge_analyzer/src/utils.rs @@ -27,5 +27,5 @@ const STACK_SIZE: usize = 1024 * 1024; #[inline] pub(crate) fn ensure_sufficient_stack(f: impl FnOnce() -> T) -> T { - stacker::maybe_grow(RED_ZONE, STACK_SIZE, || f()) + stacker::maybe_grow(RED_ZONE, STACK_SIZE, f) } From 29c38d66790d558a7b08f64b8e8dcb61e618648d Mon Sep 17 00:00:00 2001 From: awang7 Date: Thu, 5 Oct 2023 18:06:29 -0400 Subject: [PATCH 199/517] modified into_analyzable_functions with Josh and implementation in main.rs --- crates/forge_loader/src/manifest.rs | 404 ++++++++-------------------- crates/fsrt/src/main.rs | 158 ++++++----- 2 files changed, 209 insertions(+), 353 deletions(-) diff --git a/crates/forge_loader/src/manifest.rs b/crates/forge_loader/src/manifest.rs index 25b2b0a3..79bd5df2 100644 --- a/crates/forge_loader/src/manifest.rs +++ b/crates/forge_loader/src/manifest.rs @@ -1,15 +1,16 @@ use std::{ borrow::Borrow, - collections::{HashSet}, + collections::{BTreeSet, HashSet}, hash::Hash, - path::{Path, PathBuf}, str::pattern::SearchStep, + path::{Path, PathBuf}, + sync::Arc, }; use crate::Error; use forge_utils::FxHashMap; use itertools::{Either, Itertools}; use serde::Deserialize; -use std::collections::BTreeMap; +use serde_json::map::Entry; use tracing::trace; #[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] @@ -17,7 +18,7 @@ struct AuthProviders<'a> { #[serde(borrow)] auth: Vec<&'a str>, } -// Maps the Functions Module in common Modules +// Maps the Functions Module in common Modules #[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] pub struct FunctionMod<'a> { key: &'a str, @@ -32,51 +33,48 @@ struct CommonKey<'a> { key: &'a str, function: &'a str, resolver: Option<&'a str>, - } #[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] -struct MacroMod<'a> { - #[serde(flatten, borrow)] +struct MacroMod<'a> { + #[serde(flatten, borrow)] common_keys: CommonKey<'a>, config: Option<&'a str>, export: Option<&'a str>, } #[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] -struct ContentByLineItem<'a> { - #[serde(flatten, borrow)] +struct ContentByLineItem<'a> { + #[serde(flatten, borrow)] common_keys: CommonKey<'a>, - #[serde(borrow)] + #[serde(borrow)] dynamic_properties: Option<&'a str>, } #[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] -struct IssueGlance<'a> { - #[serde(flatten, borrow)] +struct IssueGlance<'a> { + #[serde(flatten, borrow)] common_keys: CommonKey<'a>, dynamic_properties: Option<&'a str>, - } #[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] -struct AccessImportType<'a> { - #[serde(flatten, borrow)] +struct AccessImportType<'a> { + #[serde(flatten, borrow)] common_keys: CommonKey<'a>, one_delete_import: Option<&'a str>, start_import: Option<&'a str>, stop_import: Option<&'a str>, import_status: Option<&'a str>, - } -// WebTrigger => RawTrigger; WHY IS THIS NAMED DIFFERENTLY !? WHO CHANGED NAMES +// WebTrigger => RawTrigger; WHY IS THIS NAMED DIFFERENTLY !? WHO CHANGED NAMES #[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] struct RawTrigger<'a> { key: &'a str, function: &'a str, } -// Trigger => EventTriger; WHY IS THIS NAMED DIFFERENTLY !? WHO CHANGED NAMES +// Trigger => EventTriger; WHY IS THIS NAMED DIFFERENTLY !? WHO CHANGED NAMES #[derive(Debug, Clone, PartialEq, Eq, Deserialize)] struct EventTrigger<'a> { #[serde(flatten, borrow)] @@ -93,7 +91,7 @@ enum Interval { Week, } -// Thank you to whomeever kept this one the same. T.T +// Thank you to whomeever kept this one the same. T.T #[derive(Debug, Clone, PartialEq, Eq, Deserialize)] struct ScheduledTrigger<'a> { #[serde(flatten, borrow)] @@ -105,13 +103,13 @@ struct ScheduledTrigger<'a> { #[derive(Debug, Clone, PartialEq, Eq, Deserialize)] pub struct DataProvider<'a> { #[serde(flatten, borrow)] - key: &'a str, + key: &'a str, callback: Option<&'a str>, } -// Struct for Custom field Module. Check that search suggestion gets read in correctly. +// Struct for Custom field Module. Check that search suggestion gets read in correctly. #[derive(Debug, Clone, PartialEq, Eq, Deserialize)] -pub struct CustomField<'a> { +pub struct CustomField<'a> { // all attributes below involve function calls #[serde(flatten, borrow)] common_keys: CommonKey<'a>, @@ -120,23 +118,22 @@ pub struct CustomField<'a> { edit: Option<&'a str>, } - #[derive(Debug, Clone, PartialEq, Eq, Deserialize)] pub struct UiModificatons<'a> { #[serde(flatten, borrow)] - common_keys: CommonKey<'a> + common_keys: CommonKey<'a>, } #[derive(Debug, Clone, PartialEq, Eq, Deserialize)] pub struct WorkflowValidator<'a> { #[serde(flatten, borrow)] - common_keys: CommonKey<'a> + common_keys: CommonKey<'a>, } #[derive(Debug, Clone, PartialEq, Eq, Deserialize)] pub struct WorkflowPostFunction<'a> { #[serde(flatten, borrow)] - common_keys: CommonKey<'a> + common_keys: CommonKey<'a>, } // Add more structs here for deserializing forge modules @@ -250,10 +247,9 @@ pub struct FunctionRef<'a, S = Unresolved> { status: S, } - // Add an extra variant to the FunctionTy enum for non user invocable functions -// Indirect: functions indirectly invoked by user :O So kewl. -// TODO: change this to struct with bools +// Indirect: functions indirectly invoked by user :O So kewl. +// TODO: change this to struct with bools #[derive(Debug, Clone, PartialEq, Eq)] pub enum FunctionTy { Invokable(T), @@ -262,39 +258,39 @@ pub enum FunctionTy { // Struct used for tracking what scan a funtion requires. #[derive(Debug, Clone, PartialEq, Eq)] -pub struct Entrypoint<'a> { - pub function: &'a str, +pub struct Entrypoint<'a, S = Unresolved> { + pub function: FunctionRef<'a, S>, pub invokable: bool, pub web_trigger: bool, } - -// Helper functions that help filter out which functions are what. -impl FunctionTy { - pub fn map(self, f: impl FnOnce(T) -> O) -> FunctionTy { - match self { - Self::Invokable(t) => FunctionTy::Invokable(f(t)), - Self::WebTrigger(t) => FunctionTy::WebTrigger(f(t)), - } - } - - #[inline] - pub fn into_inner(self) -> T { - match self { - FunctionTy::Invokable(t) | FunctionTy::WebTrigger(t) => t, - } - } - - pub fn sequence( - self, - f: impl FnOnce(T) -> I, - ) -> impl Iterator> { - match self { - Self::Invokable(t) => Either::Left(f(t).into_iter().map(FunctionTy::Invokable)), - Self::WebTrigger(t) => Either::Right(f(t).into_iter().map(FunctionTy::WebTrigger)), - } - } -} +// Helper functions that help filter out which functions are what. +// original code that's commented out to modify methods. Here for reference +// impl FunctionTy { +// pub fn map(self, f: impl FnOnce(T) -> O) -> FunctionTy { +// match self { +// Self::Invokable(t) => FunctionTy::Invokable(f(t)), +// Self::WebTrigger(t) => FunctionTy::WebTrigger(f(t)), +// } +// } + +// #[inline] +// pub fn into_inner(self) -> T { +// match self { +// FunctionTy::Invokable(t) | FunctionTy::WebTrigger(t) => t, +// } +// } + +// pub fn sequence( +// self, +// f: impl FnOnce(T) -> I, +// ) -> impl Iterator> { +// match self { +// Self::Invokable(t) => Either::Left(f(t).into_iter().map(FunctionTy::Invokable)), +// Self::WebTrigger(t) => Either::Right(f(t).into_iter().map(FunctionTy::WebTrigger)), +// } +// } +// } impl AsRef for FunctionTy { #[inline] @@ -305,261 +301,94 @@ impl AsRef for FunctionTy { } } - impl<'a> ForgeModules<'a> { - -// TODO: function returns iterator where each item is some specified type. - pub fn into_analyzable_functions ( - &mut self, - ) -> BTreeMap<&'a str, Entrypoint<'a>>{ + // TODO: function returns iterator where each item is some specified type. + pub fn into_analyzable_functions(mut self) -> impl Iterator> { // number of webtriggers are usually low, so it's better to just sort them and reuse - self.webtriggers.sort_unstable_by_key(|trigger| trigger.function); + self.webtriggers + .sort_unstable_by_key(|trigger| trigger.function); - // Get all the Triggers and represent them as a new struct thing where "webtrigger" attribute is true - // for all trigger things + // Get all the Triggers and represent them as a new struct thing where "webtrigger" attribute is true + // for all trigger things - let mut functions_to_scan = BTreeMap::new(); - - // Get all functions for module from manifest.yml - self.functions.iter().for_each(|func| { - functions_to_scan.insert(func.key, Entrypoint { - function: func.key, - invokable: false, - web_trigger: false, - }); - - }); - - - self.webtriggers.iter().for_each(|webtriggers| { - if functions_to_scan.contains_key(webtriggers.function) { - if let Some(entry) = functions_to_scan.get_mut(webtriggers.function) { - entry.web_trigger = true; - } - } - - }); - - - self.event_triggers.iter().for_each(|event_triggers| { - if functions_to_scan.contains_key(event_triggers.raw.function) { - if let Some(entry) = functions_to_scan.get_mut(event_triggers.raw.function) { - entry.web_trigger = true; - } - } - }); - self.scheduled_triggers.iter().for_each(|schedule_triggers| { - if functions_to_scan.contains_key(schedule_triggers.raw.function) { - if let Some(entry) = functions_to_scan.get_mut(schedule_triggers.raw.function) { - entry.web_trigger = true; - } - } - }); - - // create arrays representing functions that expose user non-invokable functions - // self.consumers.iter().for_each(|consumer| { - // if functions_to_scan.contains_key(consumer.resolver.function) { - // if let Some(entry) = functions_to_scan.get_mut(consumer.resolver.function) { - // entry.invokable = true; - // } - // } - // }); + let mut invokable_functions = BTreeSet::new(); self.data_provider.iter().for_each(|dataprovider| { - if let Some(call) = dataprovider.callback { - if let Some(entry) = functions_to_scan.get_mut(call) { - entry.invokable = true; - } - } - + invokable_functions.extend(dataprovider.callback); }); self.custom_field.iter().for_each(|customfield| { - if let Some(value)= customfield.value { - if let Some(entry) = functions_to_scan.get_mut(value) { - entry.invokable = true; - } - } - - if let Some(search) = customfield.search_suggestions { - if let Some(entry) = functions_to_scan.get_mut(search) { - entry.invokable = true; - } - - } - - if let Some(entry) = functions_to_scan.get_mut(customfield.common_keys.function) { - entry.invokable = true; - } - - if let Some(resolver) = customfield.common_keys.resolver { - if let Some(entry) = functions_to_scan.get_mut(resolver) { - entry.invokable = true; - } - } - - if let Some(edit) = customfield.edit { - if let Some(entry) = functions_to_scan.get_mut(edit) { - entry.invokable = true; - } - - } + invokable_functions.extend(customfield.value); + invokable_functions.extend(customfield.search_suggestions); + invokable_functions.extend(customfield.edit); + invokable_functions.insert(customfield.common_keys.function); + invokable_functions.extend(customfield.common_keys.resolver); }); self.ui_modifications.iter().for_each(|ui| { - if let Some(entry) = functions_to_scan.get_mut(ui.common_keys.function) { - entry.invokable = true; - } - - if let Some(resolver) = ui.common_keys.resolver { - if let Some(entry) = functions_to_scan.get_mut(resolver) { - entry.invokable = true; - } - } + invokable_functions.insert(ui.common_keys.function); + invokable_functions.extend(ui.common_keys.resolver); }); self.workflow_validator.iter().for_each(|validator| { - if let Some(entry) = functions_to_scan.get_mut(validator.common_keys.function) { - entry.invokable = true; - } + invokable_functions.insert(validator.common_keys.key); - if let Some(resolver) = validator.common_keys.resolver { - if let Some(entry) = functions_to_scan.get_mut(resolver) { - entry.invokable = true; - } - } - }); - - self.workflow_post_function.iter().for_each(|post_function| { - if let Some(entry) = functions_to_scan.get_mut(post_function.common_keys.function) { - entry.invokable = true; - } + invokable_functions.insert(validator.common_keys.function); - if let Some(resolver) = post_function.common_keys.resolver { - if let Some(entry) = functions_to_scan.get_mut(resolver) { - entry.invokable = true; - } - } + invokable_functions.extend(validator.common_keys.resolver); }); - // get user invokable modules that have additional exposure endpoints. - // ie macros has config and export fields on top of resolver fields that are functions - self.macros.iter().for_each(|macros| { - if let Some(resolver)= macros.common_keys.resolver { - if let Some(entry) = functions_to_scan.get_mut(resolver) { - entry.invokable = true; - } - } - - if let Some(config)= macros.config { - if let Some(entry) = functions_to_scan.get_mut(config) { - entry.invokable = true; - } - } - - if let Some(export)= macros.export { - if let Some(entry) = functions_to_scan.get_mut(export) { - entry.invokable = true; - } - } - - }); + self.workflow_post_function + .iter() + .for_each(|post_function| { + invokable_functions.insert(post_function.common_keys.key); - self.content_by_line_item.iter().for_each(|contentitem| { - if let Some(entry) = functions_to_scan.get_mut(contentitem.common_keys.function) { - entry.invokable = true; - } + invokable_functions.insert(post_function.common_keys.function); + invokable_functions.extend(post_function.common_keys.resolver); + }); - if let Some(resolver)= contentitem.common_keys.resolver { - if let Some(entry) = functions_to_scan.get_mut(resolver) { - entry.invokable = true; - } - } + // get user invokable modules that have additional exposure endpoints. + // ie macros has config and export fields on top of resolver fields that are functions + self.macros.iter().for_each(|macros| { + invokable_functions.insert(macros.common_keys.key); - if let Some(dynamic_properties)= contentitem.dynamic_properties { - if let Some(entry) = functions_to_scan.get_mut(dynamic_properties) { - entry.invokable = true; - } - } + invokable_functions.insert(macros.common_keys.function); + invokable_functions.extend(macros.common_keys.resolver); + invokable_functions.extend(macros.config); + invokable_functions.extend(macros.export); }); self.issue_glance.iter().for_each(|issue| { - if let Some(entry) = functions_to_scan.get_mut(issue.common_keys.function) { - entry.invokable = true; - } - - - if let Some(resolver)= issue.common_keys.resolver { - if let Some(entry) = functions_to_scan.get_mut(resolver) { - entry.invokable = true; - } - } - - if let Some(dynamic_properties)= issue.dynamic_properties { - if let Some(entry) = functions_to_scan.get_mut(dynamic_properties) { - entry.invokable = true; - } - } - + invokable_functions.insert(issue.common_keys.function); + invokable_functions.extend(issue.common_keys.resolver); + invokable_functions.extend(issue.dynamic_properties); }); self.access_import_type.iter().for_each(|access| { - if let Some(entry) = functions_to_scan.get_mut(access.common_keys.function) { - entry.invokable = true; - } - - if let Some(resolver) = access.common_keys.resolver { - if let Some(entry) = functions_to_scan.get_mut(resolver) { - entry.invokable = true; - } - } - - if let Some(delete) = access.one_delete_import { - if let Some(entry) = functions_to_scan.get_mut(delete) { - entry.invokable = true; - } - } - - if let Some(start) = access.start_import { - if let Some(entry) = functions_to_scan.get_mut(start) { - entry.invokable = true; - } - } - - if let Some(stop) = access.stop_import { - if let Some(entry) = functions_to_scan.get_mut(stop) { - entry.invokable = true; - } - } - - if let Some(status)= access.import_status { - if let Some(entry) = functions_to_scan.get_mut(status) { - entry.invokable = true; - } - } + invokable_functions.insert(access.common_keys.function); + invokable_functions.extend(access.common_keys.resolver); + invokable_functions.extend(access.one_delete_import); + invokable_functions.extend(access.stop_import); + invokable_functions.extend(access.start_import); + invokable_functions.extend(access.import_status); }); - - // get array for user invokable module functions - // make alternate_functions all user-invokable functions - for module in self.extra.clone().into_values().flatten() { - if let Some(mod_function) = module.function { - if let Some(entry) = functions_to_scan.get_mut(mod_function) { - entry.invokable = true; - } - } - - if let Some(resolver) = module.resolver { - if let Some(entry) = functions_to_scan.get_mut(resolver.function) { - entry.invokable = true; - } - } - } - - functions_to_scan + + self.functions.into_iter().flat_map(move |func| { + let web_trigger = self + .webtriggers + .binary_search_by_key(&func.key, |trigger| &trigger.function) + .is_ok(); + let invokable = invokable_functions.contains(func.key); + Ok::<_, Error>(Entrypoint { + function: FunctionRef::try_from(func)?, + invokable, + web_trigger, + }) + }) } } @@ -711,10 +540,9 @@ mod tests { ); } - // Modified specific deserialization schemes for modules. Checking that new schemes can deserialize function values. + // Modified specific deserialization schemes for modules. Checking that new schemes can deserialize function values. #[test] fn test_new_deserialize() { - let json = r#"{ "app": { "name": "My App", @@ -769,23 +597,21 @@ mod tests { }"#; let manifest: ForgeManifest = serde_json::from_str(json).unwrap(); assert_eq!(manifest.modules.macros.len(), 1); - assert_eq!(manifest.modules.macros[0].common_keys.function, "Catch-me-if-you-can0"); - + assert_eq!( + manifest.modules.macros[0].common_keys.function, + "Catch-me-if-you-can0" + ); if let Some(func) = manifest.modules.macros[0].common_keys.resolver { assert_eq!(func, "Catch-me-if-you-can1"); - } if let Some(func) = manifest.modules.macros[0].config { assert_eq!(func, "Catch-me-if-you-can2"); - } if let Some(func) = manifest.modules.macros[0].export { assert_eq!(func, "Catch-me-if-you-can3"); - - } - + } } } diff --git a/crates/fsrt/src/main.rs b/crates/fsrt/src/main.rs index fb4e5df0..ce44f75f 100644 --- a/crates/fsrt/src/main.rs +++ b/crates/fsrt/src/main.rs @@ -1,9 +1,7 @@ #![allow(clippy::type_complexity)] use std::{ - collections::{HashSet, BTreeMap, hash_map::Entry}, - convert::TryFrom, + collections::HashSet, fs, - io::{self, BufReader}, os::unix::prelude::OsStrExt, path::{Path, PathBuf}, sync::Arc, @@ -12,7 +10,6 @@ use std::{ use clap::{Parser, ValueHint}; use miette::{IntoDiagnostic, Result}; -use serde_json::map::Entry; use swc_core::{ common::{Globals, Mark, SourceMap, GLOBALS}, ecma::{ @@ -35,7 +32,7 @@ use forge_analyzer::{ resolver::resolve_calls, }; -use forge_loader::manifest::{ForgeManifest, FunctionRef, FunctionTy, Entrypoint}; +use forge_loader::manifest::{Entrypoint, ForgeManifest, Resolved}; use walkdir::WalkDir; #[derive(Parser, Debug)] @@ -78,16 +75,26 @@ struct Opts { out: Option, } +#[derive(Debug, Clone)] +struct ResolvedEntryPoint<'a> { + func_name: &'a str, + path: PathBuf, + module: ModId, + def_id: DefId, + webtrigger: bool, + invokable: bool, +} + struct ForgeProject<'a> { #[allow(dead_code)] sm: Arc, ctx: AppCtx, env: Environment, - funcs:BTreeMap<&'a str, Entrypoint<'a>>, + funcs: Vec>, opts: Opts, } -impl ForgeProject<'_> { +impl<'a> ForgeProject<'a> { #[instrument(skip(src, iter))] fn with_files_and_sourceroot, I: IntoIterator>( src: P, @@ -134,21 +141,24 @@ impl ForgeProject<'_> { opts: Opts::default(), } } -// TODO: edit to work with new iterator that not FUNCTIONTY - fn add_funcs<'a, I: IntoIterator>>(&mut self, iter: I) { - self.funcs.extend(iter); + // TODO: edit to work with new iterator that not FUNCTIONTY + fn add_funcs>>(&mut self, iter: I) { + self.funcs.extend(iter.into_iter().filter_map(|entrypoint| { + let (func_name, path) = entrypoint.function.into_func_path(); + let module = self.ctx.modid_from_path(&path)?; + let def_id = self.env.module_export(module, func_name)?; + Some(ResolvedEntryPoint { + func_name, + path, + module, + def_id, + invokable: entrypoint.invokable, + webtrigger: entrypoint.web_trigger, + }) + })); } } - -// self.funcs.extend(iter.into_iter().flat_map(|ftype| { -// ftype.sequence(|(func_name, path)| { -// let modid = self.ctx.modid_from_path(&path)?; -// let func = self.env.module_export(modid, func_name)?; -// Some((func_name.to_owned(), path, modid, func)) -// }) -// })); - fn is_js_file>(path: P) -> bool { matches!( path.as_ref().extension().map(|s| s.as_bytes()), @@ -168,7 +178,7 @@ fn collect_sourcefiles>(root: P) -> impl Iterator } #[tracing::instrument(level = "debug")] -fn scan_directory(dir: PathBuf, function: Option<&str>, opts: Opts) -> Result { +fn scan_directory(dir: PathBuf, function: Option<&str>, opts: Opts) -> Result<()> { let mut manifest_file = dir.clone(); manifest_file.push("manifest.yaml"); if !manifest_file.exists() { @@ -191,19 +201,22 @@ fn scan_directory(dir: PathBuf, function: Option<&str>, opts: Opts) -> Result(resolved_func.into_func_path()) - // }) - // }); + let funcrefs = manifest + .modules + .into_analyzable_functions() + .flat_map(|entrypoint| { + Ok::<_, forge_loader::Error>(Entrypoint { + function: entrypoint.function.try_resolve(&paths, &dir)?, + invokable: entrypoint.invokable, + web_trigger: entrypoint.web_trigger, + }) + }); + let src_root = dir.join("src"); let mut proj = ForgeProject::with_files_and_sourceroot(src_root, paths.clone()); if transpiled_async { warn!("Unable to scan due to transpiled async"); - return Ok(proj); + return Ok(()); } proj.opts = opts.clone(); proj.add_funcs(funcrefs); @@ -216,48 +229,65 @@ fn scan_directory(dir: PathBuf, function: Option<&str>, opts: Opts) -> Result { - // let mut checker = AuthZChecker::new(); - // debug!("checking {func} at {path:?}"); - // if let Err(err) = interp.run_checker(def, &mut checker, path.clone(), func.clone()) - // { - // warn!("error while scanning {func} in {path:?}: {err}"); - // } - // reporter.add_vulnerabilities(checker.into_vulns()); - // } - // FunctionTy::WebTrigger((ref func, ref path, _, def)) => { - // let mut checker = AuthenticateChecker::new(); - // debug!("checking webtrigger {func} at {path:?}"); - // if let Err(err) = - // authn_interp.run_checker(def, &mut checker, path.clone(), func.clone()) - // { - // warn!("error while scanning {func} in {path:?}: {err}"); - // } - // reporter.add_vulnerabilities(checker.into_vulns()); - // } - // } + // match *func { + // FunctionTy::Invokable((ref func, ref path, _, def)) => { + // let mut checker = AuthZChecker::new(); + // debug!("checking {func} at {path:?}"); + // if let Err(err) = interp.run_checker(def, &mut checker, path.clone(), func.clone()) + // { + // warn!("error while scanning {func} in {path:?}: {err}"); + // } + // reporter.add_vulnerabilities(checker.into_vulns()); + // } + // FunctionTy::WebTrigger((ref func, ref path, _, def)) => { + // let mut checker = AuthenticateChecker::new(); + // debug!("checking webtrigger {func} at {path:?}"); + // if let Err(err) = + // authn_interp.run_checker(def, &mut checker, path.clone(), func.clone()) + // { + // warn!("error while scanning {func} in {path:?}: {err}"); + // } + // reporter.add_vulnerabilities(checker.into_vulns()); + // } + // } + // Get entrypoint value from tuple + // Logic for performing scans. + // If it's invokable, then run invokable scan. If web_trigger, then trigger scan. + // And if it's both, run both scans. if func.invokable { let mut checker = AuthZChecker::new(); - debug!("checking {:?} at {path:?}", func.function); - if let Err(err) = interp.run_checker(def, &mut checker, path.clone(), func.function.to_string()) - { - warn!("error while scanning {:?} in {path:?}: {err}", func.function); + debug!("checking {:?} at {:?}", func.func_name, &func.path); + if let Err(err) = interp.run_checker( + func.def_id, + &mut checker, + func.path.clone(), + func.func_name.to_string(), + ) { + warn!( + "error while scanning {:?} in {:?}: {err}", + func.func_name, func.path, + ); } reporter.add_vulnerabilities(checker.into_vulns()); - - } else if func.web_trigger { + } else if func.webtrigger { let mut checker = AuthenticateChecker::new(); - debug!("checking webtrigger {:?} at {path:?}", func.function); - if let Err(err) = - authn_interp.run_checker(def, &mut checker, path.clone(), func.function.to_string()) - { - warn!("error while scanning {:?} in {path:?}: {err}", func.function); + debug!( + "checking webtrigger {:?} at {:?}", + func.func_name, func.path, + ); + if let Err(err) = authn_interp.run_checker( + func.def_id, + &mut checker, + func.path.clone(), + func.func_name.to_string(), + ) { + warn!( + "error while scanning {:?} in {:?}: {err}", + func.func_name, func.path, + ); } reporter.add_vulnerabilities(checker.into_vulns()); - - } } let report = serde_json::to_string(&reporter.into_report()).into_diagnostic()?; @@ -268,7 +298,7 @@ fn scan_directory(dir: PathBuf, function: Option<&str>, opts: Opts) -> Result println!("{report}"), } - Ok(proj) + Ok(()) } fn main() -> Result<()> { From e32c33dcb13b46f239958840aee92cf2f5a05b5b Mon Sep 17 00:00:00 2001 From: awang7 Date: Thu, 5 Oct 2023 18:06:29 -0400 Subject: [PATCH 200/517] modified into_analyzable_functions with Josh and implementation in main.rs --- crates/forge_loader/src/manifest.rs | 404 ++++++++-------------------- crates/fsrt/src/main.rs | 158 ++++++----- 2 files changed, 209 insertions(+), 353 deletions(-) diff --git a/crates/forge_loader/src/manifest.rs b/crates/forge_loader/src/manifest.rs index 25b2b0a3..79bd5df2 100644 --- a/crates/forge_loader/src/manifest.rs +++ b/crates/forge_loader/src/manifest.rs @@ -1,15 +1,16 @@ use std::{ borrow::Borrow, - collections::{HashSet}, + collections::{BTreeSet, HashSet}, hash::Hash, - path::{Path, PathBuf}, str::pattern::SearchStep, + path::{Path, PathBuf}, + sync::Arc, }; use crate::Error; use forge_utils::FxHashMap; use itertools::{Either, Itertools}; use serde::Deserialize; -use std::collections::BTreeMap; +use serde_json::map::Entry; use tracing::trace; #[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] @@ -17,7 +18,7 @@ struct AuthProviders<'a> { #[serde(borrow)] auth: Vec<&'a str>, } -// Maps the Functions Module in common Modules +// Maps the Functions Module in common Modules #[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] pub struct FunctionMod<'a> { key: &'a str, @@ -32,51 +33,48 @@ struct CommonKey<'a> { key: &'a str, function: &'a str, resolver: Option<&'a str>, - } #[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] -struct MacroMod<'a> { - #[serde(flatten, borrow)] +struct MacroMod<'a> { + #[serde(flatten, borrow)] common_keys: CommonKey<'a>, config: Option<&'a str>, export: Option<&'a str>, } #[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] -struct ContentByLineItem<'a> { - #[serde(flatten, borrow)] +struct ContentByLineItem<'a> { + #[serde(flatten, borrow)] common_keys: CommonKey<'a>, - #[serde(borrow)] + #[serde(borrow)] dynamic_properties: Option<&'a str>, } #[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] -struct IssueGlance<'a> { - #[serde(flatten, borrow)] +struct IssueGlance<'a> { + #[serde(flatten, borrow)] common_keys: CommonKey<'a>, dynamic_properties: Option<&'a str>, - } #[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] -struct AccessImportType<'a> { - #[serde(flatten, borrow)] +struct AccessImportType<'a> { + #[serde(flatten, borrow)] common_keys: CommonKey<'a>, one_delete_import: Option<&'a str>, start_import: Option<&'a str>, stop_import: Option<&'a str>, import_status: Option<&'a str>, - } -// WebTrigger => RawTrigger; WHY IS THIS NAMED DIFFERENTLY !? WHO CHANGED NAMES +// WebTrigger => RawTrigger; WHY IS THIS NAMED DIFFERENTLY !? WHO CHANGED NAMES #[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] struct RawTrigger<'a> { key: &'a str, function: &'a str, } -// Trigger => EventTriger; WHY IS THIS NAMED DIFFERENTLY !? WHO CHANGED NAMES +// Trigger => EventTriger; WHY IS THIS NAMED DIFFERENTLY !? WHO CHANGED NAMES #[derive(Debug, Clone, PartialEq, Eq, Deserialize)] struct EventTrigger<'a> { #[serde(flatten, borrow)] @@ -93,7 +91,7 @@ enum Interval { Week, } -// Thank you to whomeever kept this one the same. T.T +// Thank you to whomeever kept this one the same. T.T #[derive(Debug, Clone, PartialEq, Eq, Deserialize)] struct ScheduledTrigger<'a> { #[serde(flatten, borrow)] @@ -105,13 +103,13 @@ struct ScheduledTrigger<'a> { #[derive(Debug, Clone, PartialEq, Eq, Deserialize)] pub struct DataProvider<'a> { #[serde(flatten, borrow)] - key: &'a str, + key: &'a str, callback: Option<&'a str>, } -// Struct for Custom field Module. Check that search suggestion gets read in correctly. +// Struct for Custom field Module. Check that search suggestion gets read in correctly. #[derive(Debug, Clone, PartialEq, Eq, Deserialize)] -pub struct CustomField<'a> { +pub struct CustomField<'a> { // all attributes below involve function calls #[serde(flatten, borrow)] common_keys: CommonKey<'a>, @@ -120,23 +118,22 @@ pub struct CustomField<'a> { edit: Option<&'a str>, } - #[derive(Debug, Clone, PartialEq, Eq, Deserialize)] pub struct UiModificatons<'a> { #[serde(flatten, borrow)] - common_keys: CommonKey<'a> + common_keys: CommonKey<'a>, } #[derive(Debug, Clone, PartialEq, Eq, Deserialize)] pub struct WorkflowValidator<'a> { #[serde(flatten, borrow)] - common_keys: CommonKey<'a> + common_keys: CommonKey<'a>, } #[derive(Debug, Clone, PartialEq, Eq, Deserialize)] pub struct WorkflowPostFunction<'a> { #[serde(flatten, borrow)] - common_keys: CommonKey<'a> + common_keys: CommonKey<'a>, } // Add more structs here for deserializing forge modules @@ -250,10 +247,9 @@ pub struct FunctionRef<'a, S = Unresolved> { status: S, } - // Add an extra variant to the FunctionTy enum for non user invocable functions -// Indirect: functions indirectly invoked by user :O So kewl. -// TODO: change this to struct with bools +// Indirect: functions indirectly invoked by user :O So kewl. +// TODO: change this to struct with bools #[derive(Debug, Clone, PartialEq, Eq)] pub enum FunctionTy { Invokable(T), @@ -262,39 +258,39 @@ pub enum FunctionTy { // Struct used for tracking what scan a funtion requires. #[derive(Debug, Clone, PartialEq, Eq)] -pub struct Entrypoint<'a> { - pub function: &'a str, +pub struct Entrypoint<'a, S = Unresolved> { + pub function: FunctionRef<'a, S>, pub invokable: bool, pub web_trigger: bool, } - -// Helper functions that help filter out which functions are what. -impl FunctionTy { - pub fn map(self, f: impl FnOnce(T) -> O) -> FunctionTy { - match self { - Self::Invokable(t) => FunctionTy::Invokable(f(t)), - Self::WebTrigger(t) => FunctionTy::WebTrigger(f(t)), - } - } - - #[inline] - pub fn into_inner(self) -> T { - match self { - FunctionTy::Invokable(t) | FunctionTy::WebTrigger(t) => t, - } - } - - pub fn sequence( - self, - f: impl FnOnce(T) -> I, - ) -> impl Iterator> { - match self { - Self::Invokable(t) => Either::Left(f(t).into_iter().map(FunctionTy::Invokable)), - Self::WebTrigger(t) => Either::Right(f(t).into_iter().map(FunctionTy::WebTrigger)), - } - } -} +// Helper functions that help filter out which functions are what. +// original code that's commented out to modify methods. Here for reference +// impl FunctionTy { +// pub fn map(self, f: impl FnOnce(T) -> O) -> FunctionTy { +// match self { +// Self::Invokable(t) => FunctionTy::Invokable(f(t)), +// Self::WebTrigger(t) => FunctionTy::WebTrigger(f(t)), +// } +// } + +// #[inline] +// pub fn into_inner(self) -> T { +// match self { +// FunctionTy::Invokable(t) | FunctionTy::WebTrigger(t) => t, +// } +// } + +// pub fn sequence( +// self, +// f: impl FnOnce(T) -> I, +// ) -> impl Iterator> { +// match self { +// Self::Invokable(t) => Either::Left(f(t).into_iter().map(FunctionTy::Invokable)), +// Self::WebTrigger(t) => Either::Right(f(t).into_iter().map(FunctionTy::WebTrigger)), +// } +// } +// } impl AsRef for FunctionTy { #[inline] @@ -305,261 +301,94 @@ impl AsRef for FunctionTy { } } - impl<'a> ForgeModules<'a> { - -// TODO: function returns iterator where each item is some specified type. - pub fn into_analyzable_functions ( - &mut self, - ) -> BTreeMap<&'a str, Entrypoint<'a>>{ + // TODO: function returns iterator where each item is some specified type. + pub fn into_analyzable_functions(mut self) -> impl Iterator> { // number of webtriggers are usually low, so it's better to just sort them and reuse - self.webtriggers.sort_unstable_by_key(|trigger| trigger.function); + self.webtriggers + .sort_unstable_by_key(|trigger| trigger.function); - // Get all the Triggers and represent them as a new struct thing where "webtrigger" attribute is true - // for all trigger things + // Get all the Triggers and represent them as a new struct thing where "webtrigger" attribute is true + // for all trigger things - let mut functions_to_scan = BTreeMap::new(); - - // Get all functions for module from manifest.yml - self.functions.iter().for_each(|func| { - functions_to_scan.insert(func.key, Entrypoint { - function: func.key, - invokable: false, - web_trigger: false, - }); - - }); - - - self.webtriggers.iter().for_each(|webtriggers| { - if functions_to_scan.contains_key(webtriggers.function) { - if let Some(entry) = functions_to_scan.get_mut(webtriggers.function) { - entry.web_trigger = true; - } - } - - }); - - - self.event_triggers.iter().for_each(|event_triggers| { - if functions_to_scan.contains_key(event_triggers.raw.function) { - if let Some(entry) = functions_to_scan.get_mut(event_triggers.raw.function) { - entry.web_trigger = true; - } - } - }); - self.scheduled_triggers.iter().for_each(|schedule_triggers| { - if functions_to_scan.contains_key(schedule_triggers.raw.function) { - if let Some(entry) = functions_to_scan.get_mut(schedule_triggers.raw.function) { - entry.web_trigger = true; - } - } - }); - - // create arrays representing functions that expose user non-invokable functions - // self.consumers.iter().for_each(|consumer| { - // if functions_to_scan.contains_key(consumer.resolver.function) { - // if let Some(entry) = functions_to_scan.get_mut(consumer.resolver.function) { - // entry.invokable = true; - // } - // } - // }); + let mut invokable_functions = BTreeSet::new(); self.data_provider.iter().for_each(|dataprovider| { - if let Some(call) = dataprovider.callback { - if let Some(entry) = functions_to_scan.get_mut(call) { - entry.invokable = true; - } - } - + invokable_functions.extend(dataprovider.callback); }); self.custom_field.iter().for_each(|customfield| { - if let Some(value)= customfield.value { - if let Some(entry) = functions_to_scan.get_mut(value) { - entry.invokable = true; - } - } - - if let Some(search) = customfield.search_suggestions { - if let Some(entry) = functions_to_scan.get_mut(search) { - entry.invokable = true; - } - - } - - if let Some(entry) = functions_to_scan.get_mut(customfield.common_keys.function) { - entry.invokable = true; - } - - if let Some(resolver) = customfield.common_keys.resolver { - if let Some(entry) = functions_to_scan.get_mut(resolver) { - entry.invokable = true; - } - } - - if let Some(edit) = customfield.edit { - if let Some(entry) = functions_to_scan.get_mut(edit) { - entry.invokable = true; - } - - } + invokable_functions.extend(customfield.value); + invokable_functions.extend(customfield.search_suggestions); + invokable_functions.extend(customfield.edit); + invokable_functions.insert(customfield.common_keys.function); + invokable_functions.extend(customfield.common_keys.resolver); }); self.ui_modifications.iter().for_each(|ui| { - if let Some(entry) = functions_to_scan.get_mut(ui.common_keys.function) { - entry.invokable = true; - } - - if let Some(resolver) = ui.common_keys.resolver { - if let Some(entry) = functions_to_scan.get_mut(resolver) { - entry.invokable = true; - } - } + invokable_functions.insert(ui.common_keys.function); + invokable_functions.extend(ui.common_keys.resolver); }); self.workflow_validator.iter().for_each(|validator| { - if let Some(entry) = functions_to_scan.get_mut(validator.common_keys.function) { - entry.invokable = true; - } + invokable_functions.insert(validator.common_keys.key); - if let Some(resolver) = validator.common_keys.resolver { - if let Some(entry) = functions_to_scan.get_mut(resolver) { - entry.invokable = true; - } - } - }); - - self.workflow_post_function.iter().for_each(|post_function| { - if let Some(entry) = functions_to_scan.get_mut(post_function.common_keys.function) { - entry.invokable = true; - } + invokable_functions.insert(validator.common_keys.function); - if let Some(resolver) = post_function.common_keys.resolver { - if let Some(entry) = functions_to_scan.get_mut(resolver) { - entry.invokable = true; - } - } + invokable_functions.extend(validator.common_keys.resolver); }); - // get user invokable modules that have additional exposure endpoints. - // ie macros has config and export fields on top of resolver fields that are functions - self.macros.iter().for_each(|macros| { - if let Some(resolver)= macros.common_keys.resolver { - if let Some(entry) = functions_to_scan.get_mut(resolver) { - entry.invokable = true; - } - } - - if let Some(config)= macros.config { - if let Some(entry) = functions_to_scan.get_mut(config) { - entry.invokable = true; - } - } - - if let Some(export)= macros.export { - if let Some(entry) = functions_to_scan.get_mut(export) { - entry.invokable = true; - } - } - - }); + self.workflow_post_function + .iter() + .for_each(|post_function| { + invokable_functions.insert(post_function.common_keys.key); - self.content_by_line_item.iter().for_each(|contentitem| { - if let Some(entry) = functions_to_scan.get_mut(contentitem.common_keys.function) { - entry.invokable = true; - } + invokable_functions.insert(post_function.common_keys.function); + invokable_functions.extend(post_function.common_keys.resolver); + }); - if let Some(resolver)= contentitem.common_keys.resolver { - if let Some(entry) = functions_to_scan.get_mut(resolver) { - entry.invokable = true; - } - } + // get user invokable modules that have additional exposure endpoints. + // ie macros has config and export fields on top of resolver fields that are functions + self.macros.iter().for_each(|macros| { + invokable_functions.insert(macros.common_keys.key); - if let Some(dynamic_properties)= contentitem.dynamic_properties { - if let Some(entry) = functions_to_scan.get_mut(dynamic_properties) { - entry.invokable = true; - } - } + invokable_functions.insert(macros.common_keys.function); + invokable_functions.extend(macros.common_keys.resolver); + invokable_functions.extend(macros.config); + invokable_functions.extend(macros.export); }); self.issue_glance.iter().for_each(|issue| { - if let Some(entry) = functions_to_scan.get_mut(issue.common_keys.function) { - entry.invokable = true; - } - - - if let Some(resolver)= issue.common_keys.resolver { - if let Some(entry) = functions_to_scan.get_mut(resolver) { - entry.invokable = true; - } - } - - if let Some(dynamic_properties)= issue.dynamic_properties { - if let Some(entry) = functions_to_scan.get_mut(dynamic_properties) { - entry.invokable = true; - } - } - + invokable_functions.insert(issue.common_keys.function); + invokable_functions.extend(issue.common_keys.resolver); + invokable_functions.extend(issue.dynamic_properties); }); self.access_import_type.iter().for_each(|access| { - if let Some(entry) = functions_to_scan.get_mut(access.common_keys.function) { - entry.invokable = true; - } - - if let Some(resolver) = access.common_keys.resolver { - if let Some(entry) = functions_to_scan.get_mut(resolver) { - entry.invokable = true; - } - } - - if let Some(delete) = access.one_delete_import { - if let Some(entry) = functions_to_scan.get_mut(delete) { - entry.invokable = true; - } - } - - if let Some(start) = access.start_import { - if let Some(entry) = functions_to_scan.get_mut(start) { - entry.invokable = true; - } - } - - if let Some(stop) = access.stop_import { - if let Some(entry) = functions_to_scan.get_mut(stop) { - entry.invokable = true; - } - } - - if let Some(status)= access.import_status { - if let Some(entry) = functions_to_scan.get_mut(status) { - entry.invokable = true; - } - } + invokable_functions.insert(access.common_keys.function); + invokable_functions.extend(access.common_keys.resolver); + invokable_functions.extend(access.one_delete_import); + invokable_functions.extend(access.stop_import); + invokable_functions.extend(access.start_import); + invokable_functions.extend(access.import_status); }); - - // get array for user invokable module functions - // make alternate_functions all user-invokable functions - for module in self.extra.clone().into_values().flatten() { - if let Some(mod_function) = module.function { - if let Some(entry) = functions_to_scan.get_mut(mod_function) { - entry.invokable = true; - } - } - - if let Some(resolver) = module.resolver { - if let Some(entry) = functions_to_scan.get_mut(resolver.function) { - entry.invokable = true; - } - } - } - - functions_to_scan + + self.functions.into_iter().flat_map(move |func| { + let web_trigger = self + .webtriggers + .binary_search_by_key(&func.key, |trigger| &trigger.function) + .is_ok(); + let invokable = invokable_functions.contains(func.key); + Ok::<_, Error>(Entrypoint { + function: FunctionRef::try_from(func)?, + invokable, + web_trigger, + }) + }) } } @@ -711,10 +540,9 @@ mod tests { ); } - // Modified specific deserialization schemes for modules. Checking that new schemes can deserialize function values. + // Modified specific deserialization schemes for modules. Checking that new schemes can deserialize function values. #[test] fn test_new_deserialize() { - let json = r#"{ "app": { "name": "My App", @@ -769,23 +597,21 @@ mod tests { }"#; let manifest: ForgeManifest = serde_json::from_str(json).unwrap(); assert_eq!(manifest.modules.macros.len(), 1); - assert_eq!(manifest.modules.macros[0].common_keys.function, "Catch-me-if-you-can0"); - + assert_eq!( + manifest.modules.macros[0].common_keys.function, + "Catch-me-if-you-can0" + ); if let Some(func) = manifest.modules.macros[0].common_keys.resolver { assert_eq!(func, "Catch-me-if-you-can1"); - } if let Some(func) = manifest.modules.macros[0].config { assert_eq!(func, "Catch-me-if-you-can2"); - } if let Some(func) = manifest.modules.macros[0].export { assert_eq!(func, "Catch-me-if-you-can3"); - - } - + } } } diff --git a/crates/fsrt/src/main.rs b/crates/fsrt/src/main.rs index fb4e5df0..ce44f75f 100644 --- a/crates/fsrt/src/main.rs +++ b/crates/fsrt/src/main.rs @@ -1,9 +1,7 @@ #![allow(clippy::type_complexity)] use std::{ - collections::{HashSet, BTreeMap, hash_map::Entry}, - convert::TryFrom, + collections::HashSet, fs, - io::{self, BufReader}, os::unix::prelude::OsStrExt, path::{Path, PathBuf}, sync::Arc, @@ -12,7 +10,6 @@ use std::{ use clap::{Parser, ValueHint}; use miette::{IntoDiagnostic, Result}; -use serde_json::map::Entry; use swc_core::{ common::{Globals, Mark, SourceMap, GLOBALS}, ecma::{ @@ -35,7 +32,7 @@ use forge_analyzer::{ resolver::resolve_calls, }; -use forge_loader::manifest::{ForgeManifest, FunctionRef, FunctionTy, Entrypoint}; +use forge_loader::manifest::{Entrypoint, ForgeManifest, Resolved}; use walkdir::WalkDir; #[derive(Parser, Debug)] @@ -78,16 +75,26 @@ struct Opts { out: Option, } +#[derive(Debug, Clone)] +struct ResolvedEntryPoint<'a> { + func_name: &'a str, + path: PathBuf, + module: ModId, + def_id: DefId, + webtrigger: bool, + invokable: bool, +} + struct ForgeProject<'a> { #[allow(dead_code)] sm: Arc, ctx: AppCtx, env: Environment, - funcs:BTreeMap<&'a str, Entrypoint<'a>>, + funcs: Vec>, opts: Opts, } -impl ForgeProject<'_> { +impl<'a> ForgeProject<'a> { #[instrument(skip(src, iter))] fn with_files_and_sourceroot, I: IntoIterator>( src: P, @@ -134,21 +141,24 @@ impl ForgeProject<'_> { opts: Opts::default(), } } -// TODO: edit to work with new iterator that not FUNCTIONTY - fn add_funcs<'a, I: IntoIterator>>(&mut self, iter: I) { - self.funcs.extend(iter); + // TODO: edit to work with new iterator that not FUNCTIONTY + fn add_funcs>>(&mut self, iter: I) { + self.funcs.extend(iter.into_iter().filter_map(|entrypoint| { + let (func_name, path) = entrypoint.function.into_func_path(); + let module = self.ctx.modid_from_path(&path)?; + let def_id = self.env.module_export(module, func_name)?; + Some(ResolvedEntryPoint { + func_name, + path, + module, + def_id, + invokable: entrypoint.invokable, + webtrigger: entrypoint.web_trigger, + }) + })); } } - -// self.funcs.extend(iter.into_iter().flat_map(|ftype| { -// ftype.sequence(|(func_name, path)| { -// let modid = self.ctx.modid_from_path(&path)?; -// let func = self.env.module_export(modid, func_name)?; -// Some((func_name.to_owned(), path, modid, func)) -// }) -// })); - fn is_js_file>(path: P) -> bool { matches!( path.as_ref().extension().map(|s| s.as_bytes()), @@ -168,7 +178,7 @@ fn collect_sourcefiles>(root: P) -> impl Iterator } #[tracing::instrument(level = "debug")] -fn scan_directory(dir: PathBuf, function: Option<&str>, opts: Opts) -> Result { +fn scan_directory(dir: PathBuf, function: Option<&str>, opts: Opts) -> Result<()> { let mut manifest_file = dir.clone(); manifest_file.push("manifest.yaml"); if !manifest_file.exists() { @@ -191,19 +201,22 @@ fn scan_directory(dir: PathBuf, function: Option<&str>, opts: Opts) -> Result(resolved_func.into_func_path()) - // }) - // }); + let funcrefs = manifest + .modules + .into_analyzable_functions() + .flat_map(|entrypoint| { + Ok::<_, forge_loader::Error>(Entrypoint { + function: entrypoint.function.try_resolve(&paths, &dir)?, + invokable: entrypoint.invokable, + web_trigger: entrypoint.web_trigger, + }) + }); + let src_root = dir.join("src"); let mut proj = ForgeProject::with_files_and_sourceroot(src_root, paths.clone()); if transpiled_async { warn!("Unable to scan due to transpiled async"); - return Ok(proj); + return Ok(()); } proj.opts = opts.clone(); proj.add_funcs(funcrefs); @@ -216,48 +229,65 @@ fn scan_directory(dir: PathBuf, function: Option<&str>, opts: Opts) -> Result { - // let mut checker = AuthZChecker::new(); - // debug!("checking {func} at {path:?}"); - // if let Err(err) = interp.run_checker(def, &mut checker, path.clone(), func.clone()) - // { - // warn!("error while scanning {func} in {path:?}: {err}"); - // } - // reporter.add_vulnerabilities(checker.into_vulns()); - // } - // FunctionTy::WebTrigger((ref func, ref path, _, def)) => { - // let mut checker = AuthenticateChecker::new(); - // debug!("checking webtrigger {func} at {path:?}"); - // if let Err(err) = - // authn_interp.run_checker(def, &mut checker, path.clone(), func.clone()) - // { - // warn!("error while scanning {func} in {path:?}: {err}"); - // } - // reporter.add_vulnerabilities(checker.into_vulns()); - // } - // } + // match *func { + // FunctionTy::Invokable((ref func, ref path, _, def)) => { + // let mut checker = AuthZChecker::new(); + // debug!("checking {func} at {path:?}"); + // if let Err(err) = interp.run_checker(def, &mut checker, path.clone(), func.clone()) + // { + // warn!("error while scanning {func} in {path:?}: {err}"); + // } + // reporter.add_vulnerabilities(checker.into_vulns()); + // } + // FunctionTy::WebTrigger((ref func, ref path, _, def)) => { + // let mut checker = AuthenticateChecker::new(); + // debug!("checking webtrigger {func} at {path:?}"); + // if let Err(err) = + // authn_interp.run_checker(def, &mut checker, path.clone(), func.clone()) + // { + // warn!("error while scanning {func} in {path:?}: {err}"); + // } + // reporter.add_vulnerabilities(checker.into_vulns()); + // } + // } + // Get entrypoint value from tuple + // Logic for performing scans. + // If it's invokable, then run invokable scan. If web_trigger, then trigger scan. + // And if it's both, run both scans. if func.invokable { let mut checker = AuthZChecker::new(); - debug!("checking {:?} at {path:?}", func.function); - if let Err(err) = interp.run_checker(def, &mut checker, path.clone(), func.function.to_string()) - { - warn!("error while scanning {:?} in {path:?}: {err}", func.function); + debug!("checking {:?} at {:?}", func.func_name, &func.path); + if let Err(err) = interp.run_checker( + func.def_id, + &mut checker, + func.path.clone(), + func.func_name.to_string(), + ) { + warn!( + "error while scanning {:?} in {:?}: {err}", + func.func_name, func.path, + ); } reporter.add_vulnerabilities(checker.into_vulns()); - - } else if func.web_trigger { + } else if func.webtrigger { let mut checker = AuthenticateChecker::new(); - debug!("checking webtrigger {:?} at {path:?}", func.function); - if let Err(err) = - authn_interp.run_checker(def, &mut checker, path.clone(), func.function.to_string()) - { - warn!("error while scanning {:?} in {path:?}: {err}", func.function); + debug!( + "checking webtrigger {:?} at {:?}", + func.func_name, func.path, + ); + if let Err(err) = authn_interp.run_checker( + func.def_id, + &mut checker, + func.path.clone(), + func.func_name.to_string(), + ) { + warn!( + "error while scanning {:?} in {:?}: {err}", + func.func_name, func.path, + ); } reporter.add_vulnerabilities(checker.into_vulns()); - - } } let report = serde_json::to_string(&reporter.into_report()).into_diagnostic()?; @@ -268,7 +298,7 @@ fn scan_directory(dir: PathBuf, function: Option<&str>, opts: Opts) -> Result println!("{report}"), } - Ok(proj) + Ok(()) } fn main() -> Result<()> { From dfdcf19a2e29b524f495edf1d5aaac0372445f1a Mon Sep 17 00:00:00 2001 From: James Gersbach Date: Sun, 15 Oct 2023 23:03:18 -0400 Subject: [PATCH 201/517] expanding scope for definitions --- crates/forge_analyzer/src/checkers.rs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/crates/forge_analyzer/src/checkers.rs b/crates/forge_analyzer/src/checkers.rs index 029f2e66..8486148b 100644 --- a/crates/forge_analyzer/src/checkers.rs +++ b/crates/forge_analyzer/src/checkers.rs @@ -1256,6 +1256,13 @@ impl<'cx> Dataflow<'cx> for DefintionAnalysisRunner { *def, Value::Const(Const::Literal(str.to_string())), ); + } else if let Some(VarKind::LocalDef(def)) = + interp.body().vars.get(varid) + { + interp.defid_to_value.insert( + *def, + Value::Const(Const::Literal(str.to_string())), + ); } } } From 82e6e2c1cffc7d8d9abff20021c4913744d0b73f Mon Sep 17 00:00:00 2001 From: Gersbach Date: Mon, 16 Oct 2023 16:11:26 -0400 Subject: [PATCH 202/517] cleaning up codebase --- crates/forge_analyzer/src/checkers.rs | 16 ++++++++++------ crates/forge_analyzer/src/interp.rs | 1 - crates/forge_analyzer/src/utils.rs | 2 +- crates/fsrt/src/main.rs | 2 -- 4 files changed, 11 insertions(+), 10 deletions(-) diff --git a/crates/forge_analyzer/src/checkers.rs b/crates/forge_analyzer/src/checkers.rs index 8486148b..0f98c681 100644 --- a/crates/forge_analyzer/src/checkers.rs +++ b/crates/forge_analyzer/src/checkers.rs @@ -880,10 +880,7 @@ impl<'cx> Dataflow<'cx> for PermissionDataflow { //println!("intrinsic_argument {:?}", intrinsic_argument); if intrinsic_argument.first_arg == None { - println!("interp permissions {:?}", _interp.permissions); - println!("found none ...."); _interp.permissions.drain(..); - println!("interp permissions {:?}", _interp.permissions); } else { intrinsic_argument .first_arg @@ -1263,6 +1260,13 @@ impl<'cx> Dataflow<'cx> for DefintionAnalysisRunner { *def, Value::Const(Const::Literal(str.to_string())), ); + } else if let Some(VarKind::Temp { parent }) = interp.body().vars.get(varid) { + if let Some(defid_parent) = parent { + interp.defid_to_value.insert( + *defid_parent, + Value::Const(Const::Literal(str.to_string())), + ); + } } } } @@ -1489,8 +1493,8 @@ impl<'cx> Dataflow<'cx> for DefintionAnalysisRunner { prev_values: Option>, ) { match operand { - Operand::Lit(_lit) => { - if &Literal::Undef != _lit { + Operand::Lit(lit) => { + if &Literal::Undef != lit { if let Some(prev_values) = prev_values { if let Some(lit_value) = convert_operand_to_raw(operand) { let const_value = Const::Literal(lit_value); @@ -1557,7 +1561,7 @@ impl<'cx> Dataflow<'cx> for DefintionAnalysisRunner { worklist: &mut WorkList, ) { self.super_join_term(interp, def, block, state, worklist); - for (def, arguments, values) in self.needs_call.drain(..) { + for (def, _arguments, values) in self.needs_call.drain(..) { worklist.push_front_blocks(interp.env(), def, interp.call_all); interp.callstack_arguments.push(values.clone()); } diff --git a/crates/forge_analyzer/src/interp.rs b/crates/forge_analyzer/src/interp.rs index 5bfb919c..a2fed5b1 100644 --- a/crates/forge_analyzer/src/interp.rs +++ b/crates/forge_analyzer/src/interp.rs @@ -746,7 +746,6 @@ impl<'cx, C: Runner<'cx>> Interp<'cx, C> { let arguments = self.callstack_arguments.pop(); let name = self.env.def_name(def); debug!("Dataflow: {name} - {block_id}"); - // println!("Dataflow: {name} - {block_id}"); self.dataflow_visited.insert(def); let func = self.env().def_ref(def).expect_body(); self.curr_body.set(Some(func)); diff --git a/crates/forge_analyzer/src/utils.rs b/crates/forge_analyzer/src/utils.rs index 490fbe10..9720fab9 100644 --- a/crates/forge_analyzer/src/utils.rs +++ b/crates/forge_analyzer/src/utils.rs @@ -157,4 +157,4 @@ pub fn eq_prop_name(n: &MemberProp, name: &str) -> bool { }, _ => false, } -} +} \ No newline at end of file diff --git a/crates/fsrt/src/main.rs b/crates/fsrt/src/main.rs index de1f2d62..ad4b0cdf 100644 --- a/crates/fsrt/src/main.rs +++ b/crates/fsrt/src/main.rs @@ -372,14 +372,12 @@ fn scan_directory(dir: PathBuf, function: Option<&str>, opts: Opts) -> Result Date: Tue, 17 Oct 2023 15:05:53 -0400 Subject: [PATCH 203/517] refractoring to a value manager --- crates/forge_analyzer/src/checkers.rs | 83 +++++++++++-------- crates/forge_analyzer/src/interp.rs | 30 ++++--- crates/forge_analyzer/src/utils.rs | 2 +- crates/fsrt/src/main.rs | 16 ++-- .../src/utils.js | 9 +- 5 files changed, 83 insertions(+), 57 deletions(-) diff --git a/crates/forge_analyzer/src/checkers.rs b/crates/forge_analyzer/src/checkers.rs index 0f98c681..ef7378cf 100644 --- a/crates/forge_analyzer/src/checkers.rs +++ b/crates/forge_analyzer/src/checkers.rs @@ -729,7 +729,7 @@ impl<'cx> Runner<'cx> for SecretChecker { } } else if let Some(value) = interp.body().vars.get(varid) { if let VarKind::GlobalRef(def) = value { - if let Some(value) = interp.defid_to_value.get(def) { + if let Some(value) = interp.value_manager.defid_to_value.get(def) { println!("value [] {value:?}"); match value { Value::Const(_) | Value::Phi(_) => { @@ -850,7 +850,7 @@ impl<'cx> Dataflow<'cx> for PermissionDataflow { } else if let Some(value) = _interp.body().vars.get(varid) { if let VarKind::GlobalRef(def) = value { if let Some(Value::Const(value)) = - _interp.defid_to_value.get(def) + _interp.value_manager.defid_to_value.get(def) { intrinsic_argument.first_arg = Some(vec![]); add_elements_to_intrinsic_struct( @@ -877,8 +877,6 @@ impl<'cx> Dataflow<'cx> for PermissionDataflow { let mut permissions_within_call: Vec = vec![]; let intrinsic_func_type = intrinsic_argument.name.unwrap(); - //println!("intrinsic_argument {:?}", intrinsic_argument); - if intrinsic_argument.first_arg == None { _interp.permissions.drain(..); } else { @@ -1231,8 +1229,8 @@ impl<'cx> Dataflow<'cx> for DefintionAnalysisRunner { interp.body().vars.get(varid) { if let Base::Var(varid_to_assign) = var.base { - interp - .expected_return_values + interp. + value_manager.expected_return_values .insert(*defid, (def, varid_to_assign)); } } @@ -1240,36 +1238,51 @@ impl<'cx> Dataflow<'cx> for DefintionAnalysisRunner { } } Rvalue::Read(operand) => { - // TODO: Handle for all cases - if let Rvalue::Read(read) = rvalue { - if let Operand::Lit(lit) = read { - if let Literal::Str(str) = lit { - if let Base::Var(varid) = var.base { - if let Some(VarKind::GlobalRef(def)) = - interp.body().vars.get(varid) - { - interp.defid_to_value.insert( - *def, - Value::Const(Const::Literal(str.to_string())), - ); - } else if let Some(VarKind::LocalDef(def)) = - interp.body().vars.get(varid) - { - interp.defid_to_value.insert( - *def, - Value::Const(Const::Literal(str.to_string())), - ); - } else if let Some(VarKind::Temp { parent }) = interp.body().vars.get(varid) { - if let Some(defid_parent) = parent { - interp.defid_to_value.insert( - *defid_parent, - Value::Const(Const::Literal(str.to_string())), - ); + match read { + Operand::Lit(lit) => { + if let Literal::Str(str) = lit { + if let Base::Var(varid) = var.base { + if let Some(VarKind::GlobalRef(def)) = + interp.body().vars.get(varid) + { + interp.value_manager.defid_to_value.insert( + *def, + Value::Const(Const::Literal( + str.to_string(), + )), + ); + } else if let Some(VarKind::LocalDef(def)) = + interp.body().vars.get(varid) + { + interp.value_manager.defid_to_value.insert( + *def, + Value::Const(Const::Literal( + str.to_string(), + )), + ); + } else if let Some(VarKind::Temp { parent }) = + interp.body().vars.get(varid) + { + if let Some(defid_parent) = parent { + interp.value_manager.defid_to_value.insert( + *defid_parent, + Value::Const(Const::Literal( + str.to_string(), + )), + ); + } } } } } + Operand::Var(var) => { + println!("{var}"); + if let Base::Var(varid) = var.base { + let values = interp.value_manager.varid_to_value.get(&(def, varid, None)); + println!("values: {values:?}") + } + } } } /* should be expanded to include all of the cases ... */ @@ -1318,6 +1331,7 @@ impl<'cx> Dataflow<'cx> for DefintionAnalysisRunner { ) { if defid == defid_alt && varid_alt != varid { interp + .value_manager .varid_to_value .insert((def, varid_alt, None), operand.clone()); } @@ -1336,7 +1350,7 @@ impl<'cx> Dataflow<'cx> for DefintionAnalysisRunner { for (varid, varkind) in interp.body().vars.clone().iter_enumerated() { if &VarKind::Ret == varkind { if let Some((defid_calling_func, varid_calling_func)) = - interp.expected_return_values.get(&def) + interp.value_manager.expected_return_values.get(&def) { if let Some(value) = interp.get_value(def, varid, None) { interp.add_value( @@ -1366,7 +1380,7 @@ impl<'cx> Dataflow<'cx> for DefintionAnalysisRunner { // transfer all of the variables if let Operand::Var(variable) = operand { if let Base::Var(varid_rval) = variable.base { - interp.varid_to_value.clone().iter().for_each( + interp.value_manager.varid_to_value.clone().iter().for_each( |((defid, varid_rval_potential, projection), value)| { if varid_rval_potential == &varid_rval { interp.add_value(def, *varid, value.clone(), projection.clone()) @@ -1470,6 +1484,7 @@ impl<'cx> Dataflow<'cx> for DefintionAnalysisRunner { _ => {} } interp + .value_manager .varid_to_value .insert((def, *varid, None), return_value_from_string(new_vals)); } else if let Some(val1) = val1 { @@ -1541,7 +1556,7 @@ impl<'cx> Dataflow<'cx> for DefintionAnalysisRunner { } } else { if let Some(potential_value) = interp.get_value(def, prev_varid, None) { - interp.varid_to_value.insert( + interp.value_manager.varid_to_value.insert( (def, *varid, var.projections.get(0).cloned()), potential_value.clone(), ); diff --git a/crates/forge_analyzer/src/interp.rs b/crates/forge_analyzer/src/interp.rs index a2fed5b1..60b8c131 100644 --- a/crates/forge_analyzer/src/interp.rs +++ b/crates/forge_analyzer/src/interp.rs @@ -506,17 +506,21 @@ pub struct Interp<'cx, C: Runner<'cx>> { checker_visited: RefCell>, callstack: RefCell>, pub callstack_arguments: Vec>, - // vulns: RefCell>, - pub expected_return_values: HashMap, + pub value_manager: ValueManager, pub permissions: Vec, - pub expecting_value: VecDeque<(DefId, (VarId, DefId))>, pub jira_permission_resolver: &'cx PermissionHashMap, pub confluence_permission_resolver: &'cx PermissionHashMap, pub jira_regex_map: &'cx HashMap, pub confluence_regex_map: &'cx HashMap, + _checker: PhantomData, +} + +#[derive(Debug)] +pub struct ValueManager { pub varid_to_value: FxHashMap<(DefId, VarId, Option), Value>, pub defid_to_value: FxHashMap, - _checker: PhantomData, + pub expecting_value: VecDeque<(DefId, (VarId, DefId))>, + pub expected_return_values: HashMap, } #[derive(Debug)] @@ -585,6 +589,8 @@ impl<'cx, C: Runner<'cx>> Interp<'cx, C> { confluence_regex_map: &'cx HashMap, ) -> Self { let call_graph = CallGraph::new(env); + + Self { env, call_graph, @@ -599,13 +605,15 @@ impl<'cx, C: Runner<'cx>> Interp<'cx, C> { dataflow_visited: FxHashSet::default(), checker_visited: RefCell::new(FxHashSet::default()), callstack_arguments: Vec::new(), - expecting_value: VecDeque::default(), callstack: RefCell::new(Vec::new()), // vulns: RefCell::new(Vec::new()), - expected_return_values: HashMap::default(), + value_manager: ValueManager { + varid_to_value: DefinitionAnalysisMap::default(), + defid_to_value: FxHashMap::default(), + expected_return_values: HashMap::default(), + expecting_value: VecDeque::default() + }, permissions, - varid_to_value: DefinitionAnalysisMap::default(), - defid_to_value: FxHashMap::default(), jira_permission_resolver, confluence_permission_resolver, jira_regex_map, @@ -616,7 +624,7 @@ impl<'cx, C: Runner<'cx>> Interp<'cx, C> { #[inline] pub fn get_defs(&self) -> DefinitionAnalysisMap { - self.varid_to_value.clone() + self.value_manager.varid_to_value.clone() } #[inline] @@ -652,7 +660,7 @@ impl<'cx, C: Runner<'cx>> Interp<'cx, C> { value: Value, projection: Option, ) { - self.varid_to_value + self.value_manager.varid_to_value .insert((defid_block, varid, projection), value); } @@ -663,7 +671,7 @@ impl<'cx, C: Runner<'cx>> Interp<'cx, C> { varid: VarId, projection: Option, ) -> Option<&Value> { - self.varid_to_value.get(&(defid_block, varid, projection)) + self.value_manager.varid_to_value.get(&(defid_block, varid, projection)) } #[inline] diff --git a/crates/forge_analyzer/src/utils.rs b/crates/forge_analyzer/src/utils.rs index 9720fab9..490fbe10 100644 --- a/crates/forge_analyzer/src/utils.rs +++ b/crates/forge_analyzer/src/utils.rs @@ -157,4 +157,4 @@ pub fn eq_prop_name(n: &MemberProp, name: &str) -> bool { }, _ => false, } -} \ No newline at end of file +} diff --git a/crates/fsrt/src/main.rs b/crates/fsrt/src/main.rs index ad4b0cdf..550d47ce 100644 --- a/crates/fsrt/src/main.rs +++ b/crates/fsrt/src/main.rs @@ -316,8 +316,8 @@ fn scan_directory(dir: PathBuf, function: Option<&str>, opts: Opts) -> Result, opts: Opts) -> Result, opts: Opts) -> Result, opts: Opts) -> Result Date: Wed, 18 Oct 2023 14:14:57 -0400 Subject: [PATCH 204/517] resolving merge conflicts with main branch --- crates/forge_analyzer/src/checkers.rs | 22 +++++++++++----------- crates/forge_analyzer/src/definitions.rs | 8 ++++---- crates/forge_analyzer/src/ir.rs | 8 +++++--- 3 files changed, 20 insertions(+), 18 deletions(-) diff --git a/crates/forge_analyzer/src/checkers.rs b/crates/forge_analyzer/src/checkers.rs index 4af1c602..e0111c07 100644 --- a/crates/forge_analyzer/src/checkers.rs +++ b/crates/forge_analyzer/src/checkers.rs @@ -97,8 +97,8 @@ impl<'cx> Dataflow<'cx> for AuthorizeDataflow { Intrinsic::JWTSign(_) | Intrinsic::Fetch | Intrinsic::ApiCustomField - | Intrinsic::ApiCall - | Intrinsic::SafeCall + | Intrinsic::ApiCall(_) + | Intrinsic::SafeCall(_) | Intrinsic::EnvRead | Intrinsic::StorageRead => initial_state, } @@ -263,7 +263,7 @@ impl<'cx> Runner<'cx> for AuthZChecker { ControlFlow::Continue(AuthorizeState::Yes) } Intrinsic::Fetch => ControlFlow::Continue(*state), - Intrinsic::ApiCall if *state != AuthorizeState::Yes => { + Intrinsic::ApiCall(_) if *state != AuthorizeState::Yes => { let vuln = AuthZVuln::new(interp.callstack(), interp.env(), interp.entry()); info!("Found a vuln!"); self.vulns.push(vuln); @@ -276,8 +276,8 @@ impl<'cx> Runner<'cx> for AuthZChecker { ControlFlow::Break(()) } Intrinsic::JWTSign(_) - | Intrinsic::ApiCall - | Intrinsic::SafeCall + | Intrinsic::ApiCall(_) + | Intrinsic::SafeCall(_) | Intrinsic::EnvRead | Intrinsic::UserFieldAccess | Intrinsic::ApiCustomField @@ -340,11 +340,11 @@ impl<'cx> Dataflow<'cx> for AuthenticateDataflow { debug!("authenticated"); Authenticated::Yes } - Intrinsic::JWTSign - | Intrinsic::ApiCall + Intrinsic::JWTSign(_) + | Intrinsic::ApiCall(_) | Intrinsic::ApiCustomField | Intrinsic::UserFieldAccess - | Intrinsic::SafeCall => initial_state, + | Intrinsic::SafeCall(_) => initial_state, } } @@ -435,17 +435,17 @@ impl<'cx> Runner<'cx> for AuthenticateChecker { debug!("authenticated"); ControlFlow::Continue(Authenticated::Yes) } - Intrinsic::ApiCall | Intrinsic::ApiCustomField if *state == Authenticated::No => { + Intrinsic::ApiCall(_) | Intrinsic::ApiCustomField if *state == Authenticated::No => { let vuln = AuthNVuln::new(interp.callstack(), interp.env(), interp.entry()); info!("Found a vuln!"); self.vulns.push(vuln); ControlFlow::Break(()) } Intrinsic::JWTSign(_) => ControlFlow::Continue(*state), - Intrinsic::ApiCall | Intrinsic::UserFieldAccess | Intrinsic::ApiCustomField => { + Intrinsic::ApiCall(_) | Intrinsic::UserFieldAccess | Intrinsic::ApiCustomField => { ControlFlow::Continue(*state) } - Intrinsic::SafeCall => ControlFlow::Continue(*state), + Intrinsic::SafeCall(_) => ControlFlow::Continue(*state), } } } diff --git a/crates/forge_analyzer/src/definitions.rs b/crates/forge_analyzer/src/definitions.rs index 9e11f3b1..b97e34c4 100644 --- a/crates/forge_analyzer/src/definitions.rs +++ b/crates/forge_analyzer/src/definitions.rs @@ -935,7 +935,7 @@ impl<'cx> FunctionAnalyzer<'cx> { match classify_api_call(first_arg) { ApiCallKind::Unknown => { if is_as_app { - Some(Intrinsic::ApiCall) + Some(Intrinsic::ApiCall(IntrinsicName::Other)) } else { Some(Intrinsic::SafeCall(function_name)) } @@ -944,7 +944,7 @@ impl<'cx> FunctionAnalyzer<'cx> { if is_as_app { Some(Intrinsic::ApiCustomField) } else { - Some(Intrinsic::SafeCall) + Some(Intrinsic::SafeCall(function_name)) } } ApiCallKind::Fields => { @@ -954,8 +954,8 @@ impl<'cx> FunctionAnalyzer<'cx> { Some(Intrinsic::UserFieldAccess) } } - ApiCallKind::Trivial => Some(Intrinsic::SafeCall), - ApiCallKind::Authorize => Some(Intrinsic::Authorize), + ApiCallKind::Trivial => Some(Intrinsic::SafeCall(IntrinsicName::Other)), + ApiCallKind::Authorize => Some(Intrinsic::Authorize(IntrinsicName::Other)), } } [PropPath::Def(def), ref authn @ .., PropPath::Static(ref last)] diff --git a/crates/forge_analyzer/src/ir.rs b/crates/forge_analyzer/src/ir.rs index 28c34e1f..92089dde 100644 --- a/crates/forge_analyzer/src/ir.rs +++ b/crates/forge_analyzer/src/ir.rs @@ -74,6 +74,8 @@ pub enum Terminator { pub enum Intrinsic { Authorize(IntrinsicName), Fetch, + UserFieldAccess, + ApiCustomField, ApiCall(IntrinsicName), SafeCall(IntrinsicName), JWTSign(PackageData), @@ -760,12 +762,12 @@ impl fmt::Display for Intrinsic { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match *self { Intrinsic::Fetch => write!(f, "fetch"), - Intrinsic::Authorize => write!(f, "authorize"), + Intrinsic::Authorize(_) => write!(f, "authorize"), Intrinsic::JWTSign(_) => write!(f, "jwt sign"), - Intrinsic::ApiCall => write!(f, "api call"), + Intrinsic::ApiCall(_) => write!(f, "api call"), Intrinsic::ApiCustomField => write!(f, "accessing custom field route asApp"), Intrinsic::UserFieldAccess => write!(f, "accessing which fields a user can access"), - Intrinsic::SafeCall => write!(f, "safe api call"), + Intrinsic::SafeCall(_) => write!(f, "safe api call"), Intrinsic::EnvRead => write!(f, "env read"), Intrinsic::StorageRead => write!(f, "forge storage read"), } From 9048b93a9d0ba4e63c7b3811d2f2e3859eb3c858 Mon Sep 17 00:00:00 2001 From: Gersbach Date: Wed, 18 Oct 2023 14:20:11 -0400 Subject: [PATCH 205/517] formatting --- crates/forge_analyzer/src/checkers.rs | 16 +++++++++++----- crates/forge_analyzer/src/interp.rs | 10 ++++++---- crates/fsrt/src/main.rs | 20 ++++++++++++++++---- 3 files changed, 33 insertions(+), 13 deletions(-) diff --git a/crates/forge_analyzer/src/checkers.rs b/crates/forge_analyzer/src/checkers.rs index e0111c07..2196b1e7 100644 --- a/crates/forge_analyzer/src/checkers.rs +++ b/crates/forge_analyzer/src/checkers.rs @@ -96,7 +96,7 @@ impl<'cx> Dataflow<'cx> for AuthorizeDataflow { } Intrinsic::JWTSign(_) | Intrinsic::Fetch - | Intrinsic::ApiCustomField + | Intrinsic::ApiCustomField | Intrinsic::ApiCall(_) | Intrinsic::SafeCall(_) | Intrinsic::EnvRead @@ -747,7 +747,9 @@ impl<'cx> Runner<'cx> for SecretChecker { } } else if let Some(value) = interp.body().vars.get(varid) { if let VarKind::GlobalRef(def) = value { - if let Some(value) = interp.value_manager.defid_to_value.get(def) { + if let Some(value) = + interp.value_manager.defid_to_value.get(def) + { println!("value [] {value:?}"); match value { Value::Const(_) | Value::Phi(_) => { @@ -1247,8 +1249,9 @@ impl<'cx> Dataflow<'cx> for DefintionAnalysisRunner { interp.body().vars.get(varid) { if let Base::Var(varid_to_assign) = var.base { - interp. - value_manager.expected_return_values + interp + .value_manager + .expected_return_values .insert(*defid, (def, varid_to_assign)); } } @@ -1297,7 +1300,10 @@ impl<'cx> Dataflow<'cx> for DefintionAnalysisRunner { Operand::Var(var) => { println!("{var}"); if let Base::Var(varid) = var.base { - let values = interp.value_manager.varid_to_value.get(&(def, varid, None)); + let values = interp + .value_manager + .varid_to_value + .get(&(def, varid, None)); println!("values: {values:?}") } } diff --git a/crates/forge_analyzer/src/interp.rs b/crates/forge_analyzer/src/interp.rs index 60b8c131..5280e650 100644 --- a/crates/forge_analyzer/src/interp.rs +++ b/crates/forge_analyzer/src/interp.rs @@ -590,7 +590,6 @@ impl<'cx, C: Runner<'cx>> Interp<'cx, C> { ) -> Self { let call_graph = CallGraph::new(env); - Self { env, call_graph, @@ -611,7 +610,7 @@ impl<'cx, C: Runner<'cx>> Interp<'cx, C> { varid_to_value: DefinitionAnalysisMap::default(), defid_to_value: FxHashMap::default(), expected_return_values: HashMap::default(), - expecting_value: VecDeque::default() + expecting_value: VecDeque::default(), }, permissions, jira_permission_resolver, @@ -660,7 +659,8 @@ impl<'cx, C: Runner<'cx>> Interp<'cx, C> { value: Value, projection: Option, ) { - self.value_manager.varid_to_value + self.value_manager + .varid_to_value .insert((defid_block, varid, projection), value); } @@ -671,7 +671,9 @@ impl<'cx, C: Runner<'cx>> Interp<'cx, C> { varid: VarId, projection: Option, ) -> Option<&Value> { - self.value_manager.varid_to_value.get(&(defid_block, varid, projection)) + self.value_manager + .varid_to_value + .get(&(defid_block, varid, projection)) } #[inline] diff --git a/crates/fsrt/src/main.rs b/crates/fsrt/src/main.rs index 49430a5c..4da1f022 100644 --- a/crates/fsrt/src/main.rs +++ b/crates/fsrt/src/main.rs @@ -316,7 +316,10 @@ fn scan_directory(dir: PathBuf, function: Option<&str>, opts: Opts) -> Result, opts: Opts) -> Result, opts: Opts) -> Result, opts: Opts) -> Result Date: Mon, 30 Oct 2023 10:35:17 -0400 Subject: [PATCH 206/517] chore: added additional known common package functions in forge apps for secret scanner --- crates/forge_analyzer/src/checkers.rs | 4 +-- crates/fsrt/src/main.rs | 1 + secretdata.yaml | 32 +++++++++++++++++++ .../src/index.jsx | 2 +- 4 files changed, 36 insertions(+), 3 deletions(-) diff --git a/crates/forge_analyzer/src/checkers.rs b/crates/forge_analyzer/src/checkers.rs index 2196b1e7..aa16bb4e 100644 --- a/crates/forge_analyzer/src/checkers.rs +++ b/crates/forge_analyzer/src/checkers.rs @@ -1298,13 +1298,13 @@ impl<'cx> Dataflow<'cx> for DefintionAnalysisRunner { } } Operand::Var(var) => { - println!("{var}"); + // println!("{var}"); if let Base::Var(varid) = var.base { let values = interp .value_manager .varid_to_value .get(&(def, varid, None)); - println!("values: {values:?}") + // println!("values: {values:?}") } } } diff --git a/crates/fsrt/src/main.rs b/crates/fsrt/src/main.rs index 4da1f022..5a306f90 100644 --- a/crates/fsrt/src/main.rs +++ b/crates/fsrt/src/main.rs @@ -415,6 +415,7 @@ fn scan_directory(dir: PathBuf, function: Option<&str>, opts: Opts) -> Result Result<()> { + println!("I'm in Main"); let args = Args::parse(); tracing_subscriber::registry() .with(HierarchicalLayer::new(2)) diff --git a/secretdata.yaml b/secretdata.yaml index 11865b0b..19480660 100644 --- a/secretdata.yaml +++ b/secretdata.yaml @@ -1,6 +1,38 @@ +# Data Structure explained +# - package_name: name of package being checked +# function_name: name of the function that takes the secret data +# secret_position: which argument takes in the secret data + - package_name: jsonwebtoken function_name: sign secret_position: 2 - package_name: jwt-simple function_name: encode secret_position: 2 +- package_name: alassian-jwt + function_name: encodeSymmetric + secret_position: 2 +- package_name: alassian-jwt + function_name: encodeAsymmetric + secret_position: 2 +- package_name: crypto-js + function_name: HmacMD5 + secret_position: 2 +- package_name: crypto-js + function_name: HmacSHA1 + secret_position: 2 +- package_name: crypto-js + function_name: HmacSHA256 + secret_position: 2 +- package_name: crypto-js + function_name: HmacSHA512 + secret_position: 2 +- package_name: crypto-js + function_name: PBKDF2 + secret_position: 1 +- package_name: crypto-js + function_name: encrypt + secret_position: 2 +- package_name: crypto-js + function_name: decrypt + secret_position: 2 diff --git a/test-apps/jira-damn-vulnerable-forge-app/src/index.jsx b/test-apps/jira-damn-vulnerable-forge-app/src/index.jsx index fca8fbde..130663bb 100755 --- a/test-apps/jira-damn-vulnerable-forge-app/src/index.jsx +++ b/test-apps/jira-damn-vulnerable-forge-app/src/index.jsx @@ -135,7 +135,7 @@ export function runWebTrigger({ method, path, headers }, { installContext }) { const secret = storage.getSecret('sharedSecret'); console.log(`secret: ${secret}`); try { - jwt.verify(token, secret, { + jwt.verify(token, "secret :O", { algorithms: ['HS256', 'HS512'], audience: installContext, }); From 85838ba52f47d3493df08f3c5384d88c883f4300 Mon Sep 17 00:00:00 2001 From: awang7 Date: Mon, 6 Nov 2023 11:46:14 -0500 Subject: [PATCH 207/517] feat: added scanning in secret scanner for packages imported using star method --- crates/forge_analyzer/src/definitions.rs | 108 ++++++++++++----- secretdata.yaml | 59 +++++----- .../package-lock.json | 70 +++++++++++ .../package.json | 3 + .../src/index.jsx | 14 ++- .../src/utils.js | 111 ++++++++++++------ 6 files changed, 270 insertions(+), 95 deletions(-) diff --git a/crates/forge_analyzer/src/definitions.rs b/crates/forge_analyzer/src/definitions.rs index b97e34c4..be6ae356 100644 --- a/crates/forge_analyzer/src/definitions.rs +++ b/crates/forge_analyzer/src/definitions.rs @@ -33,10 +33,11 @@ use swc_core::{ VarDeclarator, WhileStmt, WithStmt, YieldExpr, }, atoms::{Atom, JsWord}, + utils::function, visit::{noop_visit_type, Visit, VisitWith}, }, }; -use tracing::{debug, info, instrument, warn}; +use tracing::{debug, field::debug, info, instrument, warn}; use typed_index_collections::{TiSlice, TiVec}; /** @@ -571,6 +572,7 @@ pub enum IntrinsicName { #[derive(Serialize, Deserialize, PartialEq, Debug, Clone, Eq)] pub struct PackageData { pub package_name: String, + pub identifier: String, pub function_name: String, pub secret_position: u8, } @@ -920,10 +922,11 @@ impl<'cx> FunctionAnalyzer<'cx> { } match *callee { [PropPath::Unknown((ref name, ..))] if *name == *"fetch" => Some(Intrinsic::Fetch), + // [PropPath::Def(def)] => {}] [PropPath::Def(def), ref authn @ .., PropPath::Static(ref last)] if (*last == *"requestJira" || *last == *"requestConfluence") && Some(&ImportKind::Default) - == self.res.as_foreign_import(def, "@forge/api") => + == self.res.is_imported_from(def, "@forge/api") => { let function_name = if *last == String::from("requestJira") { IntrinsicName::RequestJira @@ -959,36 +962,67 @@ impl<'cx> FunctionAnalyzer<'cx> { } } [PropPath::Def(def), ref authn @ .., PropPath::Static(ref last)] - if self.secret_packages.iter().any(|package_data| { - return *package_data.function_name == *last - && Some(&ImportKind::Default) - == self.res.as_foreign_import(def, &package_data.package_name); - }) => + if self + .secret_packages + .iter() + .any(|ref package_data| *package_data.function_name == *last) => { - Some(Intrinsic::JWTSign( - self.secret_packages - .iter() - .cloned() - .filter(|package| { - *package.function_name == *last - && Some(&ImportKind::Default) - == self.res.as_foreign_import(def, &package.package_name) - }) - .collect_vec() - .get(0) - .unwrap() - .clone(), - )) + debug!("last: {last}"); + let package = self + .secret_packages + .iter() + .filter(|package_data| *package_data.function_name == *last) + .next() + .unwrap(); + + let check = self.res.is_imported_from(def, &package.package_name); + debug!("checkingggg: {check:?}"); + debug!("package.identifier: {package:?}"); + match self.res.is_imported_from(def, &package.package_name) { + Some(ImportKind::Default) if *package.identifier == *"default" => { + return Some(Intrinsic::JWTSign( + self.secret_packages + .iter() + .cloned() + .filter(|package| { + *package.function_name == *last + && self.res.is_imported_from(def, &package.package_name) + == Some(&ImportKind::Default) + }) + .collect_vec() + .get(0) + .unwrap() + .clone(), + )); + } + Some(ImportKind::Star) if *package.identifier == *"star" => { + Some(Intrinsic::JWTSign( + self.secret_packages + .iter() + .cloned() + .filter(|package| { + *package.function_name == *last + && self.res.is_imported_from(def, &package.package_name) + == Some(&ImportKind::Star) + }) + .collect_vec() + .get(0) + .unwrap() + .clone(), + )) + } + _ => None, + } } [PropPath::Def(def), PropPath::Static(ref s), ..] if is_storage_read(s) => { - match self.res.as_foreign_import(def, "@forge/api") { + match self.res.is_imported_from(def, "@forge/api") { Some(ImportKind::Named(ref name)) if *name == *"storage" => { Some(Intrinsic::StorageRead) } _ => None, } } - [PropPath::Def(def), ..] => match self.res.as_foreign_import(def, "@forge/api") { + [PropPath::Def(def), ..] => match self.res.is_imported_from(def, "@forge/api") { Some(ImportKind::Named(ref name)) if *name == *"authorize" => { Some(Intrinsic::Authorize(IntrinsicName::Other)) } @@ -1176,7 +1210,7 @@ impl<'cx> FunctionAnalyzer<'cx> { fn lower_call(&mut self, callee: CalleeRef<'_>, args: &[ExprOrSpread]) -> Operand { let props = normalize_callee_expr(callee, self.res, self.module); if let Some(&PropPath::Def(id)) = props.first() { - if self.res.as_foreign_import(id, "@forge/ui").map_or( + if self.res.is_imported_from(id, "@forge/ui").map_or( false, |imp| matches!(imp, ImportKind::Named(s) if *s == *"useState" || *s == *"useEffect"), ) || calls_method(callee, "then") @@ -1227,6 +1261,10 @@ impl<'cx> FunctionAnalyzer<'cx> { }; let first_arg = args.first().map(|expr| &*expr.expr); + // debug!("checking in lower_call. Think it's not getting classified correctly during lowering and IDK why"); + // debug!("first_arg in lower call: {first_arg:?}"); + let intrinsic = self.as_intrinsic(&props, first_arg); + debug!("WHAT FaUCKING INTRINSIC IS THIS: {intrinsic:?}"); let call = match self.as_intrinsic(&props, first_arg) { Some(int) => Rvalue::Intrinsic(int, lowered_args), None => Rvalue::Call(callee, lowered_args), @@ -2239,7 +2277,7 @@ impl Visit for FunctionCollector<'_> { let Some(def) = self.res.sym_to_id(ident, self.module) else { return; }; - if matches!(self.res.as_foreign_import(def, "@forge/ui"), Some(ImportKind::Named(imp)) if *imp == *"render") + if matches!(self.res.is_imported_from(def, "@forge/ui"), Some(ImportKind::Named(imp)) if *imp == *"render") { let owner = self.res @@ -3379,10 +3417,26 @@ impl Environment { } } + pub fn as_foreign_import(&self, def: DefId) -> Option<(JsWord, ImportKind)> { + let DefKind::Foreign(f) = self.def_ref(def) else { + return None; + }; + Some((f.module_name.clone(), f.kind.clone())) + } + /// Check if def is exported from the foreign module specified in `module_name`. - pub fn as_foreign_import(&self, def: DefId, module_name: &str) -> Option<&ImportKind> { + pub fn is_imported_from(&self, def: DefId, module_name: &str) -> Option<&ImportKind> { + // let thing = self.def_ref(def) == DefKind::Foreign(f); + // debug!("def id thing: {thing:?}"); match self.def_ref(def) { - DefKind::Foreign(f) if f.module_name == *module_name => Some(&f.kind), + DefKind::Foreign(f) if f.module_name == *module_name => { + let return_value = Some(&f.kind); + debug!( + "Entered match statement. Does evaluate true! return_value: {return_value:?}" + ); + + Some(&f.kind) + } _ => None, } } diff --git a/secretdata.yaml b/secretdata.yaml index 19480660..b2f3121e 100644 --- a/secretdata.yaml +++ b/secretdata.yaml @@ -1,38 +1,43 @@ # Data Structure explained # - package_name: name of package being checked +# identifier: type of import # function_name: name of the function that takes the secret data # secret_position: which argument takes in the secret data - package_name: jsonwebtoken - function_name: sign + identifier: default + function_name: Some secret_position: 2 -- package_name: jwt-simple - function_name: encode - secret_position: 2 -- package_name: alassian-jwt +- package_name: atlassian-jwt + identifier: star function_name: encodeSymmetric secret_position: 2 -- package_name: alassian-jwt +- package_name: atlassian-jwt + identifier: star function_name: encodeAsymmetric secret_position: 2 -- package_name: crypto-js - function_name: HmacMD5 - secret_position: 2 -- package_name: crypto-js - function_name: HmacSHA1 - secret_position: 2 -- package_name: crypto-js - function_name: HmacSHA256 - secret_position: 2 -- package_name: crypto-js - function_name: HmacSHA512 - secret_position: 2 -- package_name: crypto-js - function_name: PBKDF2 - secret_position: 1 -- package_name: crypto-js - function_name: encrypt - secret_position: 2 -- package_name: crypto-js - function_name: decrypt - secret_position: 2 +# - package_name: crypto-js +# function_name: HmacMD5 +# secret_position: 2 +# - package_name: crypto-js +# function_name: HmacSHA1 +# secret_position: 2 +# - package_name: crypto-js +# function_name: HmacSHA256 +# secret_position: 2 +# - package_name: crypto-js +# function_name: HmacSHA512 +# secret_position: 2 +# - package_name: crypto-js +# function_name: PBKDF2 +# secret_position: 1 +# - package_name: crypto-js +# function_name: encrypt +# secret_position: 2 +# - package_name: crypto-js +# function_name: decrypt +# secret_position: 2 +# - package_name: jwt-simple +# function_name: encode +# secret_position: 2 + diff --git a/test-apps/jira-damn-vulnerable-forge-app/package-lock.json b/test-apps/jira-damn-vulnerable-forge-app/package-lock.json index 36f27786..25a88362 100755 --- a/test-apps/jira-damn-vulnerable-forge-app/package-lock.json +++ b/test-apps/jira-damn-vulnerable-forge-app/package-lock.json @@ -11,6 +11,9 @@ "dependencies": { "@forge/api": "^2.7.0", "@forge/ui": "1.2.1", + "atlassian-jwt": "^2.0.2", + "crypto-js": "^4.2.0", + "install": "^0.13.0", "jsonwebtoken": "^8.5.1" }, "devDependencies": { @@ -5905,6 +5908,18 @@ "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" }, + "node_modules/atlassian-jwt": { + "version": "2.0.2", + "resolved": "https://packages.atlassian.com/api/npm/npm-remote/atlassian-jwt/-/atlassian-jwt-2.0.2.tgz", + "integrity": "sha512-HgG2p+gJeEqKREnPynEDy76UoShF01jTYMSe0NlRL2rEqXBNDzJiiCyUP7P2cbxcRmI4OC8g40zJjqftFNJ/4A==", + "dependencies": { + "jsuri": "^1.3.1", + "lodash": "^4.17.21" + }, + "engines": { + "node": ">= 0.4.0" + } + }, "node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", @@ -6051,6 +6066,11 @@ "node": ">= 8" } }, + "node_modules/crypto-js": { + "version": "4.2.0", + "resolved": "https://packages.atlassian.com/api/npm/npm-remote/crypto-js/-/crypto-js-4.2.0.tgz", + "integrity": "sha512-KALDyEYgpY+Rlob/iriUtjV6d5Eq+Y191A5g4UqLAi8CyGP9N1+FdVbkc1SxKc2r4YAYqG8JzO2KGL+AizD70Q==" + }, "node_modules/debug": { "version": "4.3.2", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.2.tgz", @@ -6521,6 +6541,14 @@ "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" }, + "node_modules/install": { + "version": "0.13.0", + "resolved": "https://packages.atlassian.com/api/npm/npm-remote/install/-/install-0.13.0.tgz", + "integrity": "sha512-zDml/jzr2PKU9I8J/xyZBQn8rPCAY//UOYNmR01XwNwyfhEWObo2SWfSl1+0tm1u6PhxLwDnfsT/6jB7OUxqFA==", + "engines": { + "node": ">= 0.10" + } + }, "node_modules/is-extglob": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", @@ -6617,6 +6645,14 @@ "semver": "bin/semver" } }, + "node_modules/jsuri": { + "version": "1.3.1", + "resolved": "https://packages.atlassian.com/api/npm/npm-remote/jsuri/-/jsuri-1.3.1.tgz", + "integrity": "sha512-LLdAeqOf88/X0hylAI7oSir6QUsz/8kOW0FcJzzu/SJRfORA/oPHycAOthkNp7eLPlTAbqVDFbqNRHkRVzEA3g==", + "engines": { + "node": "*" + } + }, "node_modules/jwa": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.1.tgz", @@ -6649,6 +6685,11 @@ "node": ">= 0.8.0" } }, + "node_modules/lodash": { + "version": "4.17.21", + "resolved": "https://packages.atlassian.com/api/npm/npm-remote/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" + }, "node_modules/lodash.clonedeep": { "version": "4.5.0", "resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz", @@ -13199,6 +13240,15 @@ "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" }, + "atlassian-jwt": { + "version": "2.0.2", + "resolved": "https://packages.atlassian.com/api/npm/npm-remote/atlassian-jwt/-/atlassian-jwt-2.0.2.tgz", + "integrity": "sha512-HgG2p+gJeEqKREnPynEDy76UoShF01jTYMSe0NlRL2rEqXBNDzJiiCyUP7P2cbxcRmI4OC8g40zJjqftFNJ/4A==", + "requires": { + "jsuri": "^1.3.1", + "lodash": "^4.17.21" + } + }, "balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", @@ -13317,6 +13367,11 @@ "which": "^2.0.1" } }, + "crypto-js": { + "version": "4.2.0", + "resolved": "https://packages.atlassian.com/api/npm/npm-remote/crypto-js/-/crypto-js-4.2.0.tgz", + "integrity": "sha512-KALDyEYgpY+Rlob/iriUtjV6d5Eq+Y191A5g4UqLAi8CyGP9N1+FdVbkc1SxKc2r4YAYqG8JzO2KGL+AizD70Q==" + }, "debug": { "version": "4.3.2", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.2.tgz", @@ -13673,6 +13728,11 @@ "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" }, + "install": { + "version": "0.13.0", + "resolved": "https://packages.atlassian.com/api/npm/npm-remote/install/-/install-0.13.0.tgz", + "integrity": "sha512-zDml/jzr2PKU9I8J/xyZBQn8rPCAY//UOYNmR01XwNwyfhEWObo2SWfSl1+0tm1u6PhxLwDnfsT/6jB7OUxqFA==" + }, "is-extglob": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", @@ -13752,6 +13812,11 @@ } } }, + "jsuri": { + "version": "1.3.1", + "resolved": "https://packages.atlassian.com/api/npm/npm-remote/jsuri/-/jsuri-1.3.1.tgz", + "integrity": "sha512-LLdAeqOf88/X0hylAI7oSir6QUsz/8kOW0FcJzzu/SJRfORA/oPHycAOthkNp7eLPlTAbqVDFbqNRHkRVzEA3g==" + }, "jwa": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.1.tgz", @@ -13781,6 +13846,11 @@ "type-check": "~0.4.0" } }, + "lodash": { + "version": "4.17.21", + "resolved": "https://packages.atlassian.com/api/npm/npm-remote/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" + }, "lodash.clonedeep": { "version": "4.5.0", "resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz", diff --git a/test-apps/jira-damn-vulnerable-forge-app/package.json b/test-apps/jira-damn-vulnerable-forge-app/package.json index 69be5981..38101a18 100755 --- a/test-apps/jira-damn-vulnerable-forge-app/package.json +++ b/test-apps/jira-damn-vulnerable-forge-app/package.json @@ -18,6 +18,9 @@ "dependencies": { "@forge/api": "^2.7.0", "@forge/ui": "1.2.1", + "atlassian-jwt": "^2.0.2", + "crypto-js": "^4.2.0", + "install": "^0.13.0", "jsonwebtoken": "^8.5.1" } } diff --git a/test-apps/jira-damn-vulnerable-forge-app/src/index.jsx b/test-apps/jira-damn-vulnerable-forge-app/src/index.jsx index 130663bb..98ac94bb 100755 --- a/test-apps/jira-damn-vulnerable-forge-app/src/index.jsx +++ b/test-apps/jira-damn-vulnerable-forge-app/src/index.jsx @@ -18,7 +18,7 @@ import api, { webTrigger, route, storage, properties } from '@forge/api'; import { createHash } from 'crypto'; import jwt from 'jsonwebtoken'; import { fetchIssueSummary } from './utils'; -import {IssuePanelApp} from './IssuePanelApp'; +import { IssuePanelApp } from './IssuePanelApp'; function SharedSecretForm() { const [hashedSecret, setHashedSecret] = useState(null); @@ -98,8 +98,11 @@ function SecureGlance() { return ''; } const [flagVal] = useState(async () => { - const issueData = await fetchIssueSummary(platformContext.issueKey, "test_value_passed_in_as_argument"); - const test = writeComment("test", "test"); + const issueData = await fetchIssueSummary( + platformContext.issueKey, + 'test_value_passed_in_as_argument' + ); + const test = writeComment('test', 'test'); return JSON.stringify(issueData + test); }); @@ -115,7 +118,7 @@ function SecureGlance() { export const glance = render( - + ); @@ -134,8 +137,9 @@ export function runWebTrigger({ method, path, headers }, { installContext }) { } const secret = storage.getSecret('sharedSecret'); console.log(`secret: ${secret}`); + // Trying various libraries requiring secrets to test try { - jwt.verify(token, "secret :O", { + jwt.verify(token, 'secret :O', { algorithms: ['HS256', 'HS512'], audience: installContext, }); diff --git a/test-apps/jira-damn-vulnerable-forge-app/src/utils.js b/test-apps/jira-damn-vulnerable-forge-app/src/utils.js index 21f71950..7333ea93 100755 --- a/test-apps/jira-damn-vulnerable-forge-app/src/utils.js +++ b/test-apps/jira-damn-vulnerable-forge-app/src/utils.js @@ -1,76 +1,115 @@ import api, { route } from '@forge/api'; -import jwt from "jsonwebtoken"; - -import {testFunctionFromTestFile} from './testfile'; -import module_exports_func from './moduleex.js' -import {func_from_exports, diffunc} from "./exportse.js" -import {another_export, newExport} from './newexports.js' +import jwt from 'jsonwebtoken'; +import * as atlassian_jwt from 'atlassian-jwt'; + +import { testFunctionFromTestFile } from './testfile'; +import module_exports_func from './moduleex.js'; +import { func_from_exports, diffunc } from './exportse.js'; +// foo = DefId(22) +import { another_export as foo, newExport } from './newexports.js'; import func_defult from './export_default'; -import my_function from "./export_default2.js"; -import {classone} from './anewclass'; - - - -let global = "test"; +import my_function from './export_default2.js'; +import { classone } from './anewclass'; +import * as c1 from './anewclass'; +/* +import {default as something} from what; +import something from what; + +packagename: jsonwebtoken +identifier: default +type: Object("sign") +position: 4 + +function require(input) {} +require('foo'); + +type: Enum { + Function, + Object(String), // <- method_name +} +*/ + +// import HmacMD5 from 'crypto-js'; +// DefId(4) + +// DEFINITIONS +// 0 -> name +// 0 -> Definition(enum) + +// function chair() { +// DefId(22) +//as_foreign_import(DefId(22), 'newexports')) -> Some(ImportKind("another_export")) +// foo(); +// +// [DefId(4), Static('blah'), Static('open')] +// atlassian_jwt['blah' + 'test'](); +// atlassian_jwt.blah.open(); +// atlassian_jwt['blah'](); + +// const blah = 'sign'; +// atlassian_jwt[blah](); +// atlassian_jwt.sign(); +// } + +// var jwt = require('jwt-simple'); +let global = 'test'; +let CryptoJS = require('crypto-js'); export async function fetchIssueSummary(issueIdOrKey, test_value) { - let obj = { method: 'POST', bananas: 'apple', - headers: { // + headers: { + // Accept: 'application/json', }, }; + module_exports_func(); + func_from_exports(); + another_export(); + newExport(); + func_defult(); + my_function(); + let global = 'test'; - - - module_exports_func() - func_from_exports() - another_export() - newExport() - func_defult() - my_function() - let global = "test"; - - var token = jwt.sign({ foo: 'bar' }, global); + // testing all the libraries + var token = jwt.sign({ foo: 'bar' }, 'peek a boo'); + // var hmac = HmacMD5('Secret Message', 'HMAC PASSWORD'); + var token = atlassian_jwt.encodeSymmetric({ foo: 'bar' }, 'Atlassian jwt'); + // var simple_token = jwt.encode({ foo: 'bar' }, 'Simple JWT'); // testFunctionFromTestFile(); - diffunc() + diffunc(); // different_function(); let a_class = new ANewClass(); a_class.function_a_new_class(); - let val = "grapefruit"; + let val = 'grapefruit'; - val = "peach"; + val = 'peach'; - let pre_url = "/rest/api/3/issue/" + val; + let pre_url = '/rest/api/3/issue/' + val; let a_url = route`/rest/api/3/issue/${issueIdOrKey}?fields=summary/${val}`; - const resp = await api - .asApp() - .requestJira(global, obj); + const resp = await api.asApp().requestJira(global, obj); const data = await resp.json(); console.log(JSON.stringify(data)); return data['fields']['summary']; } function get_random_string() { - //return a_url = route`/rest/api/3/issue/${issueIdOrKey}?fields=summary`; - return "test_string_from_get_random_string"; + return 'test_string_from_get_random_string'; } export async function writeComment(issueIdOrKey, comment) { /* const api = require('@forge/api'); */ - // ERROR, even if this is not assigned anything then it will assign the param to the issueIdOrKey var - let issueIdOrKey = testFunctionFromTestFile("test_value_param") + let issueIdOrKey = testFunctionFromTestFile('test_value_param'); let my_class = new UselessClass(); From 4907cbfe5ad040535a94c4ea948d307a1747c3ab Mon Sep 17 00:00:00 2001 From: awang7 Date: Mon, 6 Nov 2023 11:51:06 -0500 Subject: [PATCH 208/517] chore: removed reptitive return statement --- crates/forge_analyzer/src/definitions.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/forge_analyzer/src/definitions.rs b/crates/forge_analyzer/src/definitions.rs index be6ae356..2579c968 100644 --- a/crates/forge_analyzer/src/definitions.rs +++ b/crates/forge_analyzer/src/definitions.rs @@ -980,7 +980,7 @@ impl<'cx> FunctionAnalyzer<'cx> { debug!("package.identifier: {package:?}"); match self.res.is_imported_from(def, &package.package_name) { Some(ImportKind::Default) if *package.identifier == *"default" => { - return Some(Intrinsic::JWTSign( + Some(Intrinsic::JWTSign( self.secret_packages .iter() .cloned() @@ -993,7 +993,7 @@ impl<'cx> FunctionAnalyzer<'cx> { .get(0) .unwrap() .clone(), - )); + )) } Some(ImportKind::Star) if *package.identifier == *"star" => { Some(Intrinsic::JWTSign( From 1b22ccd6280f448a0e0017da19dd3da09baa0df0 Mon Sep 17 00:00:00 2001 From: awang7 Date: Mon, 6 Nov 2023 11:53:41 -0500 Subject: [PATCH 209/517] chore: removed repetitive code and simplified returned Intrinsic objects. And deleted excess debug statements --- crates/forge_analyzer/src/definitions.rs | 34 ++---------------------- 1 file changed, 2 insertions(+), 32 deletions(-) diff --git a/crates/forge_analyzer/src/definitions.rs b/crates/forge_analyzer/src/definitions.rs index 2579c968..9fd0855f 100644 --- a/crates/forge_analyzer/src/definitions.rs +++ b/crates/forge_analyzer/src/definitions.rs @@ -967,7 +967,6 @@ impl<'cx> FunctionAnalyzer<'cx> { .iter() .any(|ref package_data| *package_data.function_name == *last) => { - debug!("last: {last}"); let package = self .secret_packages .iter() @@ -975,41 +974,12 @@ impl<'cx> FunctionAnalyzer<'cx> { .next() .unwrap(); - let check = self.res.is_imported_from(def, &package.package_name); - debug!("checkingggg: {check:?}"); - debug!("package.identifier: {package:?}"); match self.res.is_imported_from(def, &package.package_name) { Some(ImportKind::Default) if *package.identifier == *"default" => { - Some(Intrinsic::JWTSign( - self.secret_packages - .iter() - .cloned() - .filter(|package| { - *package.function_name == *last - && self.res.is_imported_from(def, &package.package_name) - == Some(&ImportKind::Default) - }) - .collect_vec() - .get(0) - .unwrap() - .clone(), - )) + Some(Intrinsic::JWTSign(package.clone())) } Some(ImportKind::Star) if *package.identifier == *"star" => { - Some(Intrinsic::JWTSign( - self.secret_packages - .iter() - .cloned() - .filter(|package| { - *package.function_name == *last - && self.res.is_imported_from(def, &package.package_name) - == Some(&ImportKind::Star) - }) - .collect_vec() - .get(0) - .unwrap() - .clone(), - )) + Some(Intrinsic::JWTSign(package.clone())) } _ => None, } From c8a52bc09a7842d949def0e480eede870badeccf Mon Sep 17 00:00:00 2001 From: awang7 Date: Mon, 6 Nov 2023 16:25:47 -0500 Subject: [PATCH 210/517] feat: added crypto-js to secretdata.yaml scanning list. And updated DVFA with test cases --- secretdata.yaml | 57 +++++++++++-------- .../src/utils.js | 9 ++- 2 files changed, 36 insertions(+), 30 deletions(-) diff --git a/secretdata.yaml b/secretdata.yaml index b2f3121e..f3061f98 100644 --- a/secretdata.yaml +++ b/secretdata.yaml @@ -16,28 +16,35 @@ identifier: star function_name: encodeAsymmetric secret_position: 2 -# - package_name: crypto-js -# function_name: HmacMD5 -# secret_position: 2 -# - package_name: crypto-js -# function_name: HmacSHA1 -# secret_position: 2 -# - package_name: crypto-js -# function_name: HmacSHA256 -# secret_position: 2 -# - package_name: crypto-js -# function_name: HmacSHA512 -# secret_position: 2 -# - package_name: crypto-js -# function_name: PBKDF2 -# secret_position: 1 -# - package_name: crypto-js -# function_name: encrypt -# secret_position: 2 -# - package_name: crypto-js -# function_name: decrypt -# secret_position: 2 -# - package_name: jwt-simple -# function_name: encode -# secret_position: 2 - +- package_name: crypto-js + identifier: star + function_name: HmacMD5 + secret_position: 2 +- package_name: crypto-js + identifier: star + function_name: HmacSHA1 + secret_position: 2 +- package_name: crypto-js + identifier: star + function_name: HmacSHA256 + secret_position: 2 +- package_name: crypto-js + identifier: star + function_name: HmacSHA512 + secret_position: 2 +- package_name: crypto-js + identifier: star + function_name: PBKDF2 + secret_position: 1 +- package_name: crypto-js + identifier: star + function_name: encrypt + secret_position: 2 +- package_name: crypto-js + identifier: star + function_name: decrypt + secret_position: 2 +- package_name: jwt-simple + identifier: star + function_name: encode + secret_position: 2 diff --git a/test-apps/jira-damn-vulnerable-forge-app/src/utils.js b/test-apps/jira-damn-vulnerable-forge-app/src/utils.js index 7333ea93..0375eec5 100755 --- a/test-apps/jira-damn-vulnerable-forge-app/src/utils.js +++ b/test-apps/jira-damn-vulnerable-forge-app/src/utils.js @@ -1,11 +1,11 @@ import api, { route } from '@forge/api'; import jwt from 'jsonwebtoken'; import * as atlassian_jwt from 'atlassian-jwt'; - +import * as cryptoJS from 'crypto-js'; +import * as jwtSimple from 'jwt-simple'; import { testFunctionFromTestFile } from './testfile'; import module_exports_func from './moduleex.js'; import { func_from_exports, diffunc } from './exportse.js'; -// foo = DefId(22) import { another_export as foo, newExport } from './newexports.js'; import func_defult from './export_default'; import my_function from './export_default2.js'; @@ -51,9 +51,7 @@ type: Enum { // atlassian_jwt.sign(); // } -// var jwt = require('jwt-simple'); let global = 'test'; -let CryptoJS = require('crypto-js'); export async function fetchIssueSummary(issueIdOrKey, test_value) { let obj = { @@ -74,9 +72,10 @@ export async function fetchIssueSummary(issueIdOrKey, test_value) { let global = 'test'; // testing all the libraries - var token = jwt.sign({ foo: 'bar' }, 'peek a boo'); + // var token = jwt.sign({ foo: 'bar' }, 'peek a boo'); // var hmac = HmacMD5('Secret Message', 'HMAC PASSWORD'); var token = atlassian_jwt.encodeSymmetric({ foo: 'bar' }, 'Atlassian jwt'); + var aes = cryptoJS.AES.encrypt('Secret messege', 'secret password'); // var simple_token = jwt.encode({ foo: 'bar' }, 'Simple JWT'); // testFunctionFromTestFile(); From 4afe8b453bd8f92521507708dcfd9ae2d999dc50 Mon Sep 17 00:00:00 2001 From: awang7 Date: Tue, 7 Nov 2023 22:42:41 -0500 Subject: [PATCH 211/517] feat: updated secretdata.yaml with new flag identifier to catch object exports. added new scanning pathway for star imports where prop path is identifier or method with children object imports. --- crates/forge_analyzer/src/definitions.rs | 137 ++++++++++++++++++++--- secretdata.yaml | 82 ++++++++++---- 2 files changed, 180 insertions(+), 39 deletions(-) diff --git a/crates/forge_analyzer/src/definitions.rs b/crates/forge_analyzer/src/definitions.rs index 9fd0855f..bd6c2d9d 100644 --- a/crates/forge_analyzer/src/definitions.rs +++ b/crates/forge_analyzer/src/definitions.rs @@ -573,7 +573,7 @@ pub enum IntrinsicName { pub struct PackageData { pub package_name: String, pub identifier: String, - pub function_name: String, + pub method: Option, pub secret_position: u8, } @@ -961,29 +961,132 @@ impl<'cx> FunctionAnalyzer<'cx> { ApiCallKind::Authorize => Some(Intrinsic::Authorize(IntrinsicName::Other)), } } + // Case 1: the “root” object is a star import and the Static proppath member is either identifier [PropPath::Def(def), ref authn @ .., PropPath::Static(ref last)] - if self - .secret_packages - .iter() - .any(|ref package_data| *package_data.function_name == *last) => + if self.secret_packages.iter().any(|ref package_data| { + if let Some(tuple) = self.res.as_foreign_import(def) { + return tuple.0 == package_data.package_name && tuple.1 == ImportKind::Star; + } else { + return false; + } + }) => { - let package = self - .secret_packages - .iter() - .filter(|package_data| *package_data.function_name == *last) - .next() - .unwrap(); - - match self.res.is_imported_from(def, &package.package_name) { - Some(ImportKind::Default) if *package.identifier == *"default" => { - Some(Intrinsic::JWTSign(package.clone())) + debug!("what's in authn? {authn:?}"); + match authn[0] { + PropPath::Static(ref object) + if self + .secret_packages + .iter() + .filter(|ref package_data| { + if let Some(tuple) = self.res.as_foreign_import(def) { + return tuple.0 == package_data.package_name + && tuple.1 == ImportKind::Star; + } else { + return false; + } + }) + .any(|package_data| *package_data.identifier == *object) => + { + let package = self + .secret_packages + .iter() + .filter(|ref package_data| { + if let Some(tuple) = self.res.as_foreign_import(def) { + return tuple.0 == package_data.package_name + && tuple.1 == ImportKind::Star + && *package_data.identifier == *object; + } else { + return false; + } + }) + .next() + .unwrap(); + if let Some(method) = &package.method { + if *method == **last { + Some(Intrinsic::JWTSign(package.clone())) + } else { + None + } + } else { + None + } } - Some(ImportKind::Star) if *package.identifier == *"star" => { - Some(Intrinsic::JWTSign(package.clone())) + + _ if self.secret_packages.iter().any(|ref package_data| { + if let Some(tuple) = self.res.as_foreign_import(def) { + return tuple.0 == package_data.package_name + && tuple.1 == ImportKind::Star + && *package_data.identifier == *last + && package_data.method == None; + } else { + return false; + } + }) => + { + let package = self + .secret_packages + .iter() + .filter(|ref package_data| { + if let Some(tuple) = self.res.as_foreign_import(def) { + return tuple.0 == package_data.package_name + && tuple.1 == ImportKind::Star + && *package_data.identifier == *last + && package_data.method == None; + } else { + return false; + } + }) + .next(); + + if package.is_none() { + None + } else { + Some(Intrinsic::JWTSign(package.unwrap().clone())) + } } _ => None, } } + + // Case 2: The case where the root object is a named or default import, + // in which case, you only need to check PackageData if it is an object + // [PropPath::Def(def), ref authn @ .., PropPath::Static(ref last)] + // if self.secret_packages.iter().any(|ref package_data| { + // if let Some(tuple) = self.res.as_foreign_import(def) { + // return tuple.0 == package_data.package_name + // && (tuple.1 != ImportKind::Star); + // } else { + // return false; + // } + // }) => + // { + // debug!("what's last? {last}"); + // debug!("what's in the DEFID THING!: {def:?}"); + // let package = self + // .secret_packages + // .iter() + // .filter(|ref package_data| { + // if let Some(tuple) = self.res.as_foreign_import(def) { + // return tuple.0 == package_data.package_name + // && (tuple.1 != ImportKind::Star); + // } else { + // return false; + // } + // }) + // .next() + // .unwrap(); + // let def_kind = self.res.as_foreign_import(def); + // debug!("What's the defkind? {def_kind:?}"); + // match self.res.is_imported_from(def, &package.package_name) { + // Some(ImportKind::Default) if *package.identifier == *"default" => { + // Some(Intrinsic::JWTSign(package.clone())) + // } + // Some(ImportKind::Star) if *package.identifier == *"star" => { + // Some(Intrinsic::JWTSign(package.clone())) + // } + // _ => None, + // } + // } [PropPath::Def(def), PropPath::Static(ref s), ..] if is_storage_read(s) => { match self.res.is_imported_from(def, "@forge/api") { Some(ImportKind::Named(ref name)) if *name == *"storage" => { diff --git a/secretdata.yaml b/secretdata.yaml index f3061f98..4275e413 100644 --- a/secretdata.yaml +++ b/secretdata.yaml @@ -5,46 +5,84 @@ # secret_position: which argument takes in the secret data - package_name: jsonwebtoken - identifier: default - function_name: Some + identifier: sign secret_position: 2 - package_name: atlassian-jwt - identifier: star - function_name: encodeSymmetric + identifier: encodeSymmetric secret_position: 2 - package_name: atlassian-jwt - identifier: star - function_name: encodeAsymmetric + identifier: encodeAsymmetric + method: secret_position: 2 - package_name: crypto-js - identifier: star - function_name: HmacMD5 + identifier: HmacMD5 + method: secret_position: 2 - package_name: crypto-js - identifier: star - function_name: HmacSHA1 + identifier: HmacSHA1 + method: secret_position: 2 - package_name: crypto-js - identifier: star - function_name: HmacSHA256 + identifier: HmacSHA256 + method: secret_position: 2 - package_name: crypto-js - identifier: star - function_name: HmacSHA512 + identifier: HmacSHA512 + method: secret_position: 2 - package_name: crypto-js - identifier: star - function_name: PBKDF2 + identifier: PBKDF2 + method: secret_position: 1 - package_name: crypto-js - identifier: star - function_name: encrypt + identifier: AES + method: encrypt secret_position: 2 - package_name: crypto-js - identifier: star - function_name: decrypt + identifier: AES + method: decrypt + secret_position: 2 +- package_name: crypto-js + identifier: DES + method: encrypt + secret_position: 2 +- package_name: crypto-js + identifier: DES + method: decrypt + secret_position: 2 +- package_name: crypto-js + identifier: TripleDES + method: encrypt + secret_position: 2 +- package_name: crypto-js + identifier: TripleDES + method: decrypt + secret_position: 2 +- package_name: crypto-js + identifier: Rabbit + method: encrypt + secret_position: 2 +- package_name: crypto-js + identifier: Rabbit + method: decrypt + secret_position: 2 +- package_name: crypto-js + identifier: RC4 + method: encrypt + secret_position: 2 +- package_name: crypto-js + identifier: RC4 + method: decrypt + secret_position: 2 +- package_name: crypto-js + identifier: RC4DROP + method: encrypt + secret_position: 2 +- package_name: crypto-js + identifier: RC4Drop + method: decrypt secret_position: 2 - package_name: jwt-simple - identifier: star - function_name: encode + identifier: encode + method: secret_position: 2 From 731968dfd04eb96a58b005c7931d6ebae22e9bc1 Mon Sep 17 00:00:00 2001 From: awang7 Date: Wed, 8 Nov 2023 14:06:29 -0500 Subject: [PATCH 212/517] feat: added feature to catch secrets in functions from default imported packages. Working on case to catch functions called from packages in nameed imports --- crates/forge_analyzer/src/definitions.rs | 74 ++++++++++++------------ 1 file changed, 36 insertions(+), 38 deletions(-) diff --git a/crates/forge_analyzer/src/definitions.rs b/crates/forge_analyzer/src/definitions.rs index bd6c2d9d..8d3dc7fc 100644 --- a/crates/forge_analyzer/src/definitions.rs +++ b/crates/forge_analyzer/src/definitions.rs @@ -971,7 +971,7 @@ impl<'cx> FunctionAnalyzer<'cx> { } }) => { - debug!("what's in authn? {authn:?}"); + // debug!("what's in authn? {authn:?}"); match authn[0] { PropPath::Static(ref object) if self @@ -1050,43 +1050,41 @@ impl<'cx> FunctionAnalyzer<'cx> { // Case 2: The case where the root object is a named or default import, // in which case, you only need to check PackageData if it is an object - // [PropPath::Def(def), ref authn @ .., PropPath::Static(ref last)] - // if self.secret_packages.iter().any(|ref package_data| { - // if let Some(tuple) = self.res.as_foreign_import(def) { - // return tuple.0 == package_data.package_name - // && (tuple.1 != ImportKind::Star); - // } else { - // return false; - // } - // }) => - // { - // debug!("what's last? {last}"); - // debug!("what's in the DEFID THING!: {def:?}"); - // let package = self - // .secret_packages - // .iter() - // .filter(|ref package_data| { - // if let Some(tuple) = self.res.as_foreign_import(def) { - // return tuple.0 == package_data.package_name - // && (tuple.1 != ImportKind::Star); - // } else { - // return false; - // } - // }) - // .next() - // .unwrap(); - // let def_kind = self.res.as_foreign_import(def); - // debug!("What's the defkind? {def_kind:?}"); - // match self.res.is_imported_from(def, &package.package_name) { - // Some(ImportKind::Default) if *package.identifier == *"default" => { - // Some(Intrinsic::JWTSign(package.clone())) - // } - // Some(ImportKind::Star) if *package.identifier == *"star" => { - // Some(Intrinsic::JWTSign(package.clone())) - // } - // _ => None, - // } - // } + [PropPath::Def(def), ref authn @ .., PropPath::Static(ref last)] + if self.secret_packages.iter().any(|ref package_data| { + if let Some(tuple) = self.res.as_foreign_import(def) { + return tuple.0 == package_data.package_name && tuple.1 != ImportKind::Star; + } else { + return false; + } + }) => + { + debug!("Entering case 2!!!"); + let mut package = self.secret_packages.iter().filter(|ref package_data| { + if let Some(tuple) = self.res.as_foreign_import(def) { + return tuple.0 == package_data.package_name + && (tuple.1 != ImportKind::Star) + && *last == package_data.identifier; + } else { + return false; + } + }); + + if let Some(obj) = package.next() { + Some(Intrinsic::JWTSign(obj.clone())) + } else { + None + } + // match self.res.is_imported_from(def, &package.package_name) { + // Some(ImportKind::Default) if *package.identifier == *"default" => { + // Some(Intrinsic::JWTSign(package.clone())) + // } + // Some(ImportKind::Star) if *package.identifier == *"star" => { + // Some(Intrinsic::JWTSign(package.clone())) + // } + // _ => None, + // } + } [PropPath::Def(def), PropPath::Static(ref s), ..] if is_storage_read(s) => { match self.res.is_imported_from(def, "@forge/api") { Some(ImportKind::Named(ref name)) if *name == *"storage" => { From 9e9dfc676964f032acb5b68d90c41e10ae7ab9f6 Mon Sep 17 00:00:00 2001 From: awang7 Date: Thu, 9 Nov 2023 18:04:55 -0500 Subject: [PATCH 213/517] feat: added secret scanning for Named imports. TODO: check for edge cases and add more tests --- crates/forge_analyzer/src/definitions.rs | 63 ++++++++++++++++++------ crates/fsrt/src/main.rs | 21 ++++---- 2 files changed, 58 insertions(+), 26 deletions(-) diff --git a/crates/forge_analyzer/src/definitions.rs b/crates/forge_analyzer/src/definitions.rs index 8d3dc7fc..bdae8935 100644 --- a/crates/forge_analyzer/src/definitions.rs +++ b/crates/forge_analyzer/src/definitions.rs @@ -971,7 +971,6 @@ impl<'cx> FunctionAnalyzer<'cx> { } }) => { - // debug!("what's in authn? {authn:?}"); match authn[0] { PropPath::Static(ref object) if self @@ -1053,18 +1052,18 @@ impl<'cx> FunctionAnalyzer<'cx> { [PropPath::Def(def), ref authn @ .., PropPath::Static(ref last)] if self.secret_packages.iter().any(|ref package_data| { if let Some(tuple) = self.res.as_foreign_import(def) { - return tuple.0 == package_data.package_name && tuple.1 != ImportKind::Star; + return tuple.0 == package_data.package_name + && tuple.1 == ImportKind::Default; } else { return false; } }) => { - debug!("Entering case 2!!!"); let mut package = self.secret_packages.iter().filter(|ref package_data| { if let Some(tuple) = self.res.as_foreign_import(def) { return tuple.0 == package_data.package_name - && (tuple.1 != ImportKind::Star) - && *last == package_data.identifier; + && self.res.is_import(tuple.1, None) + && *last == *package_data.identifier; } else { return false; } @@ -1075,15 +1074,6 @@ impl<'cx> FunctionAnalyzer<'cx> { } else { None } - // match self.res.is_imported_from(def, &package.package_name) { - // Some(ImportKind::Default) if *package.identifier == *"default" => { - // Some(Intrinsic::JWTSign(package.clone())) - // } - // Some(ImportKind::Star) if *package.identifier == *"star" => { - // Some(Intrinsic::JWTSign(package.clone())) - // } - // _ => None, - // } } [PropPath::Def(def), PropPath::Static(ref s), ..] if is_storage_read(s) => { match self.res.is_imported_from(def, "@forge/api") { @@ -1093,6 +1083,32 @@ impl<'cx> FunctionAnalyzer<'cx> { _ => None, } } + // Case 2: The case where the root object is a named or default import, + // in which case, you only need to check PackageData if it is an object + // where iimport type is Named + [PropPath::Def(def), ..] if self.res.as_foreign_import(def).is_some() => { + if let Some((package_name, import_kind)) = self.res.as_foreign_import(def) { + // Debug LOG when finding the darn thing + // package_name: Atom('crypto-js' type=dynamic) + // import_kind: Named(Atom('HmacMD5' type=inline)) + let package = self + .secret_packages + .iter() + .filter(|ref package_data| { + *package_name == *package_data.package_name + && self.res.is_import( + import_kind.clone(), + Some(package_data.identifier.clone()), + ) + }) + .next(); + if package != None { + return Some(Intrinsic::JWTSign(package.unwrap().clone())); + } + } + + None + } [PropPath::Def(def), ..] => match self.res.is_imported_from(def, "@forge/api") { Some(ImportKind::Named(ref name)) if *name == *"authorize" => { Some(Intrinsic::Authorize(IntrinsicName::Other)) @@ -1332,10 +1348,10 @@ impl<'cx> FunctionAnalyzer<'cx> { }; let first_arg = args.first().map(|expr| &*expr.expr); - // debug!("checking in lower_call. Think it's not getting classified correctly during lowering and IDK why"); + // debug!("props in lower call: {props:?}"); // debug!("first_arg in lower call: {first_arg:?}"); let intrinsic = self.as_intrinsic(&props, first_arg); - debug!("WHAT FaUCKING INTRINSIC IS THIS: {intrinsic:?}"); + // debug!("Intrinsic Returned: {intrinsic:?}"); let call = match self.as_intrinsic(&props, first_arg) { Some(int) => Rvalue::Intrinsic(int, lowered_args), None => Rvalue::Call(callee, lowered_args), @@ -3487,6 +3503,21 @@ impl Environment { DefKind::Undefined => DefKind::Undefined, } } + pub fn is_import(&self, import_kind: ImportKind, named_import: Option) -> bool { + // debug!("{named_import}"); + match import_kind { + ImportKind::Star => true, + ImportKind::Default => true, + ImportKind::Named(ref name) => { + if let Some(import_name) = named_import { + return *name == *import_name; + } else { + false + } + } + _ => false, + } + } pub fn as_foreign_import(&self, def: DefId) -> Option<(JsWord, ImportKind)> { let DefKind::Foreign(f) = self.def_ref(def) else { diff --git a/crates/fsrt/src/main.rs b/crates/fsrt/src/main.rs index 5a306f90..efb97d52 100644 --- a/crates/fsrt/src/main.rs +++ b/crates/fsrt/src/main.rs @@ -230,8 +230,6 @@ fn scan_directory(dir: PathBuf, function: Option<&str>, opts: Opts) -> Result, opts: Opts) -> Result { let mut runner = DefintionAnalysisRunner::new(); - debug!("checking {func} at {path:?}"); + debug!("checking Invokable {func} at {path:?}"); if let Err(err) = defintion_analysis_interp.run_checker( def, &mut runner, @@ -307,7 +305,7 @@ fn scan_directory(dir: PathBuf, function: Option<&str>, opts: Opts) -> Result, opts: Opts) -> Result, opts: Opts) -> Result, opts: Opts) -> Result { let mut runner = DefintionAnalysisRunner::new(); - debug!("checking {func} at {path:?}"); + debug!("checking Web Trigger {func} at {path:?}"); if let Err(err) = defintion_analysis_interp.run_checker( def, &mut runner, @@ -360,7 +359,7 @@ fn scan_directory(dir: PathBuf, function: Option<&str>, opts: Opts) -> Result, opts: Opts) -> Result, opts: Opts) -> Result, opts: Opts) -> Result { fs::write(path, report).into_diagnostic()?; @@ -415,7 +417,6 @@ fn scan_directory(dir: PathBuf, function: Option<&str>, opts: Opts) -> Result Result<()> { - println!("I'm in Main"); let args = Args::parse(); tracing_subscriber::registry() .with(HierarchicalLayer::new(2)) From 0f1d9f79c004716f4db1978a0362970edaa5cfbb Mon Sep 17 00:00:00 2001 From: awang7 Date: Mon, 20 Nov 2023 17:04:23 -0500 Subject: [PATCH 214/517] fixed: revised fix_import to check for default and named functions. Added more test cases for named imports. todo: add more edge cases and review is_import --- crates/forge_analyzer/src/definitions.rs | 48 +++++++++---------- secretdata.yaml | 5 ++ .../src/utils.js | 44 ++--------------- 3 files changed, 32 insertions(+), 65 deletions(-) diff --git a/crates/forge_analyzer/src/definitions.rs b/crates/forge_analyzer/src/definitions.rs index bdae8935..6b644fe7 100644 --- a/crates/forge_analyzer/src/definitions.rs +++ b/crates/forge_analyzer/src/definitions.rs @@ -971,8 +971,11 @@ impl<'cx> FunctionAnalyzer<'cx> { } }) => { - match authn[0] { - PropPath::Static(ref object) + match authn.get(0) { + // case where function requires object to be invoked first + // example: import * as cryptoJS from 'crypto-js'; + // var aes = cryptoJS.AES.encrypt('Secret message', 'secret password'); + Some(PropPath::Static(ref object)) if self .secret_packages .iter() @@ -1002,6 +1005,7 @@ impl<'cx> FunctionAnalyzer<'cx> { .unwrap(); if let Some(method) = &package.method { if *method == **last { + debug!("bug caught on package: {package:?}"); Some(Intrinsic::JWTSign(package.clone())) } else { None @@ -1011,6 +1015,9 @@ impl<'cx> FunctionAnalyzer<'cx> { } } + // Star imports that don't have any object call to invoke function + // import * as atlassian_jwt from "atlassian-jwt"; + // atlassian_jwt.encode(blah, blah); _ if self.secret_packages.iter().any(|ref package_data| { if let Some(tuple) = self.res.as_foreign_import(def) { return tuple.0 == package_data.package_name @@ -1040,6 +1047,7 @@ impl<'cx> FunctionAnalyzer<'cx> { if package.is_none() { None } else { + debug!("bug caught on package: {package:?}"); Some(Intrinsic::JWTSign(package.unwrap().clone())) } } @@ -1047,13 +1055,14 @@ impl<'cx> FunctionAnalyzer<'cx> { } } - // Case 2: The case where the root object is a named or default import, + // Case 2: The case where the root object is default import, // in which case, you only need to check PackageData if it is an object [PropPath::Def(def), ref authn @ .., PropPath::Static(ref last)] if self.secret_packages.iter().any(|ref package_data| { if let Some(tuple) = self.res.as_foreign_import(def) { - return tuple.0 == package_data.package_name - && tuple.1 == ImportKind::Default; + let checking_value = tuple.1.to_string(); + return *tuple.0 == *package_data.package_name + && self.res.is_import(Some(tuple.1.to_string())); } else { return false; } @@ -1062,7 +1071,7 @@ impl<'cx> FunctionAnalyzer<'cx> { let mut package = self.secret_packages.iter().filter(|ref package_data| { if let Some(tuple) = self.res.as_foreign_import(def) { return tuple.0 == package_data.package_name - && self.res.is_import(tuple.1, None) + && self.res.is_import(Some(tuple.1.to_string())) && *last == *package_data.identifier; } else { return false; @@ -1091,15 +1100,13 @@ impl<'cx> FunctionAnalyzer<'cx> { // Debug LOG when finding the darn thing // package_name: Atom('crypto-js' type=dynamic) // import_kind: Named(Atom('HmacMD5' type=inline)) + let package = self .secret_packages .iter() .filter(|ref package_data| { *package_name == *package_data.package_name - && self.res.is_import( - import_kind.clone(), - Some(package_data.identifier.clone()), - ) + && import_kind.to_string() == package_data.identifier }) .next(); if package != None { @@ -3503,18 +3510,11 @@ impl Environment { DefKind::Undefined => DefKind::Undefined, } } - pub fn is_import(&self, import_kind: ImportKind, named_import: Option) -> bool { - // debug!("{named_import}"); + pub fn is_import(&self, import_kind: Option) -> bool { match import_kind { - ImportKind::Star => true, - ImportKind::Default => true, - ImportKind::Named(ref name) => { - if let Some(import_name) = named_import { - return *name == *import_name; - } else { - false - } - } + Some(default) if default == "default" => true, + Some(named) if named == "named" => true, + Some(star) => false, _ => false, } } @@ -3533,9 +3533,9 @@ impl Environment { match self.def_ref(def) { DefKind::Foreign(f) if f.module_name == *module_name => { let return_value = Some(&f.kind); - debug!( - "Entered match statement. Does evaluate true! return_value: {return_value:?}" - ); + // debug!( + // "Entered match statement. Does evaluate true! return_value: {return_value:?}" + // ); Some(&f.kind) } diff --git a/secretdata.yaml b/secretdata.yaml index 4275e413..083a19f1 100644 --- a/secretdata.yaml +++ b/secretdata.yaml @@ -6,7 +6,12 @@ - package_name: jsonwebtoken identifier: sign + method: secret_position: 2 +# - package_name: jsonwebtoken +# identifier: verify +# method: +# secret_position: 2 - package_name: atlassian-jwt identifier: encodeSymmetric secret_position: 2 diff --git a/test-apps/jira-damn-vulnerable-forge-app/src/utils.js b/test-apps/jira-damn-vulnerable-forge-app/src/utils.js index 0375eec5..8d62a5c2 100755 --- a/test-apps/jira-damn-vulnerable-forge-app/src/utils.js +++ b/test-apps/jira-damn-vulnerable-forge-app/src/utils.js @@ -3,6 +3,7 @@ import jwt from 'jsonwebtoken'; import * as atlassian_jwt from 'atlassian-jwt'; import * as cryptoJS from 'crypto-js'; import * as jwtSimple from 'jwt-simple'; +import { HmacMD5 } from 'crypto-js'; import { testFunctionFromTestFile } from './testfile'; import module_exports_func from './moduleex.js'; import { func_from_exports, diffunc } from './exportse.js'; @@ -11,45 +12,6 @@ import func_defult from './export_default'; import my_function from './export_default2.js'; import { classone } from './anewclass'; import * as c1 from './anewclass'; -/* -import {default as something} from what; -import something from what; - -packagename: jsonwebtoken -identifier: default -type: Object("sign") -position: 4 - -function require(input) {} -require('foo'); - -type: Enum { - Function, - Object(String), // <- method_name -} -*/ - -// import HmacMD5 from 'crypto-js'; -// DefId(4) - -// DEFINITIONS -// 0 -> name -// 0 -> Definition(enum) - -// function chair() { -// DefId(22) -//as_foreign_import(DefId(22), 'newexports')) -> Some(ImportKind("another_export")) -// foo(); -// -// [DefId(4), Static('blah'), Static('open')] -// atlassian_jwt['blah' + 'test'](); -// atlassian_jwt.blah.open(); -// atlassian_jwt['blah'](); - -// const blah = 'sign'; -// atlassian_jwt[blah](); -// atlassian_jwt.sign(); -// } let global = 'test'; @@ -74,8 +36,8 @@ export async function fetchIssueSummary(issueIdOrKey, test_value) { // testing all the libraries // var token = jwt.sign({ foo: 'bar' }, 'peek a boo'); // var hmac = HmacMD5('Secret Message', 'HMAC PASSWORD'); - var token = atlassian_jwt.encodeSymmetric({ foo: 'bar' }, 'Atlassian jwt'); - var aes = cryptoJS.AES.encrypt('Secret messege', 'secret password'); + // var token = atlassian_jwt.encodeSymmetric({ foo: 'bar' }, 'Atlassian jwt'); + // var aes = cryptoJS.AES.encrypt('Secret message', 'secret password'); // var simple_token = jwt.encode({ foo: 'bar' }, 'Simple JWT'); // testFunctionFromTestFile(); From 7df35f3ecdec6990b50d721dc2e6f17352050925 Mon Sep 17 00:00:00 2001 From: awang7 Date: Mon, 27 Nov 2023 15:55:14 -0500 Subject: [PATCH 215/517] chore: cleaned up test cases in util.js. Added verify to secretdata.yaml. Todo: resolve edge case where same secret scanner vuln got reported twice - after adding jsonwebtoken funtion verify --- crates/forge_analyzer/src/definitions.rs | 28 ++++++++----- secretdata.yaml | 9 +++-- .../src/utils.js | 40 ++++++++++++++----- 3 files changed, 53 insertions(+), 24 deletions(-) diff --git a/crates/forge_analyzer/src/definitions.rs b/crates/forge_analyzer/src/definitions.rs index 6b644fe7..ddb883b9 100644 --- a/crates/forge_analyzer/src/definitions.rs +++ b/crates/forge_analyzer/src/definitions.rs @@ -1061,6 +1061,11 @@ impl<'cx> FunctionAnalyzer<'cx> { if self.secret_packages.iter().any(|ref package_data| { if let Some(tuple) = self.res.as_foreign_import(def) { let checking_value = tuple.1.to_string(); + // debug!("TUPLE VALUES: {tuple:?} checking_value of to_string: {checking_value}"); + // debug!("PACKAGE: {package_data:?}"); + // let boolean_value = tuple.0 == package_data.package_name; + // let is_import_value = self.res.is_import(Some(tuple.1.to_string())); + // debug!("WHAT IS GOING ON WHY EVERYTHING FAILING!? Boolean value = {boolean_value} is_import_value: {is_import_value}"); return *tuple.0 == *package_data.package_name && self.res.is_import(Some(tuple.1.to_string())); } else { @@ -1070,6 +1075,8 @@ impl<'cx> FunctionAnalyzer<'cx> { { let mut package = self.secret_packages.iter().filter(|ref package_data| { if let Some(tuple) = self.res.as_foreign_import(def) { + // debug!("LAST: {last}"); + // debug!("PACKAGE: {package_data:?}"); return tuple.0 == package_data.package_name && self.res.is_import(Some(tuple.1.to_string())) && *last == *package_data.identifier; @@ -1079,6 +1086,8 @@ impl<'cx> FunctionAnalyzer<'cx> { }); if let Some(obj) = package.next() { + // debug!("bug caught on package: {obj:?}"); + // debug!("tf is last!? {last}"); Some(Intrinsic::JWTSign(obj.clone())) } else { None @@ -1110,6 +1119,12 @@ impl<'cx> FunctionAnalyzer<'cx> { }) .next(); if package != None { + // debug!("Chicken!"); + // let is_import_value = self.res.is_import(Some(import_kind.to_string())); + // debug!("import kind !>>>!>??@?? {import_kind}"); + // debug!("checking is_import value: {is_import_value}"); + // debug!("bug caught on package: {package:?}"); + // debug!("tf is callee!? {callee:?}"); return Some(Intrinsic::JWTSign(package.unwrap().clone())); } } @@ -3514,7 +3529,7 @@ impl Environment { match import_kind { Some(default) if default == "default" => true, Some(named) if named == "named" => true, - Some(star) => false, + Some(star) if star == "star" => false, _ => false, } } @@ -3528,17 +3543,8 @@ impl Environment { /// Check if def is exported from the foreign module specified in `module_name`. pub fn is_imported_from(&self, def: DefId, module_name: &str) -> Option<&ImportKind> { - // let thing = self.def_ref(def) == DefKind::Foreign(f); - // debug!("def id thing: {thing:?}"); match self.def_ref(def) { - DefKind::Foreign(f) if f.module_name == *module_name => { - let return_value = Some(&f.kind); - // debug!( - // "Entered match statement. Does evaluate true! return_value: {return_value:?}" - // ); - - Some(&f.kind) - } + DefKind::Foreign(f) if f.module_name == *module_name => Some(&f.kind), _ => None, } } diff --git a/secretdata.yaml b/secretdata.yaml index 083a19f1..7d4c77b8 100644 --- a/secretdata.yaml +++ b/secretdata.yaml @@ -8,12 +8,13 @@ identifier: sign method: secret_position: 2 -# - package_name: jsonwebtoken -# identifier: verify -# method: -# secret_position: 2 +- package_name: jsonwebtoken + identifier: verify + method: + secret_position: 2 - package_name: atlassian-jwt identifier: encodeSymmetric + method: secret_position: 2 - package_name: atlassian-jwt identifier: encodeAsymmetric diff --git a/test-apps/jira-damn-vulnerable-forge-app/src/utils.js b/test-apps/jira-damn-vulnerable-forge-app/src/utils.js index 8d62a5c2..e05b103f 100755 --- a/test-apps/jira-damn-vulnerable-forge-app/src/utils.js +++ b/test-apps/jira-damn-vulnerable-forge-app/src/utils.js @@ -1,9 +1,4 @@ import api, { route } from '@forge/api'; -import jwt from 'jsonwebtoken'; -import * as atlassian_jwt from 'atlassian-jwt'; -import * as cryptoJS from 'crypto-js'; -import * as jwtSimple from 'jwt-simple'; -import { HmacMD5 } from 'crypto-js'; import { testFunctionFromTestFile } from './testfile'; import module_exports_func from './moduleex.js'; import { func_from_exports, diffunc } from './exportse.js'; @@ -13,6 +8,17 @@ import my_function from './export_default2.js'; import { classone } from './anewclass'; import * as c1 from './anewclass'; +// Secret Scanner Default Imports +import jwt from 'jsonwebtoken'; +import * as atlassian_jwt from 'atlassian-jwt'; + +// Secret Scanner Star Imports +// import * as cryptoJS from 'crypto-js'; +// import * as jwtSimple from 'jwt-simple'; + +// Secret Scanner Named Imports +import { HmacSHA256 } from 'crypto-js'; + let global = 'test'; export async function fetchIssueSummary(issueIdOrKey, test_value) { @@ -33,16 +39,24 @@ export async function fetchIssueSummary(issueIdOrKey, test_value) { my_function(); let global = 'test'; - // testing all the libraries + // Function calls from default imports // var token = jwt.sign({ foo: 'bar' }, 'peek a boo'); - // var hmac = HmacMD5('Secret Message', 'HMAC PASSWORD'); // var token = atlassian_jwt.encodeSymmetric({ foo: 'bar' }, 'Atlassian jwt'); + + // Function calls from star imports // var aes = cryptoJS.AES.encrypt('Secret message', 'secret password'); - // var simple_token = jwt.encode({ foo: 'bar' }, 'Simple JWT'); + // var simple_token = jwtSimple.encode({ foo: 'bar' }, 'Simple JWT'); + + // Function calls from named imports + var hmac = HmacSHA256('Secret Message', 'HMAC PASSWORD'); + + // calling edge case + console.log(sign()); + console.log('End secret scanning test cases'); // testFunctionFromTestFile(); - diffunc(); + // diffunc(); // different_function(); let a_class = new ANewClass(); @@ -62,6 +76,14 @@ export async function fetchIssueSummary(issueIdOrKey, test_value) { return data['fields']['summary']; } +// add Secret Scanner Edge cases here + +// TODO: Calling function with same name as function from Secret Scanner +function sign() { + console.log('this is a test function'); + return 'test function'; +} + function get_random_string() { return 'test_string_from_get_random_string'; } From f4168415a83241854a40f84eb1e745da67624fd6 Mon Sep 17 00:00:00 2001 From: Joshua Wong Date: Tue, 28 Nov 2023 00:38:04 -0600 Subject: [PATCH 216/517] chore: bump deps --- Cargo.lock | 477 ++++++++++++++++++----------- Cargo.toml | 45 ++- crates/forge_analyzer/Cargo.toml | 1 - crates/forge_analyzer/src/utils.rs | 9 - 4 files changed, 322 insertions(+), 210 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 71775572..d744f974 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -57,6 +57,54 @@ dependencies = [ "libc", ] +[[package]] +name = "anstream" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ab91ebe16eb252986481c5b62f6098f3b698a45e34b5b98200cf20dd2484a44" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7079075b41f533b8c61d2a4d073c4676e1f8b249ff94a393b0595db304e0dd87" + +[[package]] +name = "anstyle-parse" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "317b9a89c1868f5ea6ff1d9539a69f45dffc21ce321ac1fd1160dfa48c8e2140" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ca11d4be1bab0c8bc8734a9aa7bf4ee8316d462a08c6ac5052f888fef5b494b" +dependencies = [ + "windows-sys 0.48.0", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0699d10d2f4d628a98ee7b57b289abbc98ff3bad977cb3152709d4bf2330628" +dependencies = [ + "anstyle", + "windows-sys 0.48.0", +] + [[package]] name = "anyhow" version = "1.0.68" @@ -154,6 +202,12 @@ version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" +[[package]] +name = "bitflags" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "327762f6e5a765692301e5bb513e0d9fef63be86bbc14528052b1cd3e6f03e07" + [[package]] name = "block-buffer" version = "0.10.3" @@ -173,7 +227,7 @@ dependencies = [ "anyhow", "chrono", "either", - "itertools", + "itertools 0.10.5", "js-sys", "nom", "once_cell", @@ -222,41 +276,44 @@ dependencies = [ [[package]] name = "clap" -version = "4.1.1" +version = "4.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ec7a4128863c188deefe750ac1d1dfe66c236909f845af04beed823638dc1b2" +checksum = "2275f18819641850fa26c89acc84d465c1bf91ce57bc2748b28c420473352f64" dependencies = [ - "bitflags", + "clap_builder", "clap_derive", +] + +[[package]] +name = "clap_builder" +version = "4.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07cdf1b148b25c1e1f7a42225e30a0d99a615cd4637eae7365548dd4529b95bc" +dependencies = [ + "anstream", + "anstyle", "clap_lex", - "is-terminal", - "once_cell", "strsim", - "termcolor", - "terminal_size 0.2.3", + "terminal_size 0.3.0", ] [[package]] name = "clap_derive" -version = "4.1.0" +version = "4.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "684a277d672e91966334af371f1a7b5833f9aa00b07c84e92fbce95e00208ce8" +checksum = "cf9804afaaf59a91e75b022a30fb7229a7901f60c755489cc61c9b423b836442" dependencies = [ "heck", - "proc-macro-error", "proc-macro2", "quote", - "syn 1.0.107", + "syn 2.0.28", ] [[package]] name = "clap_lex" -version = "0.3.1" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "783fe232adfca04f90f56201b26d79682d4cd2625e0bc7290b95123afe558ade" -dependencies = [ - "os_str_bytes", -] +checksum = "702fc72eb24e5a1e48ce58027a675bc24edd52096d5397d4aea7c6dd9eca0bd1" [[package]] name = "codespan-reporting" @@ -268,6 +325,12 @@ dependencies = [ "unicode-width", ] +[[package]] +name = "colorchoice" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" + [[package]] name = "core-foundation-sys" version = "0.8.3" @@ -345,16 +408,6 @@ dependencies = [ "typenum", ] -[[package]] -name = "ctor" -version = "0.1.26" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d2301688392eb071b0bf1a37be05c469d3cc4dbbd95df672fe28ab021e6a096" -dependencies = [ - "quote", - "syn 1.0.107", -] - [[package]] name = "cxx" version = "1.0.86" @@ -441,12 +494,22 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "907076dfda823b0b36d2a1bb5f90c96660a5bbcd7729e10727f07858f22c4edc" dependencies = [ "cfg-if", - "hashbrown", + "hashbrown 0.12.3", "lock_api", "once_cell", "parking_lot_core", ] +[[package]] +name = "deranged" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f32d04922c60427da6f9fef14d042d9edddef64cb9d4ce0d64d0685fbeb1fd3" +dependencies = [ + "powerfmt", + "serde", +] + [[package]] name = "diff" version = "0.1.13" @@ -511,24 +574,19 @@ dependencies = [ ] [[package]] -name = "errno" -version = "0.2.8" +name = "equivalent" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f639046355ee4f37944e44f60642c6f3a7efa3cf6b78c78a0d989a8ce6c396a1" -dependencies = [ - "errno-dragonfly", - "libc", - "winapi", -] +checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" [[package]] -name = "errno-dragonfly" -version = "0.1.2" +name = "errno" +version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf" +checksum = "f258a7194e7f7c2a7837a8913aeab7fd8c383457034fa20ce4dd3dcb813e8eb8" dependencies = [ - "cc", "libc", + "windows-sys 0.48.0", ] [[package]] @@ -561,7 +619,7 @@ dependencies = [ "forge_file_resolver", "forge_permission_resolver", "forge_utils", - "itertools", + "itertools 0.12.0", "num-bigint", "once_cell", "petgraph", @@ -570,9 +628,8 @@ dependencies = [ "serde", "serde_json", "smallvec", - "stacker", "swc_core", - "time 0.3.17", + "time 0.3.30", "tracing", "typed-index-collections", ] @@ -586,7 +643,7 @@ name = "forge_loader" version = "0.1.0" dependencies = [ "forge_utils", - "itertools", + "itertools 0.12.0", "miette 5.5.0", "pretty_assertions", "serde", @@ -612,7 +669,7 @@ dependencies = [ name = "forge_utils" version = "0.1.0" dependencies = [ - "indexmap", + "indexmap 2.1.0", "rustc-hash", ] @@ -705,6 +762,12 @@ dependencies = [ "ahash", ] +[[package]] +name = "hashbrown" +version = "0.14.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f93e7192158dbcda357bdec5fb5788eebf8bbac027f3f33e719d29135ae84156" + [[package]] name = "heck" version = "0.4.0" @@ -782,18 +845,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1885e79c1fc4b10f0e172c475f458b7f7b93061064d98c3293e98c5ba0c8b399" dependencies = [ "autocfg", - "hashbrown", + "hashbrown 0.12.3", "serde", ] [[package]] -name = "io-lifetimes" -version = "1.0.4" +name = "indexmap" +version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7d6c6f8c91b4b9ed43484ad1a938e393caf35960fce7f82a040497207bd8e9e" +checksum = "d530e1a18b1cb4c484e6e34556a0d948706958449fca0cab753d649f2bce3d1f" dependencies = [ - "libc", - "windows-sys", + "equivalent", + "hashbrown 0.14.2", ] [[package]] @@ -809,18 +872,6 @@ dependencies = [ "syn 1.0.107", ] -[[package]] -name = "is-terminal" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28dfb6c8100ccc63462345b67d1bbc3679177c75ee4bf59bf29c8b1d110b8189" -dependencies = [ - "hermit-abi 0.2.6", - "io-lifetimes", - "rustix", - "windows-sys", -] - [[package]] name = "is_ci" version = "1.1.1" @@ -836,6 +887,15 @@ dependencies = [ "either", ] +[[package]] +name = "itertools" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25db6b064527c5d482d0423354fcd07a89a2dfe07b67892e62411946db7f07b0" +dependencies = [ + "either", +] + [[package]] name = "itoa" version = "1.0.5" @@ -941,9 +1001,9 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.147" +version = "0.2.150" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4668fb0ea861c1df094127ac5f1da3409a82116a4ba74fca2e58ef927159bb3" +checksum = "89d92a4743f9a61002fae18374ed11e7973f530cb3a3255fb354818118b2203c" [[package]] name = "link-cplusplus" @@ -956,9 +1016,9 @@ dependencies = [ [[package]] name = "linux-raw-sys" -version = "0.1.4" +version = "0.4.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f051f77a7c8e6957c0696eac88f26b0117e54f52d3fc682ab19397a8812846a4" +checksum = "969488b55f8ac402214f3f5fd243ebb7206cf82de60d3172994707a4bcc2b829" [[package]] name = "lock_api" @@ -985,7 +1045,7 @@ version = "0.7.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e999beba7b6e8345721bd280141ed958096a2e4abdf74f67ff4ce49b4b54e47a" dependencies = [ - "hashbrown", + "hashbrown 0.12.3", ] [[package]] @@ -1133,6 +1193,15 @@ dependencies = [ "winapi", ] +[[package]] +name = "nu-ansi-term" +version = "0.49.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c073d3c1930d0751774acf49e66653acecb416c3a54c6ec095a9b11caddb5a68" +dependencies = [ + "windows-sys 0.48.0", +] + [[package]] name = "num-bigint" version = "0.4.3" @@ -1198,21 +1267,6 @@ version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6f61fba1741ea2b3d6a1e3178721804bb716a68a6aeba1149b5d52e3d464ea66" -[[package]] -name = "os_str_bytes" -version = "6.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b7820b9daea5457c9f21c69448905d723fbd21136ccf521748f23fd49e723ee" - -[[package]] -name = "output_vt100" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "628223faebab4e3e40667ee0b2336d34a5b960ff60ea743ddfdbcf7770bcfb66" -dependencies = [ - "winapi", -] - [[package]] name = "overload" version = "0.1.1" @@ -1245,7 +1299,7 @@ dependencies = [ "libc", "redox_syscall", "smallvec", - "windows-sys", + "windows-sys 0.42.0", ] [[package]] @@ -1273,7 +1327,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6d5014253a1331579ce62aa67443b4a658c5e7dd03d4bc6d302b94474888143" dependencies = [ "fixedbitset", - "indexmap", + "indexmap 1.9.2", ] [[package]] @@ -1337,6 +1391,12 @@ dependencies = [ "syn 1.0.107", ] +[[package]] +name = "powerfmt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" + [[package]] name = "ppv-lite86" version = "0.2.17" @@ -1369,13 +1429,11 @@ dependencies = [ [[package]] name = "pretty_assertions" -version = "1.3.0" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a25e9bcb20aa780fd0bb16b72403a9064d6b3f22f026946029acb941a50af755" +checksum = "af7cee1a6c8a5b9208b3cb1061f10c0cb689087b3d8ce85fb9d2dd7a29b6ba66" dependencies = [ - "ctor", "diff", - "output_vt100", "yansi", ] @@ -1418,15 +1476,6 @@ dependencies = [ "unicode-ident", ] -[[package]] -name = "psm" -version = "0.1.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5787f7cda34e3033a72192c018bc5883100330f362ef279a8cbccfce8bb4e874" -dependencies = [ - "cc", -] - [[package]] name = "quote" version = "1.0.32" @@ -1500,7 +1549,7 @@ version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" dependencies = [ - "bitflags", + "bitflags 1.3.2", ] [[package]] @@ -1573,16 +1622,15 @@ dependencies = [ [[package]] name = "rustix" -version = "0.36.7" +version = "0.38.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4fdebc4b395b7fbb9ab11e462e20ed9051e7b16e42d24042c776eca0ac81b03" +checksum = "9ad981d6c340a49cdc40a1028d9c6084ec7e9fa33fcb839cab656a267071e234" dependencies = [ - "bitflags", + "bitflags 2.4.1", "errno", - "io-lifetimes", "libc", "linux-raw-sys", - "windows-sys", + "windows-sys 0.48.0", ] [[package]] @@ -1692,9 +1740,9 @@ checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" [[package]] name = "serde" -version = "1.0.181" +version = "1.0.192" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d3e73c93c3240c0bda063c239298e633114c69a888c3e37ca8bb33f343e9890" +checksum = "bca2a08484b285dcb282d0f67b26cadc0df8b19f8c12502c13d966bf9482f001" dependencies = [ "serde_derive", ] @@ -1712,9 +1760,9 @@ dependencies = [ [[package]] name = "serde_derive" -version = "1.0.181" +version = "1.0.192" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be02f6cb0cd3a5ec20bbcfbcbd749f57daddb1a0882dc2e46a6c236c90b977ed" +checksum = "d6c7207fbec9faa48073f3e3074cbe553af6ea512d7c21ba46e434e70ea9fbc1" dependencies = [ "proc-macro2", "quote", @@ -1723,9 +1771,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.104" +version = "1.0.108" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "076066c5f1078eac5b722a31827a8832fe108bed65dfa75e233c89f8206e976c" +checksum = "3d1c7e3eac408d115102c4c24ad393e0821bb3a5df4d506a80f85f7a742a526b" dependencies = [ "itoa", "ryu", @@ -1734,11 +1782,11 @@ dependencies = [ [[package]] name = "serde_yaml" -version = "0.9.17" +version = "0.9.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fb06d4b6cdaef0e0c51fa881acb721bed3c924cfaa71d9c94a3b771dfdf6567" +checksum = "3cc7a1570e38322cfe4154732e5110f887ea57e22b76f4bfd32b5bdd3368666c" dependencies = [ - "indexmap", + "indexmap 2.1.0", "itoa", "ryu", "serde", @@ -1773,9 +1821,9 @@ checksum = "7bd3e3206899af3f8b12af284fafc038cc1dc2b41d1b89dd17297221c5d225de" [[package]] name = "smallvec" -version = "1.10.0" +version = "1.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0" +checksum = "4dccd0940a2dcdf68d092b8cbab7dc0ad8fa938bf95787e1b916b0e3d0e8e970" [[package]] name = "smawk" @@ -1820,19 +1868,6 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" -[[package]] -name = "stacker" -version = "0.1.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c886bd4480155fd3ef527d45e9ac8dd7118a898a46530b7b94c3e21866259fce" -dependencies = [ - "cc", - "cfg-if", - "libc", - "psm", - "winapi", -] - [[package]] name = "static-map-macro" version = "0.2.3" @@ -1935,7 +1970,7 @@ dependencies = [ "base64 0.13.1", "dashmap", "either", - "indexmap", + "indexmap 1.9.2", "jsonc-parser", "lru", "once_cell", @@ -2035,7 +2070,7 @@ version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b4de36224eb9498fccd4e68971f0b83326ccf8592c2d424f257f3a1c76b2b211" dependencies = [ - "indexmap", + "indexmap 1.9.2", "serde", "serde_json", "swc_config_macro", @@ -2079,7 +2114,7 @@ version = "0.95.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "81e5ff66d8aa3c21c32ffcdd871712f78a50819118690ba8195974d665d008b4" dependencies = [ - "bitflags", + "bitflags 1.3.2", "is-macro", "num-bigint", "scoped-tls", @@ -2187,7 +2222,7 @@ checksum = "4d446b81d163c76cc172366ae54cf26c841c67c776dc57e97d327842d6e27b87" dependencies = [ "ahash", "arrayvec", - "indexmap", + "indexmap 1.9.2", "num-bigint", "num_cpus", "once_cell", @@ -2242,7 +2277,7 @@ dependencies = [ "ahash", "anyhow", "dashmap", - "indexmap", + "indexmap 1.9.2", "once_cell", "preset_env_base", "semver 1.0.16", @@ -2285,7 +2320,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8136889c97ee3c8fd9409f836e30f01c97e1ee0ca01d74a887a1943e2a92d41f" dependencies = [ "better_scoped_tls", - "bitflags", + "bitflags 1.3.2", "once_cell", "phf", "rustc-hash", @@ -2322,7 +2357,7 @@ checksum = "c0c867a21058a6918b0e1ebd9f1ec2b3fd947de4ff71a3125c3f98f1cc40628e" dependencies = [ "ahash", "arrayvec", - "indexmap", + "indexmap 1.9.2", "is-macro", "num-bigint", "serde", @@ -2362,8 +2397,8 @@ dependencies = [ "Inflector", "ahash", "anyhow", - "bitflags", - "indexmap", + "bitflags 1.3.2", + "indexmap 1.9.2", "is-macro", "path-clean", "pathdiff", @@ -2389,7 +2424,7 @@ checksum = "36cea8460fa0f28fea76afc4906a1f8848d9360dcbed181720b0e92a7f2e2a13" dependencies = [ "ahash", "dashmap", - "indexmap", + "indexmap 1.9.2", "once_cell", "petgraph", "rustc-hash", @@ -2434,7 +2469,7 @@ dependencies = [ "ahash", "base64 0.13.1", "dashmap", - "indexmap", + "indexmap 1.9.2", "once_cell", "regex", "serde", @@ -2474,7 +2509,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "95430933ea004dfa5d5da3cc383f57f578dedab27b8e4645c84610c6c72b3ab8" dependencies = [ "ahash", - "indexmap", + "indexmap 1.9.2", "rustc-hash", "swc_atoms", "swc_common", @@ -2491,7 +2526,7 @@ version = "0.106.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6bdb27e68d2c658204efd98c506ea2ecf942737b513f5b2140b8d81d04fcedc1" dependencies = [ - "indexmap", + "indexmap 1.9.2", "num_cpus", "once_cell", "rustc-hash", @@ -2549,7 +2584,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1c54361a935915a14d68a906126a8ee6e08ac27a0698a74337cbd6c0e15af40a" dependencies = [ "ahash", - "indexmap", + "indexmap 1.9.2", "petgraph", "swc_common", ] @@ -2665,12 +2700,12 @@ dependencies = [ [[package]] name = "terminal_size" -version = "0.2.3" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb20089a8ba2b69debd491f8d2d023761cbf196e999218c591fa1e7e15a21907" +checksum = "21bebf2b7c9e0a515f6e0f8c51dc0f8e4696391e6f1ff30379559f8365fb0df7" dependencies = [ "rustix", - "windows-sys", + "windows-sys 0.48.0", ] [[package]] @@ -2686,22 +2721,22 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.38" +version = "1.0.50" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a9cd18aa97d5c45c6603caea1da6628790b37f7a34b6ca89522331c5180fed0" +checksum = "f9a7210f5c9a7156bb50aa36aed4c95afb51df0df00713949448cf9e97d382d2" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.38" +version = "1.0.50" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fb327af4685e4d03fa8cbcf1716380da910eeb2bb8be417e7f9fd3fb164f36f" +checksum = "266b2e40bc00e5a6c09c3584011e08b06f123c00362c92b975ba9843aaaa14b8" dependencies = [ "proc-macro2", "quote", - "syn 1.0.107", + "syn 2.0.28", ] [[package]] @@ -2726,13 +2761,15 @@ dependencies = [ [[package]] name = "time" -version = "0.3.17" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a561bf4617eebd33bca6434b988f39ed798e527f51a1e797d0ee4f61c0a38376" +checksum = "c4a34ab300f2dee6e562c10a046fc05e358b29f9bf92277f30c3c8d82275f6f5" dependencies = [ + "deranged", "itoa", "libc", "num_threads", + "powerfmt", "serde", "time-core", "time-macros", @@ -2740,15 +2777,15 @@ dependencies = [ [[package]] name = "time-core" -version = "0.1.0" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e153e1f1acaef8acc537e68b44906d2db6436e2b35ac2c6b42640fff91f00fd" +checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" [[package]] name = "time-macros" -version = "0.2.6" +version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d967f99f534ca7e495c575c62638eebc2898a8c84c119b89e250477bc4ba16b2" +checksum = "4ad70d68dba9e1f8aceda7aa6711965dfec1cac869f311a51bd08b3a2ccbce20" dependencies = [ "time-core", ] @@ -2770,11 +2807,10 @@ checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" [[package]] name = "tracing" -version = "0.1.37" +version = "0.1.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ce8c33a8d48bd45d624a6e523445fd21ec13d3653cd51f681abf67418f54eb8" +checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" dependencies = [ - "cfg-if", "pin-project-lite", "tracing-attributes", "tracing-core", @@ -2782,20 +2818,20 @@ dependencies = [ [[package]] name = "tracing-attributes" -version = "0.1.23" +version = "0.1.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4017f8f45139870ca7e672686113917c71c7a6e02d4924eda67186083c03081a" +checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" dependencies = [ "proc-macro2", "quote", - "syn 1.0.107", + "syn 2.0.28", ] [[package]] name = "tracing-core" -version = "0.1.30" +version = "0.1.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24eb03ba0eab1fd845050058ce5e616558e8f8d8fca633e6b163fe25c797213a" +checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" dependencies = [ "once_cell", "valuable", @@ -2803,23 +2839,23 @@ dependencies = [ [[package]] name = "tracing-log" -version = "0.1.3" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78ddad33d2d10b1ed7eb9d1f518a5674713876e97e5bb9b7345a7984fbb4f922" +checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" dependencies = [ - "lazy_static", "log", + "once_cell", "tracing-core", ] [[package]] name = "tracing-subscriber" -version = "0.3.16" +version = "0.3.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a6176eae26dd70d0c919749377897b54a9276bd7061339665dd68777926b5a70" +checksum = "ad0f048c97dbd9faa9b7df56362b8ebcaa52adb06b498c050d2f4e32f90a7a8b" dependencies = [ "matchers", - "nu-ansi-term", + "nu-ansi-term 0.46.0", "once_cell", "regex", "sharded-slab", @@ -2832,12 +2868,11 @@ dependencies = [ [[package]] name = "tracing-tree" -version = "0.2.2" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "758e983ab7c54fee18403994507e7f212b9005e957ce7984996fac8d11facedb" +checksum = "65139ecd2c3f6484c3b99bc01c77afe21e95473630747c7aca525e78b0666675" dependencies = [ - "atty", - "nu-ansi-term", + "nu-ansi-term 0.49.0", "tracing-core", "tracing-log", "tracing-subscriber", @@ -2895,7 +2930,7 @@ version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c5faade31a542b8b35855fff6e8def199853b2da8da256da52f52f1316ee3137" dependencies = [ - "hashbrown", + "hashbrown 0.12.3", "regex", ] @@ -2916,9 +2951,9 @@ checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b" [[package]] name = "unsafe-libyaml" -version = "0.2.5" +version = "0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc7ed8ba44ca06be78ea1ad2c3682a43349126c8818054231ee6f4748012aed2" +checksum = "f28467d3e1d3c6586d8f25fa243f544f5800fec42d97032474e17222c2b75cfa" [[package]] name = "untrusted" @@ -2956,6 +2991,12 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "utf8parse" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" + [[package]] name = "valuable" version = "0.1.0" @@ -2974,7 +3015,7 @@ dependencies = [ "getset", "rustversion", "thiserror", - "time 0.3.17", + "time 0.3.30", ] [[package]] @@ -3116,13 +3157,37 @@ version = "0.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7" dependencies = [ - "windows_aarch64_gnullvm", - "windows_aarch64_msvc", - "windows_i686_gnu", - "windows_i686_msvc", - "windows_x86_64_gnu", - "windows_x86_64_gnullvm", - "windows_x86_64_msvc", + "windows_aarch64_gnullvm 0.42.0", + "windows_aarch64_msvc 0.42.0", + "windows_i686_gnu 0.42.0", + "windows_i686_msvc 0.42.0", + "windows_x86_64_gnu 0.42.0", + "windows_x86_64_gnullvm 0.42.0", + "windows_x86_64_msvc 0.42.0", +] + +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-targets" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +dependencies = [ + "windows_aarch64_gnullvm 0.48.5", + "windows_aarch64_msvc 0.48.5", + "windows_i686_gnu 0.48.5", + "windows_i686_msvc 0.48.5", + "windows_x86_64_gnu 0.48.5", + "windows_x86_64_gnullvm 0.48.5", + "windows_x86_64_msvc 0.48.5", ] [[package]] @@ -3131,42 +3196,84 @@ version = "0.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "41d2aa71f6f0cbe00ae5167d90ef3cfe66527d6f613ca78ac8024c3ccab9a19e" +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" + [[package]] name = "windows_aarch64_msvc" version = "0.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dd0f252f5a35cac83d6311b2e795981f5ee6e67eb1f9a7f64eb4500fbc4dcdb4" +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" + [[package]] name = "windows_i686_gnu" version = "0.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fbeae19f6716841636c28d695375df17562ca208b2b7d0dc47635a50ae6c5de7" +[[package]] +name = "windows_i686_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" + [[package]] name = "windows_i686_msvc" version = "0.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "84c12f65daa39dd2babe6e442988fc329d6243fdce47d7d2d155b8d874862246" +[[package]] +name = "windows_i686_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" + [[package]] name = "windows_x86_64_gnu" version = "0.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bf7b1b21b5362cbc318f686150e5bcea75ecedc74dd157d874d754a2ca44b0ed" +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" + [[package]] name = "windows_x86_64_gnullvm" version = "0.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09d525d2ba30eeb3297665bd434a54297e4170c7f1a44cad4ef58095b4cd2028" +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" + [[package]] name = "windows_x86_64_msvc" version = "0.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f40009d85759725a34da6d89a94e63d7bdc50a862acf0dbc7c8e488f1edcb6f5" +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" + [[package]] name = "yansi" version = "0.5.1" diff --git a/Cargo.toml b/Cargo.toml index d744ea1c..def13455 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,28 +9,43 @@ edition = "2021" license = "MIT OR Apache-2.0" [workspace.dependencies] -clap = { version = "4.1.1", features = ["derive", "wrap_help"] } +clap = { version = "4.4.8", features = ["derive", "wrap_help"] } fixedbitset = "0.4.2" -itertools = "0.10.5" +itertools = "0.12.0" miette = { version = "5.5.0", features = ["fancy"] } num-bigint = { version = "0.4.3" } -serde = { version = "1.0.152", features = ["derive"] } -serde_json = "1.0.97" -serde_yaml = "0.9.17" -stacker = "0.1.15" +serde = { version = "1.0.192", features = ["derive"] } +serde_json = "1.0.108" +serde_yaml = "0.9.27" petgraph = "0.6.2" -pretty_assertions = "1.3.0" -indexmap = { version = "1.9.2", features = ["std"] } +pretty_assertions = "1.4.0" +indexmap = { version = "2.1.0", features = ["std"] } once_cell = "1.17.0" regex = "1.7.1" rustc-hash = "1.1.0" -smallvec = { version = "1.10.0", features = ["union", "const_new"] } -swc_core = { version = "0.53.1", features = ["common_perf", "common", "common_sourcemap", "ecma_visit_path", "ecma_utils", "ecma_ast", "swc", "ecma_visit", "ecma_transforms", "ecma_transforms_module", "ecma_transforms_typescript", "ecma_parser_typescript"] } -thiserror = "1.0.38" -time = { version = "0.3.17", features = ["local-offset", "serde-human-readable"] } -tracing = "0.1.37" -tracing-subscriber = { version = "0.3.16", features = ["env-filter"] } -tracing-tree = "0.2.2" +smallvec = { version = "1.11.2", features = ["union", "const_new"] } +swc_core = { version = "0.53.1", features = [ + "common_perf", + "common", + "common_sourcemap", + "ecma_visit_path", + "ecma_utils", + "ecma_ast", + "swc", + "ecma_visit", + "ecma_transforms", + "ecma_transforms_module", + "ecma_transforms_typescript", + "ecma_parser_typescript", +] } +thiserror = "1.0.50" +time = { version = "0.3.30", features = [ + "local-offset", + "serde-human-readable", +] } +tracing = "0.1.40" +tracing-subscriber = { version = "0.3.18", features = ["env-filter"] } +tracing-tree = "0.3.0" typed-index-collections = "3.1.0" walkdir = "2.3.2" forge_loader = { path = "crates/forge_loader" } diff --git a/crates/forge_analyzer/Cargo.toml b/crates/forge_analyzer/Cargo.toml index 73f4d1d3..1f8d123c 100644 --- a/crates/forge_analyzer/Cargo.toml +++ b/crates/forge_analyzer/Cargo.toml @@ -19,7 +19,6 @@ petgraph.workspace = true regex.workspace = true serde_json.workspace = true serde.workspace = true -stacker.workspace = true smallvec.workspace = true swc_core.workspace = true time.workspace = true diff --git a/crates/forge_analyzer/src/utils.rs b/crates/forge_analyzer/src/utils.rs index 4a0ad371..1d11af3b 100644 --- a/crates/forge_analyzer/src/utils.rs +++ b/crates/forge_analyzer/src/utils.rs @@ -20,12 +20,3 @@ pub fn eq_prop_name(n: &MemberProp, name: &str) -> bool { _ => false, } } - -const RED_ZONE: usize = 100 * 1024; - -const STACK_SIZE: usize = 1024 * 1024; - -#[inline] -pub(crate) fn ensure_sufficient_stack(f: impl FnOnce() -> T) -> T { - stacker::maybe_grow(RED_ZONE, STACK_SIZE, f) -} From 91d36208d9520a01b65e2ca80c0d1ad1f0d3f248 Mon Sep 17 00:00:00 2001 From: Joshua Wong Date: Tue, 28 Nov 2023 00:38:22 -0600 Subject: [PATCH 217/517] chore: bump rustc version in Dockerfile --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index fa58fe64..43961378 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM rust:1.70.0 as build-env +FROM rust:1.74.0 as build-env WORKDIR /app COPY . /app RUN cargo build --release From c39eaca08f9655675967d53cb6980ae5233778bb Mon Sep 17 00:00:00 2001 From: Joshua Wong Date: Tue, 28 Nov 2023 00:41:44 -0600 Subject: [PATCH 218/517] feat: add nicer hunks for rust files --- .gitattributes | 1 + 1 file changed, 1 insertion(+) create mode 100644 .gitattributes diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 00000000..be642893 --- /dev/null +++ b/.gitattributes @@ -0,0 +1 @@ +*.rs diff=rust From fb9c10b8659f9430067f25c62cbc8075f83f7126 Mon Sep 17 00:00:00 2001 From: Joshua Wong Date: Tue, 28 Nov 2023 01:12:40 -0600 Subject: [PATCH 219/517] fix: suppress elided lifetime in constant lint --- crates/forge_loader/src/manifest.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/forge_loader/src/manifest.rs b/crates/forge_loader/src/manifest.rs index 65aa39f1..3b27583c 100644 --- a/crates/forge_loader/src/manifest.rs +++ b/crates/forge_loader/src/manifest.rs @@ -270,7 +270,7 @@ impl<'a> ForgeModules<'a> { } impl FunctionRef<'_, S> { - const VALID_EXTS: [&str; 4] = ["jsx", "tsx", "ts", "js"]; + const VALID_EXTS: [&'static str; 4] = ["jsx", "tsx", "ts", "js"]; } impl<'a> FunctionRef<'a> { From 545dbf001e0ee4ce07a9c072b31c16e227736801 Mon Sep 17 00:00:00 2001 From: Joshua Wong Date: Tue, 28 Nov 2023 01:13:27 -0600 Subject: [PATCH 220/517] chore: bump swc version --- Cargo.lock | 964 +++++++++++++---------- Cargo.toml | 3 +- crates/forge_analyzer/src/definitions.rs | 55 +- crates/forge_analyzer/src/exports.rs | 9 +- 4 files changed, 569 insertions(+), 462 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index d744f974..279217d0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -33,10 +33,23 @@ version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fcb51a0695d8f838b1ee009b3fbf66bda078cd64590202a864a8f3e8c4315c47" dependencies = [ + "getrandom", + "once_cell", + "version_check", +] + +[[package]] +name = "ahash" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91429305e9f0a25f6205c5b8e0d2db09e0708a7a6df0f42212bb56c32c8ac97a" +dependencies = [ + "cfg-if", "getrandom", "once_cell", "serde", "version_check", + "zerocopy", ] [[package]] @@ -107,28 +120,27 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.68" +version = "1.0.75" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2cb2f989d18dd141ab8ae82f64d1a8cdd37e0840f73a406896cf5e99502fab61" +checksum = "a4668cab20f66d8d020e1fbc0ebe47217433c1b6c8f2040faf858554e394ace6" [[package]] name = "arrayvec" -version = "0.7.2" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8da52d66c7071e2e3fa2a1e5c6d088fec47b593032b254f5e980de8ea54454d6" +checksum = "96d30a06541fbafbc7f82ed10c06164cfbd2c401138f6addd8404629c4b16711" [[package]] name = "ast_node" -version = "0.8.6" +version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf94863c5fdfee166d0907c44e5fee970123b2b7307046d35d1e671aa93afbba" +checksum = "c09c69dffe06d222d072c878c3afe86eee2179806f20503faec97250268b4c24" dependencies = [ - "darling", "pmutil", "proc-macro2", "quote", "swc_macros_common", - "syn 1.0.107", + "syn 2.0.32", ] [[package]] @@ -144,9 +156,9 @@ dependencies = [ [[package]] name = "auto_impl" -version = "0.5.0" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7862e21c893d65a1650125d157eaeec691439379a1cee17ee49031b79236ada4" +checksum = "fee3da8ef1276b0bee5dd1c7258010d8fffd31801447323115a25560e1327b89" dependencies = [ "proc-macro-error", "proc-macro2", @@ -189,9 +201,9 @@ checksum = "604178f6c5c21f02dc555784810edfb88d34ac2c73b2eae109655649ee73ce3d" [[package]] name = "better_scoped_tls" -version = "0.1.0" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b73e8ecdec39e98aa3b19e8cd0b8ed8f77ccb86a6b0b2dc7cd86d105438a2123" +checksum = "794edcc9b3fb07bb4aecaa11f093fd45663b4feadb782d68303a2268bc2701de" dependencies = [ "scoped-tls", ] @@ -219,26 +231,23 @@ dependencies = [ [[package]] name = "browserslist-rs" -version = "0.12.3" +version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef956561c9a03c35af46714efd0c135e21768a2a012f900ca8a59b28e75d0cd1" +checksum = "e33066f72a558361eeb1077b0aff0f1dce1ac75bdc20b38a642f155f767b2824" dependencies = [ - "ahash", + "ahash 0.8.6", "anyhow", "chrono", "either", "itertools 0.10.5", - "js-sys", "nom", "once_cell", "quote", "serde", - "serde-wasm-bindgen", "serde_json", "string_cache", "string_cache_codegen", "thiserror", - "wasm-bindgen", ] [[package]] @@ -266,11 +275,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "16b0a3d9ed01224b22057780a37bb8c5dbfe1be8ba48678e7bf57ec4b385411f" dependencies = [ "iana-time-zone", - "js-sys", "num-integer", "num-traits", "time 0.1.45", - "wasm-bindgen", "winapi", ] @@ -306,7 +313,7 @@ dependencies = [ "heck", "proc-macro2", "quote", - "syn 2.0.28", + "syn 2.0.32", ] [[package]] @@ -355,16 +362,6 @@ dependencies = [ "cfg-if", ] -[[package]] -name = "crossbeam-channel" -version = "0.5.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2dd04ddaf88237dc3b8d8f9a3c1004b506b54b3313403944054d23c0870c521" -dependencies = [ - "cfg-if", - "crossbeam-utils", -] - [[package]] name = "crossbeam-deque" version = "0.8.2" @@ -452,41 +449,6 @@ dependencies = [ "syn 1.0.107", ] -[[package]] -name = "darling" -version = "0.13.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a01d95850c592940db9b8194bc39f4bc0e89dee5c4265e4b1807c34a9aba453c" -dependencies = [ - "darling_core", - "darling_macro", -] - -[[package]] -name = "darling_core" -version = "0.13.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "859d65a907b6852c9361e3185c862aae7fafd2887876799fa55f5f99dc40d610" -dependencies = [ - "fnv", - "ident_case", - "proc-macro2", - "quote", - "strsim", - "syn 1.0.107", -] - -[[package]] -name = "darling_macro" -version = "0.13.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c972679f83bdf9c42bd905396b6c3588a843a17f0f16dfcfa3e2c5d57441835" -dependencies = [ - "darling_core", - "quote", - "syn 1.0.107", -] - [[package]] name = "dashmap" version = "5.4.0" @@ -541,38 +503,6 @@ dependencies = [ "cfg-if", ] -[[package]] -name = "enum-iterator" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91a4ec26efacf4aeff80887a175a419493cb6f8b5480d26387eb0bd038976187" -dependencies = [ - "enum-iterator-derive", -] - -[[package]] -name = "enum-iterator-derive" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "828de45d0ca18782232dfb8f3ea9cc428e8ced380eb26a520baaacfc70de39ce" -dependencies = [ - "proc-macro2", - "quote", - "syn 1.0.107", -] - -[[package]] -name = "enum_kind" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78b940da354ae81ef0926c5eaa428207b8f4f091d3956c891dfbd124162bed99" -dependencies = [ - "pmutil", - "proc-macro2", - "swc_macros_common", - "syn 1.0.107", -] - [[package]] name = "equivalent" version = "1.0.1" @@ -605,12 +535,6 @@ dependencies = [ "miniz_oxide 0.7.1", ] -[[package]] -name = "fnv" -version = "1.0.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" - [[package]] name = "forge_analyzer" version = "0.1.0" @@ -675,23 +599,23 @@ dependencies = [ [[package]] name = "form_urlencoded" -version = "1.1.0" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9c384f161156f5260c24a097c56119f9be8c798586aecc13afbcbe7b7e26bf8" +checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" dependencies = [ "percent-encoding", ] [[package]] name = "from_variant" -version = "0.1.4" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0981e470d2ab9f643df3921d54f1952ea100c39fdb6a3fdc820e20d2291df6c" +checksum = "03ec5dc38ee19078d84a692b1c41181ff9f94331c76cee66ff0208c770b5e54f" dependencies = [ "pmutil", "proc-macro2", "swc_macros_common", - "syn 1.0.107", + "syn 2.0.32", ] [[package]] @@ -735,18 +659,6 @@ dependencies = [ "wasi 0.11.0+wasi-snapshot-preview1", ] -[[package]] -name = "getset" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e45727250e75cc04ff2846a66397da8ef2b3db8e40e0cef4df67950a07621eb9" -dependencies = [ - "proc-macro-error", - "proc-macro2", - "quote", - "syn 1.0.107", -] - [[package]] name = "gimli" version = "0.27.0" @@ -759,7 +671,16 @@ version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" dependencies = [ - "ahash", + "ahash 0.7.6", +] + +[[package]] +name = "hashbrown" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43a3c133739dddd0d2990f9a4bdf8eb4b21ef50e4851ca85ab661199821d510e" +dependencies = [ + "ahash 0.8.6", ] [[package]] @@ -792,6 +713,19 @@ dependencies = [ "libc", ] +[[package]] +name = "hstr" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de90d3db62411eb62eddabe402d706ac4970f7ac8d088c05f11069cad9be9857" +dependencies = [ + "new_debug_unreachable", + "once_cell", + "phf", + "rustc-hash", + "smallvec", +] + [[package]] name = "iana-time-zone" version = "0.1.53" @@ -816,17 +750,11 @@ dependencies = [ "cxx-build", ] -[[package]] -name = "ident_case" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" - [[package]] name = "idna" -version = "0.3.0" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e14ddfc70884202db2244c223200c204c2bda1bc6e0998d11b5e024d657209e6" +checksum = "634d9b1461af396cad843f47fdba5597a4f9e6ddd4bfb6ff5d85028c25cb12f6" dependencies = [ "unicode-bidi", "unicode-normalization", @@ -840,9 +768,9 @@ checksum = "cb56e1aa765b4b4f3aadfab769793b7087bb03a4ea4920644a6d238e2df5b9ed" [[package]] name = "indexmap" -version = "1.9.2" +version = "1.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1885e79c1fc4b10f0e172c475f458b7f7b93061064d98c3293e98c5ba0c8b399" +checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" dependencies = [ "autocfg", "hashbrown 0.12.3", @@ -861,15 +789,15 @@ dependencies = [ [[package]] name = "is-macro" -version = "0.2.1" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c068d4c6b922cd6284c609cfa6dec0e41615c9c5a1a4ba729a970d8daba05fb" +checksum = "f4467ed1321b310c2625c5aa6c1b1ffc5de4d9e42668cf697a08fb033ee8265e" dependencies = [ "Inflector", "pmutil", "proc-macro2", "quote", - "syn 1.0.107", + "syn 2.0.32", ] [[package]] @@ -926,79 +854,6 @@ version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" -[[package]] -name = "lexical" -version = "6.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7aefb36fd43fef7003334742cbf77b243fcd36418a1d1bdd480d613a67968f6" -dependencies = [ - "lexical-core", -] - -[[package]] -name = "lexical-core" -version = "0.8.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2cde5de06e8d4c2faabc400238f9ae1c74d5412d03a7bd067645ccbc47070e46" -dependencies = [ - "lexical-parse-float", - "lexical-parse-integer", - "lexical-util", - "lexical-write-float", - "lexical-write-integer", -] - -[[package]] -name = "lexical-parse-float" -version = "0.8.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "683b3a5ebd0130b8fb52ba0bdc718cc56815b6a097e28ae5a6997d0ad17dc05f" -dependencies = [ - "lexical-parse-integer", - "lexical-util", - "static_assertions", -] - -[[package]] -name = "lexical-parse-integer" -version = "0.8.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d0994485ed0c312f6d965766754ea177d07f9c00c9b82a5ee62ed5b47945ee9" -dependencies = [ - "lexical-util", - "static_assertions", -] - -[[package]] -name = "lexical-util" -version = "0.8.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5255b9ff16ff898710eb9eb63cb39248ea8a5bb036bea8085b1a767ff6c4e3fc" -dependencies = [ - "static_assertions", -] - -[[package]] -name = "lexical-write-float" -version = "0.8.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "accabaa1c4581f05a3923d1b4cfd124c329352288b7b9da09e766b0668116862" -dependencies = [ - "lexical-util", - "lexical-write-integer", - "static_assertions", -] - -[[package]] -name = "lexical-write-integer" -version = "0.8.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e1b6f3d1f4422866b68192d62f77bc5c700bee84f3069f2469d7bc8c77852446" -dependencies = [ - "lexical-util", - "static_assertions", -] - [[package]] name = "libc" version = "0.2.150" @@ -1041,11 +896,11 @@ dependencies = [ [[package]] name = "lru" -version = "0.7.8" +version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e999beba7b6e8345721bd280141ed958096a2e4abdf74f67ff4ce49b4b54e47a" +checksum = "718e8fae447df0c7e1ba7f5189829e63fd536945c8988d61444c19039f16b670" dependencies = [ - "hashbrown 0.12.3", + "hashbrown 0.13.2", ] [[package]] @@ -1059,9 +914,9 @@ dependencies = [ [[package]] name = "memchr" -version = "2.5.0" +version = "2.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" +checksum = "f665ee40bc4a3c5590afb1e9677db74a508659dfd71e126420da8274909a0167" [[package]] name = "memoffset" @@ -1263,9 +1118,9 @@ dependencies = [ [[package]] name = "once_cell" -version = "1.17.0" +version = "1.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f61fba1741ea2b3d6a1e3178721804bb716a68a6aeba1149b5d52e3d464ea66" +checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" [[package]] name = "overload" @@ -1316,9 +1171,9 @@ checksum = "8835116a5c179084a830efb3adc117ab007512b535bc1a21c991d3b32a6b44dd" [[package]] name = "percent-encoding" -version = "2.2.0" +version = "2.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "478c572c3d73181ff3c2539045f6eb99e5491218eae919370993b890cdbdd98e" +checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" [[package]] name = "petgraph" @@ -1327,18 +1182,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6d5014253a1331579ce62aa67443b4a658c5e7dd03d4bc6d302b94474888143" dependencies = [ "fixedbitset", - "indexmap 1.9.2", + "indexmap 1.9.3", ] [[package]] name = "phf" -version = "0.10.1" +version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fabbf1ead8a5bcbc20f5f8b939ee3f5b0f6f281b6ad3468b84656b658b455259" +checksum = "ade2d8b8f33c7333b51bcf0428d37e217e9f32192ae4772156f65063b8ce03dc" dependencies = [ "phf_macros", - "phf_shared", - "proc-macro-hack", + "phf_shared 0.11.2", ] [[package]] @@ -1347,22 +1201,31 @@ version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5d5285893bb5eb82e6aaf5d59ee909a06a16737a8970984dd7746ba9283498d6" dependencies = [ - "phf_shared", + "phf_shared 0.10.0", + "rand", +] + +[[package]] +name = "phf_generator" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48e4cc64c2ad9ebe670cb8fd69dd50ae301650392e81c05f9bfcb2d5bdbc24b0" +dependencies = [ + "phf_shared 0.11.2", "rand", ] [[package]] name = "phf_macros" -version = "0.10.0" +version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "58fdf3184dd560f160dd73922bea2d5cd6e8f064bf4b13110abd81b03697b4e0" +checksum = "3444646e286606587e49f3bcf1679b8cef1dc2c5ecc29ddacaffc305180d464b" dependencies = [ - "phf_generator", - "phf_shared", - "proc-macro-hack", + "phf_generator 0.11.2", + "phf_shared 0.11.2", "proc-macro2", "quote", - "syn 1.0.107", + "syn 2.0.32", ] [[package]] @@ -1374,6 +1237,15 @@ dependencies = [ "siphasher", ] +[[package]] +name = "phf_shared" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90fcb95eef784c2ac79119d1dd819e162b5da872ce6f3c3abe1e8ca1c082f72b" +dependencies = [ + "siphasher", +] + [[package]] name = "pin-project-lite" version = "0.2.9" @@ -1382,13 +1254,13 @@ checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116" [[package]] name = "pmutil" -version = "0.5.3" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3894e5d549cccbe44afecf72922f277f603cd4bb0219c8342631ef18fffbe004" +checksum = "52a40bc70c2c58040d2d8b167ba9a5ff59fc9dab7ad44771cfde3dcfde7a09c6" dependencies = [ "proc-macro2", "quote", - "syn 1.0.107", + "syn 2.0.32", ] [[package]] @@ -1411,17 +1283,17 @@ checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c" [[package]] name = "preset_env_base" -version = "0.4.0" +version = "0.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db4a43af74678e784b17db952b7fd726937b0a058c7c972624ddfd366e7603e4" +checksum = "008ae5e91d877e168b73c17bed2e4dc7553cd7cc237a776d9479eab6642352c5" dependencies = [ - "ahash", + "ahash 0.8.6", "anyhow", "browserslist-rs", "dashmap", "from_variant", "once_cell", - "semver 1.0.16", + "semver 1.0.20", "serde", "st-map", "tracing", @@ -1461,12 +1333,6 @@ dependencies = [ "version_check", ] -[[package]] -name = "proc-macro-hack" -version = "0.5.20+deprecated" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc375e1527247fe1a97d8b7156678dfe7c1af2fc075c9a4db3690ecd2a148068" - [[package]] name = "proc-macro2" version = "1.0.66" @@ -1476,6 +1342,15 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "psm" +version = "0.1.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5787f7cda34e3033a72192c018bc5883100330f362ef279a8cbccfce8bb4e874" +dependencies = [ + "cc", +] + [[package]] name = "quote" version = "1.0.32" @@ -1523,9 +1398,9 @@ dependencies = [ [[package]] name = "rayon" -version = "1.6.1" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6db3a213adf02b3bcfd2d3846bb41cb22857d131789e01df434fb7e7bc0759b7" +checksum = "9c27db03db7734835b3f53954b534c91069375ce6ccaa2e065441e07d9b6cdb1" dependencies = [ "either", "rayon-core", @@ -1533,14 +1408,12 @@ dependencies = [ [[package]] name = "rayon-core" -version = "1.10.1" +version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cac410af5d00ab6884528b4ab69d1e8e146e8d471201800fa1b4524126de6ad3" +checksum = "5ce3fb6ad83f861aac485e76e1985cd109d9a3713802152be56c3b1f0e0658ed" dependencies = [ - "crossbeam-channel", "crossbeam-deque", "crossbeam-utils", - "num_cpus", ] [[package]] @@ -1578,12 +1451,6 @@ version = "0.6.28" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "456c603be3e8d448b072f410900c09faf164fbce2d480456f50eea6e25f9c848" -[[package]] -name = "retain_mut" -version = "0.1.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4389f1d5789befaf6029ebd9f7dac4af7f7e3d61b69d4f30e2ac02b57e7712b0" - [[package]] name = "ring" version = "0.16.20" @@ -1667,9 +1534,9 @@ dependencies = [ [[package]] name = "rustversion" -version = "1.0.11" +version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5583e89e108996506031660fe09baa5011b9dd0341b89029313006d1fb508d70" +checksum = "7ffc183a10b4478d04cbbbfc96d0873219d962dd5accaff2ffbd4ceb7df837f4" [[package]] name = "ryu" @@ -1677,6 +1544,12 @@ version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7b4b9743ed687d4b4bcedf9ff5eaa7398495ae14e61cba0a295704edbc7decde" +[[package]] +name = "ryu-js" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4950d85bc52415f8432144c97c4791bd0c4f7954de32a7270ee9cccd3c22b12b" + [[package]] name = "same-file" version = "1.0.6" @@ -1725,9 +1598,9 @@ dependencies = [ [[package]] name = "semver" -version = "1.0.16" +version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "58bc9567378fc7690d6b2addae4e60ac2eeea07becb2c64b9f218b53865cba2a" +checksum = "836fa6a3e1e547f9a2c4040802ec865b5d85f4014efe00555d7090a3dcaa1090" dependencies = [ "serde", ] @@ -1747,17 +1620,6 @@ dependencies = [ "serde_derive", ] -[[package]] -name = "serde-wasm-bindgen" -version = "0.4.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3b4c031cd0d9014307d82b8abf653c0290fbdaeb4c02d00c63cf52f728628bf" -dependencies = [ - "js-sys", - "serde", - "wasm-bindgen", -] - [[package]] name = "serde_derive" version = "1.0.192" @@ -1766,7 +1628,7 @@ checksum = "d6c7207fbec9faa48073f3e3074cbe553af6ea512d7c21ba46e434e70ea9fbc1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.28", + "syn 2.0.32", ] [[package]] @@ -1825,6 +1687,17 @@ version = "1.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4dccd0940a2dcdf68d092b8cbab7dc0ad8fa938bf95787e1b916b0e3d0e8e970" +[[package]] +name = "smartstring" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fb72c633efbaa2dd666986505016c32c3044395ceaf881518399d2f4127ee29" +dependencies = [ + "autocfg", + "static_assertions", + "version_check", +] + [[package]] name = "smawk" version = "0.3.1" @@ -1854,30 +1727,37 @@ checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" [[package]] name = "st-map" -version = "0.1.6" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc9c9f3a1df5f73b7392bd9773108fef41ad9126f0282412fd5904389f0c0c4f" +checksum = "f352d5d14be5a1f956d76ae0c8060c3487aaa2a080f10a4b4ff023c7c05a9047" dependencies = [ "arrayvec", "static-map-macro", ] [[package]] -name = "stable_deref_trait" -version = "1.2.0" +name = "stacker" +version = "0.1.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" +checksum = "c886bd4480155fd3ef527d45e9ac8dd7118a898a46530b7b94c3e21866259fce" +dependencies = [ + "cc", + "cfg-if", + "libc", + "psm", + "winapi", +] [[package]] name = "static-map-macro" -version = "0.2.3" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "752564de9cd8937fdbc1c55d47ac391758c352ab3755607cc391b659fe87d56b" +checksum = "7628ae0bd92555d3de4303da41a5c8b1c5363e892001325f34e4be9ed024d0d7" dependencies = [ "pmutil", "proc-macro2", "quote", - "syn 1.0.107", + "syn 2.0.32", ] [[package]] @@ -1895,7 +1775,7 @@ dependencies = [ "new_debug_unreachable", "once_cell", "parking_lot", - "phf_shared", + "phf_shared 0.10.0", "precomputed-hash", "serde", ] @@ -1906,23 +1786,23 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6bb30289b722be4ff74a408c3cc27edeaad656e06cb1fe8fa9231fa59c728988" dependencies = [ - "phf_generator", - "phf_shared", + "phf_generator 0.10.0", + "phf_shared 0.10.0", "proc-macro2", "quote", ] [[package]] name = "string_enum" -version = "0.3.2" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "994453cd270ad0265796eb24abf5540091ed03e681c5f3c12bc33e4db33253e1" +checksum = "8fa4d4f81d7c05b9161f8de839975d3326328b8ba2831164b465524cc2f55252" dependencies = [ "pmutil", "proc-macro2", "quote", "swc_macros_common", - "syn 1.0.107", + "syn 2.0.32", ] [[package]] @@ -1961,16 +1841,15 @@ dependencies = [ [[package]] name = "swc" -version = "0.239.1" +version = "0.269.73" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a68788142778ec4fb76367b7ae2883b6829763e139d2531a8dce17df63659242" +checksum = "a6f3f1bab39cd61b255c7d11a4e759ea8553ce17b56591ec5bb44d1c80787400" dependencies = [ - "ahash", "anyhow", "base64 0.13.1", "dashmap", "either", - "indexmap 1.9.2", + "indexmap 1.9.3", "jsonc-parser", "lru", "once_cell", @@ -1984,6 +1863,7 @@ dependencies = [ "swc_atoms", "swc_cached", "swc_common", + "swc_compiler_base", "swc_config", "swc_ecma_ast", "swc_ecma_codegen", @@ -2009,25 +1889,23 @@ dependencies = [ [[package]] name = "swc_atoms" -version = "0.4.33" +version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93a49e93996e1c1cfdb641cd94888da545d440ff6cada04155ef118adee620c1" +checksum = "b8a9e1b6d97f27b6abe5571f8fe3bdbd2fa987299fc2126450c7cde6214896ef" dependencies = [ + "hstr", "once_cell", "rustc-hash", "serde", - "string_cache", - "string_cache_codegen", - "triomphe", ] [[package]] name = "swc_cached" -version = "0.3.15" +version = "0.3.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9745d42d167cb60aeb1e85d2ee813ca455c3185bf7417f11fd102d745ae2b9e1" +checksum = "68b357b80879f6c4f4f34879d02eeae63aafc7730293e6eda3686f990d160486" dependencies = [ - "ahash", + "ahash 0.8.6", "anyhow", "dashmap", "once_cell", @@ -2037,11 +1915,11 @@ dependencies = [ [[package]] name = "swc_common" -version = "0.29.27" +version = "0.33.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90a2c285d33b47a5e532a662c178dc91956534ff52207892918d3034a534ae12" +checksum = "5ccb656cd57c93614e4e8b33a60e75ca095383565c1a8d2bbe6a1103942831e0" dependencies = [ - "ahash", + "ahash 0.8.6", "ast_node", "better_scoped_tls", "cfg-if", @@ -2055,7 +1933,6 @@ dependencies = [ "serde", "siphasher", "sourcemap", - "string_cache", "swc_atoms", "swc_eq_ignore_macros", "swc_visit", @@ -2064,13 +1941,35 @@ dependencies = [ "url", ] +[[package]] +name = "swc_compiler_base" +version = "0.3.77" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fdfb49fbaf555fe7e18afd26343f47cfe52b07a506d59b25fb85e4016c48d516" +dependencies = [ + "anyhow", + "base64 0.13.1", + "pathdiff", + "serde", + "sourcemap", + "swc_atoms", + "swc_common", + "swc_config", + "swc_ecma_ast", + "swc_ecma_codegen", + "swc_ecma_minifier", + "swc_ecma_parser", + "swc_ecma_visit", + "swc_timer", +] + [[package]] name = "swc_config" -version = "0.1.4" +version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4de36224eb9498fccd4e68971f0b83326ccf8592c2d424f257f3a1c76b2b211" +checksum = "9ba1c7a40d38f9dd4e9a046975d3faf95af42937b34b2b963be4d8f01239584b" dependencies = [ - "indexmap 1.9.2", + "indexmap 1.9.3", "serde", "serde_json", "swc_config_macro", @@ -2078,22 +1977,22 @@ dependencies = [ [[package]] name = "swc_config_macro" -version = "0.1.0" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fb64bc03d90fd5c90d6ab917bb2b1d7fbd31957df39e31ea24a3f554b4372251" +checksum = "e5b5aaca9a0082be4515f0fbbecc191bf5829cd25b5b9c0a2810f6a2bb0d6829" dependencies = [ "pmutil", "proc-macro2", "quote", "swc_macros_common", - "syn 1.0.107", + "syn 2.0.32", ] [[package]] name = "swc_core" -version = "0.53.1" +version = "0.86.82" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b250fdfac2060ec6996c45939ff1ec295f78913738be9dcb6853fca88445a524" +checksum = "8faddb80e001ac29e7e99f65d20acb2bd58f8cd0c2728be796eaa77b5c5ebaa6" dependencies = [ "swc", "swc_atoms", @@ -2110,13 +2009,14 @@ dependencies = [ [[package]] name = "swc_ecma_ast" -version = "0.95.11" +version = "0.110.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81e5ff66d8aa3c21c32ffcdd871712f78a50819118690ba8195974d665d008b4" +checksum = "2c3d416121da2d56bcbd1b1623725a68890af4552fef0c6d1e4bfa92776ccd6a" dependencies = [ - "bitflags 1.3.2", + "bitflags 2.4.1", "is-macro", "num-bigint", + "phf", "scoped-tls", "serde", "string_enum", @@ -2127,9 +2027,9 @@ dependencies = [ [[package]] name = "swc_ecma_codegen" -version = "0.128.18" +version = "0.146.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ecb5143ce3b7089539fb55875a43b6111e4e1af1bb505e54c9cd4095ce12ab09" +checksum = "bfd3775ed7ccf01feaac61890d6fc50eefe21ace5b29667147f50e7c79701b32" dependencies = [ "memchr", "num-bigint", @@ -2146,22 +2046,216 @@ dependencies = [ [[package]] name = "swc_ecma_codegen_macros" -version = "0.7.1" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0159c99f81f52e48fe692ef7af1b0990b45d3006b14c6629be0b1ffee1b23aea" +checksum = "dcdff076dccca6cc6a0e0b2a2c8acfb066014382bc6df98ec99e755484814384" dependencies = [ "pmutil", "proc-macro2", "quote", "swc_macros_common", - "syn 1.0.107", + "syn 2.0.32", +] + +[[package]] +name = "swc_ecma_compat_bugfixes" +version = "0.1.47" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "263594aba60063b5fe54a62b7feb075f17897241d2fe38fd060cc7ce8b369c9b" +dependencies = [ + "swc_atoms", + "swc_common", + "swc_ecma_ast", + "swc_ecma_compat_es2015", + "swc_ecma_transforms_base", + "swc_ecma_utils", + "swc_ecma_visit", + "swc_trace_macro", + "tracing", +] + +[[package]] +name = "swc_ecma_compat_common" +version = "0.1.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5abed404c9425bf0d1f44da426f9fec6fe1833b696275ed4dff85550376a32d4" +dependencies = [ + "swc_common", + "swc_ecma_ast", + "swc_ecma_utils", + "swc_ecma_visit", + "swc_trace_macro", +] + +[[package]] +name = "swc_ecma_compat_es2015" +version = "0.1.47" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f62ff03e2d6cbd5e101af8d4d38692da849db6036b06f2ebbe24d0284e8bf906" +dependencies = [ + "arrayvec", + "indexmap 1.9.3", + "is-macro", + "serde", + "serde_derive", + "smallvec", + "swc_atoms", + "swc_common", + "swc_config", + "swc_ecma_ast", + "swc_ecma_compat_common", + "swc_ecma_transforms_base", + "swc_ecma_transforms_classes", + "swc_ecma_transforms_macros", + "swc_ecma_utils", + "swc_ecma_visit", + "swc_trace_macro", + "tracing", +] + +[[package]] +name = "swc_ecma_compat_es2016" +version = "0.1.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43ef9881b31538c273ff79c31fbc45ea2e5c2e3cbbd93999565dad551d030c0a" +dependencies = [ + "swc_atoms", + "swc_common", + "swc_ecma_ast", + "swc_ecma_transforms_base", + "swc_ecma_transforms_macros", + "swc_ecma_utils", + "swc_ecma_visit", + "swc_trace_macro", + "tracing", +] + +[[package]] +name = "swc_ecma_compat_es2017" +version = "0.1.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "636edbfdf1986120e17876aed860553e4cbf6d75367b43be28959098ae564090" +dependencies = [ + "serde", + "swc_atoms", + "swc_common", + "swc_ecma_ast", + "swc_ecma_transforms_base", + "swc_ecma_transforms_macros", + "swc_ecma_utils", + "swc_ecma_visit", + "swc_trace_macro", + "tracing", +] + +[[package]] +name = "swc_ecma_compat_es2018" +version = "0.1.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85d32ff56a80fcbcec069eeeac489be1f2bc306bf3003b7d1dbdb86be3f98484" +dependencies = [ + "serde", + "swc_atoms", + "swc_common", + "swc_ecma_ast", + "swc_ecma_compat_common", + "swc_ecma_transforms_base", + "swc_ecma_transforms_macros", + "swc_ecma_utils", + "swc_ecma_visit", + "swc_trace_macro", + "tracing", +] + +[[package]] +name = "swc_ecma_compat_es2019" +version = "0.1.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b2e8701265c4d7c5dcb7d93553b464477b2b517c935ab566ab3d682d530d6b2" +dependencies = [ + "swc_atoms", + "swc_common", + "swc_ecma_ast", + "swc_ecma_transforms_base", + "swc_ecma_utils", + "swc_ecma_visit", + "swc_trace_macro", + "tracing", +] + +[[package]] +name = "swc_ecma_compat_es2020" +version = "0.1.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "506289478d965d8d234e70b6b57d226cc050573eba67145f0f0071d214ad0147" +dependencies = [ + "serde", + "swc_atoms", + "swc_common", + "swc_ecma_ast", + "swc_ecma_compat_es2022", + "swc_ecma_transforms_base", + "swc_ecma_utils", + "swc_ecma_visit", + "swc_trace_macro", + "tracing", +] + +[[package]] +name = "swc_ecma_compat_es2021" +version = "0.1.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34755368b408350d700e9eea0983e0c396602c0776ed3c340bf701f0f256f271" +dependencies = [ + "swc_atoms", + "swc_common", + "swc_ecma_ast", + "swc_ecma_transforms_base", + "swc_ecma_utils", + "swc_ecma_visit", + "swc_trace_macro", + "tracing", +] + +[[package]] +name = "swc_ecma_compat_es2022" +version = "0.1.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33a3856e485682d9df95dd693a35f1d4b10faa2e9af1eb638f38ca92aadcf076" +dependencies = [ + "swc_atoms", + "swc_common", + "swc_ecma_ast", + "swc_ecma_compat_common", + "swc_ecma_transforms_base", + "swc_ecma_transforms_classes", + "swc_ecma_transforms_macros", + "swc_ecma_utils", + "swc_ecma_visit", + "swc_trace_macro", + "tracing", +] + +[[package]] +name = "swc_ecma_compat_es3" +version = "0.1.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "301a197861113095eb0e62201c4ea1e357691195b15fdc634188070ef3281ce0" +dependencies = [ + "swc_common", + "swc_ecma_ast", + "swc_ecma_transforms_base", + "swc_ecma_utils", + "swc_ecma_visit", + "swc_trace_macro", + "tracing", ] [[package]] name = "swc_ecma_ext_transforms" -version = "0.92.19" +version = "0.110.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f28c4cf9f3fb45067b0300a0d3020dde63f836d87ab4ca12485b37d7868f55d7" +checksum = "3cc0000e38d2c67772c632f3e0fe1d8c2a63479bcb9d2746b8042af6f65f42c7" dependencies = [ "phf", "swc_atoms", @@ -2173,11 +2267,10 @@ dependencies = [ [[package]] name = "swc_ecma_lints" -version = "0.67.24" +version = "0.89.45" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64de81f7e8a3af805797766aba92c410bc203f29816fd3d48d3394fe34445030" +checksum = "bc420c5cda6b25f2fe8823ab4ad7bd3437cdc50c74628b0027043dd7aea8cc8f" dependencies = [ - "ahash", "auto_impl", "dashmap", "parking_lot", @@ -2194,11 +2287,10 @@ dependencies = [ [[package]] name = "swc_ecma_loader" -version = "0.41.28" +version = "0.45.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f85e707c11871e45d103f81699189d8e94d06abf9b7dc958dcea8ff410762cd" +checksum = "31cf7549feec3698d0110a0a71ae547f31ae272dc92db3285ce126d6dcbdadf3" dependencies = [ - "ahash", "anyhow", "dashmap", "lru", @@ -2216,21 +2308,20 @@ dependencies = [ [[package]] name = "swc_ecma_minifier" -version = "0.160.38" +version = "0.189.71" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4d446b81d163c76cc172366ae54cf26c841c67c776dc57e97d327842d6e27b87" +checksum = "860edb2c920b6737950256682d9e186b717edc849ab1a5529fa348be34afc438" dependencies = [ - "ahash", "arrayvec", - "indexmap 1.9.2", + "indexmap 1.9.3", "num-bigint", "num_cpus", "once_cell", "parking_lot", "radix_fmt", "regex", - "retain_mut", "rustc-hash", + "ryu-js", "serde", "serde_json", "swc_atoms", @@ -2251,16 +2342,19 @@ dependencies = [ [[package]] name = "swc_ecma_parser" -version = "0.123.16" +version = "0.141.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "928c148fee43281df26871a2d1f242092734622e70aa3ea7ad474723537c702d" +checksum = "0b8436a58ac9f31068e9bf871e2ce7f3a18fe1d7e3fe8d54abe4896673eae20c" dependencies = [ "either", - "enum_kind", - "lexical", + "new_debug_unreachable", "num-bigint", + "num-traits", + "phf", "serde", "smallvec", + "smartstring", + "stacker", "swc_atoms", "swc_common", "swc_ecma_ast", @@ -2270,17 +2364,17 @@ dependencies = [ [[package]] name = "swc_ecma_preset_env" -version = "0.176.6" +version = "0.203.59" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ebd54f36f15affcd628a192ef2ef9174e6cecc066036684d613cbc705e15425" +checksum = "f7aa97cb699e64e259db89d106d5fc1f982cd3cebc8a149dab479064b9b6544e" dependencies = [ - "ahash", "anyhow", "dashmap", - "indexmap 1.9.2", + "indexmap 1.9.3", "once_cell", "preset_env_base", - "semver 1.0.16", + "rustc-hash", + "semver 1.0.20", "serde", "serde_json", "st-map", @@ -2295,9 +2389,9 @@ dependencies = [ [[package]] name = "swc_ecma_transforms" -version = "0.199.27" +version = "0.226.59" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "947d24e05c1ab93c6e64d9534c41c91add3d40533e8ba4bd8504cf456dfcfe0f" +checksum = "cae6b1951361fd848ab55d0782563b8f88ab9502108b13937fed979adc0edf89" dependencies = [ "swc_atoms", "swc_common", @@ -2315,12 +2409,13 @@ dependencies = [ [[package]] name = "swc_ecma_transforms_base" -version = "0.112.24" +version = "0.134.46" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8136889c97ee3c8fd9409f836e30f01c97e1ee0ca01d74a887a1943e2a92d41f" +checksum = "dde2e2c69dc9d38cd594329c71f5cbcbcfd2ea2475cddacc8b5e60ffe4fc72e2" dependencies = [ "better_scoped_tls", - "bitflags 1.3.2", + "bitflags 2.4.1", + "indexmap 1.9.3", "once_cell", "phf", "rustc-hash", @@ -2337,9 +2432,9 @@ dependencies = [ [[package]] name = "swc_ecma_transforms_classes" -version = "0.101.24" +version = "0.123.47" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42225b4adf0400ca97852f42c521474d9960623732a7f687804913256936c1f2" +checksum = "bf77248c0126ef404e16bb578da9af89329680ee34b6aa2cd39c4f03f0207f2d" dependencies = [ "swc_atoms", "swc_common", @@ -2351,13 +2446,12 @@ dependencies = [ [[package]] name = "swc_ecma_transforms_compat" -version = "0.137.25" +version = "0.160.52" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0c867a21058a6918b0e1ebd9f1ec2b3fd947de4ff71a3125c3f98f1cc40628e" +checksum = "15e244f72c6a725b779484967afe2dbc70fa4bc5659a74a81562737a5cb38381" dependencies = [ - "ahash", "arrayvec", - "indexmap 1.9.2", + "indexmap 1.9.3", "is-macro", "num-bigint", "serde", @@ -2366,6 +2460,17 @@ dependencies = [ "swc_common", "swc_config", "swc_ecma_ast", + "swc_ecma_compat_bugfixes", + "swc_ecma_compat_common", + "swc_ecma_compat_es2015", + "swc_ecma_compat_es2016", + "swc_ecma_compat_es2017", + "swc_ecma_compat_es2018", + "swc_ecma_compat_es2019", + "swc_ecma_compat_es2020", + "swc_ecma_compat_es2021", + "swc_ecma_compat_es2022", + "swc_ecma_compat_es3", "swc_ecma_transforms_base", "swc_ecma_transforms_classes", "swc_ecma_transforms_macros", @@ -2377,28 +2482,27 @@ dependencies = [ [[package]] name = "swc_ecma_transforms_macros" -version = "0.5.0" +version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ebf907935ec5492256b523ae7935a824d9fdc0368dcadc41375bad0dca91cd8b" +checksum = "8188eab297da773836ef5cf2af03ee5cca7a563e1be4b146f8141452c28cc690" dependencies = [ "pmutil", "proc-macro2", "quote", "swc_macros_common", - "syn 1.0.107", + "syn 2.0.32", ] [[package]] name = "swc_ecma_transforms_module" -version = "0.154.25" +version = "0.177.55" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5745e55266ffd7c14a392474e3a25885857c214bd45074b04c9a0c84a81509df" +checksum = "7b031ccdc24a608a77b5ad5ed82f9b8d8ee1a85200e9a8534d3aa8c12384104d" dependencies = [ "Inflector", - "ahash", "anyhow", - "bitflags 1.3.2", - "indexmap 1.9.2", + "bitflags 2.4.1", + "indexmap 1.9.3", "is-macro", "path-clean", "pathdiff", @@ -2418,13 +2522,12 @@ dependencies = [ [[package]] name = "swc_ecma_transforms_optimization" -version = "0.168.27" +version = "0.195.59" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "36cea8460fa0f28fea76afc4906a1f8848d9360dcbed181720b0e92a7f2e2a13" +checksum = "66edab303416a23ae8f9723e0265c4179b5fda114bcb4002a3664cc34f35ed50" dependencies = [ - "ahash", "dashmap", - "indexmap 1.9.2", + "indexmap 1.9.3", "once_cell", "petgraph", "rustc-hash", @@ -2443,11 +2546,12 @@ dependencies = [ [[package]] name = "swc_ecma_transforms_proposal" -version = "0.145.25" +version = "0.168.56" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8972e9b16e89917d99f669af270a41320a6ca08c3480eef075845414526ccc5e" +checksum = "1fcb12797536f9a41691994be4ff1927d5c1196a452a6bdd68af17cf135f0ee8" dependencies = [ "either", + "rustc-hash", "serde", "smallvec", "swc_atoms", @@ -2462,16 +2566,14 @@ dependencies = [ [[package]] name = "swc_ecma_transforms_react" -version = "0.156.26" +version = "0.180.57" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c3499f6c71eeb443a3cdcb0a82a8dcb70d83ce589f5be652fc0ff25ff68344b" +checksum = "685b914122d1af2ab348261f6aeec13693e4a2ed6af69a5a15af79bc496d4b03" dependencies = [ - "ahash", "base64 0.13.1", "dashmap", - "indexmap 1.9.2", + "indexmap 1.9.3", "once_cell", - "regex", "serde", "sha-1", "string_enum", @@ -2488,10 +2590,11 @@ dependencies = [ [[package]] name = "swc_ecma_transforms_typescript" -version = "0.160.27" +version = "0.185.57" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86e08d5a9a6ecf206585753ff67052c9d936d2914f3683e8b0021204ef355b04" +checksum = "56f68de144cbc081f83b1994767133e2cf351e4452434376d7b7d9527c65cba9" dependencies = [ + "ryu-js", "serde", "swc_atoms", "swc_common", @@ -2504,12 +2607,11 @@ dependencies = [ [[package]] name = "swc_ecma_usage_analyzer" -version = "0.1.13" +version = "0.20.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95430933ea004dfa5d5da3cc383f57f578dedab27b8e4645c84610c6c72b3ab8" +checksum = "dfa8d5dc775d4bf35967a0216783058b13ffe5423b807927aa5dbc92251f6839" dependencies = [ - "ahash", - "indexmap 1.9.2", + "indexmap 1.9.3", "rustc-hash", "swc_atoms", "swc_common", @@ -2522,11 +2624,11 @@ dependencies = [ [[package]] name = "swc_ecma_utils" -version = "0.106.18" +version = "0.124.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6bdb27e68d2c658204efd98c506ea2ecf942737b513f5b2140b8d81d04fcedc1" +checksum = "ad013c92a6e05a850db088ae7a17dc667209780b2a1a313544540111f33b5233" dependencies = [ - "indexmap 1.9.2", + "indexmap 1.9.3", "num_cpus", "once_cell", "rustc-hash", @@ -2540,9 +2642,9 @@ dependencies = [ [[package]] name = "swc_ecma_visit" -version = "0.81.11" +version = "0.96.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aeee9de2cf4e80f0c6802f9a44989e6b1ad973b94849e76b1745f6d87144f517" +checksum = "ba962f0becf83bab12a17365dface5a4f636c9e1743d479e292b96910a753743" dependencies = [ "num-bigint", "swc_atoms", @@ -2554,21 +2656,21 @@ dependencies = [ [[package]] name = "swc_eq_ignore_macros" -version = "0.1.1" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c20468634668c2bbab581947bb8c75c97158d5a6959f4ba33df20983b20b4f6" +checksum = "05a95d367e228d52484c53336991fdcf47b6b553ef835d9159db4ba40efb0ee8" dependencies = [ "pmutil", "proc-macro2", "quote", - "syn 1.0.107", + "syn 2.0.32", ] [[package]] name = "swc_error_reporters" -version = "0.13.28" +version = "0.17.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad1b6b90dbfe7a4c7962c21732cc403f071bb795d93c5a82bce5290e31b3a9b5" +checksum = "d29add35412af288be50e1012bbb825a66871bb2b4d960d1c456917ec3ccea32" dependencies = [ "anyhow", "miette 4.7.1", @@ -2579,35 +2681,34 @@ dependencies = [ [[package]] name = "swc_fast_graph" -version = "0.17.28" +version = "0.21.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c54361a935915a14d68a906126a8ee6e08ac27a0698a74337cbd6c0e15af40a" +checksum = "8117f6d10bbcb30cb3e549d6fa7637cb6d7c713cb71b2ce1808105a6825c788d" dependencies = [ - "ahash", - "indexmap 1.9.2", + "indexmap 1.9.3", "petgraph", + "rustc-hash", "swc_common", ] [[package]] name = "swc_macros_common" -version = "0.3.6" +version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4be988307882648d9bc7c71a6a73322b7520ef0211e920489a98f8391d8caa2" +checksum = "7a273205ccb09b51fabe88c49f3b34c5a4631c4c00a16ae20e03111d6a42e832" dependencies = [ "pmutil", "proc-macro2", "quote", - "syn 1.0.107", + "syn 2.0.32", ] [[package]] name = "swc_node_comments" -version = "0.16.27" +version = "0.20.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fda859d1a9e48fb151e9da8cfc29cac4dd788941ba9cf639a77828c978adcb7" +checksum = "282ef548f602694c4eaa36a1d704282fd9713b9725b58bce7fb41630feefc4f7" dependencies = [ - "ahash", "dashmap", "swc_atoms", "swc_common", @@ -2615,29 +2716,29 @@ dependencies = [ [[package]] name = "swc_timer" -version = "0.17.29" +version = "0.21.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e5e7e11a10d9900fc60809bf560ef7ebf0b571dc5b0733005fb11343270574b" +checksum = "5a200243f3c296f74f52a562342ec0e972376377f4c202b0fa84a0e860a3bff7" dependencies = [ "tracing", ] [[package]] name = "swc_trace_macro" -version = "0.1.2" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4795c8d23e0de62eef9cac0a20ae52429ee2ffc719768e838490f195b7d7267" +checksum = "ff9719b6085dd2824fd61938a881937be14b08f95e2d27c64c825a9f65e052ba" dependencies = [ "proc-macro2", "quote", - "syn 1.0.107", + "syn 2.0.32", ] [[package]] name = "swc_visit" -version = "0.5.3" +version = "0.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82f2bcb7223e185c4c7cbf5e0c1207dec6d2bfd5e72e3fb7b3e8d179747e9130" +checksum = "e87c337fbb2d191bf371173dea6a957f01899adb8f189c6c31b122a6cfc98fc3" dependencies = [ "either", "swc_visit_macros", @@ -2645,16 +2746,16 @@ dependencies = [ [[package]] name = "swc_visit_macros" -version = "0.5.4" +version = "0.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fb1f3561674d84947694d41fb6d5737d19539222779baeac1b3a071a2b29428" +checksum = "0f322730fb82f3930a450ac24de8c98523af7d34ab8cb2f46bcb405839891a99" dependencies = [ "Inflector", "pmutil", "proc-macro2", "quote", "swc_macros_common", - "syn 1.0.107", + "syn 2.0.32", ] [[package]] @@ -2670,9 +2771,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.28" +version = "2.0.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04361975b3f5e348b2189d8dc55bc942f278b2d482a6a0365de5bdd62d351567" +checksum = "239814284fd6f1a4ffe4ca893952cdd93c224b6a1571c9a9eadd670295c0c9e2" dependencies = [ "proc-macro2", "quote", @@ -2736,7 +2837,7 @@ checksum = "266b2e40bc00e5a6c09c3584011e08b06f123c00362c92b975ba9843aaaa14b8" dependencies = [ "proc-macro2", "quote", - "syn 2.0.28", + "syn 2.0.32", ] [[package]] @@ -2824,7 +2925,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.28", + "syn 2.0.32", ] [[package]] @@ -2878,16 +2979,6 @@ dependencies = [ "tracing-subscriber", ] -[[package]] -name = "triomphe" -version = "0.1.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1ee9bd9239c339d714d657fac840c6d2a4f9c45f4f9ec7b0975113458be78db" -dependencies = [ - "serde", - "stable_deref_trait", -] - [[package]] name = "typed-arena" version = "2.0.2" @@ -2908,9 +2999,9 @@ checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba" [[package]] name = "unicode-bidi" -version = "0.3.8" +version = "0.3.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "099b7128301d285f79ddd55b9a83d5e6b9e97c92e0ea0daebee7263e932de992" +checksum = "92888ba5573ff080736b3648696b70cafad7d250551175acbaa4e0385b3e1460" [[package]] name = "unicode-id" @@ -2982,9 +3073,9 @@ dependencies = [ [[package]] name = "url" -version = "2.3.1" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d68c799ae75762b8c3fe375feb6600ef5602c883c5d21eb51c09f22b83c4643" +checksum = "31e6302e3bb753d46e83516cae55ae196fc0c309407cf11ab35cc51a4c2a4633" dependencies = [ "form_urlencoded", "idna", @@ -3005,17 +3096,12 @@ checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" [[package]] name = "vergen" -version = "7.5.0" +version = "8.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "571b69f690c855821462709b6f41d42ceccc316fbd17b60bd06d06928cfe6a99" +checksum = "1290fd64cc4e7d3c9b07d7f333ce0ce0007253e32870e632624835cc80b83939" dependencies = [ "anyhow", - "cfg-if", - "enum-iterator", - "getset", "rustversion", - "thiserror", - "time 0.3.30", ] [[package]] @@ -3279,3 +3365,23 @@ name = "yansi" version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09041cd90cf85f7f8b2df60c646f853b7f535ce68f85244eb6731cf89fa498ec" + +[[package]] +name = "zerocopy" +version = "0.7.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e97e415490559a91254a2979b4829267a57d2fcd741a98eee8b722fb57289aa0" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.7.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd7e48ccf166952882ca8bd778a43502c64f33bf94c12ebe2a7f08e5a0f6689f" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.32", +] diff --git a/Cargo.toml b/Cargo.toml index def13455..1b6e40d5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -24,8 +24,7 @@ once_cell = "1.17.0" regex = "1.7.1" rustc-hash = "1.1.0" smallvec = { version = "1.11.2", features = ["union", "const_new"] } -swc_core = { version = "0.53.1", features = [ - "common_perf", +swc_core = { version = "0.86.82", features = [ "common", "common_sourcemap", "ecma_visit_path", diff --git a/crates/forge_analyzer/src/definitions.rs b/crates/forge_analyzer/src/definitions.rs index 71a15ca4..f30ae608 100644 --- a/crates/forge_analyzer/src/definitions.rs +++ b/crates/forge_analyzer/src/definitions.rs @@ -16,8 +16,8 @@ use swc_core::{ CallExpr, Callee, ClassDecl, ClassExpr, ClassMethod, ComputedPropName, CondExpr, Constructor, ContinueStmt, Decl, DefaultDecl, DoWhileStmt, ExportAll, ExportDecl, ExportDefaultDecl, ExportDefaultExpr, ExportNamedSpecifier, Expr, ExprOrSpread, - ExprStmt, FnDecl, FnExpr, ForInStmt, ForOfStmt, ForStmt, Function, Id, Ident, IfStmt, - Import, ImportDecl, ImportDefaultSpecifier, ImportNamedSpecifier, + ExprStmt, FnDecl, FnExpr, ForHead, ForInStmt, ForOfStmt, ForStmt, Function, Id, Ident, + IfStmt, Import, ImportDecl, ImportDefaultSpecifier, ImportNamedSpecifier, ImportStarAsSpecifier, JSXAttrName, JSXAttrOrSpread, JSXAttrValue, JSXElement, JSXElementChild, JSXElementName, JSXExpr, JSXExprContainer, JSXFragment, JSXMemberExpr, JSXNamespacedName, JSXObject, JSXSpreadChild, JSXText, KeyValuePatProp, KeyValueProp, @@ -27,8 +27,8 @@ use swc_core::{ PropName, PropOrSpread, RestPat, ReturnStmt, SeqExpr, Stmt, Str, Super, SuperProp, SuperPropExpr, SwitchStmt, TaggedTpl, ThisExpr, ThrowStmt, Tpl, TplElement, TryStmt, TsAsExpr, TsConstAssertion, TsInstantiation, TsNonNullExpr, TsSatisfiesExpr, - TsTypeAssertion, UnaryExpr, UpdateExpr, VarDecl, VarDeclOrExpr, VarDeclOrPat, - VarDeclarator, WhileStmt, WithStmt, YieldExpr, + TsTypeAssertion, UnaryExpr, UpdateExpr, VarDecl, VarDeclOrExpr, VarDeclarator, + WhileStmt, WithStmt, YieldExpr, }, atoms::{Atom, JsWord}, visit::{noop_visit_type, Visit, VisitWith}, @@ -1036,7 +1036,7 @@ impl<'cx> FunctionAnalyzer<'cx> { if let [ExprOrSpread { expr, .. }] = args { debug!("found useState/then/map/foreach/filter"); match &**expr { - Expr::Arrow(ArrowExpr { body, .. }) => match body { + Expr::Arrow(ArrowExpr { body, .. }) => match &**body { BlockStmtOrExpr::BlockStmt(stmt) => { self.lower_stmts(&stmt.stmts); return Operand::UNDEF; @@ -1141,7 +1141,7 @@ impl<'cx> FunctionAnalyzer<'cx> { if ident_value.sym.starts_with("on") && second_char.is_uppercase() { match &**expr { Expr::Arrow(arrow_expr) => { - if let BlockStmtOrExpr::Expr(expr) = &arrow_expr.body { + if let BlockStmtOrExpr::Expr(expr) = &*arrow_expr.body { self.lower_expr(&**expr); } } @@ -1497,7 +1497,7 @@ impl<'cx> FunctionAnalyzer<'cx> { | Expr::TsInstantiation(TsInstantiation { expr, .. }) | Expr::TsSatisfies(TsSatisfiesExpr { expr, .. }) => self.lower_expr(expr), Expr::PrivateName(PrivateName { id, .. }) => todo!(), - Expr::OptChain(OptChainExpr { base, .. }) => match base { + Expr::OptChain(OptChainExpr { base, .. }) => match &**base { OptChainBase::Call(OptCall { callee, args, .. }) => { self.lower_call(callee.as_ref().into(), args) } @@ -1666,6 +1666,7 @@ impl<'cx> FunctionAnalyzer<'cx> { Decl::Var(var) => { self.lower_var_decl(var); } + Decl::Using(_) => {} Decl::TsInterface(_) | Decl::TsTypeAlias(_) | Decl::TsEnum(_) @@ -1678,12 +1679,15 @@ impl<'cx> FunctionAnalyzer<'cx> { } } - fn lower_loop(&mut self, left: &VarDeclOrPat, right: &Expr, body: &Stmt) { + fn lower_loop(&mut self, left: &ForHead, right: &Expr, body: &Stmt) { // FIXME: don't assume loops are infinite let opnd = self.lower_expr(right); match left { - VarDeclOrPat::VarDecl(var) => self.lower_var_decl(var), - VarDeclOrPat::Pat(pat) => self.bind_pats(pat, Rvalue::Read(opnd)), + ForHead::VarDecl(var) => self.lower_var_decl(&*var), + ForHead::Pat(pat) => self.bind_pats(pat, Rvalue::Read(opnd)), + ForHead::UsingDecl(_) => { + //FIXME: investigate this case + } } self.lower_stmt(body); } @@ -1786,7 +1790,11 @@ impl Visit for LocalDefiner<'_> { ident.visit_with(self); } Decl::Var(vars) => vars.visit_children_with(self), - Decl::TsInterface(_) | Decl::TsTypeAlias(_) | Decl::TsEnum(_) | Decl::TsModule(_) => {} + Decl::Using(_) + | Decl::TsInterface(_) + | Decl::TsTypeAlias(_) + | Decl::TsEnum(_) + | Decl::TsModule(_) => {} } } @@ -1965,7 +1973,7 @@ impl Visit for FunctionCollector<'_> { operand_stack: vec![], in_lhs: false, }; - match func_body { + match &**func_body { BlockStmtOrExpr::BlockStmt(BlockStmt { stmts, .. }) => { analyzer.lower_stmts(stmts); } @@ -2228,8 +2236,7 @@ impl Visit for Lowerer<'_> { let owner = self.res .add_anonymous("__CONSTRUCTOR", AnonType::Closure, self.curr_mod); - self.res - .add_class_method(&ident.sym, n.key.clone(), class_def, owner); + self.res.add_class_method(n.key.clone(), class_def, owner); } } } @@ -2243,8 +2250,7 @@ impl Visit for Lowerer<'_> { let owner = self.res .add_anonymous("__CLASSMETHOD", AnonType::Closure, self.curr_mod); - self.res - .add_class_method(&ident.sym, n.key.clone(), class_def, owner); + self.res.add_class_method(n.key.clone(), class_def, owner); } } } @@ -2588,10 +2594,11 @@ impl Visit for ExportCollector<'_> { let VarDecl { decls, .. } = &**vardecls; decls.iter().for_each(|var| self.visit_var_declarator(var)); } - Decl::TsInterface(_) => {} - Decl::TsTypeAlias(_) => {} - Decl::TsEnum(_) => {} - Decl::TsModule(_) => {} + Decl::Using(_) + | Decl::TsInterface(_) + | Decl::TsTypeAlias(_) + | Decl::TsEnum(_) + | Decl::TsModule(_) => {} }; } @@ -2782,13 +2789,7 @@ impl Environment { } } - fn add_class_method( - &mut self, - name: impl Into, - n: PropName, - class_def: DefId, - owner: DefId, - ) { + fn add_class_method(&mut self, n: PropName, class_def: DefId, owner: DefId) { if let DefKind::Class(class) = self.def_mut(class_def) { if let PropName::Ident(ident) = &n { class.pub_members.push((ident.sym.to_owned(), owner)); diff --git a/crates/forge_analyzer/src/exports.rs b/crates/forge_analyzer/src/exports.rs index da36c186..826bc46c 100644 --- a/crates/forge_analyzer/src/exports.rs +++ b/crates/forge_analyzer/src/exports.rs @@ -39,10 +39,11 @@ impl Visit for ExportCollector { let VarDecl { decls, .. } = &**vardecls; decls.iter().for_each(|var| self.visit_var_declarator(var)); } - Decl::TsInterface(_) => {} - Decl::TsTypeAlias(_) => {} - Decl::TsEnum(_) => {} - Decl::TsModule(_) => {} + Decl::Using(_) + | Decl::TsInterface(_) + | Decl::TsTypeAlias(_) + | Decl::TsEnum(_) + | Decl::TsModule(_) => {} }; } From 056cb2f89cc1a410386a591250a2fec18eac395b Mon Sep 17 00:00:00 2001 From: Joshua Wong Date: Tue, 28 Nov 2023 12:51:20 -0600 Subject: [PATCH 221/517] fix: swc compile errors --- crates/forge_analyzer/src/checkers.rs | 38 ++++++++---------------- crates/forge_analyzer/src/definitions.rs | 26 +++++++++++++--- crates/forge_analyzer/src/interp.rs | 1 - crates/forge_analyzer/src/utils.rs | 4 +-- crates/fsrt/src/main.rs | 1 - 5 files changed, 36 insertions(+), 34 deletions(-) diff --git a/crates/forge_analyzer/src/checkers.rs b/crates/forge_analyzer/src/checkers.rs index aa16bb4e..b2490e16 100644 --- a/crates/forge_analyzer/src/checkers.rs +++ b/crates/forge_analyzer/src/checkers.rs @@ -1,41 +1,27 @@ use core::fmt; -use forge_loader::forgepermissions::ForgePermissions; -use forge_permission_resolver::permissions_resolver::{ - check_url_for_permissions, get_permission_resolver_confluence, get_permission_resolver_jira, - PermissionHashMap, RequestType, -}; +use forge_permission_resolver::permissions_resolver::{check_url_for_permissions, RequestType}; use forge_utils::FxHashMap; use itertools::Itertools; -use petgraph::operator; -use regex::Regex; -use serde::de::value; use smallvec::SmallVec; -use std::{ - cmp::max, - collections::{HashMap, HashSet}, - iter, mem, - ops::ControlFlow, - path::PathBuf, -}; -use swc_core::ecma::{transforms::base::perf::Check, utils::Value::Known}; +use std::{cmp::max, iter, mem, ops::ControlFlow, path::PathBuf}; use tracing::{debug, info, warn}; use crate::{ - definitions::{Class, Const, DefId, DefKind, Environment, IntrinsicName, PackageData, Value}, + definitions::{Const, DefId, Environment, IntrinsicName, Value}, interp::{ Checker, Dataflow, EntryKind, EntryPoint, Frame, Interp, JoinSemiLattice, Runner, WithCallStack, }, ir::{ Base, BasicBlock, BasicBlockId, BinOp, Inst, Intrinsic, Literal, Location, Operand, - Projection, Rvalue, Successors, VarId, VarKind, Variable, + Projection, Rvalue, VarId, VarKind, Variable, }, reporter::{IntoVuln, Reporter, Severity, Vulnerability}, utils::{ add_const_to_val_vec, add_elements_to_intrinsic_struct, convert_operand_to_raw, - get_defid_from_varkind, get_prev_value, get_str_from_operand, resolve_var_from_operand, - return_value_from_string, translate_request_type, + get_defid_from_varkind, get_prev_value, get_str_from_operand, return_value_from_string, + translate_request_type, }, worklist::WorkList, }; @@ -1425,7 +1411,8 @@ impl<'cx> Dataflow<'cx> for DefintionAnalysisRunner { } } Rvalue::Template(template) => { - let quasis_joined = template.quasis.join(""); + let quasis_joined: String = + template.quasis.iter().map(ToString::to_string).collect(); let mut all_potential_values = vec![String::from("")]; if template.exprs.len() == 0 { all_potential_values.push(quasis_joined.clone()); @@ -1436,7 +1423,7 @@ impl<'cx> Dataflow<'cx> for DefintionAnalysisRunner { if let Some(quasis) = template.quasis.get(i) { all_values = all_values .iter() - .map(|value| value.to_owned() + &quasis.to_string()) + .map(|value| format!("{value}{quasis}")) .collect(); } let mut new_values = vec![]; @@ -1446,7 +1433,7 @@ impl<'cx> Dataflow<'cx> for DefintionAnalysisRunner { for str_value in values { for value in &all_values { if let Some(str) = &str_value { - new_values.push(value.clone() + &str) + new_values.push(value.clone() + &**str) } else { new_values.push(value.clone()) } @@ -1457,11 +1444,10 @@ impl<'cx> Dataflow<'cx> for DefintionAnalysisRunner { } if template.quasis.len() > template.exprs.len() { + let last_elem = template.quasis.last().unwrap(); all_values = all_values .iter() - .map(|value| { - value.to_owned() + &template.quasis.last().unwrap().to_string() - }) + .map(|value| format!("{value}{last_elem}")) .collect(); } diff --git a/crates/forge_analyzer/src/definitions.rs b/crates/forge_analyzer/src/definitions.rs index 112586b7..27c2c3b7 100644 --- a/crates/forge_analyzer/src/definitions.rs +++ b/crates/forge_analyzer/src/definitions.rs @@ -1057,8 +1057,26 @@ impl<'cx> FunctionAnalyzer<'cx> { // Case 2: The case where the root object is default import, // in which case, you only need to check PackageData if it is an object + // import { foo } from 'foo'; + // foo.bar.sign('what'); + // foo.bar.bar.sign('whatever'); + // PackageData { package: 'foo', identifier: 'bar', method: 'sign' } + // [Def, Static, Static] + //[PropPath::Def(def), PropPath::Static(a), PropPath::Static(b)] + // 1. Star, identifier, method + // + // [Def, Static] + //[PropPath::Def(def), PropPath::Static(atom)] + // 1. Star, identifier (NO METHOD) + // 2. Named/Default => package matches imported from and ident is the same, method + // + // [PropPath::Def(def)] + // 1. Named/Default => package matches imported from and ident is the same, (NO METHOD) + + // [Def, .., Static] star + // [Def, .., Static] named/default [PropPath::Def(def), ref authn @ .., PropPath::Static(ref last)] - if self.secret_packages.iter().any(|ref package_data| { + if self.secret_packages.iter().any(|package_data| { if let Some(tuple) = self.res.as_foreign_import(def) { let checking_value = tuple.1.to_string(); // debug!("TUPLE VALUES: {tuple:?} checking_value of to_string: {checking_value}"); @@ -1073,7 +1091,7 @@ impl<'cx> FunctionAnalyzer<'cx> { } }) => { - let mut package = self.secret_packages.iter().filter(|ref package_data| { + let mut package = self.secret_packages.iter().filter(|&package_data| { if let Some(tuple) = self.res.as_foreign_import(def) { // debug!("LAST: {last}"); // debug!("PACKAGE: {package_data:?}"); @@ -1446,7 +1464,7 @@ impl<'cx> FunctionAnalyzer<'cx> { match &**expr { Expr::Arrow(arrow_expr) => { if let BlockStmtOrExpr::Expr(expr) = &*arrow_expr.body { - self.lower_expr(&**expr); + self.lower_expr(&**expr, None); } } Expr::Ident(ident) => { @@ -3016,7 +3034,7 @@ impl Visit for ImportCollector<'_> { impl Visit for GlobalCollector<'_> { fn visit_function(&mut self, _: &Function) {} - fn visit_class(&mut self, _: &Clss) {} + fn visit_class(&mut self, _: &swc_core::ecma::ast::Class) {} fn visit_module(&mut self, n: &Module) { // we encouter 2 statements, so the defid gets reassigned every time diff --git a/crates/forge_analyzer/src/interp.rs b/crates/forge_analyzer/src/interp.rs index 5280e650..6c8c2fb2 100644 --- a/crates/forge_analyzer/src/interp.rs +++ b/crates/forge_analyzer/src/interp.rs @@ -11,7 +11,6 @@ use std::{ path::PathBuf, }; -use forge_loader::forgepermissions::ForgePermissions; use forge_permission_resolver::permissions_resolver::{ check_url_for_permissions, get_permission_resolver_confluence, get_permission_resolver_jira, PermissionHashMap, RequestType, diff --git a/crates/forge_analyzer/src/utils.rs b/crates/forge_analyzer/src/utils.rs index 490fbe10..49c0859e 100644 --- a/crates/forge_analyzer/src/utils.rs +++ b/crates/forge_analyzer/src/utils.rs @@ -28,12 +28,12 @@ pub fn add_const_to_val_vec(val: &Value, const_val: &Const, vals: &mut Vec { if let Const::Literal(lit2) = const_val { - vals.push(lit.to_owned() + &lit2); + vals.push(format!("{lit}{lit2}")); } } Value::Phi(phi_val2) => phi_val2.iter().for_each(|val2| { if let (Const::Literal(lit1), Const::Literal(lit2)) = (&const_val, val2) { - vals.push(lit1.to_owned() + lit2); + vals.push(format!("{lit1}{lit2}")); } }), _ => {} diff --git a/crates/fsrt/src/main.rs b/crates/fsrt/src/main.rs index efb97d52..e2eda5f7 100644 --- a/crates/fsrt/src/main.rs +++ b/crates/fsrt/src/main.rs @@ -1,6 +1,5 @@ #![allow(clippy::type_complexity)] use clap::{Parser, ValueHint}; -use forge_loader::forgepermissions::ForgePermissions; use forge_permission_resolver::permissions_resolver::{ get_permission_resolver_confluence, get_permission_resolver_jira, }; From f9db56e41aecef88a3cda8d193cf09af0025b214 Mon Sep 17 00:00:00 2001 From: awang7 Date: Tue, 28 Nov 2023 23:58:30 -0500 Subject: [PATCH 222/517] refactor: rebuilt as_intrinsic to be sleaker and more organized. Tests passes in utils.js. --- crates/forge_analyzer/src/definitions.rs | 259 +++++++----------- .../src/utils.js | 25 +- 2 files changed, 110 insertions(+), 174 deletions(-) diff --git a/crates/forge_analyzer/src/definitions.rs b/crates/forge_analyzer/src/definitions.rs index 27c2c3b7..d76e9e71 100644 --- a/crates/forge_analyzer/src/definitions.rs +++ b/crates/forge_analyzer/src/definitions.rs @@ -922,7 +922,6 @@ impl<'cx> FunctionAnalyzer<'cx> { } match *callee { [PropPath::Unknown((ref name, ..))] if *name == *"fetch" => Some(Intrinsic::Fetch), - // [PropPath::Def(def)] => {}] [PropPath::Def(def), ref authn @ .., PropPath::Static(ref last)] if (*last == *"requestJira" || *last == *"requestConfluence") && Some(&ImportKind::Default) @@ -961,156 +960,104 @@ impl<'cx> FunctionAnalyzer<'cx> { ApiCallKind::Authorize => Some(Intrinsic::Authorize(IntrinsicName::Other)), } } - // Case 1: the “root” object is a star import and the Static proppath member is either identifier - [PropPath::Def(def), ref authn @ .., PropPath::Static(ref last)] - if self.secret_packages.iter().any(|ref package_data| { - if let Some(tuple) = self.res.as_foreign_import(def) { - return tuple.0 == package_data.package_name && tuple.1 == ImportKind::Star; - } else { - return false; - } - }) => - { - match authn.get(0) { - // case where function requires object to be invoked first - // example: import * as cryptoJS from 'crypto-js'; - // var aes = cryptoJS.AES.encrypt('Secret message', 'secret password'); - Some(PropPath::Static(ref object)) - if self - .secret_packages - .iter() - .filter(|ref package_data| { - if let Some(tuple) = self.res.as_foreign_import(def) { - return tuple.0 == package_data.package_name - && tuple.1 == ImportKind::Star; - } else { - return false; - } - }) - .any(|package_data| *package_data.identifier == *object) => - { - let package = self - .secret_packages - .iter() - .filter(|ref package_data| { - if let Some(tuple) = self.res.as_foreign_import(def) { - return tuple.0 == package_data.package_name - && tuple.1 == ImportKind::Star - && *package_data.identifier == *object; - } else { - return false; - } - }) - .next() - .unwrap(); - if let Some(method) = &package.method { - if *method == **last { - debug!("bug caught on package: {package:?}"); - Some(Intrinsic::JWTSign(package.clone())) - } else { - None - } - } else { - None - } - } - - // Star imports that don't have any object call to invoke function - // import * as atlassian_jwt from "atlassian-jwt"; - // atlassian_jwt.encode(blah, blah); - _ if self.secret_packages.iter().any(|ref package_data| { - if let Some(tuple) = self.res.as_foreign_import(def) { - return tuple.0 == package_data.package_name - && tuple.1 == ImportKind::Star - && *package_data.identifier == *last - && package_data.method == None; + // Case 1: the “root” object is a star import and the Static proppath member is either identifier or method + // [Def, Static, Static] + //[PropPath::Def(def), PropPath::Static(a), PropPath::Static(b)] + // 1. Star, identifier, method + [PropPath::Def(def), PropPath::Static(ref identifier), PropPath::Static(ref method)] => { + // case where function requires object to be invoked first + // example: import * as cryptoJS from 'crypto-js'; + // var aes = cryptoJS.AES.encrypt('Secret message', 'secret password'); + if let Some((package_name, import_kind)) = self.res.as_foreign_import(def) { + let package_found = self.secret_packages.iter().find(|&package_data| { + if let Some(package_method) = &package_data.method { + return package_name == package_data.package_name + && import_kind == ImportKind::Star + && identifier == &package_data.identifier + && *method == *package_method; } else { - return false; + false } - }) => - { - let package = self - .secret_packages - .iter() - .filter(|ref package_data| { - if let Some(tuple) = self.res.as_foreign_import(def) { - return tuple.0 == package_data.package_name - && tuple.1 == ImportKind::Star - && *package_data.identifier == *last - && package_data.method == None; - } else { - return false; - } - }) - .next(); + }); - if package.is_none() { - None - } else { - debug!("bug caught on package: {package:?}"); - Some(Intrinsic::JWTSign(package.unwrap().clone())) - } + if let Some(package) = package_found { + Some(Intrinsic::JWTSign(package.clone())) + } else { + None } - _ => None, + } else { + None } } - - // Case 2: The case where the root object is default import, - // in which case, you only need to check PackageData if it is an object // import { foo } from 'foo'; // foo.bar.sign('what'); // foo.bar.bar.sign('whatever'); // PackageData { package: 'foo', identifier: 'bar', method: 'sign' } - // [Def, Static, Static] - //[PropPath::Def(def), PropPath::Static(a), PropPath::Static(b)] - // 1. Star, identifier, method - // + // [Def, Static] //[PropPath::Def(def), PropPath::Static(atom)] // 1. Star, identifier (NO METHOD) - // 2. Named/Default => package matches imported from and ident is the same, method - // - // [PropPath::Def(def)] - // 1. Named/Default => package matches imported from and ident is the same, (NO METHOD) + // 2. Named/Default => import kind matches idenntifier and static(atom) matches method + [PropPath::Def(def), PropPath::Static(ref method_name)] => { + if let Some((package_name, import_kind)) = self.res.as_foreign_import(def) { + // Star imports that don't have any object call to invoke function + // import * as atlassian_jwt from "atlassian-jwt"; + // atlassian_jwt.encode(blah, blah); - // [Def, .., Static] star - // [Def, .., Static] named/default - [PropPath::Def(def), ref authn @ .., PropPath::Static(ref last)] - if self.secret_packages.iter().any(|package_data| { - if let Some(tuple) = self.res.as_foreign_import(def) { - let checking_value = tuple.1.to_string(); - // debug!("TUPLE VALUES: {tuple:?} checking_value of to_string: {checking_value}"); - // debug!("PACKAGE: {package_data:?}"); - // let boolean_value = tuple.0 == package_data.package_name; - // let is_import_value = self.res.is_import(Some(tuple.1.to_string())); - // debug!("WHAT IS GOING ON WHY EVERYTHING FAILING!? Boolean value = {boolean_value} is_import_value: {is_import_value}"); - return *tuple.0 == *package_data.package_name - && self.res.is_import(Some(tuple.1.to_string())); - } else { - return false; - } - }) => - { - let mut package = self.secret_packages.iter().filter(|&package_data| { - if let Some(tuple) = self.res.as_foreign_import(def) { - // debug!("LAST: {last}"); - // debug!("PACKAGE: {package_data:?}"); - return tuple.0 == package_data.package_name - && self.res.is_import(Some(tuple.1.to_string())) - && *last == *package_data.identifier; + if import_kind == ImportKind::Star { + let package_found = self.secret_packages.iter().find(|&package_data| { + package_name == package_data.package_name + && *method_name == package_data.identifier + && package_data.method.is_none() + }); + if let Some(package) = package_found { + Some(Intrinsic::JWTSign(package.clone())) + } else { + None + } } else { - return false; + // Named or default imports that + // import AES from "crypto-js"; + // import {AES} from "crypto-js" + // Aes.encrypt(blah, blah); + let package_found = self.secret_packages.iter().find(|&package_data| { + if let Some(method) = &package_data.method { + return package_name == package_data.package_name + && import_kind == *package_data.identifier + && *method_name == *method; + } else { + false + } + }); + if let Some(package) = package_found { + Some(Intrinsic::JWTSign(package.clone())) + } else { + None + } } - }); - - if let Some(obj) = package.next() { - // debug!("bug caught on package: {obj:?}"); - // debug!("tf is last!? {last}"); - Some(Intrinsic::JWTSign(obj.clone())) } else { None } } + // [Def, .., Static] star + // [Def, .., Static] named/default + // [PropPath::Def(def)] + // 1. Named/Default => package matches imported from and ident is the same, (NO METHOD) + [PropPath::Def(def)] if self.res.as_foreign_import(def).is_some() => { + if let Some((package_name, import_kind)) = self.res.as_foreign_import(def) { + let package = self.secret_packages.iter().find(|&package_data| { + *package_name == *package_data.package_name + && import_kind == *package_data.identifier + && package_data.method.is_none() + }); + if package.is_some() { + return Some(Intrinsic::JWTSign(package.unwrap().clone())); + } + } + + None + } + [PropPath::Def(def), PropPath::Static(ref s), ..] if is_storage_read(s) => { match self.res.is_imported_from(def, "@forge/api") { Some(ImportKind::Named(ref name)) if *name == *"storage" => { @@ -1119,30 +1066,6 @@ impl<'cx> FunctionAnalyzer<'cx> { _ => None, } } - // Case 2: The case where the root object is a named or default import, - // in which case, you only need to check PackageData if it is an object - // where iimport type is Named - [PropPath::Def(def), ..] if self.res.as_foreign_import(def).is_some() => { - if let Some((package_name, import_kind)) = self.res.as_foreign_import(def) { - // Debug LOG when finding the darn thing - // package_name: Atom('crypto-js' type=dynamic) - // import_kind: Named(Atom('HmacMD5' type=inline)) - - let package = self - .secret_packages - .iter() - .filter(|ref package_data| { - *package_name == *package_data.package_name - && import_kind.to_string() == package_data.identifier - }) - .next(); - if package != None { - return Some(Intrinsic::JWTSign(package.unwrap().clone())); - } - } - - None - } [PropPath::Def(def), ..] => match self.res.is_imported_from(def, "@forge/api") { Some(ImportKind::Named(ref name)) if *name == *"authorize" => { Some(Intrinsic::Authorize(IntrinsicName::Other)) @@ -3538,14 +3461,14 @@ impl Environment { DefKind::Undefined => DefKind::Undefined, } } - pub fn is_import(&self, import_kind: Option) -> bool { - match import_kind { - Some(default) if default == "default" => true, - Some(named) if named == "named" => true, - Some(star) if star == "star" => false, - _ => false, - } - } + // pub fn is_named_or(&self, import_kind: Option) -> bool { + // match import_kind { + // Some(default) if default == "default" => true, + // Some(named) if named == "named" => true, + // Some(star) if star == "star" => false, + // _ => false, + // } + // } pub fn as_foreign_import(&self, def: DefId) -> Option<(JsWord, ImportKind)> { let DefKind::Foreign(f) = self.def_ref(def) else { @@ -3784,3 +3707,13 @@ impl DefId { Self(raw) } } + +impl PartialEq for ImportKind { + fn eq(&self, other: &str) -> bool { + match self { + ImportKind::Star => false, + ImportKind::Default => other == "default", + ImportKind::Named(name) => name == other, + } + } +} diff --git a/test-apps/jira-damn-vulnerable-forge-app/src/utils.js b/test-apps/jira-damn-vulnerable-forge-app/src/utils.js index e05b103f..025fed04 100755 --- a/test-apps/jira-damn-vulnerable-forge-app/src/utils.js +++ b/test-apps/jira-damn-vulnerable-forge-app/src/utils.js @@ -9,12 +9,14 @@ import { classone } from './anewclass'; import * as c1 from './anewclass'; // Secret Scanner Default Imports -import jwt from 'jsonwebtoken'; +import * as bleep from 'jsonwebtoken'; +import { sign } from 'jsonwebtoken'; + import * as atlassian_jwt from 'atlassian-jwt'; // Secret Scanner Star Imports -// import * as cryptoJS from 'crypto-js'; -// import * as jwtSimple from 'jwt-simple'; +import * as cryptoJS from 'crypto-js'; +import * as jwtSimple from 'jwt-simple'; // Secret Scanner Named Imports import { HmacSHA256 } from 'crypto-js'; @@ -40,7 +42,8 @@ export async function fetchIssueSummary(issueIdOrKey, test_value) { let global = 'test'; // Function calls from default imports - // var token = jwt.sign({ foo: 'bar' }, 'peek a boo'); + // var token = bleep.sign({ foo: 'bar' }, 'peek a boo'); + var siggy = sign('Message', 'secret'); // var token = atlassian_jwt.encodeSymmetric({ foo: 'bar' }, 'Atlassian jwt'); // Function calls from star imports @@ -48,11 +51,11 @@ export async function fetchIssueSummary(issueIdOrKey, test_value) { // var simple_token = jwtSimple.encode({ foo: 'bar' }, 'Simple JWT'); // Function calls from named imports - var hmac = HmacSHA256('Secret Message', 'HMAC PASSWORD'); + // var hmac = HmacSHA256('Secret Message', 'HMAC PASSWORD'); // calling edge case - console.log(sign()); - console.log('End secret scanning test cases'); + // console.log(sign()); + // console.log('End secret scanning test cases'); // testFunctionFromTestFile(); @@ -79,10 +82,10 @@ export async function fetchIssueSummary(issueIdOrKey, test_value) { // add Secret Scanner Edge cases here // TODO: Calling function with same name as function from Secret Scanner -function sign() { - console.log('this is a test function'); - return 'test function'; -} +// function sign() { +// console.log('this is a test function'); +// return 'test function'; +// } function get_random_string() { return 'test_string_from_get_random_string'; From 899ed4e1a1ecfa82025b869334bcfb5cbdc1cefa Mon Sep 17 00:00:00 2001 From: awang7 Date: Wed, 29 Nov 2023 13:06:17 -0500 Subject: [PATCH 223/517] chore: deleted is_import implementation --- crates/forge_analyzer/src/definitions.rs | 8 -------- 1 file changed, 8 deletions(-) diff --git a/crates/forge_analyzer/src/definitions.rs b/crates/forge_analyzer/src/definitions.rs index d76e9e71..1e1fd0f5 100644 --- a/crates/forge_analyzer/src/definitions.rs +++ b/crates/forge_analyzer/src/definitions.rs @@ -3461,14 +3461,6 @@ impl Environment { DefKind::Undefined => DefKind::Undefined, } } - // pub fn is_named_or(&self, import_kind: Option) -> bool { - // match import_kind { - // Some(default) if default == "default" => true, - // Some(named) if named == "named" => true, - // Some(star) if star == "star" => false, - // _ => false, - // } - // } pub fn as_foreign_import(&self, def: DefId) -> Option<(JsWord, ImportKind)> { let DefKind::Foreign(f) = self.def_ref(def) else { From c4acb83096ece7caaf5d23553d1d4c4e0b45b5cb Mon Sep 17 00:00:00 2001 From: awang7 Date: Wed, 29 Nov 2023 15:28:04 -0500 Subject: [PATCH 224/517] fix: resolving PR comments. Pushing fixes to see which comments are left --- .gitignore | 1 - crates/forge_analyzer/src/definitions.rs | 43 +++++++++++------------- 2 files changed, 20 insertions(+), 24 deletions(-) diff --git a/.gitignore b/.gitignore index 831f1db6..ea8c4bf7 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1 @@ /target -/test-apps diff --git a/crates/forge_analyzer/src/definitions.rs b/crates/forge_analyzer/src/definitions.rs index 1e1fd0f5..22af08b3 100644 --- a/crates/forge_analyzer/src/definitions.rs +++ b/crates/forge_analyzer/src/definitions.rs @@ -960,6 +960,21 @@ impl<'cx> FunctionAnalyzer<'cx> { ApiCallKind::Authorize => Some(Intrinsic::Authorize(IntrinsicName::Other)), } } + + [PropPath::Def(def), PropPath::Static(ref s), ..] if is_storage_read(s) => { + match self.res.is_imported_from(def, "@forge/api") { + Some(ImportKind::Named(ref name)) if *name == *"storage" => { + Some(Intrinsic::StorageRead) + } + _ => None, + } + } + [PropPath::Def(def), ..] => match self.res.is_imported_from(def, "@forge/api") { + Some(ImportKind::Named(ref name)) if *name == *"authorize" => { + Some(Intrinsic::Authorize(IntrinsicName::Other)) + } + _ => None, + }, // Case 1: the “root” object is a star import and the Static proppath member is either identifier or method // [Def, Static, Static] //[PropPath::Def(def), PropPath::Static(a), PropPath::Static(b)] @@ -1022,9 +1037,9 @@ impl<'cx> FunctionAnalyzer<'cx> { // Aes.encrypt(blah, blah); let package_found = self.secret_packages.iter().find(|&package_data| { if let Some(method) = &package_data.method { - return package_name == package_data.package_name + package_name == package_data.package_name && import_kind == *package_data.identifier - && *method_name == *method; + && *method_name == *method } else { false } @@ -1057,21 +1072,6 @@ impl<'cx> FunctionAnalyzer<'cx> { None } - - [PropPath::Def(def), PropPath::Static(ref s), ..] if is_storage_read(s) => { - match self.res.is_imported_from(def, "@forge/api") { - Some(ImportKind::Named(ref name)) if *name == *"storage" => { - Some(Intrinsic::StorageRead) - } - _ => None, - } - } - [PropPath::Def(def), ..] => match self.res.is_imported_from(def, "@forge/api") { - Some(ImportKind::Named(ref name)) if *name == *"authorize" => { - Some(Intrinsic::Authorize(IntrinsicName::Other)) - } - _ => None, - }, _ => None, } } @@ -1305,10 +1305,7 @@ impl<'cx> FunctionAnalyzer<'cx> { }; let first_arg = args.first().map(|expr| &*expr.expr); - // debug!("props in lower call: {props:?}"); - // debug!("first_arg in lower call: {first_arg:?}"); let intrinsic = self.as_intrinsic(&props, first_arg); - // debug!("Intrinsic Returned: {intrinsic:?}"); let call = match self.as_intrinsic(&props, first_arg) { Some(int) => Rvalue::Intrinsic(int, lowered_args), None => Rvalue::Call(callee, lowered_args), @@ -1322,7 +1319,7 @@ impl<'cx> FunctionAnalyzer<'cx> { match n { Pat::Ident(BindingIdent { id, .. }) => { let id = id.to_id(); - let def = self.res.get_or_insert_sym(id.clone(), self.module); + let def = self.res.get_or_insert_sym(id, self.module); let var = self.body.get_or_insert_global(def); self.push_curr_inst(Inst::Assign(Variable::new(var), val)); } @@ -3027,7 +3024,7 @@ impl Visit for ExportCollector<'_> { if ident.sym.to_string() == "module" { if let Some(mem_expr) = mem_expr_from_assign(n) { if let MemberProp::Ident(ident_property) = &mem_expr.prop { - if ident_property.sym.to_string() == "exports" { + if ident_property.sym == "exports" { match &*n.right { Expr::Fn(FnExpr { ident, function }) => self.add_default( DefRes::Function(()), @@ -3050,7 +3047,7 @@ impl Visit for ExportCollector<'_> { } } } - } else if ident.sym.to_string() == "exports" { + } else if ident.sym == "exports" { if let Some(mem_expr) = mem_expr_from_assign(n) { if let MemberProp::Ident(ident_property) = &mem_expr.prop { match &*n.right { From d97b938ba9a67cacf7a157a1303f77ba1bb76ad1 Mon Sep 17 00:00:00 2001 From: awang7 Date: Wed, 29 Nov 2023 17:23:33 -0500 Subject: [PATCH 225/517] fix: addressed more PR comments. updated let statements, and rewrote match statement logic to now swallow rest of secret checker --- crates/forge_analyzer/src/definitions.rs | 67 +++++++++++++----------- 1 file changed, 37 insertions(+), 30 deletions(-) diff --git a/crates/forge_analyzer/src/definitions.rs b/crates/forge_analyzer/src/definitions.rs index 22af08b3..7267d742 100644 --- a/crates/forge_analyzer/src/definitions.rs +++ b/crates/forge_analyzer/src/definitions.rs @@ -969,12 +969,20 @@ impl<'cx> FunctionAnalyzer<'cx> { _ => None, } } - [PropPath::Def(def), ..] => match self.res.is_imported_from(def, "@forge/api") { - Some(ImportKind::Named(ref name)) if *name == *"authorize" => { - Some(Intrinsic::Authorize(IntrinsicName::Other)) + + [PropPath::Def(def), ..] if self.res.is_imported_from(def, "@forge/api").is_some() => { + if let Some(ImportKind::Named(ref name)) = + self.res.is_imported_from(def, "@forge/api") + { + if *name == *"authorize" { + Some(Intrinsic::Authorize(IntrinsicName::Other)); + } else { + return None; + } } - _ => None, - }, + None + } + // Case 1: the “root” object is a star import and the Static proppath member is either identifier or method // [Def, Static, Static] //[PropPath::Def(def), PropPath::Static(a), PropPath::Static(b)] @@ -983,23 +991,21 @@ impl<'cx> FunctionAnalyzer<'cx> { // case where function requires object to be invoked first // example: import * as cryptoJS from 'crypto-js'; // var aes = cryptoJS.AES.encrypt('Secret message', 'secret password'); - if let Some((package_name, import_kind)) = self.res.as_foreign_import(def) { - let package_found = self.secret_packages.iter().find(|&package_data| { - if let Some(package_method) = &package_data.method { - return package_name == package_data.package_name - && import_kind == ImportKind::Star - && identifier == &package_data.identifier - && *method == *package_method; - } else { - false - } - }); - - if let Some(package) = package_found { - Some(Intrinsic::JWTSign(package.clone())) + debug!("Checking if printing wth! thing"); + let (package_name, import_kind) = self.res.as_foreign_import(def)?; + let package_found = self.secret_packages.iter().find(|&package_data| { + if let Some(package_method) = &package_data.method { + return package_name == package_data.package_name + && import_kind == ImportKind::Star + && identifier == &package_data.identifier + && *method == *package_method; } else { - None + false } + }); + + if let Some(package) = package_found { + Some(Intrinsic::JWTSign(package.clone())) } else { None } @@ -1018,7 +1024,7 @@ impl<'cx> FunctionAnalyzer<'cx> { // Star imports that don't have any object call to invoke function // import * as atlassian_jwt from "atlassian-jwt"; // atlassian_jwt.encode(blah, blah); - + info!("Checking atlassian-jwt thing"); if import_kind == ImportKind::Star { let package_found = self.secret_packages.iter().find(|&package_data| { package_name == package_data.package_name @@ -1059,19 +1065,19 @@ impl<'cx> FunctionAnalyzer<'cx> { // [PropPath::Def(def)] // 1. Named/Default => package matches imported from and ident is the same, (NO METHOD) [PropPath::Def(def)] if self.res.as_foreign_import(def).is_some() => { - if let Some((package_name, import_kind)) = self.res.as_foreign_import(def) { - let package = self.secret_packages.iter().find(|&package_data| { - *package_name == *package_data.package_name - && import_kind == *package_data.identifier - && package_data.method.is_none() - }); - if package.is_some() { - return Some(Intrinsic::JWTSign(package.unwrap().clone())); - } + let (package_name, import_kind) = self.res.as_foreign_import(def)?; + let package = self.secret_packages.iter().find(|&package_data| { + *package_name == *package_data.package_name + && import_kind == *package_data.identifier + && package_data.method.is_none() + }); + if package.is_some() { + return Some(Intrinsic::JWTSign(package.unwrap().clone())); } None } + _ => None, } } @@ -1306,6 +1312,7 @@ impl<'cx> FunctionAnalyzer<'cx> { let first_arg = args.first().map(|expr| &*expr.expr); let intrinsic = self.as_intrinsic(&props, first_arg); + debug!("HELLO INTRINSIC!? {intrinsic:?}"); let call = match self.as_intrinsic(&props, first_arg) { Some(int) => Rvalue::Intrinsic(int, lowered_args), None => Rvalue::Call(callee, lowered_args), From e10b9552ee0011a32a647d583c13d5e96e9fc866 Mon Sep 17 00:00:00 2001 From: awang7 Date: Wed, 29 Nov 2023 17:24:41 -0500 Subject: [PATCH 226/517] chore: not all changes went through the first time --- crates/forge_analyzer/src/definitions.rs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/crates/forge_analyzer/src/definitions.rs b/crates/forge_analyzer/src/definitions.rs index 7267d742..35666582 100644 --- a/crates/forge_analyzer/src/definitions.rs +++ b/crates/forge_analyzer/src/definitions.rs @@ -991,7 +991,6 @@ impl<'cx> FunctionAnalyzer<'cx> { // case where function requires object to be invoked first // example: import * as cryptoJS from 'crypto-js'; // var aes = cryptoJS.AES.encrypt('Secret message', 'secret password'); - debug!("Checking if printing wth! thing"); let (package_name, import_kind) = self.res.as_foreign_import(def)?; let package_found = self.secret_packages.iter().find(|&package_data| { if let Some(package_method) = &package_data.method { @@ -1016,7 +1015,7 @@ impl<'cx> FunctionAnalyzer<'cx> { // PackageData { package: 'foo', identifier: 'bar', method: 'sign' } // [Def, Static] - //[PropPath::Def(def), PropPath::Static(atom)] + // [PropPath::Def(def), PropPath::Static(atom)] // 1. Star, identifier (NO METHOD) // 2. Named/Default => import kind matches idenntifier and static(atom) matches method [PropPath::Def(def), PropPath::Static(ref method_name)] => { @@ -1024,7 +1023,6 @@ impl<'cx> FunctionAnalyzer<'cx> { // Star imports that don't have any object call to invoke function // import * as atlassian_jwt from "atlassian-jwt"; // atlassian_jwt.encode(blah, blah); - info!("Checking atlassian-jwt thing"); if import_kind == ImportKind::Star { let package_found = self.secret_packages.iter().find(|&package_data| { package_name == package_data.package_name From 3e481ac99296c460c1a94ad57d0569e963e821ba Mon Sep 17 00:00:00 2001 From: awang7 Date: Thu, 30 Nov 2023 10:39:39 -0500 Subject: [PATCH 227/517] fix: removed the unwrap() on self.default and made add_defult return a DefId --- crates/forge_analyzer/src/definitions.rs | 42 ++++++++++++++---------- 1 file changed, 24 insertions(+), 18 deletions(-) diff --git a/crates/forge_analyzer/src/definitions.rs b/crates/forge_analyzer/src/definitions.rs index 35666582..90bcccea 100644 --- a/crates/forge_analyzer/src/definitions.rs +++ b/crates/forge_analyzer/src/definitions.rs @@ -2819,7 +2819,7 @@ impl ExportCollector<'_> { defid } - fn add_default(&mut self, def: DefRes, id: Option) { + fn add_default(&mut self, def: DefRes, id: Option) -> DefId { let defid = match id { Some(id) => self.res_table.add_sym(def, id, self.curr_mod), None => { @@ -2829,6 +2829,7 @@ impl ExportCollector<'_> { } }; self.default = Some(defid); + defid } fn add_default_with_id(&mut self, def: DefRes, defid: DefId) { @@ -2836,6 +2837,8 @@ impl ExportCollector<'_> { self.res_table.owning_module.push(self.curr_mod); self.default = Some(defid); } + + // fn add_default(&mut self, def: DefRes, id: Option) -> DefId {} } impl Visit for ImportCollector<'_> { @@ -3031,21 +3034,24 @@ impl Visit for ExportCollector<'_> { if let MemberProp::Ident(ident_property) = &mem_expr.prop { if ident_property.sym == "exports" { match &*n.right { - Expr::Fn(FnExpr { ident, function }) => self.add_default( - DefRes::Function(()), - ident.as_ref().map(Ident::to_id), - ), - Expr::Class(ClassExpr { ident, class }) => self.add_default( - DefRes::Class(()), - ident.as_ref().map(Ident::to_id), - ), + Expr::Fn(FnExpr { ident, function }) => { + self.add_default( + DefRes::Function(()), + ident.as_ref().map(Ident::to_id), + ); + } + Expr::Class(ClassExpr { ident, class }) => { + self.add_default( + DefRes::Class(()), + ident.as_ref().map(Ident::to_id), + ); + } Expr::Ident(ident) => { - self.add_default(DefRes::Undefined, None); + let default_id = self.add_default(DefRes::Undefined, None); // adding the default export, so we can resolve it during the lowering - self.res_table.exported_names.insert( - (ident.sym.clone(), self.curr_mod), - self.default.unwrap(), - ); + self.res_table + .exported_names + .insert((ident.sym.clone(), self.curr_mod), default_id); } _ => {} } @@ -3111,10 +3117,10 @@ impl Visit for ExportCollector<'_> { fn visit_export_default_decl(&mut self, n: &ExportDefaultDecl) { match &n.decl { DefaultDecl::Class(ClassExpr { ident, .. }) => { - self.add_default(DefRes::Class(()), ident.as_ref().map(Ident::to_id)) + self.add_default(DefRes::Class(()), ident.as_ref().map(Ident::to_id)); } DefaultDecl::Fn(FnExpr { ident, .. }) => { - self.add_default(DefRes::Function(()), ident.as_ref().map(Ident::to_id)) + self.add_default(DefRes::Function(()), ident.as_ref().map(Ident::to_id)); } DefaultDecl::TsInterfaceDecl(_) => {} } @@ -3140,11 +3146,11 @@ impl Visit for ExportCollector<'_> { fn visit_export_default_expr(&mut self, n: &ExportDefaultExpr) { if let Expr::Ident(ident) = &*n.expr { - self.add_default(DefRes::Undefined, None); + let default_id = self.add_default(DefRes::Undefined, None); // adding the default export, so we can resolve it during the lowering self.res_table .exported_names - .insert((ident.sym.clone(), self.curr_mod), self.default.unwrap()); + .insert((ident.sym.clone(), self.curr_mod), default_id); } } } From 9523f52b014451f5f97b34e92bd32e447087bebf Mon Sep 17 00:00:00 2001 From: Joshua Wong Date: Thu, 30 Nov 2023 13:21:55 -0600 Subject: [PATCH 228/517] fix: clean up some code --- .DS_Store | Bin 6148 -> 0 bytes crates/forge_analyzer/src/checkers.rs | 14 ++-- crates/forge_analyzer/src/definitions.rs | 97 ++++++++--------------- crates/forge_analyzer/src/ir.rs | 4 +- 4 files changed, 41 insertions(+), 74 deletions(-) delete mode 100644 .DS_Store diff --git a/.DS_Store b/.DS_Store deleted file mode 100644 index 8be8bb9e33b0a65196180c3aa6e75f780e7f5e35..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6148 zcmeHKJ8r^25S>X}IHI9Uxfe*m4OSMMfD44g>5z=%q<6)+I2vz01;|O~H0TYQdAqao z*77SnjEHFW({Du9BGSSQ*LJnwoxF+)P=Uu#z`hR!ZdeniK>u_g_y_=Okaok` zX9-}j1h6Jffylr#sKB6Vju;wrU$5 ds~1IGu{F+X;uPp~ Dataflow<'cx> for AuthorizeDataflow { _block: &'cx BasicBlock, intrinsic: &'cx Intrinsic, initial_state: Self::State, - operands: SmallVec<[crate::ir::Operand; 4]>, + _operands: SmallVec<[crate::ir::Operand; 4]>, ) -> Self::State { match *intrinsic { Intrinsic::Authorize(_) => { @@ -80,7 +80,7 @@ impl<'cx> Dataflow<'cx> for AuthorizeDataflow { debug!("user field access found"); std::cmp::max(AuthorizeState::CustomFieldOnly, initial_state) } - Intrinsic::JWTSign(_) + Intrinsic::SecretFunction(_) | Intrinsic::Fetch | Intrinsic::ApiCustomField | Intrinsic::ApiCall(_) @@ -98,7 +98,7 @@ impl<'cx> Dataflow<'cx> for AuthorizeDataflow { _block: &'cx BasicBlock, callee: &'cx crate::ir::Operand, initial_state: Self::State, - operands: SmallVec<[crate::ir::Operand; 4]>, + _operands: SmallVec<[crate::ir::Operand; 4]>, ) -> Self::State { let Some((callee_def, _body)) = self.resolve_call(interp, callee) else { return initial_state; @@ -261,7 +261,7 @@ impl<'cx> Runner<'cx> for AuthZChecker { self.vulns.push(vuln); ControlFlow::Break(()) } - Intrinsic::JWTSign(_) + Intrinsic::SecretFunction(_) | Intrinsic::ApiCall(_) | Intrinsic::SafeCall(_) | Intrinsic::EnvRead @@ -326,7 +326,7 @@ impl<'cx> Dataflow<'cx> for AuthenticateDataflow { debug!("authenticated"); Authenticated::Yes } - Intrinsic::JWTSign(_) + Intrinsic::SecretFunction(_) | Intrinsic::ApiCall(_) | Intrinsic::ApiCustomField | Intrinsic::UserFieldAccess @@ -427,7 +427,7 @@ impl<'cx> Runner<'cx> for AuthenticateChecker { self.vulns.push(vuln); ControlFlow::Break(()) } - Intrinsic::JWTSign(_) => ControlFlow::Continue(*state), + Intrinsic::SecretFunction(_) => ControlFlow::Continue(*state), Intrinsic::ApiCall(_) | Intrinsic::UserFieldAccess | Intrinsic::ApiCustomField => { ControlFlow::Continue(*state) } @@ -700,7 +700,7 @@ impl<'cx> Runner<'cx> for SecretChecker { state: &Self::State, operands: Option>, ) -> ControlFlow<(), Self::State> { - if let Intrinsic::JWTSign(package_data) = intrinsic { + if let Intrinsic::SecretFunction(package_data) = intrinsic { if let Some(operand) = operands .unwrap_or_default() .get((package_data.secret_position - 1) as usize) diff --git a/crates/forge_analyzer/src/definitions.rs b/crates/forge_analyzer/src/definitions.rs index 90bcccea..7e51fe8f 100644 --- a/crates/forge_analyzer/src/definitions.rs +++ b/crates/forge_analyzer/src/definitions.rs @@ -40,30 +40,6 @@ use swc_core::{ use tracing::{debug, field::debug, info, instrument, warn}; use typed_index_collections::{TiSlice, TiVec}; -/** - * ident`, `block`, `stmt`, `expr`, `pat`, `ty`, `lifetime`, `literal`, `path`, `meta`, `tt`, `item` and `vis - */ -macro_rules! unwrap_or { - ($c:vis, $e:expr, $or_do_what:expr) => { - if let c(d) = $e { - d - } else { - $or_do_what - } - }; -} - -macro_rules! add { - // macth like arm for macro - ($a:expr,$b:expr) => { - // macro expand to this code - { - // $a and $b will be templated using the value/variable provided to macro - $a + $b - } - }; -} - use crate::{ ctx::ModId, ir::{ @@ -530,8 +506,6 @@ pub struct Environment { pub resolver: ResolverTable, } -// POI - struct ImportCollector<'cx> { resolver: &'cx mut Environment, file_resolver: &'cx ForgeResolver, @@ -1004,7 +978,7 @@ impl<'cx> FunctionAnalyzer<'cx> { }); if let Some(package) = package_found { - Some(Intrinsic::JWTSign(package.clone())) + Some(Intrinsic::SecretFunction(package.clone())) } else { None } @@ -1019,61 +993,54 @@ impl<'cx> FunctionAnalyzer<'cx> { // 1. Star, identifier (NO METHOD) // 2. Named/Default => import kind matches idenntifier and static(atom) matches method [PropPath::Def(def), PropPath::Static(ref method_name)] => { - if let Some((package_name, import_kind)) = self.res.as_foreign_import(def) { - // Star imports that don't have any object call to invoke function - // import * as atlassian_jwt from "atlassian-jwt"; - // atlassian_jwt.encode(blah, blah); - if import_kind == ImportKind::Star { - let package_found = self.secret_packages.iter().find(|&package_data| { + let (package_name, import_kind) = self.res.as_foreign_import(def)?; + // Star imports that don't have any object call to invoke function + // import * as atlassian_jwt from "atlassian-jwt"; + // atlassian_jwt.encode(blah, blah); + if import_kind == ImportKind::Star { + let package_found = self.secret_packages.iter().find(|&package_data| { + package_name == package_data.package_name + && *method_name == package_data.identifier + && package_data.method.is_none() + }); + if let Some(package) = package_found { + Some(Intrinsic::SecretFunction(package.clone())) + } else { + None + } + } else { + // Named or default imports that + // import AES from "crypto-js"; + // import {AES} from "crypto-js" + // Aes.encrypt(blah, blah); + let package_found = self.secret_packages.iter().find(|&package_data| { + if let Some(method) = &package_data.method { package_name == package_data.package_name - && *method_name == package_data.identifier - && package_data.method.is_none() - }); - if let Some(package) = package_found { - Some(Intrinsic::JWTSign(package.clone())) + && import_kind == *package_data.identifier + && *method_name == *method } else { - None + false } + }); + if let Some(package) = package_found { + Some(Intrinsic::SecretFunction(package.clone())) } else { - // Named or default imports that - // import AES from "crypto-js"; - // import {AES} from "crypto-js" - // Aes.encrypt(blah, blah); - let package_found = self.secret_packages.iter().find(|&package_data| { - if let Some(method) = &package_data.method { - package_name == package_data.package_name - && import_kind == *package_data.identifier - && *method_name == *method - } else { - false - } - }); - if let Some(package) = package_found { - Some(Intrinsic::JWTSign(package.clone())) - } else { - None - } + None } - } else { - None } } // [Def, .., Static] star // [Def, .., Static] named/default // [PropPath::Def(def)] // 1. Named/Default => package matches imported from and ident is the same, (NO METHOD) - [PropPath::Def(def)] if self.res.as_foreign_import(def).is_some() => { + [PropPath::Def(def)] => { let (package_name, import_kind) = self.res.as_foreign_import(def)?; let package = self.secret_packages.iter().find(|&package_data| { *package_name == *package_data.package_name && import_kind == *package_data.identifier && package_data.method.is_none() }); - if package.is_some() { - return Some(Intrinsic::JWTSign(package.unwrap().clone())); - } - - None + package.cloned().map(Intrinsic::SecretFunction) } _ => None, diff --git a/crates/forge_analyzer/src/ir.rs b/crates/forge_analyzer/src/ir.rs index 92089dde..d8741eaf 100644 --- a/crates/forge_analyzer/src/ir.rs +++ b/crates/forge_analyzer/src/ir.rs @@ -78,7 +78,7 @@ pub enum Intrinsic { ApiCustomField, ApiCall(IntrinsicName), SafeCall(IntrinsicName), - JWTSign(PackageData), + SecretFunction(PackageData), EnvRead, StorageRead, } @@ -763,7 +763,7 @@ impl fmt::Display for Intrinsic { match *self { Intrinsic::Fetch => write!(f, "fetch"), Intrinsic::Authorize(_) => write!(f, "authorize"), - Intrinsic::JWTSign(_) => write!(f, "jwt sign"), + Intrinsic::SecretFunction(_) => write!(f, "jwt sign"), Intrinsic::ApiCall(_) => write!(f, "api call"), Intrinsic::ApiCustomField => write!(f, "accessing custom field route asApp"), Intrinsic::UserFieldAccess => write!(f, "accessing which fields a user can access"), From b6d08de312249f753154677c2d66060834be5805 Mon Sep 17 00:00:00 2001 From: Joshua Wong Date: Thu, 30 Nov 2023 13:30:16 -0600 Subject: [PATCH 229/517] perf: remove unnecessary string allocations --- crates/forge_analyzer/src/definitions.rs | 25 +++++++++++------------- 1 file changed, 11 insertions(+), 14 deletions(-) diff --git a/crates/forge_analyzer/src/definitions.rs b/crates/forge_analyzer/src/definitions.rs index 7e51fe8f..0878afba 100644 --- a/crates/forge_analyzer/src/definitions.rs +++ b/crates/forge_analyzer/src/definitions.rs @@ -709,7 +709,7 @@ impl ResolverTable { #[inline] fn get_default(&self, module: ModId) -> Option { for (defid, sym) in self.names.iter_enumerated() { - if &sym.to_string() == "default" && self.owning_module.contains(&module) { + if sym == "default" && self.owning_module.contains(&module) { return Some(defid); } } @@ -1261,11 +1261,9 @@ impl<'cx> FunctionAnalyzer<'cx> { .iter() .enumerate() .map(|(i, arg)| { - let defid = self.res.add_anonymous( - i.to_string() + "argument", - AnonType::Unknown, - self.module, - ); + let defid = + self.res + .add_anonymous(format!("{i}argument"), AnonType::Unknown, self.module); self.lower_expr(&arg.expr, Some(defid)) }) .collect(); @@ -1381,8 +1379,7 @@ impl<'cx> FunctionAnalyzer<'cx> { fn lower_jsx_child(&mut self, n: &JSXElementChild) -> Operand { match n { JSXElementChild::JSXText(JSXText { value, .. }) => { - let value = JsWord::from(value.to_string()); - Operand::Lit(Literal::Str(value)) + Operand::Lit(Literal::Str(value.clone())) } JSXElementChild::JSXExprContainer(JSXExprContainer { expr, .. }) => match expr { JSXExpr::JSXEmptyExpr(_) => Operand::UNDEF, @@ -2054,10 +2051,10 @@ impl Visit for FunctionCollector<'_> { fn visit_assign_expr(&mut self, n: &AssignExpr) { if let Some(ident) = ident_from_assign_expr(n) { - if ident.sym.to_string() == "module" { + if ident.sym == "module" { if let Some(mem_expr) = mem_expr_from_assign(n) { if let MemberProp::Ident(ident_property) = &mem_expr.prop { - if ident_property.sym.to_string() == "exports" { + if ident_property.sym == "exports" { match &*n.right { Expr::Fn(FnExpr { ident, function }) => self.handle_function( &**&function, @@ -2072,7 +2069,7 @@ impl Visit for FunctionCollector<'_> { } } } - } else if ident.sym.to_string() == "exports" { + } else if ident.sym == "exports" { if let Some(mem_expr) = mem_expr_from_assign(n) { if let MemberProp::Ident(ident_property) = &mem_expr.prop { match &*n.right { @@ -2528,10 +2525,10 @@ impl Visit for Lowerer<'_> { fn visit_assign_expr(&mut self, n: &AssignExpr) { if let Some(ident) = ident_from_assign_expr(n) { - if ident.sym.to_string() == "module" { + if ident.sym == "module" { if let Some(mem_expr) = mem_expr_from_assign(n) { if let MemberProp::Ident(ident_property) = &mem_expr.prop { - if ident_property.sym.to_string() == "exports" { + if ident_property.sym == "exports" { if let Expr::Class(ClassExpr { ident, class }) = &*n.right { if let Some(defid) = self.res.default_export(self.curr_mod) { self.curr_class = Some(defid); @@ -2996,7 +2993,7 @@ impl Visit for ExportCollector<'_> { fn visit_assign_expr(&mut self, n: &AssignExpr) { if let Some(ident) = ident_from_assign_expr(n) { - if ident.sym.to_string() == "module" { + if ident.sym == "module" { if let Some(mem_expr) = mem_expr_from_assign(n) { if let MemberProp::Ident(ident_property) = &mem_expr.prop { if ident_property.sym == "exports" { From 0a5edfa2af927edf216cbd8c407ded5e50d14686 Mon Sep 17 00:00:00 2001 From: Joshua Wong Date: Wed, 6 Dec 2023 10:35:22 -0600 Subject: [PATCH 230/517] feat: add initial implementation of the prototype pollution scanner --- crates/forge_analyzer/src/checkers.rs | 227 +++++++++++++++++++++++ crates/forge_analyzer/src/definitions.rs | 103 ++++++++-- crates/forge_analyzer/src/interp.rs | 2 +- crates/forge_analyzer/src/ir.rs | 28 ++- crates/fsrt/src/main.rs | 33 +++- test-apps/jira-pp/manifest.yml | 31 ++++ test-apps/jira-pp/src/frontend/index.jsx | 30 +++ test-apps/jira-pp/src/index.js | 1 + test-apps/jira-pp/src/resolvers/index.js | 54 ++++++ 9 files changed, 483 insertions(+), 26 deletions(-) create mode 100644 test-apps/jira-pp/manifest.yml create mode 100644 test-apps/jira-pp/src/frontend/index.jsx create mode 100644 test-apps/jira-pp/src/index.js create mode 100644 test-apps/jira-pp/src/resolvers/index.js diff --git a/crates/forge_analyzer/src/checkers.rs b/crates/forge_analyzer/src/checkers.rs index b5f27066..08a24001 100644 --- a/crates/forge_analyzer/src/checkers.rs +++ b/crates/forge_analyzer/src/checkers.rs @@ -4,6 +4,7 @@ use forge_utils::FxHashMap; use itertools::Itertools; use smallvec::SmallVec; use std::{cmp::max, iter, mem, ops::ControlFlow, path::PathBuf}; +use typed_index_collections::TiVec; use tracing::{debug, info, warn}; @@ -26,6 +27,167 @@ use crate::{ worklist::WorkList, }; +#[derive(Debug, PartialEq, Eq, Clone, Copy, PartialOrd, Ord, Default)] +pub enum Taint { + No, + Yes, + #[default] + Unknown, +} + +impl JoinSemiLattice for Taint { + const BOTTOM: Self = Self::No; + + #[inline] + fn join_changed(&mut self, other: &Self) -> bool { + let old = mem::replace(self, max(*other, *self)); + old == *self + } + + #[inline] + fn join(&self, other: &Self) -> Self { + max(*other, *self) + } +} + +impl JoinSemiLattice for Vec { + const BOTTOM: Self = vec![]; + fn join_changed(&mut self, other: &Self) -> bool { + let mut changed = false; + self.iter_mut().zip(other).for_each(|(a, b)| { + changed |= a.join_changed(b); + }); + changed + } + fn join(&self, other: &Self) -> Self { + self.iter() + .zip(other.iter()) + .map(|(a, b)| a.join(b)) + .collect() + } +} + +pub struct TaintDataflow { + needs_call: Vec, + started: bool, +} + +impl<'cx> Dataflow<'cx> for TaintDataflow { + type State = Vec; + + fn with_interp>(interp: &Interp<'cx, C>) -> Self { + Self { + needs_call: vec![], + started: false, + } + } + + fn transfer_call>( + &mut self, + interp: &Interp<'cx, C>, + def: DefId, + loc: Location, + block: &'cx BasicBlock, + callee: &'cx Operand, + initial_state: Self::State, + // operands: &SmallVec<[Operand; 4]>, + oprands: SmallVec<[crate::ir::Operand; 4]>, + ) -> Self::State { + initial_state + } + + fn transfer_intrinsic>( + &mut self, + interp: &mut Interp<'cx, C>, + def: DefId, + loc: Location, + block: &'cx BasicBlock, + intrinsic: &'cx Intrinsic, + initial_state: Self::State, + operands: SmallVec<[crate::ir::Operand; 4]>, + ) -> Self::State { + initial_state + } + + fn transfer_inst>( + &mut self, + interp: &mut Interp<'cx, C>, + def: DefId, + loc: Location, + block: &'cx BasicBlock, + inst: &'cx Inst, + mut initial_state: Self::State, + ) -> Self::State { + match inst { + Inst::Assign(l, v) => { + let Some(var) = l.as_var_id() else { + return initial_state; + }; + + if let Some(var) = v.as_var() { + let Some(var_id) = var.as_var_id() else { + return initial_state; + }; + let taint = initial_state[var_id.0 as usize]; + if !self.started && taint == Taint::Yes { + let Some(Projection::Known(s)) = var.projections.get(0) else { + return initial_state; + }; + if *s == "payload" { + initial_state[var_id.0 as usize] = Taint::Yes; + self.started = true; + } + return initial_state; + } else { + let new_state = initial_state[var_id.0 as usize].join(&taint); + initial_state[var_id.0 as usize] = new_state; + return initial_state; + } + } else if initial_state[var.0 as usize] == Taint::Yes { + initial_state[var.0 as usize] = Taint::Unknown; + } + initial_state + } + Inst::Expr(rvalue) => { + self.transfer_rvalue(interp, def, loc, block, rvalue, initial_state) + } + } + } + + fn transfer_block>( + &mut self, + interp: &mut Interp<'cx, C>, + def: DefId, + bb: BasicBlockId, + block: &'cx BasicBlock, + mut initial_state: Self::State, + arguments: Option>, + ) -> Self::State { + if initial_state.len() < interp.body().vars.len() { + initial_state.resize(interp.body().vars.len(), Taint::Unknown); + } + if matches!(interp.entry.kind, EntryKind::Resolver(..)) { + if matches!( + interp.body().vars.get(VarId::from(2)), + Some(VarKind::Arg(_)) + ) { + initial_state[2] = Taint::Yes; + } + } + for (idx, inst) in block.iter().enumerate() { + initial_state = self.transfer_inst( + interp, + def, + Location::new(bb, idx as u32), + block, + inst, + initial_state, + ); + } + initial_state + } +} + pub struct AuthorizeDataflow { needs_call: Vec, } @@ -135,6 +297,71 @@ impl<'cx> Dataflow<'cx> for AuthorizeDataflow { } } +pub struct PrototypePollutionChecker; + +#[derive(Debug, Clone, Copy, PartialOrd, Ord, PartialEq, Eq, Default)] +enum PrototypePollutionState { + Yes, + #[default] + No, +} + +impl JoinSemiLattice for PrototypePollutionState { + const BOTTOM: Self = Self::No; + fn join(&self, other: &Self) -> Self { + match (self, other) { + (Self::Yes, _) | (_, Self::Yes) => Self::Yes, + _ => Self::No, + } + } + + fn join_changed(&mut self, other: &Self) -> bool { + let old = mem::replace(self, self.join(other)); + old != *self + } +} + +impl<'cx> Runner<'cx> for PrototypePollutionChecker { + type State = Vec; + + type Dataflow = TaintDataflow; + + fn visit_intrinsic( + &mut self, + interp: &Interp<'cx, Self>, + intrinsic: &'cx Intrinsic, + def: DefId, + state: &Self::State, + operands: Option>, + ) -> ControlFlow<(), Self::State> { + ControlFlow::Continue(state.clone()) + } + fn visit_block( + &mut self, + interp: &Interp<'cx, Self>, + def: DefId, + id: BasicBlockId, + block: &'cx BasicBlock, + curr_state: &Self::State, + ) -> ControlFlow<(), Self::State> { + for inst in &block.insts { + if let Inst::Assign(l, r) = inst { + if let [Projection::Computed(Base::Var(fst)), Projection::Computed(Base::Var(snd)), ..] = + *l.projections + { + if curr_state.get(fst.0 as usize).copied() == Some(Taint::Yes) + && curr_state.get(snd.0 as usize).copied() == Some(Taint::Yes) + { + info!("Prototype pollution vuln detected"); + return ControlFlow::Break(()); + } + } + } + } + ControlFlow::Continue(curr_state.clone()) + } +} + pub struct AuthZChecker { visit: bool, vulns: Vec, diff --git a/crates/forge_analyzer/src/definitions.rs b/crates/forge_analyzer/src/definitions.rs index 0878afba..b5c133f0 100644 --- a/crates/forge_analyzer/src/definitions.rs +++ b/crates/forge_analyzer/src/definitions.rs @@ -10,7 +10,7 @@ use itertools::Itertools; use serde::{Deserialize, Serialize}; use smallvec::{smallvec, SmallVec}; use swc_core::{ - common::SyntaxContext, + common::{Span, SyntaxContext, DUMMY_SP}, ecma::{ ast::{ ArrayLit, ArrayPat, ArrowExpr, AssignExpr, AssignOp, AssignPat, AssignPatProp, @@ -25,15 +25,15 @@ use swc_core::{ JSXNamespacedName, JSXObject, JSXSpreadChild, JSXText, KeyValuePatProp, KeyValueProp, LabeledStmt, Lit, MemberExpr, MemberProp, MetaPropExpr, MethodProp, Module, ModuleDecl, ModuleExportName, ModuleItem, NewExpr, Number, ObjectLit, ObjectPat, ObjectPatProp, - OptCall, OptChainBase, OptChainExpr, ParenExpr, Pat, PatOrExpr, PrivateName, Prop, - PropName, PropOrSpread, RestPat, ReturnStmt, SeqExpr, Stmt, Str, Super, SuperProp, - SuperPropExpr, SwitchStmt, TaggedTpl, ThisExpr, ThrowStmt, Tpl, TplElement, TryStmt, - TsAsExpr, TsConstAssertion, TsInstantiation, TsNonNullExpr, TsSatisfiesExpr, + OptCall, OptChainBase, OptChainExpr, Param, ParenExpr, Pat, PatOrExpr, PrivateName, + Prop, PropName, PropOrSpread, RestPat, ReturnStmt, SeqExpr, Stmt, Str, Super, + SuperProp, SuperPropExpr, SwitchStmt, TaggedTpl, ThisExpr, ThrowStmt, Tpl, TplElement, + TryStmt, TsAsExpr, TsConstAssertion, TsInstantiation, TsNonNullExpr, TsSatisfiesExpr, TsTypeAssertion, UnaryExpr, UpdateExpr, VarDecl, VarDeclOrExpr, VarDeclarator, WhileStmt, WithStmt, YieldExpr, }, atoms::{Atom, JsWord}, - utils::function, + utils::{function, ident::IdentLike}, visit::{noop_visit_type, Visit, VisitWith}, }, }; @@ -44,7 +44,7 @@ use crate::{ ctx::ModId, ir::{ Base, BasicBlockId, Body, Inst, Intrinsic, Literal, Operand, Projection, Rvalue, Template, - Terminator, VarKind, Variable, RETURN_VAR, + Terminator, VarKind, Variable, RETURN_VAR, STARTING_BLOCK, }, }; @@ -1941,24 +1941,65 @@ struct ArgDefiner<'cx> { module: ModId, func: DefId, body: Body, + current_arg: Variable, } impl Visit for ArgDefiner<'_> { + noop_visit_type!(); fn visit_ident(&mut self, n: &Ident) { let id = n.to_id(); let defid = self .res .get_or_overwrite_sym(id.clone(), self.module, DefRes::Arg); self.res.add_parent(defid, self.func); - self.body.add_arg(defid, id); + let var = self.body.get_or_insert_global(defid); + self.body.push_assign( + STARTING_BLOCK, + var.into(), + Rvalue::Read(Operand::Var(self.current_arg.clone().into())), + ); } fn visit_object_pat_prop(&mut self, n: &ObjectPatProp) { match n { - ObjectPatProp::KeyValue(KeyValuePatProp { key, .. }) => key.visit_with(self), - ObjectPatProp::Assign(AssignPatProp { key, .. }) => self.visit_ident(key), - ObjectPatProp::Rest(_) => {} + ObjectPatProp::KeyValue(KeyValuePatProp { key, value }) => { + match key { + PropName::Ident(ident) => { + self.current_arg + .projections + .push(Projection::Known(ident.sym.clone())); + } + PropName::Str(str) => { + self.current_arg + .projections + .push(Projection::Known(str.value.clone())); + } + PropName::Num(num) => { + self.current_arg + .projections + .push(Projection::Known(num.value.to_string().into())); + } + PropName::BigInt(bigint) => { + self.current_arg + .projections + .push(Projection::Known(bigint.value.to_string().into())); + } + PropName::Computed(ComputedPropName { expr, .. }) => { + // TODO: handle this case + return; + } + } + value.visit_with(self); + } + ObjectPatProp::Assign(AssignPatProp { key, .. }) => { + self.current_arg + .projections + .push(Projection::Known(key.sym.clone())); + key.visit_with(self); + } + ObjectPatProp::Rest(_) => return, } + self.current_arg.projections.pop(); } fn visit_pat(&mut self, n: &Pat) { @@ -1966,16 +2007,40 @@ impl Visit for ArgDefiner<'_> { Pat::Ident(_) | Pat::Array(_) => n.visit_children_with(self), Pat::Object(ObjectPat { props, .. }) => props.visit_children_with(self), Pat::Assign(AssignPat { left, .. }) => left.visit_with(self), - Pat::Expr(id) => { - if let Expr::Ident(id) = &**id { - id.visit_with(self); - } - } + // This is syntactically invalid in arguments + Pat::Expr(id) => {} Pat::Invalid(_) => {} Pat::Rest(_) => {} - Pat::Invalid(_) => {} } } + + fn visit_pats(&mut self, n: &[Pat]) { + for (pos, pat) in n.iter().enumerate() { + let (defid, id) = if let Some(id) = pat.as_ident().map(BindingIdent::to_id) { + let defid = self + .res + .get_or_overwrite_sym(id.clone(), self.module, DefRes::Arg); + self.res.add_parent(defid, self.func); + self.res.add_parent(defid, self.func); + continue; + } else { + //FIXME: clean up unnecessary allocations + let id = format!("{pos}argument"); + let defid = self.res.add_anonymous(id, AnonType::Unknown, self.module); + let id = Atom::from(self.res.def_name(defid)).to_id(); + (defid, id) + }; + let var_id = self.body.add_arg(defid, id); + // FIXME: use clone_from once we specialize + self.current_arg = Variable::new(var_id); + self.visit_pat(pat); + } + } + + fn visit_params(&mut self, n: &[Param]) { + let pats = n.iter().map(|param| param.pat.clone()).collect::>(); + self.visit_pats(&pats); + } } struct LocalDefiner<'cx> { @@ -2127,6 +2192,7 @@ impl Visit for FunctionCollector<'_> { module: self.module, func: *owner, body: Body::with_owner(*owner), + current_arg: Default::default(), }; n.params.visit_children_with(&mut argdef); let body = argdef.body; @@ -2174,6 +2240,7 @@ impl Visit for FunctionCollector<'_> { module: self.module, func: *owner, body: Body::with_owner(*owner), + current_arg: Default::default(), }; n.function.params.visit_children_with(&mut argdef); let body = argdef.body; @@ -2225,6 +2292,7 @@ impl Visit for FunctionCollector<'_> { module: self.module, func: owner, body: Body::with_owner(owner), + current_arg: Default::default(), }; params.visit_children_with(&mut argdef); let body = argdef.body; @@ -2400,6 +2468,7 @@ impl FunctionCollector<'_> { module: self.module, func: owner, body: Body::with_owner(owner), + current_arg: Default::default(), }; n.params.visit_children_with(&mut argdef); let body = argdef.body; diff --git a/crates/forge_analyzer/src/interp.rs b/crates/forge_analyzer/src/interp.rs index 6c8c2fb2..340de81f 100644 --- a/crates/forge_analyzer/src/interp.rs +++ b/crates/forge_analyzer/src/interp.rs @@ -497,7 +497,7 @@ pub struct Interp<'cx, C: Runner<'cx>> { call_graph: CallGraph, pub return_value: Option<(Value, DefId)>, pub return_value_alt: HashMap, - entry: EntryPoint, + pub(crate) entry: EntryPoint, func_state: RefCell>, pub curr_body: Cell>, states: RefCell>, diff --git a/crates/forge_analyzer/src/ir.rs b/crates/forge_analyzer/src/ir.rs index d8741eaf..b25a01e7 100644 --- a/crates/forge_analyzer/src/ir.rs +++ b/crates/forge_analyzer/src/ir.rs @@ -216,8 +216,9 @@ pub enum Operand { Lit(Literal), } -#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Default)] pub enum Base { + #[default] This, Super, Var(VarId), @@ -228,10 +229,10 @@ create_newtype! { } create_newtype! { - pub struct VarId(u32); + pub struct VarId(pub u32); } -#[derive(Clone, Debug, PartialEq, Eq, Hash)] +#[derive(Clone, Debug, PartialEq, Eq, Hash, Default)] pub struct Variable { pub(crate) base: Base, pub(crate) projections: SmallVec<[Projection; 1]>, @@ -346,10 +347,11 @@ impl Body { } #[inline] - pub(crate) fn add_arg(&mut self, def: DefId, id: Id) { - self.vars.push(VarKind::Arg(def)); + pub(crate) fn add_arg(&mut self, def: DefId, id: Id) -> VarId { + let var_id = self.vars.push_and_get_key(VarKind::Arg(def)); self.ident_to_local .insert(id, VarId((self.vars.len() - 1) as u32)); + var_id } #[inline] @@ -516,6 +518,13 @@ impl Variable { projections: SmallVec::new_const(), }; + pub(crate) fn as_var_id(&self) -> Option { + match self.base { + Base::Var(var) => Some(var), + _ => None, + } + } + #[inline] pub(crate) const fn new(var: VarId) -> Self { Self { @@ -565,6 +574,13 @@ impl Rvalue { _ => None, } } + + pub(crate) fn as_var(&self) -> Option<&Variable> { + match self { + Rvalue::Read(Operand::Var(var)) => Some(var), + _ => None, + } + } } impl Location { @@ -763,7 +779,7 @@ impl fmt::Display for Intrinsic { match *self { Intrinsic::Fetch => write!(f, "fetch"), Intrinsic::Authorize(_) => write!(f, "authorize"), - Intrinsic::SecretFunction(_) => write!(f, "jwt sign"), + Intrinsic::SecretFunction(_) => write!(f, "secret function"), Intrinsic::ApiCall(_) => write!(f, "api call"), Intrinsic::ApiCustomField => write!(f, "accessing custom field route asApp"), Intrinsic::UserFieldAccess => write!(f, "accessing which fields a user can access"), diff --git a/crates/fsrt/src/main.rs b/crates/fsrt/src/main.rs index e2eda5f7..ef898feb 100644 --- a/crates/fsrt/src/main.rs +++ b/crates/fsrt/src/main.rs @@ -29,7 +29,7 @@ use tracing_tree::HierarchicalLayer; use forge_analyzer::{ checkers::{ AuthZChecker, AuthenticateChecker, DefintionAnalysisRunner, PermissionChecker, - PermissionVuln, SecretChecker, + PermissionVuln, PrototypePollutionChecker, SecretChecker, }, ctx::{AppCtx, ModId}, definitions::{run_resolver, DefId, Environment, PackageData}, @@ -68,9 +68,13 @@ struct Args { out: Option, // Run the permission checker - #[arg(short, long)] + #[arg(long)] check_permissions: bool, + // Run the prototype pollution scanner + #[arg(long)] + check_prototype_pollution: bool, + /// The directory to scan. Assumes there is a `manifest.ya?ml` file in the top level /// directory, and that the source code is located in `src/` #[arg(name = "DIRS", value_hint = ValueHint::DirPath)] @@ -82,6 +86,7 @@ struct Opts { dump_cfg: bool, dump_callgraph: bool, check_permissions: bool, + check_prototype_pollution: bool, appkey: Option, out: Option, } @@ -287,6 +292,16 @@ fn scan_directory(dir: PathBuf, function: Option<&str>, opts: Opts) -> Result, opts: Opts) -> Result { let mut runner = DefintionAnalysisRunner::new(); @@ -426,6 +454,7 @@ fn main() -> Result<()> { dump_callgraph: args.callgraph, dump_cfg: args.cfg, check_permissions: args.check_permissions, + check_prototype_pollution: args.check_prototype_pollution, out: args.out, appkey: args.appkey, }; diff --git a/test-apps/jira-pp/manifest.yml b/test-apps/jira-pp/manifest.yml new file mode 100644 index 00000000..53d72945 --- /dev/null +++ b/test-apps/jira-pp/manifest.yml @@ -0,0 +1,31 @@ +modules: + jira:issuePanel: + - key: jira-pp-hello-world-issue-panel + resource: main + resolver: + function: resolver + render: native + title: jira-pp + icon: https://developer.atlassian.com/platform/forge/images/icons/issue-panel-icon.svg + function: + - key: resolver + handler: index.handler +resources: + - key: main + path: src/frontend/index.jsx +app: + id: ari:cloud:ecosystem::app/363ad44a-c9d3-4e52-a65c-ae718f7d574c + runtime: + name: nodejs18.x +permissions: + scopes: + - read:jira-work + - write:jira-work + - manage:jira-project + - read:jira-user + - manage:jira-webhook + - manage:jira-configuration + external: + fetch: + backend: + - "*" diff --git a/test-apps/jira-pp/src/frontend/index.jsx b/test-apps/jira-pp/src/frontend/index.jsx new file mode 100644 index 00000000..d7f3c900 --- /dev/null +++ b/test-apps/jira-pp/src/frontend/index.jsx @@ -0,0 +1,30 @@ +import React, { useEffect, useState } from "react"; +import ForgeReconciler, { Option, Select, Form, TextField } from "@forge/react"; +import { invoke } from "@forge/bridge"; + +const App = () => { + const onSubmit = ({ input, func: { value } }) => { + console.log(`submitted: ${input} to ${value}`); + const parsed = JSON.parse(input); + console.log(`parsed: ${input} to ${value}`); + invoke(value, parsed); + }; + + return ( + <> +
+ + + + + ); +}; + +ForgeReconciler.render( + + + , +); diff --git a/test-apps/jira-pp/src/index.js b/test-apps/jira-pp/src/index.js new file mode 100644 index 00000000..db829e18 --- /dev/null +++ b/test-apps/jira-pp/src/index.js @@ -0,0 +1 @@ +export { handler } from './resolvers'; diff --git a/test-apps/jira-pp/src/resolvers/index.js b/test-apps/jira-pp/src/resolvers/index.js new file mode 100644 index 00000000..0f904d38 --- /dev/null +++ b/test-apps/jira-pp/src/resolvers/index.js @@ -0,0 +1,54 @@ +import api, { route, fetch } from "@forge/api"; +import Resolver from "@forge/resolver"; + +function merge(tgt, src) { + for (const attr of Object.keys(src)) { + console.log(`merging: ${attr}`); + if (typeof src[attr] === "object") { + console.log(`recursive merge of ${JSON.stringify(src[attr])}`); + merge(tgt[attr], src[attr]); + } else { + tgt[attr] = src[attr]; + } + } +} + +function merge2(tgt, src) { + let { p1, p2 } = src.dummy; + tgt[p1][p2] = src.foo; +} + +const resolver = new Resolver(); + +resolver.define("anon", async ({ payload, context }) => { + merge2({}, payload); + console.log(`entered anon, version: ${process.version}`); + console.log(`payload: ${JSON.stringify(payload)}`); + console.log(`context: ${JSON.stringify(context)}`); + const response = await fetch("https://google.com"); + console.log(`Response: ${response.status} ${response.statusText}`); +}); + +resolver.define("asApp", async ({ payload, context }) => { + console.log("entered asApp"); + console.log( + `payload: ${JSON.stringify(payload)}, context: ${JSON.stringify(context)}`, + ); + const { + extension: { + issue: { key }, + }, + } = context; + merge({}, payload); + // TEST polluting OPTIONS + // pollution should create a new comment instead of fetching all comments + console.log(`body: ${{}.body}, method: ${{}.method}`); + console.log(`version: ${process.version}`); + const response = await api.asApp().requestJira(route`/rest/api/3/serverInfo`); + + console.log(`Response: ${response.status} ${response.statusText}`); + console.log(await response.json()); + return "Hello, world!"; +}); + +export const handler = resolver.getDefinitions(); From 50d5941a7d6834725e852805430b34b607ec21bc Mon Sep 17 00:00:00 2001 From: Joshua Wong Date: Fri, 22 Dec 2023 13:50:40 -0600 Subject: [PATCH 231/517] feat: enable new scanners by default --- crates/fsrt/src/main.rs | 28 +++++++++++----------------- 1 file changed, 11 insertions(+), 17 deletions(-) diff --git a/crates/fsrt/src/main.rs b/crates/fsrt/src/main.rs index ef898feb..91ea281b 100644 --- a/crates/fsrt/src/main.rs +++ b/crates/fsrt/src/main.rs @@ -71,10 +71,6 @@ struct Args { #[arg(long)] check_permissions: bool, - // Run the prototype pollution scanner - #[arg(long)] - check_prototype_pollution: bool, - /// The directory to scan. Assumes there is a `manifest.ya?ml` file in the top level /// directory, and that the source code is located in `src/` #[arg(name = "DIRS", value_hint = ValueHint::DirPath)] @@ -354,19 +350,17 @@ fn scan_directory(dir: PathBuf, function: Option<&str>, opts: Opts) -> Result { let mut runner = DefintionAnalysisRunner::new(); From b46376a77ffea0149fc26fc2e3fc5ee9f553f297 Mon Sep 17 00:00:00 2001 From: Joshua Wong Date: Fri, 22 Dec 2023 15:57:26 -0600 Subject: [PATCH 232/517] fix: remove deleted cli opt --- crates/fsrt/src/main.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/crates/fsrt/src/main.rs b/crates/fsrt/src/main.rs index 91ea281b..16238a13 100644 --- a/crates/fsrt/src/main.rs +++ b/crates/fsrt/src/main.rs @@ -82,7 +82,6 @@ struct Opts { dump_cfg: bool, dump_callgraph: bool, check_permissions: bool, - check_prototype_pollution: bool, appkey: Option, out: Option, } @@ -448,7 +447,6 @@ fn main() -> Result<()> { dump_callgraph: args.callgraph, dump_cfg: args.cfg, check_permissions: args.check_permissions, - check_prototype_pollution: args.check_prototype_pollution, out: args.out, appkey: args.appkey, }; From 6b3f6d98fdeb48bc067b5b0c7d55379d20b0ad7a Mon Sep 17 00:00:00 2001 From: Joshua Wong Date: Wed, 3 Jan 2024 10:34:03 -0600 Subject: [PATCH 233/517] build: slightly optimize debug builds --- Cargo.lock | 43 ++++++++++++++++++++++++++++++++ Cargo.toml | 9 +++++++ crates/forge_analyzer/Cargo.toml | 1 + 3 files changed, 53 insertions(+) diff --git a/Cargo.lock b/Cargo.lock index 8feda9c0..e7b274b7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -220,6 +220,15 @@ version = "2.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "327762f6e5a765692301e5bb513e0d9fef63be86bbc14528052b1cd3e6f03e07" +[[package]] +name = "bitmaps" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "031043d04099746d8db04daf1fa424b2bc8bd69d92b25962dcde24da39ab64a2" +dependencies = [ + "typenum", +] + [[package]] name = "block-buffer" version = "0.10.3" @@ -544,6 +553,7 @@ dependencies = [ "forge_loader", "forge_permission_resolver", "forge_utils", + "im-rc", "itertools 0.12.0", "num-bigint", "once_cell", @@ -768,6 +778,20 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cb56e1aa765b4b4f3aadfab769793b7087bb03a4ea4920644a6d238e2df5b9ed" +[[package]] +name = "im-rc" +version = "15.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af1955a75fa080c677d3972822ec4bad316169ab1cfc6c257a942c2265dbe5fe" +dependencies = [ + "bitmaps", + "rand_core", + "rand_xoshiro", + "sized-chunks", + "typenum", + "version_check", +] + [[package]] name = "indexmap" version = "1.9.3" @@ -1398,6 +1422,15 @@ dependencies = [ "getrandom", ] +[[package]] +name = "rand_xoshiro" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f97cdb2a36ed4183de61b2f824cc45c9f1037f28afe0a322e9fff4c108b5aaa" +dependencies = [ + "rand_core", +] + [[package]] name = "rayon" version = "1.8.0" @@ -1683,6 +1716,16 @@ version = "0.3.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7bd3e3206899af3f8b12af284fafc038cc1dc2b41d1b89dd17297221c5d225de" +[[package]] +name = "sized-chunks" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16d69225bde7a69b235da73377861095455d298f2b970996eec25ddbb42b3d1e" +dependencies = [ + "bitmaps", + "typenum", +] + [[package]] name = "smallvec" version = "1.11.2" diff --git a/Cargo.toml b/Cargo.toml index 1b6e40d5..b151c4ec 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -23,6 +23,7 @@ indexmap = { version = "2.1.0", features = ["std"] } once_cell = "1.17.0" regex = "1.7.1" rustc-hash = "1.1.0" +im-rc = "15.1.0" smallvec = { version = "1.11.2", features = ["union", "const_new"] } swc_core = { version = "0.86.82", features = [ "common", @@ -52,3 +53,11 @@ forge_analyzer = { path = "crates/forge_analyzer" } forge_file_resolver = { path = "crates/forge_file_resolver" } forge_utils = { path = "crates/forge_utils" } forge_permission_resolver = { path = "crates/forge_permission_resolver" } + +# enable a small amount of optimization in debug mode +[profile.dev] +opt-level = 1 + +# enable full optimization for dependencies +[profile.dev.package."*"] +opt-level = 3 diff --git a/crates/forge_analyzer/Cargo.toml b/crates/forge_analyzer/Cargo.toml index a2404053..050e88a1 100644 --- a/crates/forge_analyzer/Cargo.toml +++ b/crates/forge_analyzer/Cargo.toml @@ -14,6 +14,7 @@ forge_utils.workspace = true forge_loader.workspace = true forge_permission_resolver.workspace = true itertools.workspace = true +im-rc.workspace = true num-bigint.workspace = true once_cell.workspace = true petgraph.workspace = true From 70f9ae02140052a11173e3a2070de88eb799ce0e Mon Sep 17 00:00:00 2001 From: Joshua Wong Date: Wed, 3 Jan 2024 10:35:12 -0600 Subject: [PATCH 234/517] fix: use correct check name in secret scanner vulner report --- crates/forge_analyzer/src/checkers.rs | 49 ++++++++++------ crates/forge_analyzer/src/definitions.rs | 73 +++++++++++++++++------- crates/forge_analyzer/src/interp.rs | 51 ++++++----------- crates/forge_analyzer/src/pretty.rs | 23 ++++++++ crates/fsrt/src/main.rs | 55 ++++++++---------- 5 files changed, 150 insertions(+), 101 deletions(-) diff --git a/crates/forge_analyzer/src/checkers.rs b/crates/forge_analyzer/src/checkers.rs index 08a24001..10212f82 100644 --- a/crates/forge_analyzer/src/checkers.rs +++ b/crates/forge_analyzer/src/checkers.rs @@ -3,8 +3,13 @@ use forge_permission_resolver::permissions_resolver::{check_url_for_permissions, use forge_utils::FxHashMap; use itertools::Itertools; use smallvec::SmallVec; -use std::{cmp::max, iter, mem, ops::ControlFlow, path::PathBuf}; -use typed_index_collections::TiVec; +use std::{ + cmp::max, + iter::{self, zip}, + mem, + ops::ControlFlow, + path::PathBuf, +}; use tracing::{debug, info, warn}; @@ -54,9 +59,9 @@ impl JoinSemiLattice for Vec { const BOTTOM: Self = vec![]; fn join_changed(&mut self, other: &Self) -> bool { let mut changed = false; - self.iter_mut().zip(other).for_each(|(a, b)| { - changed |= a.join_changed(b); - }); + for (l, r) in zip(self, other) { + changed |= l.join_changed(r); + } changed } fn join(&self, other: &Self) -> Self { @@ -90,7 +95,6 @@ impl<'cx> Dataflow<'cx> for TaintDataflow { block: &'cx BasicBlock, callee: &'cx Operand, initial_state: Self::State, - // operands: &SmallVec<[Operand; 4]>, oprands: SmallVec<[crate::ir::Operand; 4]>, ) -> Self::State { initial_state @@ -167,11 +171,13 @@ impl<'cx> Dataflow<'cx> for TaintDataflow { initial_state.resize(interp.body().vars.len(), Taint::Unknown); } if matches!(interp.entry.kind, EntryKind::Resolver(..)) { - if matches!( - interp.body().vars.get(VarId::from(2)), - Some(VarKind::Arg(_)) - ) { - initial_state[2] = Taint::Yes; + debug!("analyzing resolver"); + let kind = interp.body().vars.get(VarId::from(1)); + if matches!(kind, Some(VarKind::Arg(_))) { + debug!("found taint start"); + initial_state[1] = Taint::Yes; + } else { + debug!(first_var = ?kind, "no arguments read"); } } for (idx, inst) in block.iter().enumerate() { @@ -326,6 +332,8 @@ impl<'cx> Runner<'cx> for PrototypePollutionChecker { type Dataflow = TaintDataflow; + const NAME: &'static str = "PrototypePollution"; + fn visit_intrinsic( &mut self, interp: &Interp<'cx, Self>, @@ -462,6 +470,8 @@ impl<'cx> Runner<'cx> for AuthZChecker { type State = AuthorizeState; type Dataflow = AuthorizeDataflow; + const NAME: &'static str = "Authorization"; + fn visit_intrinsic( &mut self, interp: &Interp<'cx, Self>, @@ -634,6 +644,8 @@ impl<'cx> Runner<'cx> for AuthenticateChecker { type State = Authenticated; type Dataflow = AuthenticateDataflow; + const NAME: &'static str = "Authentication"; + fn visit_intrinsic( &mut self, interp: &Interp<'cx, Self>, @@ -896,7 +908,7 @@ impl IntoVuln for SecretVuln { self.entry_func.hash(&mut hasher); self.stack.hash(&mut hasher); Vulnerability { - check_name: format!("Secret-{}", hasher.finish()), + check_name: format!("Hardcoded-Secret-{}", hasher.finish()), description: format!( "Hardcoded secret found within codebase {} in {:?}.", self.entry_func, self.file @@ -919,6 +931,8 @@ impl<'cx> Runner<'cx> for SecretChecker { type State = SecretState; type Dataflow = SecretDataflow; + const NAME: &'static str = "Secret"; + fn visit_intrinsic( &mut self, interp: &Interp<'cx, Self>, @@ -1269,9 +1283,11 @@ impl fmt::Display for PermissionVuln { } } -#[derive(Debug, PartialEq, Eq, Clone, Copy, PartialOrd, Ord)] +#[derive(Debug, PartialEq, Eq, Clone, Copy, PartialOrd, Ord, Default)] pub enum PermissionTest { + #[default] Yes, + No, } impl JoinSemiLattice for PermissionTest { @@ -1279,8 +1295,8 @@ impl JoinSemiLattice for PermissionTest { #[inline] fn join_changed(&mut self, other: &Self) -> bool { - let old = mem::replace(self, max(*other, *self)); - old == *self + let old = mem::replace(self, self.join(other)); + old != *self } #[inline] @@ -1348,6 +1364,7 @@ impl IntoVuln for PermissionVuln { impl<'cx> Runner<'cx> for DefintionAnalysisRunner { type State = PermissionTest; type Dataflow = DefintionAnalysisRunner; + const NAME: &'static str = "DefinitionAnalysis"; fn visit_intrinsic( &mut self, @@ -1357,7 +1374,7 @@ impl<'cx> Runner<'cx> for DefintionAnalysisRunner { state: &Self::State, operands: Option>, ) -> ControlFlow<(), Self::State> { - ControlFlow::Continue(*state) + ControlFlow::Break(()) } } diff --git a/crates/forge_analyzer/src/definitions.rs b/crates/forge_analyzer/src/definitions.rs index b5c133f0..73de0bd4 100644 --- a/crates/forge_analyzer/src/definitions.rs +++ b/crates/forge_analyzer/src/definitions.rs @@ -17,20 +17,20 @@ use swc_core::{ AssignProp, AwaitExpr, BinExpr, BindingIdent, BlockStmt, BlockStmtOrExpr, BreakStmt, CallExpr, Callee, ClassDecl, ClassExpr, ClassMethod, ComputedPropName, CondExpr, Constructor, ContinueStmt, Decl, DefaultDecl, DoWhileStmt, ExportAll, ExportDecl, - ExportDefaultDecl, ExportDefaultExpr, ExportNamedSpecifier, Expr, ExprOrSpread, - ExprStmt, FnDecl, FnExpr, ForHead, ForInStmt, ForOfStmt, ForStmt, Function, Id, Ident, - IfStmt, Import, ImportDecl, ImportDefaultSpecifier, ImportNamedSpecifier, - ImportStarAsSpecifier, JSXAttrName, JSXAttrOrSpread, JSXAttrValue, JSXElement, - JSXElementChild, JSXElementName, JSXExpr, JSXExprContainer, JSXFragment, JSXMemberExpr, - JSXNamespacedName, JSXObject, JSXSpreadChild, JSXText, KeyValuePatProp, KeyValueProp, - LabeledStmt, Lit, MemberExpr, MemberProp, MetaPropExpr, MethodProp, Module, ModuleDecl, - ModuleExportName, ModuleItem, NewExpr, Number, ObjectLit, ObjectPat, ObjectPatProp, - OptCall, OptChainBase, OptChainExpr, Param, ParenExpr, Pat, PatOrExpr, PrivateName, - Prop, PropName, PropOrSpread, RestPat, ReturnStmt, SeqExpr, Stmt, Str, Super, - SuperProp, SuperPropExpr, SwitchStmt, TaggedTpl, ThisExpr, ThrowStmt, Tpl, TplElement, - TryStmt, TsAsExpr, TsConstAssertion, TsInstantiation, TsNonNullExpr, TsSatisfiesExpr, - TsTypeAssertion, UnaryExpr, UpdateExpr, VarDecl, VarDeclOrExpr, VarDeclarator, - WhileStmt, WithStmt, YieldExpr, + ExportDefaultDecl, ExportDefaultExpr, ExportNamedSpecifier, ExportSpecifier, Expr, + ExprOrSpread, ExprStmt, FnDecl, FnExpr, ForHead, ForInStmt, ForOfStmt, ForStmt, + Function, Id, Ident, IfStmt, Import, ImportDecl, ImportDefaultSpecifier, + ImportNamedSpecifier, ImportStarAsSpecifier, JSXAttrName, JSXAttrOrSpread, + JSXAttrValue, JSXElement, JSXElementChild, JSXElementName, JSXExpr, JSXExprContainer, + JSXFragment, JSXMemberExpr, JSXNamespacedName, JSXObject, JSXSpreadChild, JSXText, + KeyValuePatProp, KeyValueProp, LabeledStmt, Lit, MemberExpr, MemberProp, MetaPropExpr, + MethodProp, Module, ModuleDecl, ModuleExportName, ModuleItem, NamedExport, NewExpr, + Number, ObjectLit, ObjectPat, ObjectPatProp, OptCall, OptChainBase, OptChainExpr, + Param, ParenExpr, Pat, PatOrExpr, PrivateName, Prop, PropName, PropOrSpread, RestPat, + ReturnStmt, SeqExpr, Stmt, Str, Super, SuperProp, SuperPropExpr, SwitchStmt, TaggedTpl, + ThisExpr, ThrowStmt, Tpl, TplElement, TryStmt, TsAsExpr, TsConstAssertion, + TsInstantiation, TsNonNullExpr, TsSatisfiesExpr, TsTypeAssertion, UnaryExpr, + UpdateExpr, VarDecl, VarDeclOrExpr, VarDeclarator, WhileStmt, WithStmt, YieldExpr, }, atoms::{Atom, JsWord}, utils::{function, ident::IdentLike}, @@ -419,6 +419,13 @@ impl DefKind { } } + pub fn to_body(self) -> Option { + match self { + Self::Function(f) | Self::Closure(f) => Some(f), + _ => None, + } + } + pub fn as_handler(&self) -> Option { match *self { Self::ResolverHandler(id) => Some(id), @@ -2194,7 +2201,7 @@ impl Visit for FunctionCollector<'_> { body: Body::with_owner(*owner), current_arg: Default::default(), }; - n.params.visit_children_with(&mut argdef); + n.params.visit_with(&mut argdef); let body = argdef.body; let mut localdef = LocalDefiner { res: self.res, @@ -2242,7 +2249,7 @@ impl Visit for FunctionCollector<'_> { body: Body::with_owner(*owner), current_arg: Default::default(), }; - n.function.params.visit_children_with(&mut argdef); + n.function.params.visit_with(&mut argdef); let body = argdef.body; let mut localdef = LocalDefiner { res: self.res, @@ -2294,7 +2301,7 @@ impl Visit for FunctionCollector<'_> { body: Body::with_owner(owner), current_arg: Default::default(), }; - params.visit_children_with(&mut argdef); + params.visit_with(&mut argdef); let body = argdef.body; let mut localdef = LocalDefiner { res: self.res, @@ -2470,7 +2477,7 @@ impl FunctionCollector<'_> { body: Body::with_owner(owner), current_arg: Default::default(), }; - n.params.visit_children_with(&mut argdef); + n.params.visit_with(&mut argdef); let body = argdef.body; let mut localdef = LocalDefiner { res: self.res, @@ -3126,6 +3133,19 @@ impl Visit for ExportCollector<'_> { n.visit_children_with(self); } + fn visit_named_export(&mut self, n: &NamedExport) { + if n.type_only { + return; + } + for export in &n.specifiers { + match export { + ExportSpecifier::Namespace(_) => {} + ExportSpecifier::Default(_) => {} + ExportSpecifier::Named(n) => {} + } + } + } + fn visit_module_item(&mut self, n: &ModuleItem) { match n { ModuleItem::ModuleDecl(decl) @@ -3236,13 +3256,14 @@ impl Environment { } #[inline] + #[cfg_attr(debug_assertions, track_caller)] fn get_or_insert_sym(&mut self, id: Id, module: ModId) -> DefId { let def_id = self.resolver.get_or_insert_sym(id, module); let def_id2 = self.defs.defs.get(def_id).copied().map_or_else( || self.defs.defs.push_and_get_key(DefKey::default()), |_| def_id, ); - debug_assert_eq!(def_id, def_id2); + debug_assert_eq!(def_id, def_id2, "resolver and defs out of sync"); def_id } @@ -3535,16 +3556,26 @@ impl Environment { } } + #[instrument(level = "debug", skip(self))] pub fn resolver_defs(&self, def: DefId) -> Vec<(JsWord, DefId)> { - let def = self.resolve_alias(def); + let def = { + let new_def = self.resolve_alias(def); + #[cfg(debug_assertions)] + if new_def != def { + debug!(new = ?new_def, old = ?def, "resolved alias"); + } + new_def + }; // TODO: return an iterator instead of a Vec - if let DefKind::Resolver(class) = self.def_ref(def) { + let defkind = self.def_ref(def); + if let DefKind::Resolver(class) = defkind { class .pub_members .iter() .map(|(k, v)| (k.clone(), self.resolve_alias(*v))) .collect() } else { + debug!(?defkind, "not a resolver"); vec![] } } diff --git a/crates/forge_analyzer/src/interp.rs b/crates/forge_analyzer/src/interp.rs index 340de81f..32dc3611 100644 --- a/crates/forge_analyzer/src/interp.rs +++ b/crates/forge_analyzer/src/interp.rs @@ -4,23 +4,19 @@ use std::{ collections::{BTreeMap, HashMap, VecDeque}, fmt::{self, Display}, hash::Hash, - io::{self, Write}, iter, marker::PhantomData, ops::ControlFlow, path::PathBuf, }; -use forge_permission_resolver::permissions_resolver::{ - check_url_for_permissions, get_permission_resolver_confluence, get_permission_resolver_jira, - PermissionHashMap, RequestType, -}; +use forge_permission_resolver::permissions_resolver::PermissionHashMap; use forge_utils::{FxHashMap, FxHashSet}; use itertools::Itertools; use regex::Regex; use smallvec::SmallVec; use swc_core::ecma::atoms::JsWord; -use tracing::{debug, info, instrument, warn}; +use tracing::{debug, instrument, warn}; use crate::{ checkers::IntrinsicArguments, @@ -29,7 +25,7 @@ use crate::{ Base, BasicBlock, BasicBlockId, Body, Inst, Intrinsic, Location, Operand, Projection, Rvalue, Successors, VarId, Variable, STARTING_BLOCK, }, - utils::{get_defid_from_varkind, get_str_from_operand, resolve_var_from_operand}, + utils::{get_str_from_operand, resolve_var_from_operand}, worklist::WorkList, }; @@ -85,7 +81,6 @@ pub trait Dataflow<'cx>: Sized { block: &'cx BasicBlock, callee: &'cx Operand, initial_state: Self::State, - // operands: &SmallVec<[Operand; 4]>, oprands: SmallVec<[crate::ir::Operand; 4]>, ) -> Self::State; @@ -355,7 +350,9 @@ pub trait Runner<'cx>: Sized { type State: JoinSemiLattice + Clone + fmt::Debug; type Dataflow: Dataflow<'cx, State = Self::State>; - const visit_all: bool = true; + const VISIT_ALL: bool = true; + + const NAME: &'static str = "Runner"; fn visit_intrinsic( &mut self, @@ -808,40 +805,41 @@ impl<'cx, C: Runner<'cx>> Interp<'cx, C> { fn try_check_function(&mut self, def: DefId, checker: &mut C) -> Result<(), Error> { let resolved_def = self.env.resolve_alias(def); let name = self.env.def_name(resolved_def); - info!("Checking function: {name}"); - let body = *self - .env - .def_ref(resolved_def) - .as_body() - .ok_or_else(|| Error::NotAFunction(name.to_owned()))?; + debug!(%name, "found definition"); + let body = *self.env.def_ref(resolved_def).as_body().ok_or_else(|| { + debug!(%name, "unknown function"); + Error::NotAFunction(name.to_owned()) + })?; self.set_body(body); self.run(resolved_def); checker.visit_body(self, resolved_def, body, &C::State::BOTTOM); Ok(()) } - #[instrument(skip(self, checker))] + #[instrument(level = "debug", skip(self, checker, entry_file), fields(checker = %C::NAME, file = %entry_file.display()))] pub fn run_checker( &mut self, def: DefId, checker: &mut C, entry_file: PathBuf, - fname: String, + function: String, ) -> Result<(), Error> { self.entry = EntryPoint { file: entry_file, - kind: EntryKind::Function(fname), + kind: EntryKind::Function(function), }; let Err(error) = self.try_check_function(def, checker) else { return Ok(()); }; + debug!("failed to check function, trying resolver"); let resolver = self.env.resolver_defs(def); if resolver.is_empty() { + warn!("no resolver found"); return Err(error); } - info!("Found potential resolver"); + debug!("found potential resolver"); for (name, prop) in resolver { - debug!("Checking resolver prop: {name}"); + debug!("checking resolver prop: {name}"); self.entry.kind = match std::mem::take(&mut self.entry.kind) { EntryKind::Function(fname) => EntryKind::Resolver(fname, name.clone()), EntryKind::Resolver(res, _) => EntryKind::Resolver(res, name.clone()), @@ -853,17 +851,4 @@ impl<'cx, C: Runner<'cx>> Interp<'cx, C> { } Ok(()) } - - // pub fn dump_results(&self, out: &mut dyn Write) -> io::Result<()> { - // let vulns = &**self.vulns.borrow(); - // if vulns.is_empty() { - // writeln!(out, "No vulnerabilities found") - // } else { - // writeln!(out, "Found {} vulnerabilities", vulns.len())?; - // for vuln in vulns { - // writeln!(out, "{vuln}")?; - // } - // Ok(()) - // } - // } } diff --git a/crates/forge_analyzer/src/pretty.rs b/crates/forge_analyzer/src/pretty.rs index 774a3229..aa4b112d 100644 --- a/crates/forge_analyzer/src/pretty.rs +++ b/crates/forge_analyzer/src/pretty.rs @@ -5,6 +5,29 @@ use crate::{ ir::{Body, VarKind}, }; +impl Environment { + pub fn dump_function(&self, output: &mut dyn Write, func_name: &str) { + let Some(body) = self + .resolver + .names + .iter_enumerated() + .find_map(|(id, name)| { + if *func_name == *name { + self.def_ref(id).to_body() + } else { + None + } + }) + else { + eprintln!("No function named {func_name}"); + return; + }; + if let Err(e) = dump_ir(output, self, body) { + tracing::error!("Error dumping IR: {e}"); + } + } +} + pub fn dump_ir(output: &mut dyn Write, env: &Environment, body: &Body) -> io::Result<()> { let name = body .owner() diff --git a/crates/fsrt/src/main.rs b/crates/fsrt/src/main.rs index 16238a13..770d7a70 100644 --- a/crates/fsrt/src/main.rs +++ b/crates/fsrt/src/main.rs @@ -55,6 +55,10 @@ struct Args { #[arg(long)] cfg: bool, + /// Dump the IR for the specified function + #[arg(long)] + dump_ir: Option, + /// A specific function to scan. Must be an entrypoint specified in `manifest.yml` #[arg(short, long)] function: Option, @@ -77,22 +81,12 @@ struct Args { dirs: Vec, } -#[derive(Debug, Clone, Default)] -struct Opts { - dump_cfg: bool, - dump_callgraph: bool, - check_permissions: bool, - appkey: Option, - out: Option, -} - struct ForgeProject { #[allow(dead_code)] sm: Arc, ctx: AppCtx, env: Environment, funcs: Vec>, - opts: Opts, } impl ForgeProject { @@ -140,7 +134,6 @@ impl ForgeProject { ctx, env, funcs: vec![], - opts: Opts::default(), } } @@ -174,7 +167,7 @@ fn collect_sourcefiles>(root: P) -> impl Iterator } #[tracing::instrument(level = "debug")] -fn scan_directory(dir: PathBuf, function: Option<&str>, opts: Opts) -> Result { +fn scan_directory(dir: PathBuf, function: Option<&str>, opts: &Args) -> Result { let mut manifest_file = dir.clone(); manifest_file.push("manifest.yaml"); if !manifest_file.exists() { @@ -225,9 +218,13 @@ fn scan_directory(dir: PathBuf, function: Option<&str>, opts: Opts) -> Result, opts: Opts) -> Result, opts: Opts) -> Result, opts: Opts) -> Result, opts: Opts) -> Result, opts: Opts) -> Result, opts: Opts) -> Result { let mut runner = DefintionAnalysisRunner::new(); @@ -426,7 +425,7 @@ fn scan_directory(dir: PathBuf, function: Option<&str>, opts: Opts) -> Result { fs::write(path, report).into_diagnostic()?; } @@ -437,22 +436,16 @@ fn scan_directory(dir: PathBuf, function: Option<&str>, opts: Opts) -> Result Result<()> { - let args = Args::parse(); + let mut args = Args::parse(); tracing_subscriber::registry() .with(HierarchicalLayer::new(2)) .with(EnvFilter::from_env("FORGE_LOG")) .init(); + let dirs = std::mem::take(&mut args.dirs); let function = args.function.as_deref(); - let opts = Opts { - dump_callgraph: args.callgraph, - dump_cfg: args.cfg, - check_permissions: args.check_permissions, - out: args.out, - appkey: args.appkey, - }; - for dir in args.dirs { + for dir in dirs { debug!(?dir); - scan_directory(dir, function, opts.clone())?; + scan_directory(dir, function, &args)?; } Ok(()) } From bb27803fc1ca12201c3e1f149c8fea9b51f2e8d6 Mon Sep 17 00:00:00 2001 From: awang7 Date: Wed, 3 Jan 2024 11:34:53 -0800 Subject: [PATCH 235/517] NEW: Fresh branch branching off of EAS-1893 to whitelist admin module functions --- crates/forge_loader/src/manifest.rs | 5 ++++- crates/fsrt/src/main.rs | 1 + 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/crates/forge_loader/src/manifest.rs b/crates/forge_loader/src/manifest.rs index 79bd5df2..82cb8a09 100644 --- a/crates/forge_loader/src/manifest.rs +++ b/crates/forge_loader/src/manifest.rs @@ -256,12 +256,13 @@ pub enum FunctionTy { WebTrigger(T), } -// Struct used for tracking what scan a funtion requires. +// Struct used for tracking what scan a funtcion requires. #[derive(Debug, Clone, PartialEq, Eq)] pub struct Entrypoint<'a, S = Unresolved> { pub function: FunctionRef<'a, S>, pub invokable: bool, pub web_trigger: bool, + pub admin: bool, } // Helper functions that help filter out which functions are what. @@ -383,10 +384,12 @@ impl<'a> ForgeModules<'a> { .binary_search_by_key(&func.key, |trigger| &trigger.function) .is_ok(); let invokable = invokable_functions.contains(func.key); + let admin = false; Ok::<_, Error>(Entrypoint { function: FunctionRef::try_from(func)?, invokable, web_trigger, + admin, }) }) } diff --git a/crates/fsrt/src/main.rs b/crates/fsrt/src/main.rs index ce44f75f..85308295 100644 --- a/crates/fsrt/src/main.rs +++ b/crates/fsrt/src/main.rs @@ -209,6 +209,7 @@ fn scan_directory(dir: PathBuf, function: Option<&str>, opts: Opts) -> Result<() function: entrypoint.function.try_resolve(&paths, &dir)?, invokable: entrypoint.invokable, web_trigger: entrypoint.web_trigger, + admin: entrypoint.admin, }) }); From 5b6e3076c84bfe88f60f4406cf8a991fa5500578 Mon Sep 17 00:00:00 2001 From: awang7 Date: Wed, 3 Jan 2024 14:51:50 -0800 Subject: [PATCH 236/517] feat: tracking functions exposed in jira:adminPage module added. TODO: test whether they are being tagged. --- crates/forge_loader/src/manifest.rs | 23 ++++++++++++++++++++--- crates/fsrt/src/main.rs | 26 ++------------------------ 2 files changed, 22 insertions(+), 27 deletions(-) diff --git a/crates/forge_loader/src/manifest.rs b/crates/forge_loader/src/manifest.rs index 82cb8a09..d0d77363 100644 --- a/crates/forge_loader/src/manifest.rs +++ b/crates/forge_loader/src/manifest.rs @@ -139,6 +139,7 @@ pub struct WorkflowPostFunction<'a> { // Add more structs here for deserializing forge modules #[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] pub struct ForgeModules<'a> { + // deserializing non user-invocable modules #[serde(rename = "macro", default, borrow)] macros: Vec>, #[serde(rename = "function", default, borrow)] @@ -149,7 +150,7 @@ pub struct ForgeModules<'a> { issue_glance: Vec>, #[serde(rename = "jira:accessImportType", default, borrow)] access_import_type: Vec>, - // deserializing non user-invocable modules + // deserializing user invokable module functions #[serde(rename = "webtrigger", default, borrow)] webtriggers: Vec>, #[serde(rename = "trigger", default, borrow)] @@ -168,10 +169,20 @@ pub struct ForgeModules<'a> { pub workflow_validator: Vec>, #[serde(rename = "jira:workflowPostFunction", default, borrow)] pub workflow_post_function: Vec>, - // deserializing user invokable module functions + // deserializing admin pages + #[serde(rename = "jira:adminPage", default, borrow)] + pub jira_admin: Vec>, #[serde(flatten)] extra: FxHashMap>>, } +#[derive(Debug, Clone, PartialEq, Eq, Deserialize)] +pub struct JiraAdminPage<'a> { + key: &'a str, + function: &'a str, + resource: &'a str, + render: &'a str, + title: &'a str, +} #[derive(Debug, Clone, PartialEq, Eq, Deserialize)] pub struct Consumer<'a> { @@ -378,13 +389,19 @@ impl<'a> ForgeModules<'a> { invokable_functions.extend(access.import_status); }); + // TODO: create admin list and check whether function is in admin list then set admin bool to true. If not, set to false. self.functions.into_iter().flat_map(move |func| { let web_trigger = self .webtriggers .binary_search_by_key(&func.key, |trigger| &trigger.function) .is_ok(); let invokable = invokable_functions.contains(func.key); - let admin = false; + // this checks whether the funton being scanned is being used in an admin module. Rn it only checks for jira_admin page module. + // optionally: compass:adminPage could also be considered. + let admin = self + .jira_admin + .iter() + .any(|admin_function| admin_function.function == func.key); Ok::<_, Error>(Entrypoint { function: FunctionRef::try_from(func)?, invokable, diff --git a/crates/fsrt/src/main.rs b/crates/fsrt/src/main.rs index 85308295..a62e9c04 100644 --- a/crates/fsrt/src/main.rs +++ b/crates/fsrt/src/main.rs @@ -83,6 +83,7 @@ struct ResolvedEntryPoint<'a> { def_id: DefId, webtrigger: bool, invokable: bool, + admin: bool, } struct ForgeProject<'a> { @@ -154,6 +155,7 @@ impl<'a> ForgeProject<'a> { def_id, invokable: entrypoint.invokable, webtrigger: entrypoint.web_trigger, + admin: entrypoint.admin, }) })); } @@ -228,30 +230,6 @@ fn scan_directory(dir: PathBuf, function: Option<&str>, opts: Opts) -> Result<() let mut reporter = Reporter::new(); reporter.add_app(opts.appkey.unwrap_or_default(), name.to_owned()); for func in &proj.funcs { - // TODO: Update operations in for loop to scan functions. - // idea: iterate over each func which should be struct that tracks the function to be scanned. And performs scans according to bool. - // match *func { - // FunctionTy::Invokable((ref func, ref path, _, def)) => { - // let mut checker = AuthZChecker::new(); - // debug!("checking {func} at {path:?}"); - // if let Err(err) = interp.run_checker(def, &mut checker, path.clone(), func.clone()) - // { - // warn!("error while scanning {func} in {path:?}: {err}"); - // } - // reporter.add_vulnerabilities(checker.into_vulns()); - // } - // FunctionTy::WebTrigger((ref func, ref path, _, def)) => { - // let mut checker = AuthenticateChecker::new(); - // debug!("checking webtrigger {func} at {path:?}"); - // if let Err(err) = - // authn_interp.run_checker(def, &mut checker, path.clone(), func.clone()) - // { - // warn!("error while scanning {func} in {path:?}: {err}"); - // } - // reporter.add_vulnerabilities(checker.into_vulns()); - // } - // } - // Get entrypoint value from tuple // Logic for performing scans. // If it's invokable, then run invokable scan. If web_trigger, then trigger scan. From b0305c8659653a7371e4f8ec79a51fbaa1a48de7 Mon Sep 17 00:00:00 2001 From: awang7 Date: Thu, 4 Jan 2024 17:31:45 -0800 Subject: [PATCH 237/517] feat: wrote smol test to check for admin flag based on current behavior --- crates/forge_loader/src/manifest.rs | 160 ++++++++++++++++++++++------ 1 file changed, 128 insertions(+), 32 deletions(-) diff --git a/crates/forge_loader/src/manifest.rs b/crates/forge_loader/src/manifest.rs index d0d77363..e8e78fc5 100644 --- a/crates/forge_loader/src/manifest.rs +++ b/crates/forge_loader/src/manifest.rs @@ -179,8 +179,6 @@ pub struct ForgeModules<'a> { pub struct JiraAdminPage<'a> { key: &'a str, function: &'a str, - resource: &'a str, - render: &'a str, title: &'a str, } @@ -323,70 +321,70 @@ impl<'a> ForgeModules<'a> { // Get all the Triggers and represent them as a new struct thing where "webtrigger" attribute is true // for all trigger things - let mut invokable_functions = BTreeSet::new(); + let mut non_user_invokable_mod_functions = BTreeSet::new(); self.data_provider.iter().for_each(|dataprovider| { - invokable_functions.extend(dataprovider.callback); + non_user_invokable_mod_functions.extend(dataprovider.callback); }); self.custom_field.iter().for_each(|customfield| { - invokable_functions.extend(customfield.value); - invokable_functions.extend(customfield.search_suggestions); - invokable_functions.extend(customfield.edit); + non_user_invokable_mod_functions.extend(customfield.value); + non_user_invokable_mod_functions.extend(customfield.search_suggestions); + non_user_invokable_mod_functions.extend(customfield.edit); - invokable_functions.insert(customfield.common_keys.function); - invokable_functions.extend(customfield.common_keys.resolver); + non_user_invokable_mod_functions.insert(customfield.common_keys.function); + non_user_invokable_mod_functions.extend(customfield.common_keys.resolver); }); self.ui_modifications.iter().for_each(|ui| { - invokable_functions.insert(ui.common_keys.function); - invokable_functions.extend(ui.common_keys.resolver); + non_user_invokable_mod_functions.insert(ui.common_keys.function); + non_user_invokable_mod_functions.extend(ui.common_keys.resolver); }); self.workflow_validator.iter().for_each(|validator| { - invokable_functions.insert(validator.common_keys.key); + non_user_invokable_mod_functions.insert(validator.common_keys.key); - invokable_functions.insert(validator.common_keys.function); + non_user_invokable_mod_functions.insert(validator.common_keys.function); - invokable_functions.extend(validator.common_keys.resolver); + non_user_invokable_mod_functions.extend(validator.common_keys.resolver); }); self.workflow_post_function .iter() .for_each(|post_function| { - invokable_functions.insert(post_function.common_keys.key); + non_user_invokable_mod_functions.insert(post_function.common_keys.key); - invokable_functions.insert(post_function.common_keys.function); + non_user_invokable_mod_functions.insert(post_function.common_keys.function); - invokable_functions.extend(post_function.common_keys.resolver); + non_user_invokable_mod_functions.extend(post_function.common_keys.resolver); }); // get user invokable modules that have additional exposure endpoints. // ie macros has config and export fields on top of resolver fields that are functions self.macros.iter().for_each(|macros| { - invokable_functions.insert(macros.common_keys.key); + non_user_invokable_mod_functions.insert(macros.common_keys.key); - invokable_functions.insert(macros.common_keys.function); - invokable_functions.extend(macros.common_keys.resolver); + non_user_invokable_mod_functions.insert(macros.common_keys.function); + non_user_invokable_mod_functions.extend(macros.common_keys.resolver); - invokable_functions.extend(macros.config); - invokable_functions.extend(macros.export); + non_user_invokable_mod_functions.extend(macros.config); + non_user_invokable_mod_functions.extend(macros.export); }); self.issue_glance.iter().for_each(|issue| { - invokable_functions.insert(issue.common_keys.function); - invokable_functions.extend(issue.common_keys.resolver); - invokable_functions.extend(issue.dynamic_properties); + non_user_invokable_mod_functions.insert(issue.common_keys.function); + non_user_invokable_mod_functions.extend(issue.common_keys.resolver); + non_user_invokable_mod_functions.extend(issue.dynamic_properties); }); self.access_import_type.iter().for_each(|access| { - invokable_functions.insert(access.common_keys.function); - invokable_functions.extend(access.common_keys.resolver); + non_user_invokable_mod_functions.insert(access.common_keys.function); + non_user_invokable_mod_functions.extend(access.common_keys.resolver); - invokable_functions.extend(access.one_delete_import); - invokable_functions.extend(access.stop_import); - invokable_functions.extend(access.start_import); - invokable_functions.extend(access.import_status); + non_user_invokable_mod_functions.extend(access.one_delete_import); + non_user_invokable_mod_functions.extend(access.stop_import); + non_user_invokable_mod_functions.extend(access.start_import); + non_user_invokable_mod_functions.extend(access.import_status); }); // TODO: create admin list and check whether function is in admin list then set admin bool to true. If not, set to false. @@ -395,7 +393,7 @@ impl<'a> ForgeModules<'a> { .webtriggers .binary_search_by_key(&func.key, |trigger| &trigger.function) .is_ok(); - let invokable = invokable_functions.contains(func.key); + let invokable = non_user_invokable_mod_functions.contains(func.key); // this checks whether the funton being scanned is being used in an admin module. Rn it only checks for jira_admin page module. // optionally: compass:adminPage could also be considered. let admin = self @@ -634,4 +632,102 @@ mod tests { assert_eq!(func, "Catch-me-if-you-can3"); } } + + // Test checking whether jira:adminPage gets flagged. + #[test] + fn test_deserialize_admin_check() { + let json = r#"{ + "app": { + "name": "My App", + "id": "my-app" + }, + "modules": { + "jira:adminPage": [ + { + "key": "testing-admin-tag", + "function": "main1", + "title": "writing-a-test-for-admin-flag" + } + ], + "macro": [ + { + "key": "my-macro", + "function": "main2" + } + ], + "function": [ + { + "key": "main1", + "handler": "index.run" + }, + { + "key": "main2", + "handler": "src.run" + } + ] + }, + "permissions": { + "scopes": [ + "my-scope" + ], + "content": { + "scripts": [ + "my-script.js" + ], + "styles": [ + "my-style.css" + ] + } + } + }"#; + let manifest: ForgeManifest = serde_json::from_str(json).unwrap(); + let mut admin_func = manifest.modules.into_analyzable_functions(); + + assert_eq!( + admin_func.next(), + Some(Entrypoint { + function: FunctionRef::try_from(FunctionMod { + key: "main1", + handler: "index.run", + providers: None, + }) + .unwrap(), + invokable: false, + web_trigger: false, + admin: true + }) + ); + + assert_eq!( + admin_func.next(), + Some(Entrypoint { + function: FunctionRef::try_from(FunctionMod { + key: "main2", + handler: "src.run", + providers: None, + }) + .unwrap(), + invokable: true, + web_trigger: false, + admin: false + }) + ); + + // assert_eq!(manifest.app.name, Some("My App")); + // assert_eq!(manifest.app.id, "my-app"); + // assert_eq!(manifest.modules.macros.len(), 1); + // assert_eq!(manifest.modules.macros[0].common_keys.key, "My Macro"); + // // assert_eq!(manifest.modules.macros[0].function, "my-macro"); + // assert_eq!(manifest.modules.functions.len(), 1); + // assert_eq!( + // manifest.modules.functions[0], + // FunctionMod { + // key: "my-function", + // handler: "my-function-handler", + // providers: Some(AuthProviders { + // auth: vec!["my-auth-provider"] + // }), + // } + // ); + } } From 5e241068db389275a8437597a9adcfc05abebe25 Mon Sep 17 00:00:00 2001 From: awang7 Date: Thu, 4 Jan 2024 17:35:14 -0800 Subject: [PATCH 238/517] chore: cleaning up and reverting iterator to og name. --- crates/forge_loader/src/manifest.rs | 60 ++++++++++++++--------------- 1 file changed, 30 insertions(+), 30 deletions(-) diff --git a/crates/forge_loader/src/manifest.rs b/crates/forge_loader/src/manifest.rs index e8e78fc5..d1e7721a 100644 --- a/crates/forge_loader/src/manifest.rs +++ b/crates/forge_loader/src/manifest.rs @@ -321,70 +321,70 @@ impl<'a> ForgeModules<'a> { // Get all the Triggers and represent them as a new struct thing where "webtrigger" attribute is true // for all trigger things - let mut non_user_invokable_mod_functions = BTreeSet::new(); + let mut invokable_functions = BTreeSet::new(); self.data_provider.iter().for_each(|dataprovider| { - non_user_invokable_mod_functions.extend(dataprovider.callback); + invokable_functions.extend(dataprovider.callback); }); self.custom_field.iter().for_each(|customfield| { - non_user_invokable_mod_functions.extend(customfield.value); - non_user_invokable_mod_functions.extend(customfield.search_suggestions); - non_user_invokable_mod_functions.extend(customfield.edit); + invokable_functions.extend(customfield.value); + invokable_functions.extend(customfield.search_suggestions); + invokable_functions.extend(customfield.edit); - non_user_invokable_mod_functions.insert(customfield.common_keys.function); - non_user_invokable_mod_functions.extend(customfield.common_keys.resolver); + invokable_functions.insert(customfield.common_keys.function); + invokable_functions.extend(customfield.common_keys.resolver); }); self.ui_modifications.iter().for_each(|ui| { - non_user_invokable_mod_functions.insert(ui.common_keys.function); - non_user_invokable_mod_functions.extend(ui.common_keys.resolver); + invokable_functions.insert(ui.common_keys.function); + invokable_functions.extend(ui.common_keys.resolver); }); self.workflow_validator.iter().for_each(|validator| { - non_user_invokable_mod_functions.insert(validator.common_keys.key); + invokable_functions.insert(validator.common_keys.key); - non_user_invokable_mod_functions.insert(validator.common_keys.function); + invokable_functions.insert(validator.common_keys.function); - non_user_invokable_mod_functions.extend(validator.common_keys.resolver); + invokable_functions.extend(validator.common_keys.resolver); }); self.workflow_post_function .iter() .for_each(|post_function| { - non_user_invokable_mod_functions.insert(post_function.common_keys.key); + invokable_functions.insert(post_function.common_keys.key); - non_user_invokable_mod_functions.insert(post_function.common_keys.function); + invokable_functions.insert(post_function.common_keys.function); - non_user_invokable_mod_functions.extend(post_function.common_keys.resolver); + invokable_functions.extend(post_function.common_keys.resolver); }); // get user invokable modules that have additional exposure endpoints. // ie macros has config and export fields on top of resolver fields that are functions self.macros.iter().for_each(|macros| { - non_user_invokable_mod_functions.insert(macros.common_keys.key); + invokable_functions.insert(macros.common_keys.key); - non_user_invokable_mod_functions.insert(macros.common_keys.function); - non_user_invokable_mod_functions.extend(macros.common_keys.resolver); + invokable_functions.insert(macros.common_keys.function); + invokable_functions.extend(macros.common_keys.resolver); - non_user_invokable_mod_functions.extend(macros.config); - non_user_invokable_mod_functions.extend(macros.export); + invokable_functions.extend(macros.config); + invokable_functions.extend(macros.export); }); self.issue_glance.iter().for_each(|issue| { - non_user_invokable_mod_functions.insert(issue.common_keys.function); - non_user_invokable_mod_functions.extend(issue.common_keys.resolver); - non_user_invokable_mod_functions.extend(issue.dynamic_properties); + invokable_functions.insert(issue.common_keys.function); + invokable_functions.extend(issue.common_keys.resolver); + invokable_functions.extend(issue.dynamic_properties); }); self.access_import_type.iter().for_each(|access| { - non_user_invokable_mod_functions.insert(access.common_keys.function); - non_user_invokable_mod_functions.extend(access.common_keys.resolver); + invokable_functions.insert(access.common_keys.function); + invokable_functions.extend(access.common_keys.resolver); - non_user_invokable_mod_functions.extend(access.one_delete_import); - non_user_invokable_mod_functions.extend(access.stop_import); - non_user_invokable_mod_functions.extend(access.start_import); - non_user_invokable_mod_functions.extend(access.import_status); + invokable_functions.extend(access.one_delete_import); + invokable_functions.extend(access.stop_import); + invokable_functions.extend(access.start_import); + invokable_functions.extend(access.import_status); }); // TODO: create admin list and check whether function is in admin list then set admin bool to true. If not, set to false. @@ -393,7 +393,7 @@ impl<'a> ForgeModules<'a> { .webtriggers .binary_search_by_key(&func.key, |trigger| &trigger.function) .is_ok(); - let invokable = non_user_invokable_mod_functions.contains(func.key); + let invokable = invokable_functions.contains(func.key); // this checks whether the funton being scanned is being used in an admin module. Rn it only checks for jira_admin page module. // optionally: compass:adminPage could also be considered. let admin = self From db9f8f1cea92c5a0f2bc4830f8f7c95a950621c4 Mon Sep 17 00:00:00 2001 From: awang7 Date: Mon, 8 Jan 2024 10:12:26 -0800 Subject: [PATCH 239/517] clean: removed commented out functions --- crates/forge_loader/src/manifest.rs | 28 ---------------------------- 1 file changed, 28 deletions(-) diff --git a/crates/forge_loader/src/manifest.rs b/crates/forge_loader/src/manifest.rs index d1e7721a..26e78319 100644 --- a/crates/forge_loader/src/manifest.rs +++ b/crates/forge_loader/src/manifest.rs @@ -274,34 +274,6 @@ pub struct Entrypoint<'a, S = Unresolved> { pub admin: bool, } -// Helper functions that help filter out which functions are what. -// original code that's commented out to modify methods. Here for reference -// impl FunctionTy { -// pub fn map(self, f: impl FnOnce(T) -> O) -> FunctionTy { -// match self { -// Self::Invokable(t) => FunctionTy::Invokable(f(t)), -// Self::WebTrigger(t) => FunctionTy::WebTrigger(f(t)), -// } -// } - -// #[inline] -// pub fn into_inner(self) -> T { -// match self { -// FunctionTy::Invokable(t) | FunctionTy::WebTrigger(t) => t, -// } -// } - -// pub fn sequence( -// self, -// f: impl FnOnce(T) -> I, -// ) -> impl Iterator> { -// match self { -// Self::Invokable(t) => Either::Left(f(t).into_iter().map(FunctionTy::Invokable)), -// Self::WebTrigger(t) => Either::Right(f(t).into_iter().map(FunctionTy::WebTrigger)), -// } -// } -// } - impl AsRef for FunctionTy { #[inline] fn as_ref(&self) -> &T { From 506ee57fa6e01889f2ddb3b087ae81dac7c59e8d Mon Sep 17 00:00:00 2001 From: awang7 Date: Mon, 8 Jan 2024 12:06:54 -0800 Subject: [PATCH 240/517] resolving merge conflicts for rebase --- crates/forge_loader/src/manifest.rs | 132 ++++++++++++++++++++++++++-- 1 file changed, 125 insertions(+), 7 deletions(-) diff --git a/crates/forge_loader/src/manifest.rs b/crates/forge_loader/src/manifest.rs index f980e78e..41256df8 100644 --- a/crates/forge_loader/src/manifest.rs +++ b/crates/forge_loader/src/manifest.rs @@ -66,18 +66,56 @@ struct ScheduledTrigger<'a> { interval: Interval, } +#[derive(Debug, Clone, PartialEq, Eq, Deserialize)] +struct DataProvider<'a> { + key: &'a str, + #[serde(flatten, borrow)] + callback: Callback<'a>, +} + +#[derive(Debug, Clone, PartialEq, Eq, Deserialize)] +pub struct Callback<'a> { + pub function: &'a str, +} + +// Struct for Custom field Module. Check that search suggestion gets read in correctly. +#[derive(Debug, Clone, PartialEq, Eq, Deserialize)] +struct CustomField<'a> { + #[serde(flatten, borrow)] + key: &'a str, + search_suggestion: &'a str, +} + +#[derive(Debug, Clone, PartialEq, Eq, Deserialize)] +struct UiModificatons<'a> { + #[serde(flatten, borrow)] + key: &'a str, + resolver: Callback<'a>, +} + +#[derive(Debug, Clone, PartialEq, Eq, Deserialize)] +struct WorkflowValidator<'a> { + #[serde(flatten, borrow)] + key: &'a str, + functon: &'a str, + resolver: Callback<'a>, +} + #[derive(Debug, Clone, PartialEq, Eq, Deserialize)] struct WorkflowPostFunction<'a> { #[serde(flatten, borrow)] - raw: RawTrigger<'a>, + key: &'a str, + function: &'a str, } +// Add more structs here for deserializing forge modules #[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] pub struct ForgeModules<'a> { #[serde(rename = "macro", default, borrow)] macros: Vec>, #[serde(rename = "function", default, borrow)] pub functions: Vec>, + // deserializing non user-invocable modules #[serde(rename = "webtrigger", default, borrow)] webtriggers: Vec>, #[serde(rename = "trigger", default, borrow)] @@ -86,8 +124,17 @@ pub struct ForgeModules<'a> { scheduled_triggers: Vec>, #[serde(rename = "consumer", default, borrow)] pub consumers: Vec>, + #[serde(rename = "compass:dataProvider", default, borrow)] + pub data_provider: Vec>, + #[serde(rename = "jira:customField", default, borrow)] + pub custom_field: Vec>, + #[serde(rename = "jira:uiModificatons", default, borrow)] + pub ui_modifications: Vec>, + #[serde(rename = "jira:workflowValidator", default, borrow)] + pub workflow_validator: Vec>, #[serde(rename = "jira:workflowPostFunction", default, borrow)] - workflow_post_functions: Vec>, + pub workflow_post_function: Vec>, + // deserializing user invokable module functions #[serde(flatten)] extra: FxHashMap>>, } @@ -166,12 +213,24 @@ pub struct FunctionRef<'a, S = Unresolved> { status: S, } +// Add an extra variant to the FunctionTy enum for non user invocable functions +// Indirect: functions indirectly invoked by user :O So kewl. +// TODO: change this to struct with bools #[derive(Debug, Clone, PartialEq, Eq)] pub enum FunctionTy { Invokable(T), WebTrigger(T), } +// Struct used for tracking what scan a funtion requires. +#[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] +pub struct Entrypoints<'a> { + function: Vec>, + invokable: bool, + web_trigger: bool, +} + +// Helper functions that help filter out which functions are what. impl FunctionTy { pub fn map(self, f: impl FnOnce(T) -> O) -> FunctionTy { match self { @@ -215,11 +274,9 @@ impl<'a> ForgeModules<'a> { //is not invoked by a user-invocable module. // number of webtriggers are usually low, so it's better to just sort them and reuse - // the vec's storage instead of using a HashSet self.webtriggers .sort_unstable_by_key(|trigger| trigger.function); - // same rational for using a BTreeSet let mut ignored_functions: BTreeSet<_> = self .scheduled_triggers .into_iter() @@ -230,13 +287,14 @@ impl<'a> ForgeModules<'a> { .map(|trigger| trigger.raw.function), ) .chain( - self.workflow_post_functions + self.workflow_post_function .into_iter() - .map(|pf| pf.raw.function), + .map(|pf| pf.function), ) .collect(); - let mut alternate_functions = vec![]; + // make alternate_functions all user-invokable functions + let mut alternate_functions: Vec<&str> = Vec::new(); for module in self.extra.into_values().flatten() { alternate_functions.extend(module.function); if let Some(resolver) = module.resolver { @@ -244,12 +302,19 @@ impl<'a> ForgeModules<'a> { } } + // Iterate over Consumers and check that if consumers isn't in alternate functions, add consumer funtion to be ignored + // assuming that alternate functions already has all user invokable functions. self.consumers.iter().for_each(|consumer| { if !alternate_functions.contains(&consumer.resolver.function) { ignored_functions.insert(consumer.resolver.function); } }); + // TODO: Iterate through all deserialized entrypoints that are represented as a struct when deserialized + // Update Struct values to be true or not. If any part true, then scan. + // This solution fixes the problem that we only check known user invokable modules and also acccounts for non-invokable module entry points + + // return non-user invokable functions self.functions.into_iter().filter_map(move |func| { if ignored_functions.contains(&func.key) { return None; @@ -416,4 +481,57 @@ mod tests { } ); } + + // Modified specific deserialization schemes for modules. Checking that new schemes can deserialize function values. + #[test] + fn test_new_deserialize() { + let json = r#"{ + "app": { + "name": "My App", + "id": "my-app" + }, + "modules": { + "macro": [ + { + "key": "my-macro", + "title": "My Macro", + "resolver": { + "function": Catch-me-if-you-can1 + }, + "config": { + "function": Catch-me-if-you-can2 + } + } + ], + "function": [ + { + "key": "my-function", + "handler": "my-function-handler", + "providers": { + "auth": ["my-auth-provider"] + } + } + ], + "webtrigger": [ + { + "key": "my-webtrigger", + "function": "my-webtrigger-handler" + } + ] + }, + "permissions": { + "scopes": [ + "my-scope" + ], + "content": { + "scripts": [ + "my-script.js" + ], + "styles": [ + "my-style.css" + ] + } + } + }"#; + } } From 1b1c2121bc89170c29d97428fc07315561be5759 Mon Sep 17 00:00:00 2001 From: awang7 Date: Mon, 8 Jan 2024 12:06:54 -0800 Subject: [PATCH 241/517] resolving merge conflicts for rebase --- crates/forge_loader/src/manifest.rs | 132 ++++++++++++++++++++++++++-- 1 file changed, 125 insertions(+), 7 deletions(-) diff --git a/crates/forge_loader/src/manifest.rs b/crates/forge_loader/src/manifest.rs index f980e78e..41256df8 100644 --- a/crates/forge_loader/src/manifest.rs +++ b/crates/forge_loader/src/manifest.rs @@ -66,18 +66,56 @@ struct ScheduledTrigger<'a> { interval: Interval, } +#[derive(Debug, Clone, PartialEq, Eq, Deserialize)] +struct DataProvider<'a> { + key: &'a str, + #[serde(flatten, borrow)] + callback: Callback<'a>, +} + +#[derive(Debug, Clone, PartialEq, Eq, Deserialize)] +pub struct Callback<'a> { + pub function: &'a str, +} + +// Struct for Custom field Module. Check that search suggestion gets read in correctly. +#[derive(Debug, Clone, PartialEq, Eq, Deserialize)] +struct CustomField<'a> { + #[serde(flatten, borrow)] + key: &'a str, + search_suggestion: &'a str, +} + +#[derive(Debug, Clone, PartialEq, Eq, Deserialize)] +struct UiModificatons<'a> { + #[serde(flatten, borrow)] + key: &'a str, + resolver: Callback<'a>, +} + +#[derive(Debug, Clone, PartialEq, Eq, Deserialize)] +struct WorkflowValidator<'a> { + #[serde(flatten, borrow)] + key: &'a str, + functon: &'a str, + resolver: Callback<'a>, +} + #[derive(Debug, Clone, PartialEq, Eq, Deserialize)] struct WorkflowPostFunction<'a> { #[serde(flatten, borrow)] - raw: RawTrigger<'a>, + key: &'a str, + function: &'a str, } +// Add more structs here for deserializing forge modules #[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] pub struct ForgeModules<'a> { #[serde(rename = "macro", default, borrow)] macros: Vec>, #[serde(rename = "function", default, borrow)] pub functions: Vec>, + // deserializing non user-invocable modules #[serde(rename = "webtrigger", default, borrow)] webtriggers: Vec>, #[serde(rename = "trigger", default, borrow)] @@ -86,8 +124,17 @@ pub struct ForgeModules<'a> { scheduled_triggers: Vec>, #[serde(rename = "consumer", default, borrow)] pub consumers: Vec>, + #[serde(rename = "compass:dataProvider", default, borrow)] + pub data_provider: Vec>, + #[serde(rename = "jira:customField", default, borrow)] + pub custom_field: Vec>, + #[serde(rename = "jira:uiModificatons", default, borrow)] + pub ui_modifications: Vec>, + #[serde(rename = "jira:workflowValidator", default, borrow)] + pub workflow_validator: Vec>, #[serde(rename = "jira:workflowPostFunction", default, borrow)] - workflow_post_functions: Vec>, + pub workflow_post_function: Vec>, + // deserializing user invokable module functions #[serde(flatten)] extra: FxHashMap>>, } @@ -166,12 +213,24 @@ pub struct FunctionRef<'a, S = Unresolved> { status: S, } +// Add an extra variant to the FunctionTy enum for non user invocable functions +// Indirect: functions indirectly invoked by user :O So kewl. +// TODO: change this to struct with bools #[derive(Debug, Clone, PartialEq, Eq)] pub enum FunctionTy { Invokable(T), WebTrigger(T), } +// Struct used for tracking what scan a funtion requires. +#[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] +pub struct Entrypoints<'a> { + function: Vec>, + invokable: bool, + web_trigger: bool, +} + +// Helper functions that help filter out which functions are what. impl FunctionTy { pub fn map(self, f: impl FnOnce(T) -> O) -> FunctionTy { match self { @@ -215,11 +274,9 @@ impl<'a> ForgeModules<'a> { //is not invoked by a user-invocable module. // number of webtriggers are usually low, so it's better to just sort them and reuse - // the vec's storage instead of using a HashSet self.webtriggers .sort_unstable_by_key(|trigger| trigger.function); - // same rational for using a BTreeSet let mut ignored_functions: BTreeSet<_> = self .scheduled_triggers .into_iter() @@ -230,13 +287,14 @@ impl<'a> ForgeModules<'a> { .map(|trigger| trigger.raw.function), ) .chain( - self.workflow_post_functions + self.workflow_post_function .into_iter() - .map(|pf| pf.raw.function), + .map(|pf| pf.function), ) .collect(); - let mut alternate_functions = vec![]; + // make alternate_functions all user-invokable functions + let mut alternate_functions: Vec<&str> = Vec::new(); for module in self.extra.into_values().flatten() { alternate_functions.extend(module.function); if let Some(resolver) = module.resolver { @@ -244,12 +302,19 @@ impl<'a> ForgeModules<'a> { } } + // Iterate over Consumers and check that if consumers isn't in alternate functions, add consumer funtion to be ignored + // assuming that alternate functions already has all user invokable functions. self.consumers.iter().for_each(|consumer| { if !alternate_functions.contains(&consumer.resolver.function) { ignored_functions.insert(consumer.resolver.function); } }); + // TODO: Iterate through all deserialized entrypoints that are represented as a struct when deserialized + // Update Struct values to be true or not. If any part true, then scan. + // This solution fixes the problem that we only check known user invokable modules and also acccounts for non-invokable module entry points + + // return non-user invokable functions self.functions.into_iter().filter_map(move |func| { if ignored_functions.contains(&func.key) { return None; @@ -416,4 +481,57 @@ mod tests { } ); } + + // Modified specific deserialization schemes for modules. Checking that new schemes can deserialize function values. + #[test] + fn test_new_deserialize() { + let json = r#"{ + "app": { + "name": "My App", + "id": "my-app" + }, + "modules": { + "macro": [ + { + "key": "my-macro", + "title": "My Macro", + "resolver": { + "function": Catch-me-if-you-can1 + }, + "config": { + "function": Catch-me-if-you-can2 + } + } + ], + "function": [ + { + "key": "my-function", + "handler": "my-function-handler", + "providers": { + "auth": ["my-auth-provider"] + } + } + ], + "webtrigger": [ + { + "key": "my-webtrigger", + "function": "my-webtrigger-handler" + } + ] + }, + "permissions": { + "scopes": [ + "my-scope" + ], + "content": { + "scripts": [ + "my-script.js" + ], + "styles": [ + "my-style.css" + ] + } + } + }"#; + } } From fb13240dce3515bb58ffd4cbcb7d414851c24302 Mon Sep 17 00:00:00 2001 From: awang7 Date: Mon, 11 Sep 2023 15:50:57 -0400 Subject: [PATCH 242/517] added more todo statements and cleaned up struct definitions --- crates/forge_loader/src/manifest.rs | 21 +++++++++++++++------ crates/fsrt/src/main.rs | 2 ++ 2 files changed, 17 insertions(+), 6 deletions(-) diff --git a/crates/forge_loader/src/manifest.rs b/crates/forge_loader/src/manifest.rs index 41256df8..8cf22a68 100644 --- a/crates/forge_loader/src/manifest.rs +++ b/crates/forge_loader/src/manifest.rs @@ -16,7 +16,7 @@ struct AuthProviders<'a> { #[serde(borrow)] auth: Vec<&'a str>, } - +// Maps the Functions Module in common Modules #[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] pub struct FunctionMod<'a> { key: &'a str, @@ -25,24 +25,30 @@ pub struct FunctionMod<'a> { providers: Option>, } +// Modified #[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] struct ModInfo<'a> { - key: &'a str, - title: &'a str, + function: &'a str, } #[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] -struct MacroMod<'a> { - #[serde(flatten, borrow)] - info: ModInfo<'a>, +struct MacroMod<'a> { + #[serde(flatten, borrow)] + key: &'a str, + function: &'a str, + resolver: ModInfo<'a>, + config: ModInfo<'a>, + export: ModInfo<'a>, } +// WebTrigger => RawTrigger; WHY IS THIS NAMED DIFFERENTLY !? WHO CHANGED NAMES #[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] struct RawTrigger<'a> { key: &'a str, function: &'a str, } +// Trigger => EventTriger; WHY IS THIS NAMED DIFFERENTLY !? WHO CHANGED NAMES #[derive(Debug, Clone, PartialEq, Eq, Deserialize)] struct EventTrigger<'a> { #[serde(flatten, borrow)] @@ -59,6 +65,7 @@ enum Interval { Week, } +// Thank you to whomeever kept this one the same. T.T #[derive(Debug, Clone, PartialEq, Eq, Deserialize)] struct ScheduledTrigger<'a> { #[serde(flatten, borrow)] @@ -66,6 +73,7 @@ struct ScheduledTrigger<'a> { interval: Interval, } +// compass DataProvider module #[derive(Debug, Clone, PartialEq, Eq, Deserialize)] struct DataProvider<'a> { key: &'a str, @@ -73,6 +81,7 @@ struct DataProvider<'a> { callback: Callback<'a>, } +// Struct for mapping functions defined one more level in whose value is {function: string}. Used to represent resolver types. #[derive(Debug, Clone, PartialEq, Eq, Deserialize)] pub struct Callback<'a> { pub function: &'a str, diff --git a/crates/fsrt/src/main.rs b/crates/fsrt/src/main.rs index 770d7a70..b1281be5 100644 --- a/crates/fsrt/src/main.rs +++ b/crates/fsrt/src/main.rs @@ -298,6 +298,8 @@ fn scan_directory(dir: PathBuf, function: Option<&str>, opts: &Args) -> Result { let mut runner = DefintionAnalysisRunner::new(); From 931f3ba5f279f1114a2253c0d6f6665c63f11801 Mon Sep 17 00:00:00 2001 From: awang7 Date: Mon, 11 Sep 2023 15:50:57 -0400 Subject: [PATCH 243/517] added more todo statements and cleaned up struct definitions --- crates/forge_loader/src/manifest.rs | 21 +++++++++++++++------ crates/fsrt/src/main.rs | 2 ++ 2 files changed, 17 insertions(+), 6 deletions(-) diff --git a/crates/forge_loader/src/manifest.rs b/crates/forge_loader/src/manifest.rs index 41256df8..8cf22a68 100644 --- a/crates/forge_loader/src/manifest.rs +++ b/crates/forge_loader/src/manifest.rs @@ -16,7 +16,7 @@ struct AuthProviders<'a> { #[serde(borrow)] auth: Vec<&'a str>, } - +// Maps the Functions Module in common Modules #[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] pub struct FunctionMod<'a> { key: &'a str, @@ -25,24 +25,30 @@ pub struct FunctionMod<'a> { providers: Option>, } +// Modified #[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] struct ModInfo<'a> { - key: &'a str, - title: &'a str, + function: &'a str, } #[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] -struct MacroMod<'a> { - #[serde(flatten, borrow)] - info: ModInfo<'a>, +struct MacroMod<'a> { + #[serde(flatten, borrow)] + key: &'a str, + function: &'a str, + resolver: ModInfo<'a>, + config: ModInfo<'a>, + export: ModInfo<'a>, } +// WebTrigger => RawTrigger; WHY IS THIS NAMED DIFFERENTLY !? WHO CHANGED NAMES #[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] struct RawTrigger<'a> { key: &'a str, function: &'a str, } +// Trigger => EventTriger; WHY IS THIS NAMED DIFFERENTLY !? WHO CHANGED NAMES #[derive(Debug, Clone, PartialEq, Eq, Deserialize)] struct EventTrigger<'a> { #[serde(flatten, borrow)] @@ -59,6 +65,7 @@ enum Interval { Week, } +// Thank you to whomeever kept this one the same. T.T #[derive(Debug, Clone, PartialEq, Eq, Deserialize)] struct ScheduledTrigger<'a> { #[serde(flatten, borrow)] @@ -66,6 +73,7 @@ struct ScheduledTrigger<'a> { interval: Interval, } +// compass DataProvider module #[derive(Debug, Clone, PartialEq, Eq, Deserialize)] struct DataProvider<'a> { key: &'a str, @@ -73,6 +81,7 @@ struct DataProvider<'a> { callback: Callback<'a>, } +// Struct for mapping functions defined one more level in whose value is {function: string}. Used to represent resolver types. #[derive(Debug, Clone, PartialEq, Eq, Deserialize)] pub struct Callback<'a> { pub function: &'a str, diff --git a/crates/fsrt/src/main.rs b/crates/fsrt/src/main.rs index 770d7a70..b1281be5 100644 --- a/crates/fsrt/src/main.rs +++ b/crates/fsrt/src/main.rs @@ -298,6 +298,8 @@ fn scan_directory(dir: PathBuf, function: Option<&str>, opts: &Args) -> Result { let mut runner = DefintionAnalysisRunner::new(); From dc0dfa2163021855c88ec0019e0c24604fd0214a Mon Sep 17 00:00:00 2001 From: awang7 Date: Mon, 8 Jan 2024 12:09:16 -0800 Subject: [PATCH 244/517] resolving merge conflicts for rebase --- crates/forge_loader/src/manifest.rs | 221 ++++++++++++++----- crates/forge_permission_resolver/src/test.rs | 7 + 2 files changed, 168 insertions(+), 60 deletions(-) diff --git a/crates/forge_loader/src/manifest.rs b/crates/forge_loader/src/manifest.rs index 8cf22a68..1ef7ca6d 100644 --- a/crates/forge_loader/src/manifest.rs +++ b/crates/forge_loader/src/manifest.rs @@ -9,6 +9,7 @@ use crate::{forgepermissions::ForgePermissions, Error}; use forge_utils::FxHashMap; use itertools::{Either, Itertools}; use serde::Deserialize; +use serde_json::map::Entry; use tracing::trace; #[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] @@ -16,7 +17,7 @@ struct AuthProviders<'a> { #[serde(borrow)] auth: Vec<&'a str>, } -// Maps the Functions Module in common Modules +// Maps the Functions Module in common Modules #[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] pub struct FunctionMod<'a> { key: &'a str, @@ -25,15 +26,15 @@ pub struct FunctionMod<'a> { providers: Option>, } -// Modified +// Modified #[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] struct ModInfo<'a> { function: &'a str, } #[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] -struct MacroMod<'a> { - #[serde(flatten, borrow)] +struct MacroMod<'a> { + #[serde(flatten, borrow)] key: &'a str, function: &'a str, resolver: ModInfo<'a>, @@ -41,14 +42,14 @@ struct MacroMod<'a> { export: ModInfo<'a>, } -// WebTrigger => RawTrigger; WHY IS THIS NAMED DIFFERENTLY !? WHO CHANGED NAMES +// WebTrigger => RawTrigger; WHY IS THIS NAMED DIFFERENTLY !? WHO CHANGED NAMES #[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] struct RawTrigger<'a> { key: &'a str, function: &'a str, } -// Trigger => EventTriger; WHY IS THIS NAMED DIFFERENTLY !? WHO CHANGED NAMES +// Trigger => EventTriger; WHY IS THIS NAMED DIFFERENTLY !? WHO CHANGED NAMES #[derive(Debug, Clone, PartialEq, Eq, Deserialize)] struct EventTrigger<'a> { #[serde(flatten, borrow)] @@ -65,7 +66,7 @@ enum Interval { Week, } -// Thank you to whomeever kept this one the same. T.T +// Thank you to whomeever kept this one the same. T.T #[derive(Debug, Clone, PartialEq, Eq, Deserialize)] struct ScheduledTrigger<'a> { #[serde(flatten, borrow)] @@ -75,7 +76,7 @@ struct ScheduledTrigger<'a> { // compass DataProvider module #[derive(Debug, Clone, PartialEq, Eq, Deserialize)] -struct DataProvider<'a> { +pub struct DataProvider<'a> { key: &'a str, #[serde(flatten, borrow)] callback: Callback<'a>, @@ -89,21 +90,21 @@ pub struct Callback<'a> { // Struct for Custom field Module. Check that search suggestion gets read in correctly. #[derive(Debug, Clone, PartialEq, Eq, Deserialize)] -struct CustomField<'a> { +pub struct CustomField<'a> { #[serde(flatten, borrow)] key: &'a str, search_suggestion: &'a str, } #[derive(Debug, Clone, PartialEq, Eq, Deserialize)] -struct UiModificatons<'a> { +pub struct UiModificatons<'a> { #[serde(flatten, borrow)] key: &'a str, resolver: Callback<'a>, } #[derive(Debug, Clone, PartialEq, Eq, Deserialize)] -struct WorkflowValidator<'a> { +pub struct WorkflowValidator<'a> { #[serde(flatten, borrow)] key: &'a str, functon: &'a str, @@ -111,7 +112,7 @@ struct WorkflowValidator<'a> { } #[derive(Debug, Clone, PartialEq, Eq, Deserialize)] -struct WorkflowPostFunction<'a> { +pub struct WorkflowPostFunction<'a> { #[serde(flatten, borrow)] key: &'a str, function: &'a str, @@ -232,9 +233,9 @@ pub enum FunctionTy { } // Struct used for tracking what scan a funtion requires. -#[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] +#[derive(Debug, Clone, PartialEq, Eq, Deserialize)] pub struct Entrypoints<'a> { - function: Vec>, + function: &'a str, invokable: bool, web_trigger: bool, } @@ -276,70 +277,170 @@ impl AsRef for FunctionTy { } impl<'a> ForgeModules<'a> { - pub fn into_analyzable_functions( - mut self, - ) -> impl Iterator>> { - //FIXME: The logic here is incorrect, a function should be in the ignored function IIF it - //is not invoked by a user-invocable module. - + // TODO: fix return type whop + pub fn into_analyzable_functions(mut self) -> impl Iterator> { // number of webtriggers are usually low, so it's better to just sort them and reuse self.webtriggers .sort_unstable_by_key(|trigger| trigger.function); - let mut ignored_functions: BTreeSet<_> = self + // Get all the Triggers and represent them as a new struct thing where "webtrigger" attribute is true + // for all trigger things + let web = self.webtriggers.iter().for_each(|webtriggers| { + Entrypoints { + function: webtriggers.function, + invokable: false, + web_trigger: true, + }; + }); + let event = self.event_triggers.iter().for_each(|event_triggers| { + Entrypoints { + function: event_triggers.raw.function, + invokable: false, + web_trigger: true, + }; + }); + let schedule = self .scheduled_triggers - .into_iter() - .map(|trigger| trigger.raw.function) - .chain( - self.event_triggers - .into_iter() - .map(|trigger| trigger.raw.function), - ) - .chain( - self.workflow_post_function - .into_iter() - .map(|pf| pf.function), - ) - .collect(); + .iter() + .for_each(|schedule_triggers| { + Entrypoints { + function: schedule_triggers.raw.function, + invokable: false, + web_trigger: true, + }; + }); + + // create arrays representing functions that expose user non-invokable functions + let consumer = self.consumers.iter().for_each(|consumers| { + Entrypoints { + function: consumers.resolver.function, + invokable: true, + web_trigger: false, + }; + }); + + let data_provider = self.data_provider.iter().for_each(|dataprovider| { + Entrypoints { + function: dataprovider.callback.function, + invokable: true, + web_trigger: false, + }; + }); + + let custom_field = self.custom_field.iter().for_each(|customfield| { + Entrypoints { + function: customfield.search_suggestion, + invokable: true, + web_trigger: false, + }; + }); + let ui_mod = self.ui_modifications.iter().for_each(|ui| { + Entrypoints { + function: ui.resolver.function, + invokable: true, + web_trigger: false, + }; + }); + + let workflow_validator = self.workflow_validator.iter().for_each(|validator| { + Entrypoints { + function: validator.resolver.function, + invokable: true, + web_trigger: false, + }; + }); + + let workflow_post = self + .workflow_post_function + .iter() + .for_each(|post_function| { + Entrypoints { + function: post_function.function, + invokable: true, + web_trigger: false, + }; + }); + + // let user_invokable = self.extra.into_values().flatten().into_iter().for_each(|invokable| { + + // if invokable.resolver != None { + // Entrypoints { + // function: invokable.resolver, + // invokable: true, + // web_trigger: false, + // }; + + // } + // Entrypoints { + // function: invokable.function, + // invokable: true, + // web_trigger: false, + // }; + // }); + + // let mut ignored_functions: BTreeSet<_> = self + // .scheduled_triggers + // .into_iter() + // .map(|trigger| trigger.raw.function) + // .chain( + // self.event_triggers + // .into_iter() + // .map(|trigger| trigger.raw.function), + // ) + // .collect(); + + // get array for user invokable module functions // make alternate_functions all user-invokable functions - let mut alternate_functions: Vec<&str> = Vec::new(); + let mut alternate_functions = Vec::new(); for module in self.extra.into_values().flatten() { - alternate_functions.extend(module.function); + if let Some(mod_function) = module.function { + alternate_functions.push(Entrypoints { + function: mod_function, + invokable: true, + web_trigger: false, + }); + } + if let Some(resolver) = module.resolver { - alternate_functions.push(resolver.function); + alternate_functions.push(Entrypoints { + function: resolver.function, + invokable: true, + web_trigger: false, + }); } } + workflow_post // Iterate over Consumers and check that if consumers isn't in alternate functions, add consumer funtion to be ignored // assuming that alternate functions already has all user invokable functions. - self.consumers.iter().for_each(|consumer| { - if !alternate_functions.contains(&consumer.resolver.function) { - ignored_functions.insert(consumer.resolver.function); - } - }); + // self.consumers.iter().for_each(|consumer| { + // if !alternate_functions.contains(&consumer.resolver.function) { + // ignored_functions.insert(consumer.resolver.function); + // } + // }); // TODO: Iterate through all deserialized entrypoints that are represented as a struct when deserialized // Update Struct values to be true or not. If any part true, then scan. // This solution fixes the problem that we only check known user invokable modules and also acccounts for non-invokable module entry points // return non-user invokable functions - self.functions.into_iter().filter_map(move |func| { - if ignored_functions.contains(&func.key) { - return None; - } - Some( - if self - .webtriggers - .binary_search_by_key(&func.key, |trigger| trigger.function) - .is_ok() - { - FunctionTy::WebTrigger(func) - } else { - FunctionTy::Invokable(func) - }, - ) - }) + // self.functions.into_iter().filter_map(move |func| { + // if ignored_functions.contains(&func.key) { + // return None; + // } + // Some( + // if self + // .webtriggers + // .binary_search_by_key(&func.key, |trigger| trigger.function) + // .is_ok() + // { + // FunctionTy::WebTrigger(func) + // } else { + // FunctionTy::Invokable(func) + // }, + // ) + // }) } } @@ -455,8 +556,8 @@ mod tests { assert_eq!(manifest.app.name, Some("My App")); assert_eq!(manifest.app.id, "my-app"); assert_eq!(manifest.modules.macros.len(), 1); - assert_eq!(manifest.modules.macros[0].info.title, "My Macro"); - assert_eq!(manifest.modules.macros[0].info.key, "my-macro"); + assert_eq!(manifest.modules.macros[0].key, "My Macro"); + assert_eq!(manifest.modules.macros[0].function, "my-macro"); assert_eq!(manifest.modules.functions.len(), 1); assert_eq!( manifest.modules.functions[0], diff --git a/crates/forge_permission_resolver/src/test.rs b/crates/forge_permission_resolver/src/test.rs index e9b8a01e..a0e6ad08 100644 --- a/crates/forge_permission_resolver/src/test.rs +++ b/crates/forge_permission_resolver/src/test.rs @@ -75,4 +75,11 @@ mod tests { assert_eq!(result, expected_permission); } + + + // TODO: Add a test case using a manifest that has a function exposed through both a non user invocable module and a user invocable module + #[test] + fn test_catch_indirect_func_invoke() { + assert_eq!(0, 0); + } } From 70f27637b54ca3fa5b73b44645b8638a3c4aa2e9 Mon Sep 17 00:00:00 2001 From: awang7 Date: Mon, 8 Jan 2024 12:09:16 -0800 Subject: [PATCH 245/517] resolving merge conflicts for rebase --- crates/forge_loader/src/manifest.rs | 221 ++++++++++++++----- crates/forge_permission_resolver/src/test.rs | 7 + 2 files changed, 168 insertions(+), 60 deletions(-) diff --git a/crates/forge_loader/src/manifest.rs b/crates/forge_loader/src/manifest.rs index 8cf22a68..1ef7ca6d 100644 --- a/crates/forge_loader/src/manifest.rs +++ b/crates/forge_loader/src/manifest.rs @@ -9,6 +9,7 @@ use crate::{forgepermissions::ForgePermissions, Error}; use forge_utils::FxHashMap; use itertools::{Either, Itertools}; use serde::Deserialize; +use serde_json::map::Entry; use tracing::trace; #[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] @@ -16,7 +17,7 @@ struct AuthProviders<'a> { #[serde(borrow)] auth: Vec<&'a str>, } -// Maps the Functions Module in common Modules +// Maps the Functions Module in common Modules #[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] pub struct FunctionMod<'a> { key: &'a str, @@ -25,15 +26,15 @@ pub struct FunctionMod<'a> { providers: Option>, } -// Modified +// Modified #[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] struct ModInfo<'a> { function: &'a str, } #[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] -struct MacroMod<'a> { - #[serde(flatten, borrow)] +struct MacroMod<'a> { + #[serde(flatten, borrow)] key: &'a str, function: &'a str, resolver: ModInfo<'a>, @@ -41,14 +42,14 @@ struct MacroMod<'a> { export: ModInfo<'a>, } -// WebTrigger => RawTrigger; WHY IS THIS NAMED DIFFERENTLY !? WHO CHANGED NAMES +// WebTrigger => RawTrigger; WHY IS THIS NAMED DIFFERENTLY !? WHO CHANGED NAMES #[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] struct RawTrigger<'a> { key: &'a str, function: &'a str, } -// Trigger => EventTriger; WHY IS THIS NAMED DIFFERENTLY !? WHO CHANGED NAMES +// Trigger => EventTriger; WHY IS THIS NAMED DIFFERENTLY !? WHO CHANGED NAMES #[derive(Debug, Clone, PartialEq, Eq, Deserialize)] struct EventTrigger<'a> { #[serde(flatten, borrow)] @@ -65,7 +66,7 @@ enum Interval { Week, } -// Thank you to whomeever kept this one the same. T.T +// Thank you to whomeever kept this one the same. T.T #[derive(Debug, Clone, PartialEq, Eq, Deserialize)] struct ScheduledTrigger<'a> { #[serde(flatten, borrow)] @@ -75,7 +76,7 @@ struct ScheduledTrigger<'a> { // compass DataProvider module #[derive(Debug, Clone, PartialEq, Eq, Deserialize)] -struct DataProvider<'a> { +pub struct DataProvider<'a> { key: &'a str, #[serde(flatten, borrow)] callback: Callback<'a>, @@ -89,21 +90,21 @@ pub struct Callback<'a> { // Struct for Custom field Module. Check that search suggestion gets read in correctly. #[derive(Debug, Clone, PartialEq, Eq, Deserialize)] -struct CustomField<'a> { +pub struct CustomField<'a> { #[serde(flatten, borrow)] key: &'a str, search_suggestion: &'a str, } #[derive(Debug, Clone, PartialEq, Eq, Deserialize)] -struct UiModificatons<'a> { +pub struct UiModificatons<'a> { #[serde(flatten, borrow)] key: &'a str, resolver: Callback<'a>, } #[derive(Debug, Clone, PartialEq, Eq, Deserialize)] -struct WorkflowValidator<'a> { +pub struct WorkflowValidator<'a> { #[serde(flatten, borrow)] key: &'a str, functon: &'a str, @@ -111,7 +112,7 @@ struct WorkflowValidator<'a> { } #[derive(Debug, Clone, PartialEq, Eq, Deserialize)] -struct WorkflowPostFunction<'a> { +pub struct WorkflowPostFunction<'a> { #[serde(flatten, borrow)] key: &'a str, function: &'a str, @@ -232,9 +233,9 @@ pub enum FunctionTy { } // Struct used for tracking what scan a funtion requires. -#[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] +#[derive(Debug, Clone, PartialEq, Eq, Deserialize)] pub struct Entrypoints<'a> { - function: Vec>, + function: &'a str, invokable: bool, web_trigger: bool, } @@ -276,70 +277,170 @@ impl AsRef for FunctionTy { } impl<'a> ForgeModules<'a> { - pub fn into_analyzable_functions( - mut self, - ) -> impl Iterator>> { - //FIXME: The logic here is incorrect, a function should be in the ignored function IIF it - //is not invoked by a user-invocable module. - + // TODO: fix return type whop + pub fn into_analyzable_functions(mut self) -> impl Iterator> { // number of webtriggers are usually low, so it's better to just sort them and reuse self.webtriggers .sort_unstable_by_key(|trigger| trigger.function); - let mut ignored_functions: BTreeSet<_> = self + // Get all the Triggers and represent them as a new struct thing where "webtrigger" attribute is true + // for all trigger things + let web = self.webtriggers.iter().for_each(|webtriggers| { + Entrypoints { + function: webtriggers.function, + invokable: false, + web_trigger: true, + }; + }); + let event = self.event_triggers.iter().for_each(|event_triggers| { + Entrypoints { + function: event_triggers.raw.function, + invokable: false, + web_trigger: true, + }; + }); + let schedule = self .scheduled_triggers - .into_iter() - .map(|trigger| trigger.raw.function) - .chain( - self.event_triggers - .into_iter() - .map(|trigger| trigger.raw.function), - ) - .chain( - self.workflow_post_function - .into_iter() - .map(|pf| pf.function), - ) - .collect(); + .iter() + .for_each(|schedule_triggers| { + Entrypoints { + function: schedule_triggers.raw.function, + invokable: false, + web_trigger: true, + }; + }); + + // create arrays representing functions that expose user non-invokable functions + let consumer = self.consumers.iter().for_each(|consumers| { + Entrypoints { + function: consumers.resolver.function, + invokable: true, + web_trigger: false, + }; + }); + + let data_provider = self.data_provider.iter().for_each(|dataprovider| { + Entrypoints { + function: dataprovider.callback.function, + invokable: true, + web_trigger: false, + }; + }); + + let custom_field = self.custom_field.iter().for_each(|customfield| { + Entrypoints { + function: customfield.search_suggestion, + invokable: true, + web_trigger: false, + }; + }); + let ui_mod = self.ui_modifications.iter().for_each(|ui| { + Entrypoints { + function: ui.resolver.function, + invokable: true, + web_trigger: false, + }; + }); + + let workflow_validator = self.workflow_validator.iter().for_each(|validator| { + Entrypoints { + function: validator.resolver.function, + invokable: true, + web_trigger: false, + }; + }); + + let workflow_post = self + .workflow_post_function + .iter() + .for_each(|post_function| { + Entrypoints { + function: post_function.function, + invokable: true, + web_trigger: false, + }; + }); + + // let user_invokable = self.extra.into_values().flatten().into_iter().for_each(|invokable| { + + // if invokable.resolver != None { + // Entrypoints { + // function: invokable.resolver, + // invokable: true, + // web_trigger: false, + // }; + + // } + // Entrypoints { + // function: invokable.function, + // invokable: true, + // web_trigger: false, + // }; + // }); + + // let mut ignored_functions: BTreeSet<_> = self + // .scheduled_triggers + // .into_iter() + // .map(|trigger| trigger.raw.function) + // .chain( + // self.event_triggers + // .into_iter() + // .map(|trigger| trigger.raw.function), + // ) + // .collect(); + + // get array for user invokable module functions // make alternate_functions all user-invokable functions - let mut alternate_functions: Vec<&str> = Vec::new(); + let mut alternate_functions = Vec::new(); for module in self.extra.into_values().flatten() { - alternate_functions.extend(module.function); + if let Some(mod_function) = module.function { + alternate_functions.push(Entrypoints { + function: mod_function, + invokable: true, + web_trigger: false, + }); + } + if let Some(resolver) = module.resolver { - alternate_functions.push(resolver.function); + alternate_functions.push(Entrypoints { + function: resolver.function, + invokable: true, + web_trigger: false, + }); } } + workflow_post // Iterate over Consumers and check that if consumers isn't in alternate functions, add consumer funtion to be ignored // assuming that alternate functions already has all user invokable functions. - self.consumers.iter().for_each(|consumer| { - if !alternate_functions.contains(&consumer.resolver.function) { - ignored_functions.insert(consumer.resolver.function); - } - }); + // self.consumers.iter().for_each(|consumer| { + // if !alternate_functions.contains(&consumer.resolver.function) { + // ignored_functions.insert(consumer.resolver.function); + // } + // }); // TODO: Iterate through all deserialized entrypoints that are represented as a struct when deserialized // Update Struct values to be true or not. If any part true, then scan. // This solution fixes the problem that we only check known user invokable modules and also acccounts for non-invokable module entry points // return non-user invokable functions - self.functions.into_iter().filter_map(move |func| { - if ignored_functions.contains(&func.key) { - return None; - } - Some( - if self - .webtriggers - .binary_search_by_key(&func.key, |trigger| trigger.function) - .is_ok() - { - FunctionTy::WebTrigger(func) - } else { - FunctionTy::Invokable(func) - }, - ) - }) + // self.functions.into_iter().filter_map(move |func| { + // if ignored_functions.contains(&func.key) { + // return None; + // } + // Some( + // if self + // .webtriggers + // .binary_search_by_key(&func.key, |trigger| trigger.function) + // .is_ok() + // { + // FunctionTy::WebTrigger(func) + // } else { + // FunctionTy::Invokable(func) + // }, + // ) + // }) } } @@ -455,8 +556,8 @@ mod tests { assert_eq!(manifest.app.name, Some("My App")); assert_eq!(manifest.app.id, "my-app"); assert_eq!(manifest.modules.macros.len(), 1); - assert_eq!(manifest.modules.macros[0].info.title, "My Macro"); - assert_eq!(manifest.modules.macros[0].info.key, "my-macro"); + assert_eq!(manifest.modules.macros[0].key, "My Macro"); + assert_eq!(manifest.modules.macros[0].function, "my-macro"); assert_eq!(manifest.modules.functions.len(), 1); assert_eq!( manifest.modules.functions[0], diff --git a/crates/forge_permission_resolver/src/test.rs b/crates/forge_permission_resolver/src/test.rs index e9b8a01e..a0e6ad08 100644 --- a/crates/forge_permission_resolver/src/test.rs +++ b/crates/forge_permission_resolver/src/test.rs @@ -75,4 +75,11 @@ mod tests { assert_eq!(result, expected_permission); } + + + // TODO: Add a test case using a manifest that has a function exposed through both a non user invocable module and a user invocable module + #[test] + fn test_catch_indirect_func_invoke() { + assert_eq!(0, 0); + } } From a9894c35f9c50e9fae46bc2985b071c32a5507c5 Mon Sep 17 00:00:00 2001 From: awang7 Date: Mon, 8 Jan 2024 12:10:04 -0800 Subject: [PATCH 246/517] resolving merge conflicts --- crates/forge_loader/src/manifest.rs | 76 ++++++++++++++--------------- 1 file changed, 38 insertions(+), 38 deletions(-) diff --git a/crates/forge_loader/src/manifest.rs b/crates/forge_loader/src/manifest.rs index 1ef7ca6d..7cc0b617 100644 --- a/crates/forge_loader/src/manifest.rs +++ b/crates/forge_loader/src/manifest.rs @@ -277,89 +277,89 @@ impl AsRef for FunctionTy { } impl<'a> ForgeModules<'a> { - // TODO: fix return type whop - pub fn into_analyzable_functions(mut self) -> impl Iterator> { + // TODO: function returns iterator where each item is some specified type. + pub fn into_analyzable_functions(mut self) { // number of webtriggers are usually low, so it's better to just sort them and reuse - self.webtriggers - .sort_unstable_by_key(|trigger| trigger.function); + // self.webtriggers + // .sort_unstable_by_key(|trigger| trigger.function); // Get all the Triggers and represent them as a new struct thing where "webtrigger" attribute is true // for all trigger things - let web = self.webtriggers.iter().for_each(|webtriggers| { - Entrypoints { + + let mut functions_to_scan = Vec::new(); + self.webtriggers.iter().for_each(|webtriggers| { + functions_to_scan.push(Entrypoints { function: webtriggers.function, invokable: false, web_trigger: true, - }; + }); }); - let event = self.event_triggers.iter().for_each(|event_triggers| { - Entrypoints { + self.event_triggers.iter().for_each(|event_triggers| { + functions_to_scan.push(Entrypoints { function: event_triggers.raw.function, invokable: false, web_trigger: true, - }; + }); }); - let schedule = self - .scheduled_triggers + self.scheduled_triggers .iter() .for_each(|schedule_triggers| { - Entrypoints { + functions_to_scan.push(Entrypoints { function: schedule_triggers.raw.function, invokable: false, web_trigger: true, - }; + }) }); // create arrays representing functions that expose user non-invokable functions - let consumer = self.consumers.iter().for_each(|consumers| { - Entrypoints { + self.consumers.iter().for_each(|consumers| { + functions_to_scan.push(Entrypoints { function: consumers.resolver.function, invokable: true, web_trigger: false, - }; + }) }); - let data_provider = self.data_provider.iter().for_each(|dataprovider| { - Entrypoints { + self.data_provider.iter().for_each(|dataprovider| { + functions_to_scan.push(Entrypoints { function: dataprovider.callback.function, invokable: true, web_trigger: false, - }; + }) }); - let custom_field = self.custom_field.iter().for_each(|customfield| { - Entrypoints { + self.custom_field.iter().for_each(|customfield| { + functions_to_scan.push(Entrypoints { function: customfield.search_suggestion, invokable: true, web_trigger: false, - }; + }) }); - let ui_mod = self.ui_modifications.iter().for_each(|ui| { - Entrypoints { + self.ui_modifications.iter().for_each(|ui| { + functions_to_scan.push(Entrypoints { function: ui.resolver.function, invokable: true, web_trigger: false, - }; + }) }); - let workflow_validator = self.workflow_validator.iter().for_each(|validator| { - Entrypoints { + self.workflow_validator.iter().for_each(|validator| { + functions_to_scan.push(Entrypoints { function: validator.resolver.function, invokable: true, web_trigger: false, - }; + }) }); - let workflow_post = self - .workflow_post_function + self.workflow_post_function .iter() .for_each(|post_function| { - Entrypoints { + functions_to_scan.push(Entrypoints { function: post_function.function, invokable: true, web_trigger: false, - }; + }) }); // let user_invokable = self.extra.into_values().flatten().into_iter().for_each(|invokable| { @@ -392,10 +392,10 @@ impl<'a> ForgeModules<'a> { // get array for user invokable module functions // make alternate_functions all user-invokable functions - let mut alternate_functions = Vec::new(); + // let mut alternate_functions = Vec::new(); for module in self.extra.into_values().flatten() { if let Some(mod_function) = module.function { - alternate_functions.push(Entrypoints { + functions_to_scan.push(Entrypoints { function: mod_function, invokable: true, web_trigger: false, @@ -403,15 +403,15 @@ impl<'a> ForgeModules<'a> { } if let Some(resolver) = module.resolver { - alternate_functions.push(Entrypoints { + functions_to_scan.push(Entrypoints { function: resolver.function, invokable: true, web_trigger: false, }); } } - - workflow_post + functions_to_scan.into_iter(); + // alternate_functions.into_iter(); // Iterate over Consumers and check that if consumers isn't in alternate functions, add consumer funtion to be ignored // assuming that alternate functions already has all user invokable functions. // self.consumers.iter().for_each(|consumer| { From 2cee0de218810f5b660b8d302c8956d54a9c46d8 Mon Sep 17 00:00:00 2001 From: awang7 Date: Mon, 8 Jan 2024 12:10:04 -0800 Subject: [PATCH 247/517] resolving merge conflicts --- crates/forge_loader/src/manifest.rs | 76 ++++++++++++++--------------- 1 file changed, 38 insertions(+), 38 deletions(-) diff --git a/crates/forge_loader/src/manifest.rs b/crates/forge_loader/src/manifest.rs index 1ef7ca6d..7cc0b617 100644 --- a/crates/forge_loader/src/manifest.rs +++ b/crates/forge_loader/src/manifest.rs @@ -277,89 +277,89 @@ impl AsRef for FunctionTy { } impl<'a> ForgeModules<'a> { - // TODO: fix return type whop - pub fn into_analyzable_functions(mut self) -> impl Iterator> { + // TODO: function returns iterator where each item is some specified type. + pub fn into_analyzable_functions(mut self) { // number of webtriggers are usually low, so it's better to just sort them and reuse - self.webtriggers - .sort_unstable_by_key(|trigger| trigger.function); + // self.webtriggers + // .sort_unstable_by_key(|trigger| trigger.function); // Get all the Triggers and represent them as a new struct thing where "webtrigger" attribute is true // for all trigger things - let web = self.webtriggers.iter().for_each(|webtriggers| { - Entrypoints { + + let mut functions_to_scan = Vec::new(); + self.webtriggers.iter().for_each(|webtriggers| { + functions_to_scan.push(Entrypoints { function: webtriggers.function, invokable: false, web_trigger: true, - }; + }); }); - let event = self.event_triggers.iter().for_each(|event_triggers| { - Entrypoints { + self.event_triggers.iter().for_each(|event_triggers| { + functions_to_scan.push(Entrypoints { function: event_triggers.raw.function, invokable: false, web_trigger: true, - }; + }); }); - let schedule = self - .scheduled_triggers + self.scheduled_triggers .iter() .for_each(|schedule_triggers| { - Entrypoints { + functions_to_scan.push(Entrypoints { function: schedule_triggers.raw.function, invokable: false, web_trigger: true, - }; + }) }); // create arrays representing functions that expose user non-invokable functions - let consumer = self.consumers.iter().for_each(|consumers| { - Entrypoints { + self.consumers.iter().for_each(|consumers| { + functions_to_scan.push(Entrypoints { function: consumers.resolver.function, invokable: true, web_trigger: false, - }; + }) }); - let data_provider = self.data_provider.iter().for_each(|dataprovider| { - Entrypoints { + self.data_provider.iter().for_each(|dataprovider| { + functions_to_scan.push(Entrypoints { function: dataprovider.callback.function, invokable: true, web_trigger: false, - }; + }) }); - let custom_field = self.custom_field.iter().for_each(|customfield| { - Entrypoints { + self.custom_field.iter().for_each(|customfield| { + functions_to_scan.push(Entrypoints { function: customfield.search_suggestion, invokable: true, web_trigger: false, - }; + }) }); - let ui_mod = self.ui_modifications.iter().for_each(|ui| { - Entrypoints { + self.ui_modifications.iter().for_each(|ui| { + functions_to_scan.push(Entrypoints { function: ui.resolver.function, invokable: true, web_trigger: false, - }; + }) }); - let workflow_validator = self.workflow_validator.iter().for_each(|validator| { - Entrypoints { + self.workflow_validator.iter().for_each(|validator| { + functions_to_scan.push(Entrypoints { function: validator.resolver.function, invokable: true, web_trigger: false, - }; + }) }); - let workflow_post = self - .workflow_post_function + self.workflow_post_function .iter() .for_each(|post_function| { - Entrypoints { + functions_to_scan.push(Entrypoints { function: post_function.function, invokable: true, web_trigger: false, - }; + }) }); // let user_invokable = self.extra.into_values().flatten().into_iter().for_each(|invokable| { @@ -392,10 +392,10 @@ impl<'a> ForgeModules<'a> { // get array for user invokable module functions // make alternate_functions all user-invokable functions - let mut alternate_functions = Vec::new(); + // let mut alternate_functions = Vec::new(); for module in self.extra.into_values().flatten() { if let Some(mod_function) = module.function { - alternate_functions.push(Entrypoints { + functions_to_scan.push(Entrypoints { function: mod_function, invokable: true, web_trigger: false, @@ -403,15 +403,15 @@ impl<'a> ForgeModules<'a> { } if let Some(resolver) = module.resolver { - alternate_functions.push(Entrypoints { + functions_to_scan.push(Entrypoints { function: resolver.function, invokable: true, web_trigger: false, }); } } - - workflow_post + functions_to_scan.into_iter(); + // alternate_functions.into_iter(); // Iterate over Consumers and check that if consumers isn't in alternate functions, add consumer funtion to be ignored // assuming that alternate functions already has all user invokable functions. // self.consumers.iter().for_each(|consumer| { From 4d4f789ed04a57611f5babcd469c8b92cd95b2c4 Mon Sep 17 00:00:00 2001 From: awang7 Date: Mon, 8 Jan 2024 12:10:31 -0800 Subject: [PATCH 248/517] resolving merge conflicts --- crates/forge_loader/src/manifest.rs | 53 ++++++++++++++--------------- 1 file changed, 26 insertions(+), 27 deletions(-) diff --git a/crates/forge_loader/src/manifest.rs b/crates/forge_loader/src/manifest.rs index 7cc0b617..52384ffe 100644 --- a/crates/forge_loader/src/manifest.rs +++ b/crates/forge_loader/src/manifest.rs @@ -278,7 +278,7 @@ impl AsRef for FunctionTy { impl<'a> ForgeModules<'a> { // TODO: function returns iterator where each item is some specified type. - pub fn into_analyzable_functions(mut self) { + pub fn into_analyzable_functions(self) { // number of webtriggers are usually low, so it's better to just sort them and reuse // self.webtriggers // .sort_unstable_by_key(|trigger| trigger.function); @@ -362,33 +362,32 @@ impl<'a> ForgeModules<'a> { }) }); - // let user_invokable = self.extra.into_values().flatten().into_iter().for_each(|invokable| { - - // if invokable.resolver != None { - // Entrypoints { - // function: invokable.resolver, - // invokable: true, - // web_trigger: false, - // }; - - // } - // Entrypoints { - // function: invokable.function, - // invokable: true, - // web_trigger: false, - // }; - // }); + // get user invokable modules that have additional exposure endpoints. + // ie macros has config and export fields on top of resolver fields that are functions + for macros in self.macros { + if let Some(resolver) = Some(macros.resolver) { + functions_to_scan.push(Entrypoints { + function: resolver.function, + invokable: true, + web_trigger: false, + }) + } - // let mut ignored_functions: BTreeSet<_> = self - // .scheduled_triggers - // .into_iter() - // .map(|trigger| trigger.raw.function) - // .chain( - // self.event_triggers - // .into_iter() - // .map(|trigger| trigger.raw.function), - // ) - // .collect(); + if let Some(config) = Some(macros.config) { + functions_to_scan.push(Entrypoints { + function: config.function, + invokable: true, + web_trigger: false, + }) + } + if let Some(export) = Some(macros.export) { + functions_to_scan.push(Entrypoints { + function: export.function, + invokable: true, + web_trigger: false, + }) + } + } // get array for user invokable module functions // make alternate_functions all user-invokable functions From 391b65715856cc4bf88713941a4f4c2fa18ac3ff Mon Sep 17 00:00:00 2001 From: awang7 Date: Mon, 8 Jan 2024 12:10:31 -0800 Subject: [PATCH 249/517] resolving merge conflicts --- crates/forge_loader/src/manifest.rs | 53 ++++++++++++++--------------- 1 file changed, 26 insertions(+), 27 deletions(-) diff --git a/crates/forge_loader/src/manifest.rs b/crates/forge_loader/src/manifest.rs index 7cc0b617..52384ffe 100644 --- a/crates/forge_loader/src/manifest.rs +++ b/crates/forge_loader/src/manifest.rs @@ -278,7 +278,7 @@ impl AsRef for FunctionTy { impl<'a> ForgeModules<'a> { // TODO: function returns iterator where each item is some specified type. - pub fn into_analyzable_functions(mut self) { + pub fn into_analyzable_functions(self) { // number of webtriggers are usually low, so it's better to just sort them and reuse // self.webtriggers // .sort_unstable_by_key(|trigger| trigger.function); @@ -362,33 +362,32 @@ impl<'a> ForgeModules<'a> { }) }); - // let user_invokable = self.extra.into_values().flatten().into_iter().for_each(|invokable| { - - // if invokable.resolver != None { - // Entrypoints { - // function: invokable.resolver, - // invokable: true, - // web_trigger: false, - // }; - - // } - // Entrypoints { - // function: invokable.function, - // invokable: true, - // web_trigger: false, - // }; - // }); + // get user invokable modules that have additional exposure endpoints. + // ie macros has config and export fields on top of resolver fields that are functions + for macros in self.macros { + if let Some(resolver) = Some(macros.resolver) { + functions_to_scan.push(Entrypoints { + function: resolver.function, + invokable: true, + web_trigger: false, + }) + } - // let mut ignored_functions: BTreeSet<_> = self - // .scheduled_triggers - // .into_iter() - // .map(|trigger| trigger.raw.function) - // .chain( - // self.event_triggers - // .into_iter() - // .map(|trigger| trigger.raw.function), - // ) - // .collect(); + if let Some(config) = Some(macros.config) { + functions_to_scan.push(Entrypoints { + function: config.function, + invokable: true, + web_trigger: false, + }) + } + if let Some(export) = Some(macros.export) { + functions_to_scan.push(Entrypoints { + function: export.function, + invokable: true, + web_trigger: false, + }) + } + } // get array for user invokable module functions // make alternate_functions all user-invokable functions From 2c90327144fb8b5a1b6260c5177fd087d44d19c2 Mon Sep 17 00:00:00 2001 From: awang7 Date: Mon, 8 Jan 2024 12:10:57 -0800 Subject: [PATCH 250/517] resolving merge conflicts --- crates/forge_loader/src/manifest.rs | 42 ++++++++++++++++++++++------- 1 file changed, 32 insertions(+), 10 deletions(-) diff --git a/crates/forge_loader/src/manifest.rs b/crates/forge_loader/src/manifest.rs index 52384ffe..e7965b72 100644 --- a/crates/forge_loader/src/manifest.rs +++ b/crates/forge_loader/src/manifest.rs @@ -34,12 +34,15 @@ struct ModInfo<'a> { #[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] struct MacroMod<'a> { - #[serde(flatten, borrow)] + // #[serde(flatten, borrow)] key: &'a str, function: &'a str, - resolver: ModInfo<'a>, - config: ModInfo<'a>, - export: ModInfo<'a>, + #[serde(borrow)] + resolver: Option>, + #[serde(borrow)] + config: Option>, + #[serde(borrow)] + export: Option>, } // WebTrigger => RawTrigger; WHY IS THIS NAMED DIFFERENTLY !? WHO CHANGED NAMES @@ -365,7 +368,7 @@ impl<'a> ForgeModules<'a> { // get user invokable modules that have additional exposure endpoints. // ie macros has config and export fields on top of resolver fields that are functions for macros in self.macros { - if let Some(resolver) = Some(macros.resolver) { + if let Some(resolver) = macros.resolver { functions_to_scan.push(Entrypoints { function: resolver.function, invokable: true, @@ -373,14 +376,14 @@ impl<'a> ForgeModules<'a> { }) } - if let Some(config) = Some(macros.config) { + if let Some(config) = macros.config { functions_to_scan.push(Entrypoints { function: config.function, invokable: true, web_trigger: false, }) } - if let Some(export) = Some(macros.export) { + if let Some(export) = macros.export { functions_to_scan.push(Entrypoints { function: export.function, invokable: true, @@ -518,7 +521,7 @@ mod tests { "macro": [ { "key": "my-macro", - "title": "My Macro" + "function": "My Macro" } ], "function": [ @@ -604,11 +607,15 @@ mod tests { { "key": "my-macro", "title": "My Macro", + "function": "Catch-me-if-you-can0", "resolver": { - "function": Catch-me-if-you-can1 + "function": "Catch-me-if-you-can1" }, "config": { - "function": Catch-me-if-you-can2 + "function": "Catch-me-if-you-can2" + }, + "export": { + "function": "Catch-me-if-you-can3" } } ], @@ -642,5 +649,20 @@ mod tests { } } }"#; + let manifest: ForgeManifest = serde_json::from_str(json).unwrap(); + assert_eq!(manifest.modules.macros.len(), 1); + assert_eq!(manifest.modules.macros[0].function, "Catch-me-if-you-can0"); + + if let Some(func) = &manifest.modules.macros[0].resolver { + assert_eq!(func.function, "Catch-me-if-you-can1"); + } + + if let Some(func) = &manifest.modules.macros[0].config { + assert_eq!(func.function, "Catch-me-if-you-can2"); + } + + if let Some(func) = &manifest.modules.macros[0].export { + assert_eq!(func.function, "Catch-me-if-you-can3"); + } } } From 796a3205069f40ceaaad08ec1d10fe7a7034fc53 Mon Sep 17 00:00:00 2001 From: awang7 Date: Mon, 8 Jan 2024 12:10:57 -0800 Subject: [PATCH 251/517] resolving merge conflicts --- crates/forge_loader/src/manifest.rs | 42 ++++++++++++++++++++++------- 1 file changed, 32 insertions(+), 10 deletions(-) diff --git a/crates/forge_loader/src/manifest.rs b/crates/forge_loader/src/manifest.rs index 52384ffe..e7965b72 100644 --- a/crates/forge_loader/src/manifest.rs +++ b/crates/forge_loader/src/manifest.rs @@ -34,12 +34,15 @@ struct ModInfo<'a> { #[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] struct MacroMod<'a> { - #[serde(flatten, borrow)] + // #[serde(flatten, borrow)] key: &'a str, function: &'a str, - resolver: ModInfo<'a>, - config: ModInfo<'a>, - export: ModInfo<'a>, + #[serde(borrow)] + resolver: Option>, + #[serde(borrow)] + config: Option>, + #[serde(borrow)] + export: Option>, } // WebTrigger => RawTrigger; WHY IS THIS NAMED DIFFERENTLY !? WHO CHANGED NAMES @@ -365,7 +368,7 @@ impl<'a> ForgeModules<'a> { // get user invokable modules that have additional exposure endpoints. // ie macros has config and export fields on top of resolver fields that are functions for macros in self.macros { - if let Some(resolver) = Some(macros.resolver) { + if let Some(resolver) = macros.resolver { functions_to_scan.push(Entrypoints { function: resolver.function, invokable: true, @@ -373,14 +376,14 @@ impl<'a> ForgeModules<'a> { }) } - if let Some(config) = Some(macros.config) { + if let Some(config) = macros.config { functions_to_scan.push(Entrypoints { function: config.function, invokable: true, web_trigger: false, }) } - if let Some(export) = Some(macros.export) { + if let Some(export) = macros.export { functions_to_scan.push(Entrypoints { function: export.function, invokable: true, @@ -518,7 +521,7 @@ mod tests { "macro": [ { "key": "my-macro", - "title": "My Macro" + "function": "My Macro" } ], "function": [ @@ -604,11 +607,15 @@ mod tests { { "key": "my-macro", "title": "My Macro", + "function": "Catch-me-if-you-can0", "resolver": { - "function": Catch-me-if-you-can1 + "function": "Catch-me-if-you-can1" }, "config": { - "function": Catch-me-if-you-can2 + "function": "Catch-me-if-you-can2" + }, + "export": { + "function": "Catch-me-if-you-can3" } } ], @@ -642,5 +649,20 @@ mod tests { } } }"#; + let manifest: ForgeManifest = serde_json::from_str(json).unwrap(); + assert_eq!(manifest.modules.macros.len(), 1); + assert_eq!(manifest.modules.macros[0].function, "Catch-me-if-you-can0"); + + if let Some(func) = &manifest.modules.macros[0].resolver { + assert_eq!(func.function, "Catch-me-if-you-can1"); + } + + if let Some(func) = &manifest.modules.macros[0].config { + assert_eq!(func.function, "Catch-me-if-you-can2"); + } + + if let Some(func) = &manifest.modules.macros[0].export { + assert_eq!(func.function, "Catch-me-if-you-can3"); + } } } From c0efb3f5ccaa9eef6c7e329eb9de2b03f05d7c76 Mon Sep 17 00:00:00 2001 From: awang7 Date: Mon, 8 Jan 2024 12:11:22 -0800 Subject: [PATCH 252/517] resolving merge conflicts --- crates/forge_loader/src/manifest.rs | 42 +++------------- crates/fsrt/src/main.rs | 76 +++++++++++++++++++++++++---- 2 files changed, 73 insertions(+), 45 deletions(-) diff --git a/crates/forge_loader/src/manifest.rs b/crates/forge_loader/src/manifest.rs index e7965b72..bc19201d 100644 --- a/crates/forge_loader/src/manifest.rs +++ b/crates/forge_loader/src/manifest.rs @@ -238,9 +238,9 @@ pub enum FunctionTy { // Struct used for tracking what scan a funtion requires. #[derive(Debug, Clone, PartialEq, Eq, Deserialize)] pub struct Entrypoints<'a> { - function: &'a str, - invokable: bool, - web_trigger: bool, + pub function: &'a str, + pub invokable: bool, + pub web_trigger: bool, } // Helper functions that help filter out which functions are what. @@ -281,7 +281,7 @@ impl AsRef for FunctionTy { impl<'a> ForgeModules<'a> { // TODO: function returns iterator where each item is some specified type. - pub fn into_analyzable_functions(self) { + pub fn into_analyzable_functions(self) -> Vec> { // number of webtriggers are usually low, so it's better to just sort them and reuse // self.webtriggers // .sort_unstable_by_key(|trigger| trigger.function); @@ -394,7 +394,6 @@ impl<'a> ForgeModules<'a> { // get array for user invokable module functions // make alternate_functions all user-invokable functions - // let mut alternate_functions = Vec::new(); for module in self.extra.into_values().flatten() { if let Some(mod_function) = module.function { functions_to_scan.push(Entrypoints { @@ -412,37 +411,8 @@ impl<'a> ForgeModules<'a> { }); } } - functions_to_scan.into_iter(); - // alternate_functions.into_iter(); - // Iterate over Consumers and check that if consumers isn't in alternate functions, add consumer funtion to be ignored - // assuming that alternate functions already has all user invokable functions. - // self.consumers.iter().for_each(|consumer| { - // if !alternate_functions.contains(&consumer.resolver.function) { - // ignored_functions.insert(consumer.resolver.function); - // } - // }); - - // TODO: Iterate through all deserialized entrypoints that are represented as a struct when deserialized - // Update Struct values to be true or not. If any part true, then scan. - // This solution fixes the problem that we only check known user invokable modules and also acccounts for non-invokable module entry points - - // return non-user invokable functions - // self.functions.into_iter().filter_map(move |func| { - // if ignored_functions.contains(&func.key) { - // return None; - // } - // Some( - // if self - // .webtriggers - // .binary_search_by_key(&func.key, |trigger| trigger.function) - // .is_ok() - // { - // FunctionTy::WebTrigger(func) - // } else { - // FunctionTy::Invokable(func) - // }, - // ) - // }) + + return functions_to_scan; } } diff --git a/crates/fsrt/src/main.rs b/crates/fsrt/src/main.rs index b1281be5..d649b2f8 100644 --- a/crates/fsrt/src/main.rs +++ b/crates/fsrt/src/main.rs @@ -13,6 +13,13 @@ use std::{ sync::Arc, }; +<<<<<<< HEAD +======= +use clap::{Parser, ValueHint}; +use miette::{IntoDiagnostic, Result}; + +use serde_json::map::Entry; +>>>>>>> cd2ed7b (edited main to iterate over vector of functions. TODO: edit add_funcs in main.rs and test into_analyzble_function use case in main.rs) use swc_core::{ common::{Globals, Mark, SourceMap, GLOBALS}, ecma::{ @@ -38,7 +45,7 @@ use forge_analyzer::{ resolver::resolve_calls, }; -use forge_loader::manifest::{ForgeManifest, FunctionRef, FunctionTy}; +use forge_loader::manifest::{ForgeManifest, FunctionRef, FunctionTy, Entrypoints}; use walkdir::WalkDir; #[derive(Parser, Debug)] @@ -86,7 +93,12 @@ struct ForgeProject { sm: Arc, ctx: AppCtx, env: Environment, +<<<<<<< HEAD funcs: Vec>, +======= + funcs: Vec>, + opts: Opts, +>>>>>>> cd2ed7b (edited main to iterate over vector of functions. TODO: edit add_funcs in main.rs and test into_analyzble_function use case in main.rs) } impl ForgeProject { @@ -136,8 +148,8 @@ impl ForgeProject { funcs: vec![], } } - - fn add_funcs<'a, I: IntoIterator>>(&mut self, iter: I) { +// TODO: edit to work with new iterator that not FUNCTIONTY + fn add_funcs<'a, I: IntoIterator>>(&mut self, iter: I) { self.funcs.extend(iter.into_iter().flat_map(|ftype| { ftype.sequence(|(func_name, path)| { let modid = self.ctx.modid_from_path(&path)?; @@ -206,12 +218,14 @@ fn scan_directory(dir: PathBuf, function: Option<&str>, opts: &Args) -> Result(resolved_func.into_func_path()) - }) - }); + let funcrefs = &manifest.modules.into_analyzable_functions(); + + // .flat_map(|f| { + // f.sequence(|fmod| { + // let resolved_func = FunctionRef::try_from(fmod)?.try_resolve(&paths, &dir)?; + // Ok::<_, forge_loader::Error>(resolved_func.into_func_path()) + // }) + // }); let src_root = dir.join("src"); let mut proj = ForgeProject::with_files_and_sourceroot(src_root, paths.clone(), secret_packages); @@ -300,6 +314,7 @@ fn scan_directory(dir: PathBuf, function: Option<&str>, opts: &Args) -> Result { let mut runner = DefintionAnalysisRunner::new(); @@ -413,7 +428,50 @@ fn scan_directory(dir: PathBuf, function: Option<&str>, opts: &Args) -> Result { + // let mut checker = AuthZChecker::new(); + // debug!("checking {func} at {path:?}"); + // if let Err(err) = interp.run_checker(def, &mut checker, path.clone(), func.clone()) + // { + // warn!("error while scanning {func} in {path:?}: {err}"); + // } + // reporter.add_vulnerabilities(checker.into_vulns()); + // } + // FunctionTy::WebTrigger((ref func, ref path, _, def)) => { + // let mut checker = AuthenticateChecker::new(); + // debug!("checking webtrigger {func} at {path:?}"); + // if let Err(err) = + // authn_interp.run_checker(def, &mut checker, path.clone(), func.clone()) + // { + // warn!("error while scanning {func} in {path:?}: {err}"); + // } + // reporter.add_vulnerabilities(checker.into_vulns()); + // } + // } + + if func.invokable { + let mut checker = AuthZChecker::new(); + debug!("checking {func} at {path:?}"); + if let Err(err) = interp.run_checker(def, &mut checker, path.clone(), func.clone()) + { + warn!("error while scanning {func} in {path:?}: {err}"); + } + reporter.add_vulnerabilities(checker.into_vulns()); + + } else if func.web_trigger { + let mut checker = AuthenticateChecker::new(); + debug!("checking webtrigger {func} at {path:?}"); + if let Err(err) = + authn_interp.run_checker(def, &mut checker, path.clone(), func.clone()) + { + warn!("error while scanning {func} in {path:?}: {err}"); +>>>>>>> cd2ed7b (edited main to iterate over vector of functions. TODO: edit add_funcs in main.rs and test into_analyzble_function use case in main.rs) } + reporter.add_vulnerabilities(checker.into_vulns()); + + } } From 931e7f3c49c8351065106c683b08e461e08e6130 Mon Sep 17 00:00:00 2001 From: awang7 Date: Mon, 8 Jan 2024 12:11:22 -0800 Subject: [PATCH 253/517] resolving merge conflicts --- crates/forge_loader/src/manifest.rs | 42 +++------------- crates/fsrt/src/main.rs | 76 +++++++++++++++++++++++++---- 2 files changed, 73 insertions(+), 45 deletions(-) diff --git a/crates/forge_loader/src/manifest.rs b/crates/forge_loader/src/manifest.rs index e7965b72..bc19201d 100644 --- a/crates/forge_loader/src/manifest.rs +++ b/crates/forge_loader/src/manifest.rs @@ -238,9 +238,9 @@ pub enum FunctionTy { // Struct used for tracking what scan a funtion requires. #[derive(Debug, Clone, PartialEq, Eq, Deserialize)] pub struct Entrypoints<'a> { - function: &'a str, - invokable: bool, - web_trigger: bool, + pub function: &'a str, + pub invokable: bool, + pub web_trigger: bool, } // Helper functions that help filter out which functions are what. @@ -281,7 +281,7 @@ impl AsRef for FunctionTy { impl<'a> ForgeModules<'a> { // TODO: function returns iterator where each item is some specified type. - pub fn into_analyzable_functions(self) { + pub fn into_analyzable_functions(self) -> Vec> { // number of webtriggers are usually low, so it's better to just sort them and reuse // self.webtriggers // .sort_unstable_by_key(|trigger| trigger.function); @@ -394,7 +394,6 @@ impl<'a> ForgeModules<'a> { // get array for user invokable module functions // make alternate_functions all user-invokable functions - // let mut alternate_functions = Vec::new(); for module in self.extra.into_values().flatten() { if let Some(mod_function) = module.function { functions_to_scan.push(Entrypoints { @@ -412,37 +411,8 @@ impl<'a> ForgeModules<'a> { }); } } - functions_to_scan.into_iter(); - // alternate_functions.into_iter(); - // Iterate over Consumers and check that if consumers isn't in alternate functions, add consumer funtion to be ignored - // assuming that alternate functions already has all user invokable functions. - // self.consumers.iter().for_each(|consumer| { - // if !alternate_functions.contains(&consumer.resolver.function) { - // ignored_functions.insert(consumer.resolver.function); - // } - // }); - - // TODO: Iterate through all deserialized entrypoints that are represented as a struct when deserialized - // Update Struct values to be true or not. If any part true, then scan. - // This solution fixes the problem that we only check known user invokable modules and also acccounts for non-invokable module entry points - - // return non-user invokable functions - // self.functions.into_iter().filter_map(move |func| { - // if ignored_functions.contains(&func.key) { - // return None; - // } - // Some( - // if self - // .webtriggers - // .binary_search_by_key(&func.key, |trigger| trigger.function) - // .is_ok() - // { - // FunctionTy::WebTrigger(func) - // } else { - // FunctionTy::Invokable(func) - // }, - // ) - // }) + + return functions_to_scan; } } diff --git a/crates/fsrt/src/main.rs b/crates/fsrt/src/main.rs index b1281be5..d649b2f8 100644 --- a/crates/fsrt/src/main.rs +++ b/crates/fsrt/src/main.rs @@ -13,6 +13,13 @@ use std::{ sync::Arc, }; +<<<<<<< HEAD +======= +use clap::{Parser, ValueHint}; +use miette::{IntoDiagnostic, Result}; + +use serde_json::map::Entry; +>>>>>>> cd2ed7b (edited main to iterate over vector of functions. TODO: edit add_funcs in main.rs and test into_analyzble_function use case in main.rs) use swc_core::{ common::{Globals, Mark, SourceMap, GLOBALS}, ecma::{ @@ -38,7 +45,7 @@ use forge_analyzer::{ resolver::resolve_calls, }; -use forge_loader::manifest::{ForgeManifest, FunctionRef, FunctionTy}; +use forge_loader::manifest::{ForgeManifest, FunctionRef, FunctionTy, Entrypoints}; use walkdir::WalkDir; #[derive(Parser, Debug)] @@ -86,7 +93,12 @@ struct ForgeProject { sm: Arc, ctx: AppCtx, env: Environment, +<<<<<<< HEAD funcs: Vec>, +======= + funcs: Vec>, + opts: Opts, +>>>>>>> cd2ed7b (edited main to iterate over vector of functions. TODO: edit add_funcs in main.rs and test into_analyzble_function use case in main.rs) } impl ForgeProject { @@ -136,8 +148,8 @@ impl ForgeProject { funcs: vec![], } } - - fn add_funcs<'a, I: IntoIterator>>(&mut self, iter: I) { +// TODO: edit to work with new iterator that not FUNCTIONTY + fn add_funcs<'a, I: IntoIterator>>(&mut self, iter: I) { self.funcs.extend(iter.into_iter().flat_map(|ftype| { ftype.sequence(|(func_name, path)| { let modid = self.ctx.modid_from_path(&path)?; @@ -206,12 +218,14 @@ fn scan_directory(dir: PathBuf, function: Option<&str>, opts: &Args) -> Result(resolved_func.into_func_path()) - }) - }); + let funcrefs = &manifest.modules.into_analyzable_functions(); + + // .flat_map(|f| { + // f.sequence(|fmod| { + // let resolved_func = FunctionRef::try_from(fmod)?.try_resolve(&paths, &dir)?; + // Ok::<_, forge_loader::Error>(resolved_func.into_func_path()) + // }) + // }); let src_root = dir.join("src"); let mut proj = ForgeProject::with_files_and_sourceroot(src_root, paths.clone(), secret_packages); @@ -300,6 +314,7 @@ fn scan_directory(dir: PathBuf, function: Option<&str>, opts: &Args) -> Result { let mut runner = DefintionAnalysisRunner::new(); @@ -413,7 +428,50 @@ fn scan_directory(dir: PathBuf, function: Option<&str>, opts: &Args) -> Result { + // let mut checker = AuthZChecker::new(); + // debug!("checking {func} at {path:?}"); + // if let Err(err) = interp.run_checker(def, &mut checker, path.clone(), func.clone()) + // { + // warn!("error while scanning {func} in {path:?}: {err}"); + // } + // reporter.add_vulnerabilities(checker.into_vulns()); + // } + // FunctionTy::WebTrigger((ref func, ref path, _, def)) => { + // let mut checker = AuthenticateChecker::new(); + // debug!("checking webtrigger {func} at {path:?}"); + // if let Err(err) = + // authn_interp.run_checker(def, &mut checker, path.clone(), func.clone()) + // { + // warn!("error while scanning {func} in {path:?}: {err}"); + // } + // reporter.add_vulnerabilities(checker.into_vulns()); + // } + // } + + if func.invokable { + let mut checker = AuthZChecker::new(); + debug!("checking {func} at {path:?}"); + if let Err(err) = interp.run_checker(def, &mut checker, path.clone(), func.clone()) + { + warn!("error while scanning {func} in {path:?}: {err}"); + } + reporter.add_vulnerabilities(checker.into_vulns()); + + } else if func.web_trigger { + let mut checker = AuthenticateChecker::new(); + debug!("checking webtrigger {func} at {path:?}"); + if let Err(err) = + authn_interp.run_checker(def, &mut checker, path.clone(), func.clone()) + { + warn!("error while scanning {func} in {path:?}: {err}"); +>>>>>>> cd2ed7b (edited main to iterate over vector of functions. TODO: edit add_funcs in main.rs and test into_analyzble_function use case in main.rs) } + reporter.add_vulnerabilities(checker.into_vulns()); + + } } From 2deab958709ac308c0be42714b04514447694882 Mon Sep 17 00:00:00 2001 From: awang7 Date: Mon, 18 Sep 2023 11:09:18 -0400 Subject: [PATCH 254/517] added new modules for additional endpoints in user invokable modules --- crates/forge_loader/src/manifest.rs | 50 +++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) diff --git a/crates/forge_loader/src/manifest.rs b/crates/forge_loader/src/manifest.rs index bc19201d..5b26f378 100644 --- a/crates/forge_loader/src/manifest.rs +++ b/crates/forge_loader/src/manifest.rs @@ -33,8 +33,12 @@ struct ModInfo<'a> { } #[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] +<<<<<<< HEAD struct MacroMod<'a> { // #[serde(flatten, borrow)] +======= +struct MacroMod<'a> { +>>>>>>> 31a3048 (added new modules for additional endpoints in user invokable modules) key: &'a str, function: &'a str, #[serde(borrow)] @@ -45,7 +49,47 @@ struct MacroMod<'a> { export: Option>, } +<<<<<<< HEAD // WebTrigger => RawTrigger; WHY IS THIS NAMED DIFFERENTLY !? WHO CHANGED NAMES +======= +#[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] +struct ContentByLineItem<'a> { + key: &'a str, + function: &'a str, + #[serde(borrow)] + resolver: Option>, + #[serde(borrow)] + dynamic_properties: Option>, +} + +#[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] +struct IssueGlance<'a> { + key: &'a str, + function: &'a str, + #[serde(borrow)] + resolver: Option>, + #[serde(borrow)] + dynamic_properties: Option>, + +} + +#[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] +struct AccessImportType<'a> { + key: &'a str, + function: &'a str, + #[serde(borrow)] + one_delete_import: Option>, + #[serde(borrow)] + start_import: Option>, + #[serde(borrow)] + stop_import: Option>, + #[serde(borrow)] + import_status: Option>, + +} + +// WebTrigger => RawTrigger; WHY IS THIS NAMED DIFFERENTLY !? WHO CHANGED NAMES +>>>>>>> 31a3048 (added new modules for additional endpoints in user invokable modules) #[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] struct RawTrigger<'a> { key: &'a str, @@ -128,6 +172,12 @@ pub struct ForgeModules<'a> { macros: Vec>, #[serde(rename = "function", default, borrow)] pub functions: Vec>, + #[serde(rename = "contentByLineItem", default, borrow)] + content_by_line_item: Vec>, + #[serde(rename = "jira:issueGlance", default, borrow)] + issue_glance: Vec>, + #[serde(rename = "jira:accessImportType", default, borrow)] + access_import_type: Vec>, // deserializing non user-invocable modules #[serde(rename = "webtrigger", default, borrow)] webtriggers: Vec>, From 7e169e70a80c3557862a1a22ca76638264afbaf4 Mon Sep 17 00:00:00 2001 From: awang7 Date: Mon, 18 Sep 2023 11:09:18 -0400 Subject: [PATCH 255/517] added new modules for additional endpoints in user invokable modules --- crates/forge_loader/src/manifest.rs | 50 +++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) diff --git a/crates/forge_loader/src/manifest.rs b/crates/forge_loader/src/manifest.rs index bc19201d..5b26f378 100644 --- a/crates/forge_loader/src/manifest.rs +++ b/crates/forge_loader/src/manifest.rs @@ -33,8 +33,12 @@ struct ModInfo<'a> { } #[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] +<<<<<<< HEAD struct MacroMod<'a> { // #[serde(flatten, borrow)] +======= +struct MacroMod<'a> { +>>>>>>> 31a3048 (added new modules for additional endpoints in user invokable modules) key: &'a str, function: &'a str, #[serde(borrow)] @@ -45,7 +49,47 @@ struct MacroMod<'a> { export: Option>, } +<<<<<<< HEAD // WebTrigger => RawTrigger; WHY IS THIS NAMED DIFFERENTLY !? WHO CHANGED NAMES +======= +#[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] +struct ContentByLineItem<'a> { + key: &'a str, + function: &'a str, + #[serde(borrow)] + resolver: Option>, + #[serde(borrow)] + dynamic_properties: Option>, +} + +#[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] +struct IssueGlance<'a> { + key: &'a str, + function: &'a str, + #[serde(borrow)] + resolver: Option>, + #[serde(borrow)] + dynamic_properties: Option>, + +} + +#[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] +struct AccessImportType<'a> { + key: &'a str, + function: &'a str, + #[serde(borrow)] + one_delete_import: Option>, + #[serde(borrow)] + start_import: Option>, + #[serde(borrow)] + stop_import: Option>, + #[serde(borrow)] + import_status: Option>, + +} + +// WebTrigger => RawTrigger; WHY IS THIS NAMED DIFFERENTLY !? WHO CHANGED NAMES +>>>>>>> 31a3048 (added new modules for additional endpoints in user invokable modules) #[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] struct RawTrigger<'a> { key: &'a str, @@ -128,6 +172,12 @@ pub struct ForgeModules<'a> { macros: Vec>, #[serde(rename = "function", default, borrow)] pub functions: Vec>, + #[serde(rename = "contentByLineItem", default, borrow)] + content_by_line_item: Vec>, + #[serde(rename = "jira:issueGlance", default, borrow)] + issue_glance: Vec>, + #[serde(rename = "jira:accessImportType", default, borrow)] + access_import_type: Vec>, // deserializing non user-invocable modules #[serde(rename = "webtrigger", default, borrow)] webtriggers: Vec>, From f992f13b1623982b301d9d01fef889c60fe6e6b6 Mon Sep 17 00:00:00 2001 From: awang7 Date: Mon, 18 Sep 2023 11:20:37 -0400 Subject: [PATCH 256/517] added methods for adding additional user invokable endpoints to vector for scanning. --- crates/forge_loader/src/manifest.rs | 120 ++++++++++++++++++++++------ 1 file changed, 97 insertions(+), 23 deletions(-) diff --git a/crates/forge_loader/src/manifest.rs b/crates/forge_loader/src/manifest.rs index 5b26f378..270b16b1 100644 --- a/crates/forge_loader/src/manifest.rs +++ b/crates/forge_loader/src/manifest.rs @@ -33,12 +33,7 @@ struct ModInfo<'a> { } #[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] -<<<<<<< HEAD struct MacroMod<'a> { - // #[serde(flatten, borrow)] -======= -struct MacroMod<'a> { ->>>>>>> 31a3048 (added new modules for additional endpoints in user invokable modules) key: &'a str, function: &'a str, #[serde(borrow)] @@ -49,47 +44,41 @@ struct MacroMod<'a> { export: Option>, } -<<<<<<< HEAD -// WebTrigger => RawTrigger; WHY IS THIS NAMED DIFFERENTLY !? WHO CHANGED NAMES -======= #[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] -struct ContentByLineItem<'a> { +struct ContentByLineItem<'a> { key: &'a str, function: &'a str, - #[serde(borrow)] + #[serde(borrow)] resolver: Option>, - #[serde(borrow)] + #[serde(borrow)] dynamic_properties: Option>, } #[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] -struct IssueGlance<'a> { +struct IssueGlance<'a> { key: &'a str, function: &'a str, - #[serde(borrow)] + #[serde(borrow)] resolver: Option>, - #[serde(borrow)] + #[serde(borrow)] dynamic_properties: Option>, - } #[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] -struct AccessImportType<'a> { +struct AccessImportType<'a> { key: &'a str, function: &'a str, - #[serde(borrow)] + #[serde(borrow)] one_delete_import: Option>, - #[serde(borrow)] + #[serde(borrow)] start_import: Option>, - #[serde(borrow)] + #[serde(borrow)] stop_import: Option>, - #[serde(borrow)] + #[serde(borrow)] import_status: Option>, - } -// WebTrigger => RawTrigger; WHY IS THIS NAMED DIFFERENTLY !? WHO CHANGED NAMES ->>>>>>> 31a3048 (added new modules for additional endpoints in user invokable modules) +// WebTrigger => RawTrigger; WHY IS THIS NAMED DIFFERENTLY !? WHO CHANGED NAMES #[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] struct RawTrigger<'a> { key: &'a str, @@ -442,6 +431,91 @@ impl<'a> ForgeModules<'a> { } } + for contentitem in self.content_by_line_item { + functions_to_scan.push(Entrypoints { + function: contentitem.function, + invokable: true, + web_trigger: false, + }); + if let Some(resolver) = contentitem.resolver { + functions_to_scan.push(Entrypoints { + function: resolver.function, + invokable: true, + web_trigger: false, + }) + } + + if let Some(dynamic_properties) = contentitem.dynamic_properties { + functions_to_scan.push(Entrypoints { + function: dynamic_properties.function, + invokable: true, + web_trigger: false, + }) + } + } + + for issue in self.issue_glance { + functions_to_scan.push(Entrypoints { + function: issue.function, + invokable: true, + web_trigger: false, + }); + if let Some(resolver) = issue.resolver { + functions_to_scan.push(Entrypoints { + function: resolver.function, + invokable: true, + web_trigger: false, + }) + } + + if let Some(dynamic_properties) = issue.dynamic_properties { + functions_to_scan.push(Entrypoints { + function: dynamic_properties.function, + invokable: true, + web_trigger: false, + }) + } + } + + for access in self.access_import_type { + functions_to_scan.push(Entrypoints { + function: access.function, + invokable: true, + web_trigger: false, + }); + if let Some(delete) = access.one_delete_import { + functions_to_scan.push(Entrypoints { + function: delete.function, + invokable: true, + web_trigger: false, + }) + } + + if let Some(start) = access.start_import { + functions_to_scan.push(Entrypoints { + function: start.function, + invokable: true, + web_trigger: false, + }) + } + + if let Some(stop) = access.stop_import { + functions_to_scan.push(Entrypoints { + function: stop.function, + invokable: true, + web_trigger: false, + }) + } + + if let Some(status) = access.import_status { + functions_to_scan.push(Entrypoints { + function: status.function, + invokable: true, + web_trigger: false, + }) + } + } + // get array for user invokable module functions // make alternate_functions all user-invokable functions for module in self.extra.into_values().flatten() { From 1ebcec46524c3a08e0c3f916cee7700d293db363 Mon Sep 17 00:00:00 2001 From: awang7 Date: Mon, 18 Sep 2023 11:20:37 -0400 Subject: [PATCH 257/517] added methods for adding additional user invokable endpoints to vector for scanning. --- crates/forge_loader/src/manifest.rs | 120 ++++++++++++++++++++++------ 1 file changed, 97 insertions(+), 23 deletions(-) diff --git a/crates/forge_loader/src/manifest.rs b/crates/forge_loader/src/manifest.rs index 5b26f378..270b16b1 100644 --- a/crates/forge_loader/src/manifest.rs +++ b/crates/forge_loader/src/manifest.rs @@ -33,12 +33,7 @@ struct ModInfo<'a> { } #[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] -<<<<<<< HEAD struct MacroMod<'a> { - // #[serde(flatten, borrow)] -======= -struct MacroMod<'a> { ->>>>>>> 31a3048 (added new modules for additional endpoints in user invokable modules) key: &'a str, function: &'a str, #[serde(borrow)] @@ -49,47 +44,41 @@ struct MacroMod<'a> { export: Option>, } -<<<<<<< HEAD -// WebTrigger => RawTrigger; WHY IS THIS NAMED DIFFERENTLY !? WHO CHANGED NAMES -======= #[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] -struct ContentByLineItem<'a> { +struct ContentByLineItem<'a> { key: &'a str, function: &'a str, - #[serde(borrow)] + #[serde(borrow)] resolver: Option>, - #[serde(borrow)] + #[serde(borrow)] dynamic_properties: Option>, } #[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] -struct IssueGlance<'a> { +struct IssueGlance<'a> { key: &'a str, function: &'a str, - #[serde(borrow)] + #[serde(borrow)] resolver: Option>, - #[serde(borrow)] + #[serde(borrow)] dynamic_properties: Option>, - } #[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] -struct AccessImportType<'a> { +struct AccessImportType<'a> { key: &'a str, function: &'a str, - #[serde(borrow)] + #[serde(borrow)] one_delete_import: Option>, - #[serde(borrow)] + #[serde(borrow)] start_import: Option>, - #[serde(borrow)] + #[serde(borrow)] stop_import: Option>, - #[serde(borrow)] + #[serde(borrow)] import_status: Option>, - } -// WebTrigger => RawTrigger; WHY IS THIS NAMED DIFFERENTLY !? WHO CHANGED NAMES ->>>>>>> 31a3048 (added new modules for additional endpoints in user invokable modules) +// WebTrigger => RawTrigger; WHY IS THIS NAMED DIFFERENTLY !? WHO CHANGED NAMES #[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] struct RawTrigger<'a> { key: &'a str, @@ -442,6 +431,91 @@ impl<'a> ForgeModules<'a> { } } + for contentitem in self.content_by_line_item { + functions_to_scan.push(Entrypoints { + function: contentitem.function, + invokable: true, + web_trigger: false, + }); + if let Some(resolver) = contentitem.resolver { + functions_to_scan.push(Entrypoints { + function: resolver.function, + invokable: true, + web_trigger: false, + }) + } + + if let Some(dynamic_properties) = contentitem.dynamic_properties { + functions_to_scan.push(Entrypoints { + function: dynamic_properties.function, + invokable: true, + web_trigger: false, + }) + } + } + + for issue in self.issue_glance { + functions_to_scan.push(Entrypoints { + function: issue.function, + invokable: true, + web_trigger: false, + }); + if let Some(resolver) = issue.resolver { + functions_to_scan.push(Entrypoints { + function: resolver.function, + invokable: true, + web_trigger: false, + }) + } + + if let Some(dynamic_properties) = issue.dynamic_properties { + functions_to_scan.push(Entrypoints { + function: dynamic_properties.function, + invokable: true, + web_trigger: false, + }) + } + } + + for access in self.access_import_type { + functions_to_scan.push(Entrypoints { + function: access.function, + invokable: true, + web_trigger: false, + }); + if let Some(delete) = access.one_delete_import { + functions_to_scan.push(Entrypoints { + function: delete.function, + invokable: true, + web_trigger: false, + }) + } + + if let Some(start) = access.start_import { + functions_to_scan.push(Entrypoints { + function: start.function, + invokable: true, + web_trigger: false, + }) + } + + if let Some(stop) = access.stop_import { + functions_to_scan.push(Entrypoints { + function: stop.function, + invokable: true, + web_trigger: false, + }) + } + + if let Some(status) = access.import_status { + functions_to_scan.push(Entrypoints { + function: status.function, + invokable: true, + web_trigger: false, + }) + } + } + // get array for user invokable module functions // make alternate_functions all user-invokable functions for module in self.extra.into_values().flatten() { From b202d39bef1b4532472be8988aaf6b2172f9e0f1 Mon Sep 17 00:00:00 2001 From: awang7 Date: Mon, 8 Jan 2024 12:12:59 -0800 Subject: [PATCH 258/517] resolving merge conflicts --- crates/forge_loader/src/manifest.rs | 44 +++++++++++++++++++++-------- 1 file changed, 32 insertions(+), 12 deletions(-) diff --git a/crates/forge_loader/src/manifest.rs b/crates/forge_loader/src/manifest.rs index 270b16b1..b5783e61 100644 --- a/crates/forge_loader/src/manifest.rs +++ b/crates/forge_loader/src/manifest.rs @@ -115,13 +115,7 @@ struct ScheduledTrigger<'a> { pub struct DataProvider<'a> { key: &'a str, #[serde(flatten, borrow)] - callback: Callback<'a>, -} - -// Struct for mapping functions defined one more level in whose value is {function: string}. Used to represent resolver types. -#[derive(Debug, Clone, PartialEq, Eq, Deserialize)] -pub struct Callback<'a> { - pub function: &'a str, + callback: ModInfo<'a>, } // Struct for Custom field Module. Check that search suggestion gets read in correctly. @@ -129,22 +123,27 @@ pub struct Callback<'a> { pub struct CustomField<'a> { #[serde(flatten, borrow)] key: &'a str, + // all attributes below involve function calls + value: &'a str, search_suggestion: &'a str, + function: &'a str, + edit: &'a str, + resolver: ModInfo<'a>, } #[derive(Debug, Clone, PartialEq, Eq, Deserialize)] pub struct UiModificatons<'a> { - #[serde(flatten, borrow)] key: &'a str, - resolver: Callback<'a>, + #[serde(flatten, borrow)] + resolver: ModInfo<'a>, } #[derive(Debug, Clone, PartialEq, Eq, Deserialize)] pub struct WorkflowValidator<'a> { - #[serde(flatten, borrow)] key: &'a str, functon: &'a str, - resolver: Callback<'a>, + #[serde(flatten, borrow)] + resolver: ModInfo<'a>, } #[derive(Debug, Clone, PartialEq, Eq, Deserialize)] @@ -371,11 +370,32 @@ impl<'a> ForgeModules<'a> { }); self.custom_field.iter().for_each(|customfield| { + functions_to_scan.push(Entrypoints { + function: customfield.value, + invokable: true, + web_trigger: false, + }); functions_to_scan.push(Entrypoints { function: customfield.search_suggestion, invokable: true, web_trigger: false, - }) + }); + functions_to_scan.push(Entrypoints { + function: customfield.function, + invokable: true, + web_trigger: false, + }); + functions_to_scan.push(Entrypoints { + function: customfield.edit, + invokable: true, + web_trigger: false, + }); + + functions_to_scan.push(Entrypoints { + function: customfield.resolver.function, + invokable: true, + web_trigger: false, + }); }); self.ui_modifications.iter().for_each(|ui| { From 40acab119ab9825655d7118bfcf0870064c780a5 Mon Sep 17 00:00:00 2001 From: awang7 Date: Mon, 8 Jan 2024 12:12:59 -0800 Subject: [PATCH 259/517] resolving merge conflicts --- crates/forge_loader/src/manifest.rs | 44 +++++++++++++++++++++-------- 1 file changed, 32 insertions(+), 12 deletions(-) diff --git a/crates/forge_loader/src/manifest.rs b/crates/forge_loader/src/manifest.rs index 270b16b1..b5783e61 100644 --- a/crates/forge_loader/src/manifest.rs +++ b/crates/forge_loader/src/manifest.rs @@ -115,13 +115,7 @@ struct ScheduledTrigger<'a> { pub struct DataProvider<'a> { key: &'a str, #[serde(flatten, borrow)] - callback: Callback<'a>, -} - -// Struct for mapping functions defined one more level in whose value is {function: string}. Used to represent resolver types. -#[derive(Debug, Clone, PartialEq, Eq, Deserialize)] -pub struct Callback<'a> { - pub function: &'a str, + callback: ModInfo<'a>, } // Struct for Custom field Module. Check that search suggestion gets read in correctly. @@ -129,22 +123,27 @@ pub struct Callback<'a> { pub struct CustomField<'a> { #[serde(flatten, borrow)] key: &'a str, + // all attributes below involve function calls + value: &'a str, search_suggestion: &'a str, + function: &'a str, + edit: &'a str, + resolver: ModInfo<'a>, } #[derive(Debug, Clone, PartialEq, Eq, Deserialize)] pub struct UiModificatons<'a> { - #[serde(flatten, borrow)] key: &'a str, - resolver: Callback<'a>, + #[serde(flatten, borrow)] + resolver: ModInfo<'a>, } #[derive(Debug, Clone, PartialEq, Eq, Deserialize)] pub struct WorkflowValidator<'a> { - #[serde(flatten, borrow)] key: &'a str, functon: &'a str, - resolver: Callback<'a>, + #[serde(flatten, borrow)] + resolver: ModInfo<'a>, } #[derive(Debug, Clone, PartialEq, Eq, Deserialize)] @@ -371,11 +370,32 @@ impl<'a> ForgeModules<'a> { }); self.custom_field.iter().for_each(|customfield| { + functions_to_scan.push(Entrypoints { + function: customfield.value, + invokable: true, + web_trigger: false, + }); functions_to_scan.push(Entrypoints { function: customfield.search_suggestion, invokable: true, web_trigger: false, - }) + }); + functions_to_scan.push(Entrypoints { + function: customfield.function, + invokable: true, + web_trigger: false, + }); + functions_to_scan.push(Entrypoints { + function: customfield.edit, + invokable: true, + web_trigger: false, + }); + + functions_to_scan.push(Entrypoints { + function: customfield.resolver.function, + invokable: true, + web_trigger: false, + }); }); self.ui_modifications.iter().for_each(|ui| { From 1ad833479698a831f0d17ed577012a58148f6749 Mon Sep 17 00:00:00 2001 From: awang7 Date: Mon, 18 Sep 2023 18:21:10 -0400 Subject: [PATCH 260/517] changed entrypoints to entrypoint. Working on other comments --- crates/forge_loader/src/manifest.rs | 233 ++++++++++++++++------------ crates/fsrt/src/main.rs | 41 +++-- 2 files changed, 153 insertions(+), 121 deletions(-) diff --git a/crates/forge_loader/src/manifest.rs b/crates/forge_loader/src/manifest.rs index b5783e61..df0db8db 100644 --- a/crates/forge_loader/src/manifest.rs +++ b/crates/forge_loader/src/manifest.rs @@ -140,10 +140,10 @@ pub struct UiModificatons<'a> { #[derive(Debug, Clone, PartialEq, Eq, Deserialize)] pub struct WorkflowValidator<'a> { - key: &'a str, - functon: &'a str, #[serde(flatten, borrow)] - resolver: ModInfo<'a>, + key: &'a str, + function: &'a str, + resolver: ModInfo<'a> } #[derive(Debug, Clone, PartialEq, Eq, Deserialize)] @@ -275,7 +275,7 @@ pub enum FunctionTy { // Struct used for tracking what scan a funtion requires. #[derive(Debug, Clone, PartialEq, Eq, Deserialize)] -pub struct Entrypoints<'a> { +pub struct Entrypoint<'a> { pub function: &'a str, pub invokable: bool, pub web_trigger: bool, @@ -318,34 +318,41 @@ impl AsRef for FunctionTy { } impl<'a> ForgeModules<'a> { - // TODO: function returns iterator where each item is some specified type. - pub fn into_analyzable_functions(self) -> Vec> { + +// TODO: function returns iterator where each item is some specified type. + pub fn into_analyzable_functions ( + self, + ) -> Vec>{ // number of webtriggers are usually low, so it's better to just sort them and reuse - // self.webtriggers - // .sort_unstable_by_key(|trigger| trigger.function); + self.webtriggers.iter(). + .sort_unstable_by_key(|trigger| trigger.function); // Get all the Triggers and represent them as a new struct thing where "webtrigger" attribute is true // for all trigger things let mut functions_to_scan = Vec::new(); self.webtriggers.iter().for_each(|webtriggers| { - functions_to_scan.push(Entrypoints { - function: webtriggers.function, - invokable: false, - web_trigger: true, - }); + functions_to_scan.push( + Entrypoint { + function: webtriggers.function, + invokable: false, + web_trigger: true, + } + ); + }); self.event_triggers.iter().for_each(|event_triggers| { - functions_to_scan.push(Entrypoints { - function: event_triggers.raw.function, - invokable: false, - web_trigger: true, - }); + functions_to_scan.push( + Entrypoint { + function: event_triggers.raw.function, + invokable: false, + web_trigger: true, + } + ); }); - self.scheduled_triggers - .iter() - .for_each(|schedule_triggers| { - functions_to_scan.push(Entrypoints { + self.scheduled_triggers.iter().for_each(|schedule_triggers| { + functions_to_scan.push( + Entrypoint { function: schedule_triggers.raw.function, invokable: false, web_trigger: true, @@ -354,70 +361,96 @@ impl<'a> ForgeModules<'a> { // create arrays representing functions that expose user non-invokable functions self.consumers.iter().for_each(|consumers| { - functions_to_scan.push(Entrypoints { - function: consumers.resolver.function, - invokable: true, - web_trigger: false, - }) + functions_to_scan.push( + Entrypoint { + function: consumers.resolver.function, + invokable: true, + web_trigger: false, + } + ) }); self.data_provider.iter().for_each(|dataprovider| { - functions_to_scan.push(Entrypoints { - function: dataprovider.callback.function, - invokable: true, - web_trigger: false, - }) + functions_to_scan.push( + Entrypoint { + function: dataprovider.callback.function, + invokable: true, + web_trigger: false, + } + ) }); self.custom_field.iter().for_each(|customfield| { - functions_to_scan.push(Entrypoints { - function: customfield.value, - invokable: true, - web_trigger: false, - }); - functions_to_scan.push(Entrypoints { - function: customfield.search_suggestion, - invokable: true, - web_trigger: false, - }); - functions_to_scan.push(Entrypoints { - function: customfield.function, - invokable: true, - web_trigger: false, - }); - functions_to_scan.push(Entrypoints { - function: customfield.edit, - invokable: true, - web_trigger: false, - }); + functions_to_scan.push( + Entrypoint { + function: customfield.value, + invokable: true, + web_trigger: false, + } + ); + functions_to_scan.push( + Entrypoint { + function: customfield.search_suggestion, + invokable: true, + web_trigger: false, + } + ); + functions_to_scan.push( + Entrypoint { + function: customfield.function, + invokable: true, + web_trigger: false, + } + ); + functions_to_scan.push( + Entrypoint { + function: customfield.edit, + invokable: true, + web_trigger: false, + } + ); + + functions_to_scan.push( + Entrypoint { + function: customfield.resolver.function, + invokable: true, + web_trigger: false, + } + ); - functions_to_scan.push(Entrypoints { - function: customfield.resolver.function, - invokable: true, - web_trigger: false, - }); }); self.ui_modifications.iter().for_each(|ui| { - functions_to_scan.push(Entrypoints { - function: ui.resolver.function, - invokable: true, - web_trigger: false, - }) + functions_to_scan.push( + Entrypoint { + function: ui.resolver.function, + invokable: true, + web_trigger: false, + } + ) }); self.workflow_validator.iter().for_each(|validator| { - functions_to_scan.push(Entrypoints { - function: validator.resolver.function, - invokable: true, - web_trigger: false, - }) - }); + functions_to_scan.push( + Entrypoint { + function: validator.function, + invokable: true, + web_trigger: false, + } + ); - self.workflow_post_function - .iter() - .for_each(|post_function| { - functions_to_scan.push(Entrypoints { + functions_to_scan.push( + Entrypoint { + function: validator.resolver.function, + invokable: true, + web_trigger: false, + } + ); + }); + + self.workflow_post_function.iter().for_each(|post_function| { + functions_to_scan.push( + Entrypoint { function: post_function.function, invokable: true, web_trigger: false, @@ -427,23 +460,23 @@ impl<'a> ForgeModules<'a> { // get user invokable modules that have additional exposure endpoints. // ie macros has config and export fields on top of resolver fields that are functions for macros in self.macros { - if let Some(resolver) = macros.resolver { - functions_to_scan.push(Entrypoints { + if let Some(resolver)= macros.resolver { + functions_to_scan.push(Entrypoint { function: resolver.function, invokable: true, web_trigger: false, }) } - if let Some(config) = macros.config { - functions_to_scan.push(Entrypoints { + if let Some(config)= macros.config { + functions_to_scan.push(Entrypoint { function: config.function, invokable: true, web_trigger: false, }) } - if let Some(export) = macros.export { - functions_to_scan.push(Entrypoints { + if let Some(export)= macros.export { + functions_to_scan.push(Entrypoint { function: export.function, invokable: true, web_trigger: false, @@ -452,21 +485,21 @@ impl<'a> ForgeModules<'a> { } for contentitem in self.content_by_line_item { - functions_to_scan.push(Entrypoints { + functions_to_scan.push(Entrypoint { function: contentitem.function, invokable: true, web_trigger: false, }); - if let Some(resolver) = contentitem.resolver { - functions_to_scan.push(Entrypoints { + if let Some(resolver)= contentitem.resolver { + functions_to_scan.push(Entrypoint { function: resolver.function, invokable: true, web_trigger: false, }) } - if let Some(dynamic_properties) = contentitem.dynamic_properties { - functions_to_scan.push(Entrypoints { + if let Some(dynamic_properties)= contentitem.dynamic_properties { + functions_to_scan.push(Entrypoint { function: dynamic_properties.function, invokable: true, web_trigger: false, @@ -475,21 +508,21 @@ impl<'a> ForgeModules<'a> { } for issue in self.issue_glance { - functions_to_scan.push(Entrypoints { + functions_to_scan.push(Entrypoint { function: issue.function, invokable: true, web_trigger: false, }); - if let Some(resolver) = issue.resolver { - functions_to_scan.push(Entrypoints { + if let Some(resolver)= issue.resolver { + functions_to_scan.push(Entrypoint { function: resolver.function, invokable: true, web_trigger: false, }) } - if let Some(dynamic_properties) = issue.dynamic_properties { - functions_to_scan.push(Entrypoints { + if let Some(dynamic_properties)= issue.dynamic_properties { + functions_to_scan.push(Entrypoint { function: dynamic_properties.function, invokable: true, web_trigger: false, @@ -498,37 +531,37 @@ impl<'a> ForgeModules<'a> { } for access in self.access_import_type { - functions_to_scan.push(Entrypoints { + functions_to_scan.push(Entrypoint { function: access.function, invokable: true, web_trigger: false, }); if let Some(delete) = access.one_delete_import { - functions_to_scan.push(Entrypoints { + functions_to_scan.push(Entrypoint { function: delete.function, invokable: true, web_trigger: false, }) } - if let Some(start) = access.start_import { - functions_to_scan.push(Entrypoints { + if let Some(start)= access.start_import { + functions_to_scan.push(Entrypoint { function: start.function, invokable: true, web_trigger: false, }) } - if let Some(stop) = access.stop_import { - functions_to_scan.push(Entrypoints { + if let Some(stop)= access.stop_import { + functions_to_scan.push(Entrypoint { function: stop.function, invokable: true, web_trigger: false, }) } - if let Some(status) = access.import_status { - functions_to_scan.push(Entrypoints { + if let Some(status)= access.import_status { + functions_to_scan.push(Entrypoint { function: status.function, invokable: true, web_trigger: false, @@ -540,7 +573,7 @@ impl<'a> ForgeModules<'a> { // make alternate_functions all user-invokable functions for module in self.extra.into_values().flatten() { if let Some(mod_function) = module.function { - functions_to_scan.push(Entrypoints { + functions_to_scan.push(Entrypoint { function: mod_function, invokable: true, web_trigger: false, @@ -548,15 +581,15 @@ impl<'a> ForgeModules<'a> { } if let Some(resolver) = module.resolver { - functions_to_scan.push(Entrypoints { + functions_to_scan.push(Entrypoint { function: resolver.function, invokable: true, web_trigger: false, }); } } - - return functions_to_scan; + + functions_to_scan } } diff --git a/crates/fsrt/src/main.rs b/crates/fsrt/src/main.rs index d649b2f8..4b345ef9 100644 --- a/crates/fsrt/src/main.rs +++ b/crates/fsrt/src/main.rs @@ -45,7 +45,7 @@ use forge_analyzer::{ resolver::resolve_calls, }; -use forge_loader::manifest::{ForgeManifest, FunctionRef, FunctionTy, Entrypoints}; +use forge_loader::manifest::{ForgeManifest, FunctionRef, FunctionTy, Entrypoint}; use walkdir::WalkDir; #[derive(Parser, Debug)] @@ -93,10 +93,7 @@ struct ForgeProject { sm: Arc, ctx: AppCtx, env: Environment, -<<<<<<< HEAD - funcs: Vec>, -======= - funcs: Vec>, + funcs: Vec>, opts: Opts, >>>>>>> cd2ed7b (edited main to iterate over vector of functions. TODO: edit add_funcs in main.rs and test into_analyzble_function use case in main.rs) } @@ -149,17 +146,20 @@ impl ForgeProject { } } // TODO: edit to work with new iterator that not FUNCTIONTY - fn add_funcs<'a, I: IntoIterator>>(&mut self, iter: I) { - self.funcs.extend(iter.into_iter().flat_map(|ftype| { - ftype.sequence(|(func_name, path)| { - let modid = self.ctx.modid_from_path(&path)?; - let func = self.env.module_export(modid, func_name)?; - Some((func_name.to_owned(), path, modid, func)) - }) - })); + fn add_funcs<'a, I: IntoIterator>>(&mut self, iter: I) { + self.funcs.extend(iter); } } + +// self.funcs.extend(iter.into_iter().flat_map(|ftype| { +// ftype.sequence(|(func_name, path)| { +// let modid = self.ctx.modid_from_path(&path)?; +// let func = self.env.module_export(modid, func_name)?; +// Some((func_name.to_owned(), path, modid, func)) +// }) +// })); + fn is_js_file>(path: P) -> bool { matches!( path.as_ref().extension().map(|s| s.as_bytes()), @@ -218,7 +218,7 @@ fn scan_directory(dir: PathBuf, function: Option<&str>, opts: &Args) -> Result, opts: &Args) -> Result>>>>>> cd2ed7b (edited main to iterate over vector of functions. TODO: edit add_funcs in main.rs and test into_analyzble_function use case in main.rs) + warn!("error while scanning {:?} in {path:?}: {err}", func.function); } reporter.add_vulnerabilities(checker.into_vulns()); From e6573f0f2484f1811b305d704d21765da1a315e3 Mon Sep 17 00:00:00 2001 From: awang7 Date: Mon, 18 Sep 2023 18:21:10 -0400 Subject: [PATCH 261/517] changed entrypoints to entrypoint. Working on other comments --- crates/forge_loader/src/manifest.rs | 233 ++++++++++++++++------------ crates/fsrt/src/main.rs | 41 +++-- 2 files changed, 153 insertions(+), 121 deletions(-) diff --git a/crates/forge_loader/src/manifest.rs b/crates/forge_loader/src/manifest.rs index b5783e61..df0db8db 100644 --- a/crates/forge_loader/src/manifest.rs +++ b/crates/forge_loader/src/manifest.rs @@ -140,10 +140,10 @@ pub struct UiModificatons<'a> { #[derive(Debug, Clone, PartialEq, Eq, Deserialize)] pub struct WorkflowValidator<'a> { - key: &'a str, - functon: &'a str, #[serde(flatten, borrow)] - resolver: ModInfo<'a>, + key: &'a str, + function: &'a str, + resolver: ModInfo<'a> } #[derive(Debug, Clone, PartialEq, Eq, Deserialize)] @@ -275,7 +275,7 @@ pub enum FunctionTy { // Struct used for tracking what scan a funtion requires. #[derive(Debug, Clone, PartialEq, Eq, Deserialize)] -pub struct Entrypoints<'a> { +pub struct Entrypoint<'a> { pub function: &'a str, pub invokable: bool, pub web_trigger: bool, @@ -318,34 +318,41 @@ impl AsRef for FunctionTy { } impl<'a> ForgeModules<'a> { - // TODO: function returns iterator where each item is some specified type. - pub fn into_analyzable_functions(self) -> Vec> { + +// TODO: function returns iterator where each item is some specified type. + pub fn into_analyzable_functions ( + self, + ) -> Vec>{ // number of webtriggers are usually low, so it's better to just sort them and reuse - // self.webtriggers - // .sort_unstable_by_key(|trigger| trigger.function); + self.webtriggers.iter(). + .sort_unstable_by_key(|trigger| trigger.function); // Get all the Triggers and represent them as a new struct thing where "webtrigger" attribute is true // for all trigger things let mut functions_to_scan = Vec::new(); self.webtriggers.iter().for_each(|webtriggers| { - functions_to_scan.push(Entrypoints { - function: webtriggers.function, - invokable: false, - web_trigger: true, - }); + functions_to_scan.push( + Entrypoint { + function: webtriggers.function, + invokable: false, + web_trigger: true, + } + ); + }); self.event_triggers.iter().for_each(|event_triggers| { - functions_to_scan.push(Entrypoints { - function: event_triggers.raw.function, - invokable: false, - web_trigger: true, - }); + functions_to_scan.push( + Entrypoint { + function: event_triggers.raw.function, + invokable: false, + web_trigger: true, + } + ); }); - self.scheduled_triggers - .iter() - .for_each(|schedule_triggers| { - functions_to_scan.push(Entrypoints { + self.scheduled_triggers.iter().for_each(|schedule_triggers| { + functions_to_scan.push( + Entrypoint { function: schedule_triggers.raw.function, invokable: false, web_trigger: true, @@ -354,70 +361,96 @@ impl<'a> ForgeModules<'a> { // create arrays representing functions that expose user non-invokable functions self.consumers.iter().for_each(|consumers| { - functions_to_scan.push(Entrypoints { - function: consumers.resolver.function, - invokable: true, - web_trigger: false, - }) + functions_to_scan.push( + Entrypoint { + function: consumers.resolver.function, + invokable: true, + web_trigger: false, + } + ) }); self.data_provider.iter().for_each(|dataprovider| { - functions_to_scan.push(Entrypoints { - function: dataprovider.callback.function, - invokable: true, - web_trigger: false, - }) + functions_to_scan.push( + Entrypoint { + function: dataprovider.callback.function, + invokable: true, + web_trigger: false, + } + ) }); self.custom_field.iter().for_each(|customfield| { - functions_to_scan.push(Entrypoints { - function: customfield.value, - invokable: true, - web_trigger: false, - }); - functions_to_scan.push(Entrypoints { - function: customfield.search_suggestion, - invokable: true, - web_trigger: false, - }); - functions_to_scan.push(Entrypoints { - function: customfield.function, - invokable: true, - web_trigger: false, - }); - functions_to_scan.push(Entrypoints { - function: customfield.edit, - invokable: true, - web_trigger: false, - }); + functions_to_scan.push( + Entrypoint { + function: customfield.value, + invokable: true, + web_trigger: false, + } + ); + functions_to_scan.push( + Entrypoint { + function: customfield.search_suggestion, + invokable: true, + web_trigger: false, + } + ); + functions_to_scan.push( + Entrypoint { + function: customfield.function, + invokable: true, + web_trigger: false, + } + ); + functions_to_scan.push( + Entrypoint { + function: customfield.edit, + invokable: true, + web_trigger: false, + } + ); + + functions_to_scan.push( + Entrypoint { + function: customfield.resolver.function, + invokable: true, + web_trigger: false, + } + ); - functions_to_scan.push(Entrypoints { - function: customfield.resolver.function, - invokable: true, - web_trigger: false, - }); }); self.ui_modifications.iter().for_each(|ui| { - functions_to_scan.push(Entrypoints { - function: ui.resolver.function, - invokable: true, - web_trigger: false, - }) + functions_to_scan.push( + Entrypoint { + function: ui.resolver.function, + invokable: true, + web_trigger: false, + } + ) }); self.workflow_validator.iter().for_each(|validator| { - functions_to_scan.push(Entrypoints { - function: validator.resolver.function, - invokable: true, - web_trigger: false, - }) - }); + functions_to_scan.push( + Entrypoint { + function: validator.function, + invokable: true, + web_trigger: false, + } + ); - self.workflow_post_function - .iter() - .for_each(|post_function| { - functions_to_scan.push(Entrypoints { + functions_to_scan.push( + Entrypoint { + function: validator.resolver.function, + invokable: true, + web_trigger: false, + } + ); + }); + + self.workflow_post_function.iter().for_each(|post_function| { + functions_to_scan.push( + Entrypoint { function: post_function.function, invokable: true, web_trigger: false, @@ -427,23 +460,23 @@ impl<'a> ForgeModules<'a> { // get user invokable modules that have additional exposure endpoints. // ie macros has config and export fields on top of resolver fields that are functions for macros in self.macros { - if let Some(resolver) = macros.resolver { - functions_to_scan.push(Entrypoints { + if let Some(resolver)= macros.resolver { + functions_to_scan.push(Entrypoint { function: resolver.function, invokable: true, web_trigger: false, }) } - if let Some(config) = macros.config { - functions_to_scan.push(Entrypoints { + if let Some(config)= macros.config { + functions_to_scan.push(Entrypoint { function: config.function, invokable: true, web_trigger: false, }) } - if let Some(export) = macros.export { - functions_to_scan.push(Entrypoints { + if let Some(export)= macros.export { + functions_to_scan.push(Entrypoint { function: export.function, invokable: true, web_trigger: false, @@ -452,21 +485,21 @@ impl<'a> ForgeModules<'a> { } for contentitem in self.content_by_line_item { - functions_to_scan.push(Entrypoints { + functions_to_scan.push(Entrypoint { function: contentitem.function, invokable: true, web_trigger: false, }); - if let Some(resolver) = contentitem.resolver { - functions_to_scan.push(Entrypoints { + if let Some(resolver)= contentitem.resolver { + functions_to_scan.push(Entrypoint { function: resolver.function, invokable: true, web_trigger: false, }) } - if let Some(dynamic_properties) = contentitem.dynamic_properties { - functions_to_scan.push(Entrypoints { + if let Some(dynamic_properties)= contentitem.dynamic_properties { + functions_to_scan.push(Entrypoint { function: dynamic_properties.function, invokable: true, web_trigger: false, @@ -475,21 +508,21 @@ impl<'a> ForgeModules<'a> { } for issue in self.issue_glance { - functions_to_scan.push(Entrypoints { + functions_to_scan.push(Entrypoint { function: issue.function, invokable: true, web_trigger: false, }); - if let Some(resolver) = issue.resolver { - functions_to_scan.push(Entrypoints { + if let Some(resolver)= issue.resolver { + functions_to_scan.push(Entrypoint { function: resolver.function, invokable: true, web_trigger: false, }) } - if let Some(dynamic_properties) = issue.dynamic_properties { - functions_to_scan.push(Entrypoints { + if let Some(dynamic_properties)= issue.dynamic_properties { + functions_to_scan.push(Entrypoint { function: dynamic_properties.function, invokable: true, web_trigger: false, @@ -498,37 +531,37 @@ impl<'a> ForgeModules<'a> { } for access in self.access_import_type { - functions_to_scan.push(Entrypoints { + functions_to_scan.push(Entrypoint { function: access.function, invokable: true, web_trigger: false, }); if let Some(delete) = access.one_delete_import { - functions_to_scan.push(Entrypoints { + functions_to_scan.push(Entrypoint { function: delete.function, invokable: true, web_trigger: false, }) } - if let Some(start) = access.start_import { - functions_to_scan.push(Entrypoints { + if let Some(start)= access.start_import { + functions_to_scan.push(Entrypoint { function: start.function, invokable: true, web_trigger: false, }) } - if let Some(stop) = access.stop_import { - functions_to_scan.push(Entrypoints { + if let Some(stop)= access.stop_import { + functions_to_scan.push(Entrypoint { function: stop.function, invokable: true, web_trigger: false, }) } - if let Some(status) = access.import_status { - functions_to_scan.push(Entrypoints { + if let Some(status)= access.import_status { + functions_to_scan.push(Entrypoint { function: status.function, invokable: true, web_trigger: false, @@ -540,7 +573,7 @@ impl<'a> ForgeModules<'a> { // make alternate_functions all user-invokable functions for module in self.extra.into_values().flatten() { if let Some(mod_function) = module.function { - functions_to_scan.push(Entrypoints { + functions_to_scan.push(Entrypoint { function: mod_function, invokable: true, web_trigger: false, @@ -548,15 +581,15 @@ impl<'a> ForgeModules<'a> { } if let Some(resolver) = module.resolver { - functions_to_scan.push(Entrypoints { + functions_to_scan.push(Entrypoint { function: resolver.function, invokable: true, web_trigger: false, }); } } - - return functions_to_scan; + + functions_to_scan } } diff --git a/crates/fsrt/src/main.rs b/crates/fsrt/src/main.rs index d649b2f8..4b345ef9 100644 --- a/crates/fsrt/src/main.rs +++ b/crates/fsrt/src/main.rs @@ -45,7 +45,7 @@ use forge_analyzer::{ resolver::resolve_calls, }; -use forge_loader::manifest::{ForgeManifest, FunctionRef, FunctionTy, Entrypoints}; +use forge_loader::manifest::{ForgeManifest, FunctionRef, FunctionTy, Entrypoint}; use walkdir::WalkDir; #[derive(Parser, Debug)] @@ -93,10 +93,7 @@ struct ForgeProject { sm: Arc, ctx: AppCtx, env: Environment, -<<<<<<< HEAD - funcs: Vec>, -======= - funcs: Vec>, + funcs: Vec>, opts: Opts, >>>>>>> cd2ed7b (edited main to iterate over vector of functions. TODO: edit add_funcs in main.rs and test into_analyzble_function use case in main.rs) } @@ -149,17 +146,20 @@ impl ForgeProject { } } // TODO: edit to work with new iterator that not FUNCTIONTY - fn add_funcs<'a, I: IntoIterator>>(&mut self, iter: I) { - self.funcs.extend(iter.into_iter().flat_map(|ftype| { - ftype.sequence(|(func_name, path)| { - let modid = self.ctx.modid_from_path(&path)?; - let func = self.env.module_export(modid, func_name)?; - Some((func_name.to_owned(), path, modid, func)) - }) - })); + fn add_funcs<'a, I: IntoIterator>>(&mut self, iter: I) { + self.funcs.extend(iter); } } + +// self.funcs.extend(iter.into_iter().flat_map(|ftype| { +// ftype.sequence(|(func_name, path)| { +// let modid = self.ctx.modid_from_path(&path)?; +// let func = self.env.module_export(modid, func_name)?; +// Some((func_name.to_owned(), path, modid, func)) +// }) +// })); + fn is_js_file>(path: P) -> bool { matches!( path.as_ref().extension().map(|s| s.as_bytes()), @@ -218,7 +218,7 @@ fn scan_directory(dir: PathBuf, function: Option<&str>, opts: &Args) -> Result, opts: &Args) -> Result>>>>>> cd2ed7b (edited main to iterate over vector of functions. TODO: edit add_funcs in main.rs and test into_analyzble_function use case in main.rs) + warn!("error while scanning {:?} in {path:?}: {err}", func.function); } reporter.add_vulnerabilities(checker.into_vulns()); From 4728982c6f736dc4241ee2e74eeeae734027484f Mon Sep 17 00:00:00 2001 From: awang7 Date: Mon, 8 Jan 2024 12:14:30 -0800 Subject: [PATCH 262/517] resolving merge conflicts --- crates/forge_loader/src/manifest.rs | 214 +++++++++++++--------------- 1 file changed, 100 insertions(+), 114 deletions(-) diff --git a/crates/forge_loader/src/manifest.rs b/crates/forge_loader/src/manifest.rs index df0db8db..fca0670f 100644 --- a/crates/forge_loader/src/manifest.rs +++ b/crates/forge_loader/src/manifest.rs @@ -35,7 +35,7 @@ struct ModInfo<'a> { #[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] struct MacroMod<'a> { key: &'a str, - function: &'a str, + function: Option<&'a str>, #[serde(borrow)] resolver: Option>, #[serde(borrow)] @@ -124,11 +124,11 @@ pub struct CustomField<'a> { #[serde(flatten, borrow)] key: &'a str, // all attributes below involve function calls - value: &'a str, - search_suggestion: &'a str, - function: &'a str, - edit: &'a str, - resolver: ModInfo<'a>, + value: Option<&'a str>, + search_suggestions: &'a str, + function: Option<&'a str>, + edit: Option<&'a str>, + resolver: Option>, } #[derive(Debug, Clone, PartialEq, Eq, Deserialize)] @@ -143,12 +143,11 @@ pub struct WorkflowValidator<'a> { #[serde(flatten, borrow)] key: &'a str, function: &'a str, - resolver: ModInfo<'a> + resolver: ModInfo<'a>, } #[derive(Debug, Clone, PartialEq, Eq, Deserialize)] pub struct WorkflowPostFunction<'a> { - #[serde(flatten, borrow)] key: &'a str, function: &'a str, } @@ -274,7 +273,7 @@ pub enum FunctionTy { } // Struct used for tracking what scan a funtion requires. -#[derive(Debug, Clone, PartialEq, Eq, Deserialize)] +#[derive(Debug, Clone, PartialEq, Eq)] pub struct Entrypoint<'a> { pub function: &'a str, pub invokable: bool, @@ -318,41 +317,34 @@ impl AsRef for FunctionTy { } impl<'a> ForgeModules<'a> { - -// TODO: function returns iterator where each item is some specified type. - pub fn into_analyzable_functions ( - self, - ) -> Vec>{ + // TODO: function returns iterator where each item is some specified type. + pub fn into_analyzable_functions(self) -> Vec> { // number of webtriggers are usually low, so it's better to just sort them and reuse - self.webtriggers.iter(). - .sort_unstable_by_key(|trigger| trigger.function); + // self.webtriggers.iter(). + // .sort_unstable_by_key(|trigger| trigger.function); // Get all the Triggers and represent them as a new struct thing where "webtrigger" attribute is true // for all trigger things let mut functions_to_scan = Vec::new(); self.webtriggers.iter().for_each(|webtriggers| { - functions_to_scan.push( - Entrypoint { - function: webtriggers.function, - invokable: false, - web_trigger: true, - } - ); - + functions_to_scan.push(Entrypoint { + function: webtriggers.function, + invokable: false, + web_trigger: true, + }); }); self.event_triggers.iter().for_each(|event_triggers| { - functions_to_scan.push( - Entrypoint { - function: event_triggers.raw.function, - invokable: false, - web_trigger: true, - } - ); + functions_to_scan.push(Entrypoint { + function: event_triggers.raw.function, + invokable: false, + web_trigger: true, + }); }); - self.scheduled_triggers.iter().for_each(|schedule_triggers| { - functions_to_scan.push( - Entrypoint { + self.scheduled_triggers + .iter() + .for_each(|schedule_triggers| { + functions_to_scan.push(Entrypoint { function: schedule_triggers.raw.function, invokable: false, web_trigger: true, @@ -361,96 +353,87 @@ impl<'a> ForgeModules<'a> { // create arrays representing functions that expose user non-invokable functions self.consumers.iter().for_each(|consumers| { - functions_to_scan.push( - Entrypoint { - function: consumers.resolver.function, - invokable: true, - web_trigger: false, - } - ) + functions_to_scan.push(Entrypoint { + function: consumers.resolver.function, + invokable: true, + web_trigger: false, + }) }); self.data_provider.iter().for_each(|dataprovider| { - functions_to_scan.push( - Entrypoint { - function: dataprovider.callback.function, - invokable: true, - web_trigger: false, - } - ) + functions_to_scan.push(Entrypoint { + function: dataprovider.callback.function, + invokable: true, + web_trigger: false, + }) }); self.custom_field.iter().for_each(|customfield| { - functions_to_scan.push( - Entrypoint { - function: customfield.value, - invokable: true, - web_trigger: false, - } - ); - functions_to_scan.push( - Entrypoint { - function: customfield.search_suggestion, + if let Some(value) = customfield.value { + functions_to_scan.push(Entrypoint { + function: value, invokable: true, web_trigger: false, - } - ); - functions_to_scan.push( - Entrypoint { - function: customfield.function, + }); + } + + functions_to_scan.push(Entrypoint { + function: customfield.search_suggestions, + invokable: true, + web_trigger: false, + }); + + if let Some(func) = customfield.function { + functions_to_scan.push(Entrypoint { + function: func, invokable: true, web_trigger: false, - } - ); - functions_to_scan.push( - Entrypoint { - function: customfield.edit, + }); + } + + if let Some(edit) = customfield.edit { + functions_to_scan.push(Entrypoint { + function: edit, invokable: true, web_trigger: false, - } - ); + }); + } - functions_to_scan.push( - Entrypoint { - function: customfield.resolver.function, + if let Some(resolver) = &customfield.resolver { + functions_to_scan.push(Entrypoint { + function: resolver.function, invokable: true, web_trigger: false, - } - ); - + }); + } }); self.ui_modifications.iter().for_each(|ui| { - functions_to_scan.push( - Entrypoint { - function: ui.resolver.function, - invokable: true, - web_trigger: false, - } - ) + functions_to_scan.push(Entrypoint { + function: ui.resolver.function, + invokable: true, + web_trigger: false, + }) }); self.workflow_validator.iter().for_each(|validator| { - functions_to_scan.push( - Entrypoint { - function: validator.function, - invokable: true, - web_trigger: false, - } - ); + functions_to_scan.push(Entrypoint { + function: validator.function, + invokable: true, + web_trigger: false, + }); - functions_to_scan.push( - Entrypoint { - function: validator.resolver.function, - invokable: true, - web_trigger: false, - } - ); + functions_to_scan.push(Entrypoint { + function: validator.resolver.function, + invokable: true, + web_trigger: false, + }); }); - - self.workflow_post_function.iter().for_each(|post_function| { - functions_to_scan.push( - Entrypoint { + + self.workflow_post_function + .iter() + .for_each(|post_function| { + functions_to_scan.push(Entrypoint { function: post_function.function, invokable: true, web_trigger: false, @@ -460,7 +443,7 @@ impl<'a> ForgeModules<'a> { // get user invokable modules that have additional exposure endpoints. // ie macros has config and export fields on top of resolver fields that are functions for macros in self.macros { - if let Some(resolver)= macros.resolver { + if let Some(resolver) = macros.resolver { functions_to_scan.push(Entrypoint { function: resolver.function, invokable: true, @@ -468,14 +451,14 @@ impl<'a> ForgeModules<'a> { }) } - if let Some(config)= macros.config { + if let Some(config) = macros.config { functions_to_scan.push(Entrypoint { function: config.function, invokable: true, web_trigger: false, }) } - if let Some(export)= macros.export { + if let Some(export) = macros.export { functions_to_scan.push(Entrypoint { function: export.function, invokable: true, @@ -490,7 +473,7 @@ impl<'a> ForgeModules<'a> { invokable: true, web_trigger: false, }); - if let Some(resolver)= contentitem.resolver { + if let Some(resolver) = contentitem.resolver { functions_to_scan.push(Entrypoint { function: resolver.function, invokable: true, @@ -498,7 +481,7 @@ impl<'a> ForgeModules<'a> { }) } - if let Some(dynamic_properties)= contentitem.dynamic_properties { + if let Some(dynamic_properties) = contentitem.dynamic_properties { functions_to_scan.push(Entrypoint { function: dynamic_properties.function, invokable: true, @@ -513,7 +496,7 @@ impl<'a> ForgeModules<'a> { invokable: true, web_trigger: false, }); - if let Some(resolver)= issue.resolver { + if let Some(resolver) = issue.resolver { functions_to_scan.push(Entrypoint { function: resolver.function, invokable: true, @@ -521,7 +504,7 @@ impl<'a> ForgeModules<'a> { }) } - if let Some(dynamic_properties)= issue.dynamic_properties { + if let Some(dynamic_properties) = issue.dynamic_properties { functions_to_scan.push(Entrypoint { function: dynamic_properties.function, invokable: true, @@ -544,7 +527,7 @@ impl<'a> ForgeModules<'a> { }) } - if let Some(start)= access.start_import { + if let Some(start) = access.start_import { functions_to_scan.push(Entrypoint { function: start.function, invokable: true, @@ -552,7 +535,7 @@ impl<'a> ForgeModules<'a> { }) } - if let Some(stop)= access.stop_import { + if let Some(stop) = access.stop_import { functions_to_scan.push(Entrypoint { function: stop.function, invokable: true, @@ -560,7 +543,7 @@ impl<'a> ForgeModules<'a> { }) } - if let Some(status)= access.import_status { + if let Some(status) = access.import_status { functions_to_scan.push(Entrypoint { function: status.function, invokable: true, @@ -588,7 +571,7 @@ impl<'a> ForgeModules<'a> { }); } } - + functions_to_scan } } @@ -706,7 +689,7 @@ mod tests { assert_eq!(manifest.app.id, "my-app"); assert_eq!(manifest.modules.macros.len(), 1); assert_eq!(manifest.modules.macros[0].key, "My Macro"); - assert_eq!(manifest.modules.macros[0].function, "my-macro"); + // assert_eq!(manifest.modules.macros[0].function, "my-macro"); assert_eq!(manifest.modules.functions.len(), 1); assert_eq!( manifest.modules.functions[0], @@ -798,7 +781,10 @@ mod tests { }"#; let manifest: ForgeManifest = serde_json::from_str(json).unwrap(); assert_eq!(manifest.modules.macros.len(), 1); - assert_eq!(manifest.modules.macros[0].function, "Catch-me-if-you-can0"); + + if let Some(func) = manifest.modules.macros[0].function { + assert_eq!(func, "Catch-me-if-you-can0"); + } if let Some(func) = &manifest.modules.macros[0].resolver { assert_eq!(func.function, "Catch-me-if-you-can1"); From f68bd19a85ce6c964545984978efdff2d0e92992 Mon Sep 17 00:00:00 2001 From: awang7 Date: Mon, 8 Jan 2024 12:14:30 -0800 Subject: [PATCH 263/517] resolving merge conflicts --- crates/forge_loader/src/manifest.rs | 214 +++++++++++++--------------- 1 file changed, 100 insertions(+), 114 deletions(-) diff --git a/crates/forge_loader/src/manifest.rs b/crates/forge_loader/src/manifest.rs index df0db8db..fca0670f 100644 --- a/crates/forge_loader/src/manifest.rs +++ b/crates/forge_loader/src/manifest.rs @@ -35,7 +35,7 @@ struct ModInfo<'a> { #[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] struct MacroMod<'a> { key: &'a str, - function: &'a str, + function: Option<&'a str>, #[serde(borrow)] resolver: Option>, #[serde(borrow)] @@ -124,11 +124,11 @@ pub struct CustomField<'a> { #[serde(flatten, borrow)] key: &'a str, // all attributes below involve function calls - value: &'a str, - search_suggestion: &'a str, - function: &'a str, - edit: &'a str, - resolver: ModInfo<'a>, + value: Option<&'a str>, + search_suggestions: &'a str, + function: Option<&'a str>, + edit: Option<&'a str>, + resolver: Option>, } #[derive(Debug, Clone, PartialEq, Eq, Deserialize)] @@ -143,12 +143,11 @@ pub struct WorkflowValidator<'a> { #[serde(flatten, borrow)] key: &'a str, function: &'a str, - resolver: ModInfo<'a> + resolver: ModInfo<'a>, } #[derive(Debug, Clone, PartialEq, Eq, Deserialize)] pub struct WorkflowPostFunction<'a> { - #[serde(flatten, borrow)] key: &'a str, function: &'a str, } @@ -274,7 +273,7 @@ pub enum FunctionTy { } // Struct used for tracking what scan a funtion requires. -#[derive(Debug, Clone, PartialEq, Eq, Deserialize)] +#[derive(Debug, Clone, PartialEq, Eq)] pub struct Entrypoint<'a> { pub function: &'a str, pub invokable: bool, @@ -318,41 +317,34 @@ impl AsRef for FunctionTy { } impl<'a> ForgeModules<'a> { - -// TODO: function returns iterator where each item is some specified type. - pub fn into_analyzable_functions ( - self, - ) -> Vec>{ + // TODO: function returns iterator where each item is some specified type. + pub fn into_analyzable_functions(self) -> Vec> { // number of webtriggers are usually low, so it's better to just sort them and reuse - self.webtriggers.iter(). - .sort_unstable_by_key(|trigger| trigger.function); + // self.webtriggers.iter(). + // .sort_unstable_by_key(|trigger| trigger.function); // Get all the Triggers and represent them as a new struct thing where "webtrigger" attribute is true // for all trigger things let mut functions_to_scan = Vec::new(); self.webtriggers.iter().for_each(|webtriggers| { - functions_to_scan.push( - Entrypoint { - function: webtriggers.function, - invokable: false, - web_trigger: true, - } - ); - + functions_to_scan.push(Entrypoint { + function: webtriggers.function, + invokable: false, + web_trigger: true, + }); }); self.event_triggers.iter().for_each(|event_triggers| { - functions_to_scan.push( - Entrypoint { - function: event_triggers.raw.function, - invokable: false, - web_trigger: true, - } - ); + functions_to_scan.push(Entrypoint { + function: event_triggers.raw.function, + invokable: false, + web_trigger: true, + }); }); - self.scheduled_triggers.iter().for_each(|schedule_triggers| { - functions_to_scan.push( - Entrypoint { + self.scheduled_triggers + .iter() + .for_each(|schedule_triggers| { + functions_to_scan.push(Entrypoint { function: schedule_triggers.raw.function, invokable: false, web_trigger: true, @@ -361,96 +353,87 @@ impl<'a> ForgeModules<'a> { // create arrays representing functions that expose user non-invokable functions self.consumers.iter().for_each(|consumers| { - functions_to_scan.push( - Entrypoint { - function: consumers.resolver.function, - invokable: true, - web_trigger: false, - } - ) + functions_to_scan.push(Entrypoint { + function: consumers.resolver.function, + invokable: true, + web_trigger: false, + }) }); self.data_provider.iter().for_each(|dataprovider| { - functions_to_scan.push( - Entrypoint { - function: dataprovider.callback.function, - invokable: true, - web_trigger: false, - } - ) + functions_to_scan.push(Entrypoint { + function: dataprovider.callback.function, + invokable: true, + web_trigger: false, + }) }); self.custom_field.iter().for_each(|customfield| { - functions_to_scan.push( - Entrypoint { - function: customfield.value, - invokable: true, - web_trigger: false, - } - ); - functions_to_scan.push( - Entrypoint { - function: customfield.search_suggestion, + if let Some(value) = customfield.value { + functions_to_scan.push(Entrypoint { + function: value, invokable: true, web_trigger: false, - } - ); - functions_to_scan.push( - Entrypoint { - function: customfield.function, + }); + } + + functions_to_scan.push(Entrypoint { + function: customfield.search_suggestions, + invokable: true, + web_trigger: false, + }); + + if let Some(func) = customfield.function { + functions_to_scan.push(Entrypoint { + function: func, invokable: true, web_trigger: false, - } - ); - functions_to_scan.push( - Entrypoint { - function: customfield.edit, + }); + } + + if let Some(edit) = customfield.edit { + functions_to_scan.push(Entrypoint { + function: edit, invokable: true, web_trigger: false, - } - ); + }); + } - functions_to_scan.push( - Entrypoint { - function: customfield.resolver.function, + if let Some(resolver) = &customfield.resolver { + functions_to_scan.push(Entrypoint { + function: resolver.function, invokable: true, web_trigger: false, - } - ); - + }); + } }); self.ui_modifications.iter().for_each(|ui| { - functions_to_scan.push( - Entrypoint { - function: ui.resolver.function, - invokable: true, - web_trigger: false, - } - ) + functions_to_scan.push(Entrypoint { + function: ui.resolver.function, + invokable: true, + web_trigger: false, + }) }); self.workflow_validator.iter().for_each(|validator| { - functions_to_scan.push( - Entrypoint { - function: validator.function, - invokable: true, - web_trigger: false, - } - ); + functions_to_scan.push(Entrypoint { + function: validator.function, + invokable: true, + web_trigger: false, + }); - functions_to_scan.push( - Entrypoint { - function: validator.resolver.function, - invokable: true, - web_trigger: false, - } - ); + functions_to_scan.push(Entrypoint { + function: validator.resolver.function, + invokable: true, + web_trigger: false, + }); }); - - self.workflow_post_function.iter().for_each(|post_function| { - functions_to_scan.push( - Entrypoint { + + self.workflow_post_function + .iter() + .for_each(|post_function| { + functions_to_scan.push(Entrypoint { function: post_function.function, invokable: true, web_trigger: false, @@ -460,7 +443,7 @@ impl<'a> ForgeModules<'a> { // get user invokable modules that have additional exposure endpoints. // ie macros has config and export fields on top of resolver fields that are functions for macros in self.macros { - if let Some(resolver)= macros.resolver { + if let Some(resolver) = macros.resolver { functions_to_scan.push(Entrypoint { function: resolver.function, invokable: true, @@ -468,14 +451,14 @@ impl<'a> ForgeModules<'a> { }) } - if let Some(config)= macros.config { + if let Some(config) = macros.config { functions_to_scan.push(Entrypoint { function: config.function, invokable: true, web_trigger: false, }) } - if let Some(export)= macros.export { + if let Some(export) = macros.export { functions_to_scan.push(Entrypoint { function: export.function, invokable: true, @@ -490,7 +473,7 @@ impl<'a> ForgeModules<'a> { invokable: true, web_trigger: false, }); - if let Some(resolver)= contentitem.resolver { + if let Some(resolver) = contentitem.resolver { functions_to_scan.push(Entrypoint { function: resolver.function, invokable: true, @@ -498,7 +481,7 @@ impl<'a> ForgeModules<'a> { }) } - if let Some(dynamic_properties)= contentitem.dynamic_properties { + if let Some(dynamic_properties) = contentitem.dynamic_properties { functions_to_scan.push(Entrypoint { function: dynamic_properties.function, invokable: true, @@ -513,7 +496,7 @@ impl<'a> ForgeModules<'a> { invokable: true, web_trigger: false, }); - if let Some(resolver)= issue.resolver { + if let Some(resolver) = issue.resolver { functions_to_scan.push(Entrypoint { function: resolver.function, invokable: true, @@ -521,7 +504,7 @@ impl<'a> ForgeModules<'a> { }) } - if let Some(dynamic_properties)= issue.dynamic_properties { + if let Some(dynamic_properties) = issue.dynamic_properties { functions_to_scan.push(Entrypoint { function: dynamic_properties.function, invokable: true, @@ -544,7 +527,7 @@ impl<'a> ForgeModules<'a> { }) } - if let Some(start)= access.start_import { + if let Some(start) = access.start_import { functions_to_scan.push(Entrypoint { function: start.function, invokable: true, @@ -552,7 +535,7 @@ impl<'a> ForgeModules<'a> { }) } - if let Some(stop)= access.stop_import { + if let Some(stop) = access.stop_import { functions_to_scan.push(Entrypoint { function: stop.function, invokable: true, @@ -560,7 +543,7 @@ impl<'a> ForgeModules<'a> { }) } - if let Some(status)= access.import_status { + if let Some(status) = access.import_status { functions_to_scan.push(Entrypoint { function: status.function, invokable: true, @@ -588,7 +571,7 @@ impl<'a> ForgeModules<'a> { }); } } - + functions_to_scan } } @@ -706,7 +689,7 @@ mod tests { assert_eq!(manifest.app.id, "my-app"); assert_eq!(manifest.modules.macros.len(), 1); assert_eq!(manifest.modules.macros[0].key, "My Macro"); - assert_eq!(manifest.modules.macros[0].function, "my-macro"); + // assert_eq!(manifest.modules.macros[0].function, "my-macro"); assert_eq!(manifest.modules.functions.len(), 1); assert_eq!( manifest.modules.functions[0], @@ -798,7 +781,10 @@ mod tests { }"#; let manifest: ForgeManifest = serde_json::from_str(json).unwrap(); assert_eq!(manifest.modules.macros.len(), 1); - assert_eq!(manifest.modules.macros[0].function, "Catch-me-if-you-can0"); + + if let Some(func) = manifest.modules.macros[0].function { + assert_eq!(func, "Catch-me-if-you-can0"); + } if let Some(func) = &manifest.modules.macros[0].resolver { assert_eq!(func.function, "Catch-me-if-you-can1"); From 95b735ecb545999c202184a70d0e573af3860d1b Mon Sep 17 00:00:00 2001 From: awang7 Date: Mon, 8 Jan 2024 12:15:17 -0800 Subject: [PATCH 264/517] resolving merge conflicts --- crates/forge_loader/src/manifest.rs | 129 ++++++++++++++-------------- crates/fsrt/src/main.rs | 14 ++- 2 files changed, 76 insertions(+), 67 deletions(-) diff --git a/crates/forge_loader/src/manifest.rs b/crates/forge_loader/src/manifest.rs index fca0670f..a7b5bbdb 100644 --- a/crates/forge_loader/src/manifest.rs +++ b/crates/forge_loader/src/manifest.rs @@ -1,6 +1,6 @@ use std::{ borrow::Borrow, - collections::{BTreeSet, HashSet}, + collections::HashSet, hash::Hash, path::{Path, PathBuf}, }; @@ -9,7 +9,7 @@ use crate::{forgepermissions::ForgePermissions, Error}; use forge_utils::FxHashMap; use itertools::{Either, Itertools}; use serde::Deserialize; -use serde_json::map::Entry; +use std::collections::BTreeMap; use tracing::trace; #[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] @@ -26,56 +26,44 @@ pub struct FunctionMod<'a> { providers: Option>, } -// Modified +// Abstracting away key, function, and resolver into a single struct for reuse whoo! #[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] -struct ModInfo<'a> { +struct CommonKey<'a> { + key: &'a str, function: &'a str, + resolver: Option<&'a str>, } #[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] struct MacroMod<'a> { - key: &'a str, - function: Option<&'a str>, - #[serde(borrow)] - resolver: Option>, - #[serde(borrow)] - config: Option>, - #[serde(borrow)] - export: Option>, + #[serde(flatten, borrow)] + common_keys: CommonKey<'a>, + config: Option<&'a str>, + export: Option<&'a str>, } #[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] struct ContentByLineItem<'a> { - key: &'a str, - function: &'a str, - #[serde(borrow)] - resolver: Option>, + #[serde(flatten, borrow)] + common_keys: CommonKey<'a>, #[serde(borrow)] - dynamic_properties: Option>, + dynamic_properties: Option<&'a str>, } #[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] struct IssueGlance<'a> { - key: &'a str, - function: &'a str, - #[serde(borrow)] - resolver: Option>, - #[serde(borrow)] - dynamic_properties: Option>, + #[serde(flatten, borrow)] + common_keys: CommonKey<'a>, + dynamic_properties: Option<&'a str>, } - #[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] struct AccessImportType<'a> { - key: &'a str, - function: &'a str, - #[serde(borrow)] - one_delete_import: Option>, - #[serde(borrow)] - start_import: Option>, - #[serde(borrow)] - stop_import: Option>, - #[serde(borrow)] - import_status: Option>, + #[serde(flatten, borrow)] + common_keys: CommonKey<'a>, + one_delete_import: Option<&'a str>, + start_import: Option<&'a str>, + stop_import: Option<&'a str>, + import_status: Option<&'a str>, } // WebTrigger => RawTrigger; WHY IS THIS NAMED DIFFERENTLY !? WHO CHANGED NAMES @@ -113,43 +101,38 @@ struct ScheduledTrigger<'a> { // compass DataProvider module #[derive(Debug, Clone, PartialEq, Eq, Deserialize)] pub struct DataProvider<'a> { - key: &'a str, #[serde(flatten, borrow)] - callback: ModInfo<'a>, + key: &'a str, + callback: Option<&'a str>, } // Struct for Custom field Module. Check that search suggestion gets read in correctly. #[derive(Debug, Clone, PartialEq, Eq, Deserialize)] pub struct CustomField<'a> { - #[serde(flatten, borrow)] - key: &'a str, // all attributes below involve function calls + #[serde(flatten, borrow)] + common_keys: CommonKey<'a>, value: Option<&'a str>, search_suggestions: &'a str, - function: Option<&'a str>, edit: Option<&'a str>, - resolver: Option>, } #[derive(Debug, Clone, PartialEq, Eq, Deserialize)] pub struct UiModificatons<'a> { - key: &'a str, #[serde(flatten, borrow)] - resolver: ModInfo<'a>, + common_key: CommonKey<'a>, } #[derive(Debug, Clone, PartialEq, Eq, Deserialize)] pub struct WorkflowValidator<'a> { #[serde(flatten, borrow)] - key: &'a str, - function: &'a str, - resolver: ModInfo<'a>, + common_keys: CommonKey<'a>, } #[derive(Debug, Clone, PartialEq, Eq, Deserialize)] pub struct WorkflowPostFunction<'a> { - key: &'a str, - function: &'a str, + #[serde(flatten, borrow)] + common_keys: CommonKey<'a>, } // Add more structs here for deserializing forge modules @@ -318,37 +301,51 @@ impl AsRef for FunctionTy { impl<'a> ForgeModules<'a> { // TODO: function returns iterator where each item is some specified type. - pub fn into_analyzable_functions(self) -> Vec> { + pub fn into_analyzable_functions(&mut self) -> Vec> { // number of webtriggers are usually low, so it's better to just sort them and reuse - // self.webtriggers.iter(). - // .sort_unstable_by_key(|trigger| trigger.function); + self.webtriggers + .sort_unstable_by_key(|trigger| trigger.function); // Get all the Triggers and represent them as a new struct thing where "webtrigger" attribute is true // for all trigger things - let mut functions_to_scan = Vec::new(); + let mut functions_to_scan = BTreeMap::new(); + + // Get all functions for module from manifest.yml + self.functions.iter().for_each(|func| { + functions_to_scan.insert( + func.handler, + Entrypoint { + function: func.handler, + invokable: false, + web_trigger: false, + }, + ); + }); + self.webtriggers.iter().for_each(|webtriggers| { - functions_to_scan.push(Entrypoint { - function: webtriggers.function, - invokable: false, - web_trigger: true, - }); + if functions_to_scan.contains_key(webtriggers.function) { + if let Some(entry) = functions_to_scan.get_mut(webtriggers.function) { + entry.web_trigger = true; + } + } }); + self.event_triggers.iter().for_each(|event_triggers| { - functions_to_scan.push(Entrypoint { - function: event_triggers.raw.function, - invokable: false, - web_trigger: true, - }); + if functions_to_scan.contains_key(event_triggers.raw.function) { + if let Some(entry) = functions_to_scan.get_mut(event_triggers.raw.function) { + entry.web_trigger = true; + } + } }); self.scheduled_triggers .iter() .for_each(|schedule_triggers| { - functions_to_scan.push(Entrypoint { - function: schedule_triggers.raw.function, - invokable: false, - web_trigger: true, - }) + if functions_to_scan.contains_key(schedule_triggers.raw.function) { + if let Some(entry) = functions_to_scan.get_mut(schedule_triggers.raw.function) { + entry.web_trigger = true; + } + } }); // create arrays representing functions that expose user non-invokable functions diff --git a/crates/fsrt/src/main.rs b/crates/fsrt/src/main.rs index 4b345ef9..ad2ebf7b 100644 --- a/crates/fsrt/src/main.rs +++ b/crates/fsrt/src/main.rs @@ -88,7 +88,19 @@ struct Args { dirs: Vec, } +<<<<<<< HEAD struct ForgeProject { +======= +#[derive(Debug, Clone, Default)] +struct Opts { + dump_cfg: bool, + dump_callgraph: bool, + appkey: Option, + out: Option, +} + +struct ForgeProject<'a> { +>>>>>>> 7e86f46 (abstracted structs to use a CommonKeys struct that holds: key, function, and resolver for less code duplication. Updated into_analayzable method to update values based on functions specified in function mod. TODO: Update rest of non trigger modules to update mapping to functions to scan from function mod) #[allow(dead_code)] sm: Arc, ctx: AppCtx, @@ -98,7 +110,7 @@ struct ForgeProject { >>>>>>> cd2ed7b (edited main to iterate over vector of functions. TODO: edit add_funcs in main.rs and test into_analyzble_function use case in main.rs) } -impl ForgeProject { +impl ForgeProject<'_> { #[instrument(skip(src, iter))] fn with_files_and_sourceroot, I: IntoIterator>( src: P, From 3770bfe0d55b4cd527a09847a829cd36a6a57623 Mon Sep 17 00:00:00 2001 From: awang7 Date: Mon, 8 Jan 2024 12:15:17 -0800 Subject: [PATCH 265/517] resolving merge conflicts --- crates/forge_loader/src/manifest.rs | 129 ++++++++++++++-------------- crates/fsrt/src/main.rs | 14 ++- 2 files changed, 76 insertions(+), 67 deletions(-) diff --git a/crates/forge_loader/src/manifest.rs b/crates/forge_loader/src/manifest.rs index fca0670f..a7b5bbdb 100644 --- a/crates/forge_loader/src/manifest.rs +++ b/crates/forge_loader/src/manifest.rs @@ -1,6 +1,6 @@ use std::{ borrow::Borrow, - collections::{BTreeSet, HashSet}, + collections::HashSet, hash::Hash, path::{Path, PathBuf}, }; @@ -9,7 +9,7 @@ use crate::{forgepermissions::ForgePermissions, Error}; use forge_utils::FxHashMap; use itertools::{Either, Itertools}; use serde::Deserialize; -use serde_json::map::Entry; +use std::collections::BTreeMap; use tracing::trace; #[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] @@ -26,56 +26,44 @@ pub struct FunctionMod<'a> { providers: Option>, } -// Modified +// Abstracting away key, function, and resolver into a single struct for reuse whoo! #[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] -struct ModInfo<'a> { +struct CommonKey<'a> { + key: &'a str, function: &'a str, + resolver: Option<&'a str>, } #[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] struct MacroMod<'a> { - key: &'a str, - function: Option<&'a str>, - #[serde(borrow)] - resolver: Option>, - #[serde(borrow)] - config: Option>, - #[serde(borrow)] - export: Option>, + #[serde(flatten, borrow)] + common_keys: CommonKey<'a>, + config: Option<&'a str>, + export: Option<&'a str>, } #[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] struct ContentByLineItem<'a> { - key: &'a str, - function: &'a str, - #[serde(borrow)] - resolver: Option>, + #[serde(flatten, borrow)] + common_keys: CommonKey<'a>, #[serde(borrow)] - dynamic_properties: Option>, + dynamic_properties: Option<&'a str>, } #[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] struct IssueGlance<'a> { - key: &'a str, - function: &'a str, - #[serde(borrow)] - resolver: Option>, - #[serde(borrow)] - dynamic_properties: Option>, + #[serde(flatten, borrow)] + common_keys: CommonKey<'a>, + dynamic_properties: Option<&'a str>, } - #[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] struct AccessImportType<'a> { - key: &'a str, - function: &'a str, - #[serde(borrow)] - one_delete_import: Option>, - #[serde(borrow)] - start_import: Option>, - #[serde(borrow)] - stop_import: Option>, - #[serde(borrow)] - import_status: Option>, + #[serde(flatten, borrow)] + common_keys: CommonKey<'a>, + one_delete_import: Option<&'a str>, + start_import: Option<&'a str>, + stop_import: Option<&'a str>, + import_status: Option<&'a str>, } // WebTrigger => RawTrigger; WHY IS THIS NAMED DIFFERENTLY !? WHO CHANGED NAMES @@ -113,43 +101,38 @@ struct ScheduledTrigger<'a> { // compass DataProvider module #[derive(Debug, Clone, PartialEq, Eq, Deserialize)] pub struct DataProvider<'a> { - key: &'a str, #[serde(flatten, borrow)] - callback: ModInfo<'a>, + key: &'a str, + callback: Option<&'a str>, } // Struct for Custom field Module. Check that search suggestion gets read in correctly. #[derive(Debug, Clone, PartialEq, Eq, Deserialize)] pub struct CustomField<'a> { - #[serde(flatten, borrow)] - key: &'a str, // all attributes below involve function calls + #[serde(flatten, borrow)] + common_keys: CommonKey<'a>, value: Option<&'a str>, search_suggestions: &'a str, - function: Option<&'a str>, edit: Option<&'a str>, - resolver: Option>, } #[derive(Debug, Clone, PartialEq, Eq, Deserialize)] pub struct UiModificatons<'a> { - key: &'a str, #[serde(flatten, borrow)] - resolver: ModInfo<'a>, + common_key: CommonKey<'a>, } #[derive(Debug, Clone, PartialEq, Eq, Deserialize)] pub struct WorkflowValidator<'a> { #[serde(flatten, borrow)] - key: &'a str, - function: &'a str, - resolver: ModInfo<'a>, + common_keys: CommonKey<'a>, } #[derive(Debug, Clone, PartialEq, Eq, Deserialize)] pub struct WorkflowPostFunction<'a> { - key: &'a str, - function: &'a str, + #[serde(flatten, borrow)] + common_keys: CommonKey<'a>, } // Add more structs here for deserializing forge modules @@ -318,37 +301,51 @@ impl AsRef for FunctionTy { impl<'a> ForgeModules<'a> { // TODO: function returns iterator where each item is some specified type. - pub fn into_analyzable_functions(self) -> Vec> { + pub fn into_analyzable_functions(&mut self) -> Vec> { // number of webtriggers are usually low, so it's better to just sort them and reuse - // self.webtriggers.iter(). - // .sort_unstable_by_key(|trigger| trigger.function); + self.webtriggers + .sort_unstable_by_key(|trigger| trigger.function); // Get all the Triggers and represent them as a new struct thing where "webtrigger" attribute is true // for all trigger things - let mut functions_to_scan = Vec::new(); + let mut functions_to_scan = BTreeMap::new(); + + // Get all functions for module from manifest.yml + self.functions.iter().for_each(|func| { + functions_to_scan.insert( + func.handler, + Entrypoint { + function: func.handler, + invokable: false, + web_trigger: false, + }, + ); + }); + self.webtriggers.iter().for_each(|webtriggers| { - functions_to_scan.push(Entrypoint { - function: webtriggers.function, - invokable: false, - web_trigger: true, - }); + if functions_to_scan.contains_key(webtriggers.function) { + if let Some(entry) = functions_to_scan.get_mut(webtriggers.function) { + entry.web_trigger = true; + } + } }); + self.event_triggers.iter().for_each(|event_triggers| { - functions_to_scan.push(Entrypoint { - function: event_triggers.raw.function, - invokable: false, - web_trigger: true, - }); + if functions_to_scan.contains_key(event_triggers.raw.function) { + if let Some(entry) = functions_to_scan.get_mut(event_triggers.raw.function) { + entry.web_trigger = true; + } + } }); self.scheduled_triggers .iter() .for_each(|schedule_triggers| { - functions_to_scan.push(Entrypoint { - function: schedule_triggers.raw.function, - invokable: false, - web_trigger: true, - }) + if functions_to_scan.contains_key(schedule_triggers.raw.function) { + if let Some(entry) = functions_to_scan.get_mut(schedule_triggers.raw.function) { + entry.web_trigger = true; + } + } }); // create arrays representing functions that expose user non-invokable functions diff --git a/crates/fsrt/src/main.rs b/crates/fsrt/src/main.rs index 4b345ef9..ad2ebf7b 100644 --- a/crates/fsrt/src/main.rs +++ b/crates/fsrt/src/main.rs @@ -88,7 +88,19 @@ struct Args { dirs: Vec, } +<<<<<<< HEAD struct ForgeProject { +======= +#[derive(Debug, Clone, Default)] +struct Opts { + dump_cfg: bool, + dump_callgraph: bool, + appkey: Option, + out: Option, +} + +struct ForgeProject<'a> { +>>>>>>> 7e86f46 (abstracted structs to use a CommonKeys struct that holds: key, function, and resolver for less code duplication. Updated into_analayzable method to update values based on functions specified in function mod. TODO: Update rest of non trigger modules to update mapping to functions to scan from function mod) #[allow(dead_code)] sm: Arc, ctx: AppCtx, @@ -98,7 +110,7 @@ struct ForgeProject { >>>>>>> cd2ed7b (edited main to iterate over vector of functions. TODO: edit add_funcs in main.rs and test into_analyzble_function use case in main.rs) } -impl ForgeProject { +impl ForgeProject<'_> { #[instrument(skip(src, iter))] fn with_files_and_sourceroot, I: IntoIterator>( src: P, From e9aa82a25c39b9e6fa5ee8530e1e481c45897d20 Mon Sep 17 00:00:00 2001 From: awang7 Date: Wed, 20 Sep 2023 17:59:39 -0400 Subject: [PATCH 266/517] finished updating methods to check functions_to_scan and update entrypoint struct when needed. TODO: test and toggle function call in main.rs --- crates/forge_loader/src/manifest.rs | 304 +++++++++++++--------------- crates/fsrt/src/main.rs | 4 +- 2 files changed, 140 insertions(+), 168 deletions(-) diff --git a/crates/forge_loader/src/manifest.rs b/crates/forge_loader/src/manifest.rs index a7b5bbdb..a971e388 100644 --- a/crates/forge_loader/src/manifest.rs +++ b/crates/forge_loader/src/manifest.rs @@ -120,7 +120,7 @@ pub struct CustomField<'a> { #[derive(Debug, Clone, PartialEq, Eq, Deserialize)] pub struct UiModificatons<'a> { #[serde(flatten, borrow)] - common_key: CommonKey<'a>, + common_keys: CommonKey<'a>, } #[derive(Debug, Clone, PartialEq, Eq, Deserialize)] @@ -301,7 +301,7 @@ impl AsRef for FunctionTy { impl<'a> ForgeModules<'a> { // TODO: function returns iterator where each item is some specified type. - pub fn into_analyzable_functions(&mut self) -> Vec> { + pub fn into_analyzable_functions(&mut self) -> BTreeMap<&'a str, Entrypoint<'a>> { // number of webtriggers are usually low, so it's better to just sort them and reuse self.webtriggers .sort_unstable_by_key(|trigger| trigger.function); @@ -314,9 +314,9 @@ impl<'a> ForgeModules<'a> { // Get all functions for module from manifest.yml self.functions.iter().for_each(|func| { functions_to_scan.insert( - func.handler, + func.key, Entrypoint { - function: func.handler, + function: func.key, invokable: false, web_trigger: false, }, @@ -349,223 +349,195 @@ impl<'a> ForgeModules<'a> { }); // create arrays representing functions that expose user non-invokable functions - self.consumers.iter().for_each(|consumers| { - functions_to_scan.push(Entrypoint { - function: consumers.resolver.function, - invokable: true, - web_trigger: false, - }) + self.consumers.iter().for_each(|consumer| { + if functions_to_scan.contains_key(consumer.resolver.function) { + if let Some(entry) = functions_to_scan.get_mut(consumer.resolver.function) { + entry.invokable = true; + } + } }); self.data_provider.iter().for_each(|dataprovider| { - functions_to_scan.push(Entrypoint { - function: dataprovider.callback.function, - invokable: true, - web_trigger: false, - }) + if let Some(call) = dataprovider.callback { + if let Some(entry) = functions_to_scan.get_mut(call) { + entry.invokable = true; + } + } }); self.custom_field.iter().for_each(|customfield| { if let Some(value) = customfield.value { - functions_to_scan.push(Entrypoint { - function: value, - invokable: true, - web_trigger: false, - }); + if let Some(entry) = functions_to_scan.get_mut(value) { + entry.invokable = true; + } } - functions_to_scan.push(Entrypoint { - function: customfield.search_suggestions, - invokable: true, - web_trigger: false, - }); + if let Some(entry) = functions_to_scan.get_mut(customfield.search_suggestions) { + entry.invokable = true; + } - if let Some(func) = customfield.function { - functions_to_scan.push(Entrypoint { - function: func, - invokable: true, - web_trigger: false, - }); + if let Some(entry) = functions_to_scan.get_mut(customfield.common_keys.function) { + entry.invokable = true; } - if let Some(edit) = customfield.edit { - functions_to_scan.push(Entrypoint { - function: edit, - invokable: true, - web_trigger: false, - }); + if let Some(resolver) = customfield.common_keys.resolver { + if let Some(entry) = functions_to_scan.get_mut(resolver) { + entry.invokable = true; + } } - if let Some(resolver) = &customfield.resolver { - functions_to_scan.push(Entrypoint { - function: resolver.function, - invokable: true, - web_trigger: false, - }); + if let Some(edit) = customfield.edit { + if let Some(entry) = functions_to_scan.get_mut(edit) { + entry.invokable = true; + } } }); self.ui_modifications.iter().for_each(|ui| { - functions_to_scan.push(Entrypoint { - function: ui.resolver.function, - invokable: true, - web_trigger: false, - }) + if let Some(entry) = functions_to_scan.get_mut(ui.common_keys.function) { + entry.invokable = true; + } + + if let Some(resolver) = ui.common_keys.resolver { + if let Some(entry) = functions_to_scan.get_mut(resolver) { + entry.invokable = true; + } + } }); self.workflow_validator.iter().for_each(|validator| { - functions_to_scan.push(Entrypoint { - function: validator.function, - invokable: true, - web_trigger: false, - }); + if let Some(entry) = functions_to_scan.get_mut(validator.common_keys.function) { + entry.invokable = true; + } - functions_to_scan.push(Entrypoint { - function: validator.resolver.function, - invokable: true, - web_trigger: false, - }); + if let Some(resolver) = validator.common_keys.resolver { + if let Some(entry) = functions_to_scan.get_mut(resolver) { + entry.invokable = true; + } + } }); self.workflow_post_function .iter() .for_each(|post_function| { - functions_to_scan.push(Entrypoint { - function: post_function.function, - invokable: true, - web_trigger: false, - }) + if let Some(entry) = functions_to_scan.get_mut(post_function.common_keys.function) { + entry.invokable = true; + } + + if let Some(resolver) = post_function.common_keys.resolver { + if let Some(entry) = functions_to_scan.get_mut(resolver) { + entry.invokable = true; + } + } }); // get user invokable modules that have additional exposure endpoints. // ie macros has config and export fields on top of resolver fields that are functions - for macros in self.macros { - if let Some(resolver) = macros.resolver { - functions_to_scan.push(Entrypoint { - function: resolver.function, - invokable: true, - web_trigger: false, - }) + self.macros.iter().for_each(|macros| { + if let Some(resolver) = macros.common_keys.resolver { + if let Some(entry) = functions_to_scan.get_mut(resolver) { + entry.invokable = true; + } } if let Some(config) = macros.config { - functions_to_scan.push(Entrypoint { - function: config.function, - invokable: true, - web_trigger: false, - }) + if let Some(entry) = functions_to_scan.get_mut(config) { + entry.invokable = true; + } } + if let Some(export) = macros.export { - functions_to_scan.push(Entrypoint { - function: export.function, - invokable: true, - web_trigger: false, - }) + if let Some(entry) = functions_to_scan.get_mut(export) { + entry.invokable = true; + } } - } + }); - for contentitem in self.content_by_line_item { - functions_to_scan.push(Entrypoint { - function: contentitem.function, - invokable: true, - web_trigger: false, - }); - if let Some(resolver) = contentitem.resolver { - functions_to_scan.push(Entrypoint { - function: resolver.function, - invokable: true, - web_trigger: false, - }) + self.content_by_line_item.iter().for_each(|contentitem| { + if let Some(entry) = functions_to_scan.get_mut(contentitem.common_keys.function) { + entry.invokable = true; + } + + if let Some(resolver) = contentitem.common_keys.resolver { + if let Some(entry) = functions_to_scan.get_mut(resolver) { + entry.invokable = true; + } } if let Some(dynamic_properties) = contentitem.dynamic_properties { - functions_to_scan.push(Entrypoint { - function: dynamic_properties.function, - invokable: true, - web_trigger: false, - }) + if let Some(entry) = functions_to_scan.get_mut(dynamic_properties) { + entry.invokable = true; + } } - } + }); - for issue in self.issue_glance { - functions_to_scan.push(Entrypoint { - function: issue.function, - invokable: true, - web_trigger: false, - }); - if let Some(resolver) = issue.resolver { - functions_to_scan.push(Entrypoint { - function: resolver.function, - invokable: true, - web_trigger: false, - }) + self.issue_glance.iter().for_each(|issue| { + if let Some(entry) = functions_to_scan.get_mut(issue.common_keys.function) { + entry.invokable = true; + } + + if let Some(resolver) = issue.common_keys.resolver { + if let Some(entry) = functions_to_scan.get_mut(resolver) { + entry.invokable = true; + } } if let Some(dynamic_properties) = issue.dynamic_properties { - functions_to_scan.push(Entrypoint { - function: dynamic_properties.function, - invokable: true, - web_trigger: false, - }) + if let Some(entry) = functions_to_scan.get_mut(dynamic_properties) { + entry.invokable = true; + } + } + }); + + self.access_import_type.iter().for_each(|access| { + if let Some(entry) = functions_to_scan.get_mut(access.common_keys.function) { + entry.invokable = true; + } + + if let Some(resolver) = access.common_keys.resolver { + if let Some(entry) = functions_to_scan.get_mut(resolver) { + entry.invokable = true; + } } - } - for access in self.access_import_type { - functions_to_scan.push(Entrypoint { - function: access.function, - invokable: true, - web_trigger: false, - }); if let Some(delete) = access.one_delete_import { - functions_to_scan.push(Entrypoint { - function: delete.function, - invokable: true, - web_trigger: false, - }) + if let Some(entry) = functions_to_scan.get_mut(delete) { + entry.invokable = true; + } } if let Some(start) = access.start_import { - functions_to_scan.push(Entrypoint { - function: start.function, - invokable: true, - web_trigger: false, - }) + if let Some(entry) = functions_to_scan.get_mut(start) { + entry.invokable = true; + } } if let Some(stop) = access.stop_import { - functions_to_scan.push(Entrypoint { - function: stop.function, - invokable: true, - web_trigger: false, - }) + if let Some(entry) = functions_to_scan.get_mut(stop) { + entry.invokable = true; + } } if let Some(status) = access.import_status { - functions_to_scan.push(Entrypoint { - function: status.function, - invokable: true, - web_trigger: false, - }) + if let Some(entry) = functions_to_scan.get_mut(status) { + entry.invokable = true; + } } - } + }); // get array for user invokable module functions // make alternate_functions all user-invokable functions - for module in self.extra.into_values().flatten() { + for module in self.extra.clone().into_values().flatten() { if let Some(mod_function) = module.function { - functions_to_scan.push(Entrypoint { - function: mod_function, - invokable: true, - web_trigger: false, - }); + if let Some(entry) = functions_to_scan.get_mut(mod_function) { + entry.invokable = true; + } } if let Some(resolver) = module.resolver { - functions_to_scan.push(Entrypoint { - function: resolver.function, - invokable: true, - web_trigger: false, - }); + if let Some(entry) = functions_to_scan.get_mut(resolver.function) { + entry.invokable = true; + } } } @@ -685,7 +657,7 @@ mod tests { assert_eq!(manifest.app.name, Some("My App")); assert_eq!(manifest.app.id, "my-app"); assert_eq!(manifest.modules.macros.len(), 1); - assert_eq!(manifest.modules.macros[0].key, "My Macro"); + assert_eq!(manifest.modules.macros[0].common_keys.key, "My Macro"); // assert_eq!(manifest.modules.macros[0].function, "my-macro"); assert_eq!(manifest.modules.functions.len(), 1); assert_eq!( @@ -778,21 +750,21 @@ mod tests { }"#; let manifest: ForgeManifest = serde_json::from_str(json).unwrap(); assert_eq!(manifest.modules.macros.len(), 1); + assert_eq!( + manifest.modules.macros[0].common_keys.function, + "Catch-me-if-you-can0" + ); - if let Some(func) = manifest.modules.macros[0].function { - assert_eq!(func, "Catch-me-if-you-can0"); - } - - if let Some(func) = &manifest.modules.macros[0].resolver { - assert_eq!(func.function, "Catch-me-if-you-can1"); + if let Some(func) = manifest.modules.macros[0].common_keys.resolver { + assert_eq!(func, "Catch-me-if-you-can1"); } - if let Some(func) = &manifest.modules.macros[0].config { - assert_eq!(func.function, "Catch-me-if-you-can2"); + if let Some(func) = manifest.modules.macros[0].config { + assert_eq!(func, "Catch-me-if-you-can2"); } - if let Some(func) = &manifest.modules.macros[0].export { - assert_eq!(func.function, "Catch-me-if-you-can3"); + if let Some(func) = manifest.modules.macros[0].export { + assert_eq!(func, "Catch-me-if-you-can3"); } } } diff --git a/crates/fsrt/src/main.rs b/crates/fsrt/src/main.rs index ad2ebf7b..2a4f0ba5 100644 --- a/crates/fsrt/src/main.rs +++ b/crates/fsrt/src/main.rs @@ -5,7 +5,7 @@ use forge_permission_resolver::permissions_resolver::{ }; use miette::{IntoDiagnostic, Result}; use std::{ - collections::HashSet, + collections::{HashSet, BTreeMap, hash_map::Entry}, convert::TryFrom, fs, os::unix::prelude::OsStrExt, @@ -105,7 +105,7 @@ struct ForgeProject<'a> { sm: Arc, ctx: AppCtx, env: Environment, - funcs: Vec>, + funcs:BTreeMap<&'a str, Entrypoint<'a>>, opts: Opts, >>>>>>> cd2ed7b (edited main to iterate over vector of functions. TODO: edit add_funcs in main.rs and test into_analyzble_function use case in main.rs) } From db83b1f906683f31a7de8c7a3dcc15f9fee0e308 Mon Sep 17 00:00:00 2001 From: awang7 Date: Wed, 20 Sep 2023 17:59:39 -0400 Subject: [PATCH 267/517] finished updating methods to check functions_to_scan and update entrypoint struct when needed. TODO: test and toggle function call in main.rs --- crates/forge_loader/src/manifest.rs | 304 +++++++++++++--------------- crates/fsrt/src/main.rs | 4 +- 2 files changed, 140 insertions(+), 168 deletions(-) diff --git a/crates/forge_loader/src/manifest.rs b/crates/forge_loader/src/manifest.rs index a7b5bbdb..a971e388 100644 --- a/crates/forge_loader/src/manifest.rs +++ b/crates/forge_loader/src/manifest.rs @@ -120,7 +120,7 @@ pub struct CustomField<'a> { #[derive(Debug, Clone, PartialEq, Eq, Deserialize)] pub struct UiModificatons<'a> { #[serde(flatten, borrow)] - common_key: CommonKey<'a>, + common_keys: CommonKey<'a>, } #[derive(Debug, Clone, PartialEq, Eq, Deserialize)] @@ -301,7 +301,7 @@ impl AsRef for FunctionTy { impl<'a> ForgeModules<'a> { // TODO: function returns iterator where each item is some specified type. - pub fn into_analyzable_functions(&mut self) -> Vec> { + pub fn into_analyzable_functions(&mut self) -> BTreeMap<&'a str, Entrypoint<'a>> { // number of webtriggers are usually low, so it's better to just sort them and reuse self.webtriggers .sort_unstable_by_key(|trigger| trigger.function); @@ -314,9 +314,9 @@ impl<'a> ForgeModules<'a> { // Get all functions for module from manifest.yml self.functions.iter().for_each(|func| { functions_to_scan.insert( - func.handler, + func.key, Entrypoint { - function: func.handler, + function: func.key, invokable: false, web_trigger: false, }, @@ -349,223 +349,195 @@ impl<'a> ForgeModules<'a> { }); // create arrays representing functions that expose user non-invokable functions - self.consumers.iter().for_each(|consumers| { - functions_to_scan.push(Entrypoint { - function: consumers.resolver.function, - invokable: true, - web_trigger: false, - }) + self.consumers.iter().for_each(|consumer| { + if functions_to_scan.contains_key(consumer.resolver.function) { + if let Some(entry) = functions_to_scan.get_mut(consumer.resolver.function) { + entry.invokable = true; + } + } }); self.data_provider.iter().for_each(|dataprovider| { - functions_to_scan.push(Entrypoint { - function: dataprovider.callback.function, - invokable: true, - web_trigger: false, - }) + if let Some(call) = dataprovider.callback { + if let Some(entry) = functions_to_scan.get_mut(call) { + entry.invokable = true; + } + } }); self.custom_field.iter().for_each(|customfield| { if let Some(value) = customfield.value { - functions_to_scan.push(Entrypoint { - function: value, - invokable: true, - web_trigger: false, - }); + if let Some(entry) = functions_to_scan.get_mut(value) { + entry.invokable = true; + } } - functions_to_scan.push(Entrypoint { - function: customfield.search_suggestions, - invokable: true, - web_trigger: false, - }); + if let Some(entry) = functions_to_scan.get_mut(customfield.search_suggestions) { + entry.invokable = true; + } - if let Some(func) = customfield.function { - functions_to_scan.push(Entrypoint { - function: func, - invokable: true, - web_trigger: false, - }); + if let Some(entry) = functions_to_scan.get_mut(customfield.common_keys.function) { + entry.invokable = true; } - if let Some(edit) = customfield.edit { - functions_to_scan.push(Entrypoint { - function: edit, - invokable: true, - web_trigger: false, - }); + if let Some(resolver) = customfield.common_keys.resolver { + if let Some(entry) = functions_to_scan.get_mut(resolver) { + entry.invokable = true; + } } - if let Some(resolver) = &customfield.resolver { - functions_to_scan.push(Entrypoint { - function: resolver.function, - invokable: true, - web_trigger: false, - }); + if let Some(edit) = customfield.edit { + if let Some(entry) = functions_to_scan.get_mut(edit) { + entry.invokable = true; + } } }); self.ui_modifications.iter().for_each(|ui| { - functions_to_scan.push(Entrypoint { - function: ui.resolver.function, - invokable: true, - web_trigger: false, - }) + if let Some(entry) = functions_to_scan.get_mut(ui.common_keys.function) { + entry.invokable = true; + } + + if let Some(resolver) = ui.common_keys.resolver { + if let Some(entry) = functions_to_scan.get_mut(resolver) { + entry.invokable = true; + } + } }); self.workflow_validator.iter().for_each(|validator| { - functions_to_scan.push(Entrypoint { - function: validator.function, - invokable: true, - web_trigger: false, - }); + if let Some(entry) = functions_to_scan.get_mut(validator.common_keys.function) { + entry.invokable = true; + } - functions_to_scan.push(Entrypoint { - function: validator.resolver.function, - invokable: true, - web_trigger: false, - }); + if let Some(resolver) = validator.common_keys.resolver { + if let Some(entry) = functions_to_scan.get_mut(resolver) { + entry.invokable = true; + } + } }); self.workflow_post_function .iter() .for_each(|post_function| { - functions_to_scan.push(Entrypoint { - function: post_function.function, - invokable: true, - web_trigger: false, - }) + if let Some(entry) = functions_to_scan.get_mut(post_function.common_keys.function) { + entry.invokable = true; + } + + if let Some(resolver) = post_function.common_keys.resolver { + if let Some(entry) = functions_to_scan.get_mut(resolver) { + entry.invokable = true; + } + } }); // get user invokable modules that have additional exposure endpoints. // ie macros has config and export fields on top of resolver fields that are functions - for macros in self.macros { - if let Some(resolver) = macros.resolver { - functions_to_scan.push(Entrypoint { - function: resolver.function, - invokable: true, - web_trigger: false, - }) + self.macros.iter().for_each(|macros| { + if let Some(resolver) = macros.common_keys.resolver { + if let Some(entry) = functions_to_scan.get_mut(resolver) { + entry.invokable = true; + } } if let Some(config) = macros.config { - functions_to_scan.push(Entrypoint { - function: config.function, - invokable: true, - web_trigger: false, - }) + if let Some(entry) = functions_to_scan.get_mut(config) { + entry.invokable = true; + } } + if let Some(export) = macros.export { - functions_to_scan.push(Entrypoint { - function: export.function, - invokable: true, - web_trigger: false, - }) + if let Some(entry) = functions_to_scan.get_mut(export) { + entry.invokable = true; + } } - } + }); - for contentitem in self.content_by_line_item { - functions_to_scan.push(Entrypoint { - function: contentitem.function, - invokable: true, - web_trigger: false, - }); - if let Some(resolver) = contentitem.resolver { - functions_to_scan.push(Entrypoint { - function: resolver.function, - invokable: true, - web_trigger: false, - }) + self.content_by_line_item.iter().for_each(|contentitem| { + if let Some(entry) = functions_to_scan.get_mut(contentitem.common_keys.function) { + entry.invokable = true; + } + + if let Some(resolver) = contentitem.common_keys.resolver { + if let Some(entry) = functions_to_scan.get_mut(resolver) { + entry.invokable = true; + } } if let Some(dynamic_properties) = contentitem.dynamic_properties { - functions_to_scan.push(Entrypoint { - function: dynamic_properties.function, - invokable: true, - web_trigger: false, - }) + if let Some(entry) = functions_to_scan.get_mut(dynamic_properties) { + entry.invokable = true; + } } - } + }); - for issue in self.issue_glance { - functions_to_scan.push(Entrypoint { - function: issue.function, - invokable: true, - web_trigger: false, - }); - if let Some(resolver) = issue.resolver { - functions_to_scan.push(Entrypoint { - function: resolver.function, - invokable: true, - web_trigger: false, - }) + self.issue_glance.iter().for_each(|issue| { + if let Some(entry) = functions_to_scan.get_mut(issue.common_keys.function) { + entry.invokable = true; + } + + if let Some(resolver) = issue.common_keys.resolver { + if let Some(entry) = functions_to_scan.get_mut(resolver) { + entry.invokable = true; + } } if let Some(dynamic_properties) = issue.dynamic_properties { - functions_to_scan.push(Entrypoint { - function: dynamic_properties.function, - invokable: true, - web_trigger: false, - }) + if let Some(entry) = functions_to_scan.get_mut(dynamic_properties) { + entry.invokable = true; + } + } + }); + + self.access_import_type.iter().for_each(|access| { + if let Some(entry) = functions_to_scan.get_mut(access.common_keys.function) { + entry.invokable = true; + } + + if let Some(resolver) = access.common_keys.resolver { + if let Some(entry) = functions_to_scan.get_mut(resolver) { + entry.invokable = true; + } } - } - for access in self.access_import_type { - functions_to_scan.push(Entrypoint { - function: access.function, - invokable: true, - web_trigger: false, - }); if let Some(delete) = access.one_delete_import { - functions_to_scan.push(Entrypoint { - function: delete.function, - invokable: true, - web_trigger: false, - }) + if let Some(entry) = functions_to_scan.get_mut(delete) { + entry.invokable = true; + } } if let Some(start) = access.start_import { - functions_to_scan.push(Entrypoint { - function: start.function, - invokable: true, - web_trigger: false, - }) + if let Some(entry) = functions_to_scan.get_mut(start) { + entry.invokable = true; + } } if let Some(stop) = access.stop_import { - functions_to_scan.push(Entrypoint { - function: stop.function, - invokable: true, - web_trigger: false, - }) + if let Some(entry) = functions_to_scan.get_mut(stop) { + entry.invokable = true; + } } if let Some(status) = access.import_status { - functions_to_scan.push(Entrypoint { - function: status.function, - invokable: true, - web_trigger: false, - }) + if let Some(entry) = functions_to_scan.get_mut(status) { + entry.invokable = true; + } } - } + }); // get array for user invokable module functions // make alternate_functions all user-invokable functions - for module in self.extra.into_values().flatten() { + for module in self.extra.clone().into_values().flatten() { if let Some(mod_function) = module.function { - functions_to_scan.push(Entrypoint { - function: mod_function, - invokable: true, - web_trigger: false, - }); + if let Some(entry) = functions_to_scan.get_mut(mod_function) { + entry.invokable = true; + } } if let Some(resolver) = module.resolver { - functions_to_scan.push(Entrypoint { - function: resolver.function, - invokable: true, - web_trigger: false, - }); + if let Some(entry) = functions_to_scan.get_mut(resolver.function) { + entry.invokable = true; + } } } @@ -685,7 +657,7 @@ mod tests { assert_eq!(manifest.app.name, Some("My App")); assert_eq!(manifest.app.id, "my-app"); assert_eq!(manifest.modules.macros.len(), 1); - assert_eq!(manifest.modules.macros[0].key, "My Macro"); + assert_eq!(manifest.modules.macros[0].common_keys.key, "My Macro"); // assert_eq!(manifest.modules.macros[0].function, "my-macro"); assert_eq!(manifest.modules.functions.len(), 1); assert_eq!( @@ -778,21 +750,21 @@ mod tests { }"#; let manifest: ForgeManifest = serde_json::from_str(json).unwrap(); assert_eq!(manifest.modules.macros.len(), 1); + assert_eq!( + manifest.modules.macros[0].common_keys.function, + "Catch-me-if-you-can0" + ); - if let Some(func) = manifest.modules.macros[0].function { - assert_eq!(func, "Catch-me-if-you-can0"); - } - - if let Some(func) = &manifest.modules.macros[0].resolver { - assert_eq!(func.function, "Catch-me-if-you-can1"); + if let Some(func) = manifest.modules.macros[0].common_keys.resolver { + assert_eq!(func, "Catch-me-if-you-can1"); } - if let Some(func) = &manifest.modules.macros[0].config { - assert_eq!(func.function, "Catch-me-if-you-can2"); + if let Some(func) = manifest.modules.macros[0].config { + assert_eq!(func, "Catch-me-if-you-can2"); } - if let Some(func) = &manifest.modules.macros[0].export { - assert_eq!(func.function, "Catch-me-if-you-can3"); + if let Some(func) = manifest.modules.macros[0].export { + assert_eq!(func, "Catch-me-if-you-can3"); } } } diff --git a/crates/fsrt/src/main.rs b/crates/fsrt/src/main.rs index ad2ebf7b..2a4f0ba5 100644 --- a/crates/fsrt/src/main.rs +++ b/crates/fsrt/src/main.rs @@ -5,7 +5,7 @@ use forge_permission_resolver::permissions_resolver::{ }; use miette::{IntoDiagnostic, Result}; use std::{ - collections::HashSet, + collections::{HashSet, BTreeMap, hash_map::Entry}, convert::TryFrom, fs, os::unix::prelude::OsStrExt, @@ -105,7 +105,7 @@ struct ForgeProject<'a> { sm: Arc, ctx: AppCtx, env: Environment, - funcs: Vec>, + funcs:BTreeMap<&'a str, Entrypoint<'a>>, opts: Opts, >>>>>>> cd2ed7b (edited main to iterate over vector of functions. TODO: edit add_funcs in main.rs and test into_analyzble_function use case in main.rs) } From 97d085d184629062f4fcdab5d3af1c2717eb5189 Mon Sep 17 00:00:00 2001 From: awang7 Date: Mon, 8 Jan 2024 12:17:04 -0800 Subject: [PATCH 268/517] resolving merge conflicts --- crates/forge_loader/src/manifest.rs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/crates/forge_loader/src/manifest.rs b/crates/forge_loader/src/manifest.rs index a971e388..9ff3811e 100644 --- a/crates/forge_loader/src/manifest.rs +++ b/crates/forge_loader/src/manifest.rs @@ -349,13 +349,13 @@ impl<'a> ForgeModules<'a> { }); // create arrays representing functions that expose user non-invokable functions - self.consumers.iter().for_each(|consumer| { - if functions_to_scan.contains_key(consumer.resolver.function) { - if let Some(entry) = functions_to_scan.get_mut(consumer.resolver.function) { - entry.invokable = true; - } - } - }); + // self.consumers.iter().for_each(|consumer| { + // if functions_to_scan.contains_key(consumer.resolver.function) { + // if let Some(entry) = functions_to_scan.get_mut(consumer.resolver.function) { + // entry.invokable = true; + // } + // } + // }); self.data_provider.iter().for_each(|dataprovider| { if let Some(call) = dataprovider.callback { From fb8f117b99b2a39a9fe0001c69c8f1c9b115214e Mon Sep 17 00:00:00 2001 From: awang7 Date: Mon, 8 Jan 2024 12:17:04 -0800 Subject: [PATCH 269/517] resolving merge conflicts --- crates/forge_loader/src/manifest.rs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/crates/forge_loader/src/manifest.rs b/crates/forge_loader/src/manifest.rs index a971e388..9ff3811e 100644 --- a/crates/forge_loader/src/manifest.rs +++ b/crates/forge_loader/src/manifest.rs @@ -349,13 +349,13 @@ impl<'a> ForgeModules<'a> { }); // create arrays representing functions that expose user non-invokable functions - self.consumers.iter().for_each(|consumer| { - if functions_to_scan.contains_key(consumer.resolver.function) { - if let Some(entry) = functions_to_scan.get_mut(consumer.resolver.function) { - entry.invokable = true; - } - } - }); + // self.consumers.iter().for_each(|consumer| { + // if functions_to_scan.contains_key(consumer.resolver.function) { + // if let Some(entry) = functions_to_scan.get_mut(consumer.resolver.function) { + // entry.invokable = true; + // } + // } + // }); self.data_provider.iter().for_each(|dataprovider| { if let Some(call) = dataprovider.callback { From 965e97b63b1fd28645b2142c60b6ca3b6768c349 Mon Sep 17 00:00:00 2001 From: awang7 Date: Thu, 21 Sep 2023 14:57:12 -0400 Subject: [PATCH 270/517] updated search_suggestion in customfield to be an optional value. --- crates/forge_loader/src/manifest.rs | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/crates/forge_loader/src/manifest.rs b/crates/forge_loader/src/manifest.rs index 9ff3811e..f5a2d52b 100644 --- a/crates/forge_loader/src/manifest.rs +++ b/crates/forge_loader/src/manifest.rs @@ -3,6 +3,7 @@ use std::{ collections::HashSet, hash::Hash, path::{Path, PathBuf}, + str::pattern::SearchStep, }; use crate::{forgepermissions::ForgePermissions, Error}; @@ -113,7 +114,7 @@ pub struct CustomField<'a> { #[serde(flatten, borrow)] common_keys: CommonKey<'a>, value: Option<&'a str>, - search_suggestions: &'a str, + search_suggestions: Option<&'a str>, edit: Option<&'a str>, } @@ -372,8 +373,10 @@ impl<'a> ForgeModules<'a> { } } - if let Some(entry) = functions_to_scan.get_mut(customfield.search_suggestions) { - entry.invokable = true; + if let Some(search) = customfield.search_suggestions { + if let Some(entry) = functions_to_scan.get_mut(search) { + entry.invokable = true; + } } if let Some(entry) = functions_to_scan.get_mut(customfield.common_keys.function) { From 1d7ceeb50095a7b9faffe856f9ca5f4962f1b928 Mon Sep 17 00:00:00 2001 From: awang7 Date: Thu, 21 Sep 2023 14:57:12 -0400 Subject: [PATCH 271/517] updated search_suggestion in customfield to be an optional value. --- crates/forge_loader/src/manifest.rs | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/crates/forge_loader/src/manifest.rs b/crates/forge_loader/src/manifest.rs index 9ff3811e..f5a2d52b 100644 --- a/crates/forge_loader/src/manifest.rs +++ b/crates/forge_loader/src/manifest.rs @@ -3,6 +3,7 @@ use std::{ collections::HashSet, hash::Hash, path::{Path, PathBuf}, + str::pattern::SearchStep, }; use crate::{forgepermissions::ForgePermissions, Error}; @@ -113,7 +114,7 @@ pub struct CustomField<'a> { #[serde(flatten, borrow)] common_keys: CommonKey<'a>, value: Option<&'a str>, - search_suggestions: &'a str, + search_suggestions: Option<&'a str>, edit: Option<&'a str>, } @@ -372,8 +373,10 @@ impl<'a> ForgeModules<'a> { } } - if let Some(entry) = functions_to_scan.get_mut(customfield.search_suggestions) { - entry.invokable = true; + if let Some(search) = customfield.search_suggestions { + if let Some(entry) = functions_to_scan.get_mut(search) { + entry.invokable = true; + } } if let Some(entry) = functions_to_scan.get_mut(customfield.common_keys.function) { From aa06bd81720412a9cb3d847a35c60788bacb6d20 Mon Sep 17 00:00:00 2001 From: awang7 Date: Thu, 5 Oct 2023 18:06:29 -0400 Subject: [PATCH 272/517] modified into_analyzable_functions with Josh and implementation in main.rs --- crates/forge_loader/src/manifest.rs | 310 +++++++--------------------- crates/fsrt/src/main.rs | 149 +++++++++---- 2 files changed, 189 insertions(+), 270 deletions(-) diff --git a/crates/forge_loader/src/manifest.rs b/crates/forge_loader/src/manifest.rs index f5a2d52b..8c26a3d6 100644 --- a/crates/forge_loader/src/manifest.rs +++ b/crates/forge_loader/src/manifest.rs @@ -1,16 +1,16 @@ use std::{ borrow::Borrow, - collections::HashSet, + collections::{BTreeSet, HashSet}, hash::Hash, path::{Path, PathBuf}, - str::pattern::SearchStep, + sync::Arc, }; use crate::{forgepermissions::ForgePermissions, Error}; use forge_utils::FxHashMap; use itertools::{Either, Itertools}; use serde::Deserialize; -use std::collections::BTreeMap; +use serde_json::map::Entry; use tracing::trace; #[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] @@ -258,38 +258,39 @@ pub enum FunctionTy { // Struct used for tracking what scan a funtion requires. #[derive(Debug, Clone, PartialEq, Eq)] -pub struct Entrypoint<'a> { - pub function: &'a str, +pub struct Entrypoint<'a, S = Unresolved> { + pub function: FunctionRef<'a, S>, pub invokable: bool, pub web_trigger: bool, } // Helper functions that help filter out which functions are what. -impl FunctionTy { - pub fn map(self, f: impl FnOnce(T) -> O) -> FunctionTy { - match self { - Self::Invokable(t) => FunctionTy::Invokable(f(t)), - Self::WebTrigger(t) => FunctionTy::WebTrigger(f(t)), - } - } - - #[inline] - pub fn into_inner(self) -> T { - match self { - FunctionTy::Invokable(t) | FunctionTy::WebTrigger(t) => t, - } - } - - pub fn sequence( - self, - f: impl FnOnce(T) -> I, - ) -> impl Iterator> { - match self { - Self::Invokable(t) => Either::Left(f(t).into_iter().map(FunctionTy::Invokable)), - Self::WebTrigger(t) => Either::Right(f(t).into_iter().map(FunctionTy::WebTrigger)), - } - } -} +// original code that's commented out to modify methods. Here for reference +// impl FunctionTy { +// pub fn map(self, f: impl FnOnce(T) -> O) -> FunctionTy { +// match self { +// Self::Invokable(t) => FunctionTy::Invokable(f(t)), +// Self::WebTrigger(t) => FunctionTy::WebTrigger(f(t)), +// } +// } + +// #[inline] +// pub fn into_inner(self) -> T { +// match self { +// FunctionTy::Invokable(t) | FunctionTy::WebTrigger(t) => t, +// } +// } + +// pub fn sequence( +// self, +// f: impl FnOnce(T) -> I, +// ) -> impl Iterator> { +// match self { +// Self::Invokable(t) => Either::Left(f(t).into_iter().map(FunctionTy::Invokable)), +// Self::WebTrigger(t) => Either::Right(f(t).into_iter().map(FunctionTy::WebTrigger)), +// } +// } +// } impl AsRef for FunctionTy { #[inline] @@ -302,249 +303,96 @@ impl AsRef for FunctionTy { impl<'a> ForgeModules<'a> { // TODO: function returns iterator where each item is some specified type. - pub fn into_analyzable_functions(&mut self) -> BTreeMap<&'a str, Entrypoint<'a>> { + pub fn into_analyzable_functions(mut self) -> impl Iterator> { // number of webtriggers are usually low, so it's better to just sort them and reuse self.webtriggers .sort_unstable_by_key(|trigger| trigger.function); + self.webtriggers + .sort_unstable_by_key(|trigger| trigger.function); // Get all the Triggers and represent them as a new struct thing where "webtrigger" attribute is true // for all trigger things + // Get all the Triggers and represent them as a new struct thing where "webtrigger" attribute is true + // for all trigger things - let mut functions_to_scan = BTreeMap::new(); - - // Get all functions for module from manifest.yml - self.functions.iter().for_each(|func| { - functions_to_scan.insert( - func.key, - Entrypoint { - function: func.key, - invokable: false, - web_trigger: false, - }, - ); - }); - - self.webtriggers.iter().for_each(|webtriggers| { - if functions_to_scan.contains_key(webtriggers.function) { - if let Some(entry) = functions_to_scan.get_mut(webtriggers.function) { - entry.web_trigger = true; - } - } - }); - - self.event_triggers.iter().for_each(|event_triggers| { - if functions_to_scan.contains_key(event_triggers.raw.function) { - if let Some(entry) = functions_to_scan.get_mut(event_triggers.raw.function) { - entry.web_trigger = true; - } - } - }); - self.scheduled_triggers - .iter() - .for_each(|schedule_triggers| { - if functions_to_scan.contains_key(schedule_triggers.raw.function) { - if let Some(entry) = functions_to_scan.get_mut(schedule_triggers.raw.function) { - entry.web_trigger = true; - } - } - }); - - // create arrays representing functions that expose user non-invokable functions - // self.consumers.iter().for_each(|consumer| { - // if functions_to_scan.contains_key(consumer.resolver.function) { - // if let Some(entry) = functions_to_scan.get_mut(consumer.resolver.function) { - // entry.invokable = true; - // } - // } - // }); + let mut invokable_functions = BTreeSet::new(); self.data_provider.iter().for_each(|dataprovider| { - if let Some(call) = dataprovider.callback { - if let Some(entry) = functions_to_scan.get_mut(call) { - entry.invokable = true; - } - } + invokable_functions.extend(dataprovider.callback); }); self.custom_field.iter().for_each(|customfield| { - if let Some(value) = customfield.value { - if let Some(entry) = functions_to_scan.get_mut(value) { - entry.invokable = true; - } - } - - if let Some(search) = customfield.search_suggestions { - if let Some(entry) = functions_to_scan.get_mut(search) { - entry.invokable = true; - } - } - - if let Some(entry) = functions_to_scan.get_mut(customfield.common_keys.function) { - entry.invokable = true; - } + invokable_functions.extend(customfield.value); + invokable_functions.extend(customfield.search_suggestions); + invokable_functions.extend(customfield.edit); - if let Some(resolver) = customfield.common_keys.resolver { - if let Some(entry) = functions_to_scan.get_mut(resolver) { - entry.invokable = true; - } - } - - if let Some(edit) = customfield.edit { - if let Some(entry) = functions_to_scan.get_mut(edit) { - entry.invokable = true; - } - } + invokable_functions.insert(customfield.common_keys.function); + invokable_functions.extend(customfield.common_keys.resolver); }); self.ui_modifications.iter().for_each(|ui| { - if let Some(entry) = functions_to_scan.get_mut(ui.common_keys.function) { - entry.invokable = true; - } - - if let Some(resolver) = ui.common_keys.resolver { - if let Some(entry) = functions_to_scan.get_mut(resolver) { - entry.invokable = true; - } - } + invokable_functions.insert(ui.common_keys.function); + invokable_functions.extend(ui.common_keys.resolver); }); self.workflow_validator.iter().for_each(|validator| { - if let Some(entry) = functions_to_scan.get_mut(validator.common_keys.function) { - entry.invokable = true; - } + invokable_functions.insert(validator.common_keys.key); - if let Some(resolver) = validator.common_keys.resolver { - if let Some(entry) = functions_to_scan.get_mut(resolver) { - entry.invokable = true; - } - } + invokable_functions.insert(validator.common_keys.function); + + invokable_functions.extend(validator.common_keys.resolver); }); self.workflow_post_function .iter() .for_each(|post_function| { - if let Some(entry) = functions_to_scan.get_mut(post_function.common_keys.function) { - entry.invokable = true; - } + invokable_functions.insert(post_function.common_keys.key); - if let Some(resolver) = post_function.common_keys.resolver { - if let Some(entry) = functions_to_scan.get_mut(resolver) { - entry.invokable = true; - } - } + invokable_functions.insert(post_function.common_keys.function); + + invokable_functions.extend(post_function.common_keys.resolver); }); // get user invokable modules that have additional exposure endpoints. // ie macros has config and export fields on top of resolver fields that are functions self.macros.iter().for_each(|macros| { - if let Some(resolver) = macros.common_keys.resolver { - if let Some(entry) = functions_to_scan.get_mut(resolver) { - entry.invokable = true; - } - } + invokable_functions.insert(macros.common_keys.key); - if let Some(config) = macros.config { - if let Some(entry) = functions_to_scan.get_mut(config) { - entry.invokable = true; - } - } + invokable_functions.insert(macros.common_keys.function); + invokable_functions.extend(macros.common_keys.resolver); - if let Some(export) = macros.export { - if let Some(entry) = functions_to_scan.get_mut(export) { - entry.invokable = true; - } - } - }); - - self.content_by_line_item.iter().for_each(|contentitem| { - if let Some(entry) = functions_to_scan.get_mut(contentitem.common_keys.function) { - entry.invokable = true; - } - - if let Some(resolver) = contentitem.common_keys.resolver { - if let Some(entry) = functions_to_scan.get_mut(resolver) { - entry.invokable = true; - } - } - - if let Some(dynamic_properties) = contentitem.dynamic_properties { - if let Some(entry) = functions_to_scan.get_mut(dynamic_properties) { - entry.invokable = true; - } - } + invokable_functions.extend(macros.config); + invokable_functions.extend(macros.export); }); self.issue_glance.iter().for_each(|issue| { - if let Some(entry) = functions_to_scan.get_mut(issue.common_keys.function) { - entry.invokable = true; - } - - if let Some(resolver) = issue.common_keys.resolver { - if let Some(entry) = functions_to_scan.get_mut(resolver) { - entry.invokable = true; - } - } - - if let Some(dynamic_properties) = issue.dynamic_properties { - if let Some(entry) = functions_to_scan.get_mut(dynamic_properties) { - entry.invokable = true; - } - } + invokable_functions.insert(issue.common_keys.function); + invokable_functions.extend(issue.common_keys.resolver); + invokable_functions.extend(issue.dynamic_properties); }); self.access_import_type.iter().for_each(|access| { - if let Some(entry) = functions_to_scan.get_mut(access.common_keys.function) { - entry.invokable = true; - } - - if let Some(resolver) = access.common_keys.resolver { - if let Some(entry) = functions_to_scan.get_mut(resolver) { - entry.invokable = true; - } - } - - if let Some(delete) = access.one_delete_import { - if let Some(entry) = functions_to_scan.get_mut(delete) { - entry.invokable = true; - } - } - - if let Some(start) = access.start_import { - if let Some(entry) = functions_to_scan.get_mut(start) { - entry.invokable = true; - } - } - - if let Some(stop) = access.stop_import { - if let Some(entry) = functions_to_scan.get_mut(stop) { - entry.invokable = true; - } - } + invokable_functions.insert(access.common_keys.function); + invokable_functions.extend(access.common_keys.resolver); - if let Some(status) = access.import_status { - if let Some(entry) = functions_to_scan.get_mut(status) { - entry.invokable = true; - } - } + invokable_functions.extend(access.one_delete_import); + invokable_functions.extend(access.stop_import); + invokable_functions.extend(access.start_import); + invokable_functions.extend(access.import_status); }); - // get array for user invokable module functions - // make alternate_functions all user-invokable functions - for module in self.extra.clone().into_values().flatten() { - if let Some(mod_function) = module.function { - if let Some(entry) = functions_to_scan.get_mut(mod_function) { - entry.invokable = true; - } - } - - if let Some(resolver) = module.resolver { - if let Some(entry) = functions_to_scan.get_mut(resolver.function) { - entry.invokable = true; - } - } - } - - functions_to_scan + self.functions.into_iter().flat_map(move |func| { + let web_trigger = self + .webtriggers + .binary_search_by_key(&func.key, |trigger| &trigger.function) + .is_ok(); + let invokable = invokable_functions.contains(func.key); + Ok::<_, Error>(Entrypoint { + function: FunctionRef::try_from(func)?, + invokable, + web_trigger, + }) + }) } } diff --git a/crates/fsrt/src/main.rs b/crates/fsrt/src/main.rs index 2a4f0ba5..8f9654ae 100644 --- a/crates/fsrt/src/main.rs +++ b/crates/fsrt/src/main.rs @@ -5,8 +5,7 @@ use forge_permission_resolver::permissions_resolver::{ }; use miette::{IntoDiagnostic, Result}; use std::{ - collections::{HashSet, BTreeMap, hash_map::Entry}, - convert::TryFrom, + collections::HashSet, fs, os::unix::prelude::OsStrExt, path::{Path, PathBuf}, @@ -18,8 +17,11 @@ use std::{ use clap::{Parser, ValueHint}; use miette::{IntoDiagnostic, Result}; +<<<<<<< HEAD use serde_json::map::Entry; >>>>>>> cd2ed7b (edited main to iterate over vector of functions. TODO: edit add_funcs in main.rs and test into_analyzble_function use case in main.rs) +======= +>>>>>>> 29c38d6 (modified into_analyzable_functions with Josh and implementation in main.rs) use swc_core::{ common::{Globals, Mark, SourceMap, GLOBALS}, ecma::{ @@ -45,7 +47,7 @@ use forge_analyzer::{ resolver::resolve_calls, }; -use forge_loader::manifest::{ForgeManifest, FunctionRef, FunctionTy, Entrypoint}; +use forge_loader::manifest::{Entrypoint, ForgeManifest, Resolved}; use walkdir::WalkDir; #[derive(Parser, Debug)] @@ -99,18 +101,28 @@ struct Opts { out: Option, } +#[derive(Debug, Clone)] +struct ResolvedEntryPoint<'a> { + func_name: &'a str, + path: PathBuf, + module: ModId, + def_id: DefId, + webtrigger: bool, + invokable: bool, +} + struct ForgeProject<'a> { >>>>>>> 7e86f46 (abstracted structs to use a CommonKeys struct that holds: key, function, and resolver for less code duplication. Updated into_analayzable method to update values based on functions specified in function mod. TODO: Update rest of non trigger modules to update mapping to functions to scan from function mod) #[allow(dead_code)] sm: Arc, ctx: AppCtx, env: Environment, - funcs:BTreeMap<&'a str, Entrypoint<'a>>, + funcs: Vec>, opts: Opts, >>>>>>> cd2ed7b (edited main to iterate over vector of functions. TODO: edit add_funcs in main.rs and test into_analyzble_function use case in main.rs) } -impl ForgeProject<'_> { +impl<'a> ForgeProject<'a> { #[instrument(skip(src, iter))] fn with_files_and_sourceroot, I: IntoIterator>( src: P, @@ -157,21 +169,24 @@ impl ForgeProject<'_> { funcs: vec![], } } -// TODO: edit to work with new iterator that not FUNCTIONTY - fn add_funcs<'a, I: IntoIterator>>(&mut self, iter: I) { - self.funcs.extend(iter); + // TODO: edit to work with new iterator that not FUNCTIONTY + fn add_funcs>>(&mut self, iter: I) { + self.funcs.extend(iter.into_iter().filter_map(|entrypoint| { + let (func_name, path) = entrypoint.function.into_func_path(); + let module = self.ctx.modid_from_path(&path)?; + let def_id = self.env.module_export(module, func_name)?; + Some(ResolvedEntryPoint { + func_name, + path, + module, + def_id, + invokable: entrypoint.invokable, + webtrigger: entrypoint.web_trigger, + }) + })); } } - -// self.funcs.extend(iter.into_iter().flat_map(|ftype| { -// ftype.sequence(|(func_name, path)| { -// let modid = self.ctx.modid_from_path(&path)?; -// let func = self.env.module_export(modid, func_name)?; -// Some((func_name.to_owned(), path, modid, func)) -// }) -// })); - fn is_js_file>(path: P) -> bool { matches!( path.as_ref().extension().map(|s| s.as_bytes()), @@ -191,7 +206,11 @@ fn collect_sourcefiles>(root: P) -> impl Iterator } #[tracing::instrument(level = "debug")] +<<<<<<< HEAD fn scan_directory(dir: PathBuf, function: Option<&str>, opts: &Args) -> Result { +======= +fn scan_directory(dir: PathBuf, function: Option<&str>, opts: Opts) -> Result<()> { +>>>>>>> 29c38d6 (modified into_analyzable_functions with Josh and implementation in main.rs) let mut manifest_file = dir.clone(); manifest_file.push("manifest.yaml"); if !manifest_file.exists() { @@ -230,19 +249,26 @@ fn scan_directory(dir: PathBuf, function: Option<&str>, opts: &Args) -> Result(resolved_func.into_func_path()) - // }) - // }); + let funcrefs = manifest + .modules + .into_analyzable_functions() + .flat_map(|entrypoint| { + Ok::<_, forge_loader::Error>(Entrypoint { + function: entrypoint.function.try_resolve(&paths, &dir)?, + invokable: entrypoint.invokable, + web_trigger: entrypoint.web_trigger, + }) + }); + let src_root = dir.join("src"); let mut proj = ForgeProject::with_files_and_sourceroot(src_root, paths.clone(), secret_packages); if transpiled_async { warn!("Unable to scan due to transpiled async"); +<<<<<<< HEAD +======= + return Ok(()); +>>>>>>> 29c38d6 (modified into_analyzable_functions with Josh and implementation in main.rs) } proj.add_funcs(funcrefs); resolve_calls(&mut proj.ctx); @@ -326,6 +352,7 @@ fn scan_directory(dir: PathBuf, function: Option<&str>, opts: &Args) -> Result { @@ -462,27 +489,67 @@ fn scan_directory(dir: PathBuf, function: Option<&str>, opts: &Args) -> Result { + // let mut checker = AuthZChecker::new(); + // debug!("checking {func} at {path:?}"); + // if let Err(err) = interp.run_checker(def, &mut checker, path.clone(), func.clone()) + // { + // warn!("error while scanning {func} in {path:?}: {err}"); + // } + // reporter.add_vulnerabilities(checker.into_vulns()); + // } + // FunctionTy::WebTrigger((ref func, ref path, _, def)) => { + // let mut checker = AuthenticateChecker::new(); + // debug!("checking webtrigger {func} at {path:?}"); + // if let Err(err) = + // authn_interp.run_checker(def, &mut checker, path.clone(), func.clone()) + // { + // warn!("error while scanning {func} in {path:?}: {err}"); + // } + // reporter.add_vulnerabilities(checker.into_vulns()); + // } + // } +>>>>>>> 29c38d6 (modified into_analyzable_functions with Josh and implementation in main.rs) + + // Get entrypoint value from tuple + // Logic for performing scans. + // If it's invokable, then run invokable scan. If web_trigger, then trigger scan. + // And if it's both, run both scans. if func.invokable { let mut checker = AuthZChecker::new(); - debug!("checking {:?} at {path:?}", func.function); - if let Err(err) = interp.run_checker(def, &mut checker, path.clone(), func.function.to_string()) - { - warn!("error while scanning {:?} in {path:?}: {err}", func.function); + debug!("checking {:?} at {:?}", func.func_name, &func.path); + if let Err(err) = interp.run_checker( + func.def_id, + &mut checker, + func.path.clone(), + func.func_name.to_string(), + ) { + warn!( + "error while scanning {:?} in {:?}: {err}", + func.func_name, func.path, + ); } reporter.add_vulnerabilities(checker.into_vulns()); - - } else if func.web_trigger { + } else if func.webtrigger { let mut checker = AuthenticateChecker::new(); - debug!("checking webtrigger {:?} at {path:?}", func.function); - if let Err(err) = - authn_interp.run_checker(def, &mut checker, path.clone(), func.function.to_string()) - { - warn!("error while scanning {:?} in {path:?}: {err}", func.function); + debug!( + "checking webtrigger {:?} at {:?}", + func.func_name, func.path, + ); + if let Err(err) = authn_interp.run_checker( + func.def_id, + &mut checker, + func.path.clone(), + func.func_name.to_string(), + ) { + warn!( + "error while scanning {:?} in {:?}: {err}", + func.func_name, func.path, + ); } reporter.add_vulnerabilities(checker.into_vulns()); - - } } @@ -502,8 +569,12 @@ fn scan_directory(dir: PathBuf, function: Option<&str>, opts: &Args) -> Result println!("{report}"), } +<<<<<<< HEAD Ok(proj) +======= + Ok(()) +>>>>>>> 29c38d6 (modified into_analyzable_functions with Josh and implementation in main.rs) } fn main() -> Result<()> { From 4781bbbe73a8b8f92729a072b7575610cf6c1128 Mon Sep 17 00:00:00 2001 From: awang7 Date: Thu, 5 Oct 2023 18:06:29 -0400 Subject: [PATCH 273/517] modified into_analyzable_functions with Josh and implementation in main.rs --- crates/forge_loader/src/manifest.rs | 310 +++++++--------------------- crates/fsrt/src/main.rs | 149 +++++++++---- 2 files changed, 189 insertions(+), 270 deletions(-) diff --git a/crates/forge_loader/src/manifest.rs b/crates/forge_loader/src/manifest.rs index f5a2d52b..8c26a3d6 100644 --- a/crates/forge_loader/src/manifest.rs +++ b/crates/forge_loader/src/manifest.rs @@ -1,16 +1,16 @@ use std::{ borrow::Borrow, - collections::HashSet, + collections::{BTreeSet, HashSet}, hash::Hash, path::{Path, PathBuf}, - str::pattern::SearchStep, + sync::Arc, }; use crate::{forgepermissions::ForgePermissions, Error}; use forge_utils::FxHashMap; use itertools::{Either, Itertools}; use serde::Deserialize; -use std::collections::BTreeMap; +use serde_json::map::Entry; use tracing::trace; #[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] @@ -258,38 +258,39 @@ pub enum FunctionTy { // Struct used for tracking what scan a funtion requires. #[derive(Debug, Clone, PartialEq, Eq)] -pub struct Entrypoint<'a> { - pub function: &'a str, +pub struct Entrypoint<'a, S = Unresolved> { + pub function: FunctionRef<'a, S>, pub invokable: bool, pub web_trigger: bool, } // Helper functions that help filter out which functions are what. -impl FunctionTy { - pub fn map(self, f: impl FnOnce(T) -> O) -> FunctionTy { - match self { - Self::Invokable(t) => FunctionTy::Invokable(f(t)), - Self::WebTrigger(t) => FunctionTy::WebTrigger(f(t)), - } - } - - #[inline] - pub fn into_inner(self) -> T { - match self { - FunctionTy::Invokable(t) | FunctionTy::WebTrigger(t) => t, - } - } - - pub fn sequence( - self, - f: impl FnOnce(T) -> I, - ) -> impl Iterator> { - match self { - Self::Invokable(t) => Either::Left(f(t).into_iter().map(FunctionTy::Invokable)), - Self::WebTrigger(t) => Either::Right(f(t).into_iter().map(FunctionTy::WebTrigger)), - } - } -} +// original code that's commented out to modify methods. Here for reference +// impl FunctionTy { +// pub fn map(self, f: impl FnOnce(T) -> O) -> FunctionTy { +// match self { +// Self::Invokable(t) => FunctionTy::Invokable(f(t)), +// Self::WebTrigger(t) => FunctionTy::WebTrigger(f(t)), +// } +// } + +// #[inline] +// pub fn into_inner(self) -> T { +// match self { +// FunctionTy::Invokable(t) | FunctionTy::WebTrigger(t) => t, +// } +// } + +// pub fn sequence( +// self, +// f: impl FnOnce(T) -> I, +// ) -> impl Iterator> { +// match self { +// Self::Invokable(t) => Either::Left(f(t).into_iter().map(FunctionTy::Invokable)), +// Self::WebTrigger(t) => Either::Right(f(t).into_iter().map(FunctionTy::WebTrigger)), +// } +// } +// } impl AsRef for FunctionTy { #[inline] @@ -302,249 +303,96 @@ impl AsRef for FunctionTy { impl<'a> ForgeModules<'a> { // TODO: function returns iterator where each item is some specified type. - pub fn into_analyzable_functions(&mut self) -> BTreeMap<&'a str, Entrypoint<'a>> { + pub fn into_analyzable_functions(mut self) -> impl Iterator> { // number of webtriggers are usually low, so it's better to just sort them and reuse self.webtriggers .sort_unstable_by_key(|trigger| trigger.function); + self.webtriggers + .sort_unstable_by_key(|trigger| trigger.function); // Get all the Triggers and represent them as a new struct thing where "webtrigger" attribute is true // for all trigger things + // Get all the Triggers and represent them as a new struct thing where "webtrigger" attribute is true + // for all trigger things - let mut functions_to_scan = BTreeMap::new(); - - // Get all functions for module from manifest.yml - self.functions.iter().for_each(|func| { - functions_to_scan.insert( - func.key, - Entrypoint { - function: func.key, - invokable: false, - web_trigger: false, - }, - ); - }); - - self.webtriggers.iter().for_each(|webtriggers| { - if functions_to_scan.contains_key(webtriggers.function) { - if let Some(entry) = functions_to_scan.get_mut(webtriggers.function) { - entry.web_trigger = true; - } - } - }); - - self.event_triggers.iter().for_each(|event_triggers| { - if functions_to_scan.contains_key(event_triggers.raw.function) { - if let Some(entry) = functions_to_scan.get_mut(event_triggers.raw.function) { - entry.web_trigger = true; - } - } - }); - self.scheduled_triggers - .iter() - .for_each(|schedule_triggers| { - if functions_to_scan.contains_key(schedule_triggers.raw.function) { - if let Some(entry) = functions_to_scan.get_mut(schedule_triggers.raw.function) { - entry.web_trigger = true; - } - } - }); - - // create arrays representing functions that expose user non-invokable functions - // self.consumers.iter().for_each(|consumer| { - // if functions_to_scan.contains_key(consumer.resolver.function) { - // if let Some(entry) = functions_to_scan.get_mut(consumer.resolver.function) { - // entry.invokable = true; - // } - // } - // }); + let mut invokable_functions = BTreeSet::new(); self.data_provider.iter().for_each(|dataprovider| { - if let Some(call) = dataprovider.callback { - if let Some(entry) = functions_to_scan.get_mut(call) { - entry.invokable = true; - } - } + invokable_functions.extend(dataprovider.callback); }); self.custom_field.iter().for_each(|customfield| { - if let Some(value) = customfield.value { - if let Some(entry) = functions_to_scan.get_mut(value) { - entry.invokable = true; - } - } - - if let Some(search) = customfield.search_suggestions { - if let Some(entry) = functions_to_scan.get_mut(search) { - entry.invokable = true; - } - } - - if let Some(entry) = functions_to_scan.get_mut(customfield.common_keys.function) { - entry.invokable = true; - } + invokable_functions.extend(customfield.value); + invokable_functions.extend(customfield.search_suggestions); + invokable_functions.extend(customfield.edit); - if let Some(resolver) = customfield.common_keys.resolver { - if let Some(entry) = functions_to_scan.get_mut(resolver) { - entry.invokable = true; - } - } - - if let Some(edit) = customfield.edit { - if let Some(entry) = functions_to_scan.get_mut(edit) { - entry.invokable = true; - } - } + invokable_functions.insert(customfield.common_keys.function); + invokable_functions.extend(customfield.common_keys.resolver); }); self.ui_modifications.iter().for_each(|ui| { - if let Some(entry) = functions_to_scan.get_mut(ui.common_keys.function) { - entry.invokable = true; - } - - if let Some(resolver) = ui.common_keys.resolver { - if let Some(entry) = functions_to_scan.get_mut(resolver) { - entry.invokable = true; - } - } + invokable_functions.insert(ui.common_keys.function); + invokable_functions.extend(ui.common_keys.resolver); }); self.workflow_validator.iter().for_each(|validator| { - if let Some(entry) = functions_to_scan.get_mut(validator.common_keys.function) { - entry.invokable = true; - } + invokable_functions.insert(validator.common_keys.key); - if let Some(resolver) = validator.common_keys.resolver { - if let Some(entry) = functions_to_scan.get_mut(resolver) { - entry.invokable = true; - } - } + invokable_functions.insert(validator.common_keys.function); + + invokable_functions.extend(validator.common_keys.resolver); }); self.workflow_post_function .iter() .for_each(|post_function| { - if let Some(entry) = functions_to_scan.get_mut(post_function.common_keys.function) { - entry.invokable = true; - } + invokable_functions.insert(post_function.common_keys.key); - if let Some(resolver) = post_function.common_keys.resolver { - if let Some(entry) = functions_to_scan.get_mut(resolver) { - entry.invokable = true; - } - } + invokable_functions.insert(post_function.common_keys.function); + + invokable_functions.extend(post_function.common_keys.resolver); }); // get user invokable modules that have additional exposure endpoints. // ie macros has config and export fields on top of resolver fields that are functions self.macros.iter().for_each(|macros| { - if let Some(resolver) = macros.common_keys.resolver { - if let Some(entry) = functions_to_scan.get_mut(resolver) { - entry.invokable = true; - } - } + invokable_functions.insert(macros.common_keys.key); - if let Some(config) = macros.config { - if let Some(entry) = functions_to_scan.get_mut(config) { - entry.invokable = true; - } - } + invokable_functions.insert(macros.common_keys.function); + invokable_functions.extend(macros.common_keys.resolver); - if let Some(export) = macros.export { - if let Some(entry) = functions_to_scan.get_mut(export) { - entry.invokable = true; - } - } - }); - - self.content_by_line_item.iter().for_each(|contentitem| { - if let Some(entry) = functions_to_scan.get_mut(contentitem.common_keys.function) { - entry.invokable = true; - } - - if let Some(resolver) = contentitem.common_keys.resolver { - if let Some(entry) = functions_to_scan.get_mut(resolver) { - entry.invokable = true; - } - } - - if let Some(dynamic_properties) = contentitem.dynamic_properties { - if let Some(entry) = functions_to_scan.get_mut(dynamic_properties) { - entry.invokable = true; - } - } + invokable_functions.extend(macros.config); + invokable_functions.extend(macros.export); }); self.issue_glance.iter().for_each(|issue| { - if let Some(entry) = functions_to_scan.get_mut(issue.common_keys.function) { - entry.invokable = true; - } - - if let Some(resolver) = issue.common_keys.resolver { - if let Some(entry) = functions_to_scan.get_mut(resolver) { - entry.invokable = true; - } - } - - if let Some(dynamic_properties) = issue.dynamic_properties { - if let Some(entry) = functions_to_scan.get_mut(dynamic_properties) { - entry.invokable = true; - } - } + invokable_functions.insert(issue.common_keys.function); + invokable_functions.extend(issue.common_keys.resolver); + invokable_functions.extend(issue.dynamic_properties); }); self.access_import_type.iter().for_each(|access| { - if let Some(entry) = functions_to_scan.get_mut(access.common_keys.function) { - entry.invokable = true; - } - - if let Some(resolver) = access.common_keys.resolver { - if let Some(entry) = functions_to_scan.get_mut(resolver) { - entry.invokable = true; - } - } - - if let Some(delete) = access.one_delete_import { - if let Some(entry) = functions_to_scan.get_mut(delete) { - entry.invokable = true; - } - } - - if let Some(start) = access.start_import { - if let Some(entry) = functions_to_scan.get_mut(start) { - entry.invokable = true; - } - } - - if let Some(stop) = access.stop_import { - if let Some(entry) = functions_to_scan.get_mut(stop) { - entry.invokable = true; - } - } + invokable_functions.insert(access.common_keys.function); + invokable_functions.extend(access.common_keys.resolver); - if let Some(status) = access.import_status { - if let Some(entry) = functions_to_scan.get_mut(status) { - entry.invokable = true; - } - } + invokable_functions.extend(access.one_delete_import); + invokable_functions.extend(access.stop_import); + invokable_functions.extend(access.start_import); + invokable_functions.extend(access.import_status); }); - // get array for user invokable module functions - // make alternate_functions all user-invokable functions - for module in self.extra.clone().into_values().flatten() { - if let Some(mod_function) = module.function { - if let Some(entry) = functions_to_scan.get_mut(mod_function) { - entry.invokable = true; - } - } - - if let Some(resolver) = module.resolver { - if let Some(entry) = functions_to_scan.get_mut(resolver.function) { - entry.invokable = true; - } - } - } - - functions_to_scan + self.functions.into_iter().flat_map(move |func| { + let web_trigger = self + .webtriggers + .binary_search_by_key(&func.key, |trigger| &trigger.function) + .is_ok(); + let invokable = invokable_functions.contains(func.key); + Ok::<_, Error>(Entrypoint { + function: FunctionRef::try_from(func)?, + invokable, + web_trigger, + }) + }) } } diff --git a/crates/fsrt/src/main.rs b/crates/fsrt/src/main.rs index 2a4f0ba5..8f9654ae 100644 --- a/crates/fsrt/src/main.rs +++ b/crates/fsrt/src/main.rs @@ -5,8 +5,7 @@ use forge_permission_resolver::permissions_resolver::{ }; use miette::{IntoDiagnostic, Result}; use std::{ - collections::{HashSet, BTreeMap, hash_map::Entry}, - convert::TryFrom, + collections::HashSet, fs, os::unix::prelude::OsStrExt, path::{Path, PathBuf}, @@ -18,8 +17,11 @@ use std::{ use clap::{Parser, ValueHint}; use miette::{IntoDiagnostic, Result}; +<<<<<<< HEAD use serde_json::map::Entry; >>>>>>> cd2ed7b (edited main to iterate over vector of functions. TODO: edit add_funcs in main.rs and test into_analyzble_function use case in main.rs) +======= +>>>>>>> 29c38d6 (modified into_analyzable_functions with Josh and implementation in main.rs) use swc_core::{ common::{Globals, Mark, SourceMap, GLOBALS}, ecma::{ @@ -45,7 +47,7 @@ use forge_analyzer::{ resolver::resolve_calls, }; -use forge_loader::manifest::{ForgeManifest, FunctionRef, FunctionTy, Entrypoint}; +use forge_loader::manifest::{Entrypoint, ForgeManifest, Resolved}; use walkdir::WalkDir; #[derive(Parser, Debug)] @@ -99,18 +101,28 @@ struct Opts { out: Option, } +#[derive(Debug, Clone)] +struct ResolvedEntryPoint<'a> { + func_name: &'a str, + path: PathBuf, + module: ModId, + def_id: DefId, + webtrigger: bool, + invokable: bool, +} + struct ForgeProject<'a> { >>>>>>> 7e86f46 (abstracted structs to use a CommonKeys struct that holds: key, function, and resolver for less code duplication. Updated into_analayzable method to update values based on functions specified in function mod. TODO: Update rest of non trigger modules to update mapping to functions to scan from function mod) #[allow(dead_code)] sm: Arc, ctx: AppCtx, env: Environment, - funcs:BTreeMap<&'a str, Entrypoint<'a>>, + funcs: Vec>, opts: Opts, >>>>>>> cd2ed7b (edited main to iterate over vector of functions. TODO: edit add_funcs in main.rs and test into_analyzble_function use case in main.rs) } -impl ForgeProject<'_> { +impl<'a> ForgeProject<'a> { #[instrument(skip(src, iter))] fn with_files_and_sourceroot, I: IntoIterator>( src: P, @@ -157,21 +169,24 @@ impl ForgeProject<'_> { funcs: vec![], } } -// TODO: edit to work with new iterator that not FUNCTIONTY - fn add_funcs<'a, I: IntoIterator>>(&mut self, iter: I) { - self.funcs.extend(iter); + // TODO: edit to work with new iterator that not FUNCTIONTY + fn add_funcs>>(&mut self, iter: I) { + self.funcs.extend(iter.into_iter().filter_map(|entrypoint| { + let (func_name, path) = entrypoint.function.into_func_path(); + let module = self.ctx.modid_from_path(&path)?; + let def_id = self.env.module_export(module, func_name)?; + Some(ResolvedEntryPoint { + func_name, + path, + module, + def_id, + invokable: entrypoint.invokable, + webtrigger: entrypoint.web_trigger, + }) + })); } } - -// self.funcs.extend(iter.into_iter().flat_map(|ftype| { -// ftype.sequence(|(func_name, path)| { -// let modid = self.ctx.modid_from_path(&path)?; -// let func = self.env.module_export(modid, func_name)?; -// Some((func_name.to_owned(), path, modid, func)) -// }) -// })); - fn is_js_file>(path: P) -> bool { matches!( path.as_ref().extension().map(|s| s.as_bytes()), @@ -191,7 +206,11 @@ fn collect_sourcefiles>(root: P) -> impl Iterator } #[tracing::instrument(level = "debug")] +<<<<<<< HEAD fn scan_directory(dir: PathBuf, function: Option<&str>, opts: &Args) -> Result { +======= +fn scan_directory(dir: PathBuf, function: Option<&str>, opts: Opts) -> Result<()> { +>>>>>>> 29c38d6 (modified into_analyzable_functions with Josh and implementation in main.rs) let mut manifest_file = dir.clone(); manifest_file.push("manifest.yaml"); if !manifest_file.exists() { @@ -230,19 +249,26 @@ fn scan_directory(dir: PathBuf, function: Option<&str>, opts: &Args) -> Result(resolved_func.into_func_path()) - // }) - // }); + let funcrefs = manifest + .modules + .into_analyzable_functions() + .flat_map(|entrypoint| { + Ok::<_, forge_loader::Error>(Entrypoint { + function: entrypoint.function.try_resolve(&paths, &dir)?, + invokable: entrypoint.invokable, + web_trigger: entrypoint.web_trigger, + }) + }); + let src_root = dir.join("src"); let mut proj = ForgeProject::with_files_and_sourceroot(src_root, paths.clone(), secret_packages); if transpiled_async { warn!("Unable to scan due to transpiled async"); +<<<<<<< HEAD +======= + return Ok(()); +>>>>>>> 29c38d6 (modified into_analyzable_functions with Josh and implementation in main.rs) } proj.add_funcs(funcrefs); resolve_calls(&mut proj.ctx); @@ -326,6 +352,7 @@ fn scan_directory(dir: PathBuf, function: Option<&str>, opts: &Args) -> Result { @@ -462,27 +489,67 @@ fn scan_directory(dir: PathBuf, function: Option<&str>, opts: &Args) -> Result { + // let mut checker = AuthZChecker::new(); + // debug!("checking {func} at {path:?}"); + // if let Err(err) = interp.run_checker(def, &mut checker, path.clone(), func.clone()) + // { + // warn!("error while scanning {func} in {path:?}: {err}"); + // } + // reporter.add_vulnerabilities(checker.into_vulns()); + // } + // FunctionTy::WebTrigger((ref func, ref path, _, def)) => { + // let mut checker = AuthenticateChecker::new(); + // debug!("checking webtrigger {func} at {path:?}"); + // if let Err(err) = + // authn_interp.run_checker(def, &mut checker, path.clone(), func.clone()) + // { + // warn!("error while scanning {func} in {path:?}: {err}"); + // } + // reporter.add_vulnerabilities(checker.into_vulns()); + // } + // } +>>>>>>> 29c38d6 (modified into_analyzable_functions with Josh and implementation in main.rs) + + // Get entrypoint value from tuple + // Logic for performing scans. + // If it's invokable, then run invokable scan. If web_trigger, then trigger scan. + // And if it's both, run both scans. if func.invokable { let mut checker = AuthZChecker::new(); - debug!("checking {:?} at {path:?}", func.function); - if let Err(err) = interp.run_checker(def, &mut checker, path.clone(), func.function.to_string()) - { - warn!("error while scanning {:?} in {path:?}: {err}", func.function); + debug!("checking {:?} at {:?}", func.func_name, &func.path); + if let Err(err) = interp.run_checker( + func.def_id, + &mut checker, + func.path.clone(), + func.func_name.to_string(), + ) { + warn!( + "error while scanning {:?} in {:?}: {err}", + func.func_name, func.path, + ); } reporter.add_vulnerabilities(checker.into_vulns()); - - } else if func.web_trigger { + } else if func.webtrigger { let mut checker = AuthenticateChecker::new(); - debug!("checking webtrigger {:?} at {path:?}", func.function); - if let Err(err) = - authn_interp.run_checker(def, &mut checker, path.clone(), func.function.to_string()) - { - warn!("error while scanning {:?} in {path:?}: {err}", func.function); + debug!( + "checking webtrigger {:?} at {:?}", + func.func_name, func.path, + ); + if let Err(err) = authn_interp.run_checker( + func.def_id, + &mut checker, + func.path.clone(), + func.func_name.to_string(), + ) { + warn!( + "error while scanning {:?} in {:?}: {err}", + func.func_name, func.path, + ); } reporter.add_vulnerabilities(checker.into_vulns()); - - } } @@ -502,8 +569,12 @@ fn scan_directory(dir: PathBuf, function: Option<&str>, opts: &Args) -> Result println!("{report}"), } +<<<<<<< HEAD Ok(proj) +======= + Ok(()) +>>>>>>> 29c38d6 (modified into_analyzable_functions with Josh and implementation in main.rs) } fn main() -> Result<()> { From eafbb5c94619ec420a94712cb9dddac9b02f8258 Mon Sep 17 00:00:00 2001 From: awang7 Date: Mon, 8 Jan 2024 13:09:36 -0800 Subject: [PATCH 274/517] rebased EAS-1893 --- crates/forge_loader/src/manifest.rs | 34 ++--------------------------- 1 file changed, 2 insertions(+), 32 deletions(-) diff --git a/crates/forge_loader/src/manifest.rs b/crates/forge_loader/src/manifest.rs index 4066aa29..c41e7275 100644 --- a/crates/forge_loader/src/manifest.rs +++ b/crates/forge_loader/src/manifest.rs @@ -37,37 +37,6 @@ struct CommonKey<'a> { #[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] struct MacroMod<'a> { - #[serde(flatten, borrow)] - common_keys: CommonKey<'a>, - config: Option<&'a str>, - export: Option<&'a str>, -} - -#[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] -struct ContentByLineItem<'a> { - #[serde(flatten, borrow)] - common_keys: CommonKey<'a>, - #[serde(borrow)] - dynamic_properties: Option<&'a str>, -} - -#[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] -struct IssueGlance<'a> { - #[serde(flatten, borrow)] - common_keys: CommonKey<'a>, - dynamic_properties: Option<&'a str>, -} -#[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] -struct AccessImportType<'a> { - #[serde(flatten, borrow)] - common_keys: CommonKey<'a>, - one_delete_import: Option<&'a str>, - start_import: Option<&'a str>, - stop_import: Option<&'a str>, - import_status: Option<&'a str>, -} - -// WebTrigger => RawTrigger; WHY IS THIS NAMED DIFFERENTLY !? WHO CHANGED NAMES common_keys: CommonKey<'a>, config: Option<&'a str>, export: Option<&'a str>, @@ -424,6 +393,7 @@ impl<'a> ForgeModules<'a> { invokable, web_trigger, }) + }); self.access_import_type.iter().for_each(|access| { invokable_functions.insert(access.common_keys.function); invokable_functions.extend(access.common_keys.resolver); @@ -672,4 +642,4 @@ mod tests { assert_eq!(func, "Catch-me-if-you-can3"); } } -} +}} From f2203f7290dd8cbd613777886cd916a0d9952bd2 Mon Sep 17 00:00:00 2001 From: Alina Wang Date: Mon, 8 Jan 2024 13:09:36 -0800 Subject: [PATCH 275/517] rebased EAS-1893 - changing author attempt --- crates/forge_loader/src/manifest.rs | 34 ++--------------------------- 1 file changed, 2 insertions(+), 32 deletions(-) diff --git a/crates/forge_loader/src/manifest.rs b/crates/forge_loader/src/manifest.rs index 4066aa29..c41e7275 100644 --- a/crates/forge_loader/src/manifest.rs +++ b/crates/forge_loader/src/manifest.rs @@ -37,37 +37,6 @@ struct CommonKey<'a> { #[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] struct MacroMod<'a> { - #[serde(flatten, borrow)] - common_keys: CommonKey<'a>, - config: Option<&'a str>, - export: Option<&'a str>, -} - -#[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] -struct ContentByLineItem<'a> { - #[serde(flatten, borrow)] - common_keys: CommonKey<'a>, - #[serde(borrow)] - dynamic_properties: Option<&'a str>, -} - -#[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] -struct IssueGlance<'a> { - #[serde(flatten, borrow)] - common_keys: CommonKey<'a>, - dynamic_properties: Option<&'a str>, -} -#[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] -struct AccessImportType<'a> { - #[serde(flatten, borrow)] - common_keys: CommonKey<'a>, - one_delete_import: Option<&'a str>, - start_import: Option<&'a str>, - stop_import: Option<&'a str>, - import_status: Option<&'a str>, -} - -// WebTrigger => RawTrigger; WHY IS THIS NAMED DIFFERENTLY !? WHO CHANGED NAMES common_keys: CommonKey<'a>, config: Option<&'a str>, export: Option<&'a str>, @@ -424,6 +393,7 @@ impl<'a> ForgeModules<'a> { invokable, web_trigger, }) + }); self.access_import_type.iter().for_each(|access| { invokable_functions.insert(access.common_keys.function); invokable_functions.extend(access.common_keys.resolver); @@ -672,4 +642,4 @@ mod tests { assert_eq!(func, "Catch-me-if-you-can3"); } } -} +}} From a75e8171c9a88a529f9edf9c148ff6fa2569ce0c Mon Sep 17 00:00:00 2001 From: awang7 Date: Wed, 3 Jan 2024 11:34:53 -0800 Subject: [PATCH 276/517] NEW: Fresh branch branching off of EAS-1893 to whitelist admin module functions --- crates/forge_loader/src/manifest.rs | 5 ++++- crates/fsrt/src/main.rs | 1 + 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/crates/forge_loader/src/manifest.rs b/crates/forge_loader/src/manifest.rs index c41e7275..9a0530aa 100644 --- a/crates/forge_loader/src/manifest.rs +++ b/crates/forge_loader/src/manifest.rs @@ -255,12 +255,13 @@ pub enum FunctionTy { WebTrigger(T), } -// Struct used for tracking what scan a funtion requires. +// Struct used for tracking what scan a funtcion requires. #[derive(Debug, Clone, PartialEq, Eq)] pub struct Entrypoint<'a, S = Unresolved> { pub function: FunctionRef<'a, S>, pub invokable: bool, pub web_trigger: bool, + pub admin: bool, } // Helper functions that help filter out which functions are what. @@ -388,10 +389,12 @@ impl<'a> ForgeModules<'a> { .binary_search_by_key(&func.key, |trigger| &trigger.function) .is_ok(); let invokable = invokable_functions.contains(func.key); + let admin = false; Ok::<_, Error>(Entrypoint { function: FunctionRef::try_from(func)?, invokable, web_trigger, + admin, }) }); self.access_import_type.iter().for_each(|access| { diff --git a/crates/fsrt/src/main.rs b/crates/fsrt/src/main.rs index 794b453c..f07a689d 100644 --- a/crates/fsrt/src/main.rs +++ b/crates/fsrt/src/main.rs @@ -264,6 +264,7 @@ fn scan_directory(dir: PathBuf, function: Option<&str>, opts: Opts) -> Result<() function: entrypoint.function.try_resolve(&paths, &dir)?, invokable: entrypoint.invokable, web_trigger: entrypoint.web_trigger, + admin: entrypoint.admin, }) }); From c752d7d644193b13c18d9064bb2dd9636825d48f Mon Sep 17 00:00:00 2001 From: awang7 Date: Wed, 3 Jan 2024 14:51:50 -0800 Subject: [PATCH 277/517] feat: tracking functions exposed in jira:adminPage module added. TODO: test whether they are being tagged. --- crates/forge_loader/src/manifest.rs | 23 +++- crates/fsrt/src/main.rs | 197 +--------------------------- 2 files changed, 22 insertions(+), 198 deletions(-) diff --git a/crates/forge_loader/src/manifest.rs b/crates/forge_loader/src/manifest.rs index 9a0530aa..b9f43fd6 100644 --- a/crates/forge_loader/src/manifest.rs +++ b/crates/forge_loader/src/manifest.rs @@ -138,6 +138,7 @@ pub struct WorkflowPostFunction<'a> { // Add more structs here for deserializing forge modules #[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] pub struct ForgeModules<'a> { + // deserializing non user-invocable modules #[serde(rename = "macro", default, borrow)] macros: Vec>, #[serde(rename = "function", default, borrow)] @@ -148,7 +149,7 @@ pub struct ForgeModules<'a> { issue_glance: Vec>, #[serde(rename = "jira:accessImportType", default, borrow)] access_import_type: Vec>, - // deserializing non user-invocable modules + // deserializing user invokable module functions #[serde(rename = "webtrigger", default, borrow)] webtriggers: Vec>, #[serde(rename = "trigger", default, borrow)] @@ -167,10 +168,20 @@ pub struct ForgeModules<'a> { pub workflow_validator: Vec>, #[serde(rename = "jira:workflowPostFunction", default, borrow)] pub workflow_post_function: Vec>, - // deserializing user invokable module functions + // deserializing admin pages + #[serde(rename = "jira:adminPage", default, borrow)] + pub jira_admin: Vec>, #[serde(flatten)] extra: FxHashMap>>, } +#[derive(Debug, Clone, PartialEq, Eq, Deserialize)] +pub struct JiraAdminPage<'a> { + key: &'a str, + function: &'a str, + resource: &'a str, + render: &'a str, + title: &'a str, +} #[derive(Debug, Clone, PartialEq, Eq, Deserialize)] pub struct Consumer<'a> { @@ -383,13 +394,19 @@ impl<'a> ForgeModules<'a> { invokable_functions.extend(access.import_status); }); + // TODO: create admin list and check whether function is in admin list then set admin bool to true. If not, set to false. self.functions.into_iter().flat_map(move |func| { let web_trigger = self .webtriggers .binary_search_by_key(&func.key, |trigger| &trigger.function) .is_ok(); let invokable = invokable_functions.contains(func.key); - let admin = false; + // this checks whether the funton being scanned is being used in an admin module. Rn it only checks for jira_admin page module. + // optionally: compass:adminPage could also be considered. + let admin = self + .jira_admin + .iter() + .any(|admin_function| admin_function.function == func.key); Ok::<_, Error>(Entrypoint { function: FunctionRef::try_from(func)?, invokable, diff --git a/crates/fsrt/src/main.rs b/crates/fsrt/src/main.rs index f07a689d..a05f4ab3 100644 --- a/crates/fsrt/src/main.rs +++ b/crates/fsrt/src/main.rs @@ -109,6 +109,7 @@ struct ResolvedEntryPoint<'a> { def_id: DefId, webtrigger: bool, invokable: bool, + admin: bool, } struct ForgeProject<'a> { @@ -185,6 +186,7 @@ impl<'a> ForgeProject<'a> { def_id, invokable: entrypoint.invokable, webtrigger: entrypoint.web_trigger, + admin: entrypoint.admin, }) })); } @@ -376,201 +378,6 @@ fn scan_directory(dir: PathBuf, function: Option<&str>, opts: Opts) -> Result<() //let mut all_used_permissions = HashSet::default(); for func in &proj.funcs { - // TODO: Update operations in for loop to scan functions. - // idea: iterate over each func which should be struct that tracks the function to be scanned. And performs scans according to bool. -<<<<<<< HEAD -<<<<<<< HEAD -<<<<<<< HEAD - match *func { - FunctionTy::Invokable((ref func, ref path, _, def)) => { - let mut runner = DefintionAnalysisRunner::new(); - debug!("checking Invokable {func} at {path:?}"); - if let Err(err) = defintion_analysis_interp.run_checker( - def, - &mut runner, - path.clone(), - func.clone(), - ) { - warn!("error while getting definition analysis {func} in {path:?}: {err}"); - } - let mut checker = AuthZChecker::new(); - debug!("Authorization Scaner on Invokable FunctionTy: checking {func} at {path:?}"); - if let Err(err) = interp.run_checker(def, &mut checker, path.clone(), func.clone()) - { - warn!("error while scanning {func} in {path:?}: {err}"); - } - reporter.add_vulnerabilities(checker.into_vulns()); - - let mut checker2 = SecretChecker::new(); - secret_interp.value_manager.varid_to_value = defintion_analysis_interp.get_defs(); - secret_interp.value_manager.defid_to_value = defintion_analysis_interp - .value_manager - .defid_to_value - .clone(); - debug!("Secret Scanner on Invokable FunctionTy: checking {func} at {path:?}"); - if let Err(err) = - secret_interp.run_checker(def, &mut checker2, path.clone(), func.clone()) - { - warn!("error while scanning {func} in {path:?}: {err}"); - } - reporter.add_vulnerabilities(checker2.into_vulns()); - - debug!("Permission Scanners on Invokable FunctionTy: checking {func} at {path:?}"); - if run_permission_checker { - perm_interp.value_manager.varid_to_value = defintion_analysis_interp.get_defs(); - perm_interp.value_manager.defid_to_value = defintion_analysis_interp - .value_manager - .defid_to_value - .clone(); - let mut checker2 = PermissionChecker::new(); - if let Err(err) = - perm_interp.run_checker(def, &mut checker2, path.clone(), func.clone()) - { - warn!("error while scanning {func} in {path:?}: {err}"); - } - } - pp_interp.value_manager.varid_to_value = defintion_analysis_interp.get_defs(); - pp_interp.value_manager.defid_to_value = defintion_analysis_interp - .value_manager - .defid_to_value - .clone(); - if let Err(e) = pp_interp.run_checker( - def, - &mut PrototypePollutionChecker, - path.clone(), - func.clone(), - ) { - warn!("error while scanning {func} in {path:?}: {e}"); - } - } - FunctionTy::WebTrigger((ref func, ref path, _, def)) => { - let mut runner = DefintionAnalysisRunner::new(); - debug!("checking Web Trigger {func} at {path:?}"); - if let Err(err) = defintion_analysis_interp.run_checker( - def, - &mut runner, - path.clone(), - func.clone(), - ) { - warn!("error while getting definition analysis {func} in {path:?}: {err}"); - } - - let mut checker2 = SecretChecker::new(); - secret_interp.value_manager.varid_to_value = defintion_analysis_interp.get_defs(); - secret_interp.value_manager.defid_to_value = defintion_analysis_interp - .value_manager - .defid_to_value - .clone(); - debug!("Secret Scanner on Web Triggers: checking {func} at {path:?}"); - if let Err(err) = - secret_interp.run_checker(def, &mut checker2, path.clone(), func.clone()) - { - warn!("error while scanning {func} in {path:?}: {err}"); - } - reporter.add_vulnerabilities(checker2.into_vulns()); - - let mut checker = AuthenticateChecker::new(); - debug!("Authentication Checker on Web Triggers: checking webtrigger {func} at {path:?}"); - if let Err(err) = - authn_interp.run_checker(def, &mut checker, path.clone(), func.clone()) - { - warn!("error while scanning {func} in {path:?}: {err}"); - } - reporter.add_vulnerabilities(checker.into_vulns()); - - debug!( - "Permission Checker on Web Triggers: checking webtrigger {func} at {path:?}" - ); - if run_permission_checker { - perm_interp.value_manager.varid_to_value = defintion_analysis_interp.get_defs(); - perm_interp.value_manager.defid_to_value = defintion_analysis_interp - .value_manager - .defid_to_value - .clone(); - let mut checker2 = PermissionChecker::new(); - if let Err(err) = - perm_interp.run_checker(def, &mut checker2, path.clone(), func.clone()) - { - warn!("error while scanning {func} in {path:?}: {err}"); - } - } -======= - // match *func { - // FunctionTy::Invokable((ref func, ref path, _, def)) => { - // let mut checker = AuthZChecker::new(); - // debug!("checking {func} at {path:?}"); - // if let Err(err) = interp.run_checker(def, &mut checker, path.clone(), func.clone()) - // { - // warn!("error while scanning {func} in {path:?}: {err}"); - // } - // reporter.add_vulnerabilities(checker.into_vulns()); - // } - // FunctionTy::WebTrigger((ref func, ref path, _, def)) => { - // let mut checker = AuthenticateChecker::new(); - // debug!("checking webtrigger {func} at {path:?}"); - // if let Err(err) = - // authn_interp.run_checker(def, &mut checker, path.clone(), func.clone()) - // { - // warn!("error while scanning {func} in {path:?}: {err}"); - // } - // reporter.add_vulnerabilities(checker.into_vulns()); - // } - // } -======= - // match *func { - // FunctionTy::Invokable((ref func, ref path, _, def)) => { - // let mut checker = AuthZChecker::new(); - // debug!("checking {func} at {path:?}"); - // if let Err(err) = interp.run_checker(def, &mut checker, path.clone(), func.clone()) - // { - // warn!("error while scanning {func} in {path:?}: {err}"); - // } - // reporter.add_vulnerabilities(checker.into_vulns()); - // } - // FunctionTy::WebTrigger((ref func, ref path, _, def)) => { - // let mut checker = AuthenticateChecker::new(); - // debug!("checking webtrigger {func} at {path:?}"); - // if let Err(err) = - // authn_interp.run_checker(def, &mut checker, path.clone(), func.clone()) - // { - // warn!("error while scanning {func} in {path:?}: {err}"); - // } - // reporter.add_vulnerabilities(checker.into_vulns()); - // } - // } ->>>>>>> 29c38d6 (modified into_analyzable_functions with Josh and implementation in main.rs) - - // Get entrypoint value from tuple - // Logic for performing scans. - // If it's invokable, then run invokable scan. If web_trigger, then trigger scan. - // And if it's both, run both scans. - if func.invokable { - let mut checker = AuthZChecker::new(); - debug!("checking {:?} at {:?}", func.func_name, &func.path); - if let Err(err) = interp.run_checker( -======= - // match *func { - // FunctionTy::Invokable((ref func, ref path, _, def)) => { - // let mut checker = AuthZChecker::new(); - // debug!("checking {func} at {path:?}"); - // if let Err(err) = interp.run_checker(def, &mut checker, path.clone(), func.clone()) - // { - // warn!("error while scanning {func} in {path:?}: {err}"); - // } - // reporter.add_vulnerabilities(checker.into_vulns()); - // } - // FunctionTy::WebTrigger((ref func, ref path, _, def)) => { - // let mut checker = AuthenticateChecker::new(); - // debug!("checking webtrigger {func} at {path:?}"); - // if let Err(err) = - // authn_interp.run_checker(def, &mut checker, path.clone(), func.clone()) - // { - // warn!("error while scanning {func} in {path:?}: {err}"); - // } - // reporter.add_vulnerabilities(checker.into_vulns()); - // } - // } - // Get entrypoint value from tuple // Logic for performing scans. // If it's invokable, then run invokable scan. If web_trigger, then trigger scan. From 3365f96369324614daa1bc517b0648851c962b7c Mon Sep 17 00:00:00 2001 From: awang7 Date: Mon, 8 Jan 2024 13:40:16 -0800 Subject: [PATCH 278/517] rebasing EAS-1673 to be head of EAS-1893 --- crates/forge_loader/src/manifest.rs | 162 ++++++++++++++++++++++------ 1 file changed, 129 insertions(+), 33 deletions(-) diff --git a/crates/forge_loader/src/manifest.rs b/crates/forge_loader/src/manifest.rs index b9f43fd6..a53ad054 100644 --- a/crates/forge_loader/src/manifest.rs +++ b/crates/forge_loader/src/manifest.rs @@ -178,8 +178,6 @@ pub struct ForgeModules<'a> { pub struct JiraAdminPage<'a> { key: &'a str, function: &'a str, - resource: &'a str, - render: &'a str, title: &'a str, } @@ -328,70 +326,70 @@ impl<'a> ForgeModules<'a> { // Get all the Triggers and represent them as a new struct thing where "webtrigger" attribute is true // for all trigger things - let mut invokable_functions = BTreeSet::new(); + let mut non_user_invokable_mod_functions = BTreeSet::new(); self.data_provider.iter().for_each(|dataprovider| { - invokable_functions.extend(dataprovider.callback); + non_user_invokable_mod_functions.extend(dataprovider.callback); }); self.custom_field.iter().for_each(|customfield| { - invokable_functions.extend(customfield.value); - invokable_functions.extend(customfield.search_suggestions); - invokable_functions.extend(customfield.edit); + non_user_invokable_mod_functions.extend(customfield.value); + non_user_invokable_mod_functions.extend(customfield.search_suggestions); + non_user_invokable_mod_functions.extend(customfield.edit); - invokable_functions.insert(customfield.common_keys.function); - invokable_functions.extend(customfield.common_keys.resolver); + non_user_invokable_mod_functions.insert(customfield.common_keys.function); + non_user_invokable_mod_functions.extend(customfield.common_keys.resolver); }); self.ui_modifications.iter().for_each(|ui| { - invokable_functions.insert(ui.common_keys.function); - invokable_functions.extend(ui.common_keys.resolver); + non_user_invokable_mod_functions.insert(ui.common_keys.function); + non_user_invokable_mod_functions.extend(ui.common_keys.resolver); }); self.workflow_validator.iter().for_each(|validator| { - invokable_functions.insert(validator.common_keys.key); + non_user_invokable_mod_functions.insert(validator.common_keys.key); - invokable_functions.insert(validator.common_keys.function); + non_user_invokable_mod_functions.insert(validator.common_keys.function); - invokable_functions.extend(validator.common_keys.resolver); + non_user_invokable_mod_functions.extend(validator.common_keys.resolver); }); self.workflow_post_function .iter() .for_each(|post_function| { - invokable_functions.insert(post_function.common_keys.key); + non_user_invokable_mod_functions.insert(post_function.common_keys.key); - invokable_functions.insert(post_function.common_keys.function); + non_user_invokable_mod_functions.insert(post_function.common_keys.function); - invokable_functions.extend(post_function.common_keys.resolver); + non_user_invokable_mod_functions.extend(post_function.common_keys.resolver); }); // get user invokable modules that have additional exposure endpoints. // ie macros has config and export fields on top of resolver fields that are functions self.macros.iter().for_each(|macros| { - invokable_functions.insert(macros.common_keys.key); + non_user_invokable_mod_functions.insert(macros.common_keys.key); - invokable_functions.insert(macros.common_keys.function); - invokable_functions.extend(macros.common_keys.resolver); + non_user_invokable_mod_functions.insert(macros.common_keys.function); + non_user_invokable_mod_functions.extend(macros.common_keys.resolver); - invokable_functions.extend(macros.config); - invokable_functions.extend(macros.export); + non_user_invokable_mod_functions.extend(macros.config); + non_user_invokable_mod_functions.extend(macros.export); }); self.issue_glance.iter().for_each(|issue| { - invokable_functions.insert(issue.common_keys.function); - invokable_functions.extend(issue.common_keys.resolver); - invokable_functions.extend(issue.dynamic_properties); + non_user_invokable_mod_functions.insert(issue.common_keys.function); + non_user_invokable_mod_functions.extend(issue.common_keys.resolver); + non_user_invokable_mod_functions.extend(issue.dynamic_properties); }); self.access_import_type.iter().for_each(|access| { - invokable_functions.insert(access.common_keys.function); - invokable_functions.extend(access.common_keys.resolver); + non_user_invokable_mod_functions.insert(access.common_keys.function); + non_user_invokable_mod_functions.extend(access.common_keys.resolver); - invokable_functions.extend(access.one_delete_import); - invokable_functions.extend(access.stop_import); - invokable_functions.extend(access.start_import); - invokable_functions.extend(access.import_status); + non_user_invokable_mod_functions.extend(access.one_delete_import); + non_user_invokable_mod_functions.extend(access.stop_import); + non_user_invokable_mod_functions.extend(access.start_import); + non_user_invokable_mod_functions.extend(access.import_status); }); // TODO: create admin list and check whether function is in admin list then set admin bool to true. If not, set to false. @@ -400,7 +398,7 @@ impl<'a> ForgeModules<'a> { .webtriggers .binary_search_by_key(&func.key, |trigger| &trigger.function) .is_ok(); - let invokable = invokable_functions.contains(func.key); + let invokable = non_user_invokable_mod_functions.contains(func.key); // this checks whether the funton being scanned is being used in an admin module. Rn it only checks for jira_admin page module. // optionally: compass:adminPage could also be considered. let admin = self @@ -662,4 +660,102 @@ mod tests { assert_eq!(func, "Catch-me-if-you-can3"); } } -}} + + // Test checking whether jira:adminPage gets flagged. + #[test] + fn test_deserialize_admin_check() { + let json = r#"{ + "app": { + "name": "My App", + "id": "my-app" + }, + "modules": { + "jira:adminPage": [ + { + "key": "testing-admin-tag", + "function": "main1", + "title": "writing-a-test-for-admin-flag" + } + ], + "macro": [ + { + "key": "my-macro", + "function": "main2" + } + ], + "function": [ + { + "key": "main1", + "handler": "index.run" + }, + { + "key": "main2", + "handler": "src.run" + } + ] + }, + "permissions": { + "scopes": [ + "my-scope" + ], + "content": { + "scripts": [ + "my-script.js" + ], + "styles": [ + "my-style.css" + ] + } + } + }"#; + let manifest: ForgeManifest = serde_json::from_str(json).unwrap(); + let mut admin_func = manifest.modules.into_analyzable_functions(); + + assert_eq!( + admin_func.next(), + Some(Entrypoint { + function: FunctionRef::try_from(FunctionMod { + key: "main1", + handler: "index.run", + providers: None, + }) + .unwrap(), + invokable: false, + web_trigger: false, + admin: true + }) + ); + + assert_eq!( + admin_func.next(), + Some(Entrypoint { + function: FunctionRef::try_from(FunctionMod { + key: "main2", + handler: "src.run", + providers: None, + }) + .unwrap(), + invokable: true, + web_trigger: false, + admin: false + }) + ); + + // assert_eq!(manifest.app.name, Some("My App")); + // assert_eq!(manifest.app.id, "my-app"); + // assert_eq!(manifest.modules.macros.len(), 1); + // assert_eq!(manifest.modules.macros[0].common_keys.key, "My Macro"); + // // assert_eq!(manifest.modules.macros[0].function, "my-macro"); + // assert_eq!(manifest.modules.functions.len(), 1); + // assert_eq!( + // manifest.modules.functions[0], + // FunctionMod { + // key: "my-function", + // handler: "my-function-handler", + // providers: Some(AuthProviders { + // auth: vec!["my-auth-provider"] + // }), + // } + // ); + } +} From d2c6d07a84e48bfd567a7703c4fb4e9263355a55 Mon Sep 17 00:00:00 2001 From: awang7 Date: Thu, 4 Jan 2024 17:35:14 -0800 Subject: [PATCH 279/517] chore: cleaning up and reverting iterator to og name. --- crates/forge_loader/src/manifest.rs | 60 ++++++++++++++--------------- 1 file changed, 30 insertions(+), 30 deletions(-) diff --git a/crates/forge_loader/src/manifest.rs b/crates/forge_loader/src/manifest.rs index a53ad054..eca1da31 100644 --- a/crates/forge_loader/src/manifest.rs +++ b/crates/forge_loader/src/manifest.rs @@ -326,70 +326,70 @@ impl<'a> ForgeModules<'a> { // Get all the Triggers and represent them as a new struct thing where "webtrigger" attribute is true // for all trigger things - let mut non_user_invokable_mod_functions = BTreeSet::new(); + let mut invokable_functions = BTreeSet::new(); self.data_provider.iter().for_each(|dataprovider| { - non_user_invokable_mod_functions.extend(dataprovider.callback); + invokable_functions.extend(dataprovider.callback); }); self.custom_field.iter().for_each(|customfield| { - non_user_invokable_mod_functions.extend(customfield.value); - non_user_invokable_mod_functions.extend(customfield.search_suggestions); - non_user_invokable_mod_functions.extend(customfield.edit); + invokable_functions.extend(customfield.value); + invokable_functions.extend(customfield.search_suggestions); + invokable_functions.extend(customfield.edit); - non_user_invokable_mod_functions.insert(customfield.common_keys.function); - non_user_invokable_mod_functions.extend(customfield.common_keys.resolver); + invokable_functions.insert(customfield.common_keys.function); + invokable_functions.extend(customfield.common_keys.resolver); }); self.ui_modifications.iter().for_each(|ui| { - non_user_invokable_mod_functions.insert(ui.common_keys.function); - non_user_invokable_mod_functions.extend(ui.common_keys.resolver); + invokable_functions.insert(ui.common_keys.function); + invokable_functions.extend(ui.common_keys.resolver); }); self.workflow_validator.iter().for_each(|validator| { - non_user_invokable_mod_functions.insert(validator.common_keys.key); + invokable_functions.insert(validator.common_keys.key); - non_user_invokable_mod_functions.insert(validator.common_keys.function); + invokable_functions.insert(validator.common_keys.function); - non_user_invokable_mod_functions.extend(validator.common_keys.resolver); + invokable_functions.extend(validator.common_keys.resolver); }); self.workflow_post_function .iter() .for_each(|post_function| { - non_user_invokable_mod_functions.insert(post_function.common_keys.key); + invokable_functions.insert(post_function.common_keys.key); - non_user_invokable_mod_functions.insert(post_function.common_keys.function); + invokable_functions.insert(post_function.common_keys.function); - non_user_invokable_mod_functions.extend(post_function.common_keys.resolver); + invokable_functions.extend(post_function.common_keys.resolver); }); // get user invokable modules that have additional exposure endpoints. // ie macros has config and export fields on top of resolver fields that are functions self.macros.iter().for_each(|macros| { - non_user_invokable_mod_functions.insert(macros.common_keys.key); + invokable_functions.insert(macros.common_keys.key); - non_user_invokable_mod_functions.insert(macros.common_keys.function); - non_user_invokable_mod_functions.extend(macros.common_keys.resolver); + invokable_functions.insert(macros.common_keys.function); + invokable_functions.extend(macros.common_keys.resolver); - non_user_invokable_mod_functions.extend(macros.config); - non_user_invokable_mod_functions.extend(macros.export); + invokable_functions.extend(macros.config); + invokable_functions.extend(macros.export); }); self.issue_glance.iter().for_each(|issue| { - non_user_invokable_mod_functions.insert(issue.common_keys.function); - non_user_invokable_mod_functions.extend(issue.common_keys.resolver); - non_user_invokable_mod_functions.extend(issue.dynamic_properties); + invokable_functions.insert(issue.common_keys.function); + invokable_functions.extend(issue.common_keys.resolver); + invokable_functions.extend(issue.dynamic_properties); }); self.access_import_type.iter().for_each(|access| { - non_user_invokable_mod_functions.insert(access.common_keys.function); - non_user_invokable_mod_functions.extend(access.common_keys.resolver); + invokable_functions.insert(access.common_keys.function); + invokable_functions.extend(access.common_keys.resolver); - non_user_invokable_mod_functions.extend(access.one_delete_import); - non_user_invokable_mod_functions.extend(access.stop_import); - non_user_invokable_mod_functions.extend(access.start_import); - non_user_invokable_mod_functions.extend(access.import_status); + invokable_functions.extend(access.one_delete_import); + invokable_functions.extend(access.stop_import); + invokable_functions.extend(access.start_import); + invokable_functions.extend(access.import_status); }); // TODO: create admin list and check whether function is in admin list then set admin bool to true. If not, set to false. @@ -398,7 +398,7 @@ impl<'a> ForgeModules<'a> { .webtriggers .binary_search_by_key(&func.key, |trigger| &trigger.function) .is_ok(); - let invokable = non_user_invokable_mod_functions.contains(func.key); + let invokable = invokable_functions.contains(func.key); // this checks whether the funton being scanned is being used in an admin module. Rn it only checks for jira_admin page module. // optionally: compass:adminPage could also be considered. let admin = self From 6372e5b0746a0ca2dec10635b090f34a0bc18b72 Mon Sep 17 00:00:00 2001 From: awang7 Date: Mon, 8 Jan 2024 10:12:26 -0800 Subject: [PATCH 280/517] clean: removed commented out functions --- crates/forge_loader/src/manifest.rs | 28 ---------------------------- 1 file changed, 28 deletions(-) diff --git a/crates/forge_loader/src/manifest.rs b/crates/forge_loader/src/manifest.rs index eca1da31..913df524 100644 --- a/crates/forge_loader/src/manifest.rs +++ b/crates/forge_loader/src/manifest.rs @@ -273,34 +273,6 @@ pub struct Entrypoint<'a, S = Unresolved> { pub admin: bool, } -// Helper functions that help filter out which functions are what. -// original code that's commented out to modify methods. Here for reference -// impl FunctionTy { -// pub fn map(self, f: impl FnOnce(T) -> O) -> FunctionTy { -// match self { -// Self::Invokable(t) => FunctionTy::Invokable(f(t)), -// Self::WebTrigger(t) => FunctionTy::WebTrigger(f(t)), -// } -// } - -// #[inline] -// pub fn into_inner(self) -> T { -// match self { -// FunctionTy::Invokable(t) | FunctionTy::WebTrigger(t) => t, -// } -// } - -// pub fn sequence( -// self, -// f: impl FnOnce(T) -> I, -// ) -> impl Iterator> { -// match self { -// Self::Invokable(t) => Either::Left(f(t).into_iter().map(FunctionTy::Invokable)), -// Self::WebTrigger(t) => Either::Right(f(t).into_iter().map(FunctionTy::WebTrigger)), -// } -// } -// } - impl AsRef for FunctionTy { #[inline] fn as_ref(&self) -> &T { From 8ff9fac98df963a56e02c9ba2a24a5c8d28c776a Mon Sep 17 00:00:00 2001 From: awang7 Date: Mon, 8 Jan 2024 14:14:46 -0800 Subject: [PATCH 281/517] chore: resolving more conflicts --- crates/forge_loader/src/manifest.rs | 33 ----------------------------- 1 file changed, 33 deletions(-) diff --git a/crates/forge_loader/src/manifest.rs b/crates/forge_loader/src/manifest.rs index c2a23460..fa0fa3f8 100644 --- a/crates/forge_loader/src/manifest.rs +++ b/crates/forge_loader/src/manifest.rs @@ -283,8 +283,6 @@ impl AsRef for FunctionTy { } impl<'a> ForgeModules<'a> { - // TODO: function returns iterator where each item is some specified type. - pub fn into_analyzable_functions(mut self) -> impl Iterator> { // TODO: function returns iterator where each item is some specified type. pub fn into_analyzable_functions(mut self) -> impl Iterator> { // number of webtriggers are usually low, so it's better to just sort them and reuse @@ -363,37 +361,6 @@ impl<'a> ForgeModules<'a> { invokable_functions.extend(access.start_import); invokable_functions.extend(access.import_status); }); - - // TODO: create admin list and check whether function is in admin list then set admin bool to true. If not, set to false. - self.functions.into_iter().flat_map(move |func| { - let web_trigger = self - .webtriggers - .binary_search_by_key(&func.key, |trigger| &trigger.function) - .is_ok(); - let invokable = invokable_functions.contains(func.key); - // this checks whether the funton being scanned is being used in an admin module. Rn it only checks for jira_admin page module. - // optionally: compass:adminPage could also be considered. - let admin = self - .jira_admin - .iter() - .any(|admin_function| admin_function.function == func.key); - Ok::<_, Error>(Entrypoint { - function: FunctionRef::try_from(func)?, - invokable, - web_trigger, - admin, - }) - }); - self.access_import_type.iter().for_each(|access| { - invokable_functions.insert(access.common_keys.function); - invokable_functions.extend(access.common_keys.resolver); - - invokable_functions.extend(access.one_delete_import); - invokable_functions.extend(access.stop_import); - invokable_functions.extend(access.start_import); - invokable_functions.extend(access.import_status); - }); - self.functions.into_iter().flat_map(move |func| { let web_trigger = self .webtriggers From 8bbe702932a03f6dc1cda55a993aa1aa88d8e1f5 Mon Sep 17 00:00:00 2001 From: Joshua Wong Date: Mon, 8 Jan 2024 18:02:37 -0600 Subject: [PATCH 282/517] fix: update checker spawning to use new entrypoint flags --- crates/fsrt/src/main.rs | 178 +++++++++++++++------------------------- 1 file changed, 65 insertions(+), 113 deletions(-) diff --git a/crates/fsrt/src/main.rs b/crates/fsrt/src/main.rs index 39bcb26c..3b9ebad0 100644 --- a/crates/fsrt/src/main.rs +++ b/crates/fsrt/src/main.rs @@ -12,16 +12,6 @@ use std::{ sync::Arc, }; -<<<<<<< HEAD -======= -use clap::{Parser, ValueHint}; -use miette::{IntoDiagnostic, Result}; - -<<<<<<< HEAD -use serde_json::map::Entry; ->>>>>>> cd2ed7b (edited main to iterate over vector of functions. TODO: edit add_funcs in main.rs and test into_analyzble_function use case in main.rs) -======= ->>>>>>> 29c38d6 (modified into_analyzable_functions with Josh and implementation in main.rs) use swc_core::{ common::{Globals, Mark, SourceMap, GLOBALS}, ecma::{ @@ -90,17 +80,6 @@ struct Args { dirs: Vec, } -<<<<<<< HEAD -struct ForgeProject { -======= -#[derive(Debug, Clone, Default)] -struct Opts { - dump_cfg: bool, - dump_callgraph: bool, - appkey: Option, - out: Option, -} - #[derive(Debug, Clone)] struct ResolvedEntryPoint<'a> { func_name: &'a str, @@ -113,17 +92,11 @@ struct ResolvedEntryPoint<'a> { } struct ForgeProject<'a> { -<<<<<<< HEAD ->>>>>>> 7e86f46 (abstracted structs to use a CommonKeys struct that holds: key, function, and resolver for less code duplication. Updated into_analayzable method to update values based on functions specified in function mod. TODO: Update rest of non trigger modules to update mapping to functions to scan from function mod) -======= ->>>>>>> refs/remotes/origin/EAS-1893 #[allow(dead_code)] sm: Arc, ctx: AppCtx, env: Environment, funcs: Vec>, - opts: Opts, ->>>>>>> cd2ed7b (edited main to iterate over vector of functions. TODO: edit add_funcs in main.rs and test into_analyzble_function use case in main.rs) } impl<'a> ForgeProject<'a> { @@ -211,15 +184,7 @@ fn collect_sourcefiles>(root: P) -> impl Iterator } #[tracing::instrument(level = "debug")] -<<<<<<< HEAD -<<<<<<< HEAD -fn scan_directory(dir: PathBuf, function: Option<&str>, opts: &Args) -> Result { -======= -fn scan_directory(dir: PathBuf, function: Option<&str>, opts: Opts) -> Result<()> { ->>>>>>> 29c38d6 (modified into_analyzable_functions with Josh and implementation in main.rs) -======= -fn scan_directory(dir: PathBuf, function: Option<&str>, opts: Opts) -> Result<()> { ->>>>>>> refs/remotes/origin/EAS-1893 +fn scan_directory(dir: PathBuf, function: Option<&str>, opts: &Args) -> Result<()> { let mut manifest_file = dir.clone(); manifest_file.push("manifest.yaml"); if !manifest_file.exists() { @@ -270,41 +235,19 @@ fn scan_directory(dir: PathBuf, function: Option<&str>, opts: Opts) -> Result<() }) }); -<<<<<<< HEAD -======= - let funcrefs = manifest - .modules - .into_analyzable_functions() - .flat_map(|entrypoint| { - Ok::<_, forge_loader::Error>(Entrypoint { - function: entrypoint.function.try_resolve(&paths, &dir)?, - invokable: entrypoint.invokable, - web_trigger: entrypoint.web_trigger, - admin: entrypoint.admin, - }) - }); - ->>>>>>> refs/remotes/origin/EAS-1893 let src_root = dir.join("src"); let mut proj = ForgeProject::with_files_and_sourceroot(src_root, paths.clone(), secret_packages); if transpiled_async { warn!("Unable to scan due to transpiled async"); -<<<<<<< HEAD -<<<<<<< HEAD -======= return Ok(()); ->>>>>>> 29c38d6 (modified into_analyzable_functions with Josh and implementation in main.rs) -======= - return Ok(()); ->>>>>>> refs/remotes/origin/EAS-1893 } proj.add_funcs(funcrefs); resolve_calls(&mut proj.ctx); if let Some(func) = opts.dump_ir.as_ref() { let mut lock = std::io::stdout().lock(); proj.env.dump_function(&mut lock, func); - return Ok(proj); + return Ok(()); } let permissions = Vec::from_iter(permissions_declared.iter().cloned()); @@ -313,7 +256,7 @@ fn scan_directory(dir: PathBuf, function: Option<&str>, opts: Opts) -> Result<() let (confluence_permission_resolver, confluence_regex_map) = get_permission_resolver_confluence(); - let mut defintion_analysis_interp = Interp::new( + let mut definition_analysis_interp = Interp::::new( &proj.env, false, true, @@ -344,18 +287,8 @@ fn scan_directory(dir: PathBuf, function: Option<&str>, opts: Opts) -> Result<() &confluence_permission_resolver, &confluence_regex_map, ); - let mut perm_interp = Interp::new( - &proj.env, - false, - true, - permissions.clone(), - &jira_permission_resolver, - &jira_regex_map, - &confluence_permission_resolver, - &confluence_regex_map, - ); let mut reporter = Reporter::new(); - let mut secret_interp = Interp::new( + let mut secret_interp = Interp::::new( &proj.env, false, false, @@ -365,20 +298,73 @@ fn scan_directory(dir: PathBuf, function: Option<&str>, opts: Opts) -> Result<() &confluence_permission_resolver, &confluence_regex_map, ); - let mut pp_interp = Interp::new( + reporter.add_app(opts.appkey.clone().unwrap_or_default(), name.to_owned()); + //let mut all_used_permissions = HashSet::default(); + + let mut perm_interp = Interp::::new( &proj.env, false, - false, + true, permissions.clone(), &jira_permission_resolver, &jira_regex_map, &confluence_permission_resolver, &confluence_regex_map, ); - reporter.add_app(opts.appkey.clone().unwrap_or_default(), name.to_owned()); - //let mut all_used_permissions = HashSet::default(); - for func in &proj.funcs { + let mut def_checker = DefintionAnalysisRunner::new(); + if let Err(err) = definition_analysis_interp.run_checker( + func.def_id, + &mut def_checker, + func.path.clone(), + func.func_name.to_string(), + ) { + warn!( + "error while scanning {:?} in {:?}: {err}", + func.func_name, func.path, + ); + } + + if run_permission_checker { + let mut checker = PermissionChecker::new(); + perm_interp.value_manager.varid_to_value = + definition_analysis_interp.value_manager.varid_to_value; + perm_interp.value_manager.defid_to_value = + definition_analysis_interp.value_manager.defid_to_value; + if let Err(err) = perm_interp.run_checker( + func.def_id, + &mut checker, + func.path.clone(), + func.func_name.to_owned(), + ) { + warn!("error while running permission checker: {err}"); + } + definition_analysis_interp.value_manager.varid_to_value = + perm_interp.value_manager.varid_to_value; + definition_analysis_interp.value_manager.defid_to_value = + perm_interp.value_manager.defid_to_value; + } + + let mut checker = SecretChecker::new(); + secret_interp.value_manager.varid_to_value = + definition_analysis_interp.value_manager.varid_to_value; + secret_interp.value_manager.defid_to_value = + definition_analysis_interp.value_manager.defid_to_value; + if let Err(err) = secret_interp.run_checker( + func.def_id, + &mut checker, + func.path.clone(), + func.func_name.to_owned(), + ) { + warn!("error while running secret checker: {err}"); + } else { + reporter.add_vulnerabilities(checker.into_vulns()); + } + definition_analysis_interp.value_manager.varid_to_value = + secret_interp.value_manager.varid_to_value; + definition_analysis_interp.value_manager.defid_to_value = + secret_interp.value_manager.defid_to_value; + // Get entrypoint value from tuple // Logic for performing scans. // If it's invokable, then run invokable scan. If web_trigger, then trigger scan. @@ -405,7 +391,6 @@ fn scan_directory(dir: PathBuf, function: Option<&str>, opts: Opts) -> Result<() func.func_name, func.path, ); if let Err(err) = authn_interp.run_checker( ->>>>>>> refs/remotes/origin/EAS-1893 func.def_id, &mut checker, func.path.clone(), @@ -417,38 +402,13 @@ fn scan_directory(dir: PathBuf, function: Option<&str>, opts: Opts) -> Result<() ); } reporter.add_vulnerabilities(checker.into_vulns()); -<<<<<<< HEAD - } else if func.webtrigger { - let mut checker = AuthenticateChecker::new(); - debug!( - "checking webtrigger {:?} at {:?}", - func.func_name, func.path, - ); - if let Err(err) = authn_interp.run_checker( - func.def_id, - &mut checker, - func.path.clone(), - func.func_name.to_string(), - ) { - warn!( - "error while scanning {:?} in {:?}: {err}", - func.func_name, func.path, - ); - } - reporter.add_vulnerabilities(checker.into_vulns()); -======= ->>>>>>> refs/remotes/origin/EAS-1893 } } - if run_permission_checker { - if perm_interp.permissions.len() > 0 { - reporter.add_vulnerabilities( - vec![PermissionVuln::new(perm_interp.permissions)].into_iter(), - ); - } + if perm_interp.permissions.len() > 0 { + reporter + .add_vulnerabilities(vec![PermissionVuln::new(perm_interp.permissions)].into_iter()); } - let report = serde_json::to_string(&reporter.into_report()).into_diagnostic()?; debug!("On the debug layer: Writing Report"); match &opts.out { @@ -457,16 +417,8 @@ fn scan_directory(dir: PathBuf, function: Option<&str>, opts: Opts) -> Result<() } None => println!("{report}"), } -<<<<<<< HEAD -<<<<<<< HEAD - Ok(proj) -======= - Ok(()) ->>>>>>> 29c38d6 (modified into_analyzable_functions with Josh and implementation in main.rs) -======= Ok(()) ->>>>>>> refs/remotes/origin/EAS-1893 } fn main() -> Result<()> { From 4928acc9bc91c64661b83eb4d9951440be44948f Mon Sep 17 00:00:00 2001 From: Joshua Wong Date: Mon, 8 Jan 2024 18:03:31 -0600 Subject: [PATCH 283/517] chore: remove unused imports in forge_loader --- crates/forge_loader/src/manifest.rs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/crates/forge_loader/src/manifest.rs b/crates/forge_loader/src/manifest.rs index fa0fa3f8..aa3def1c 100644 --- a/crates/forge_loader/src/manifest.rs +++ b/crates/forge_loader/src/manifest.rs @@ -3,14 +3,12 @@ use std::{ collections::{BTreeSet, HashSet}, hash::Hash, path::{Path, PathBuf}, - sync::Arc, }; -use crate::{forgepermissions::ForgePermissions, Error}; +use crate::Error; use forge_utils::FxHashMap; -use itertools::{Either, Itertools}; +use itertools::Itertools; use serde::Deserialize; -use serde_json::map::Entry; use tracing::trace; #[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] From fc69b593e02f9591c47db6c72ad96bbbd1e02b2f Mon Sep 17 00:00:00 2001 From: Alina Wang Date: Wed, 10 Jan 2024 18:10:43 -0800 Subject: [PATCH 284/517] chore: updated main.rs file to remove vscode notes --- crates/fsrt/src/main.rs | 366 +++++++--------------------------------- 1 file changed, 62 insertions(+), 304 deletions(-) diff --git a/crates/fsrt/src/main.rs b/crates/fsrt/src/main.rs index 794b453c..72dd4631 100644 --- a/crates/fsrt/src/main.rs +++ b/crates/fsrt/src/main.rs @@ -12,16 +12,6 @@ use std::{ sync::Arc, }; -<<<<<<< HEAD -======= -use clap::{Parser, ValueHint}; -use miette::{IntoDiagnostic, Result}; - -<<<<<<< HEAD -use serde_json::map::Entry; ->>>>>>> cd2ed7b (edited main to iterate over vector of functions. TODO: edit add_funcs in main.rs and test into_analyzble_function use case in main.rs) -======= ->>>>>>> 29c38d6 (modified into_analyzable_functions with Josh and implementation in main.rs) use swc_core::{ common::{Globals, Mark, SourceMap, GLOBALS}, ecma::{ @@ -90,17 +80,6 @@ struct Args { dirs: Vec, } -<<<<<<< HEAD -struct ForgeProject { -======= -#[derive(Debug, Clone, Default)] -struct Opts { - dump_cfg: bool, - dump_callgraph: bool, - appkey: Option, - out: Option, -} - #[derive(Debug, Clone)] struct ResolvedEntryPoint<'a> { func_name: &'a str, @@ -112,17 +91,11 @@ struct ResolvedEntryPoint<'a> { } struct ForgeProject<'a> { -<<<<<<< HEAD ->>>>>>> 7e86f46 (abstracted structs to use a CommonKeys struct that holds: key, function, and resolver for less code duplication. Updated into_analayzable method to update values based on functions specified in function mod. TODO: Update rest of non trigger modules to update mapping to functions to scan from function mod) -======= ->>>>>>> refs/remotes/origin/EAS-1893 #[allow(dead_code)] sm: Arc, ctx: AppCtx, env: Environment, funcs: Vec>, - opts: Opts, ->>>>>>> cd2ed7b (edited main to iterate over vector of functions. TODO: edit add_funcs in main.rs and test into_analyzble_function use case in main.rs) } impl<'a> ForgeProject<'a> { @@ -209,15 +182,7 @@ fn collect_sourcefiles>(root: P) -> impl Iterator } #[tracing::instrument(level = "debug")] -<<<<<<< HEAD -<<<<<<< HEAD -fn scan_directory(dir: PathBuf, function: Option<&str>, opts: &Args) -> Result { -======= -fn scan_directory(dir: PathBuf, function: Option<&str>, opts: Opts) -> Result<()> { ->>>>>>> 29c38d6 (modified into_analyzable_functions with Josh and implementation in main.rs) -======= -fn scan_directory(dir: PathBuf, function: Option<&str>, opts: Opts) -> Result<()> { ->>>>>>> refs/remotes/origin/EAS-1893 +fn scan_directory(dir: PathBuf, function: Option<&str>, opts: &Args) -> Result<()> { let mut manifest_file = dir.clone(); manifest_file.push("manifest.yaml"); if !manifest_file.exists() { @@ -267,40 +232,19 @@ fn scan_directory(dir: PathBuf, function: Option<&str>, opts: Opts) -> Result<() }) }); -<<<<<<< HEAD -======= - let funcrefs = manifest - .modules - .into_analyzable_functions() - .flat_map(|entrypoint| { - Ok::<_, forge_loader::Error>(Entrypoint { - function: entrypoint.function.try_resolve(&paths, &dir)?, - invokable: entrypoint.invokable, - web_trigger: entrypoint.web_trigger, - }) - }); - ->>>>>>> refs/remotes/origin/EAS-1893 let src_root = dir.join("src"); let mut proj = ForgeProject::with_files_and_sourceroot(src_root, paths.clone(), secret_packages); if transpiled_async { warn!("Unable to scan due to transpiled async"); -<<<<<<< HEAD -<<<<<<< HEAD -======= return Ok(()); ->>>>>>> 29c38d6 (modified into_analyzable_functions with Josh and implementation in main.rs) -======= - return Ok(()); ->>>>>>> refs/remotes/origin/EAS-1893 } proj.add_funcs(funcrefs); resolve_calls(&mut proj.ctx); if let Some(func) = opts.dump_ir.as_ref() { let mut lock = std::io::stdout().lock(); proj.env.dump_function(&mut lock, func); - return Ok(proj); + return Ok(()); } let permissions = Vec::from_iter(permissions_declared.iter().cloned()); @@ -309,7 +253,7 @@ fn scan_directory(dir: PathBuf, function: Option<&str>, opts: Opts) -> Result<() let (confluence_permission_resolver, confluence_regex_map) = get_permission_resolver_confluence(); - let mut defintion_analysis_interp = Interp::new( + let mut definition_analysis_interp = Interp::::new( &proj.env, false, true, @@ -340,18 +284,8 @@ fn scan_directory(dir: PathBuf, function: Option<&str>, opts: Opts) -> Result<() &confluence_permission_resolver, &confluence_regex_map, ); - let mut perm_interp = Interp::new( - &proj.env, - false, - true, - permissions.clone(), - &jira_permission_resolver, - &jira_regex_map, - &confluence_permission_resolver, - &confluence_regex_map, - ); let mut reporter = Reporter::new(); - let mut secret_interp = Interp::new( + let mut secret_interp = Interp::::new( &proj.env, false, false, @@ -361,214 +295,72 @@ fn scan_directory(dir: PathBuf, function: Option<&str>, opts: Opts) -> Result<() &confluence_permission_resolver, &confluence_regex_map, ); - let mut pp_interp = Interp::new( + reporter.add_app(opts.appkey.clone().unwrap_or_default(), name.to_owned()); + //let mut all_used_permissions = HashSet::default(); + + let mut perm_interp = Interp::::new( &proj.env, false, - false, + true, permissions.clone(), &jira_permission_resolver, &jira_regex_map, &confluence_permission_resolver, &confluence_regex_map, ); - reporter.add_app(opts.appkey.clone().unwrap_or_default(), name.to_owned()); - //let mut all_used_permissions = HashSet::default(); - for func in &proj.funcs { - // TODO: Update operations in for loop to scan functions. - // idea: iterate over each func which should be struct that tracks the function to be scanned. And performs scans according to bool. -<<<<<<< HEAD -<<<<<<< HEAD -<<<<<<< HEAD - match *func { - FunctionTy::Invokable((ref func, ref path, _, def)) => { - let mut runner = DefintionAnalysisRunner::new(); - debug!("checking Invokable {func} at {path:?}"); - if let Err(err) = defintion_analysis_interp.run_checker( - def, - &mut runner, - path.clone(), - func.clone(), - ) { - warn!("error while getting definition analysis {func} in {path:?}: {err}"); - } - let mut checker = AuthZChecker::new(); - debug!("Authorization Scaner on Invokable FunctionTy: checking {func} at {path:?}"); - if let Err(err) = interp.run_checker(def, &mut checker, path.clone(), func.clone()) - { - warn!("error while scanning {func} in {path:?}: {err}"); - } - reporter.add_vulnerabilities(checker.into_vulns()); - - let mut checker2 = SecretChecker::new(); - secret_interp.value_manager.varid_to_value = defintion_analysis_interp.get_defs(); - secret_interp.value_manager.defid_to_value = defintion_analysis_interp - .value_manager - .defid_to_value - .clone(); - debug!("Secret Scanner on Invokable FunctionTy: checking {func} at {path:?}"); - if let Err(err) = - secret_interp.run_checker(def, &mut checker2, path.clone(), func.clone()) - { - warn!("error while scanning {func} in {path:?}: {err}"); - } - reporter.add_vulnerabilities(checker2.into_vulns()); - - debug!("Permission Scanners on Invokable FunctionTy: checking {func} at {path:?}"); - if run_permission_checker { - perm_interp.value_manager.varid_to_value = defintion_analysis_interp.get_defs(); - perm_interp.value_manager.defid_to_value = defintion_analysis_interp - .value_manager - .defid_to_value - .clone(); - let mut checker2 = PermissionChecker::new(); - if let Err(err) = - perm_interp.run_checker(def, &mut checker2, path.clone(), func.clone()) - { - warn!("error while scanning {func} in {path:?}: {err}"); - } - } - pp_interp.value_manager.varid_to_value = defintion_analysis_interp.get_defs(); - pp_interp.value_manager.defid_to_value = defintion_analysis_interp - .value_manager - .defid_to_value - .clone(); - if let Err(e) = pp_interp.run_checker( - def, - &mut PrototypePollutionChecker, - path.clone(), - func.clone(), - ) { - warn!("error while scanning {func} in {path:?}: {e}"); - } + let mut def_checker = DefintionAnalysisRunner::new(); + if let Err(err) = definition_analysis_interp.run_checker( + func.def_id, + &mut def_checker, + func.path.clone(), + func.func_name.to_string(), + ) { + warn!( + "error while scanning {:?} in {:?}: {err}", + func.func_name, func.path, + ); + } + + if run_permission_checker { + let mut checker = PermissionChecker::new(); + perm_interp.value_manager.varid_to_value = + definition_analysis_interp.value_manager.varid_to_value; + perm_interp.value_manager.defid_to_value = + definition_analysis_interp.value_manager.defid_to_value; + if let Err(err) = perm_interp.run_checker( + func.def_id, + &mut checker, + func.path.clone(), + func.func_name.to_owned(), + ) { + warn!("error while running permission checker: {err}"); } - FunctionTy::WebTrigger((ref func, ref path, _, def)) => { - let mut runner = DefintionAnalysisRunner::new(); - debug!("checking Web Trigger {func} at {path:?}"); - if let Err(err) = defintion_analysis_interp.run_checker( - def, - &mut runner, - path.clone(), - func.clone(), - ) { - warn!("error while getting definition analysis {func} in {path:?}: {err}"); - } - - let mut checker2 = SecretChecker::new(); - secret_interp.value_manager.varid_to_value = defintion_analysis_interp.get_defs(); - secret_interp.value_manager.defid_to_value = defintion_analysis_interp - .value_manager - .defid_to_value - .clone(); - debug!("Secret Scanner on Web Triggers: checking {func} at {path:?}"); - if let Err(err) = - secret_interp.run_checker(def, &mut checker2, path.clone(), func.clone()) - { - warn!("error while scanning {func} in {path:?}: {err}"); - } - reporter.add_vulnerabilities(checker2.into_vulns()); - - let mut checker = AuthenticateChecker::new(); - debug!("Authentication Checker on Web Triggers: checking webtrigger {func} at {path:?}"); - if let Err(err) = - authn_interp.run_checker(def, &mut checker, path.clone(), func.clone()) - { - warn!("error while scanning {func} in {path:?}: {err}"); - } - reporter.add_vulnerabilities(checker.into_vulns()); - - debug!( - "Permission Checker on Web Triggers: checking webtrigger {func} at {path:?}" - ); - if run_permission_checker { - perm_interp.value_manager.varid_to_value = defintion_analysis_interp.get_defs(); - perm_interp.value_manager.defid_to_value = defintion_analysis_interp - .value_manager - .defid_to_value - .clone(); - let mut checker2 = PermissionChecker::new(); - if let Err(err) = - perm_interp.run_checker(def, &mut checker2, path.clone(), func.clone()) - { - warn!("error while scanning {func} in {path:?}: {err}"); - } - } -======= - // match *func { - // FunctionTy::Invokable((ref func, ref path, _, def)) => { - // let mut checker = AuthZChecker::new(); - // debug!("checking {func} at {path:?}"); - // if let Err(err) = interp.run_checker(def, &mut checker, path.clone(), func.clone()) - // { - // warn!("error while scanning {func} in {path:?}: {err}"); - // } - // reporter.add_vulnerabilities(checker.into_vulns()); - // } - // FunctionTy::WebTrigger((ref func, ref path, _, def)) => { - // let mut checker = AuthenticateChecker::new(); - // debug!("checking webtrigger {func} at {path:?}"); - // if let Err(err) = - // authn_interp.run_checker(def, &mut checker, path.clone(), func.clone()) - // { - // warn!("error while scanning {func} in {path:?}: {err}"); - // } - // reporter.add_vulnerabilities(checker.into_vulns()); - // } - // } -======= - // match *func { - // FunctionTy::Invokable((ref func, ref path, _, def)) => { - // let mut checker = AuthZChecker::new(); - // debug!("checking {func} at {path:?}"); - // if let Err(err) = interp.run_checker(def, &mut checker, path.clone(), func.clone()) - // { - // warn!("error while scanning {func} in {path:?}: {err}"); - // } - // reporter.add_vulnerabilities(checker.into_vulns()); - // } - // FunctionTy::WebTrigger((ref func, ref path, _, def)) => { - // let mut checker = AuthenticateChecker::new(); - // debug!("checking webtrigger {func} at {path:?}"); - // if let Err(err) = - // authn_interp.run_checker(def, &mut checker, path.clone(), func.clone()) - // { - // warn!("error while scanning {func} in {path:?}: {err}"); - // } - // reporter.add_vulnerabilities(checker.into_vulns()); - // } - // } ->>>>>>> 29c38d6 (modified into_analyzable_functions with Josh and implementation in main.rs) + definition_analysis_interp.value_manager.varid_to_value = + perm_interp.value_manager.varid_to_value; + definition_analysis_interp.value_manager.defid_to_value = + perm_interp.value_manager.defid_to_value; + } - // Get entrypoint value from tuple - // Logic for performing scans. - // If it's invokable, then run invokable scan. If web_trigger, then trigger scan. - // And if it's both, run both scans. - if func.invokable { - let mut checker = AuthZChecker::new(); - debug!("checking {:?} at {:?}", func.func_name, &func.path); - if let Err(err) = interp.run_checker( -======= - // match *func { - // FunctionTy::Invokable((ref func, ref path, _, def)) => { - // let mut checker = AuthZChecker::new(); - // debug!("checking {func} at {path:?}"); - // if let Err(err) = interp.run_checker(def, &mut checker, path.clone(), func.clone()) - // { - // warn!("error while scanning {func} in {path:?}: {err}"); - // } - // reporter.add_vulnerabilities(checker.into_vulns()); - // } - // FunctionTy::WebTrigger((ref func, ref path, _, def)) => { - // let mut checker = AuthenticateChecker::new(); - // debug!("checking webtrigger {func} at {path:?}"); - // if let Err(err) = - // authn_interp.run_checker(def, &mut checker, path.clone(), func.clone()) - // { - // warn!("error while scanning {func} in {path:?}: {err}"); - // } - // reporter.add_vulnerabilities(checker.into_vulns()); - // } - // } + let mut checker = SecretChecker::new(); + secret_interp.value_manager.varid_to_value = + definition_analysis_interp.value_manager.varid_to_value; + secret_interp.value_manager.defid_to_value = + definition_analysis_interp.value_manager.defid_to_value; + if let Err(err) = secret_interp.run_checker( + func.def_id, + &mut checker, + func.path.clone(), + func.func_name.to_owned(), + ) { + warn!("error while running secret checker: {err}"); + } else { + reporter.add_vulnerabilities(checker.into_vulns()); + } + definition_analysis_interp.value_manager.varid_to_value = + secret_interp.value_manager.varid_to_value; + definition_analysis_interp.value_manager.defid_to_value = + secret_interp.value_manager.defid_to_value; // Get entrypoint value from tuple // Logic for performing scans. @@ -596,26 +388,6 @@ fn scan_directory(dir: PathBuf, function: Option<&str>, opts: Opts) -> Result<() func.func_name, func.path, ); if let Err(err) = authn_interp.run_checker( ->>>>>>> refs/remotes/origin/EAS-1893 - func.def_id, - &mut checker, - func.path.clone(), - func.func_name.to_string(), - ) { - warn!( - "error while scanning {:?} in {:?}: {err}", - func.func_name, func.path, - ); - } - reporter.add_vulnerabilities(checker.into_vulns()); -<<<<<<< HEAD - } else if func.webtrigger { - let mut checker = AuthenticateChecker::new(); - debug!( - "checking webtrigger {:?} at {:?}", - func.func_name, func.path, - ); - if let Err(err) = authn_interp.run_checker( func.def_id, &mut checker, func.path.clone(), @@ -627,19 +399,13 @@ fn scan_directory(dir: PathBuf, function: Option<&str>, opts: Opts) -> Result<() ); } reporter.add_vulnerabilities(checker.into_vulns()); -======= ->>>>>>> refs/remotes/origin/EAS-1893 } } - if run_permission_checker { - if perm_interp.permissions.len() > 0 { - reporter.add_vulnerabilities( - vec![PermissionVuln::new(perm_interp.permissions)].into_iter(), - ); - } + if perm_interp.permissions.len() > 0 { + reporter + .add_vulnerabilities(vec![PermissionVuln::new(perm_interp.permissions)].into_iter()); } - let report = serde_json::to_string(&reporter.into_report()).into_diagnostic()?; debug!("On the debug layer: Writing Report"); match &opts.out { @@ -648,16 +414,8 @@ fn scan_directory(dir: PathBuf, function: Option<&str>, opts: Opts) -> Result<() } None => println!("{report}"), } -<<<<<<< HEAD -<<<<<<< HEAD - Ok(proj) -======= - Ok(()) ->>>>>>> 29c38d6 (modified into_analyzable_functions with Josh and implementation in main.rs) -======= Ok(()) ->>>>>>> refs/remotes/origin/EAS-1893 } fn main() -> Result<()> { From 15e814b8ae9ee9995123788c3a745189426b4cca Mon Sep 17 00:00:00 2001 From: Alina Wang Date: Wed, 10 Jan 2024 18:10:50 -0800 Subject: [PATCH 285/517] resolving merge conflicts for rebase --- crates/forge_loader/src/manifest.rs | 132 ++++++++++++++++++++++++++-- 1 file changed, 125 insertions(+), 7 deletions(-) diff --git a/crates/forge_loader/src/manifest.rs b/crates/forge_loader/src/manifest.rs index f980e78e..41256df8 100644 --- a/crates/forge_loader/src/manifest.rs +++ b/crates/forge_loader/src/manifest.rs @@ -66,18 +66,56 @@ struct ScheduledTrigger<'a> { interval: Interval, } +#[derive(Debug, Clone, PartialEq, Eq, Deserialize)] +struct DataProvider<'a> { + key: &'a str, + #[serde(flatten, borrow)] + callback: Callback<'a>, +} + +#[derive(Debug, Clone, PartialEq, Eq, Deserialize)] +pub struct Callback<'a> { + pub function: &'a str, +} + +// Struct for Custom field Module. Check that search suggestion gets read in correctly. +#[derive(Debug, Clone, PartialEq, Eq, Deserialize)] +struct CustomField<'a> { + #[serde(flatten, borrow)] + key: &'a str, + search_suggestion: &'a str, +} + +#[derive(Debug, Clone, PartialEq, Eq, Deserialize)] +struct UiModificatons<'a> { + #[serde(flatten, borrow)] + key: &'a str, + resolver: Callback<'a>, +} + +#[derive(Debug, Clone, PartialEq, Eq, Deserialize)] +struct WorkflowValidator<'a> { + #[serde(flatten, borrow)] + key: &'a str, + functon: &'a str, + resolver: Callback<'a>, +} + #[derive(Debug, Clone, PartialEq, Eq, Deserialize)] struct WorkflowPostFunction<'a> { #[serde(flatten, borrow)] - raw: RawTrigger<'a>, + key: &'a str, + function: &'a str, } +// Add more structs here for deserializing forge modules #[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] pub struct ForgeModules<'a> { #[serde(rename = "macro", default, borrow)] macros: Vec>, #[serde(rename = "function", default, borrow)] pub functions: Vec>, + // deserializing non user-invocable modules #[serde(rename = "webtrigger", default, borrow)] webtriggers: Vec>, #[serde(rename = "trigger", default, borrow)] @@ -86,8 +124,17 @@ pub struct ForgeModules<'a> { scheduled_triggers: Vec>, #[serde(rename = "consumer", default, borrow)] pub consumers: Vec>, + #[serde(rename = "compass:dataProvider", default, borrow)] + pub data_provider: Vec>, + #[serde(rename = "jira:customField", default, borrow)] + pub custom_field: Vec>, + #[serde(rename = "jira:uiModificatons", default, borrow)] + pub ui_modifications: Vec>, + #[serde(rename = "jira:workflowValidator", default, borrow)] + pub workflow_validator: Vec>, #[serde(rename = "jira:workflowPostFunction", default, borrow)] - workflow_post_functions: Vec>, + pub workflow_post_function: Vec>, + // deserializing user invokable module functions #[serde(flatten)] extra: FxHashMap>>, } @@ -166,12 +213,24 @@ pub struct FunctionRef<'a, S = Unresolved> { status: S, } +// Add an extra variant to the FunctionTy enum for non user invocable functions +// Indirect: functions indirectly invoked by user :O So kewl. +// TODO: change this to struct with bools #[derive(Debug, Clone, PartialEq, Eq)] pub enum FunctionTy { Invokable(T), WebTrigger(T), } +// Struct used for tracking what scan a funtion requires. +#[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] +pub struct Entrypoints<'a> { + function: Vec>, + invokable: bool, + web_trigger: bool, +} + +// Helper functions that help filter out which functions are what. impl FunctionTy { pub fn map(self, f: impl FnOnce(T) -> O) -> FunctionTy { match self { @@ -215,11 +274,9 @@ impl<'a> ForgeModules<'a> { //is not invoked by a user-invocable module. // number of webtriggers are usually low, so it's better to just sort them and reuse - // the vec's storage instead of using a HashSet self.webtriggers .sort_unstable_by_key(|trigger| trigger.function); - // same rational for using a BTreeSet let mut ignored_functions: BTreeSet<_> = self .scheduled_triggers .into_iter() @@ -230,13 +287,14 @@ impl<'a> ForgeModules<'a> { .map(|trigger| trigger.raw.function), ) .chain( - self.workflow_post_functions + self.workflow_post_function .into_iter() - .map(|pf| pf.raw.function), + .map(|pf| pf.function), ) .collect(); - let mut alternate_functions = vec![]; + // make alternate_functions all user-invokable functions + let mut alternate_functions: Vec<&str> = Vec::new(); for module in self.extra.into_values().flatten() { alternate_functions.extend(module.function); if let Some(resolver) = module.resolver { @@ -244,12 +302,19 @@ impl<'a> ForgeModules<'a> { } } + // Iterate over Consumers and check that if consumers isn't in alternate functions, add consumer funtion to be ignored + // assuming that alternate functions already has all user invokable functions. self.consumers.iter().for_each(|consumer| { if !alternate_functions.contains(&consumer.resolver.function) { ignored_functions.insert(consumer.resolver.function); } }); + // TODO: Iterate through all deserialized entrypoints that are represented as a struct when deserialized + // Update Struct values to be true or not. If any part true, then scan. + // This solution fixes the problem that we only check known user invokable modules and also acccounts for non-invokable module entry points + + // return non-user invokable functions self.functions.into_iter().filter_map(move |func| { if ignored_functions.contains(&func.key) { return None; @@ -416,4 +481,57 @@ mod tests { } ); } + + // Modified specific deserialization schemes for modules. Checking that new schemes can deserialize function values. + #[test] + fn test_new_deserialize() { + let json = r#"{ + "app": { + "name": "My App", + "id": "my-app" + }, + "modules": { + "macro": [ + { + "key": "my-macro", + "title": "My Macro", + "resolver": { + "function": Catch-me-if-you-can1 + }, + "config": { + "function": Catch-me-if-you-can2 + } + } + ], + "function": [ + { + "key": "my-function", + "handler": "my-function-handler", + "providers": { + "auth": ["my-auth-provider"] + } + } + ], + "webtrigger": [ + { + "key": "my-webtrigger", + "function": "my-webtrigger-handler" + } + ] + }, + "permissions": { + "scopes": [ + "my-scope" + ], + "content": { + "scripts": [ + "my-script.js" + ], + "styles": [ + "my-style.css" + ] + } + } + }"#; + } } From 96f49172e3d646c5fc5c2b48348c71d1e86d0906 Mon Sep 17 00:00:00 2001 From: Alina Wang Date: Wed, 10 Jan 2024 18:10:50 -0800 Subject: [PATCH 286/517] added more todo statements and cleaned up struct definitions --- crates/forge_loader/src/manifest.rs | 21 +++++++++++++++------ crates/fsrt/src/main.rs | 2 ++ 2 files changed, 17 insertions(+), 6 deletions(-) diff --git a/crates/forge_loader/src/manifest.rs b/crates/forge_loader/src/manifest.rs index 41256df8..8cf22a68 100644 --- a/crates/forge_loader/src/manifest.rs +++ b/crates/forge_loader/src/manifest.rs @@ -16,7 +16,7 @@ struct AuthProviders<'a> { #[serde(borrow)] auth: Vec<&'a str>, } - +// Maps the Functions Module in common Modules #[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] pub struct FunctionMod<'a> { key: &'a str, @@ -25,24 +25,30 @@ pub struct FunctionMod<'a> { providers: Option>, } +// Modified #[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] struct ModInfo<'a> { - key: &'a str, - title: &'a str, + function: &'a str, } #[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] -struct MacroMod<'a> { - #[serde(flatten, borrow)] - info: ModInfo<'a>, +struct MacroMod<'a> { + #[serde(flatten, borrow)] + key: &'a str, + function: &'a str, + resolver: ModInfo<'a>, + config: ModInfo<'a>, + export: ModInfo<'a>, } +// WebTrigger => RawTrigger; WHY IS THIS NAMED DIFFERENTLY !? WHO CHANGED NAMES #[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] struct RawTrigger<'a> { key: &'a str, function: &'a str, } +// Trigger => EventTriger; WHY IS THIS NAMED DIFFERENTLY !? WHO CHANGED NAMES #[derive(Debug, Clone, PartialEq, Eq, Deserialize)] struct EventTrigger<'a> { #[serde(flatten, borrow)] @@ -59,6 +65,7 @@ enum Interval { Week, } +// Thank you to whomeever kept this one the same. T.T #[derive(Debug, Clone, PartialEq, Eq, Deserialize)] struct ScheduledTrigger<'a> { #[serde(flatten, borrow)] @@ -66,6 +73,7 @@ struct ScheduledTrigger<'a> { interval: Interval, } +// compass DataProvider module #[derive(Debug, Clone, PartialEq, Eq, Deserialize)] struct DataProvider<'a> { key: &'a str, @@ -73,6 +81,7 @@ struct DataProvider<'a> { callback: Callback<'a>, } +// Struct for mapping functions defined one more level in whose value is {function: string}. Used to represent resolver types. #[derive(Debug, Clone, PartialEq, Eq, Deserialize)] pub struct Callback<'a> { pub function: &'a str, diff --git a/crates/fsrt/src/main.rs b/crates/fsrt/src/main.rs index 770d7a70..b1281be5 100644 --- a/crates/fsrt/src/main.rs +++ b/crates/fsrt/src/main.rs @@ -298,6 +298,8 @@ fn scan_directory(dir: PathBuf, function: Option<&str>, opts: &Args) -> Result { let mut runner = DefintionAnalysisRunner::new(); From ad665ab6a8538395ff6b4937c91c6888bae1ae84 Mon Sep 17 00:00:00 2001 From: Alina Wang Date: Wed, 10 Jan 2024 18:10:51 -0800 Subject: [PATCH 287/517] resolving merge conflicts for rebase --- crates/forge_loader/src/manifest.rs | 221 ++++++++++++++----- crates/forge_permission_resolver/src/test.rs | 7 + 2 files changed, 168 insertions(+), 60 deletions(-) diff --git a/crates/forge_loader/src/manifest.rs b/crates/forge_loader/src/manifest.rs index 8cf22a68..1ef7ca6d 100644 --- a/crates/forge_loader/src/manifest.rs +++ b/crates/forge_loader/src/manifest.rs @@ -9,6 +9,7 @@ use crate::{forgepermissions::ForgePermissions, Error}; use forge_utils::FxHashMap; use itertools::{Either, Itertools}; use serde::Deserialize; +use serde_json::map::Entry; use tracing::trace; #[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] @@ -16,7 +17,7 @@ struct AuthProviders<'a> { #[serde(borrow)] auth: Vec<&'a str>, } -// Maps the Functions Module in common Modules +// Maps the Functions Module in common Modules #[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] pub struct FunctionMod<'a> { key: &'a str, @@ -25,15 +26,15 @@ pub struct FunctionMod<'a> { providers: Option>, } -// Modified +// Modified #[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] struct ModInfo<'a> { function: &'a str, } #[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] -struct MacroMod<'a> { - #[serde(flatten, borrow)] +struct MacroMod<'a> { + #[serde(flatten, borrow)] key: &'a str, function: &'a str, resolver: ModInfo<'a>, @@ -41,14 +42,14 @@ struct MacroMod<'a> { export: ModInfo<'a>, } -// WebTrigger => RawTrigger; WHY IS THIS NAMED DIFFERENTLY !? WHO CHANGED NAMES +// WebTrigger => RawTrigger; WHY IS THIS NAMED DIFFERENTLY !? WHO CHANGED NAMES #[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] struct RawTrigger<'a> { key: &'a str, function: &'a str, } -// Trigger => EventTriger; WHY IS THIS NAMED DIFFERENTLY !? WHO CHANGED NAMES +// Trigger => EventTriger; WHY IS THIS NAMED DIFFERENTLY !? WHO CHANGED NAMES #[derive(Debug, Clone, PartialEq, Eq, Deserialize)] struct EventTrigger<'a> { #[serde(flatten, borrow)] @@ -65,7 +66,7 @@ enum Interval { Week, } -// Thank you to whomeever kept this one the same. T.T +// Thank you to whomeever kept this one the same. T.T #[derive(Debug, Clone, PartialEq, Eq, Deserialize)] struct ScheduledTrigger<'a> { #[serde(flatten, borrow)] @@ -75,7 +76,7 @@ struct ScheduledTrigger<'a> { // compass DataProvider module #[derive(Debug, Clone, PartialEq, Eq, Deserialize)] -struct DataProvider<'a> { +pub struct DataProvider<'a> { key: &'a str, #[serde(flatten, borrow)] callback: Callback<'a>, @@ -89,21 +90,21 @@ pub struct Callback<'a> { // Struct for Custom field Module. Check that search suggestion gets read in correctly. #[derive(Debug, Clone, PartialEq, Eq, Deserialize)] -struct CustomField<'a> { +pub struct CustomField<'a> { #[serde(flatten, borrow)] key: &'a str, search_suggestion: &'a str, } #[derive(Debug, Clone, PartialEq, Eq, Deserialize)] -struct UiModificatons<'a> { +pub struct UiModificatons<'a> { #[serde(flatten, borrow)] key: &'a str, resolver: Callback<'a>, } #[derive(Debug, Clone, PartialEq, Eq, Deserialize)] -struct WorkflowValidator<'a> { +pub struct WorkflowValidator<'a> { #[serde(flatten, borrow)] key: &'a str, functon: &'a str, @@ -111,7 +112,7 @@ struct WorkflowValidator<'a> { } #[derive(Debug, Clone, PartialEq, Eq, Deserialize)] -struct WorkflowPostFunction<'a> { +pub struct WorkflowPostFunction<'a> { #[serde(flatten, borrow)] key: &'a str, function: &'a str, @@ -232,9 +233,9 @@ pub enum FunctionTy { } // Struct used for tracking what scan a funtion requires. -#[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] +#[derive(Debug, Clone, PartialEq, Eq, Deserialize)] pub struct Entrypoints<'a> { - function: Vec>, + function: &'a str, invokable: bool, web_trigger: bool, } @@ -276,70 +277,170 @@ impl AsRef for FunctionTy { } impl<'a> ForgeModules<'a> { - pub fn into_analyzable_functions( - mut self, - ) -> impl Iterator>> { - //FIXME: The logic here is incorrect, a function should be in the ignored function IIF it - //is not invoked by a user-invocable module. - + // TODO: fix return type whop + pub fn into_analyzable_functions(mut self) -> impl Iterator> { // number of webtriggers are usually low, so it's better to just sort them and reuse self.webtriggers .sort_unstable_by_key(|trigger| trigger.function); - let mut ignored_functions: BTreeSet<_> = self + // Get all the Triggers and represent them as a new struct thing where "webtrigger" attribute is true + // for all trigger things + let web = self.webtriggers.iter().for_each(|webtriggers| { + Entrypoints { + function: webtriggers.function, + invokable: false, + web_trigger: true, + }; + }); + let event = self.event_triggers.iter().for_each(|event_triggers| { + Entrypoints { + function: event_triggers.raw.function, + invokable: false, + web_trigger: true, + }; + }); + let schedule = self .scheduled_triggers - .into_iter() - .map(|trigger| trigger.raw.function) - .chain( - self.event_triggers - .into_iter() - .map(|trigger| trigger.raw.function), - ) - .chain( - self.workflow_post_function - .into_iter() - .map(|pf| pf.function), - ) - .collect(); + .iter() + .for_each(|schedule_triggers| { + Entrypoints { + function: schedule_triggers.raw.function, + invokable: false, + web_trigger: true, + }; + }); + + // create arrays representing functions that expose user non-invokable functions + let consumer = self.consumers.iter().for_each(|consumers| { + Entrypoints { + function: consumers.resolver.function, + invokable: true, + web_trigger: false, + }; + }); + + let data_provider = self.data_provider.iter().for_each(|dataprovider| { + Entrypoints { + function: dataprovider.callback.function, + invokable: true, + web_trigger: false, + }; + }); + + let custom_field = self.custom_field.iter().for_each(|customfield| { + Entrypoints { + function: customfield.search_suggestion, + invokable: true, + web_trigger: false, + }; + }); + let ui_mod = self.ui_modifications.iter().for_each(|ui| { + Entrypoints { + function: ui.resolver.function, + invokable: true, + web_trigger: false, + }; + }); + + let workflow_validator = self.workflow_validator.iter().for_each(|validator| { + Entrypoints { + function: validator.resolver.function, + invokable: true, + web_trigger: false, + }; + }); + + let workflow_post = self + .workflow_post_function + .iter() + .for_each(|post_function| { + Entrypoints { + function: post_function.function, + invokable: true, + web_trigger: false, + }; + }); + + // let user_invokable = self.extra.into_values().flatten().into_iter().for_each(|invokable| { + + // if invokable.resolver != None { + // Entrypoints { + // function: invokable.resolver, + // invokable: true, + // web_trigger: false, + // }; + + // } + // Entrypoints { + // function: invokable.function, + // invokable: true, + // web_trigger: false, + // }; + // }); + + // let mut ignored_functions: BTreeSet<_> = self + // .scheduled_triggers + // .into_iter() + // .map(|trigger| trigger.raw.function) + // .chain( + // self.event_triggers + // .into_iter() + // .map(|trigger| trigger.raw.function), + // ) + // .collect(); + + // get array for user invokable module functions // make alternate_functions all user-invokable functions - let mut alternate_functions: Vec<&str> = Vec::new(); + let mut alternate_functions = Vec::new(); for module in self.extra.into_values().flatten() { - alternate_functions.extend(module.function); + if let Some(mod_function) = module.function { + alternate_functions.push(Entrypoints { + function: mod_function, + invokable: true, + web_trigger: false, + }); + } + if let Some(resolver) = module.resolver { - alternate_functions.push(resolver.function); + alternate_functions.push(Entrypoints { + function: resolver.function, + invokable: true, + web_trigger: false, + }); } } + workflow_post // Iterate over Consumers and check that if consumers isn't in alternate functions, add consumer funtion to be ignored // assuming that alternate functions already has all user invokable functions. - self.consumers.iter().for_each(|consumer| { - if !alternate_functions.contains(&consumer.resolver.function) { - ignored_functions.insert(consumer.resolver.function); - } - }); + // self.consumers.iter().for_each(|consumer| { + // if !alternate_functions.contains(&consumer.resolver.function) { + // ignored_functions.insert(consumer.resolver.function); + // } + // }); // TODO: Iterate through all deserialized entrypoints that are represented as a struct when deserialized // Update Struct values to be true or not. If any part true, then scan. // This solution fixes the problem that we only check known user invokable modules and also acccounts for non-invokable module entry points // return non-user invokable functions - self.functions.into_iter().filter_map(move |func| { - if ignored_functions.contains(&func.key) { - return None; - } - Some( - if self - .webtriggers - .binary_search_by_key(&func.key, |trigger| trigger.function) - .is_ok() - { - FunctionTy::WebTrigger(func) - } else { - FunctionTy::Invokable(func) - }, - ) - }) + // self.functions.into_iter().filter_map(move |func| { + // if ignored_functions.contains(&func.key) { + // return None; + // } + // Some( + // if self + // .webtriggers + // .binary_search_by_key(&func.key, |trigger| trigger.function) + // .is_ok() + // { + // FunctionTy::WebTrigger(func) + // } else { + // FunctionTy::Invokable(func) + // }, + // ) + // }) } } @@ -455,8 +556,8 @@ mod tests { assert_eq!(manifest.app.name, Some("My App")); assert_eq!(manifest.app.id, "my-app"); assert_eq!(manifest.modules.macros.len(), 1); - assert_eq!(manifest.modules.macros[0].info.title, "My Macro"); - assert_eq!(manifest.modules.macros[0].info.key, "my-macro"); + assert_eq!(manifest.modules.macros[0].key, "My Macro"); + assert_eq!(manifest.modules.macros[0].function, "my-macro"); assert_eq!(manifest.modules.functions.len(), 1); assert_eq!( manifest.modules.functions[0], diff --git a/crates/forge_permission_resolver/src/test.rs b/crates/forge_permission_resolver/src/test.rs index e9b8a01e..a0e6ad08 100644 --- a/crates/forge_permission_resolver/src/test.rs +++ b/crates/forge_permission_resolver/src/test.rs @@ -75,4 +75,11 @@ mod tests { assert_eq!(result, expected_permission); } + + + // TODO: Add a test case using a manifest that has a function exposed through both a non user invocable module and a user invocable module + #[test] + fn test_catch_indirect_func_invoke() { + assert_eq!(0, 0); + } } From b58c126cab2d7c47e0a2fa82838bf1f423100d5b Mon Sep 17 00:00:00 2001 From: Alina Wang Date: Wed, 10 Jan 2024 18:10:51 -0800 Subject: [PATCH 288/517] resolving merge conflicts --- crates/forge_loader/src/manifest.rs | 76 ++++++++++++++--------------- 1 file changed, 38 insertions(+), 38 deletions(-) diff --git a/crates/forge_loader/src/manifest.rs b/crates/forge_loader/src/manifest.rs index 1ef7ca6d..7cc0b617 100644 --- a/crates/forge_loader/src/manifest.rs +++ b/crates/forge_loader/src/manifest.rs @@ -277,89 +277,89 @@ impl AsRef for FunctionTy { } impl<'a> ForgeModules<'a> { - // TODO: fix return type whop - pub fn into_analyzable_functions(mut self) -> impl Iterator> { + // TODO: function returns iterator where each item is some specified type. + pub fn into_analyzable_functions(mut self) { // number of webtriggers are usually low, so it's better to just sort them and reuse - self.webtriggers - .sort_unstable_by_key(|trigger| trigger.function); + // self.webtriggers + // .sort_unstable_by_key(|trigger| trigger.function); // Get all the Triggers and represent them as a new struct thing where "webtrigger" attribute is true // for all trigger things - let web = self.webtriggers.iter().for_each(|webtriggers| { - Entrypoints { + + let mut functions_to_scan = Vec::new(); + self.webtriggers.iter().for_each(|webtriggers| { + functions_to_scan.push(Entrypoints { function: webtriggers.function, invokable: false, web_trigger: true, - }; + }); }); - let event = self.event_triggers.iter().for_each(|event_triggers| { - Entrypoints { + self.event_triggers.iter().for_each(|event_triggers| { + functions_to_scan.push(Entrypoints { function: event_triggers.raw.function, invokable: false, web_trigger: true, - }; + }); }); - let schedule = self - .scheduled_triggers + self.scheduled_triggers .iter() .for_each(|schedule_triggers| { - Entrypoints { + functions_to_scan.push(Entrypoints { function: schedule_triggers.raw.function, invokable: false, web_trigger: true, - }; + }) }); // create arrays representing functions that expose user non-invokable functions - let consumer = self.consumers.iter().for_each(|consumers| { - Entrypoints { + self.consumers.iter().for_each(|consumers| { + functions_to_scan.push(Entrypoints { function: consumers.resolver.function, invokable: true, web_trigger: false, - }; + }) }); - let data_provider = self.data_provider.iter().for_each(|dataprovider| { - Entrypoints { + self.data_provider.iter().for_each(|dataprovider| { + functions_to_scan.push(Entrypoints { function: dataprovider.callback.function, invokable: true, web_trigger: false, - }; + }) }); - let custom_field = self.custom_field.iter().for_each(|customfield| { - Entrypoints { + self.custom_field.iter().for_each(|customfield| { + functions_to_scan.push(Entrypoints { function: customfield.search_suggestion, invokable: true, web_trigger: false, - }; + }) }); - let ui_mod = self.ui_modifications.iter().for_each(|ui| { - Entrypoints { + self.ui_modifications.iter().for_each(|ui| { + functions_to_scan.push(Entrypoints { function: ui.resolver.function, invokable: true, web_trigger: false, - }; + }) }); - let workflow_validator = self.workflow_validator.iter().for_each(|validator| { - Entrypoints { + self.workflow_validator.iter().for_each(|validator| { + functions_to_scan.push(Entrypoints { function: validator.resolver.function, invokable: true, web_trigger: false, - }; + }) }); - let workflow_post = self - .workflow_post_function + self.workflow_post_function .iter() .for_each(|post_function| { - Entrypoints { + functions_to_scan.push(Entrypoints { function: post_function.function, invokable: true, web_trigger: false, - }; + }) }); // let user_invokable = self.extra.into_values().flatten().into_iter().for_each(|invokable| { @@ -392,10 +392,10 @@ impl<'a> ForgeModules<'a> { // get array for user invokable module functions // make alternate_functions all user-invokable functions - let mut alternate_functions = Vec::new(); + // let mut alternate_functions = Vec::new(); for module in self.extra.into_values().flatten() { if let Some(mod_function) = module.function { - alternate_functions.push(Entrypoints { + functions_to_scan.push(Entrypoints { function: mod_function, invokable: true, web_trigger: false, @@ -403,15 +403,15 @@ impl<'a> ForgeModules<'a> { } if let Some(resolver) = module.resolver { - alternate_functions.push(Entrypoints { + functions_to_scan.push(Entrypoints { function: resolver.function, invokable: true, web_trigger: false, }); } } - - workflow_post + functions_to_scan.into_iter(); + // alternate_functions.into_iter(); // Iterate over Consumers and check that if consumers isn't in alternate functions, add consumer funtion to be ignored // assuming that alternate functions already has all user invokable functions. // self.consumers.iter().for_each(|consumer| { From 36bde44643b28774d6574d7ad9b0efffa90729ba Mon Sep 17 00:00:00 2001 From: Alina Wang Date: Wed, 10 Jan 2024 18:10:51 -0800 Subject: [PATCH 289/517] resolving merge conflicts --- crates/forge_loader/src/manifest.rs | 53 ++++++++++++++--------------- 1 file changed, 26 insertions(+), 27 deletions(-) diff --git a/crates/forge_loader/src/manifest.rs b/crates/forge_loader/src/manifest.rs index 7cc0b617..52384ffe 100644 --- a/crates/forge_loader/src/manifest.rs +++ b/crates/forge_loader/src/manifest.rs @@ -278,7 +278,7 @@ impl AsRef for FunctionTy { impl<'a> ForgeModules<'a> { // TODO: function returns iterator where each item is some specified type. - pub fn into_analyzable_functions(mut self) { + pub fn into_analyzable_functions(self) { // number of webtriggers are usually low, so it's better to just sort them and reuse // self.webtriggers // .sort_unstable_by_key(|trigger| trigger.function); @@ -362,33 +362,32 @@ impl<'a> ForgeModules<'a> { }) }); - // let user_invokable = self.extra.into_values().flatten().into_iter().for_each(|invokable| { - - // if invokable.resolver != None { - // Entrypoints { - // function: invokable.resolver, - // invokable: true, - // web_trigger: false, - // }; - - // } - // Entrypoints { - // function: invokable.function, - // invokable: true, - // web_trigger: false, - // }; - // }); + // get user invokable modules that have additional exposure endpoints. + // ie macros has config and export fields on top of resolver fields that are functions + for macros in self.macros { + if let Some(resolver) = Some(macros.resolver) { + functions_to_scan.push(Entrypoints { + function: resolver.function, + invokable: true, + web_trigger: false, + }) + } - // let mut ignored_functions: BTreeSet<_> = self - // .scheduled_triggers - // .into_iter() - // .map(|trigger| trigger.raw.function) - // .chain( - // self.event_triggers - // .into_iter() - // .map(|trigger| trigger.raw.function), - // ) - // .collect(); + if let Some(config) = Some(macros.config) { + functions_to_scan.push(Entrypoints { + function: config.function, + invokable: true, + web_trigger: false, + }) + } + if let Some(export) = Some(macros.export) { + functions_to_scan.push(Entrypoints { + function: export.function, + invokable: true, + web_trigger: false, + }) + } + } // get array for user invokable module functions // make alternate_functions all user-invokable functions From 4073a5e423ebc02ad052c9ae255be2148c2e9747 Mon Sep 17 00:00:00 2001 From: Alina Wang Date: Wed, 10 Jan 2024 18:10:51 -0800 Subject: [PATCH 290/517] resolving merge conflicts --- crates/forge_loader/src/manifest.rs | 42 ++++++++++++++++++++++------- 1 file changed, 32 insertions(+), 10 deletions(-) diff --git a/crates/forge_loader/src/manifest.rs b/crates/forge_loader/src/manifest.rs index 52384ffe..e7965b72 100644 --- a/crates/forge_loader/src/manifest.rs +++ b/crates/forge_loader/src/manifest.rs @@ -34,12 +34,15 @@ struct ModInfo<'a> { #[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] struct MacroMod<'a> { - #[serde(flatten, borrow)] + // #[serde(flatten, borrow)] key: &'a str, function: &'a str, - resolver: ModInfo<'a>, - config: ModInfo<'a>, - export: ModInfo<'a>, + #[serde(borrow)] + resolver: Option>, + #[serde(borrow)] + config: Option>, + #[serde(borrow)] + export: Option>, } // WebTrigger => RawTrigger; WHY IS THIS NAMED DIFFERENTLY !? WHO CHANGED NAMES @@ -365,7 +368,7 @@ impl<'a> ForgeModules<'a> { // get user invokable modules that have additional exposure endpoints. // ie macros has config and export fields on top of resolver fields that are functions for macros in self.macros { - if let Some(resolver) = Some(macros.resolver) { + if let Some(resolver) = macros.resolver { functions_to_scan.push(Entrypoints { function: resolver.function, invokable: true, @@ -373,14 +376,14 @@ impl<'a> ForgeModules<'a> { }) } - if let Some(config) = Some(macros.config) { + if let Some(config) = macros.config { functions_to_scan.push(Entrypoints { function: config.function, invokable: true, web_trigger: false, }) } - if let Some(export) = Some(macros.export) { + if let Some(export) = macros.export { functions_to_scan.push(Entrypoints { function: export.function, invokable: true, @@ -518,7 +521,7 @@ mod tests { "macro": [ { "key": "my-macro", - "title": "My Macro" + "function": "My Macro" } ], "function": [ @@ -604,11 +607,15 @@ mod tests { { "key": "my-macro", "title": "My Macro", + "function": "Catch-me-if-you-can0", "resolver": { - "function": Catch-me-if-you-can1 + "function": "Catch-me-if-you-can1" }, "config": { - "function": Catch-me-if-you-can2 + "function": "Catch-me-if-you-can2" + }, + "export": { + "function": "Catch-me-if-you-can3" } } ], @@ -642,5 +649,20 @@ mod tests { } } }"#; + let manifest: ForgeManifest = serde_json::from_str(json).unwrap(); + assert_eq!(manifest.modules.macros.len(), 1); + assert_eq!(manifest.modules.macros[0].function, "Catch-me-if-you-can0"); + + if let Some(func) = &manifest.modules.macros[0].resolver { + assert_eq!(func.function, "Catch-me-if-you-can1"); + } + + if let Some(func) = &manifest.modules.macros[0].config { + assert_eq!(func.function, "Catch-me-if-you-can2"); + } + + if let Some(func) = &manifest.modules.macros[0].export { + assert_eq!(func.function, "Catch-me-if-you-can3"); + } } } From 690947590d9c608c1a03ceb366cccdc0290caee5 Mon Sep 17 00:00:00 2001 From: Alina Wang Date: Wed, 10 Jan 2024 18:10:51 -0800 Subject: [PATCH 291/517] resolving merge conflicts --- crates/forge_loader/src/manifest.rs | 42 +++------------- crates/fsrt/src/main.rs | 76 +++++++++++++++++++++++++---- 2 files changed, 73 insertions(+), 45 deletions(-) diff --git a/crates/forge_loader/src/manifest.rs b/crates/forge_loader/src/manifest.rs index e7965b72..bc19201d 100644 --- a/crates/forge_loader/src/manifest.rs +++ b/crates/forge_loader/src/manifest.rs @@ -238,9 +238,9 @@ pub enum FunctionTy { // Struct used for tracking what scan a funtion requires. #[derive(Debug, Clone, PartialEq, Eq, Deserialize)] pub struct Entrypoints<'a> { - function: &'a str, - invokable: bool, - web_trigger: bool, + pub function: &'a str, + pub invokable: bool, + pub web_trigger: bool, } // Helper functions that help filter out which functions are what. @@ -281,7 +281,7 @@ impl AsRef for FunctionTy { impl<'a> ForgeModules<'a> { // TODO: function returns iterator where each item is some specified type. - pub fn into_analyzable_functions(self) { + pub fn into_analyzable_functions(self) -> Vec> { // number of webtriggers are usually low, so it's better to just sort them and reuse // self.webtriggers // .sort_unstable_by_key(|trigger| trigger.function); @@ -394,7 +394,6 @@ impl<'a> ForgeModules<'a> { // get array for user invokable module functions // make alternate_functions all user-invokable functions - // let mut alternate_functions = Vec::new(); for module in self.extra.into_values().flatten() { if let Some(mod_function) = module.function { functions_to_scan.push(Entrypoints { @@ -412,37 +411,8 @@ impl<'a> ForgeModules<'a> { }); } } - functions_to_scan.into_iter(); - // alternate_functions.into_iter(); - // Iterate over Consumers and check that if consumers isn't in alternate functions, add consumer funtion to be ignored - // assuming that alternate functions already has all user invokable functions. - // self.consumers.iter().for_each(|consumer| { - // if !alternate_functions.contains(&consumer.resolver.function) { - // ignored_functions.insert(consumer.resolver.function); - // } - // }); - - // TODO: Iterate through all deserialized entrypoints that are represented as a struct when deserialized - // Update Struct values to be true or not. If any part true, then scan. - // This solution fixes the problem that we only check known user invokable modules and also acccounts for non-invokable module entry points - - // return non-user invokable functions - // self.functions.into_iter().filter_map(move |func| { - // if ignored_functions.contains(&func.key) { - // return None; - // } - // Some( - // if self - // .webtriggers - // .binary_search_by_key(&func.key, |trigger| trigger.function) - // .is_ok() - // { - // FunctionTy::WebTrigger(func) - // } else { - // FunctionTy::Invokable(func) - // }, - // ) - // }) + + return functions_to_scan; } } diff --git a/crates/fsrt/src/main.rs b/crates/fsrt/src/main.rs index b1281be5..d649b2f8 100644 --- a/crates/fsrt/src/main.rs +++ b/crates/fsrt/src/main.rs @@ -13,6 +13,13 @@ use std::{ sync::Arc, }; +<<<<<<< HEAD +======= +use clap::{Parser, ValueHint}; +use miette::{IntoDiagnostic, Result}; + +use serde_json::map::Entry; +>>>>>>> cd2ed7b (edited main to iterate over vector of functions. TODO: edit add_funcs in main.rs and test into_analyzble_function use case in main.rs) use swc_core::{ common::{Globals, Mark, SourceMap, GLOBALS}, ecma::{ @@ -38,7 +45,7 @@ use forge_analyzer::{ resolver::resolve_calls, }; -use forge_loader::manifest::{ForgeManifest, FunctionRef, FunctionTy}; +use forge_loader::manifest::{ForgeManifest, FunctionRef, FunctionTy, Entrypoints}; use walkdir::WalkDir; #[derive(Parser, Debug)] @@ -86,7 +93,12 @@ struct ForgeProject { sm: Arc, ctx: AppCtx, env: Environment, +<<<<<<< HEAD funcs: Vec>, +======= + funcs: Vec>, + opts: Opts, +>>>>>>> cd2ed7b (edited main to iterate over vector of functions. TODO: edit add_funcs in main.rs and test into_analyzble_function use case in main.rs) } impl ForgeProject { @@ -136,8 +148,8 @@ impl ForgeProject { funcs: vec![], } } - - fn add_funcs<'a, I: IntoIterator>>(&mut self, iter: I) { +// TODO: edit to work with new iterator that not FUNCTIONTY + fn add_funcs<'a, I: IntoIterator>>(&mut self, iter: I) { self.funcs.extend(iter.into_iter().flat_map(|ftype| { ftype.sequence(|(func_name, path)| { let modid = self.ctx.modid_from_path(&path)?; @@ -206,12 +218,14 @@ fn scan_directory(dir: PathBuf, function: Option<&str>, opts: &Args) -> Result(resolved_func.into_func_path()) - }) - }); + let funcrefs = &manifest.modules.into_analyzable_functions(); + + // .flat_map(|f| { + // f.sequence(|fmod| { + // let resolved_func = FunctionRef::try_from(fmod)?.try_resolve(&paths, &dir)?; + // Ok::<_, forge_loader::Error>(resolved_func.into_func_path()) + // }) + // }); let src_root = dir.join("src"); let mut proj = ForgeProject::with_files_and_sourceroot(src_root, paths.clone(), secret_packages); @@ -300,6 +314,7 @@ fn scan_directory(dir: PathBuf, function: Option<&str>, opts: &Args) -> Result { let mut runner = DefintionAnalysisRunner::new(); @@ -413,7 +428,50 @@ fn scan_directory(dir: PathBuf, function: Option<&str>, opts: &Args) -> Result { + // let mut checker = AuthZChecker::new(); + // debug!("checking {func} at {path:?}"); + // if let Err(err) = interp.run_checker(def, &mut checker, path.clone(), func.clone()) + // { + // warn!("error while scanning {func} in {path:?}: {err}"); + // } + // reporter.add_vulnerabilities(checker.into_vulns()); + // } + // FunctionTy::WebTrigger((ref func, ref path, _, def)) => { + // let mut checker = AuthenticateChecker::new(); + // debug!("checking webtrigger {func} at {path:?}"); + // if let Err(err) = + // authn_interp.run_checker(def, &mut checker, path.clone(), func.clone()) + // { + // warn!("error while scanning {func} in {path:?}: {err}"); + // } + // reporter.add_vulnerabilities(checker.into_vulns()); + // } + // } + + if func.invokable { + let mut checker = AuthZChecker::new(); + debug!("checking {func} at {path:?}"); + if let Err(err) = interp.run_checker(def, &mut checker, path.clone(), func.clone()) + { + warn!("error while scanning {func} in {path:?}: {err}"); + } + reporter.add_vulnerabilities(checker.into_vulns()); + + } else if func.web_trigger { + let mut checker = AuthenticateChecker::new(); + debug!("checking webtrigger {func} at {path:?}"); + if let Err(err) = + authn_interp.run_checker(def, &mut checker, path.clone(), func.clone()) + { + warn!("error while scanning {func} in {path:?}: {err}"); +>>>>>>> cd2ed7b (edited main to iterate over vector of functions. TODO: edit add_funcs in main.rs and test into_analyzble_function use case in main.rs) } + reporter.add_vulnerabilities(checker.into_vulns()); + + } } From 34266140d51f88c5c1838dbee7fcc6cc337f7099 Mon Sep 17 00:00:00 2001 From: Alina Wang Date: Wed, 10 Jan 2024 18:10:51 -0800 Subject: [PATCH 292/517] added new modules for additional endpoints in user invokable modules --- crates/forge_loader/src/manifest.rs | 50 +++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) diff --git a/crates/forge_loader/src/manifest.rs b/crates/forge_loader/src/manifest.rs index bc19201d..5b26f378 100644 --- a/crates/forge_loader/src/manifest.rs +++ b/crates/forge_loader/src/manifest.rs @@ -33,8 +33,12 @@ struct ModInfo<'a> { } #[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] +<<<<<<< HEAD struct MacroMod<'a> { // #[serde(flatten, borrow)] +======= +struct MacroMod<'a> { +>>>>>>> 31a3048 (added new modules for additional endpoints in user invokable modules) key: &'a str, function: &'a str, #[serde(borrow)] @@ -45,7 +49,47 @@ struct MacroMod<'a> { export: Option>, } +<<<<<<< HEAD // WebTrigger => RawTrigger; WHY IS THIS NAMED DIFFERENTLY !? WHO CHANGED NAMES +======= +#[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] +struct ContentByLineItem<'a> { + key: &'a str, + function: &'a str, + #[serde(borrow)] + resolver: Option>, + #[serde(borrow)] + dynamic_properties: Option>, +} + +#[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] +struct IssueGlance<'a> { + key: &'a str, + function: &'a str, + #[serde(borrow)] + resolver: Option>, + #[serde(borrow)] + dynamic_properties: Option>, + +} + +#[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] +struct AccessImportType<'a> { + key: &'a str, + function: &'a str, + #[serde(borrow)] + one_delete_import: Option>, + #[serde(borrow)] + start_import: Option>, + #[serde(borrow)] + stop_import: Option>, + #[serde(borrow)] + import_status: Option>, + +} + +// WebTrigger => RawTrigger; WHY IS THIS NAMED DIFFERENTLY !? WHO CHANGED NAMES +>>>>>>> 31a3048 (added new modules for additional endpoints in user invokable modules) #[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] struct RawTrigger<'a> { key: &'a str, @@ -128,6 +172,12 @@ pub struct ForgeModules<'a> { macros: Vec>, #[serde(rename = "function", default, borrow)] pub functions: Vec>, + #[serde(rename = "contentByLineItem", default, borrow)] + content_by_line_item: Vec>, + #[serde(rename = "jira:issueGlance", default, borrow)] + issue_glance: Vec>, + #[serde(rename = "jira:accessImportType", default, borrow)] + access_import_type: Vec>, // deserializing non user-invocable modules #[serde(rename = "webtrigger", default, borrow)] webtriggers: Vec>, From 4dbdfc72a2cc4c67b2e018c8d946f4f084dc2659 Mon Sep 17 00:00:00 2001 From: Alina Wang Date: Wed, 10 Jan 2024 18:10:51 -0800 Subject: [PATCH 293/517] added methods for adding additional user invokable endpoints to vector for scanning. --- crates/forge_loader/src/manifest.rs | 120 ++++++++++++++++++++++------ 1 file changed, 97 insertions(+), 23 deletions(-) diff --git a/crates/forge_loader/src/manifest.rs b/crates/forge_loader/src/manifest.rs index 5b26f378..270b16b1 100644 --- a/crates/forge_loader/src/manifest.rs +++ b/crates/forge_loader/src/manifest.rs @@ -33,12 +33,7 @@ struct ModInfo<'a> { } #[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] -<<<<<<< HEAD struct MacroMod<'a> { - // #[serde(flatten, borrow)] -======= -struct MacroMod<'a> { ->>>>>>> 31a3048 (added new modules for additional endpoints in user invokable modules) key: &'a str, function: &'a str, #[serde(borrow)] @@ -49,47 +44,41 @@ struct MacroMod<'a> { export: Option>, } -<<<<<<< HEAD -// WebTrigger => RawTrigger; WHY IS THIS NAMED DIFFERENTLY !? WHO CHANGED NAMES -======= #[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] -struct ContentByLineItem<'a> { +struct ContentByLineItem<'a> { key: &'a str, function: &'a str, - #[serde(borrow)] + #[serde(borrow)] resolver: Option>, - #[serde(borrow)] + #[serde(borrow)] dynamic_properties: Option>, } #[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] -struct IssueGlance<'a> { +struct IssueGlance<'a> { key: &'a str, function: &'a str, - #[serde(borrow)] + #[serde(borrow)] resolver: Option>, - #[serde(borrow)] + #[serde(borrow)] dynamic_properties: Option>, - } #[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] -struct AccessImportType<'a> { +struct AccessImportType<'a> { key: &'a str, function: &'a str, - #[serde(borrow)] + #[serde(borrow)] one_delete_import: Option>, - #[serde(borrow)] + #[serde(borrow)] start_import: Option>, - #[serde(borrow)] + #[serde(borrow)] stop_import: Option>, - #[serde(borrow)] + #[serde(borrow)] import_status: Option>, - } -// WebTrigger => RawTrigger; WHY IS THIS NAMED DIFFERENTLY !? WHO CHANGED NAMES ->>>>>>> 31a3048 (added new modules for additional endpoints in user invokable modules) +// WebTrigger => RawTrigger; WHY IS THIS NAMED DIFFERENTLY !? WHO CHANGED NAMES #[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] struct RawTrigger<'a> { key: &'a str, @@ -442,6 +431,91 @@ impl<'a> ForgeModules<'a> { } } + for contentitem in self.content_by_line_item { + functions_to_scan.push(Entrypoints { + function: contentitem.function, + invokable: true, + web_trigger: false, + }); + if let Some(resolver) = contentitem.resolver { + functions_to_scan.push(Entrypoints { + function: resolver.function, + invokable: true, + web_trigger: false, + }) + } + + if let Some(dynamic_properties) = contentitem.dynamic_properties { + functions_to_scan.push(Entrypoints { + function: dynamic_properties.function, + invokable: true, + web_trigger: false, + }) + } + } + + for issue in self.issue_glance { + functions_to_scan.push(Entrypoints { + function: issue.function, + invokable: true, + web_trigger: false, + }); + if let Some(resolver) = issue.resolver { + functions_to_scan.push(Entrypoints { + function: resolver.function, + invokable: true, + web_trigger: false, + }) + } + + if let Some(dynamic_properties) = issue.dynamic_properties { + functions_to_scan.push(Entrypoints { + function: dynamic_properties.function, + invokable: true, + web_trigger: false, + }) + } + } + + for access in self.access_import_type { + functions_to_scan.push(Entrypoints { + function: access.function, + invokable: true, + web_trigger: false, + }); + if let Some(delete) = access.one_delete_import { + functions_to_scan.push(Entrypoints { + function: delete.function, + invokable: true, + web_trigger: false, + }) + } + + if let Some(start) = access.start_import { + functions_to_scan.push(Entrypoints { + function: start.function, + invokable: true, + web_trigger: false, + }) + } + + if let Some(stop) = access.stop_import { + functions_to_scan.push(Entrypoints { + function: stop.function, + invokable: true, + web_trigger: false, + }) + } + + if let Some(status) = access.import_status { + functions_to_scan.push(Entrypoints { + function: status.function, + invokable: true, + web_trigger: false, + }) + } + } + // get array for user invokable module functions // make alternate_functions all user-invokable functions for module in self.extra.into_values().flatten() { From d4eb2646511f8c465a3f9a7a6b9ee206ecb665a2 Mon Sep 17 00:00:00 2001 From: Alina Wang Date: Wed, 10 Jan 2024 18:10:51 -0800 Subject: [PATCH 294/517] resolving merge conflicts --- crates/forge_loader/src/manifest.rs | 44 +++++++++++++++++++++-------- 1 file changed, 32 insertions(+), 12 deletions(-) diff --git a/crates/forge_loader/src/manifest.rs b/crates/forge_loader/src/manifest.rs index 270b16b1..b5783e61 100644 --- a/crates/forge_loader/src/manifest.rs +++ b/crates/forge_loader/src/manifest.rs @@ -115,13 +115,7 @@ struct ScheduledTrigger<'a> { pub struct DataProvider<'a> { key: &'a str, #[serde(flatten, borrow)] - callback: Callback<'a>, -} - -// Struct for mapping functions defined one more level in whose value is {function: string}. Used to represent resolver types. -#[derive(Debug, Clone, PartialEq, Eq, Deserialize)] -pub struct Callback<'a> { - pub function: &'a str, + callback: ModInfo<'a>, } // Struct for Custom field Module. Check that search suggestion gets read in correctly. @@ -129,22 +123,27 @@ pub struct Callback<'a> { pub struct CustomField<'a> { #[serde(flatten, borrow)] key: &'a str, + // all attributes below involve function calls + value: &'a str, search_suggestion: &'a str, + function: &'a str, + edit: &'a str, + resolver: ModInfo<'a>, } #[derive(Debug, Clone, PartialEq, Eq, Deserialize)] pub struct UiModificatons<'a> { - #[serde(flatten, borrow)] key: &'a str, - resolver: Callback<'a>, + #[serde(flatten, borrow)] + resolver: ModInfo<'a>, } #[derive(Debug, Clone, PartialEq, Eq, Deserialize)] pub struct WorkflowValidator<'a> { - #[serde(flatten, borrow)] key: &'a str, functon: &'a str, - resolver: Callback<'a>, + #[serde(flatten, borrow)] + resolver: ModInfo<'a>, } #[derive(Debug, Clone, PartialEq, Eq, Deserialize)] @@ -371,11 +370,32 @@ impl<'a> ForgeModules<'a> { }); self.custom_field.iter().for_each(|customfield| { + functions_to_scan.push(Entrypoints { + function: customfield.value, + invokable: true, + web_trigger: false, + }); functions_to_scan.push(Entrypoints { function: customfield.search_suggestion, invokable: true, web_trigger: false, - }) + }); + functions_to_scan.push(Entrypoints { + function: customfield.function, + invokable: true, + web_trigger: false, + }); + functions_to_scan.push(Entrypoints { + function: customfield.edit, + invokable: true, + web_trigger: false, + }); + + functions_to_scan.push(Entrypoints { + function: customfield.resolver.function, + invokable: true, + web_trigger: false, + }); }); self.ui_modifications.iter().for_each(|ui| { From 6279910670a6896a99db12306d5a7fcf4319d598 Mon Sep 17 00:00:00 2001 From: Alina Wang Date: Wed, 10 Jan 2024 18:10:51 -0800 Subject: [PATCH 295/517] changed entrypoints to entrypoint. Working on other comments --- crates/forge_loader/src/manifest.rs | 233 ++++++++++++++++------------ crates/fsrt/src/main.rs | 41 +++-- 2 files changed, 153 insertions(+), 121 deletions(-) diff --git a/crates/forge_loader/src/manifest.rs b/crates/forge_loader/src/manifest.rs index b5783e61..df0db8db 100644 --- a/crates/forge_loader/src/manifest.rs +++ b/crates/forge_loader/src/manifest.rs @@ -140,10 +140,10 @@ pub struct UiModificatons<'a> { #[derive(Debug, Clone, PartialEq, Eq, Deserialize)] pub struct WorkflowValidator<'a> { - key: &'a str, - functon: &'a str, #[serde(flatten, borrow)] - resolver: ModInfo<'a>, + key: &'a str, + function: &'a str, + resolver: ModInfo<'a> } #[derive(Debug, Clone, PartialEq, Eq, Deserialize)] @@ -275,7 +275,7 @@ pub enum FunctionTy { // Struct used for tracking what scan a funtion requires. #[derive(Debug, Clone, PartialEq, Eq, Deserialize)] -pub struct Entrypoints<'a> { +pub struct Entrypoint<'a> { pub function: &'a str, pub invokable: bool, pub web_trigger: bool, @@ -318,34 +318,41 @@ impl AsRef for FunctionTy { } impl<'a> ForgeModules<'a> { - // TODO: function returns iterator where each item is some specified type. - pub fn into_analyzable_functions(self) -> Vec> { + +// TODO: function returns iterator where each item is some specified type. + pub fn into_analyzable_functions ( + self, + ) -> Vec>{ // number of webtriggers are usually low, so it's better to just sort them and reuse - // self.webtriggers - // .sort_unstable_by_key(|trigger| trigger.function); + self.webtriggers.iter(). + .sort_unstable_by_key(|trigger| trigger.function); // Get all the Triggers and represent them as a new struct thing where "webtrigger" attribute is true // for all trigger things let mut functions_to_scan = Vec::new(); self.webtriggers.iter().for_each(|webtriggers| { - functions_to_scan.push(Entrypoints { - function: webtriggers.function, - invokable: false, - web_trigger: true, - }); + functions_to_scan.push( + Entrypoint { + function: webtriggers.function, + invokable: false, + web_trigger: true, + } + ); + }); self.event_triggers.iter().for_each(|event_triggers| { - functions_to_scan.push(Entrypoints { - function: event_triggers.raw.function, - invokable: false, - web_trigger: true, - }); + functions_to_scan.push( + Entrypoint { + function: event_triggers.raw.function, + invokable: false, + web_trigger: true, + } + ); }); - self.scheduled_triggers - .iter() - .for_each(|schedule_triggers| { - functions_to_scan.push(Entrypoints { + self.scheduled_triggers.iter().for_each(|schedule_triggers| { + functions_to_scan.push( + Entrypoint { function: schedule_triggers.raw.function, invokable: false, web_trigger: true, @@ -354,70 +361,96 @@ impl<'a> ForgeModules<'a> { // create arrays representing functions that expose user non-invokable functions self.consumers.iter().for_each(|consumers| { - functions_to_scan.push(Entrypoints { - function: consumers.resolver.function, - invokable: true, - web_trigger: false, - }) + functions_to_scan.push( + Entrypoint { + function: consumers.resolver.function, + invokable: true, + web_trigger: false, + } + ) }); self.data_provider.iter().for_each(|dataprovider| { - functions_to_scan.push(Entrypoints { - function: dataprovider.callback.function, - invokable: true, - web_trigger: false, - }) + functions_to_scan.push( + Entrypoint { + function: dataprovider.callback.function, + invokable: true, + web_trigger: false, + } + ) }); self.custom_field.iter().for_each(|customfield| { - functions_to_scan.push(Entrypoints { - function: customfield.value, - invokable: true, - web_trigger: false, - }); - functions_to_scan.push(Entrypoints { - function: customfield.search_suggestion, - invokable: true, - web_trigger: false, - }); - functions_to_scan.push(Entrypoints { - function: customfield.function, - invokable: true, - web_trigger: false, - }); - functions_to_scan.push(Entrypoints { - function: customfield.edit, - invokable: true, - web_trigger: false, - }); + functions_to_scan.push( + Entrypoint { + function: customfield.value, + invokable: true, + web_trigger: false, + } + ); + functions_to_scan.push( + Entrypoint { + function: customfield.search_suggestion, + invokable: true, + web_trigger: false, + } + ); + functions_to_scan.push( + Entrypoint { + function: customfield.function, + invokable: true, + web_trigger: false, + } + ); + functions_to_scan.push( + Entrypoint { + function: customfield.edit, + invokable: true, + web_trigger: false, + } + ); + + functions_to_scan.push( + Entrypoint { + function: customfield.resolver.function, + invokable: true, + web_trigger: false, + } + ); - functions_to_scan.push(Entrypoints { - function: customfield.resolver.function, - invokable: true, - web_trigger: false, - }); }); self.ui_modifications.iter().for_each(|ui| { - functions_to_scan.push(Entrypoints { - function: ui.resolver.function, - invokable: true, - web_trigger: false, - }) + functions_to_scan.push( + Entrypoint { + function: ui.resolver.function, + invokable: true, + web_trigger: false, + } + ) }); self.workflow_validator.iter().for_each(|validator| { - functions_to_scan.push(Entrypoints { - function: validator.resolver.function, - invokable: true, - web_trigger: false, - }) - }); + functions_to_scan.push( + Entrypoint { + function: validator.function, + invokable: true, + web_trigger: false, + } + ); - self.workflow_post_function - .iter() - .for_each(|post_function| { - functions_to_scan.push(Entrypoints { + functions_to_scan.push( + Entrypoint { + function: validator.resolver.function, + invokable: true, + web_trigger: false, + } + ); + }); + + self.workflow_post_function.iter().for_each(|post_function| { + functions_to_scan.push( + Entrypoint { function: post_function.function, invokable: true, web_trigger: false, @@ -427,23 +460,23 @@ impl<'a> ForgeModules<'a> { // get user invokable modules that have additional exposure endpoints. // ie macros has config and export fields on top of resolver fields that are functions for macros in self.macros { - if let Some(resolver) = macros.resolver { - functions_to_scan.push(Entrypoints { + if let Some(resolver)= macros.resolver { + functions_to_scan.push(Entrypoint { function: resolver.function, invokable: true, web_trigger: false, }) } - if let Some(config) = macros.config { - functions_to_scan.push(Entrypoints { + if let Some(config)= macros.config { + functions_to_scan.push(Entrypoint { function: config.function, invokable: true, web_trigger: false, }) } - if let Some(export) = macros.export { - functions_to_scan.push(Entrypoints { + if let Some(export)= macros.export { + functions_to_scan.push(Entrypoint { function: export.function, invokable: true, web_trigger: false, @@ -452,21 +485,21 @@ impl<'a> ForgeModules<'a> { } for contentitem in self.content_by_line_item { - functions_to_scan.push(Entrypoints { + functions_to_scan.push(Entrypoint { function: contentitem.function, invokable: true, web_trigger: false, }); - if let Some(resolver) = contentitem.resolver { - functions_to_scan.push(Entrypoints { + if let Some(resolver)= contentitem.resolver { + functions_to_scan.push(Entrypoint { function: resolver.function, invokable: true, web_trigger: false, }) } - if let Some(dynamic_properties) = contentitem.dynamic_properties { - functions_to_scan.push(Entrypoints { + if let Some(dynamic_properties)= contentitem.dynamic_properties { + functions_to_scan.push(Entrypoint { function: dynamic_properties.function, invokable: true, web_trigger: false, @@ -475,21 +508,21 @@ impl<'a> ForgeModules<'a> { } for issue in self.issue_glance { - functions_to_scan.push(Entrypoints { + functions_to_scan.push(Entrypoint { function: issue.function, invokable: true, web_trigger: false, }); - if let Some(resolver) = issue.resolver { - functions_to_scan.push(Entrypoints { + if let Some(resolver)= issue.resolver { + functions_to_scan.push(Entrypoint { function: resolver.function, invokable: true, web_trigger: false, }) } - if let Some(dynamic_properties) = issue.dynamic_properties { - functions_to_scan.push(Entrypoints { + if let Some(dynamic_properties)= issue.dynamic_properties { + functions_to_scan.push(Entrypoint { function: dynamic_properties.function, invokable: true, web_trigger: false, @@ -498,37 +531,37 @@ impl<'a> ForgeModules<'a> { } for access in self.access_import_type { - functions_to_scan.push(Entrypoints { + functions_to_scan.push(Entrypoint { function: access.function, invokable: true, web_trigger: false, }); if let Some(delete) = access.one_delete_import { - functions_to_scan.push(Entrypoints { + functions_to_scan.push(Entrypoint { function: delete.function, invokable: true, web_trigger: false, }) } - if let Some(start) = access.start_import { - functions_to_scan.push(Entrypoints { + if let Some(start)= access.start_import { + functions_to_scan.push(Entrypoint { function: start.function, invokable: true, web_trigger: false, }) } - if let Some(stop) = access.stop_import { - functions_to_scan.push(Entrypoints { + if let Some(stop)= access.stop_import { + functions_to_scan.push(Entrypoint { function: stop.function, invokable: true, web_trigger: false, }) } - if let Some(status) = access.import_status { - functions_to_scan.push(Entrypoints { + if let Some(status)= access.import_status { + functions_to_scan.push(Entrypoint { function: status.function, invokable: true, web_trigger: false, @@ -540,7 +573,7 @@ impl<'a> ForgeModules<'a> { // make alternate_functions all user-invokable functions for module in self.extra.into_values().flatten() { if let Some(mod_function) = module.function { - functions_to_scan.push(Entrypoints { + functions_to_scan.push(Entrypoint { function: mod_function, invokable: true, web_trigger: false, @@ -548,15 +581,15 @@ impl<'a> ForgeModules<'a> { } if let Some(resolver) = module.resolver { - functions_to_scan.push(Entrypoints { + functions_to_scan.push(Entrypoint { function: resolver.function, invokable: true, web_trigger: false, }); } } - - return functions_to_scan; + + functions_to_scan } } diff --git a/crates/fsrt/src/main.rs b/crates/fsrt/src/main.rs index d649b2f8..4b345ef9 100644 --- a/crates/fsrt/src/main.rs +++ b/crates/fsrt/src/main.rs @@ -45,7 +45,7 @@ use forge_analyzer::{ resolver::resolve_calls, }; -use forge_loader::manifest::{ForgeManifest, FunctionRef, FunctionTy, Entrypoints}; +use forge_loader::manifest::{ForgeManifest, FunctionRef, FunctionTy, Entrypoint}; use walkdir::WalkDir; #[derive(Parser, Debug)] @@ -93,10 +93,7 @@ struct ForgeProject { sm: Arc, ctx: AppCtx, env: Environment, -<<<<<<< HEAD - funcs: Vec>, -======= - funcs: Vec>, + funcs: Vec>, opts: Opts, >>>>>>> cd2ed7b (edited main to iterate over vector of functions. TODO: edit add_funcs in main.rs and test into_analyzble_function use case in main.rs) } @@ -149,17 +146,20 @@ impl ForgeProject { } } // TODO: edit to work with new iterator that not FUNCTIONTY - fn add_funcs<'a, I: IntoIterator>>(&mut self, iter: I) { - self.funcs.extend(iter.into_iter().flat_map(|ftype| { - ftype.sequence(|(func_name, path)| { - let modid = self.ctx.modid_from_path(&path)?; - let func = self.env.module_export(modid, func_name)?; - Some((func_name.to_owned(), path, modid, func)) - }) - })); + fn add_funcs<'a, I: IntoIterator>>(&mut self, iter: I) { + self.funcs.extend(iter); } } + +// self.funcs.extend(iter.into_iter().flat_map(|ftype| { +// ftype.sequence(|(func_name, path)| { +// let modid = self.ctx.modid_from_path(&path)?; +// let func = self.env.module_export(modid, func_name)?; +// Some((func_name.to_owned(), path, modid, func)) +// }) +// })); + fn is_js_file>(path: P) -> bool { matches!( path.as_ref().extension().map(|s| s.as_bytes()), @@ -218,7 +218,7 @@ fn scan_directory(dir: PathBuf, function: Option<&str>, opts: &Args) -> Result, opts: &Args) -> Result>>>>>> cd2ed7b (edited main to iterate over vector of functions. TODO: edit add_funcs in main.rs and test into_analyzble_function use case in main.rs) + warn!("error while scanning {:?} in {path:?}: {err}", func.function); } reporter.add_vulnerabilities(checker.into_vulns()); From 556f10f161adc721866c70e40fe3b77a2b525022 Mon Sep 17 00:00:00 2001 From: Alina Wang Date: Wed, 10 Jan 2024 18:10:51 -0800 Subject: [PATCH 296/517] resolving merge conflicts --- crates/forge_loader/src/manifest.rs | 214 +++++++++++++--------------- 1 file changed, 100 insertions(+), 114 deletions(-) diff --git a/crates/forge_loader/src/manifest.rs b/crates/forge_loader/src/manifest.rs index df0db8db..fca0670f 100644 --- a/crates/forge_loader/src/manifest.rs +++ b/crates/forge_loader/src/manifest.rs @@ -35,7 +35,7 @@ struct ModInfo<'a> { #[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] struct MacroMod<'a> { key: &'a str, - function: &'a str, + function: Option<&'a str>, #[serde(borrow)] resolver: Option>, #[serde(borrow)] @@ -124,11 +124,11 @@ pub struct CustomField<'a> { #[serde(flatten, borrow)] key: &'a str, // all attributes below involve function calls - value: &'a str, - search_suggestion: &'a str, - function: &'a str, - edit: &'a str, - resolver: ModInfo<'a>, + value: Option<&'a str>, + search_suggestions: &'a str, + function: Option<&'a str>, + edit: Option<&'a str>, + resolver: Option>, } #[derive(Debug, Clone, PartialEq, Eq, Deserialize)] @@ -143,12 +143,11 @@ pub struct WorkflowValidator<'a> { #[serde(flatten, borrow)] key: &'a str, function: &'a str, - resolver: ModInfo<'a> + resolver: ModInfo<'a>, } #[derive(Debug, Clone, PartialEq, Eq, Deserialize)] pub struct WorkflowPostFunction<'a> { - #[serde(flatten, borrow)] key: &'a str, function: &'a str, } @@ -274,7 +273,7 @@ pub enum FunctionTy { } // Struct used for tracking what scan a funtion requires. -#[derive(Debug, Clone, PartialEq, Eq, Deserialize)] +#[derive(Debug, Clone, PartialEq, Eq)] pub struct Entrypoint<'a> { pub function: &'a str, pub invokable: bool, @@ -318,41 +317,34 @@ impl AsRef for FunctionTy { } impl<'a> ForgeModules<'a> { - -// TODO: function returns iterator where each item is some specified type. - pub fn into_analyzable_functions ( - self, - ) -> Vec>{ + // TODO: function returns iterator where each item is some specified type. + pub fn into_analyzable_functions(self) -> Vec> { // number of webtriggers are usually low, so it's better to just sort them and reuse - self.webtriggers.iter(). - .sort_unstable_by_key(|trigger| trigger.function); + // self.webtriggers.iter(). + // .sort_unstable_by_key(|trigger| trigger.function); // Get all the Triggers and represent them as a new struct thing where "webtrigger" attribute is true // for all trigger things let mut functions_to_scan = Vec::new(); self.webtriggers.iter().for_each(|webtriggers| { - functions_to_scan.push( - Entrypoint { - function: webtriggers.function, - invokable: false, - web_trigger: true, - } - ); - + functions_to_scan.push(Entrypoint { + function: webtriggers.function, + invokable: false, + web_trigger: true, + }); }); self.event_triggers.iter().for_each(|event_triggers| { - functions_to_scan.push( - Entrypoint { - function: event_triggers.raw.function, - invokable: false, - web_trigger: true, - } - ); + functions_to_scan.push(Entrypoint { + function: event_triggers.raw.function, + invokable: false, + web_trigger: true, + }); }); - self.scheduled_triggers.iter().for_each(|schedule_triggers| { - functions_to_scan.push( - Entrypoint { + self.scheduled_triggers + .iter() + .for_each(|schedule_triggers| { + functions_to_scan.push(Entrypoint { function: schedule_triggers.raw.function, invokable: false, web_trigger: true, @@ -361,96 +353,87 @@ impl<'a> ForgeModules<'a> { // create arrays representing functions that expose user non-invokable functions self.consumers.iter().for_each(|consumers| { - functions_to_scan.push( - Entrypoint { - function: consumers.resolver.function, - invokable: true, - web_trigger: false, - } - ) + functions_to_scan.push(Entrypoint { + function: consumers.resolver.function, + invokable: true, + web_trigger: false, + }) }); self.data_provider.iter().for_each(|dataprovider| { - functions_to_scan.push( - Entrypoint { - function: dataprovider.callback.function, - invokable: true, - web_trigger: false, - } - ) + functions_to_scan.push(Entrypoint { + function: dataprovider.callback.function, + invokable: true, + web_trigger: false, + }) }); self.custom_field.iter().for_each(|customfield| { - functions_to_scan.push( - Entrypoint { - function: customfield.value, - invokable: true, - web_trigger: false, - } - ); - functions_to_scan.push( - Entrypoint { - function: customfield.search_suggestion, + if let Some(value) = customfield.value { + functions_to_scan.push(Entrypoint { + function: value, invokable: true, web_trigger: false, - } - ); - functions_to_scan.push( - Entrypoint { - function: customfield.function, + }); + } + + functions_to_scan.push(Entrypoint { + function: customfield.search_suggestions, + invokable: true, + web_trigger: false, + }); + + if let Some(func) = customfield.function { + functions_to_scan.push(Entrypoint { + function: func, invokable: true, web_trigger: false, - } - ); - functions_to_scan.push( - Entrypoint { - function: customfield.edit, + }); + } + + if let Some(edit) = customfield.edit { + functions_to_scan.push(Entrypoint { + function: edit, invokable: true, web_trigger: false, - } - ); + }); + } - functions_to_scan.push( - Entrypoint { - function: customfield.resolver.function, + if let Some(resolver) = &customfield.resolver { + functions_to_scan.push(Entrypoint { + function: resolver.function, invokable: true, web_trigger: false, - } - ); - + }); + } }); self.ui_modifications.iter().for_each(|ui| { - functions_to_scan.push( - Entrypoint { - function: ui.resolver.function, - invokable: true, - web_trigger: false, - } - ) + functions_to_scan.push(Entrypoint { + function: ui.resolver.function, + invokable: true, + web_trigger: false, + }) }); self.workflow_validator.iter().for_each(|validator| { - functions_to_scan.push( - Entrypoint { - function: validator.function, - invokable: true, - web_trigger: false, - } - ); + functions_to_scan.push(Entrypoint { + function: validator.function, + invokable: true, + web_trigger: false, + }); - functions_to_scan.push( - Entrypoint { - function: validator.resolver.function, - invokable: true, - web_trigger: false, - } - ); + functions_to_scan.push(Entrypoint { + function: validator.resolver.function, + invokable: true, + web_trigger: false, + }); }); - - self.workflow_post_function.iter().for_each(|post_function| { - functions_to_scan.push( - Entrypoint { + + self.workflow_post_function + .iter() + .for_each(|post_function| { + functions_to_scan.push(Entrypoint { function: post_function.function, invokable: true, web_trigger: false, @@ -460,7 +443,7 @@ impl<'a> ForgeModules<'a> { // get user invokable modules that have additional exposure endpoints. // ie macros has config and export fields on top of resolver fields that are functions for macros in self.macros { - if let Some(resolver)= macros.resolver { + if let Some(resolver) = macros.resolver { functions_to_scan.push(Entrypoint { function: resolver.function, invokable: true, @@ -468,14 +451,14 @@ impl<'a> ForgeModules<'a> { }) } - if let Some(config)= macros.config { + if let Some(config) = macros.config { functions_to_scan.push(Entrypoint { function: config.function, invokable: true, web_trigger: false, }) } - if let Some(export)= macros.export { + if let Some(export) = macros.export { functions_to_scan.push(Entrypoint { function: export.function, invokable: true, @@ -490,7 +473,7 @@ impl<'a> ForgeModules<'a> { invokable: true, web_trigger: false, }); - if let Some(resolver)= contentitem.resolver { + if let Some(resolver) = contentitem.resolver { functions_to_scan.push(Entrypoint { function: resolver.function, invokable: true, @@ -498,7 +481,7 @@ impl<'a> ForgeModules<'a> { }) } - if let Some(dynamic_properties)= contentitem.dynamic_properties { + if let Some(dynamic_properties) = contentitem.dynamic_properties { functions_to_scan.push(Entrypoint { function: dynamic_properties.function, invokable: true, @@ -513,7 +496,7 @@ impl<'a> ForgeModules<'a> { invokable: true, web_trigger: false, }); - if let Some(resolver)= issue.resolver { + if let Some(resolver) = issue.resolver { functions_to_scan.push(Entrypoint { function: resolver.function, invokable: true, @@ -521,7 +504,7 @@ impl<'a> ForgeModules<'a> { }) } - if let Some(dynamic_properties)= issue.dynamic_properties { + if let Some(dynamic_properties) = issue.dynamic_properties { functions_to_scan.push(Entrypoint { function: dynamic_properties.function, invokable: true, @@ -544,7 +527,7 @@ impl<'a> ForgeModules<'a> { }) } - if let Some(start)= access.start_import { + if let Some(start) = access.start_import { functions_to_scan.push(Entrypoint { function: start.function, invokable: true, @@ -552,7 +535,7 @@ impl<'a> ForgeModules<'a> { }) } - if let Some(stop)= access.stop_import { + if let Some(stop) = access.stop_import { functions_to_scan.push(Entrypoint { function: stop.function, invokable: true, @@ -560,7 +543,7 @@ impl<'a> ForgeModules<'a> { }) } - if let Some(status)= access.import_status { + if let Some(status) = access.import_status { functions_to_scan.push(Entrypoint { function: status.function, invokable: true, @@ -588,7 +571,7 @@ impl<'a> ForgeModules<'a> { }); } } - + functions_to_scan } } @@ -706,7 +689,7 @@ mod tests { assert_eq!(manifest.app.id, "my-app"); assert_eq!(manifest.modules.macros.len(), 1); assert_eq!(manifest.modules.macros[0].key, "My Macro"); - assert_eq!(manifest.modules.macros[0].function, "my-macro"); + // assert_eq!(manifest.modules.macros[0].function, "my-macro"); assert_eq!(manifest.modules.functions.len(), 1); assert_eq!( manifest.modules.functions[0], @@ -798,7 +781,10 @@ mod tests { }"#; let manifest: ForgeManifest = serde_json::from_str(json).unwrap(); assert_eq!(manifest.modules.macros.len(), 1); - assert_eq!(manifest.modules.macros[0].function, "Catch-me-if-you-can0"); + + if let Some(func) = manifest.modules.macros[0].function { + assert_eq!(func, "Catch-me-if-you-can0"); + } if let Some(func) = &manifest.modules.macros[0].resolver { assert_eq!(func.function, "Catch-me-if-you-can1"); From 4eceabfb7797d9e8233276d9c8b4638650cdc916 Mon Sep 17 00:00:00 2001 From: Alina Wang Date: Wed, 10 Jan 2024 18:10:51 -0800 Subject: [PATCH 297/517] resolving merge conflicts --- crates/forge_loader/src/manifest.rs | 129 ++++++++++++++-------------- crates/fsrt/src/main.rs | 14 ++- 2 files changed, 76 insertions(+), 67 deletions(-) diff --git a/crates/forge_loader/src/manifest.rs b/crates/forge_loader/src/manifest.rs index fca0670f..a7b5bbdb 100644 --- a/crates/forge_loader/src/manifest.rs +++ b/crates/forge_loader/src/manifest.rs @@ -1,6 +1,6 @@ use std::{ borrow::Borrow, - collections::{BTreeSet, HashSet}, + collections::HashSet, hash::Hash, path::{Path, PathBuf}, }; @@ -9,7 +9,7 @@ use crate::{forgepermissions::ForgePermissions, Error}; use forge_utils::FxHashMap; use itertools::{Either, Itertools}; use serde::Deserialize; -use serde_json::map::Entry; +use std::collections::BTreeMap; use tracing::trace; #[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] @@ -26,56 +26,44 @@ pub struct FunctionMod<'a> { providers: Option>, } -// Modified +// Abstracting away key, function, and resolver into a single struct for reuse whoo! #[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] -struct ModInfo<'a> { +struct CommonKey<'a> { + key: &'a str, function: &'a str, + resolver: Option<&'a str>, } #[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] struct MacroMod<'a> { - key: &'a str, - function: Option<&'a str>, - #[serde(borrow)] - resolver: Option>, - #[serde(borrow)] - config: Option>, - #[serde(borrow)] - export: Option>, + #[serde(flatten, borrow)] + common_keys: CommonKey<'a>, + config: Option<&'a str>, + export: Option<&'a str>, } #[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] struct ContentByLineItem<'a> { - key: &'a str, - function: &'a str, - #[serde(borrow)] - resolver: Option>, + #[serde(flatten, borrow)] + common_keys: CommonKey<'a>, #[serde(borrow)] - dynamic_properties: Option>, + dynamic_properties: Option<&'a str>, } #[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] struct IssueGlance<'a> { - key: &'a str, - function: &'a str, - #[serde(borrow)] - resolver: Option>, - #[serde(borrow)] - dynamic_properties: Option>, + #[serde(flatten, borrow)] + common_keys: CommonKey<'a>, + dynamic_properties: Option<&'a str>, } - #[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] struct AccessImportType<'a> { - key: &'a str, - function: &'a str, - #[serde(borrow)] - one_delete_import: Option>, - #[serde(borrow)] - start_import: Option>, - #[serde(borrow)] - stop_import: Option>, - #[serde(borrow)] - import_status: Option>, + #[serde(flatten, borrow)] + common_keys: CommonKey<'a>, + one_delete_import: Option<&'a str>, + start_import: Option<&'a str>, + stop_import: Option<&'a str>, + import_status: Option<&'a str>, } // WebTrigger => RawTrigger; WHY IS THIS NAMED DIFFERENTLY !? WHO CHANGED NAMES @@ -113,43 +101,38 @@ struct ScheduledTrigger<'a> { // compass DataProvider module #[derive(Debug, Clone, PartialEq, Eq, Deserialize)] pub struct DataProvider<'a> { - key: &'a str, #[serde(flatten, borrow)] - callback: ModInfo<'a>, + key: &'a str, + callback: Option<&'a str>, } // Struct for Custom field Module. Check that search suggestion gets read in correctly. #[derive(Debug, Clone, PartialEq, Eq, Deserialize)] pub struct CustomField<'a> { - #[serde(flatten, borrow)] - key: &'a str, // all attributes below involve function calls + #[serde(flatten, borrow)] + common_keys: CommonKey<'a>, value: Option<&'a str>, search_suggestions: &'a str, - function: Option<&'a str>, edit: Option<&'a str>, - resolver: Option>, } #[derive(Debug, Clone, PartialEq, Eq, Deserialize)] pub struct UiModificatons<'a> { - key: &'a str, #[serde(flatten, borrow)] - resolver: ModInfo<'a>, + common_key: CommonKey<'a>, } #[derive(Debug, Clone, PartialEq, Eq, Deserialize)] pub struct WorkflowValidator<'a> { #[serde(flatten, borrow)] - key: &'a str, - function: &'a str, - resolver: ModInfo<'a>, + common_keys: CommonKey<'a>, } #[derive(Debug, Clone, PartialEq, Eq, Deserialize)] pub struct WorkflowPostFunction<'a> { - key: &'a str, - function: &'a str, + #[serde(flatten, borrow)] + common_keys: CommonKey<'a>, } // Add more structs here for deserializing forge modules @@ -318,37 +301,51 @@ impl AsRef for FunctionTy { impl<'a> ForgeModules<'a> { // TODO: function returns iterator where each item is some specified type. - pub fn into_analyzable_functions(self) -> Vec> { + pub fn into_analyzable_functions(&mut self) -> Vec> { // number of webtriggers are usually low, so it's better to just sort them and reuse - // self.webtriggers.iter(). - // .sort_unstable_by_key(|trigger| trigger.function); + self.webtriggers + .sort_unstable_by_key(|trigger| trigger.function); // Get all the Triggers and represent them as a new struct thing where "webtrigger" attribute is true // for all trigger things - let mut functions_to_scan = Vec::new(); + let mut functions_to_scan = BTreeMap::new(); + + // Get all functions for module from manifest.yml + self.functions.iter().for_each(|func| { + functions_to_scan.insert( + func.handler, + Entrypoint { + function: func.handler, + invokable: false, + web_trigger: false, + }, + ); + }); + self.webtriggers.iter().for_each(|webtriggers| { - functions_to_scan.push(Entrypoint { - function: webtriggers.function, - invokable: false, - web_trigger: true, - }); + if functions_to_scan.contains_key(webtriggers.function) { + if let Some(entry) = functions_to_scan.get_mut(webtriggers.function) { + entry.web_trigger = true; + } + } }); + self.event_triggers.iter().for_each(|event_triggers| { - functions_to_scan.push(Entrypoint { - function: event_triggers.raw.function, - invokable: false, - web_trigger: true, - }); + if functions_to_scan.contains_key(event_triggers.raw.function) { + if let Some(entry) = functions_to_scan.get_mut(event_triggers.raw.function) { + entry.web_trigger = true; + } + } }); self.scheduled_triggers .iter() .for_each(|schedule_triggers| { - functions_to_scan.push(Entrypoint { - function: schedule_triggers.raw.function, - invokable: false, - web_trigger: true, - }) + if functions_to_scan.contains_key(schedule_triggers.raw.function) { + if let Some(entry) = functions_to_scan.get_mut(schedule_triggers.raw.function) { + entry.web_trigger = true; + } + } }); // create arrays representing functions that expose user non-invokable functions diff --git a/crates/fsrt/src/main.rs b/crates/fsrt/src/main.rs index 4b345ef9..ad2ebf7b 100644 --- a/crates/fsrt/src/main.rs +++ b/crates/fsrt/src/main.rs @@ -88,7 +88,19 @@ struct Args { dirs: Vec, } +<<<<<<< HEAD struct ForgeProject { +======= +#[derive(Debug, Clone, Default)] +struct Opts { + dump_cfg: bool, + dump_callgraph: bool, + appkey: Option, + out: Option, +} + +struct ForgeProject<'a> { +>>>>>>> 7e86f46 (abstracted structs to use a CommonKeys struct that holds: key, function, and resolver for less code duplication. Updated into_analayzable method to update values based on functions specified in function mod. TODO: Update rest of non trigger modules to update mapping to functions to scan from function mod) #[allow(dead_code)] sm: Arc, ctx: AppCtx, @@ -98,7 +110,7 @@ struct ForgeProject { >>>>>>> cd2ed7b (edited main to iterate over vector of functions. TODO: edit add_funcs in main.rs and test into_analyzble_function use case in main.rs) } -impl ForgeProject { +impl ForgeProject<'_> { #[instrument(skip(src, iter))] fn with_files_and_sourceroot, I: IntoIterator>( src: P, From 5d7be6e7d7baf39d265365b2573ec21e47bf32be Mon Sep 17 00:00:00 2001 From: Alina Wang Date: Wed, 10 Jan 2024 18:10:51 -0800 Subject: [PATCH 298/517] finished updating methods to check functions_to_scan and update entrypoint struct when needed. TODO: test and toggle function call in main.rs --- crates/forge_loader/src/manifest.rs | 304 +++++++++++++--------------- crates/fsrt/src/main.rs | 4 +- 2 files changed, 140 insertions(+), 168 deletions(-) diff --git a/crates/forge_loader/src/manifest.rs b/crates/forge_loader/src/manifest.rs index a7b5bbdb..a971e388 100644 --- a/crates/forge_loader/src/manifest.rs +++ b/crates/forge_loader/src/manifest.rs @@ -120,7 +120,7 @@ pub struct CustomField<'a> { #[derive(Debug, Clone, PartialEq, Eq, Deserialize)] pub struct UiModificatons<'a> { #[serde(flatten, borrow)] - common_key: CommonKey<'a>, + common_keys: CommonKey<'a>, } #[derive(Debug, Clone, PartialEq, Eq, Deserialize)] @@ -301,7 +301,7 @@ impl AsRef for FunctionTy { impl<'a> ForgeModules<'a> { // TODO: function returns iterator where each item is some specified type. - pub fn into_analyzable_functions(&mut self) -> Vec> { + pub fn into_analyzable_functions(&mut self) -> BTreeMap<&'a str, Entrypoint<'a>> { // number of webtriggers are usually low, so it's better to just sort them and reuse self.webtriggers .sort_unstable_by_key(|trigger| trigger.function); @@ -314,9 +314,9 @@ impl<'a> ForgeModules<'a> { // Get all functions for module from manifest.yml self.functions.iter().for_each(|func| { functions_to_scan.insert( - func.handler, + func.key, Entrypoint { - function: func.handler, + function: func.key, invokable: false, web_trigger: false, }, @@ -349,223 +349,195 @@ impl<'a> ForgeModules<'a> { }); // create arrays representing functions that expose user non-invokable functions - self.consumers.iter().for_each(|consumers| { - functions_to_scan.push(Entrypoint { - function: consumers.resolver.function, - invokable: true, - web_trigger: false, - }) + self.consumers.iter().for_each(|consumer| { + if functions_to_scan.contains_key(consumer.resolver.function) { + if let Some(entry) = functions_to_scan.get_mut(consumer.resolver.function) { + entry.invokable = true; + } + } }); self.data_provider.iter().for_each(|dataprovider| { - functions_to_scan.push(Entrypoint { - function: dataprovider.callback.function, - invokable: true, - web_trigger: false, - }) + if let Some(call) = dataprovider.callback { + if let Some(entry) = functions_to_scan.get_mut(call) { + entry.invokable = true; + } + } }); self.custom_field.iter().for_each(|customfield| { if let Some(value) = customfield.value { - functions_to_scan.push(Entrypoint { - function: value, - invokable: true, - web_trigger: false, - }); + if let Some(entry) = functions_to_scan.get_mut(value) { + entry.invokable = true; + } } - functions_to_scan.push(Entrypoint { - function: customfield.search_suggestions, - invokable: true, - web_trigger: false, - }); + if let Some(entry) = functions_to_scan.get_mut(customfield.search_suggestions) { + entry.invokable = true; + } - if let Some(func) = customfield.function { - functions_to_scan.push(Entrypoint { - function: func, - invokable: true, - web_trigger: false, - }); + if let Some(entry) = functions_to_scan.get_mut(customfield.common_keys.function) { + entry.invokable = true; } - if let Some(edit) = customfield.edit { - functions_to_scan.push(Entrypoint { - function: edit, - invokable: true, - web_trigger: false, - }); + if let Some(resolver) = customfield.common_keys.resolver { + if let Some(entry) = functions_to_scan.get_mut(resolver) { + entry.invokable = true; + } } - if let Some(resolver) = &customfield.resolver { - functions_to_scan.push(Entrypoint { - function: resolver.function, - invokable: true, - web_trigger: false, - }); + if let Some(edit) = customfield.edit { + if let Some(entry) = functions_to_scan.get_mut(edit) { + entry.invokable = true; + } } }); self.ui_modifications.iter().for_each(|ui| { - functions_to_scan.push(Entrypoint { - function: ui.resolver.function, - invokable: true, - web_trigger: false, - }) + if let Some(entry) = functions_to_scan.get_mut(ui.common_keys.function) { + entry.invokable = true; + } + + if let Some(resolver) = ui.common_keys.resolver { + if let Some(entry) = functions_to_scan.get_mut(resolver) { + entry.invokable = true; + } + } }); self.workflow_validator.iter().for_each(|validator| { - functions_to_scan.push(Entrypoint { - function: validator.function, - invokable: true, - web_trigger: false, - }); + if let Some(entry) = functions_to_scan.get_mut(validator.common_keys.function) { + entry.invokable = true; + } - functions_to_scan.push(Entrypoint { - function: validator.resolver.function, - invokable: true, - web_trigger: false, - }); + if let Some(resolver) = validator.common_keys.resolver { + if let Some(entry) = functions_to_scan.get_mut(resolver) { + entry.invokable = true; + } + } }); self.workflow_post_function .iter() .for_each(|post_function| { - functions_to_scan.push(Entrypoint { - function: post_function.function, - invokable: true, - web_trigger: false, - }) + if let Some(entry) = functions_to_scan.get_mut(post_function.common_keys.function) { + entry.invokable = true; + } + + if let Some(resolver) = post_function.common_keys.resolver { + if let Some(entry) = functions_to_scan.get_mut(resolver) { + entry.invokable = true; + } + } }); // get user invokable modules that have additional exposure endpoints. // ie macros has config and export fields on top of resolver fields that are functions - for macros in self.macros { - if let Some(resolver) = macros.resolver { - functions_to_scan.push(Entrypoint { - function: resolver.function, - invokable: true, - web_trigger: false, - }) + self.macros.iter().for_each(|macros| { + if let Some(resolver) = macros.common_keys.resolver { + if let Some(entry) = functions_to_scan.get_mut(resolver) { + entry.invokable = true; + } } if let Some(config) = macros.config { - functions_to_scan.push(Entrypoint { - function: config.function, - invokable: true, - web_trigger: false, - }) + if let Some(entry) = functions_to_scan.get_mut(config) { + entry.invokable = true; + } } + if let Some(export) = macros.export { - functions_to_scan.push(Entrypoint { - function: export.function, - invokable: true, - web_trigger: false, - }) + if let Some(entry) = functions_to_scan.get_mut(export) { + entry.invokable = true; + } } - } + }); - for contentitem in self.content_by_line_item { - functions_to_scan.push(Entrypoint { - function: contentitem.function, - invokable: true, - web_trigger: false, - }); - if let Some(resolver) = contentitem.resolver { - functions_to_scan.push(Entrypoint { - function: resolver.function, - invokable: true, - web_trigger: false, - }) + self.content_by_line_item.iter().for_each(|contentitem| { + if let Some(entry) = functions_to_scan.get_mut(contentitem.common_keys.function) { + entry.invokable = true; + } + + if let Some(resolver) = contentitem.common_keys.resolver { + if let Some(entry) = functions_to_scan.get_mut(resolver) { + entry.invokable = true; + } } if let Some(dynamic_properties) = contentitem.dynamic_properties { - functions_to_scan.push(Entrypoint { - function: dynamic_properties.function, - invokable: true, - web_trigger: false, - }) + if let Some(entry) = functions_to_scan.get_mut(dynamic_properties) { + entry.invokable = true; + } } - } + }); - for issue in self.issue_glance { - functions_to_scan.push(Entrypoint { - function: issue.function, - invokable: true, - web_trigger: false, - }); - if let Some(resolver) = issue.resolver { - functions_to_scan.push(Entrypoint { - function: resolver.function, - invokable: true, - web_trigger: false, - }) + self.issue_glance.iter().for_each(|issue| { + if let Some(entry) = functions_to_scan.get_mut(issue.common_keys.function) { + entry.invokable = true; + } + + if let Some(resolver) = issue.common_keys.resolver { + if let Some(entry) = functions_to_scan.get_mut(resolver) { + entry.invokable = true; + } } if let Some(dynamic_properties) = issue.dynamic_properties { - functions_to_scan.push(Entrypoint { - function: dynamic_properties.function, - invokable: true, - web_trigger: false, - }) + if let Some(entry) = functions_to_scan.get_mut(dynamic_properties) { + entry.invokable = true; + } + } + }); + + self.access_import_type.iter().for_each(|access| { + if let Some(entry) = functions_to_scan.get_mut(access.common_keys.function) { + entry.invokable = true; + } + + if let Some(resolver) = access.common_keys.resolver { + if let Some(entry) = functions_to_scan.get_mut(resolver) { + entry.invokable = true; + } } - } - for access in self.access_import_type { - functions_to_scan.push(Entrypoint { - function: access.function, - invokable: true, - web_trigger: false, - }); if let Some(delete) = access.one_delete_import { - functions_to_scan.push(Entrypoint { - function: delete.function, - invokable: true, - web_trigger: false, - }) + if let Some(entry) = functions_to_scan.get_mut(delete) { + entry.invokable = true; + } } if let Some(start) = access.start_import { - functions_to_scan.push(Entrypoint { - function: start.function, - invokable: true, - web_trigger: false, - }) + if let Some(entry) = functions_to_scan.get_mut(start) { + entry.invokable = true; + } } if let Some(stop) = access.stop_import { - functions_to_scan.push(Entrypoint { - function: stop.function, - invokable: true, - web_trigger: false, - }) + if let Some(entry) = functions_to_scan.get_mut(stop) { + entry.invokable = true; + } } if let Some(status) = access.import_status { - functions_to_scan.push(Entrypoint { - function: status.function, - invokable: true, - web_trigger: false, - }) + if let Some(entry) = functions_to_scan.get_mut(status) { + entry.invokable = true; + } } - } + }); // get array for user invokable module functions // make alternate_functions all user-invokable functions - for module in self.extra.into_values().flatten() { + for module in self.extra.clone().into_values().flatten() { if let Some(mod_function) = module.function { - functions_to_scan.push(Entrypoint { - function: mod_function, - invokable: true, - web_trigger: false, - }); + if let Some(entry) = functions_to_scan.get_mut(mod_function) { + entry.invokable = true; + } } if let Some(resolver) = module.resolver { - functions_to_scan.push(Entrypoint { - function: resolver.function, - invokable: true, - web_trigger: false, - }); + if let Some(entry) = functions_to_scan.get_mut(resolver.function) { + entry.invokable = true; + } } } @@ -685,7 +657,7 @@ mod tests { assert_eq!(manifest.app.name, Some("My App")); assert_eq!(manifest.app.id, "my-app"); assert_eq!(manifest.modules.macros.len(), 1); - assert_eq!(manifest.modules.macros[0].key, "My Macro"); + assert_eq!(manifest.modules.macros[0].common_keys.key, "My Macro"); // assert_eq!(manifest.modules.macros[0].function, "my-macro"); assert_eq!(manifest.modules.functions.len(), 1); assert_eq!( @@ -778,21 +750,21 @@ mod tests { }"#; let manifest: ForgeManifest = serde_json::from_str(json).unwrap(); assert_eq!(manifest.modules.macros.len(), 1); + assert_eq!( + manifest.modules.macros[0].common_keys.function, + "Catch-me-if-you-can0" + ); - if let Some(func) = manifest.modules.macros[0].function { - assert_eq!(func, "Catch-me-if-you-can0"); - } - - if let Some(func) = &manifest.modules.macros[0].resolver { - assert_eq!(func.function, "Catch-me-if-you-can1"); + if let Some(func) = manifest.modules.macros[0].common_keys.resolver { + assert_eq!(func, "Catch-me-if-you-can1"); } - if let Some(func) = &manifest.modules.macros[0].config { - assert_eq!(func.function, "Catch-me-if-you-can2"); + if let Some(func) = manifest.modules.macros[0].config { + assert_eq!(func, "Catch-me-if-you-can2"); } - if let Some(func) = &manifest.modules.macros[0].export { - assert_eq!(func.function, "Catch-me-if-you-can3"); + if let Some(func) = manifest.modules.macros[0].export { + assert_eq!(func, "Catch-me-if-you-can3"); } } } diff --git a/crates/fsrt/src/main.rs b/crates/fsrt/src/main.rs index ad2ebf7b..2a4f0ba5 100644 --- a/crates/fsrt/src/main.rs +++ b/crates/fsrt/src/main.rs @@ -5,7 +5,7 @@ use forge_permission_resolver::permissions_resolver::{ }; use miette::{IntoDiagnostic, Result}; use std::{ - collections::HashSet, + collections::{HashSet, BTreeMap, hash_map::Entry}, convert::TryFrom, fs, os::unix::prelude::OsStrExt, @@ -105,7 +105,7 @@ struct ForgeProject<'a> { sm: Arc, ctx: AppCtx, env: Environment, - funcs: Vec>, + funcs:BTreeMap<&'a str, Entrypoint<'a>>, opts: Opts, >>>>>>> cd2ed7b (edited main to iterate over vector of functions. TODO: edit add_funcs in main.rs and test into_analyzble_function use case in main.rs) } From f04d418c504cd67ee14740e1908c93e7347480e8 Mon Sep 17 00:00:00 2001 From: Alina Wang Date: Wed, 10 Jan 2024 18:10:51 -0800 Subject: [PATCH 299/517] resolving merge conflicts --- crates/forge_loader/src/manifest.rs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/crates/forge_loader/src/manifest.rs b/crates/forge_loader/src/manifest.rs index a971e388..9ff3811e 100644 --- a/crates/forge_loader/src/manifest.rs +++ b/crates/forge_loader/src/manifest.rs @@ -349,13 +349,13 @@ impl<'a> ForgeModules<'a> { }); // create arrays representing functions that expose user non-invokable functions - self.consumers.iter().for_each(|consumer| { - if functions_to_scan.contains_key(consumer.resolver.function) { - if let Some(entry) = functions_to_scan.get_mut(consumer.resolver.function) { - entry.invokable = true; - } - } - }); + // self.consumers.iter().for_each(|consumer| { + // if functions_to_scan.contains_key(consumer.resolver.function) { + // if let Some(entry) = functions_to_scan.get_mut(consumer.resolver.function) { + // entry.invokable = true; + // } + // } + // }); self.data_provider.iter().for_each(|dataprovider| { if let Some(call) = dataprovider.callback { From cde73afddd4d7a7792b3257d5851e04a808defc3 Mon Sep 17 00:00:00 2001 From: Alina Wang Date: Wed, 10 Jan 2024 18:10:51 -0800 Subject: [PATCH 300/517] updated search_suggestion in customfield to be an optional value. --- crates/forge_loader/src/manifest.rs | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/crates/forge_loader/src/manifest.rs b/crates/forge_loader/src/manifest.rs index 9ff3811e..f5a2d52b 100644 --- a/crates/forge_loader/src/manifest.rs +++ b/crates/forge_loader/src/manifest.rs @@ -3,6 +3,7 @@ use std::{ collections::HashSet, hash::Hash, path::{Path, PathBuf}, + str::pattern::SearchStep, }; use crate::{forgepermissions::ForgePermissions, Error}; @@ -113,7 +114,7 @@ pub struct CustomField<'a> { #[serde(flatten, borrow)] common_keys: CommonKey<'a>, value: Option<&'a str>, - search_suggestions: &'a str, + search_suggestions: Option<&'a str>, edit: Option<&'a str>, } @@ -372,8 +373,10 @@ impl<'a> ForgeModules<'a> { } } - if let Some(entry) = functions_to_scan.get_mut(customfield.search_suggestions) { - entry.invokable = true; + if let Some(search) = customfield.search_suggestions { + if let Some(entry) = functions_to_scan.get_mut(search) { + entry.invokable = true; + } } if let Some(entry) = functions_to_scan.get_mut(customfield.common_keys.function) { From 495515718d8e35b7a9835865857ba910c87b104f Mon Sep 17 00:00:00 2001 From: Alina Wang Date: Wed, 10 Jan 2024 18:10:51 -0800 Subject: [PATCH 301/517] modified into_analyzable_functions with Josh and implementation in main.rs --- crates/forge_loader/src/manifest.rs | 310 +++++++--------------------- crates/fsrt/src/main.rs | 149 +++++++++---- 2 files changed, 189 insertions(+), 270 deletions(-) diff --git a/crates/forge_loader/src/manifest.rs b/crates/forge_loader/src/manifest.rs index f5a2d52b..8c26a3d6 100644 --- a/crates/forge_loader/src/manifest.rs +++ b/crates/forge_loader/src/manifest.rs @@ -1,16 +1,16 @@ use std::{ borrow::Borrow, - collections::HashSet, + collections::{BTreeSet, HashSet}, hash::Hash, path::{Path, PathBuf}, - str::pattern::SearchStep, + sync::Arc, }; use crate::{forgepermissions::ForgePermissions, Error}; use forge_utils::FxHashMap; use itertools::{Either, Itertools}; use serde::Deserialize; -use std::collections::BTreeMap; +use serde_json::map::Entry; use tracing::trace; #[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] @@ -258,38 +258,39 @@ pub enum FunctionTy { // Struct used for tracking what scan a funtion requires. #[derive(Debug, Clone, PartialEq, Eq)] -pub struct Entrypoint<'a> { - pub function: &'a str, +pub struct Entrypoint<'a, S = Unresolved> { + pub function: FunctionRef<'a, S>, pub invokable: bool, pub web_trigger: bool, } // Helper functions that help filter out which functions are what. -impl FunctionTy { - pub fn map(self, f: impl FnOnce(T) -> O) -> FunctionTy { - match self { - Self::Invokable(t) => FunctionTy::Invokable(f(t)), - Self::WebTrigger(t) => FunctionTy::WebTrigger(f(t)), - } - } - - #[inline] - pub fn into_inner(self) -> T { - match self { - FunctionTy::Invokable(t) | FunctionTy::WebTrigger(t) => t, - } - } - - pub fn sequence( - self, - f: impl FnOnce(T) -> I, - ) -> impl Iterator> { - match self { - Self::Invokable(t) => Either::Left(f(t).into_iter().map(FunctionTy::Invokable)), - Self::WebTrigger(t) => Either::Right(f(t).into_iter().map(FunctionTy::WebTrigger)), - } - } -} +// original code that's commented out to modify methods. Here for reference +// impl FunctionTy { +// pub fn map(self, f: impl FnOnce(T) -> O) -> FunctionTy { +// match self { +// Self::Invokable(t) => FunctionTy::Invokable(f(t)), +// Self::WebTrigger(t) => FunctionTy::WebTrigger(f(t)), +// } +// } + +// #[inline] +// pub fn into_inner(self) -> T { +// match self { +// FunctionTy::Invokable(t) | FunctionTy::WebTrigger(t) => t, +// } +// } + +// pub fn sequence( +// self, +// f: impl FnOnce(T) -> I, +// ) -> impl Iterator> { +// match self { +// Self::Invokable(t) => Either::Left(f(t).into_iter().map(FunctionTy::Invokable)), +// Self::WebTrigger(t) => Either::Right(f(t).into_iter().map(FunctionTy::WebTrigger)), +// } +// } +// } impl AsRef for FunctionTy { #[inline] @@ -302,249 +303,96 @@ impl AsRef for FunctionTy { impl<'a> ForgeModules<'a> { // TODO: function returns iterator where each item is some specified type. - pub fn into_analyzable_functions(&mut self) -> BTreeMap<&'a str, Entrypoint<'a>> { + pub fn into_analyzable_functions(mut self) -> impl Iterator> { // number of webtriggers are usually low, so it's better to just sort them and reuse self.webtriggers .sort_unstable_by_key(|trigger| trigger.function); + self.webtriggers + .sort_unstable_by_key(|trigger| trigger.function); // Get all the Triggers and represent them as a new struct thing where "webtrigger" attribute is true // for all trigger things + // Get all the Triggers and represent them as a new struct thing where "webtrigger" attribute is true + // for all trigger things - let mut functions_to_scan = BTreeMap::new(); - - // Get all functions for module from manifest.yml - self.functions.iter().for_each(|func| { - functions_to_scan.insert( - func.key, - Entrypoint { - function: func.key, - invokable: false, - web_trigger: false, - }, - ); - }); - - self.webtriggers.iter().for_each(|webtriggers| { - if functions_to_scan.contains_key(webtriggers.function) { - if let Some(entry) = functions_to_scan.get_mut(webtriggers.function) { - entry.web_trigger = true; - } - } - }); - - self.event_triggers.iter().for_each(|event_triggers| { - if functions_to_scan.contains_key(event_triggers.raw.function) { - if let Some(entry) = functions_to_scan.get_mut(event_triggers.raw.function) { - entry.web_trigger = true; - } - } - }); - self.scheduled_triggers - .iter() - .for_each(|schedule_triggers| { - if functions_to_scan.contains_key(schedule_triggers.raw.function) { - if let Some(entry) = functions_to_scan.get_mut(schedule_triggers.raw.function) { - entry.web_trigger = true; - } - } - }); - - // create arrays representing functions that expose user non-invokable functions - // self.consumers.iter().for_each(|consumer| { - // if functions_to_scan.contains_key(consumer.resolver.function) { - // if let Some(entry) = functions_to_scan.get_mut(consumer.resolver.function) { - // entry.invokable = true; - // } - // } - // }); + let mut invokable_functions = BTreeSet::new(); self.data_provider.iter().for_each(|dataprovider| { - if let Some(call) = dataprovider.callback { - if let Some(entry) = functions_to_scan.get_mut(call) { - entry.invokable = true; - } - } + invokable_functions.extend(dataprovider.callback); }); self.custom_field.iter().for_each(|customfield| { - if let Some(value) = customfield.value { - if let Some(entry) = functions_to_scan.get_mut(value) { - entry.invokable = true; - } - } - - if let Some(search) = customfield.search_suggestions { - if let Some(entry) = functions_to_scan.get_mut(search) { - entry.invokable = true; - } - } - - if let Some(entry) = functions_to_scan.get_mut(customfield.common_keys.function) { - entry.invokable = true; - } + invokable_functions.extend(customfield.value); + invokable_functions.extend(customfield.search_suggestions); + invokable_functions.extend(customfield.edit); - if let Some(resolver) = customfield.common_keys.resolver { - if let Some(entry) = functions_to_scan.get_mut(resolver) { - entry.invokable = true; - } - } - - if let Some(edit) = customfield.edit { - if let Some(entry) = functions_to_scan.get_mut(edit) { - entry.invokable = true; - } - } + invokable_functions.insert(customfield.common_keys.function); + invokable_functions.extend(customfield.common_keys.resolver); }); self.ui_modifications.iter().for_each(|ui| { - if let Some(entry) = functions_to_scan.get_mut(ui.common_keys.function) { - entry.invokable = true; - } - - if let Some(resolver) = ui.common_keys.resolver { - if let Some(entry) = functions_to_scan.get_mut(resolver) { - entry.invokable = true; - } - } + invokable_functions.insert(ui.common_keys.function); + invokable_functions.extend(ui.common_keys.resolver); }); self.workflow_validator.iter().for_each(|validator| { - if let Some(entry) = functions_to_scan.get_mut(validator.common_keys.function) { - entry.invokable = true; - } + invokable_functions.insert(validator.common_keys.key); - if let Some(resolver) = validator.common_keys.resolver { - if let Some(entry) = functions_to_scan.get_mut(resolver) { - entry.invokable = true; - } - } + invokable_functions.insert(validator.common_keys.function); + + invokable_functions.extend(validator.common_keys.resolver); }); self.workflow_post_function .iter() .for_each(|post_function| { - if let Some(entry) = functions_to_scan.get_mut(post_function.common_keys.function) { - entry.invokable = true; - } + invokable_functions.insert(post_function.common_keys.key); - if let Some(resolver) = post_function.common_keys.resolver { - if let Some(entry) = functions_to_scan.get_mut(resolver) { - entry.invokable = true; - } - } + invokable_functions.insert(post_function.common_keys.function); + + invokable_functions.extend(post_function.common_keys.resolver); }); // get user invokable modules that have additional exposure endpoints. // ie macros has config and export fields on top of resolver fields that are functions self.macros.iter().for_each(|macros| { - if let Some(resolver) = macros.common_keys.resolver { - if let Some(entry) = functions_to_scan.get_mut(resolver) { - entry.invokable = true; - } - } + invokable_functions.insert(macros.common_keys.key); - if let Some(config) = macros.config { - if let Some(entry) = functions_to_scan.get_mut(config) { - entry.invokable = true; - } - } + invokable_functions.insert(macros.common_keys.function); + invokable_functions.extend(macros.common_keys.resolver); - if let Some(export) = macros.export { - if let Some(entry) = functions_to_scan.get_mut(export) { - entry.invokable = true; - } - } - }); - - self.content_by_line_item.iter().for_each(|contentitem| { - if let Some(entry) = functions_to_scan.get_mut(contentitem.common_keys.function) { - entry.invokable = true; - } - - if let Some(resolver) = contentitem.common_keys.resolver { - if let Some(entry) = functions_to_scan.get_mut(resolver) { - entry.invokable = true; - } - } - - if let Some(dynamic_properties) = contentitem.dynamic_properties { - if let Some(entry) = functions_to_scan.get_mut(dynamic_properties) { - entry.invokable = true; - } - } + invokable_functions.extend(macros.config); + invokable_functions.extend(macros.export); }); self.issue_glance.iter().for_each(|issue| { - if let Some(entry) = functions_to_scan.get_mut(issue.common_keys.function) { - entry.invokable = true; - } - - if let Some(resolver) = issue.common_keys.resolver { - if let Some(entry) = functions_to_scan.get_mut(resolver) { - entry.invokable = true; - } - } - - if let Some(dynamic_properties) = issue.dynamic_properties { - if let Some(entry) = functions_to_scan.get_mut(dynamic_properties) { - entry.invokable = true; - } - } + invokable_functions.insert(issue.common_keys.function); + invokable_functions.extend(issue.common_keys.resolver); + invokable_functions.extend(issue.dynamic_properties); }); self.access_import_type.iter().for_each(|access| { - if let Some(entry) = functions_to_scan.get_mut(access.common_keys.function) { - entry.invokable = true; - } - - if let Some(resolver) = access.common_keys.resolver { - if let Some(entry) = functions_to_scan.get_mut(resolver) { - entry.invokable = true; - } - } - - if let Some(delete) = access.one_delete_import { - if let Some(entry) = functions_to_scan.get_mut(delete) { - entry.invokable = true; - } - } - - if let Some(start) = access.start_import { - if let Some(entry) = functions_to_scan.get_mut(start) { - entry.invokable = true; - } - } - - if let Some(stop) = access.stop_import { - if let Some(entry) = functions_to_scan.get_mut(stop) { - entry.invokable = true; - } - } + invokable_functions.insert(access.common_keys.function); + invokable_functions.extend(access.common_keys.resolver); - if let Some(status) = access.import_status { - if let Some(entry) = functions_to_scan.get_mut(status) { - entry.invokable = true; - } - } + invokable_functions.extend(access.one_delete_import); + invokable_functions.extend(access.stop_import); + invokable_functions.extend(access.start_import); + invokable_functions.extend(access.import_status); }); - // get array for user invokable module functions - // make alternate_functions all user-invokable functions - for module in self.extra.clone().into_values().flatten() { - if let Some(mod_function) = module.function { - if let Some(entry) = functions_to_scan.get_mut(mod_function) { - entry.invokable = true; - } - } - - if let Some(resolver) = module.resolver { - if let Some(entry) = functions_to_scan.get_mut(resolver.function) { - entry.invokable = true; - } - } - } - - functions_to_scan + self.functions.into_iter().flat_map(move |func| { + let web_trigger = self + .webtriggers + .binary_search_by_key(&func.key, |trigger| &trigger.function) + .is_ok(); + let invokable = invokable_functions.contains(func.key); + Ok::<_, Error>(Entrypoint { + function: FunctionRef::try_from(func)?, + invokable, + web_trigger, + }) + }) } } diff --git a/crates/fsrt/src/main.rs b/crates/fsrt/src/main.rs index 2a4f0ba5..8f9654ae 100644 --- a/crates/fsrt/src/main.rs +++ b/crates/fsrt/src/main.rs @@ -5,8 +5,7 @@ use forge_permission_resolver::permissions_resolver::{ }; use miette::{IntoDiagnostic, Result}; use std::{ - collections::{HashSet, BTreeMap, hash_map::Entry}, - convert::TryFrom, + collections::HashSet, fs, os::unix::prelude::OsStrExt, path::{Path, PathBuf}, @@ -18,8 +17,11 @@ use std::{ use clap::{Parser, ValueHint}; use miette::{IntoDiagnostic, Result}; +<<<<<<< HEAD use serde_json::map::Entry; >>>>>>> cd2ed7b (edited main to iterate over vector of functions. TODO: edit add_funcs in main.rs and test into_analyzble_function use case in main.rs) +======= +>>>>>>> 29c38d6 (modified into_analyzable_functions with Josh and implementation in main.rs) use swc_core::{ common::{Globals, Mark, SourceMap, GLOBALS}, ecma::{ @@ -45,7 +47,7 @@ use forge_analyzer::{ resolver::resolve_calls, }; -use forge_loader::manifest::{ForgeManifest, FunctionRef, FunctionTy, Entrypoint}; +use forge_loader::manifest::{Entrypoint, ForgeManifest, Resolved}; use walkdir::WalkDir; #[derive(Parser, Debug)] @@ -99,18 +101,28 @@ struct Opts { out: Option, } +#[derive(Debug, Clone)] +struct ResolvedEntryPoint<'a> { + func_name: &'a str, + path: PathBuf, + module: ModId, + def_id: DefId, + webtrigger: bool, + invokable: bool, +} + struct ForgeProject<'a> { >>>>>>> 7e86f46 (abstracted structs to use a CommonKeys struct that holds: key, function, and resolver for less code duplication. Updated into_analayzable method to update values based on functions specified in function mod. TODO: Update rest of non trigger modules to update mapping to functions to scan from function mod) #[allow(dead_code)] sm: Arc, ctx: AppCtx, env: Environment, - funcs:BTreeMap<&'a str, Entrypoint<'a>>, + funcs: Vec>, opts: Opts, >>>>>>> cd2ed7b (edited main to iterate over vector of functions. TODO: edit add_funcs in main.rs and test into_analyzble_function use case in main.rs) } -impl ForgeProject<'_> { +impl<'a> ForgeProject<'a> { #[instrument(skip(src, iter))] fn with_files_and_sourceroot, I: IntoIterator>( src: P, @@ -157,21 +169,24 @@ impl ForgeProject<'_> { funcs: vec![], } } -// TODO: edit to work with new iterator that not FUNCTIONTY - fn add_funcs<'a, I: IntoIterator>>(&mut self, iter: I) { - self.funcs.extend(iter); + // TODO: edit to work with new iterator that not FUNCTIONTY + fn add_funcs>>(&mut self, iter: I) { + self.funcs.extend(iter.into_iter().filter_map(|entrypoint| { + let (func_name, path) = entrypoint.function.into_func_path(); + let module = self.ctx.modid_from_path(&path)?; + let def_id = self.env.module_export(module, func_name)?; + Some(ResolvedEntryPoint { + func_name, + path, + module, + def_id, + invokable: entrypoint.invokable, + webtrigger: entrypoint.web_trigger, + }) + })); } } - -// self.funcs.extend(iter.into_iter().flat_map(|ftype| { -// ftype.sequence(|(func_name, path)| { -// let modid = self.ctx.modid_from_path(&path)?; -// let func = self.env.module_export(modid, func_name)?; -// Some((func_name.to_owned(), path, modid, func)) -// }) -// })); - fn is_js_file>(path: P) -> bool { matches!( path.as_ref().extension().map(|s| s.as_bytes()), @@ -191,7 +206,11 @@ fn collect_sourcefiles>(root: P) -> impl Iterator } #[tracing::instrument(level = "debug")] +<<<<<<< HEAD fn scan_directory(dir: PathBuf, function: Option<&str>, opts: &Args) -> Result { +======= +fn scan_directory(dir: PathBuf, function: Option<&str>, opts: Opts) -> Result<()> { +>>>>>>> 29c38d6 (modified into_analyzable_functions with Josh and implementation in main.rs) let mut manifest_file = dir.clone(); manifest_file.push("manifest.yaml"); if !manifest_file.exists() { @@ -230,19 +249,26 @@ fn scan_directory(dir: PathBuf, function: Option<&str>, opts: &Args) -> Result(resolved_func.into_func_path()) - // }) - // }); + let funcrefs = manifest + .modules + .into_analyzable_functions() + .flat_map(|entrypoint| { + Ok::<_, forge_loader::Error>(Entrypoint { + function: entrypoint.function.try_resolve(&paths, &dir)?, + invokable: entrypoint.invokable, + web_trigger: entrypoint.web_trigger, + }) + }); + let src_root = dir.join("src"); let mut proj = ForgeProject::with_files_and_sourceroot(src_root, paths.clone(), secret_packages); if transpiled_async { warn!("Unable to scan due to transpiled async"); +<<<<<<< HEAD +======= + return Ok(()); +>>>>>>> 29c38d6 (modified into_analyzable_functions with Josh and implementation in main.rs) } proj.add_funcs(funcrefs); resolve_calls(&mut proj.ctx); @@ -326,6 +352,7 @@ fn scan_directory(dir: PathBuf, function: Option<&str>, opts: &Args) -> Result { @@ -462,27 +489,67 @@ fn scan_directory(dir: PathBuf, function: Option<&str>, opts: &Args) -> Result { + // let mut checker = AuthZChecker::new(); + // debug!("checking {func} at {path:?}"); + // if let Err(err) = interp.run_checker(def, &mut checker, path.clone(), func.clone()) + // { + // warn!("error while scanning {func} in {path:?}: {err}"); + // } + // reporter.add_vulnerabilities(checker.into_vulns()); + // } + // FunctionTy::WebTrigger((ref func, ref path, _, def)) => { + // let mut checker = AuthenticateChecker::new(); + // debug!("checking webtrigger {func} at {path:?}"); + // if let Err(err) = + // authn_interp.run_checker(def, &mut checker, path.clone(), func.clone()) + // { + // warn!("error while scanning {func} in {path:?}: {err}"); + // } + // reporter.add_vulnerabilities(checker.into_vulns()); + // } + // } +>>>>>>> 29c38d6 (modified into_analyzable_functions with Josh and implementation in main.rs) + + // Get entrypoint value from tuple + // Logic for performing scans. + // If it's invokable, then run invokable scan. If web_trigger, then trigger scan. + // And if it's both, run both scans. if func.invokable { let mut checker = AuthZChecker::new(); - debug!("checking {:?} at {path:?}", func.function); - if let Err(err) = interp.run_checker(def, &mut checker, path.clone(), func.function.to_string()) - { - warn!("error while scanning {:?} in {path:?}: {err}", func.function); + debug!("checking {:?} at {:?}", func.func_name, &func.path); + if let Err(err) = interp.run_checker( + func.def_id, + &mut checker, + func.path.clone(), + func.func_name.to_string(), + ) { + warn!( + "error while scanning {:?} in {:?}: {err}", + func.func_name, func.path, + ); } reporter.add_vulnerabilities(checker.into_vulns()); - - } else if func.web_trigger { + } else if func.webtrigger { let mut checker = AuthenticateChecker::new(); - debug!("checking webtrigger {:?} at {path:?}", func.function); - if let Err(err) = - authn_interp.run_checker(def, &mut checker, path.clone(), func.function.to_string()) - { - warn!("error while scanning {:?} in {path:?}: {err}", func.function); + debug!( + "checking webtrigger {:?} at {:?}", + func.func_name, func.path, + ); + if let Err(err) = authn_interp.run_checker( + func.def_id, + &mut checker, + func.path.clone(), + func.func_name.to_string(), + ) { + warn!( + "error while scanning {:?} in {:?}: {err}", + func.func_name, func.path, + ); } reporter.add_vulnerabilities(checker.into_vulns()); - - } } @@ -502,8 +569,12 @@ fn scan_directory(dir: PathBuf, function: Option<&str>, opts: &Args) -> Result println!("{report}"), } +<<<<<<< HEAD Ok(proj) +======= + Ok(()) +>>>>>>> 29c38d6 (modified into_analyzable_functions with Josh and implementation in main.rs) } fn main() -> Result<()> { From 8deeb4d61236d41a898cca34164f0326fe6a07ec Mon Sep 17 00:00:00 2001 From: Alina Wang Date: Wed, 10 Jan 2024 18:12:12 -0800 Subject: [PATCH 302/517] added new structs for user non-invokable modules --- crates/forge_loader/src/manifest.rs | 248 ++++++++++++---------------- 1 file changed, 104 insertions(+), 144 deletions(-) diff --git a/crates/forge_loader/src/manifest.rs b/crates/forge_loader/src/manifest.rs index 8c26a3d6..a47b2338 100644 --- a/crates/forge_loader/src/manifest.rs +++ b/crates/forge_loader/src/manifest.rs @@ -99,41 +99,47 @@ struct ScheduledTrigger<'a> { interval: Interval, } -// compass DataProvider module #[derive(Debug, Clone, PartialEq, Eq, Deserialize)] -pub struct DataProvider<'a> { - #[serde(flatten, borrow)] +struct DataProvider<'a> { key: &'a str, - callback: Option<&'a str>, + #[serde(flatten, borrow)] + callback: Callback<'a>, +} + +#[derive(Debug, Clone, PartialEq, Eq, Deserialize)] +pub struct Callback<'a> { + pub function: &'a str, } -// Struct for Custom field Module. Check that search suggestion gets read in correctly. +// Struct for Custom field Module. Check that search suggestion gets read in correctly. #[derive(Debug, Clone, PartialEq, Eq, Deserialize)] -pub struct CustomField<'a> { - // all attributes below involve function calls +struct CustomField<'a> { #[serde(flatten, borrow)] - common_keys: CommonKey<'a>, - value: Option<&'a str>, - search_suggestions: Option<&'a str>, - edit: Option<&'a str>, + key: &'a str, + search_suggestion: &'a str, } + #[derive(Debug, Clone, PartialEq, Eq, Deserialize)] -pub struct UiModificatons<'a> { +struct UiModificatons<'a> { #[serde(flatten, borrow)] - common_keys: CommonKey<'a>, + key: &'a str, + resolver: Callback<'a>, } #[derive(Debug, Clone, PartialEq, Eq, Deserialize)] -pub struct WorkflowValidator<'a> { +struct WorkflowValidator<'a> { #[serde(flatten, borrow)] - common_keys: CommonKey<'a>, + key: &'a str, + functon: &'a str, + resolver: Callback<'a> } #[derive(Debug, Clone, PartialEq, Eq, Deserialize)] -pub struct WorkflowPostFunction<'a> { +struct WorkflowPostFunction<'a> { #[serde(flatten, borrow)] - common_keys: CommonKey<'a>, + key: &'a str, + functon: &'a str, } // Add more structs here for deserializing forge modules @@ -143,12 +149,6 @@ pub struct ForgeModules<'a> { macros: Vec>, #[serde(rename = "function", default, borrow)] pub functions: Vec>, - #[serde(rename = "contentByLineItem", default, borrow)] - content_by_line_item: Vec>, - #[serde(rename = "jira:issueGlance", default, borrow)] - issue_glance: Vec>, - #[serde(rename = "jira:accessImportType", default, borrow)] - access_import_type: Vec>, // deserializing non user-invocable modules #[serde(rename = "webtrigger", default, borrow)] webtriggers: Vec>, @@ -169,6 +169,17 @@ pub struct ForgeModules<'a> { #[serde(rename = "jira:workflowPostFunction", default, borrow)] pub workflow_post_function: Vec>, // deserializing user invokable module functions + #[serde(rename = "compass:dataProvider", default, borrow)] + pub data_provider: Vec>, + #[serde(rename = "jira:customField", default, borrow)] + pub custom_field: Vec>, + #[serde(rename = "jira:uiModificatons", default, borrow)] + pub ui_modifications: Vec>, + #[serde(rename = "jira:workflowValidator", default, borrow)] + pub workflow_validator: Vec>, + #[serde(rename = "jira:workflowPostFunction", default, borrow)] + pub workflow_post_function: Vec>, + // deserializing user invokable module functions #[serde(flatten)] extra: FxHashMap>>, } @@ -247,9 +258,10 @@ pub struct FunctionRef<'a, S = Unresolved> { status: S, } + // Add an extra variant to the FunctionTy enum for non user invocable functions -// Indirect: functions indirectly invoked by user :O So kewl. -// TODO: change this to struct with bools +// Indirect: functions indirectly invoked by user :O So kewl. +// TODO: change this to struct with bools #[derive(Debug, Clone, PartialEq, Eq)] pub enum FunctionTy { Invokable(T), @@ -257,22 +269,21 @@ pub enum FunctionTy { } // Struct used for tracking what scan a funtion requires. -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct Entrypoint<'a, S = Unresolved> { - pub function: FunctionRef<'a, S>, - pub invokable: bool, - pub web_trigger: bool, +#[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] +pub struct Entrypoints<'a> { + function:Vec>, + invokable: bool, + web_trigger: bool, } -// Helper functions that help filter out which functions are what. -// original code that's commented out to modify methods. Here for reference -// impl FunctionTy { -// pub fn map(self, f: impl FnOnce(T) -> O) -> FunctionTy { -// match self { -// Self::Invokable(t) => FunctionTy::Invokable(f(t)), -// Self::WebTrigger(t) => FunctionTy::WebTrigger(f(t)), -// } -// } +// Helper functions that help filter out which functions are what. +impl FunctionTy { + pub fn map(self, f: impl FnOnce(T) -> O) -> FunctionTy { + match self { + Self::Invokable(t) => FunctionTy::Invokable(f(t)), + Self::WebTrigger(t) => FunctionTy::WebTrigger(f(t)), + } + } // #[inline] // pub fn into_inner(self) -> T { @@ -301,97 +312,66 @@ impl AsRef for FunctionTy { } } + impl<'a> ForgeModules<'a> { - // TODO: function returns iterator where each item is some specified type. - pub fn into_analyzable_functions(mut self) -> impl Iterator> { + + + pub fn into_analyzable_functions( + mut self, + ) -> impl Iterator>> { // number of webtriggers are usually low, so it's better to just sort them and reuse self.webtriggers .sort_unstable_by_key(|trigger| trigger.function); - self.webtriggers - .sort_unstable_by_key(|trigger| trigger.function); - - // Get all the Triggers and represent them as a new struct thing where "webtrigger" attribute is true - // for all trigger things - // Get all the Triggers and represent them as a new struct thing where "webtrigger" attribute is true - // for all trigger things - - let mut invokable_functions = BTreeSet::new(); - - self.data_provider.iter().for_each(|dataprovider| { - invokable_functions.extend(dataprovider.callback); - }); - - self.custom_field.iter().for_each(|customfield| { - invokable_functions.extend(customfield.value); - invokable_functions.extend(customfield.search_suggestions); - invokable_functions.extend(customfield.edit); - - invokable_functions.insert(customfield.common_keys.function); - invokable_functions.extend(customfield.common_keys.resolver); - }); - - self.ui_modifications.iter().for_each(|ui| { - invokable_functions.insert(ui.common_keys.function); - invokable_functions.extend(ui.common_keys.resolver); - }); - - self.workflow_validator.iter().for_each(|validator| { - invokable_functions.insert(validator.common_keys.key); - - invokable_functions.insert(validator.common_keys.function); - - invokable_functions.extend(validator.common_keys.resolver); - }); - - self.workflow_post_function - .iter() - .for_each(|post_function| { - invokable_functions.insert(post_function.common_keys.key); - - invokable_functions.insert(post_function.common_keys.function); - - invokable_functions.extend(post_function.common_keys.resolver); - }); - - // get user invokable modules that have additional exposure endpoints. - // ie macros has config and export fields on top of resolver fields that are functions - self.macros.iter().for_each(|macros| { - invokable_functions.insert(macros.common_keys.key); - invokable_functions.insert(macros.common_keys.function); - invokable_functions.extend(macros.common_keys.resolver); - invokable_functions.extend(macros.config); - invokable_functions.extend(macros.export); - }); + let mut ignored_functions: BTreeSet<_> = self + .scheduled_triggers + .into_iter() + .map(|trigger| trigger.raw.function) + .chain( + self.event_triggers + .into_iter() + .map(|trigger| trigger.raw.function), + ) + .collect(); + + // make alternate_functions all user-invokable functions + let mut alternate_functions: Vec<&str> = Vec::new(); + for module in self.extra.into_values().flatten() { + alternate_functions.extend(module.function); + if let Some(resolver) = module.resolver { + alternate_functions.push(resolver.function); + } + } - self.issue_glance.iter().for_each(|issue| { - invokable_functions.insert(issue.common_keys.function); - invokable_functions.extend(issue.common_keys.resolver); - invokable_functions.extend(issue.dynamic_properties); + // Iterate over Consumers and check that if consumers isn't in alternate functions, add consumer funtion to be ignored + // assuming that alternate functions already has all user invokable functions. + self.consumers.iter().for_each(|consumer| { + if !alternate_functions.contains(&consumer.resolver.function) { + ignored_functions.insert(consumer.resolver.function); + } }); - self.access_import_type.iter().for_each(|access| { - invokable_functions.insert(access.common_keys.function); - invokable_functions.extend(access.common_keys.resolver); - - invokable_functions.extend(access.one_delete_import); - invokable_functions.extend(access.stop_import); - invokable_functions.extend(access.start_import); - invokable_functions.extend(access.import_status); - }); + // TODO: Iterate through all deserialized entrypoints that are represented as a struct when deserialized + // Update Struct values to be true or not. If any part true, then scan. + // This solution fixes the problem that we only check known user invokable modules and also acccounts for non-invokable module entry points - self.functions.into_iter().flat_map(move |func| { - let web_trigger = self - .webtriggers - .binary_search_by_key(&func.key, |trigger| &trigger.function) - .is_ok(); - let invokable = invokable_functions.contains(func.key); - Ok::<_, Error>(Entrypoint { - function: FunctionRef::try_from(func)?, - invokable, - web_trigger, - }) + // return non-user invokable functions + self.functions.into_iter().filter_map(move |func| { + if ignored_functions.contains(&func.key) { + return None; + } + Some( + if self + .webtriggers + .binary_search_by_key(&func.key, |trigger| trigger.function) + .is_ok() + { + FunctionTy::WebTrigger(func) + } else { + FunctionTy::Invokable(func) + }, + ) }) } } @@ -544,9 +524,10 @@ mod tests { ); } - // Modified specific deserialization schemes for modules. Checking that new schemes can deserialize function values. + // Modified specific deserialization schemes for modules. Checking that new schemes can deserialize function values. #[test] fn test_new_deserialize() { + let json = r#"{ "app": { "name": "My App", @@ -557,15 +538,11 @@ mod tests { { "key": "my-macro", "title": "My Macro", - "function": "Catch-me-if-you-can0", "resolver": { - "function": "Catch-me-if-you-can1" + "function": Catch-me-if-you-can1 }, "config": { - "function": "Catch-me-if-you-can2" - }, - "export": { - "function": "Catch-me-if-you-can3" + "function": Catch-me-if-you-can2 } } ], @@ -599,23 +576,6 @@ mod tests { } } }"#; - let manifest: ForgeManifest = serde_json::from_str(json).unwrap(); - assert_eq!(manifest.modules.macros.len(), 1); - assert_eq!( - manifest.modules.macros[0].common_keys.function, - "Catch-me-if-you-can0" - ); - if let Some(func) = manifest.modules.macros[0].common_keys.resolver { - assert_eq!(func, "Catch-me-if-you-can1"); - } - - if let Some(func) = manifest.modules.macros[0].config { - assert_eq!(func, "Catch-me-if-you-can2"); - } - - if let Some(func) = manifest.modules.macros[0].export { - assert_eq!(func, "Catch-me-if-you-can3"); - } } } From 216de88ef74ed5d19d1255331cd460ca226a5ed0 Mon Sep 17 00:00:00 2001 From: Alina Wang Date: Wed, 10 Jan 2024 18:13:11 -0800 Subject: [PATCH 303/517] added more todo statements and cleaned up struct definitions --- crates/forge_loader/src/manifest.rs | 52 +++++++++-------------------- crates/fsrt/src/main.rs | 2 -- 2 files changed, 15 insertions(+), 39 deletions(-) diff --git a/crates/forge_loader/src/manifest.rs b/crates/forge_loader/src/manifest.rs index a47b2338..7250233e 100644 --- a/crates/forge_loader/src/manifest.rs +++ b/crates/forge_loader/src/manifest.rs @@ -18,7 +18,7 @@ struct AuthProviders<'a> { #[serde(borrow)] auth: Vec<&'a str>, } -// Maps the Functions Module in common Modules +// Maps the Functions Module in common Modules #[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] pub struct FunctionMod<'a> { key: &'a str, @@ -27,54 +27,30 @@ pub struct FunctionMod<'a> { providers: Option>, } -// Abstracting away key, function, and resolver into a single struct for reuse whoo! +// Modified #[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] -struct CommonKey<'a> { - key: &'a str, +struct ModInfo<'a> { function: &'a str, - resolver: Option<&'a str>, -} - -#[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] -struct MacroMod<'a> { - #[serde(flatten, borrow)] - common_keys: CommonKey<'a>, - config: Option<&'a str>, - export: Option<&'a str>, -} - -#[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] -struct ContentByLineItem<'a> { - #[serde(flatten, borrow)] - common_keys: CommonKey<'a>, - #[serde(borrow)] - dynamic_properties: Option<&'a str>, } #[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] -struct IssueGlance<'a> { - #[serde(flatten, borrow)] - common_keys: CommonKey<'a>, - dynamic_properties: Option<&'a str>, -} -#[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] -struct AccessImportType<'a> { - #[serde(flatten, borrow)] - common_keys: CommonKey<'a>, - one_delete_import: Option<&'a str>, - start_import: Option<&'a str>, - stop_import: Option<&'a str>, - import_status: Option<&'a str>, +struct MacroMod<'a> { + #[serde(flatten, borrow)] + key: &'a str, + function: &'a str, + resolver: ModInfo<'a>, + config: ModInfo<'a>, + export: ModInfo<'a>, } -// WebTrigger => RawTrigger; WHY IS THIS NAMED DIFFERENTLY !? WHO CHANGED NAMES +// WebTrigger => RawTrigger; WHY IS THIS NAMED DIFFERENTLY !? WHO CHANGED NAMES #[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] struct RawTrigger<'a> { key: &'a str, function: &'a str, } -// Trigger => EventTriger; WHY IS THIS NAMED DIFFERENTLY !? WHO CHANGED NAMES +// Trigger => EventTriger; WHY IS THIS NAMED DIFFERENTLY !? WHO CHANGED NAMES #[derive(Debug, Clone, PartialEq, Eq, Deserialize)] struct EventTrigger<'a> { #[serde(flatten, borrow)] @@ -91,7 +67,7 @@ enum Interval { Week, } -// Thank you to whomeever kept this one the same. T.T +// Thank you to whomeever kept this one the same. T.T #[derive(Debug, Clone, PartialEq, Eq, Deserialize)] struct ScheduledTrigger<'a> { #[serde(flatten, borrow)] @@ -99,6 +75,7 @@ struct ScheduledTrigger<'a> { interval: Interval, } +// compass DataProvider module #[derive(Debug, Clone, PartialEq, Eq, Deserialize)] struct DataProvider<'a> { key: &'a str, @@ -106,6 +83,7 @@ struct DataProvider<'a> { callback: Callback<'a>, } +// Struct for mapping functions defined one more level in whose value is {function: string}. Used to represent resolver types. #[derive(Debug, Clone, PartialEq, Eq, Deserialize)] pub struct Callback<'a> { pub function: &'a str, diff --git a/crates/fsrt/src/main.rs b/crates/fsrt/src/main.rs index 8f9654ae..ea39b250 100644 --- a/crates/fsrt/src/main.rs +++ b/crates/fsrt/src/main.rs @@ -352,8 +352,6 @@ fn scan_directory(dir: PathBuf, function: Option<&str>, opts: Opts) -> Result<() for func in &proj.funcs { // TODO: Update operations in for loop to scan functions. // idea: iterate over each func which should be struct that tracks the function to be scanned. And performs scans according to bool. -<<<<<<< HEAD -<<<<<<< HEAD match *func { FunctionTy::Invokable((ref func, ref path, _, def)) => { let mut runner = DefintionAnalysisRunner::new(); From 3926803e3e50de35f82a6b0500073fc44653a031 Mon Sep 17 00:00:00 2001 From: Alina Wang Date: Wed, 10 Jan 2024 18:13:29 -0800 Subject: [PATCH 304/517] created iterators of all known user non-invokable functions to be scanned. And updated alternate_functions to track using entrypoints struct. Todo: capture additional entrypoints for user invokable functions(like macros), write tests + debug, and change main.rs to work with new data struct --- crates/forge_loader/src/manifest.rs | 196 +++++++++++++++++++++------- 1 file changed, 150 insertions(+), 46 deletions(-) diff --git a/crates/forge_loader/src/manifest.rs b/crates/forge_loader/src/manifest.rs index 7250233e..ca4975f5 100644 --- a/crates/forge_loader/src/manifest.rs +++ b/crates/forge_loader/src/manifest.rs @@ -11,6 +11,7 @@ use forge_utils::FxHashMap; use itertools::{Either, Itertools}; use serde::Deserialize; use serde_json::map::Entry; +use serde_json::map::Entry; use tracing::trace; #[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] @@ -77,7 +78,7 @@ struct ScheduledTrigger<'a> { // compass DataProvider module #[derive(Debug, Clone, PartialEq, Eq, Deserialize)] -struct DataProvider<'a> { +pub struct DataProvider<'a> { key: &'a str, #[serde(flatten, borrow)] callback: Callback<'a>, @@ -91,7 +92,7 @@ pub struct Callback<'a> { // Struct for Custom field Module. Check that search suggestion gets read in correctly. #[derive(Debug, Clone, PartialEq, Eq, Deserialize)] -struct CustomField<'a> { +pub struct CustomField<'a> { #[serde(flatten, borrow)] key: &'a str, search_suggestion: &'a str, @@ -99,14 +100,14 @@ struct CustomField<'a> { #[derive(Debug, Clone, PartialEq, Eq, Deserialize)] -struct UiModificatons<'a> { +pub struct UiModificatons<'a> { #[serde(flatten, borrow)] key: &'a str, resolver: Callback<'a>, } #[derive(Debug, Clone, PartialEq, Eq, Deserialize)] -struct WorkflowValidator<'a> { +pub struct WorkflowValidator<'a> { #[serde(flatten, borrow)] key: &'a str, functon: &'a str, @@ -114,10 +115,10 @@ struct WorkflowValidator<'a> { } #[derive(Debug, Clone, PartialEq, Eq, Deserialize)] -struct WorkflowPostFunction<'a> { +pub struct WorkflowPostFunction<'a> { #[serde(flatten, borrow)] key: &'a str, - functon: &'a str, + function: &'a str, } // Add more structs here for deserializing forge modules @@ -247,9 +248,9 @@ pub enum FunctionTy { } // Struct used for tracking what scan a funtion requires. -#[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] +#[derive(Debug, Clone, PartialEq, Eq, Deserialize)] pub struct Entrypoints<'a> { - function:Vec>, + function: &'a str, invokable: bool, web_trigger: bool, } @@ -293,64 +294,167 @@ impl AsRef for FunctionTy { impl<'a> ForgeModules<'a> { - +// TODO: fix return type whop pub fn into_analyzable_functions( mut self, - ) -> impl Iterator>> { + ) -> impl Iterator> { // number of webtriggers are usually low, so it's better to just sort them and reuse self.webtriggers .sort_unstable_by_key(|trigger| trigger.function); + // Get all the Triggers and represent them as a new struct thing where "webtrigger" attribute is true + // for all trigger things + let web = self.webtriggers.iter().for_each(|webtriggers| { + Entrypoints { + function: webtriggers.function, + invokable: false, + web_trigger: true, + }; + }); + let event = self.event_triggers.iter().for_each(|event_triggers| { + Entrypoints { + function: event_triggers.raw.function, + invokable: false, + web_trigger: true, + }; + }); + let schedule = self.scheduled_triggers.iter().for_each(|schedule_triggers| { + Entrypoints { + function: schedule_triggers.raw.function, + invokable: false, + web_trigger: true, + }; + }); - let mut ignored_functions: BTreeSet<_> = self - .scheduled_triggers - .into_iter() - .map(|trigger| trigger.raw.function) - .chain( - self.event_triggers - .into_iter() - .map(|trigger| trigger.raw.function), - ) - .collect(); + // create arrays representing functions that expose user non-invokable functions + let consumer = self.consumers.iter().for_each(|consumers| { + Entrypoints { + function: consumers.resolver.function, + invokable: true, + web_trigger: false, + }; + }); + let data_provider = self.data_provider.iter().for_each(|dataprovider| { + Entrypoints { + function: dataprovider.callback.function, + invokable: true, + web_trigger: false, + }; + }); + + let custom_field = self.custom_field.iter().for_each(|customfield| { + Entrypoints { + function: customfield.search_suggestion, + invokable: true, + web_trigger: false, + }; + }); + + let ui_mod = self.ui_modifications.iter().for_each(|ui| { + Entrypoints { + function: ui.resolver.function, + invokable: true, + web_trigger: false, + }; + }); + + let workflow_validator = self.workflow_validator.iter().for_each(|validator| { + Entrypoints { + function: validator.resolver.function, + invokable: true, + web_trigger: false, + }; + }); + + let workflow_post = self.workflow_post_function.iter().for_each(|post_function| { + Entrypoints { + function: post_function.function, + invokable: true, + web_trigger: false, + }; + }); + + + // let user_invokable = self.extra.into_values().flatten().into_iter().for_each(|invokable| { + + // if invokable.resolver != None { + // Entrypoints { + // function: invokable.resolver, + // invokable: true, + // web_trigger: false, + // }; + + // } + // Entrypoints { + // function: invokable.function, + // invokable: true, + // web_trigger: false, + // }; + // }); + + // let mut ignored_functions: BTreeSet<_> = self + // .scheduled_triggers + // .into_iter() + // .map(|trigger| trigger.raw.function) + // .chain( + // self.event_triggers + // .into_iter() + // .map(|trigger| trigger.raw.function), + // ) + // .collect(); + + // get array for user invokable module functions // make alternate_functions all user-invokable functions - let mut alternate_functions: Vec<&str> = Vec::new(); + let mut alternate_functions = Vec::new(); for module in self.extra.into_values().flatten() { - alternate_functions.extend(module.function); + if let Some(mod_function) = module.function { + alternate_functions.push(Entrypoints { + function: mod_function, + invokable: true, + web_trigger: false + }); + } + if let Some(resolver) = module.resolver { - alternate_functions.push(resolver.function); + alternate_functions.push(Entrypoints { + function: resolver.function, + invokable: true, + web_trigger: false + }); } } + workflow_post // Iterate over Consumers and check that if consumers isn't in alternate functions, add consumer funtion to be ignored // assuming that alternate functions already has all user invokable functions. - self.consumers.iter().for_each(|consumer| { - if !alternate_functions.contains(&consumer.resolver.function) { - ignored_functions.insert(consumer.resolver.function); - } - }); + // self.consumers.iter().for_each(|consumer| { + // if !alternate_functions.contains(&consumer.resolver.function) { + // ignored_functions.insert(consumer.resolver.function); + // } + // }); // TODO: Iterate through all deserialized entrypoints that are represented as a struct when deserialized // Update Struct values to be true or not. If any part true, then scan. // This solution fixes the problem that we only check known user invokable modules and also acccounts for non-invokable module entry points // return non-user invokable functions - self.functions.into_iter().filter_map(move |func| { - if ignored_functions.contains(&func.key) { - return None; - } - Some( - if self - .webtriggers - .binary_search_by_key(&func.key, |trigger| trigger.function) - .is_ok() - { - FunctionTy::WebTrigger(func) - } else { - FunctionTy::Invokable(func) - }, - ) - }) + // self.functions.into_iter().filter_map(move |func| { + // if ignored_functions.contains(&func.key) { + // return None; + // } + // Some( + // if self + // .webtriggers + // .binary_search_by_key(&func.key, |trigger| trigger.function) + // .is_ok() + // { + // FunctionTy::WebTrigger(func) + // } else { + // FunctionTy::Invokable(func) + // }, + // ) + // }) } } @@ -466,8 +570,8 @@ mod tests { assert_eq!(manifest.app.name, Some("My App")); assert_eq!(manifest.app.id, "my-app"); assert_eq!(manifest.modules.macros.len(), 1); - assert_eq!(manifest.modules.macros[0].common_keys.key, "My Macro"); - // assert_eq!(manifest.modules.macros[0].function, "my-macro"); + assert_eq!(manifest.modules.macros[0].key, "My Macro"); + assert_eq!(manifest.modules.macros[0].function, "my-macro"); assert_eq!(manifest.modules.functions.len(), 1); assert_eq!( manifest.modules.functions[0], From 5a206c2920dfd41a55708f2535a4e822dfc804d2 Mon Sep 17 00:00:00 2001 From: Alina Wang Date: Wed, 10 Jan 2024 18:13:29 -0800 Subject: [PATCH 305/517] fixed all the red squigglies --- crates/forge_loader/src/manifest.rs | 149 ++++++++++++++++------------ 1 file changed, 85 insertions(+), 64 deletions(-) diff --git a/crates/forge_loader/src/manifest.rs b/crates/forge_loader/src/manifest.rs index ca4975f5..1df9da19 100644 --- a/crates/forge_loader/src/manifest.rs +++ b/crates/forge_loader/src/manifest.rs @@ -294,85 +294,106 @@ impl AsRef for FunctionTy { impl<'a> ForgeModules<'a> { -// TODO: fix return type whop +// TODO: function returns iterator where each item is some specified type. pub fn into_analyzable_functions( mut self, - ) -> impl Iterator> { + ) { // number of webtriggers are usually low, so it's better to just sort them and reuse - self.webtriggers - .sort_unstable_by_key(|trigger| trigger.function); + // self.webtriggers + // .sort_unstable_by_key(|trigger| trigger.function); // Get all the Triggers and represent them as a new struct thing where "webtrigger" attribute is true // for all trigger things - let web = self.webtriggers.iter().for_each(|webtriggers| { - Entrypoints { - function: webtriggers.function, - invokable: false, - web_trigger: true, - }; + + let mut functions_to_scan = Vec::new(); + self.webtriggers.iter().for_each(|webtriggers| { + functions_to_scan.push( + Entrypoints { + function: webtriggers.function, + invokable: false, + web_trigger: true, + } + ); + }); - let event = self.event_triggers.iter().for_each(|event_triggers| { - Entrypoints { - function: event_triggers.raw.function, - invokable: false, - web_trigger: true, - }; + self.event_triggers.iter().for_each(|event_triggers| { + functions_to_scan.push( + Entrypoints { + function: event_triggers.raw.function, + invokable: false, + web_trigger: true, + } + ); }); - let schedule = self.scheduled_triggers.iter().for_each(|schedule_triggers| { - Entrypoints { - function: schedule_triggers.raw.function, - invokable: false, - web_trigger: true, - }; + self.scheduled_triggers.iter().for_each(|schedule_triggers| { + functions_to_scan.push( + Entrypoints { + function: schedule_triggers.raw.function, + invokable: false, + web_trigger: true, + } + ) }); // create arrays representing functions that expose user non-invokable functions - let consumer = self.consumers.iter().for_each(|consumers| { - Entrypoints { - function: consumers.resolver.function, - invokable: true, - web_trigger: false, - }; + self.consumers.iter().for_each(|consumers| { + functions_to_scan.push( + Entrypoints { + function: consumers.resolver.function, + invokable: true, + web_trigger: false, + } + ) }); - let data_provider = self.data_provider.iter().for_each(|dataprovider| { - Entrypoints { - function: dataprovider.callback.function, - invokable: true, - web_trigger: false, - }; + self.data_provider.iter().for_each(|dataprovider| { + functions_to_scan.push( + Entrypoints { + function: dataprovider.callback.function, + invokable: true, + web_trigger: false, + } + ) }); - let custom_field = self.custom_field.iter().for_each(|customfield| { - Entrypoints { - function: customfield.search_suggestion, - invokable: true, - web_trigger: false, - }; + self.custom_field.iter().for_each(|customfield| { + functions_to_scan.push( + Entrypoints { + function: customfield.search_suggestion, + invokable: true, + web_trigger: false, + } + ) }); - let ui_mod = self.ui_modifications.iter().for_each(|ui| { - Entrypoints { - function: ui.resolver.function, - invokable: true, - web_trigger: false, - }; + self.ui_modifications.iter().for_each(|ui| { + functions_to_scan.push( + Entrypoints { + function: ui.resolver.function, + invokable: true, + web_trigger: false, + } + ) }); - let workflow_validator = self.workflow_validator.iter().for_each(|validator| { - Entrypoints { - function: validator.resolver.function, - invokable: true, - web_trigger: false, - }; + self.workflow_validator.iter().for_each(|validator| { + functions_to_scan.push( + Entrypoints { + function: validator.resolver.function, + invokable: true, + web_trigger: false, + } + ) }); - - let workflow_post = self.workflow_post_function.iter().for_each(|post_function| { - Entrypoints { - function: post_function.function, - invokable: true, - web_trigger: false, - }; + + self.workflow_post_function.iter().for_each(|post_function| { + functions_to_scan.push( + Entrypoints { + function: post_function.function, + invokable: true, + web_trigger: false, + } + ) }); @@ -406,10 +427,10 @@ impl<'a> ForgeModules<'a> { // get array for user invokable module functions // make alternate_functions all user-invokable functions - let mut alternate_functions = Vec::new(); + // let mut alternate_functions = Vec::new(); for module in self.extra.into_values().flatten() { if let Some(mod_function) = module.function { - alternate_functions.push(Entrypoints { + functions_to_scan.push(Entrypoints { function: mod_function, invokable: true, web_trigger: false @@ -417,15 +438,15 @@ impl<'a> ForgeModules<'a> { } if let Some(resolver) = module.resolver { - alternate_functions.push(Entrypoints { + functions_to_scan.push(Entrypoints { function: resolver.function, invokable: true, web_trigger: false }); } } - - workflow_post + functions_to_scan.into_iter(); + // alternate_functions.into_iter(); // Iterate over Consumers and check that if consumers isn't in alternate functions, add consumer funtion to be ignored // assuming that alternate functions already has all user invokable functions. // self.consumers.iter().for_each(|consumer| { From a6b8c9608a940a446777f61698b33274f56b7fdf Mon Sep 17 00:00:00 2001 From: Alina Wang Date: Wed, 10 Jan 2024 18:13:29 -0800 Subject: [PATCH 306/517] added user-invokable module with additional entry points for macros. Adding more modules. Writing tests next? --- crates/forge_loader/src/manifest.rs | 56 ++++++++++++++--------------- 1 file changed, 28 insertions(+), 28 deletions(-) diff --git a/crates/forge_loader/src/manifest.rs b/crates/forge_loader/src/manifest.rs index 1df9da19..bb64246a 100644 --- a/crates/forge_loader/src/manifest.rs +++ b/crates/forge_loader/src/manifest.rs @@ -296,7 +296,7 @@ impl<'a> ForgeModules<'a> { // TODO: function returns iterator where each item is some specified type. pub fn into_analyzable_functions( - mut self, + self, ) { // number of webtriggers are usually low, so it's better to just sort them and reuse // self.webtriggers @@ -396,35 +396,35 @@ impl<'a> ForgeModules<'a> { ) }); - - // let user_invokable = self.extra.into_values().flatten().into_iter().for_each(|invokable| { + // get user invokable modules that have additional exposure endpoints. + // ie macros has config and export fields on top of resolver fields that are functions + for macros in self.macros { + if let Some(resolver)= Some(macros.resolver) { + functions_to_scan.push(Entrypoints { + function: resolver.function, + invokable: true, + web_trigger: false + }) + } - // if invokable.resolver != None { - // Entrypoints { - // function: invokable.resolver, - // invokable: true, - // web_trigger: false, - // }; + if let Some(config)= Some(macros.config) { + functions_to_scan.push(Entrypoints { + function: config.function, + invokable: true, + web_trigger: false + }) + } + if let Some(export)= Some(macros.export) { + functions_to_scan.push(Entrypoints { + function: export.function, + invokable: true, + web_trigger: false + }) + } - // } - // Entrypoints { - // function: invokable.function, - // invokable: true, - // web_trigger: false, - // }; - // }); - - // let mut ignored_functions: BTreeSet<_> = self - // .scheduled_triggers - // .into_iter() - // .map(|trigger| trigger.raw.function) - // .chain( - // self.event_triggers - // .into_iter() - // .map(|trigger| trigger.raw.function), - // ) - // .collect(); + } + // get array for user invokable module functions // make alternate_functions all user-invokable functions // let mut alternate_functions = Vec::new(); @@ -443,7 +443,7 @@ impl<'a> ForgeModules<'a> { invokable: true, web_trigger: false }); - } + } } functions_to_scan.into_iter(); // alternate_functions.into_iter(); From 8348195e412d2bbc79330a33bc7283b51c7ee007 Mon Sep 17 00:00:00 2001 From: Alina Wang Date: Wed, 10 Jan 2024 18:13:29 -0800 Subject: [PATCH 307/517] added test to deserialize macros with additional entry points like config and export --- crates/forge_loader/src/manifest.rs | 43 +++++++++++++++++++++++------ 1 file changed, 34 insertions(+), 9 deletions(-) diff --git a/crates/forge_loader/src/manifest.rs b/crates/forge_loader/src/manifest.rs index bb64246a..0efe2cb5 100644 --- a/crates/forge_loader/src/manifest.rs +++ b/crates/forge_loader/src/manifest.rs @@ -36,12 +36,15 @@ struct ModInfo<'a> { #[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] struct MacroMod<'a> { - #[serde(flatten, borrow)] + // #[serde(flatten, borrow)] key: &'a str, function: &'a str, - resolver: ModInfo<'a>, - config: ModInfo<'a>, - export: ModInfo<'a>, + #[serde(borrow)] + resolver: Option>, + #[serde(borrow)] + config: Option>, + #[serde(borrow)] + export: Option>, } // WebTrigger => RawTrigger; WHY IS THIS NAMED DIFFERENTLY !? WHO CHANGED NAMES @@ -399,7 +402,7 @@ impl<'a> ForgeModules<'a> { // get user invokable modules that have additional exposure endpoints. // ie macros has config and export fields on top of resolver fields that are functions for macros in self.macros { - if let Some(resolver)= Some(macros.resolver) { + if let Some(resolver)= macros.resolver { functions_to_scan.push(Entrypoints { function: resolver.function, invokable: true, @@ -407,14 +410,14 @@ impl<'a> ForgeModules<'a> { }) } - if let Some(config)= Some(macros.config) { + if let Some(config)= macros.config { functions_to_scan.push(Entrypoints { function: config.function, invokable: true, web_trigger: false }) } - if let Some(export)= Some(macros.export) { + if let Some(export)= macros.export { functions_to_scan.push(Entrypoints { function: export.function, invokable: true, @@ -641,11 +644,15 @@ mod tests { { "key": "my-macro", "title": "My Macro", + "function": "Catch-me-if-you-can0", "resolver": { - "function": Catch-me-if-you-can1 + "function": "Catch-me-if-you-can1" }, "config": { - "function": Catch-me-if-you-can2 + "function": "Catch-me-if-you-can2" + }, + "export": { + "function": "Catch-me-if-you-can3" } } ], @@ -679,6 +686,24 @@ mod tests { } } }"#; + let manifest: ForgeManifest = serde_json::from_str(json).unwrap(); + assert_eq!(manifest.modules.macros.len(), 1); + assert_eq!(manifest.modules.macros[0].function, "Catch-me-if-you-can0"); + + if let Some(func) = &manifest.modules.macros[0].resolver { + assert_eq!(func.function, "Catch-me-if-you-can1"); + + } + + if let Some(func) = &manifest.modules.macros[0].config { + assert_eq!(func.function, "Catch-me-if-you-can2"); + + } + + if let Some(func) = &manifest.modules.macros[0].export { + assert_eq!(func.function, "Catch-me-if-you-can3"); + + } } } From b52dfade3ebb2c829b5e36176f6ba874744d9928 Mon Sep 17 00:00:00 2001 From: Alina Wang Date: Wed, 10 Jan 2024 18:13:53 -0800 Subject: [PATCH 308/517] edited main to iterate over vector of functions. TODO: edit add_funcs in main.rs and test into_analyzble_function use case in main.rs --- crates/forge_loader/src/manifest.rs | 45 +----- crates/fsrt/src/main.rs | 237 ++++------------------------ 2 files changed, 38 insertions(+), 244 deletions(-) diff --git a/crates/forge_loader/src/manifest.rs b/crates/forge_loader/src/manifest.rs index 0efe2cb5..6a4dccc6 100644 --- a/crates/forge_loader/src/manifest.rs +++ b/crates/forge_loader/src/manifest.rs @@ -253,9 +253,9 @@ pub enum FunctionTy { // Struct used for tracking what scan a funtion requires. #[derive(Debug, Clone, PartialEq, Eq, Deserialize)] pub struct Entrypoints<'a> { - function: &'a str, - invokable: bool, - web_trigger: bool, + pub function: &'a str, + pub invokable: bool, + pub web_trigger: bool, } // Helper functions that help filter out which functions are what. @@ -298,9 +298,9 @@ impl AsRef for FunctionTy { impl<'a> ForgeModules<'a> { // TODO: function returns iterator where each item is some specified type. - pub fn into_analyzable_functions( + pub fn into_analyzable_functions ( self, - ) { + ) -> Vec>{ // number of webtriggers are usually low, so it's better to just sort them and reuse // self.webtriggers // .sort_unstable_by_key(|trigger| trigger.function); @@ -426,11 +426,9 @@ impl<'a> ForgeModules<'a> { } } - // get array for user invokable module functions // make alternate_functions all user-invokable functions - // let mut alternate_functions = Vec::new(); for module in self.extra.into_values().flatten() { if let Some(mod_function) = module.function { functions_to_scan.push(Entrypoints { @@ -448,37 +446,8 @@ impl<'a> ForgeModules<'a> { }); } } - functions_to_scan.into_iter(); - // alternate_functions.into_iter(); - // Iterate over Consumers and check that if consumers isn't in alternate functions, add consumer funtion to be ignored - // assuming that alternate functions already has all user invokable functions. - // self.consumers.iter().for_each(|consumer| { - // if !alternate_functions.contains(&consumer.resolver.function) { - // ignored_functions.insert(consumer.resolver.function); - // } - // }); - - // TODO: Iterate through all deserialized entrypoints that are represented as a struct when deserialized - // Update Struct values to be true or not. If any part true, then scan. - // This solution fixes the problem that we only check known user invokable modules and also acccounts for non-invokable module entry points - - // return non-user invokable functions - // self.functions.into_iter().filter_map(move |func| { - // if ignored_functions.contains(&func.key) { - // return None; - // } - // Some( - // if self - // .webtriggers - // .binary_search_by_key(&func.key, |trigger| trigger.function) - // .is_ok() - // { - // FunctionTy::WebTrigger(func) - // } else { - // FunctionTy::Invokable(func) - // }, - // ) - // }) + + return functions_to_scan; } } diff --git a/crates/fsrt/src/main.rs b/crates/fsrt/src/main.rs index ea39b250..89e2afcf 100644 --- a/crates/fsrt/src/main.rs +++ b/crates/fsrt/src/main.rs @@ -17,11 +17,7 @@ use std::{ use clap::{Parser, ValueHint}; use miette::{IntoDiagnostic, Result}; -<<<<<<< HEAD use serde_json::map::Entry; ->>>>>>> cd2ed7b (edited main to iterate over vector of functions. TODO: edit add_funcs in main.rs and test into_analyzble_function use case in main.rs) -======= ->>>>>>> 29c38d6 (modified into_analyzable_functions with Josh and implementation in main.rs) use swc_core::{ common::{Globals, Mark, SourceMap, GLOBALS}, ecma::{ @@ -47,7 +43,7 @@ use forge_analyzer::{ resolver::resolve_calls, }; -use forge_loader::manifest::{Entrypoint, ForgeManifest, Resolved}; +use forge_loader::manifest::{ForgeManifest, FunctionRef, FunctionTy, Entrypoints}; use walkdir::WalkDir; #[derive(Parser, Debug)] @@ -117,7 +113,7 @@ struct ForgeProject<'a> { sm: Arc, ctx: AppCtx, env: Environment, - funcs: Vec>, + funcs: Vec>, opts: Opts, >>>>>>> cd2ed7b (edited main to iterate over vector of functions. TODO: edit add_funcs in main.rs and test into_analyzble_function use case in main.rs) } @@ -167,21 +163,16 @@ impl<'a> ForgeProject<'a> { ctx, env, funcs: vec![], + opts: Opts::default(), } } - // TODO: edit to work with new iterator that not FUNCTIONTY - fn add_funcs>>(&mut self, iter: I) { - self.funcs.extend(iter.into_iter().filter_map(|entrypoint| { - let (func_name, path) = entrypoint.function.into_func_path(); - let module = self.ctx.modid_from_path(&path)?; - let def_id = self.env.module_export(module, func_name)?; - Some(ResolvedEntryPoint { - func_name, - path, - module, - def_id, - invokable: entrypoint.invokable, - webtrigger: entrypoint.web_trigger, +// TODO: edit to work with new iterator that not FUNCTIONTY + fn add_funcs<'a, I: IntoIterator>>(&mut self, iter: I) { + self.funcs.extend(iter.into_iter().flat_map(|ftype| { + ftype.sequence(|(func_name, path)| { + let modid = self.ctx.modid_from_path(&path)?; + let func = self.env.module_export(modid, func_name)?; + Some((func_name.to_owned(), path, modid, func)) }) })); } @@ -249,17 +240,14 @@ fn scan_directory(dir: PathBuf, function: Option<&str>, opts: Opts) -> Result<() }); let run_permission_checker = opts.check_permissions && !transpiled_async; - let funcrefs = manifest - .modules - .into_analyzable_functions() - .flat_map(|entrypoint| { - Ok::<_, forge_loader::Error>(Entrypoint { - function: entrypoint.function.try_resolve(&paths, &dir)?, - invokable: entrypoint.invokable, - web_trigger: entrypoint.web_trigger, - }) - }); - + let funcrefs = &manifest.modules.into_analyzable_functions(); + + // .flat_map(|f| { + // f.sequence(|fmod| { + // let resolved_func = FunctionRef::try_from(fmod)?.try_resolve(&paths, &dir)?; + // Ok::<_, forge_loader::Error>(resolved_func.into_func_path()) + // }) + // }); let src_root = dir.join("src"); let mut proj = ForgeProject::with_files_and_sourceroot(src_root, paths.clone(), secret_packages); @@ -352,120 +340,6 @@ fn scan_directory(dir: PathBuf, function: Option<&str>, opts: Opts) -> Result<() for func in &proj.funcs { // TODO: Update operations in for loop to scan functions. // idea: iterate over each func which should be struct that tracks the function to be scanned. And performs scans according to bool. - match *func { - FunctionTy::Invokable((ref func, ref path, _, def)) => { - let mut runner = DefintionAnalysisRunner::new(); - debug!("checking Invokable {func} at {path:?}"); - if let Err(err) = defintion_analysis_interp.run_checker( - def, - &mut runner, - path.clone(), - func.clone(), - ) { - warn!("error while getting definition analysis {func} in {path:?}: {err}"); - } - let mut checker = AuthZChecker::new(); - debug!("Authorization Scaner on Invokable FunctionTy: checking {func} at {path:?}"); - if let Err(err) = interp.run_checker(def, &mut checker, path.clone(), func.clone()) - { - warn!("error while scanning {func} in {path:?}: {err}"); - } - reporter.add_vulnerabilities(checker.into_vulns()); - - let mut checker2 = SecretChecker::new(); - secret_interp.value_manager.varid_to_value = defintion_analysis_interp.get_defs(); - secret_interp.value_manager.defid_to_value = defintion_analysis_interp - .value_manager - .defid_to_value - .clone(); - debug!("Secret Scanner on Invokable FunctionTy: checking {func} at {path:?}"); - if let Err(err) = - secret_interp.run_checker(def, &mut checker2, path.clone(), func.clone()) - { - warn!("error while scanning {func} in {path:?}: {err}"); - } - reporter.add_vulnerabilities(checker2.into_vulns()); - - debug!("Permission Scanners on Invokable FunctionTy: checking {func} at {path:?}"); - if run_permission_checker { - perm_interp.value_manager.varid_to_value = defintion_analysis_interp.get_defs(); - perm_interp.value_manager.defid_to_value = defintion_analysis_interp - .value_manager - .defid_to_value - .clone(); - let mut checker2 = PermissionChecker::new(); - if let Err(err) = - perm_interp.run_checker(def, &mut checker2, path.clone(), func.clone()) - { - warn!("error while scanning {func} in {path:?}: {err}"); - } - } - pp_interp.value_manager.varid_to_value = defintion_analysis_interp.get_defs(); - pp_interp.value_manager.defid_to_value = defintion_analysis_interp - .value_manager - .defid_to_value - .clone(); - if let Err(e) = pp_interp.run_checker( - def, - &mut PrototypePollutionChecker, - path.clone(), - func.clone(), - ) { - warn!("error while scanning {func} in {path:?}: {e}"); - } - } - FunctionTy::WebTrigger((ref func, ref path, _, def)) => { - let mut runner = DefintionAnalysisRunner::new(); - debug!("checking Web Trigger {func} at {path:?}"); - if let Err(err) = defintion_analysis_interp.run_checker( - def, - &mut runner, - path.clone(), - func.clone(), - ) { - warn!("error while getting definition analysis {func} in {path:?}: {err}"); - } - - let mut checker2 = SecretChecker::new(); - secret_interp.value_manager.varid_to_value = defintion_analysis_interp.get_defs(); - secret_interp.value_manager.defid_to_value = defintion_analysis_interp - .value_manager - .defid_to_value - .clone(); - debug!("Secret Scanner on Web Triggers: checking {func} at {path:?}"); - if let Err(err) = - secret_interp.run_checker(def, &mut checker2, path.clone(), func.clone()) - { - warn!("error while scanning {func} in {path:?}: {err}"); - } - reporter.add_vulnerabilities(checker2.into_vulns()); - - let mut checker = AuthenticateChecker::new(); - debug!("Authentication Checker on Web Triggers: checking webtrigger {func} at {path:?}"); - if let Err(err) = - authn_interp.run_checker(def, &mut checker, path.clone(), func.clone()) - { - warn!("error while scanning {func} in {path:?}: {err}"); - } - reporter.add_vulnerabilities(checker.into_vulns()); - - debug!( - "Permission Checker on Web Triggers: checking webtrigger {func} at {path:?}" - ); - if run_permission_checker { - perm_interp.value_manager.varid_to_value = defintion_analysis_interp.get_defs(); - perm_interp.value_manager.defid_to_value = defintion_analysis_interp - .value_manager - .defid_to_value - .clone(); - let mut checker2 = PermissionChecker::new(); - if let Err(err) = - perm_interp.run_checker(def, &mut checker2, path.clone(), func.clone()) - { - warn!("error while scanning {func} in {path:?}: {err}"); - } - } -======= // match *func { // FunctionTy::Invokable((ref func, ref path, _, def)) => { // let mut checker = AuthZChecker::new(); @@ -487,78 +361,29 @@ fn scan_directory(dir: PathBuf, function: Option<&str>, opts: Opts) -> Result<() // reporter.add_vulnerabilities(checker.into_vulns()); // } // } -======= - // match *func { - // FunctionTy::Invokable((ref func, ref path, _, def)) => { - // let mut checker = AuthZChecker::new(); - // debug!("checking {func} at {path:?}"); - // if let Err(err) = interp.run_checker(def, &mut checker, path.clone(), func.clone()) - // { - // warn!("error while scanning {func} in {path:?}: {err}"); - // } - // reporter.add_vulnerabilities(checker.into_vulns()); - // } - // FunctionTy::WebTrigger((ref func, ref path, _, def)) => { - // let mut checker = AuthenticateChecker::new(); - // debug!("checking webtrigger {func} at {path:?}"); - // if let Err(err) = - // authn_interp.run_checker(def, &mut checker, path.clone(), func.clone()) - // { - // warn!("error while scanning {func} in {path:?}: {err}"); - // } - // reporter.add_vulnerabilities(checker.into_vulns()); - // } - // } ->>>>>>> 29c38d6 (modified into_analyzable_functions with Josh and implementation in main.rs) - // Get entrypoint value from tuple - // Logic for performing scans. - // If it's invokable, then run invokable scan. If web_trigger, then trigger scan. - // And if it's both, run both scans. if func.invokable { let mut checker = AuthZChecker::new(); - debug!("checking {:?} at {:?}", func.func_name, &func.path); - if let Err(err) = interp.run_checker( - func.def_id, - &mut checker, - func.path.clone(), - func.func_name.to_string(), - ) { - warn!( - "error while scanning {:?} in {:?}: {err}", - func.func_name, func.path, - ); + debug!("checking {func} at {path:?}"); + if let Err(err) = interp.run_checker(def, &mut checker, path.clone(), func.clone()) + { + warn!("error while scanning {func} in {path:?}: {err}"); } reporter.add_vulnerabilities(checker.into_vulns()); - } else if func.webtrigger { + + } else if func.web_trigger { let mut checker = AuthenticateChecker::new(); - debug!( - "checking webtrigger {:?} at {:?}", - func.func_name, func.path, - ); - if let Err(err) = authn_interp.run_checker( - func.def_id, - &mut checker, - func.path.clone(), - func.func_name.to_string(), - ) { - warn!( - "error while scanning {:?} in {:?}: {err}", - func.func_name, func.path, - ); + debug!("checking webtrigger {func} at {path:?}"); + if let Err(err) = + authn_interp.run_checker(def, &mut checker, path.clone(), func.clone()) + { + warn!("error while scanning {func} in {path:?}: {err}"); } reporter.add_vulnerabilities(checker.into_vulns()); + + } } - - if run_permission_checker { - if perm_interp.permissions.len() > 0 { - reporter.add_vulnerabilities( - vec![PermissionVuln::new(perm_interp.permissions)].into_iter(), - ); - } - } - let report = serde_json::to_string(&reporter.into_report()).into_diagnostic()?; debug!("On the debug layer: Writing Report"); match &opts.out { From 86efbe2428922f56d4399cc6eaaa9acc9bd18baf Mon Sep 17 00:00:00 2001 From: Alina Wang Date: Wed, 10 Jan 2024 18:13:53 -0800 Subject: [PATCH 309/517] added new modules for additional endpoints in user invokable modules --- crates/forge_loader/src/manifest.rs | 43 ++++++++++++++++++++++++++++- 1 file changed, 42 insertions(+), 1 deletion(-) diff --git a/crates/forge_loader/src/manifest.rs b/crates/forge_loader/src/manifest.rs index 6a4dccc6..b1e1fbe5 100644 --- a/crates/forge_loader/src/manifest.rs +++ b/crates/forge_loader/src/manifest.rs @@ -36,7 +36,6 @@ struct ModInfo<'a> { #[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] struct MacroMod<'a> { - // #[serde(flatten, borrow)] key: &'a str, function: &'a str, #[serde(borrow)] @@ -47,6 +46,42 @@ struct MacroMod<'a> { export: Option>, } +#[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] +struct ContentByLineItem<'a> { + key: &'a str, + function: &'a str, + #[serde(borrow)] + resolver: Option>, + #[serde(borrow)] + dynamic_properties: Option>, +} + +#[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] +struct IssueGlance<'a> { + key: &'a str, + function: &'a str, + #[serde(borrow)] + resolver: Option>, + #[serde(borrow)] + dynamic_properties: Option>, + +} + +#[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] +struct AccessImportType<'a> { + key: &'a str, + function: &'a str, + #[serde(borrow)] + one_delete_import: Option>, + #[serde(borrow)] + start_import: Option>, + #[serde(borrow)] + stop_import: Option>, + #[serde(borrow)] + import_status: Option>, + +} + // WebTrigger => RawTrigger; WHY IS THIS NAMED DIFFERENTLY !? WHO CHANGED NAMES #[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] struct RawTrigger<'a> { @@ -131,6 +166,12 @@ pub struct ForgeModules<'a> { macros: Vec>, #[serde(rename = "function", default, borrow)] pub functions: Vec>, + #[serde(rename = "contentByLineItem", default, borrow)] + content_by_line_item: Vec>, + #[serde(rename = "jira:issueGlance", default, borrow)] + issue_glance: Vec>, + #[serde(rename = "jira:accessImportType", default, borrow)] + access_import_type: Vec>, // deserializing non user-invocable modules #[serde(rename = "webtrigger", default, borrow)] webtriggers: Vec>, From 5ff8ae4d66e730618f013099bff5dcad6ed2eaeb Mon Sep 17 00:00:00 2001 From: Alina Wang Date: Wed, 10 Jan 2024 18:13:53 -0800 Subject: [PATCH 310/517] added methods for adding additional user invokable endpoints to vector for scanning. --- crates/forge_loader/src/manifest.rs | 88 +++++++++++++++++++++++++++++ 1 file changed, 88 insertions(+) diff --git a/crates/forge_loader/src/manifest.rs b/crates/forge_loader/src/manifest.rs index b1e1fbe5..ba72463a 100644 --- a/crates/forge_loader/src/manifest.rs +++ b/crates/forge_loader/src/manifest.rs @@ -467,6 +467,94 @@ impl<'a> ForgeModules<'a> { } } + + for contentitem in self.content_by_line_item { + functions_to_scan.push(Entrypoints { + function: contentitem.function, + invokable: true, + web_trigger: false + }); + if let Some(resolver)= contentitem.resolver { + functions_to_scan.push(Entrypoints { + function: resolver.function, + invokable: true, + web_trigger: false + }) + } + + if let Some(dynamic_properties)= contentitem.dynamic_properties { + functions_to_scan.push(Entrypoints { + function: dynamic_properties.function, + invokable: true, + web_trigger: false + }) + } + + } + + for issue in self.issue_glance { + functions_to_scan.push(Entrypoints { + function: issue.function, + invokable: true, + web_trigger: false + }); + if let Some(resolver)= issue.resolver { + functions_to_scan.push(Entrypoints { + function: resolver.function, + invokable: true, + web_trigger: false + }) + } + + if let Some(dynamic_properties)= issue.dynamic_properties { + functions_to_scan.push(Entrypoints { + function: dynamic_properties.function, + invokable: true, + web_trigger: false + }) + } + + } + + for access in self.access_import_type { + functions_to_scan.push(Entrypoints { + function: access.function, + invokable: true, + web_trigger: false + }); + if let Some(delete) = access.one_delete_import { + functions_to_scan.push(Entrypoints { + function: delete.function, + invokable: true, + web_trigger: false + }) + } + + if let Some(start)= access.start_import { + functions_to_scan.push(Entrypoints { + function: start.function, + invokable: true, + web_trigger: false + }) + } + + if let Some(stop)= access.stop_import { + functions_to_scan.push(Entrypoints { + function: stop.function, + invokable: true, + web_trigger: false + }) + } + + if let Some(status)= access.import_status { + functions_to_scan.push(Entrypoints { + function: status.function, + invokable: true, + web_trigger: false + }) + } + + } // get array for user invokable module functions // make alternate_functions all user-invokable functions From 26011e4d58f70906257be4a93fe3f641bd4eb1e3 Mon Sep 17 00:00:00 2001 From: Alina Wang Date: Wed, 10 Jan 2024 18:13:53 -0800 Subject: [PATCH 311/517] removed callback mod and abstracted structs to use MacroMod to represent {function: str} entry points. Added missing entrypoints on jira:customField and implmented scanning for those --- crates/forge_loader/src/manifest.rs | 53 ++++++++++++++++++++++------- 1 file changed, 41 insertions(+), 12 deletions(-) diff --git a/crates/forge_loader/src/manifest.rs b/crates/forge_loader/src/manifest.rs index ba72463a..fb6d9d5b 100644 --- a/crates/forge_loader/src/manifest.rs +++ b/crates/forge_loader/src/manifest.rs @@ -119,13 +119,7 @@ struct ScheduledTrigger<'a> { pub struct DataProvider<'a> { key: &'a str, #[serde(flatten, borrow)] - callback: Callback<'a>, -} - -// Struct for mapping functions defined one more level in whose value is {function: string}. Used to represent resolver types. -#[derive(Debug, Clone, PartialEq, Eq, Deserialize)] -pub struct Callback<'a> { - pub function: &'a str, + callback: ModInfo<'a>, } // Struct for Custom field Module. Check that search suggestion gets read in correctly. @@ -133,23 +127,28 @@ pub struct Callback<'a> { pub struct CustomField<'a> { #[serde(flatten, borrow)] key: &'a str, + // all attributes below involve function calls + value: &'a str, search_suggestion: &'a str, + function: &'a str, + edit: &'a str, + resolver: ModInfo<'a> } #[derive(Debug, Clone, PartialEq, Eq, Deserialize)] pub struct UiModificatons<'a> { - #[serde(flatten, borrow)] key: &'a str, - resolver: Callback<'a>, + #[serde(flatten, borrow)] + resolver: ModInfo<'a>, } #[derive(Debug, Clone, PartialEq, Eq, Deserialize)] pub struct WorkflowValidator<'a> { - #[serde(flatten, borrow)] key: &'a str, functon: &'a str, - resolver: Callback<'a> + #[serde(flatten, borrow)] + resolver: ModInfo<'a> } #[derive(Debug, Clone, PartialEq, Eq, Deserialize)] @@ -401,13 +400,43 @@ impl<'a> ForgeModules<'a> { }); self.custom_field.iter().for_each(|customfield| { + functions_to_scan.push( + Entrypoints { + function: customfield.value, + invokable: true, + web_trigger: false, + } + ); functions_to_scan.push( Entrypoints { function: customfield.search_suggestion, invokable: true, web_trigger: false, } - ) + ); + functions_to_scan.push( + Entrypoints { + function: customfield.function, + invokable: true, + web_trigger: false, + } + ); + functions_to_scan.push( + Entrypoints { + function: customfield.edit, + invokable: true, + web_trigger: false, + } + ); + + functions_to_scan.push( + Entrypoints { + function: customfield.resolver.function, + invokable: true, + web_trigger: false, + } + ); + }); self.ui_modifications.iter().for_each(|ui| { From 40856e1e85e7a19d0df0057e44fa3de013dcb04f Mon Sep 17 00:00:00 2001 From: Alina Wang Date: Wed, 10 Jan 2024 18:13:53 -0800 Subject: [PATCH 312/517] changed entrypoints to entrypoint. Working on other comments --- crates/forge_loader/src/manifest.rs | 82 ++++++++++++++++------------- crates/fsrt/src/main.rs | 37 +++++++------ 2 files changed, 65 insertions(+), 54 deletions(-) diff --git a/crates/forge_loader/src/manifest.rs b/crates/forge_loader/src/manifest.rs index fb6d9d5b..362d8075 100644 --- a/crates/forge_loader/src/manifest.rs +++ b/crates/forge_loader/src/manifest.rs @@ -145,9 +145,9 @@ pub struct UiModificatons<'a> { #[derive(Debug, Clone, PartialEq, Eq, Deserialize)] pub struct WorkflowValidator<'a> { - key: &'a str, - functon: &'a str, #[serde(flatten, borrow)] + key: &'a str, + function: &'a str, resolver: ModInfo<'a> } @@ -292,7 +292,7 @@ pub enum FunctionTy { // Struct used for tracking what scan a funtion requires. #[derive(Debug, Clone, PartialEq, Eq, Deserialize)] -pub struct Entrypoints<'a> { +pub struct Entrypoint<'a> { pub function: &'a str, pub invokable: bool, pub web_trigger: bool, @@ -340,10 +340,10 @@ impl<'a> ForgeModules<'a> { // TODO: function returns iterator where each item is some specified type. pub fn into_analyzable_functions ( self, - ) -> Vec>{ + ) -> Vec>{ // number of webtriggers are usually low, so it's better to just sort them and reuse - // self.webtriggers - // .sort_unstable_by_key(|trigger| trigger.function); + self.webtriggers.iter(). + .sort_unstable_by_key(|trigger| trigger.function); // Get all the Triggers and represent them as a new struct thing where "webtrigger" attribute is true // for all trigger things @@ -351,7 +351,7 @@ impl<'a> ForgeModules<'a> { let mut functions_to_scan = Vec::new(); self.webtriggers.iter().for_each(|webtriggers| { functions_to_scan.push( - Entrypoints { + Entrypoint { function: webtriggers.function, invokable: false, web_trigger: true, @@ -361,7 +361,7 @@ impl<'a> ForgeModules<'a> { }); self.event_triggers.iter().for_each(|event_triggers| { functions_to_scan.push( - Entrypoints { + Entrypoint { function: event_triggers.raw.function, invokable: false, web_trigger: true, @@ -370,7 +370,7 @@ impl<'a> ForgeModules<'a> { }); self.scheduled_triggers.iter().for_each(|schedule_triggers| { functions_to_scan.push( - Entrypoints { + Entrypoint { function: schedule_triggers.raw.function, invokable: false, web_trigger: true, @@ -381,7 +381,7 @@ impl<'a> ForgeModules<'a> { // create arrays representing functions that expose user non-invokable functions self.consumers.iter().for_each(|consumers| { functions_to_scan.push( - Entrypoints { + Entrypoint { function: consumers.resolver.function, invokable: true, web_trigger: false, @@ -391,7 +391,7 @@ impl<'a> ForgeModules<'a> { self.data_provider.iter().for_each(|dataprovider| { functions_to_scan.push( - Entrypoints { + Entrypoint { function: dataprovider.callback.function, invokable: true, web_trigger: false, @@ -401,28 +401,28 @@ impl<'a> ForgeModules<'a> { self.custom_field.iter().for_each(|customfield| { functions_to_scan.push( - Entrypoints { + Entrypoint { function: customfield.value, invokable: true, web_trigger: false, } ); functions_to_scan.push( - Entrypoints { + Entrypoint { function: customfield.search_suggestion, invokable: true, web_trigger: false, } ); functions_to_scan.push( - Entrypoints { + Entrypoint { function: customfield.function, invokable: true, web_trigger: false, } ); functions_to_scan.push( - Entrypoints { + Entrypoint { function: customfield.edit, invokable: true, web_trigger: false, @@ -430,7 +430,7 @@ impl<'a> ForgeModules<'a> { ); functions_to_scan.push( - Entrypoints { + Entrypoint { function: customfield.resolver.function, invokable: true, web_trigger: false, @@ -441,7 +441,7 @@ impl<'a> ForgeModules<'a> { self.ui_modifications.iter().for_each(|ui| { functions_to_scan.push( - Entrypoints { + Entrypoint { function: ui.resolver.function, invokable: true, web_trigger: false, @@ -451,17 +451,25 @@ impl<'a> ForgeModules<'a> { self.workflow_validator.iter().for_each(|validator| { functions_to_scan.push( - Entrypoints { + Entrypoint { + function: validator.function, + invokable: true, + web_trigger: false, + } + ); + + functions_to_scan.push( + Entrypoint { function: validator.resolver.function, invokable: true, web_trigger: false, } - ) + ); }); self.workflow_post_function.iter().for_each(|post_function| { functions_to_scan.push( - Entrypoints { + Entrypoint { function: post_function.function, invokable: true, web_trigger: false, @@ -473,7 +481,7 @@ impl<'a> ForgeModules<'a> { // ie macros has config and export fields on top of resolver fields that are functions for macros in self.macros { if let Some(resolver)= macros.resolver { - functions_to_scan.push(Entrypoints { + functions_to_scan.push(Entrypoint { function: resolver.function, invokable: true, web_trigger: false @@ -481,14 +489,14 @@ impl<'a> ForgeModules<'a> { } if let Some(config)= macros.config { - functions_to_scan.push(Entrypoints { + functions_to_scan.push(Entrypoint { function: config.function, invokable: true, web_trigger: false }) } if let Some(export)= macros.export { - functions_to_scan.push(Entrypoints { + functions_to_scan.push(Entrypoint { function: export.function, invokable: true, web_trigger: false @@ -498,13 +506,13 @@ impl<'a> ForgeModules<'a> { } for contentitem in self.content_by_line_item { - functions_to_scan.push(Entrypoints { + functions_to_scan.push(Entrypoint { function: contentitem.function, invokable: true, web_trigger: false }); if let Some(resolver)= contentitem.resolver { - functions_to_scan.push(Entrypoints { + functions_to_scan.push(Entrypoint { function: resolver.function, invokable: true, web_trigger: false @@ -512,7 +520,7 @@ impl<'a> ForgeModules<'a> { } if let Some(dynamic_properties)= contentitem.dynamic_properties { - functions_to_scan.push(Entrypoints { + functions_to_scan.push(Entrypoint { function: dynamic_properties.function, invokable: true, web_trigger: false @@ -522,13 +530,13 @@ impl<'a> ForgeModules<'a> { } for issue in self.issue_glance { - functions_to_scan.push(Entrypoints { + functions_to_scan.push(Entrypoint { function: issue.function, invokable: true, web_trigger: false }); if let Some(resolver)= issue.resolver { - functions_to_scan.push(Entrypoints { + functions_to_scan.push(Entrypoint { function: resolver.function, invokable: true, web_trigger: false @@ -536,7 +544,7 @@ impl<'a> ForgeModules<'a> { } if let Some(dynamic_properties)= issue.dynamic_properties { - functions_to_scan.push(Entrypoints { + functions_to_scan.push(Entrypoint { function: dynamic_properties.function, invokable: true, web_trigger: false @@ -546,13 +554,13 @@ impl<'a> ForgeModules<'a> { } for access in self.access_import_type { - functions_to_scan.push(Entrypoints { + functions_to_scan.push(Entrypoint { function: access.function, invokable: true, web_trigger: false }); if let Some(delete) = access.one_delete_import { - functions_to_scan.push(Entrypoints { + functions_to_scan.push(Entrypoint { function: delete.function, invokable: true, web_trigger: false @@ -560,7 +568,7 @@ impl<'a> ForgeModules<'a> { } if let Some(start)= access.start_import { - functions_to_scan.push(Entrypoints { + functions_to_scan.push(Entrypoint { function: start.function, invokable: true, web_trigger: false @@ -568,7 +576,7 @@ impl<'a> ForgeModules<'a> { } if let Some(stop)= access.stop_import { - functions_to_scan.push(Entrypoints { + functions_to_scan.push(Entrypoint { function: stop.function, invokable: true, web_trigger: false @@ -576,7 +584,7 @@ impl<'a> ForgeModules<'a> { } if let Some(status)= access.import_status { - functions_to_scan.push(Entrypoints { + functions_to_scan.push(Entrypoint { function: status.function, invokable: true, web_trigger: false @@ -589,7 +597,7 @@ impl<'a> ForgeModules<'a> { // make alternate_functions all user-invokable functions for module in self.extra.into_values().flatten() { if let Some(mod_function) = module.function { - functions_to_scan.push(Entrypoints { + functions_to_scan.push(Entrypoint { function: mod_function, invokable: true, web_trigger: false @@ -597,7 +605,7 @@ impl<'a> ForgeModules<'a> { } if let Some(resolver) = module.resolver { - functions_to_scan.push(Entrypoints { + functions_to_scan.push(Entrypoint { function: resolver.function, invokable: true, web_trigger: false @@ -605,7 +613,7 @@ impl<'a> ForgeModules<'a> { } } - return functions_to_scan; + functions_to_scan } } diff --git a/crates/fsrt/src/main.rs b/crates/fsrt/src/main.rs index 89e2afcf..c467e80a 100644 --- a/crates/fsrt/src/main.rs +++ b/crates/fsrt/src/main.rs @@ -43,7 +43,7 @@ use forge_analyzer::{ resolver::resolve_calls, }; -use forge_loader::manifest::{ForgeManifest, FunctionRef, FunctionTy, Entrypoints}; +use forge_loader::manifest::{ForgeManifest, FunctionRef, FunctionTy, Entrypoint}; use walkdir::WalkDir; #[derive(Parser, Debug)] @@ -113,7 +113,7 @@ struct ForgeProject<'a> { sm: Arc, ctx: AppCtx, env: Environment, - funcs: Vec>, + funcs: Vec>, opts: Opts, >>>>>>> cd2ed7b (edited main to iterate over vector of functions. TODO: edit add_funcs in main.rs and test into_analyzble_function use case in main.rs) } @@ -167,17 +167,20 @@ impl<'a> ForgeProject<'a> { } } // TODO: edit to work with new iterator that not FUNCTIONTY - fn add_funcs<'a, I: IntoIterator>>(&mut self, iter: I) { - self.funcs.extend(iter.into_iter().flat_map(|ftype| { - ftype.sequence(|(func_name, path)| { - let modid = self.ctx.modid_from_path(&path)?; - let func = self.env.module_export(modid, func_name)?; - Some((func_name.to_owned(), path, modid, func)) - }) - })); + fn add_funcs<'a, I: IntoIterator>>(&mut self, iter: I) { + self.funcs.extend(iter); } } + +// self.funcs.extend(iter.into_iter().flat_map(|ftype| { +// ftype.sequence(|(func_name, path)| { +// let modid = self.ctx.modid_from_path(&path)?; +// let func = self.env.module_export(modid, func_name)?; +// Some((func_name.to_owned(), path, modid, func)) +// }) +// })); + fn is_js_file>(path: P) -> bool { matches!( path.as_ref().extension().map(|s| s.as_bytes()), @@ -240,7 +243,7 @@ fn scan_directory(dir: PathBuf, function: Option<&str>, opts: Opts) -> Result<() }); let run_permission_checker = opts.check_permissions && !transpiled_async; - let funcrefs = &manifest.modules.into_analyzable_functions(); + let funcrefs = manifest.modules.into_analyzable_functions(); // .flat_map(|f| { // f.sequence(|fmod| { @@ -364,20 +367,20 @@ fn scan_directory(dir: PathBuf, function: Option<&str>, opts: Opts) -> Result<() if func.invokable { let mut checker = AuthZChecker::new(); - debug!("checking {func} at {path:?}"); - if let Err(err) = interp.run_checker(def, &mut checker, path.clone(), func.clone()) + debug!("checking {:?} at {path:?}", func.function); + if let Err(err) = interp.run_checker(def, &mut checker, path.clone(), func.function.to_string()) { - warn!("error while scanning {func} in {path:?}: {err}"); + warn!("error while scanning {:?} in {path:?}: {err}", func.function); } reporter.add_vulnerabilities(checker.into_vulns()); } else if func.web_trigger { let mut checker = AuthenticateChecker::new(); - debug!("checking webtrigger {func} at {path:?}"); + debug!("checking webtrigger {:?} at {path:?}", func.function); if let Err(err) = - authn_interp.run_checker(def, &mut checker, path.clone(), func.clone()) + authn_interp.run_checker(def, &mut checker, path.clone(), func.function.to_string()) { - warn!("error while scanning {func} in {path:?}: {err}"); + warn!("error while scanning {:?} in {path:?}: {err}", func.function); } reporter.add_vulnerabilities(checker.into_vulns()); From df2517b688ba4c9440255378c8313c1c06476ba9 Mon Sep 17 00:00:00 2001 From: Alina Wang Date: Wed, 10 Jan 2024 18:13:53 -0800 Subject: [PATCH 313/517] updated struct serde features and rust attributes. Updated customField with optional values --- crates/forge_loader/src/manifest.rs | 98 +++++++++++++++++------------ 1 file changed, 57 insertions(+), 41 deletions(-) diff --git a/crates/forge_loader/src/manifest.rs b/crates/forge_loader/src/manifest.rs index 362d8075..c0177740 100644 --- a/crates/forge_loader/src/manifest.rs +++ b/crates/forge_loader/src/manifest.rs @@ -37,7 +37,7 @@ struct ModInfo<'a> { #[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] struct MacroMod<'a> { key: &'a str, - function: &'a str, + function: Option<&'a str>, #[serde(borrow)] resolver: Option>, #[serde(borrow)] @@ -128,11 +128,11 @@ pub struct CustomField<'a> { #[serde(flatten, borrow)] key: &'a str, // all attributes below involve function calls - value: &'a str, - search_suggestion: &'a str, - function: &'a str, - edit: &'a str, - resolver: ModInfo<'a> + value: Option<&'a str>, + search_suggestions: &'a str, + function: Option<&'a str>, + edit: Option<&'a str>, + resolver: Option>, } @@ -153,7 +153,6 @@ pub struct WorkflowValidator<'a> { #[derive(Debug, Clone, PartialEq, Eq, Deserialize)] pub struct WorkflowPostFunction<'a> { - #[serde(flatten, borrow)] key: &'a str, function: &'a str, } @@ -291,7 +290,7 @@ pub enum FunctionTy { } // Struct used for tracking what scan a funtion requires. -#[derive(Debug, Clone, PartialEq, Eq, Deserialize)] +#[derive(Debug, Clone, PartialEq, Eq)] pub struct Entrypoint<'a> { pub function: &'a str, pub invokable: bool, @@ -342,8 +341,8 @@ impl<'a> ForgeModules<'a> { self, ) -> Vec>{ // number of webtriggers are usually low, so it's better to just sort them and reuse - self.webtriggers.iter(). - .sort_unstable_by_key(|trigger| trigger.function); + // self.webtriggers.iter(). + // .sort_unstable_by_key(|trigger| trigger.function); // Get all the Triggers and represent them as a new struct thing where "webtrigger" attribute is true // for all trigger things @@ -400,43 +399,55 @@ impl<'a> ForgeModules<'a> { }); self.custom_field.iter().for_each(|customfield| { - functions_to_scan.push( - Entrypoint { - function: customfield.value, - invokable: true, - web_trigger: false, - } - ); - functions_to_scan.push( - Entrypoint { - function: customfield.search_suggestion, - invokable: true, - web_trigger: false, - } - ); - functions_to_scan.push( - Entrypoint { - function: customfield.function, - invokable: true, - web_trigger: false, - } - ); - functions_to_scan.push( - Entrypoint { - function: customfield.edit, - invokable: true, - web_trigger: false, - } - ); + if let Some(value)= customfield.value { + functions_to_scan.push( + Entrypoint { + function: value, + invokable: true, + web_trigger: false, + } + ); + } + functions_to_scan.push( Entrypoint { - function: customfield.resolver.function, + function: customfield.search_suggestions, invokable: true, web_trigger: false, } ); + if let Some(func)= customfield.function { + functions_to_scan.push( + Entrypoint { + function: func, + invokable: true, + web_trigger: false, + } + ); + } + + if let Some(edit)= customfield.edit{ + functions_to_scan.push( + Entrypoint { + function: edit, + invokable: true, + web_trigger: false, + } + ); + } + + if let Some(resolver)= &customfield.resolver { + functions_to_scan.push( + Entrypoint { + function: resolver.function, + invokable: true, + web_trigger: false, + } + ); + } + }); self.ui_modifications.iter().for_each(|ui| { @@ -730,7 +741,7 @@ mod tests { assert_eq!(manifest.app.id, "my-app"); assert_eq!(manifest.modules.macros.len(), 1); assert_eq!(manifest.modules.macros[0].key, "My Macro"); - assert_eq!(manifest.modules.macros[0].function, "my-macro"); + // assert_eq!(manifest.modules.macros[0].function, "my-macro"); assert_eq!(manifest.modules.functions.len(), 1); assert_eq!( manifest.modules.functions[0], @@ -823,7 +834,12 @@ mod tests { }"#; let manifest: ForgeManifest = serde_json::from_str(json).unwrap(); assert_eq!(manifest.modules.macros.len(), 1); - assert_eq!(manifest.modules.macros[0].function, "Catch-me-if-you-can0"); + + if let Some(func) = manifest.modules.macros[0].function { + assert_eq!(func, "Catch-me-if-you-can0"); + + } + if let Some(func) = &manifest.modules.macros[0].resolver { assert_eq!(func.function, "Catch-me-if-you-can1"); From b819cbf22735b3d2ca43e211d40e566048b10ca0 Mon Sep 17 00:00:00 2001 From: Alina Wang Date: Wed, 10 Jan 2024 18:15:11 -0800 Subject: [PATCH 314/517] abstracted structs to use a CommonKeys struct that holds: key, function, and resolver for less code duplication. Updated into_analayzable method to update values based on functions specified in function mod. TODO: Update rest of non trigger modules to update mapping to functions to scan from function mod --- crates/forge_loader/src/manifest.rs | 148 ++++++++++++++-------------- crates/fsrt/src/main.rs | 13 +-- 2 files changed, 75 insertions(+), 86 deletions(-) diff --git a/crates/forge_loader/src/manifest.rs b/crates/forge_loader/src/manifest.rs index c0177740..c36da18d 100644 --- a/crates/forge_loader/src/manifest.rs +++ b/crates/forge_loader/src/manifest.rs @@ -1,6 +1,6 @@ use std::{ borrow::Borrow, - collections::{BTreeSet, HashSet}, + collections::{HashSet}, hash::Hash, path::{Path, PathBuf}, sync::Arc, @@ -10,8 +10,7 @@ use crate::{forgepermissions::ForgePermissions, Error}; use forge_utils::FxHashMap; use itertools::{Either, Itertools}; use serde::Deserialize; -use serde_json::map::Entry; -use serde_json::map::Entry; +use std::collections::BTreeMap; use tracing::trace; #[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] @@ -28,57 +27,46 @@ pub struct FunctionMod<'a> { providers: Option>, } -// Modified +// Abstracting away key, function, and resolver into a single struct for reuse whoo! #[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] -struct ModInfo<'a> { +struct CommonKey<'a> { + key: &'a str, function: &'a str, + resolver: Option<&'a str>, + } #[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] struct MacroMod<'a> { - key: &'a str, - function: Option<&'a str>, - #[serde(borrow)] - resolver: Option>, - #[serde(borrow)] - config: Option>, - #[serde(borrow)] - export: Option>, + #[serde(flatten, borrow)] + common_keys: CommonKey<'a>, + config: Option<&'a str>, + export: Option<&'a str>, } #[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] struct ContentByLineItem<'a> { - key: &'a str, - function: &'a str, + #[serde(flatten, borrow)] + common_keys: CommonKey<'a>, #[serde(borrow)] - resolver: Option>, - #[serde(borrow)] - dynamic_properties: Option>, + dynamic_properties: Option<&'a str>, } #[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] struct IssueGlance<'a> { - key: &'a str, - function: &'a str, - #[serde(borrow)] - resolver: Option>, - #[serde(borrow)] - dynamic_properties: Option>, + #[serde(flatten, borrow)] + common_keys: CommonKey<'a>, + dynamic_properties: Option<&'a str>, } - #[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] struct AccessImportType<'a> { - key: &'a str, - function: &'a str, - #[serde(borrow)] - one_delete_import: Option>, - #[serde(borrow)] - start_import: Option>, - #[serde(borrow)] - stop_import: Option>, - #[serde(borrow)] - import_status: Option>, + #[serde(flatten, borrow)] + common_keys: CommonKey<'a>, + one_delete_import: Option<&'a str>, + start_import: Option<&'a str>, + stop_import: Option<&'a str>, + import_status: Option<&'a str>, } @@ -117,44 +105,39 @@ struct ScheduledTrigger<'a> { // compass DataProvider module #[derive(Debug, Clone, PartialEq, Eq, Deserialize)] pub struct DataProvider<'a> { - key: &'a str, #[serde(flatten, borrow)] - callback: ModInfo<'a>, + key: &'a str, + callback: Option<&'a str>, } // Struct for Custom field Module. Check that search suggestion gets read in correctly. #[derive(Debug, Clone, PartialEq, Eq, Deserialize)] -pub struct CustomField<'a> { - #[serde(flatten, borrow)] - key: &'a str, +pub struct CustomField<'a> { // all attributes below involve function calls + #[serde(flatten, borrow)] + common_keys: CommonKey<'a>, value: Option<&'a str>, search_suggestions: &'a str, - function: Option<&'a str>, edit: Option<&'a str>, - resolver: Option>, } #[derive(Debug, Clone, PartialEq, Eq, Deserialize)] pub struct UiModificatons<'a> { - key: &'a str, #[serde(flatten, borrow)] - resolver: ModInfo<'a>, + common_key: CommonKey<'a> } #[derive(Debug, Clone, PartialEq, Eq, Deserialize)] pub struct WorkflowValidator<'a> { #[serde(flatten, borrow)] - key: &'a str, - function: &'a str, - resolver: ModInfo<'a> + common_keys: CommonKey<'a> } #[derive(Debug, Clone, PartialEq, Eq, Deserialize)] pub struct WorkflowPostFunction<'a> { - key: &'a str, - function: &'a str, + #[serde(flatten, borrow)] + common_keys: CommonKey<'a> } // Add more structs here for deserializing forge modules @@ -297,6 +280,7 @@ pub struct Entrypoint<'a> { pub web_trigger: bool, } + // Helper functions that help filter out which functions are what. impl FunctionTy { pub fn map(self, f: impl FnOnce(T) -> O) -> FunctionTy { @@ -338,43 +322,59 @@ impl<'a> ForgeModules<'a> { // TODO: function returns iterator where each item is some specified type. pub fn into_analyzable_functions ( - self, + &mut self, ) -> Vec>{ // number of webtriggers are usually low, so it's better to just sort them and reuse - // self.webtriggers.iter(). - // .sort_unstable_by_key(|trigger| trigger.function); + self.webtriggers.sort_unstable_by_key(|trigger| trigger.function); // Get all the Triggers and represent them as a new struct thing where "webtrigger" attribute is true // for all trigger things - let mut functions_to_scan = Vec::new(); + let mut functions_to_scan = BTreeMap::new(); + + // Get all functions for module from manifest.yml + self.functions.iter().for_each(|func| { + functions_to_scan.insert(func.handler, Entrypoint { + function: func.handler, + invokable: false, + web_trigger: false, + }); + + }); + + self.webtriggers.iter().for_each(|webtriggers| { - functions_to_scan.push( - Entrypoint { - function: webtriggers.function, - invokable: false, - web_trigger: true, - } - ); + if functions_to_scan.contains_key(webtriggers.function) { + if let Some(entry) = functions_to_scan.get_mut(webtriggers.function) { + entry.web_trigger = true; + } + } + + }); + + self.webtriggers.iter().for_each(|webtriggers| { + if functions_to_scan.contains_key(webtriggers.function) { + if let Some(entry) = functions_to_scan.get_mut(webtriggers.function) { + entry.web_trigger = true; + } + } }); + + self.event_triggers.iter().for_each(|event_triggers| { - functions_to_scan.push( - Entrypoint { - function: event_triggers.raw.function, - invokable: false, - web_trigger: true, - } - ); + if functions_to_scan.contains_key(event_triggers.raw.function) { + if let Some(entry) = functions_to_scan.get_mut(event_triggers.raw.function) { + entry.web_trigger = true; + } + } }); self.scheduled_triggers.iter().for_each(|schedule_triggers| { - functions_to_scan.push( - Entrypoint { - function: schedule_triggers.raw.function, - invokable: false, - web_trigger: true, - } - ) + if functions_to_scan.contains_key(schedule_triggers.raw.function) { + if let Some(entry) = functions_to_scan.get_mut(schedule_triggers.raw.function) { + entry.web_trigger = true; + } + } }); // create arrays representing functions that expose user non-invokable functions diff --git a/crates/fsrt/src/main.rs b/crates/fsrt/src/main.rs index c467e80a..5d478b01 100644 --- a/crates/fsrt/src/main.rs +++ b/crates/fsrt/src/main.rs @@ -97,18 +97,7 @@ struct Opts { out: Option, } -#[derive(Debug, Clone)] -struct ResolvedEntryPoint<'a> { - func_name: &'a str, - path: PathBuf, - module: ModId, - def_id: DefId, - webtrigger: bool, - invokable: bool, -} - struct ForgeProject<'a> { ->>>>>>> 7e86f46 (abstracted structs to use a CommonKeys struct that holds: key, function, and resolver for less code duplication. Updated into_analayzable method to update values based on functions specified in function mod. TODO: Update rest of non trigger modules to update mapping to functions to scan from function mod) #[allow(dead_code)] sm: Arc, ctx: AppCtx, @@ -118,7 +107,7 @@ struct ForgeProject<'a> { >>>>>>> cd2ed7b (edited main to iterate over vector of functions. TODO: edit add_funcs in main.rs and test into_analyzble_function use case in main.rs) } -impl<'a> ForgeProject<'a> { +impl ForgeProject<'_> { #[instrument(skip(src, iter))] fn with_files_and_sourceroot, I: IntoIterator>( src: P, From 0181e97cd816f6d0b042d6384ac2b2a4a1940195 Mon Sep 17 00:00:00 2001 From: Alina Wang Date: Wed, 10 Jan 2024 18:15:32 -0800 Subject: [PATCH 315/517] finished updating methods to check functions_to_scan and update entrypoint struct when needed. TODO: test and toggle function call in main.rs --- crates/forge_loader/src/manifest.rs | 331 ++++++++++++---------------- crates/fsrt/src/main.rs | 5 +- 2 files changed, 143 insertions(+), 193 deletions(-) diff --git a/crates/forge_loader/src/manifest.rs b/crates/forge_loader/src/manifest.rs index c36da18d..1f466340 100644 --- a/crates/forge_loader/src/manifest.rs +++ b/crates/forge_loader/src/manifest.rs @@ -125,7 +125,7 @@ pub struct CustomField<'a> { #[derive(Debug, Clone, PartialEq, Eq, Deserialize)] pub struct UiModificatons<'a> { #[serde(flatten, borrow)] - common_key: CommonKey<'a> + common_keys: CommonKey<'a> } #[derive(Debug, Clone, PartialEq, Eq, Deserialize)] @@ -323,7 +323,7 @@ impl<'a> ForgeModules<'a> { // TODO: function returns iterator where each item is some specified type. pub fn into_analyzable_functions ( &mut self, - ) -> Vec>{ + ) -> BTreeMap<&'a str, Entrypoint<'a>>{ // number of webtriggers are usually low, so it's better to just sort them and reuse self.webtriggers.sort_unstable_by_key(|trigger| trigger.function); @@ -334,8 +334,8 @@ impl<'a> ForgeModules<'a> { // Get all functions for module from manifest.yml self.functions.iter().for_each(|func| { - functions_to_scan.insert(func.handler, Entrypoint { - function: func.handler, + functions_to_scan.insert(func.key, Entrypoint { + function: func.key, invokable: false, web_trigger: false, }); @@ -378,249 +378,202 @@ impl<'a> ForgeModules<'a> { }); // create arrays representing functions that expose user non-invokable functions - self.consumers.iter().for_each(|consumers| { - functions_to_scan.push( - Entrypoint { - function: consumers.resolver.function, - invokable: true, - web_trigger: false, - } - ) + self.consumers.iter().for_each(|consumer| { + if functions_to_scan.contains_key(consumer.resolver.function) { + if let Some(entry) = functions_to_scan.get_mut(consumer.resolver.function) { + entry.invokable = true; + } + } }); self.data_provider.iter().for_each(|dataprovider| { - functions_to_scan.push( - Entrypoint { - function: dataprovider.callback.function, - invokable: true, - web_trigger: false, - } - ) + if let Some(call) = dataprovider.callback { + if let Some(entry) = functions_to_scan.get_mut(call) { + entry.invokable = true; + } + } + }); self.custom_field.iter().for_each(|customfield| { - if let Some(value)= customfield.value { - functions_to_scan.push( - Entrypoint { - function: value, - invokable: true, - web_trigger: false, - } - ); + if let Some(entry) = functions_to_scan.get_mut(value) { + entry.invokable = true; + } } - functions_to_scan.push( - Entrypoint { - function: customfield.search_suggestions, - invokable: true, - web_trigger: false, - } - ); - - if let Some(func)= customfield.function { - functions_to_scan.push( - Entrypoint { - function: func, - invokable: true, - web_trigger: false, - } - ); + if let Some(entry) = functions_to_scan.get_mut(customfield.search_suggestions) { + entry.invokable = true; + } + + if let Some(entry) = functions_to_scan.get_mut(customfield.common_keys.function) { + entry.invokable = true; + } + + if let Some(resolver) = customfield.common_keys.resolver { + if let Some(entry) = functions_to_scan.get_mut(resolver) { + entry.invokable = true; + } } - if let Some(edit)= customfield.edit{ - functions_to_scan.push( - Entrypoint { - function: edit, - invokable: true, - web_trigger: false, - } - ); - } + if let Some(edit) = customfield.edit { + if let Some(entry) = functions_to_scan.get_mut(edit) { + entry.invokable = true; + } - if let Some(resolver)= &customfield.resolver { - functions_to_scan.push( - Entrypoint { - function: resolver.function, - invokable: true, - web_trigger: false, - } - ); } }); self.ui_modifications.iter().for_each(|ui| { - functions_to_scan.push( - Entrypoint { - function: ui.resolver.function, - invokable: true, - web_trigger: false, + if let Some(entry) = functions_to_scan.get_mut(ui.common_keys.function) { + entry.invokable = true; + } + + if let Some(resolver) = ui.common_keys.resolver { + if let Some(entry) = functions_to_scan.get_mut(resolver) { + entry.invokable = true; } - ) + } }); self.workflow_validator.iter().for_each(|validator| { - functions_to_scan.push( - Entrypoint { - function: validator.function, - invokable: true, - web_trigger: false, - } - ); + if let Some(entry) = functions_to_scan.get_mut(validator.common_keys.function) { + entry.invokable = true; + } - functions_to_scan.push( - Entrypoint { - function: validator.resolver.function, - invokable: true, - web_trigger: false, + if let Some(resolver) = validator.common_keys.resolver { + if let Some(entry) = functions_to_scan.get_mut(resolver) { + entry.invokable = true; } - ); + } }); self.workflow_post_function.iter().for_each(|post_function| { - functions_to_scan.push( - Entrypoint { - function: post_function.function, - invokable: true, - web_trigger: false, + if let Some(entry) = functions_to_scan.get_mut(post_function.common_keys.function) { + entry.invokable = true; + } + + if let Some(resolver) = post_function.common_keys.resolver { + if let Some(entry) = functions_to_scan.get_mut(resolver) { + entry.invokable = true; } - ) + } }); // get user invokable modules that have additional exposure endpoints. // ie macros has config and export fields on top of resolver fields that are functions - for macros in self.macros { - if let Some(resolver)= macros.resolver { - functions_to_scan.push(Entrypoint { - function: resolver.function, - invokable: true, - web_trigger: false - }) + self.macros.iter().for_each(|macros| { + if let Some(resolver)= macros.common_keys.resolver { + if let Some(entry) = functions_to_scan.get_mut(resolver) { + entry.invokable = true; + } } if let Some(config)= macros.config { - functions_to_scan.push(Entrypoint { - function: config.function, - invokable: true, - web_trigger: false - }) + if let Some(entry) = functions_to_scan.get_mut(config) { + entry.invokable = true; + } } + if let Some(export)= macros.export { - functions_to_scan.push(Entrypoint { - function: export.function, - invokable: true, - web_trigger: false - }) + if let Some(entry) = functions_to_scan.get_mut(export) { + entry.invokable = true; + } } - } + }); - for contentitem in self.content_by_line_item { - functions_to_scan.push(Entrypoint { - function: contentitem.function, - invokable: true, - web_trigger: false - }); - if let Some(resolver)= contentitem.resolver { - functions_to_scan.push(Entrypoint { - function: resolver.function, - invokable: true, - web_trigger: false - }) + self.content_by_line_item.iter().for_each(|contentitem| { + if let Some(entry) = functions_to_scan.get_mut(contentitem.common_keys.function) { + entry.invokable = true; + } + + + if let Some(resolver)= contentitem.common_keys.resolver { + if let Some(entry) = functions_to_scan.get_mut(resolver) { + entry.invokable = true; + } } if let Some(dynamic_properties)= contentitem.dynamic_properties { - functions_to_scan.push(Entrypoint { - function: dynamic_properties.function, - invokable: true, - web_trigger: false - }) + if let Some(entry) = functions_to_scan.get_mut(dynamic_properties) { + entry.invokable = true; + } } - } + }); - for issue in self.issue_glance { - functions_to_scan.push(Entrypoint { - function: issue.function, - invokable: true, - web_trigger: false - }); - if let Some(resolver)= issue.resolver { - functions_to_scan.push(Entrypoint { - function: resolver.function, - invokable: true, - web_trigger: false - }) + self.issue_glance.iter().for_each(|issue| { + if let Some(entry) = functions_to_scan.get_mut(issue.common_keys.function) { + entry.invokable = true; + } + + + if let Some(resolver)= issue.common_keys.resolver { + if let Some(entry) = functions_to_scan.get_mut(resolver) { + entry.invokable = true; + } } if let Some(dynamic_properties)= issue.dynamic_properties { - functions_to_scan.push(Entrypoint { - function: dynamic_properties.function, - invokable: true, - web_trigger: false - }) + if let Some(entry) = functions_to_scan.get_mut(dynamic_properties) { + entry.invokable = true; + } } - } + }); + + self.access_import_type.iter().for_each(|access| { + if let Some(entry) = functions_to_scan.get_mut(access.common_keys.function) { + entry.invokable = true; + } + + if let Some(resolver) = access.common_keys.resolver { + if let Some(entry) = functions_to_scan.get_mut(resolver) { + entry.invokable = true; + } + } - for access in self.access_import_type { - functions_to_scan.push(Entrypoint { - function: access.function, - invokable: true, - web_trigger: false - }); if let Some(delete) = access.one_delete_import { - functions_to_scan.push(Entrypoint { - function: delete.function, - invokable: true, - web_trigger: false - }) + if let Some(entry) = functions_to_scan.get_mut(delete) { + entry.invokable = true; + } } - if let Some(start)= access.start_import { - functions_to_scan.push(Entrypoint { - function: start.function, - invokable: true, - web_trigger: false - }) + if let Some(start) = access.start_import { + if let Some(entry) = functions_to_scan.get_mut(start) { + entry.invokable = true; + } } - if let Some(stop)= access.stop_import { - functions_to_scan.push(Entrypoint { - function: stop.function, - invokable: true, - web_trigger: false - }) + if let Some(stop) = access.stop_import { + if let Some(entry) = functions_to_scan.get_mut(stop) { + entry.invokable = true; + } } if let Some(status)= access.import_status { - functions_to_scan.push(Entrypoint { - function: status.function, - invokable: true, - web_trigger: false - }) + if let Some(entry) = functions_to_scan.get_mut(status) { + entry.invokable = true; + } } - } + }); // get array for user invokable module functions // make alternate_functions all user-invokable functions - for module in self.extra.into_values().flatten() { + for module in self.extra.clone().into_values().flatten() { if let Some(mod_function) = module.function { - functions_to_scan.push(Entrypoint { - function: mod_function, - invokable: true, - web_trigger: false - }); + if let Some(entry) = functions_to_scan.get_mut(mod_function) { + entry.invokable = true; + } } if let Some(resolver) = module.resolver { - functions_to_scan.push(Entrypoint { - function: resolver.function, - invokable: true, - web_trigger: false - }); + if let Some(entry) = functions_to_scan.get_mut(resolver.function) { + entry.invokable = true; + } } } @@ -740,7 +693,7 @@ mod tests { assert_eq!(manifest.app.name, Some("My App")); assert_eq!(manifest.app.id, "my-app"); assert_eq!(manifest.modules.macros.len(), 1); - assert_eq!(manifest.modules.macros[0].key, "My Macro"); + assert_eq!(manifest.modules.macros[0].common_keys.key, "My Macro"); // assert_eq!(manifest.modules.macros[0].function, "my-macro"); assert_eq!(manifest.modules.functions.len(), 1); assert_eq!( @@ -834,25 +787,21 @@ mod tests { }"#; let manifest: ForgeManifest = serde_json::from_str(json).unwrap(); assert_eq!(manifest.modules.macros.len(), 1); - - if let Some(func) = manifest.modules.macros[0].function { - assert_eq!(func, "Catch-me-if-you-can0"); - - } + assert_eq!(manifest.modules.macros[0].common_keys.function, "Catch-me-if-you-can0"); - if let Some(func) = &manifest.modules.macros[0].resolver { - assert_eq!(func.function, "Catch-me-if-you-can1"); + if let Some(func) = manifest.modules.macros[0].common_keys.resolver { + assert_eq!(func, "Catch-me-if-you-can1"); } - if let Some(func) = &manifest.modules.macros[0].config { - assert_eq!(func.function, "Catch-me-if-you-can2"); + if let Some(func) = manifest.modules.macros[0].config { + assert_eq!(func, "Catch-me-if-you-can2"); } - if let Some(func) = &manifest.modules.macros[0].export { - assert_eq!(func.function, "Catch-me-if-you-can3"); + if let Some(func) = manifest.modules.macros[0].export { + assert_eq!(func, "Catch-me-if-you-can3"); } diff --git a/crates/fsrt/src/main.rs b/crates/fsrt/src/main.rs index 5d478b01..3b0dc5e8 100644 --- a/crates/fsrt/src/main.rs +++ b/crates/fsrt/src/main.rs @@ -5,7 +5,8 @@ use forge_permission_resolver::permissions_resolver::{ }; use miette::{IntoDiagnostic, Result}; use std::{ - collections::HashSet, + collections::{HashSet, BTreeMap, hash_map::Entry}, + convert::TryFrom, fs, os::unix::prelude::OsStrExt, path::{Path, PathBuf}, @@ -102,7 +103,7 @@ struct ForgeProject<'a> { sm: Arc, ctx: AppCtx, env: Environment, - funcs: Vec>, + funcs:BTreeMap<&'a str, Entrypoint<'a>>, opts: Opts, >>>>>>> cd2ed7b (edited main to iterate over vector of functions. TODO: edit add_funcs in main.rs and test into_analyzble_function use case in main.rs) } From f1c3f47fe53e6cbb86653cd2055c9a614836df27 Mon Sep 17 00:00:00 2001 From: Alina Wang Date: Wed, 10 Jan 2024 18:15:32 -0800 Subject: [PATCH 316/517] commented out consumer filter --- crates/forge_loader/src/manifest.rs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/crates/forge_loader/src/manifest.rs b/crates/forge_loader/src/manifest.rs index 1f466340..3ab6267f 100644 --- a/crates/forge_loader/src/manifest.rs +++ b/crates/forge_loader/src/manifest.rs @@ -378,13 +378,13 @@ impl<'a> ForgeModules<'a> { }); // create arrays representing functions that expose user non-invokable functions - self.consumers.iter().for_each(|consumer| { - if functions_to_scan.contains_key(consumer.resolver.function) { - if let Some(entry) = functions_to_scan.get_mut(consumer.resolver.function) { - entry.invokable = true; - } - } - }); + // self.consumers.iter().for_each(|consumer| { + // if functions_to_scan.contains_key(consumer.resolver.function) { + // if let Some(entry) = functions_to_scan.get_mut(consumer.resolver.function) { + // entry.invokable = true; + // } + // } + // }); self.data_provider.iter().for_each(|dataprovider| { if let Some(call) = dataprovider.callback { From 87e953c2d96fd3ab76c223d40f0a4eed5cfa794e Mon Sep 17 00:00:00 2001 From: Alina Wang Date: Wed, 10 Jan 2024 18:15:40 -0800 Subject: [PATCH 317/517] updated search_suggestion in customfield to be an optional value. --- crates/forge_loader/src/manifest.rs | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/crates/forge_loader/src/manifest.rs b/crates/forge_loader/src/manifest.rs index 3ab6267f..efe8ea34 100644 --- a/crates/forge_loader/src/manifest.rs +++ b/crates/forge_loader/src/manifest.rs @@ -2,8 +2,7 @@ use std::{ borrow::Borrow, collections::{HashSet}, hash::Hash, - path::{Path, PathBuf}, - sync::Arc, + path::{Path, PathBuf}, str::pattern::SearchStep, }; use crate::{forgepermissions::ForgePermissions, Error}; @@ -117,7 +116,7 @@ pub struct CustomField<'a> { #[serde(flatten, borrow)] common_keys: CommonKey<'a>, value: Option<&'a str>, - search_suggestions: &'a str, + search_suggestions: Option<&'a str>, edit: Option<&'a str>, } @@ -402,8 +401,11 @@ impl<'a> ForgeModules<'a> { } } - if let Some(entry) = functions_to_scan.get_mut(customfield.search_suggestions) { - entry.invokable = true; + if let Some(search) = customfield.search_suggestions { + if let Some(entry) = functions_to_scan.get_mut(search) { + entry.invokable = true; + } + } if let Some(entry) = functions_to_scan.get_mut(customfield.common_keys.function) { From f3b2fccda5ecae5dd2cee08349c044f30b7650fd Mon Sep 17 00:00:00 2001 From: Alina Wang Date: Wed, 10 Jan 2024 18:16:28 -0800 Subject: [PATCH 318/517] modified into_analyzable_functions with Josh and implementation in main.rs --- crates/forge_loader/src/manifest.rs | 383 ++++++++-------------------- crates/fsrt/src/main.rs | 165 ++++++------ 2 files changed, 195 insertions(+), 353 deletions(-) diff --git a/crates/forge_loader/src/manifest.rs b/crates/forge_loader/src/manifest.rs index efe8ea34..8915d1d2 100644 --- a/crates/forge_loader/src/manifest.rs +++ b/crates/forge_loader/src/manifest.rs @@ -1,15 +1,16 @@ use std::{ borrow::Borrow, - collections::{HashSet}, + collections::{BTreeSet, HashSet}, hash::Hash, - path::{Path, PathBuf}, str::pattern::SearchStep, + path::{Path, PathBuf}, + sync::Arc, }; use crate::{forgepermissions::ForgePermissions, Error}; use forge_utils::FxHashMap; use itertools::{Either, Itertools}; use serde::Deserialize; -use std::collections::BTreeMap; +use serde_json::map::Entry; use tracing::trace; #[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] @@ -17,7 +18,7 @@ struct AuthProviders<'a> { #[serde(borrow)] auth: Vec<&'a str>, } -// Maps the Functions Module in common Modules +// Maps the Functions Module in common Modules #[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] pub struct FunctionMod<'a> { key: &'a str, @@ -32,51 +33,48 @@ struct CommonKey<'a> { key: &'a str, function: &'a str, resolver: Option<&'a str>, - } #[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] -struct MacroMod<'a> { - #[serde(flatten, borrow)] +struct MacroMod<'a> { + #[serde(flatten, borrow)] common_keys: CommonKey<'a>, config: Option<&'a str>, export: Option<&'a str>, } #[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] -struct ContentByLineItem<'a> { - #[serde(flatten, borrow)] +struct ContentByLineItem<'a> { + #[serde(flatten, borrow)] common_keys: CommonKey<'a>, - #[serde(borrow)] + #[serde(borrow)] dynamic_properties: Option<&'a str>, } #[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] -struct IssueGlance<'a> { - #[serde(flatten, borrow)] +struct IssueGlance<'a> { + #[serde(flatten, borrow)] common_keys: CommonKey<'a>, dynamic_properties: Option<&'a str>, - } #[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] -struct AccessImportType<'a> { - #[serde(flatten, borrow)] +struct AccessImportType<'a> { + #[serde(flatten, borrow)] common_keys: CommonKey<'a>, one_delete_import: Option<&'a str>, start_import: Option<&'a str>, stop_import: Option<&'a str>, import_status: Option<&'a str>, - } -// WebTrigger => RawTrigger; WHY IS THIS NAMED DIFFERENTLY !? WHO CHANGED NAMES +// WebTrigger => RawTrigger; WHY IS THIS NAMED DIFFERENTLY !? WHO CHANGED NAMES #[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] struct RawTrigger<'a> { key: &'a str, function: &'a str, } -// Trigger => EventTriger; WHY IS THIS NAMED DIFFERENTLY !? WHO CHANGED NAMES +// Trigger => EventTriger; WHY IS THIS NAMED DIFFERENTLY !? WHO CHANGED NAMES #[derive(Debug, Clone, PartialEq, Eq, Deserialize)] struct EventTrigger<'a> { #[serde(flatten, borrow)] @@ -93,7 +91,7 @@ enum Interval { Week, } -// Thank you to whomeever kept this one the same. T.T +// Thank you to whomeever kept this one the same. T.T #[derive(Debug, Clone, PartialEq, Eq, Deserialize)] struct ScheduledTrigger<'a> { #[serde(flatten, borrow)] @@ -105,13 +103,13 @@ struct ScheduledTrigger<'a> { #[derive(Debug, Clone, PartialEq, Eq, Deserialize)] pub struct DataProvider<'a> { #[serde(flatten, borrow)] - key: &'a str, + key: &'a str, callback: Option<&'a str>, } -// Struct for Custom field Module. Check that search suggestion gets read in correctly. +// Struct for Custom field Module. Check that search suggestion gets read in correctly. #[derive(Debug, Clone, PartialEq, Eq, Deserialize)] -pub struct CustomField<'a> { +pub struct CustomField<'a> { // all attributes below involve function calls #[serde(flatten, borrow)] common_keys: CommonKey<'a>, @@ -120,23 +118,22 @@ pub struct CustomField<'a> { edit: Option<&'a str>, } - #[derive(Debug, Clone, PartialEq, Eq, Deserialize)] pub struct UiModificatons<'a> { #[serde(flatten, borrow)] - common_keys: CommonKey<'a> + common_keys: CommonKey<'a>, } #[derive(Debug, Clone, PartialEq, Eq, Deserialize)] pub struct WorkflowValidator<'a> { #[serde(flatten, borrow)] - common_keys: CommonKey<'a> + common_keys: CommonKey<'a>, } #[derive(Debug, Clone, PartialEq, Eq, Deserialize)] pub struct WorkflowPostFunction<'a> { #[serde(flatten, borrow)] - common_keys: CommonKey<'a> + common_keys: CommonKey<'a>, } // Add more structs here for deserializing forge modules @@ -261,10 +258,9 @@ pub struct FunctionRef<'a, S = Unresolved> { status: S, } - // Add an extra variant to the FunctionTy enum for non user invocable functions -// Indirect: functions indirectly invoked by user :O So kewl. -// TODO: change this to struct with bools +// Indirect: functions indirectly invoked by user :O So kewl. +// TODO: change this to struct with bools #[derive(Debug, Clone, PartialEq, Eq)] pub enum FunctionTy { Invokable(T), @@ -273,22 +269,28 @@ pub enum FunctionTy { // Struct used for tracking what scan a funtion requires. #[derive(Debug, Clone, PartialEq, Eq)] -pub struct Entrypoint<'a> { - pub function: &'a str, +pub struct Entrypoint<'a, S = Unresolved> { + pub function: FunctionRef<'a, S>, pub invokable: bool, pub web_trigger: bool, } +// Helper functions that help filter out which functions are what. +// original code that's commented out to modify methods. Here for reference +// impl FunctionTy { +// pub fn map(self, f: impl FnOnce(T) -> O) -> FunctionTy { +// match self { +// Self::Invokable(t) => FunctionTy::Invokable(f(t)), +// Self::WebTrigger(t) => FunctionTy::WebTrigger(f(t)), +// } +// } -// Helper functions that help filter out which functions are what. -impl FunctionTy { - pub fn map(self, f: impl FnOnce(T) -> O) -> FunctionTy { - match self { - Self::Invokable(t) => FunctionTy::Invokable(f(t)), - Self::WebTrigger(t) => FunctionTy::WebTrigger(f(t)), - } - } - +// #[inline] +// pub fn into_inner(self) -> T { +// match self { +// FunctionTy::Invokable(t) | FunctionTy::WebTrigger(t) => t, +// } +// } // #[inline] // pub fn into_inner(self) -> T { // match self { @@ -316,270 +318,94 @@ impl AsRef for FunctionTy { } } - impl<'a> ForgeModules<'a> { - -// TODO: function returns iterator where each item is some specified type. - pub fn into_analyzable_functions ( - &mut self, - ) -> BTreeMap<&'a str, Entrypoint<'a>>{ + // TODO: function returns iterator where each item is some specified type. + pub fn into_analyzable_functions(mut self) -> impl Iterator> { // number of webtriggers are usually low, so it's better to just sort them and reuse - self.webtriggers.sort_unstable_by_key(|trigger| trigger.function); - - // Get all the Triggers and represent them as a new struct thing where "webtrigger" attribute is true - // for all trigger things + self.webtriggers + .sort_unstable_by_key(|trigger| trigger.function); - let mut functions_to_scan = BTreeMap::new(); + // Get all the Triggers and represent them as a new struct thing where "webtrigger" attribute is true + // for all trigger things - // Get all functions for module from manifest.yml - self.functions.iter().for_each(|func| { - functions_to_scan.insert(func.key, Entrypoint { - function: func.key, - invokable: false, - web_trigger: false, - }); - - }); - - - self.webtriggers.iter().for_each(|webtriggers| { - if functions_to_scan.contains_key(webtriggers.function) { - if let Some(entry) = functions_to_scan.get_mut(webtriggers.function) { - entry.web_trigger = true; - } - } - - }); - - self.webtriggers.iter().for_each(|webtriggers| { - if functions_to_scan.contains_key(webtriggers.function) { - if let Some(entry) = functions_to_scan.get_mut(webtriggers.function) { - entry.web_trigger = true; - } - } - - }); - - - self.event_triggers.iter().for_each(|event_triggers| { - if functions_to_scan.contains_key(event_triggers.raw.function) { - if let Some(entry) = functions_to_scan.get_mut(event_triggers.raw.function) { - entry.web_trigger = true; - } - } - }); - self.scheduled_triggers.iter().for_each(|schedule_triggers| { - if functions_to_scan.contains_key(schedule_triggers.raw.function) { - if let Some(entry) = functions_to_scan.get_mut(schedule_triggers.raw.function) { - entry.web_trigger = true; - } - } - }); - - // create arrays representing functions that expose user non-invokable functions - // self.consumers.iter().for_each(|consumer| { - // if functions_to_scan.contains_key(consumer.resolver.function) { - // if let Some(entry) = functions_to_scan.get_mut(consumer.resolver.function) { - // entry.invokable = true; - // } - // } - // }); + let mut invokable_functions = BTreeSet::new(); self.data_provider.iter().for_each(|dataprovider| { - if let Some(call) = dataprovider.callback { - if let Some(entry) = functions_to_scan.get_mut(call) { - entry.invokable = true; - } - } - + invokable_functions.extend(dataprovider.callback); }); self.custom_field.iter().for_each(|customfield| { - if let Some(value)= customfield.value { - if let Some(entry) = functions_to_scan.get_mut(value) { - entry.invokable = true; - } - } - - if let Some(search) = customfield.search_suggestions { - if let Some(entry) = functions_to_scan.get_mut(search) { - entry.invokable = true; - } - - } - - if let Some(entry) = functions_to_scan.get_mut(customfield.common_keys.function) { - entry.invokable = true; - } - - if let Some(resolver) = customfield.common_keys.resolver { - if let Some(entry) = functions_to_scan.get_mut(resolver) { - entry.invokable = true; - } - } - - if let Some(edit) = customfield.edit { - if let Some(entry) = functions_to_scan.get_mut(edit) { - entry.invokable = true; - } - - } + invokable_functions.extend(customfield.value); + invokable_functions.extend(customfield.search_suggestions); + invokable_functions.extend(customfield.edit); + invokable_functions.insert(customfield.common_keys.function); + invokable_functions.extend(customfield.common_keys.resolver); }); self.ui_modifications.iter().for_each(|ui| { - if let Some(entry) = functions_to_scan.get_mut(ui.common_keys.function) { - entry.invokable = true; - } - - if let Some(resolver) = ui.common_keys.resolver { - if let Some(entry) = functions_to_scan.get_mut(resolver) { - entry.invokable = true; - } - } + invokable_functions.insert(ui.common_keys.function); + invokable_functions.extend(ui.common_keys.resolver); }); self.workflow_validator.iter().for_each(|validator| { - if let Some(entry) = functions_to_scan.get_mut(validator.common_keys.function) { - entry.invokable = true; - } + invokable_functions.insert(validator.common_keys.key); - if let Some(resolver) = validator.common_keys.resolver { - if let Some(entry) = functions_to_scan.get_mut(resolver) { - entry.invokable = true; - } - } - }); - - self.workflow_post_function.iter().for_each(|post_function| { - if let Some(entry) = functions_to_scan.get_mut(post_function.common_keys.function) { - entry.invokable = true; - } + invokable_functions.insert(validator.common_keys.function); - if let Some(resolver) = post_function.common_keys.resolver { - if let Some(entry) = functions_to_scan.get_mut(resolver) { - entry.invokable = true; - } - } + invokable_functions.extend(validator.common_keys.resolver); }); - // get user invokable modules that have additional exposure endpoints. - // ie macros has config and export fields on top of resolver fields that are functions - self.macros.iter().for_each(|macros| { - if let Some(resolver)= macros.common_keys.resolver { - if let Some(entry) = functions_to_scan.get_mut(resolver) { - entry.invokable = true; - } - } - - if let Some(config)= macros.config { - if let Some(entry) = functions_to_scan.get_mut(config) { - entry.invokable = true; - } - } - - if let Some(export)= macros.export { - if let Some(entry) = functions_to_scan.get_mut(export) { - entry.invokable = true; - } - } - - }); + self.workflow_post_function + .iter() + .for_each(|post_function| { + invokable_functions.insert(post_function.common_keys.key); - self.content_by_line_item.iter().for_each(|contentitem| { - if let Some(entry) = functions_to_scan.get_mut(contentitem.common_keys.function) { - entry.invokable = true; - } + invokable_functions.insert(post_function.common_keys.function); + invokable_functions.extend(post_function.common_keys.resolver); + }); - if let Some(resolver)= contentitem.common_keys.resolver { - if let Some(entry) = functions_to_scan.get_mut(resolver) { - entry.invokable = true; - } - } + // get user invokable modules that have additional exposure endpoints. + // ie macros has config and export fields on top of resolver fields that are functions + self.macros.iter().for_each(|macros| { + invokable_functions.insert(macros.common_keys.key); - if let Some(dynamic_properties)= contentitem.dynamic_properties { - if let Some(entry) = functions_to_scan.get_mut(dynamic_properties) { - entry.invokable = true; - } - } + invokable_functions.insert(macros.common_keys.function); + invokable_functions.extend(macros.common_keys.resolver); + invokable_functions.extend(macros.config); + invokable_functions.extend(macros.export); }); self.issue_glance.iter().for_each(|issue| { - if let Some(entry) = functions_to_scan.get_mut(issue.common_keys.function) { - entry.invokable = true; - } - - - if let Some(resolver)= issue.common_keys.resolver { - if let Some(entry) = functions_to_scan.get_mut(resolver) { - entry.invokable = true; - } - } - - if let Some(dynamic_properties)= issue.dynamic_properties { - if let Some(entry) = functions_to_scan.get_mut(dynamic_properties) { - entry.invokable = true; - } - } - + invokable_functions.insert(issue.common_keys.function); + invokable_functions.extend(issue.common_keys.resolver); + invokable_functions.extend(issue.dynamic_properties); }); self.access_import_type.iter().for_each(|access| { - if let Some(entry) = functions_to_scan.get_mut(access.common_keys.function) { - entry.invokable = true; - } - - if let Some(resolver) = access.common_keys.resolver { - if let Some(entry) = functions_to_scan.get_mut(resolver) { - entry.invokable = true; - } - } - - if let Some(delete) = access.one_delete_import { - if let Some(entry) = functions_to_scan.get_mut(delete) { - entry.invokable = true; - } - } - - if let Some(start) = access.start_import { - if let Some(entry) = functions_to_scan.get_mut(start) { - entry.invokable = true; - } - } - - if let Some(stop) = access.stop_import { - if let Some(entry) = functions_to_scan.get_mut(stop) { - entry.invokable = true; - } - } - - if let Some(status)= access.import_status { - if let Some(entry) = functions_to_scan.get_mut(status) { - entry.invokable = true; - } - } + invokable_functions.insert(access.common_keys.function); + invokable_functions.extend(access.common_keys.resolver); + invokable_functions.extend(access.one_delete_import); + invokable_functions.extend(access.stop_import); + invokable_functions.extend(access.start_import); + invokable_functions.extend(access.import_status); }); - - // get array for user invokable module functions - // make alternate_functions all user-invokable functions - for module in self.extra.clone().into_values().flatten() { - if let Some(mod_function) = module.function { - if let Some(entry) = functions_to_scan.get_mut(mod_function) { - entry.invokable = true; - } - } - - if let Some(resolver) = module.resolver { - if let Some(entry) = functions_to_scan.get_mut(resolver.function) { - entry.invokable = true; - } - } - } - - functions_to_scan + + self.functions.into_iter().flat_map(move |func| { + let web_trigger = self + .webtriggers + .binary_search_by_key(&func.key, |trigger| &trigger.function) + .is_ok(); + let invokable = invokable_functions.contains(func.key); + Ok::<_, Error>(Entrypoint { + function: FunctionRef::try_from(func)?, + invokable, + web_trigger, + }) + }) } } @@ -731,10 +557,9 @@ mod tests { ); } - // Modified specific deserialization schemes for modules. Checking that new schemes can deserialize function values. + // Modified specific deserialization schemes for modules. Checking that new schemes can deserialize function values. #[test] fn test_new_deserialize() { - let json = r#"{ "app": { "name": "My App", @@ -789,23 +614,21 @@ mod tests { }"#; let manifest: ForgeManifest = serde_json::from_str(json).unwrap(); assert_eq!(manifest.modules.macros.len(), 1); - assert_eq!(manifest.modules.macros[0].common_keys.function, "Catch-me-if-you-can0"); - + assert_eq!( + manifest.modules.macros[0].common_keys.function, + "Catch-me-if-you-can0" + ); if let Some(func) = manifest.modules.macros[0].common_keys.resolver { assert_eq!(func, "Catch-me-if-you-can1"); - } if let Some(func) = manifest.modules.macros[0].config { assert_eq!(func, "Catch-me-if-you-can2"); - } if let Some(func) = manifest.modules.macros[0].export { assert_eq!(func, "Catch-me-if-you-can3"); - - } - + } } } diff --git a/crates/fsrt/src/main.rs b/crates/fsrt/src/main.rs index 3b0dc5e8..af14558f 100644 --- a/crates/fsrt/src/main.rs +++ b/crates/fsrt/src/main.rs @@ -5,8 +5,7 @@ use forge_permission_resolver::permissions_resolver::{ }; use miette::{IntoDiagnostic, Result}; use std::{ - collections::{HashSet, BTreeMap, hash_map::Entry}, - convert::TryFrom, + collections::HashSet, fs, os::unix::prelude::OsStrExt, path::{Path, PathBuf}, @@ -18,7 +17,6 @@ use std::{ use clap::{Parser, ValueHint}; use miette::{IntoDiagnostic, Result}; -use serde_json::map::Entry; use swc_core::{ common::{Globals, Mark, SourceMap, GLOBALS}, ecma::{ @@ -44,7 +42,7 @@ use forge_analyzer::{ resolver::resolve_calls, }; -use forge_loader::manifest::{ForgeManifest, FunctionRef, FunctionTy, Entrypoint}; +use forge_loader::manifest::{Entrypoint, ForgeManifest, Resolved}; use walkdir::WalkDir; #[derive(Parser, Debug)] @@ -98,17 +96,27 @@ struct Opts { out: Option, } +#[derive(Debug, Clone)] +struct ResolvedEntryPoint<'a> { + func_name: &'a str, + path: PathBuf, + module: ModId, + def_id: DefId, + webtrigger: bool, + invokable: bool, +} + struct ForgeProject<'a> { #[allow(dead_code)] sm: Arc, ctx: AppCtx, env: Environment, - funcs:BTreeMap<&'a str, Entrypoint<'a>>, + funcs: Vec>, opts: Opts, >>>>>>> cd2ed7b (edited main to iterate over vector of functions. TODO: edit add_funcs in main.rs and test into_analyzble_function use case in main.rs) } -impl ForgeProject<'_> { +impl<'a> ForgeProject<'a> { #[instrument(skip(src, iter))] fn with_files_and_sourceroot, I: IntoIterator>( src: P, @@ -156,21 +164,24 @@ impl ForgeProject<'_> { opts: Opts::default(), } } -// TODO: edit to work with new iterator that not FUNCTIONTY - fn add_funcs<'a, I: IntoIterator>>(&mut self, iter: I) { - self.funcs.extend(iter); + // TODO: edit to work with new iterator that not FUNCTIONTY + fn add_funcs>>(&mut self, iter: I) { + self.funcs.extend(iter.into_iter().filter_map(|entrypoint| { + let (func_name, path) = entrypoint.function.into_func_path(); + let module = self.ctx.modid_from_path(&path)?; + let def_id = self.env.module_export(module, func_name)?; + Some(ResolvedEntryPoint { + func_name, + path, + module, + def_id, + invokable: entrypoint.invokable, + webtrigger: entrypoint.web_trigger, + }) + })); } } - -// self.funcs.extend(iter.into_iter().flat_map(|ftype| { -// ftype.sequence(|(func_name, path)| { -// let modid = self.ctx.modid_from_path(&path)?; -// let func = self.env.module_export(modid, func_name)?; -// Some((func_name.to_owned(), path, modid, func)) -// }) -// })); - fn is_js_file>(path: P) -> bool { matches!( path.as_ref().extension().map(|s| s.as_bytes()), @@ -190,11 +201,7 @@ fn collect_sourcefiles>(root: P) -> impl Iterator } #[tracing::instrument(level = "debug")] -<<<<<<< HEAD -fn scan_directory(dir: PathBuf, function: Option<&str>, opts: &Args) -> Result { -======= fn scan_directory(dir: PathBuf, function: Option<&str>, opts: Opts) -> Result<()> { ->>>>>>> 29c38d6 (modified into_analyzable_functions with Josh and implementation in main.rs) let mut manifest_file = dir.clone(); manifest_file.push("manifest.yaml"); if !manifest_file.exists() { @@ -233,23 +240,23 @@ fn scan_directory(dir: PathBuf, function: Option<&str>, opts: Opts) -> Result<() }); let run_permission_checker = opts.check_permissions && !transpiled_async; - let funcrefs = manifest.modules.into_analyzable_functions(); - - // .flat_map(|f| { - // f.sequence(|fmod| { - // let resolved_func = FunctionRef::try_from(fmod)?.try_resolve(&paths, &dir)?; - // Ok::<_, forge_loader::Error>(resolved_func.into_func_path()) - // }) - // }); + let funcrefs = manifest + .modules + .into_analyzable_functions() + .flat_map(|entrypoint| { + Ok::<_, forge_loader::Error>(Entrypoint { + function: entrypoint.function.try_resolve(&paths, &dir)?, + invokable: entrypoint.invokable, + web_trigger: entrypoint.web_trigger, + }) + }); + let src_root = dir.join("src"); let mut proj = ForgeProject::with_files_and_sourceroot(src_root, paths.clone(), secret_packages); if transpiled_async { warn!("Unable to scan due to transpiled async"); -<<<<<<< HEAD -======= return Ok(()); ->>>>>>> 29c38d6 (modified into_analyzable_functions with Josh and implementation in main.rs) } proj.add_funcs(funcrefs); resolve_calls(&mut proj.ctx); @@ -333,48 +340,65 @@ fn scan_directory(dir: PathBuf, function: Option<&str>, opts: Opts) -> Result<() for func in &proj.funcs { // TODO: Update operations in for loop to scan functions. // idea: iterate over each func which should be struct that tracks the function to be scanned. And performs scans according to bool. - // match *func { - // FunctionTy::Invokable((ref func, ref path, _, def)) => { - // let mut checker = AuthZChecker::new(); - // debug!("checking {func} at {path:?}"); - // if let Err(err) = interp.run_checker(def, &mut checker, path.clone(), func.clone()) - // { - // warn!("error while scanning {func} in {path:?}: {err}"); - // } - // reporter.add_vulnerabilities(checker.into_vulns()); - // } - // FunctionTy::WebTrigger((ref func, ref path, _, def)) => { - // let mut checker = AuthenticateChecker::new(); - // debug!("checking webtrigger {func} at {path:?}"); - // if let Err(err) = - // authn_interp.run_checker(def, &mut checker, path.clone(), func.clone()) - // { - // warn!("error while scanning {func} in {path:?}: {err}"); - // } - // reporter.add_vulnerabilities(checker.into_vulns()); - // } - // } - + // match *func { + // FunctionTy::Invokable((ref func, ref path, _, def)) => { + // let mut checker = AuthZChecker::new(); + // debug!("checking {func} at {path:?}"); + // if let Err(err) = interp.run_checker(def, &mut checker, path.clone(), func.clone()) + // { + // warn!("error while scanning {func} in {path:?}: {err}"); + // } + // reporter.add_vulnerabilities(checker.into_vulns()); + // } + // FunctionTy::WebTrigger((ref func, ref path, _, def)) => { + // let mut checker = AuthenticateChecker::new(); + // debug!("checking webtrigger {func} at {path:?}"); + // if let Err(err) = + // authn_interp.run_checker(def, &mut checker, path.clone(), func.clone()) + // { + // warn!("error while scanning {func} in {path:?}: {err}"); + // } + // reporter.add_vulnerabilities(checker.into_vulns()); + // } + // } + + // Get entrypoint value from tuple + // Logic for performing scans. + // If it's invokable, then run invokable scan. If web_trigger, then trigger scan. + // And if it's both, run both scans. if func.invokable { let mut checker = AuthZChecker::new(); - debug!("checking {:?} at {path:?}", func.function); - if let Err(err) = interp.run_checker(def, &mut checker, path.clone(), func.function.to_string()) - { - warn!("error while scanning {:?} in {path:?}: {err}", func.function); + debug!("checking {:?} at {:?}", func.func_name, &func.path); + if let Err(err) = interp.run_checker( + func.def_id, + &mut checker, + func.path.clone(), + func.func_name.to_string(), + ) { + warn!( + "error while scanning {:?} in {:?}: {err}", + func.func_name, func.path, + ); } reporter.add_vulnerabilities(checker.into_vulns()); - - } else if func.web_trigger { + } else if func.webtrigger { let mut checker = AuthenticateChecker::new(); - debug!("checking webtrigger {:?} at {path:?}", func.function); - if let Err(err) = - authn_interp.run_checker(def, &mut checker, path.clone(), func.function.to_string()) - { - warn!("error while scanning {:?} in {path:?}: {err}", func.function); + debug!( + "checking webtrigger {:?} at {:?}", + func.func_name, func.path, + ); + if let Err(err) = authn_interp.run_checker( + func.def_id, + &mut checker, + func.path.clone(), + func.func_name.to_string(), + ) { + warn!( + "error while scanning {:?} in {:?}: {err}", + func.func_name, func.path, + ); } reporter.add_vulnerabilities(checker.into_vulns()); - - } } let report = serde_json::to_string(&reporter.into_report()).into_diagnostic()?; @@ -385,12 +409,7 @@ fn scan_directory(dir: PathBuf, function: Option<&str>, opts: Opts) -> Result<() } None => println!("{report}"), } -<<<<<<< HEAD - - Ok(proj) -======= Ok(()) ->>>>>>> 29c38d6 (modified into_analyzable_functions with Josh and implementation in main.rs) } fn main() -> Result<()> { From bdd9b49f835c7d4e3855d81681907994161bb402 Mon Sep 17 00:00:00 2001 From: Alina Wang Date: Wed, 10 Jan 2024 18:16:46 -0800 Subject: [PATCH 319/517] rebased EAS-1893 --- crates/forge_loader/src/manifest.rs | 25 +++++++++++++++++++++++-- 1 file changed, 23 insertions(+), 2 deletions(-) diff --git a/crates/forge_loader/src/manifest.rs b/crates/forge_loader/src/manifest.rs index 8915d1d2..7dd374fb 100644 --- a/crates/forge_loader/src/manifest.rs +++ b/crates/forge_loader/src/manifest.rs @@ -37,7 +37,6 @@ struct CommonKey<'a> { #[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] struct MacroMod<'a> { - #[serde(flatten, borrow)] common_keys: CommonKey<'a>, config: Option<&'a str>, export: Option<&'a str>, @@ -394,6 +393,28 @@ impl<'a> ForgeModules<'a> { invokable_functions.extend(access.import_status); }); + self.functions.into_iter().flat_map(move |func| { + let web_trigger = self + .webtriggers + .binary_search_by_key(&func.key, |trigger| &trigger.function) + .is_ok(); + let invokable = invokable_functions.contains(func.key); + Ok::<_, Error>(Entrypoint { + function: FunctionRef::try_from(func)?, + invokable, + web_trigger, + }) + }); + self.access_import_type.iter().for_each(|access| { + invokable_functions.insert(access.common_keys.function); + invokable_functions.extend(access.common_keys.resolver); + + invokable_functions.extend(access.one_delete_import); + invokable_functions.extend(access.stop_import); + invokable_functions.extend(access.start_import); + invokable_functions.extend(access.import_status); + }); + self.functions.into_iter().flat_map(move |func| { let web_trigger = self .webtriggers @@ -631,4 +652,4 @@ mod tests { assert_eq!(func, "Catch-me-if-you-can3"); } } -} +}} From 963411664f11cab9d3a34d1b2e34b6c14ce925f4 Mon Sep 17 00:00:00 2001 From: Alina Wang Date: Wed, 10 Jan 2024 18:17:09 -0800 Subject: [PATCH 320/517] chore: updated main.rs file to remove vscode notes --- crates/fsrt/src/main.rs | 127 +++++++++++++++++++++------------------- 1 file changed, 67 insertions(+), 60 deletions(-) diff --git a/crates/fsrt/src/main.rs b/crates/fsrt/src/main.rs index af14558f..06693c45 100644 --- a/crates/fsrt/src/main.rs +++ b/crates/fsrt/src/main.rs @@ -12,11 +12,6 @@ use std::{ sync::Arc, }; -<<<<<<< HEAD -======= -use clap::{Parser, ValueHint}; -use miette::{IntoDiagnostic, Result}; - use swc_core::{ common::{Globals, Mark, SourceMap, GLOBALS}, ecma::{ @@ -85,17 +80,6 @@ struct Args { dirs: Vec, } -<<<<<<< HEAD -struct ForgeProject { -======= -#[derive(Debug, Clone, Default)] -struct Opts { - dump_cfg: bool, - dump_callgraph: bool, - appkey: Option, - out: Option, -} - #[derive(Debug, Clone)] struct ResolvedEntryPoint<'a> { func_name: &'a str, @@ -112,8 +96,6 @@ struct ForgeProject<'a> { ctx: AppCtx, env: Environment, funcs: Vec>, - opts: Opts, ->>>>>>> cd2ed7b (edited main to iterate over vector of functions. TODO: edit add_funcs in main.rs and test into_analyzble_function use case in main.rs) } impl<'a> ForgeProject<'a> { @@ -201,7 +183,7 @@ fn collect_sourcefiles>(root: P) -> impl Iterator } #[tracing::instrument(level = "debug")] -fn scan_directory(dir: PathBuf, function: Option<&str>, opts: Opts) -> Result<()> { +fn scan_directory(dir: PathBuf, function: Option<&str>, opts: &Args) -> Result<()> { let mut manifest_file = dir.clone(); manifest_file.push("manifest.yaml"); if !manifest_file.exists() { @@ -263,7 +245,7 @@ fn scan_directory(dir: PathBuf, function: Option<&str>, opts: Opts) -> Result<() if let Some(func) = opts.dump_ir.as_ref() { let mut lock = std::io::stdout().lock(); proj.env.dump_function(&mut lock, func); - return Ok(proj); + return Ok(()); } let permissions = Vec::from_iter(permissions_declared.iter().cloned()); @@ -272,7 +254,7 @@ fn scan_directory(dir: PathBuf, function: Option<&str>, opts: Opts) -> Result<() let (confluence_permission_resolver, confluence_regex_map) = get_permission_resolver_confluence(); - let mut defintion_analysis_interp = Interp::new( + let mut definition_analysis_interp = Interp::::new( &proj.env, false, true, @@ -303,18 +285,8 @@ fn scan_directory(dir: PathBuf, function: Option<&str>, opts: Opts) -> Result<() &confluence_permission_resolver, &confluence_regex_map, ); - let mut perm_interp = Interp::new( - &proj.env, - false, - true, - permissions.clone(), - &jira_permission_resolver, - &jira_regex_map, - &confluence_permission_resolver, - &confluence_regex_map, - ); let mut reporter = Reporter::new(); - let mut secret_interp = Interp::new( + let mut secret_interp = Interp::::new( &proj.env, false, false, @@ -324,43 +296,72 @@ fn scan_directory(dir: PathBuf, function: Option<&str>, opts: Opts) -> Result<() &confluence_permission_resolver, &confluence_regex_map, ); - let mut pp_interp = Interp::new( + reporter.add_app(opts.appkey.clone().unwrap_or_default(), name.to_owned()); + //let mut all_used_permissions = HashSet::default(); + + let mut perm_interp = Interp::::new( &proj.env, false, - false, + true, permissions.clone(), &jira_permission_resolver, &jira_regex_map, &confluence_permission_resolver, &confluence_regex_map, ); - reporter.add_app(opts.appkey.clone().unwrap_or_default(), name.to_owned()); - //let mut all_used_permissions = HashSet::default(); - for func in &proj.funcs { - // TODO: Update operations in for loop to scan functions. - // idea: iterate over each func which should be struct that tracks the function to be scanned. And performs scans according to bool. - // match *func { - // FunctionTy::Invokable((ref func, ref path, _, def)) => { - // let mut checker = AuthZChecker::new(); - // debug!("checking {func} at {path:?}"); - // if let Err(err) = interp.run_checker(def, &mut checker, path.clone(), func.clone()) - // { - // warn!("error while scanning {func} in {path:?}: {err}"); - // } - // reporter.add_vulnerabilities(checker.into_vulns()); - // } - // FunctionTy::WebTrigger((ref func, ref path, _, def)) => { - // let mut checker = AuthenticateChecker::new(); - // debug!("checking webtrigger {func} at {path:?}"); - // if let Err(err) = - // authn_interp.run_checker(def, &mut checker, path.clone(), func.clone()) - // { - // warn!("error while scanning {func} in {path:?}: {err}"); - // } - // reporter.add_vulnerabilities(checker.into_vulns()); - // } - // } + let mut def_checker = DefintionAnalysisRunner::new(); + if let Err(err) = definition_analysis_interp.run_checker( + func.def_id, + &mut def_checker, + func.path.clone(), + func.func_name.to_string(), + ) { + warn!( + "error while scanning {:?} in {:?}: {err}", + func.func_name, func.path, + ); + } + + if run_permission_checker { + let mut checker = PermissionChecker::new(); + perm_interp.value_manager.varid_to_value = + definition_analysis_interp.value_manager.varid_to_value; + perm_interp.value_manager.defid_to_value = + definition_analysis_interp.value_manager.defid_to_value; + if let Err(err) = perm_interp.run_checker( + func.def_id, + &mut checker, + func.path.clone(), + func.func_name.to_owned(), + ) { + warn!("error while running permission checker: {err}"); + } + definition_analysis_interp.value_manager.varid_to_value = + perm_interp.value_manager.varid_to_value; + definition_analysis_interp.value_manager.defid_to_value = + perm_interp.value_manager.defid_to_value; + } + + let mut checker = SecretChecker::new(); + secret_interp.value_manager.varid_to_value = + definition_analysis_interp.value_manager.varid_to_value; + secret_interp.value_manager.defid_to_value = + definition_analysis_interp.value_manager.defid_to_value; + if let Err(err) = secret_interp.run_checker( + func.def_id, + &mut checker, + func.path.clone(), + func.func_name.to_owned(), + ) { + warn!("error while running secret checker: {err}"); + } else { + reporter.add_vulnerabilities(checker.into_vulns()); + } + definition_analysis_interp.value_manager.varid_to_value = + secret_interp.value_manager.varid_to_value; + definition_analysis_interp.value_manager.defid_to_value = + secret_interp.value_manager.defid_to_value; // Get entrypoint value from tuple // Logic for performing scans. @@ -401,6 +402,11 @@ fn scan_directory(dir: PathBuf, function: Option<&str>, opts: Opts) -> Result<() reporter.add_vulnerabilities(checker.into_vulns()); } } + + if perm_interp.permissions.len() > 0 { + reporter + .add_vulnerabilities(vec![PermissionVuln::new(perm_interp.permissions)].into_iter()); + } let report = serde_json::to_string(&reporter.into_report()).into_diagnostic()?; debug!("On the debug layer: Writing Report"); match &opts.out { @@ -409,6 +415,7 @@ fn scan_directory(dir: PathBuf, function: Option<&str>, opts: Opts) -> Result<() } None => println!("{report}"), } + Ok(()) } From 741fe076344d4998cb2c59d6ca24aa7c2719d5bb Mon Sep 17 00:00:00 2001 From: Alina Wang Date: Thu, 11 Jan 2024 09:46:10 -0800 Subject: [PATCH 321/517] chore: tried rebasing to change all author's on commits. Partially worked --- crates/forge_loader/src/manifest.rs | 24 +----------------------- 1 file changed, 1 insertion(+), 23 deletions(-) diff --git a/crates/forge_loader/src/manifest.rs b/crates/forge_loader/src/manifest.rs index ce1223c8..052653a1 100644 --- a/crates/forge_loader/src/manifest.rs +++ b/crates/forge_loader/src/manifest.rs @@ -376,28 +376,6 @@ impl<'a> ForgeModules<'a> { invokable_functions.extend(access.import_status); }); - self.functions.into_iter().flat_map(move |func| { - let web_trigger = self - .webtriggers - .binary_search_by_key(&func.key, |trigger| &trigger.function) - .is_ok(); - let invokable = invokable_functions.contains(func.key); - Ok::<_, Error>(Entrypoint { - function: FunctionRef::try_from(func)?, - invokable, - web_trigger, - }) - }); - self.access_import_type.iter().for_each(|access| { - invokable_functions.insert(access.common_keys.function); - invokable_functions.extend(access.common_keys.resolver); - - invokable_functions.extend(access.one_delete_import); - invokable_functions.extend(access.stop_import); - invokable_functions.extend(access.start_import); - invokable_functions.extend(access.import_status); - }); - self.functions.into_iter().flat_map(move |func| { let web_trigger = self .webtriggers @@ -635,4 +613,4 @@ mod tests { assert_eq!(func, "Catch-me-if-you-can3"); } } -}} +} From e9f8a0bda9268c448b7118fa26f694ec341a7eec Mon Sep 17 00:00:00 2001 From: Alina Wang Date: Thu, 11 Jan 2024 14:46:23 -0800 Subject: [PATCH 322/517] chore: resetting head on EAS-1673 to minimize commits --- crates/forge_loader/src/manifest.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/crates/forge_loader/src/manifest.rs b/crates/forge_loader/src/manifest.rs index aa3def1c..1adf0592 100644 --- a/crates/forge_loader/src/manifest.rs +++ b/crates/forge_loader/src/manifest.rs @@ -457,7 +457,6 @@ mod tests { { "key": "my-macro", "function": "My Macro" - "function": "My Macro" } ], "function": [ From e1b6f5b30e21169d569c3920eb2ecc49c78f926f Mon Sep 17 00:00:00 2001 From: Alina Wang Date: Thu, 11 Jan 2024 16:20:45 -0800 Subject: [PATCH 323/517] chore: resolved error. Fixed local tests that were failing --- crates/forge_analyzer/src/definitions.rs | 2 +- crates/forge_loader/src/manifest.rs | 52 +++++++++--------------- 2 files changed, 20 insertions(+), 34 deletions(-) diff --git a/crates/forge_analyzer/src/definitions.rs b/crates/forge_analyzer/src/definitions.rs index fc54b198..c40ab4ba 100644 --- a/crates/forge_analyzer/src/definitions.rs +++ b/crates/forge_analyzer/src/definitions.rs @@ -213,6 +213,7 @@ pub fn run_resolver( for (curr_mod, module) in modules.iter_enumerated() { let mut collector = FunctionCollector { res: &mut environment, + file_resolver, curr_class: None, curr_function: None, secret_packages: secret_packages.clone(), // remove the clone @@ -2914,7 +2915,6 @@ impl ExportCollector<'_> { self.res_table.owning_module.push(self.curr_mod); self.default = Some(defid); } - } // Import collector for run_resolver diff --git a/crates/forge_loader/src/manifest.rs b/crates/forge_loader/src/manifest.rs index bb00106a..75b14489 100644 --- a/crates/forge_loader/src/manifest.rs +++ b/crates/forge_loader/src/manifest.rs @@ -5,10 +5,9 @@ use std::{ path::{Path, PathBuf}, }; - -use crate::{forgepermissions::ForgePermissions, Error}; +use crate::Error; use forge_utils::FxHashMap; -use itertools::{Either, Itertools}; +use itertools::Itertools; use serde::Deserialize; use tracing::trace; @@ -36,6 +35,7 @@ struct CommonKey<'a> { #[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] struct MacroMod<'a> { + #[serde(flatten, borrow)] common_keys: CommonKey<'a>, config: Option<&'a str>, export: Option<&'a str>, @@ -135,7 +135,6 @@ pub struct WorkflowPostFunction<'a> { } // Add more structs here for deserializing forge modules - #[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] pub struct ForgeModules<'a> { // deserializing non user-invocable modules @@ -174,7 +173,6 @@ pub struct ForgeModules<'a> { #[serde(flatten)] extra: FxHashMap>>, } - #[derive(Debug, Clone, PartialEq, Eq, Deserialize)] pub struct JiraAdminPage<'a> { key: &'a str, @@ -289,6 +287,13 @@ impl<'a> ForgeModules<'a> { // number of webtriggers are usually low, so it's better to just sort them and reuse self.webtriggers .sort_unstable_by_key(|trigger| trigger.function); + self.webtriggers + .sort_unstable_by_key(|trigger| trigger.function); + + // Get all the Triggers and represent them as a new struct thing where "webtrigger" attribute is true + // for all trigger things + // Get all the Triggers and represent them as a new struct thing where "webtrigger" attribute is true + // for all trigger things let mut invokable_functions = BTreeSet::new(); @@ -373,7 +378,6 @@ impl<'a> ForgeModules<'a> { web_trigger, admin, }) - }) } } @@ -490,7 +494,7 @@ mod tests { assert_eq!(manifest.app.name, Some("My App")); assert_eq!(manifest.app.id, "my-app"); assert_eq!(manifest.modules.macros.len(), 1); - assert_eq!(manifest.modules.macros[0].common_keys.key, "My Macro"); + assert_eq!(manifest.modules.macros[0].common_keys.key, "my-macro"); // assert_eq!(manifest.modules.macros[0].function, "my-macro"); assert_eq!(manifest.modules.functions.len(), 1); assert_eq!( @@ -540,15 +544,9 @@ mod tests { "key": "my-macro", "title": "My Macro", "function": "Catch-me-if-you-can0", - "resolver": { - "function": "Catch-me-if-you-can1" - }, - "config": { - "function": "Catch-me-if-you-can2" - }, - "export": { - "function": "Catch-me-if-you-can3" - } + "resolver": "Catch-me-if-you-can1", + "config": "Catch-me-if-you-can2", + "export": "Catch-me-if-you-can3" } ], "function": [ @@ -588,7 +586,12 @@ mod tests { "Catch-me-if-you-can0" ); + let Some(func_test) = manifest.modules.macros[0].common_keys.resolver else { + panic!("No dice!") + }; + println!("what is func? {}", func_test); if let Some(func) = manifest.modules.macros[0].common_keys.resolver { + println!("what is func? {}", func); assert_eq!(func, "Catch-me-if-you-can1"); } @@ -680,22 +683,5 @@ mod tests { admin: false }) ); - - // assert_eq!(manifest.app.name, Some("My App")); - // assert_eq!(manifest.app.id, "my-app"); - // assert_eq!(manifest.modules.macros.len(), 1); - // assert_eq!(manifest.modules.macros[0].common_keys.key, "My Macro"); - // // assert_eq!(manifest.modules.macros[0].function, "my-macro"); - // assert_eq!(manifest.modules.functions.len(), 1); - // assert_eq!( - // manifest.modules.functions[0], - // FunctionMod { - // key: "my-function", - // handler: "my-function-handler", - // providers: Some(AuthProviders { - // auth: vec!["my-auth-provider"] - // }), - // } - // ); } } From 11aff529103e597d80ec37c6cf5b55f2da80cd08 Mon Sep 17 00:00:00 2001 From: Alina Wang Date: Thu, 11 Jan 2024 17:21:41 -0800 Subject: [PATCH 324/517] chore: resolved linter error msg --- crates/fsrt/src/main.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/crates/fsrt/src/main.rs b/crates/fsrt/src/main.rs index da196447..ecac3636 100644 --- a/crates/fsrt/src/main.rs +++ b/crates/fsrt/src/main.rs @@ -361,7 +361,6 @@ fn scan_directory(dir: PathBuf, function: Option<&str>, opts: &Args) -> Result<( warn!("error while running secret checker: {err}"); } else { reporter.add_vulnerabilities(checker.into_vulns()); - } definition_analysis_interp.value_manager.varid_to_value = secret_interp.value_manager.varid_to_value; @@ -418,7 +417,6 @@ fn scan_directory(dir: PathBuf, function: Option<&str>, opts: &Args) -> Result<( } Ok(()) - } fn main() -> Result<()> { From 467eb549c7fab376514b0ae55af50c2552f2ab89 Mon Sep 17 00:00:00 2001 From: Alina Wang Date: Fri, 26 Jan 2024 12:50:05 -0500 Subject: [PATCH 325/517] feat: resolving PR comments in progress --- crates/forge_loader/src/manifest.rs | 218 +++++++++++++++------------- 1 file changed, 118 insertions(+), 100 deletions(-) diff --git a/crates/forge_loader/src/manifest.rs b/crates/forge_loader/src/manifest.rs index 75b14489..878d74f8 100644 --- a/crates/forge_loader/src/manifest.rs +++ b/crates/forge_loader/src/manifest.rs @@ -16,63 +16,77 @@ struct AuthProviders<'a> { #[serde(borrow)] auth: Vec<&'a str>, } -// Maps the Functions Module in common Modules -#[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] -pub struct FunctionMod<'a> { - key: &'a str, - handler: &'a str, - #[serde(borrow)] - providers: Option>, -} // Abstracting away key, function, and resolver into a single struct for reuse whoo! +// And helper functions for ease of use #[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] struct CommonKey<'a> { key: &'a str, - function: &'a str, - resolver: Option<&'a str>, + resolver: Option>, +} + +impl<'a> CommonKey<'a> { + fn append_functions + Extend>( + &self, + funcs: &mut I, + ) { + funcs.extend(self.key); + if let Some(Resolver { + function, + method, + endpoint, + }) = self.resolver + { + funcs.extend(function); + } + } +} +#[derive(Debug, Clone, PartialEq, Eq, Deserialize)] +pub struct Resolver<'a> { + pub function: Option<&'a str>, + pub method: Option<&'a str>, + pub endpoint: Option<&'a str>, } +// Common Modules #[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] -struct MacroMod<'a> { - #[serde(flatten, borrow)] - common_keys: CommonKey<'a>, - config: Option<&'a str>, - export: Option<&'a str>, +pub struct FunctionMod<'a> { + key: &'a str, + handler: &'a str, + #[serde(borrow)] + providers: Option>, } -#[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] -struct ContentByLineItem<'a> { - #[serde(flatten, borrow)] - common_keys: CommonKey<'a>, +#[derive(Debug, Clone, PartialEq, Eq, Deserialize)] +pub struct Consumer<'a> { + key: &'a str, + queue: &'a str, #[serde(borrow)] - dynamic_properties: Option<&'a str>, + pub resolver: Resolver<'a>, } -#[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] -struct IssueGlance<'a> { - #[serde(flatten, borrow)] - common_keys: CommonKey<'a>, - dynamic_properties: Option<&'a str>, +// Trigger Modules +#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize)] +#[serde(rename_all = "lowercase")] +enum Interval { + Hour, + Day, + Week, } -#[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] -struct AccessImportType<'a> { + +#[derive(Debug, Clone, PartialEq, Eq, Deserialize)] +struct ScheduledTrigger<'a> { #[serde(flatten, borrow)] - common_keys: CommonKey<'a>, - one_delete_import: Option<&'a str>, - start_import: Option<&'a str>, - stop_import: Option<&'a str>, - import_status: Option<&'a str>, + raw: RawTrigger<'a>, + interval: Interval, } -// WebTrigger => RawTrigger; WHY IS THIS NAMED DIFFERENTLY !? WHO CHANGED NAMES #[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] struct RawTrigger<'a> { key: &'a str, function: &'a str, } -// Trigger => EventTriger; WHY IS THIS NAMED DIFFERENTLY !? WHO CHANGED NAMES #[derive(Debug, Clone, PartialEq, Eq, Deserialize)] struct EventTrigger<'a> { #[serde(flatten, borrow)] @@ -81,34 +95,40 @@ struct EventTrigger<'a> { events: Vec<&'a str>, } -#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize)] -#[serde(rename_all = "lowercase")] -enum Interval { - Hour, - Day, - Week, +// Confluence Modules +#[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] +struct MacroMod<'a> { + #[serde(flatten, borrow)] + common_keys: CommonKey<'a>, + config: Option<&'a str>, + export: Option<&'a str>, } -// Thank you to whomeever kept this one the same. T.T -#[derive(Debug, Clone, PartialEq, Eq, Deserialize)] -struct ScheduledTrigger<'a> { +#[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] +struct ContentByLineItem<'a> { #[serde(flatten, borrow)] - raw: RawTrigger<'a>, - interval: Interval, + common_keys: CommonKey<'a>, + #[serde(borrow)] + dynamic_properties: Option<&'a str>, } -// compass DataProvider module +// Jira Modules #[derive(Debug, Clone, PartialEq, Eq, Deserialize)] -pub struct DataProvider<'a> { - #[serde(flatten, borrow)] +pub struct JiraAdminPage<'a> { key: &'a str, - callback: Option<&'a str>, + function: &'a str, + title: &'a str, +} + +#[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] +struct IssueGlance<'a> { + #[serde(flatten, borrow)] + common_keys: CommonKey<'a>, + dynamic_properties: Option<&'a str>, } -// Struct for Custom field Module. Check that search suggestion gets read in correctly. #[derive(Debug, Clone, PartialEq, Eq, Deserialize)] pub struct CustomField<'a> { - // all attributes below involve function calls #[serde(flatten, borrow)] common_keys: CommonKey<'a>, value: Option<&'a str>, @@ -133,66 +153,67 @@ pub struct WorkflowPostFunction<'a> { #[serde(flatten, borrow)] common_keys: CommonKey<'a>, } +// Jira Service Management Modules +#[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] +struct AccessImportType<'a> { + #[serde(flatten, borrow)] + common_keys: CommonKey<'a>, + one_delete_import: Option<&'a str>, + start_import: Option<&'a str>, + stop_import: Option<&'a str>, + import_status: Option<&'a str>, +} + +// Compass Modules +#[derive(Debug, Clone, PartialEq, Eq, Deserialize)] +pub struct DataProvider<'a> { + #[serde(flatten, borrow)] + key: &'a str, + callback: Option<&'a str>, +} // Add more structs here for deserializing forge modules #[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] pub struct ForgeModules<'a> { // deserializing non user-invocable modules - #[serde(rename = "macro", default, borrow)] - macros: Vec>, + // Common Modules including triggers + #[serde(rename = "consumer", default, borrow)] + pub consumers: Vec>, #[serde(rename = "function", default, borrow)] pub functions: Vec>, - #[serde(rename = "contentByLineItem", default, borrow)] - content_by_line_item: Vec>, - #[serde(rename = "jira:issueGlance", default, borrow)] - issue_glance: Vec>, - #[serde(rename = "jira:accessImportType", default, borrow)] - access_import_type: Vec>, - // deserializing non user-invocable modules #[serde(rename = "webtrigger", default, borrow)] webtriggers: Vec>, #[serde(rename = "trigger", default, borrow)] event_triggers: Vec>, #[serde(rename = "scheduledTrigger", default, borrow)] scheduled_triggers: Vec>, - #[serde(rename = "consumer", default, borrow)] - pub consumers: Vec>, - #[serde(rename = "compass:dataProvider", default, borrow)] - pub data_provider: Vec>, + // confluence Modules + #[serde(rename = "contentByLineItem", default, borrow)] + content_by_line_item: Vec>, + #[serde(rename = "macro", default, borrow)] + macros: Vec>, + // jira modules #[serde(rename = "jira:customField", default, borrow)] pub custom_field: Vec>, + #[serde(rename = "jira:issueGlance", default, borrow)] + issue_glance: Vec>, + #[serde(rename = "jira:accessImportType", default, borrow)] + access_import_type: Vec>, #[serde(rename = "jira:uiModificatons", default, borrow)] pub ui_modifications: Vec>, #[serde(rename = "jira:workflowValidator", default, borrow)] pub workflow_validator: Vec>, #[serde(rename = "jira:workflowPostFunction", default, borrow)] pub workflow_post_function: Vec>, + // Compass Modules + #[serde(rename = "compass:dataProvider", default, borrow)] + pub data_provider: Vec>, // deserializing admin pages #[serde(rename = "jira:adminPage", default, borrow)] pub jira_admin: Vec>, #[serde(flatten)] extra: FxHashMap>>, } -#[derive(Debug, Clone, PartialEq, Eq, Deserialize)] -pub struct JiraAdminPage<'a> { - key: &'a str, - function: &'a str, - title: &'a str, -} - -#[derive(Debug, Clone, PartialEq, Eq, Deserialize)] -pub struct Consumer<'a> { - key: &'a str, - queue: &'a str, - #[serde(borrow)] - pub resolver: Resolver<'a>, -} - -#[derive(Debug, Clone, PartialEq, Eq, Deserialize)] -pub struct Resolver<'a> { - pub function: &'a str, - method: Option<&'a str>, -} #[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] struct Content<'a> { @@ -287,11 +308,6 @@ impl<'a> ForgeModules<'a> { // number of webtriggers are usually low, so it's better to just sort them and reuse self.webtriggers .sort_unstable_by_key(|trigger| trigger.function); - self.webtriggers - .sort_unstable_by_key(|trigger| trigger.function); - - // Get all the Triggers and represent them as a new struct thing where "webtrigger" attribute is true - // for all trigger things // Get all the Triggers and represent them as a new struct thing where "webtrigger" attribute is true // for all trigger things @@ -306,21 +322,21 @@ impl<'a> ForgeModules<'a> { invokable_functions.extend(customfield.search_suggestions); invokable_functions.extend(customfield.edit); - invokable_functions.insert(customfield.common_keys.function); - invokable_functions.extend(customfield.common_keys.resolver); + invokable_functions.extend(customfield.common_keys.function); + invokable_functions.extend(customfield.common_keys.function); }); self.ui_modifications.iter().for_each(|ui| { - invokable_functions.insert(ui.common_keys.function); + invokable_functions.extend(ui.common_keys.function); invokable_functions.extend(ui.common_keys.resolver); }); self.workflow_validator.iter().for_each(|validator| { invokable_functions.insert(validator.common_keys.key); - invokable_functions.insert(validator.common_keys.function); + invokable_functions.extend(validator.common_keys.function); - invokable_functions.extend(validator.common_keys.resolver); + invokable_functions.extend(validator.common_keys.endpoint); }); self.workflow_post_function @@ -328,9 +344,9 @@ impl<'a> ForgeModules<'a> { .for_each(|post_function| { invokable_functions.insert(post_function.common_keys.key); - invokable_functions.insert(post_function.common_keys.function); + invokable_functions.extend(post_function.common_keys.resolver.function); - invokable_functions.extend(post_function.common_keys.resolver); + // invokable_functions.extend(post_function.common_keys.function); }); // get user invokable modules that have additional exposure endpoints. @@ -338,7 +354,7 @@ impl<'a> ForgeModules<'a> { self.macros.iter().for_each(|macros| { invokable_functions.insert(macros.common_keys.key); - invokable_functions.insert(macros.common_keys.function); + invokable_functions.extend(macros.common_keys.function); invokable_functions.extend(macros.common_keys.resolver); invokable_functions.extend(macros.config); @@ -346,13 +362,13 @@ impl<'a> ForgeModules<'a> { }); self.issue_glance.iter().for_each(|issue| { - invokable_functions.insert(issue.common_keys.function); + invokable_functions.extend(issue.common_keys.function); invokable_functions.extend(issue.common_keys.resolver); invokable_functions.extend(issue.dynamic_properties); }); self.access_import_type.iter().for_each(|access| { - invokable_functions.insert(access.common_keys.function); + invokable_functions.extend(access.common_keys.function); invokable_functions.extend(access.common_keys.resolver); invokable_functions.extend(access.one_delete_import); @@ -544,7 +560,9 @@ mod tests { "key": "my-macro", "title": "My Macro", "function": "Catch-me-if-you-can0", - "resolver": "Catch-me-if-you-can1", + "resolver": [ + "function": "Catch-me-if-you-can1", + ] "config": "Catch-me-if-you-can2", "export": "Catch-me-if-you-can3" } From ed999b9d532becbfc3ea5600bd46074cfbaa74e5 Mon Sep 17 00:00:00 2001 From: Alina Wang Date: Mon, 5 Feb 2024 15:22:21 -0500 Subject: [PATCH 326/517] feat: implmented helper function append_function. And updated existing modules to add to Btreeset with helper function --- crates/forge_loader/src/manifest.rs | 69 +++++++++++++---------------- 1 file changed, 30 insertions(+), 39 deletions(-) diff --git a/crates/forge_loader/src/manifest.rs b/crates/forge_loader/src/manifest.rs index 878d74f8..acdf93f5 100644 --- a/crates/forge_loader/src/manifest.rs +++ b/crates/forge_loader/src/manifest.rs @@ -9,7 +9,8 @@ use crate::Error; use forge_utils::FxHashMap; use itertools::Itertools; use serde::Deserialize; -use tracing::trace; +use serde_json::map::Iter; +use tracing::{info, trace}; #[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] struct AuthProviders<'a> { @@ -22,15 +23,14 @@ struct AuthProviders<'a> { #[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] struct CommonKey<'a> { key: &'a str, + function: Option<&'a str>, resolver: Option>, } impl<'a> CommonKey<'a> { - fn append_functions + Extend>( - &self, - funcs: &mut I, - ) { - funcs.extend(self.key); + fn append_functions>(&self, funcs: &mut I) { + funcs.extend(self.function); + if let Some(Resolver { function, method, @@ -321,56 +321,50 @@ impl<'a> ForgeModules<'a> { invokable_functions.extend(customfield.value); invokable_functions.extend(customfield.search_suggestions); invokable_functions.extend(customfield.edit); - - invokable_functions.extend(customfield.common_keys.function); - invokable_functions.extend(customfield.common_keys.function); + customfield + .common_keys + .append_functions(&mut invokable_functions); }); self.ui_modifications.iter().for_each(|ui| { - invokable_functions.extend(ui.common_keys.function); - invokable_functions.extend(ui.common_keys.resolver); + ui.common_keys.append_functions(&mut invokable_functions); }); self.workflow_validator.iter().for_each(|validator| { - invokable_functions.insert(validator.common_keys.key); - - invokable_functions.extend(validator.common_keys.function); - - invokable_functions.extend(validator.common_keys.endpoint); + validator + .common_keys + .append_functions(&mut invokable_functions) }); self.workflow_post_function .iter() .for_each(|post_function| { - invokable_functions.insert(post_function.common_keys.key); - - invokable_functions.extend(post_function.common_keys.resolver.function); - - // invokable_functions.extend(post_function.common_keys.function); + post_function + .common_keys + .append_functions(&mut invokable_functions); }); // get user invokable modules that have additional exposure endpoints. // ie macros has config and export fields on top of resolver fields that are functions self.macros.iter().for_each(|macros| { invokable_functions.insert(macros.common_keys.key); - - invokable_functions.extend(macros.common_keys.function); - invokable_functions.extend(macros.common_keys.resolver); + macros + .common_keys + .append_functions(&mut invokable_functions); invokable_functions.extend(macros.config); invokable_functions.extend(macros.export); }); self.issue_glance.iter().for_each(|issue| { - invokable_functions.extend(issue.common_keys.function); - invokable_functions.extend(issue.common_keys.resolver); + issue.common_keys.append_functions(&mut invokable_functions); invokable_functions.extend(issue.dynamic_properties); }); self.access_import_type.iter().for_each(|access| { - invokable_functions.extend(access.common_keys.function); - invokable_functions.extend(access.common_keys.resolver); - + access + .common_keys + .append_functions(&mut invokable_functions); invokable_functions.extend(access.one_delete_import); invokable_functions.extend(access.stop_import); invokable_functions.extend(access.start_import); @@ -599,20 +593,17 @@ mod tests { }"#; let manifest: ForgeManifest = serde_json::from_str(json).unwrap(); assert_eq!(manifest.modules.macros.len(), 1); - assert_eq!( - manifest.modules.macros[0].common_keys.function, - "Catch-me-if-you-can0" - ); + if let Some(string) = manifest.modules.macros[0].common_keys.function { + assert_eq!(string, "Catch-me-if-you-can0"); + } - let Some(func_test) = manifest.modules.macros[0].common_keys.resolver else { + let Some(ref resolver) = manifest.modules.macros[0].common_keys.resolver else { panic!("No dice!") }; - println!("what is func? {}", func_test); - if let Some(func) = manifest.modules.macros[0].common_keys.resolver { - println!("what is func? {}", func); - assert_eq!(func, "Catch-me-if-you-can1"); - } + if let Some(string) = resolver.function { + assert_eq!(string, "Catch-me-if-you-can1"); + } if let Some(func) = manifest.modules.macros[0].config { assert_eq!(func, "Catch-me-if-you-can2"); } From 52ed5d8fe32a678aa02d55abd966e5201e14939b Mon Sep 17 00:00:00 2001 From: Alina Wang Date: Mon, 5 Feb 2024 17:27:18 -0500 Subject: [PATCH 327/517] fix: added compass and confluence modules to get deserialized. Todo: finish updating into_analyzable_functions to add functions from new modules. Then add jira modules and functions associated --- crates/forge_loader/src/manifest.rs | 211 ++++++++++++++++++++-------- 1 file changed, 150 insertions(+), 61 deletions(-) diff --git a/crates/forge_loader/src/manifest.rs b/crates/forge_loader/src/manifest.rs index acdf93f5..bc5150dc 100644 --- a/crates/forge_loader/src/manifest.rs +++ b/crates/forge_loader/src/manifest.rs @@ -41,13 +41,20 @@ impl<'a> CommonKey<'a> { } } } -#[derive(Debug, Clone, PartialEq, Eq, Deserialize)] +#[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] pub struct Resolver<'a> { pub function: Option<&'a str>, pub method: Option<&'a str>, pub endpoint: Option<&'a str>, } +// Implementing a struct for structs with 1 value (function) + +#[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] +pub struct JustFunc<'a> { + pub function: Option<&'a str>, +} + // Common Modules #[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] pub struct FunctionMod<'a> { @@ -74,6 +81,7 @@ enum Interval { Week, } +// Maps to Scheduled Trigger under Common Modules #[derive(Debug, Clone, PartialEq, Eq, Deserialize)] struct ScheduledTrigger<'a> { #[serde(flatten, borrow)] @@ -81,12 +89,14 @@ struct ScheduledTrigger<'a> { interval: Interval, } +// Maps to Web Trigger under Common Modules #[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] struct RawTrigger<'a> { key: &'a str, function: &'a str, } +// maps to Trigger under Common Modules #[derive(Debug, Clone, PartialEq, Eq, Deserialize)] struct EventTrigger<'a> { #[serde(flatten, borrow)] @@ -95,21 +105,57 @@ struct EventTrigger<'a> { events: Vec<&'a str>, } -// Confluence Modules +// Compass Modules #[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] -struct MacroMod<'a> { +pub struct CompassAdminPage<'a> { + #[serde(flatten, borrow)] + common_keys: CommonKey<'a>, +} + +#[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] +pub struct ComponentPage<'a> { + #[serde(flatten, borrow)] + common_keys: CommonKey<'a>, +} +#[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] +pub struct DataProvider<'a> { + #[serde(flatten, borrow)] + common_keys: CommonKey<'a>, + callback: JustFunc<'a>, +} + +#[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] +pub struct CompassGlobalPage<'a> { + #[serde(flatten, borrow)] + common_keys: CommonKey<'a>, +} + +#[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] +pub struct TeamPage<'a> { #[serde(flatten, borrow)] common_keys: CommonKey<'a>, - config: Option<&'a str>, - export: Option<&'a str>, } +// Confluence Modules +#[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] +struct ContentAction<'a> { + #[serde(flatten, borrow)] + common_keys: CommonKey<'a>, +} #[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] struct ContentByLineItem<'a> { #[serde(flatten, borrow)] common_keys: CommonKey<'a>, #[serde(borrow)] - dynamic_properties: Option<&'a str>, + dynamic_properties: JustFunc<'a>, +} + +#[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] +struct MacroMod<'a> { + #[serde(flatten, borrow)] + common_keys: CommonKey<'a>, + config: JustFunc<'a>, + export: JustFunc<'a>, } // Jira Modules @@ -118,13 +164,7 @@ pub struct JiraAdminPage<'a> { key: &'a str, function: &'a str, title: &'a str, -} - -#[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] -struct IssueGlance<'a> { - #[serde(flatten, borrow)] - common_keys: CommonKey<'a>, - dynamic_properties: Option<&'a str>, + resolver: Resolver<'a>, } #[derive(Debug, Clone, PartialEq, Eq, Deserialize)] @@ -136,6 +176,13 @@ pub struct CustomField<'a> { edit: Option<&'a str>, } +#[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] +struct IssueGlance<'a> { + #[serde(flatten, borrow)] + common_keys: CommonKey<'a>, + dynamic_properties: JustFunc<'a>, +} + #[derive(Debug, Clone, PartialEq, Eq, Deserialize)] pub struct UiModificatons<'a> { #[serde(flatten, borrow)] @@ -155,21 +202,13 @@ pub struct WorkflowPostFunction<'a> { } // Jira Service Management Modules #[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] -struct AccessImportType<'a> { +struct AssetsImportType<'a> { #[serde(flatten, borrow)] common_keys: CommonKey<'a>, - one_delete_import: Option<&'a str>, - start_import: Option<&'a str>, - stop_import: Option<&'a str>, - import_status: Option<&'a str>, -} - -// Compass Modules -#[derive(Debug, Clone, PartialEq, Eq, Deserialize)] -pub struct DataProvider<'a> { - #[serde(flatten, borrow)] - key: &'a str, - callback: Option<&'a str>, + one_delete_import: JustFunc<'a>, + start_import: JustFunc<'a>, + stop_import: JustFunc<'a>, + import_status: JustFunc<'a>, } // Add more structs here for deserializing forge modules @@ -187,9 +226,32 @@ pub struct ForgeModules<'a> { event_triggers: Vec>, #[serde(rename = "scheduledTrigger", default, borrow)] scheduled_triggers: Vec>, + // Compass Modules + #[serde(rename = "compass:adminPage", default, borrow)] + compass_admin_page: Vec>, + #[serde(rename = "compass:componentPage", default, borrow)] + component_page: Vec>, + #[serde(rename = "compass:dataProvider", default, borrow)] + pub data_provider: Vec>, + #[serde(rename = "compass:globalPage", default, borrow)] + compass_global_page: Vec>, + #[serde(rename = "compass:teamPage", default, borrow)] + team_page: Vec>, // confluence Modules - #[serde(rename = "contentByLineItem", default, borrow)] + #[serde(rename = "confluence:contentAction", default, borrow)] + content_action: Vec>, + #[serde(rename = "confluence:contentByLineItem", default, borrow)] content_by_line_item: Vec>, + #[serde(rename = "confluence:contextMenu", default, borrow)] + context_action: Vec>, + #[serde(rename = "confluence:globalPage", default, borrow)] + confluence_global_page: Vec>, + #[serde(rename = "confluence:homepageFeed", default, borrow)] + homepage_feed: Vec>, + #[serde(rename = "confluence:spacePage", default, borrow)] + space_page: Vec>, + #[serde(rename = "confluence:spaceSettings", default, borrow)] + space_settings: Vec>, #[serde(rename = "macro", default, borrow)] macros: Vec>, // jira modules @@ -197,20 +259,18 @@ pub struct ForgeModules<'a> { pub custom_field: Vec>, #[serde(rename = "jira:issueGlance", default, borrow)] issue_glance: Vec>, - #[serde(rename = "jira:accessImportType", default, borrow)] - access_import_type: Vec>, #[serde(rename = "jira:uiModificatons", default, borrow)] pub ui_modifications: Vec>, #[serde(rename = "jira:workflowValidator", default, borrow)] pub workflow_validator: Vec>, #[serde(rename = "jira:workflowPostFunction", default, borrow)] pub workflow_post_function: Vec>, - // Compass Modules - #[serde(rename = "compass:dataProvider", default, borrow)] - pub data_provider: Vec>, + // Jira Service Management Modules + #[serde(rename = "jiraServiceManagement:assetsImportType", default, borrow)] + access_import_type: Vec>, // deserializing admin pages #[serde(rename = "jira:adminPage", default, borrow)] - pub jira_admin: Vec>, + pub jira_admin_page: Vec>, #[serde(flatten)] extra: FxHashMap>>, } @@ -313,10 +373,61 @@ impl<'a> ForgeModules<'a> { let mut invokable_functions = BTreeSet::new(); + // Compass Module Functions + self.compass_admin_page + .iter() + .for_each(|compass_admin| compass_admin.append_functions(&mut invokable_functions)); + + self.component_page + .iter() + .for_each(|component_page| component_page.append_functions(&mut invokable_functions)); + self.data_provider.iter().for_each(|dataprovider| { - invokable_functions.extend(dataprovider.callback); + invokable_functions.extend(dataprovider.callback.function); + }); + + self.compass_global_page + .iter() + .for_each(|global_page| global_page.append_functions(&mut invokable_functions)); + + self.team_page + .iter() + .for_each(|team_page| team_page.append_functions(&mut invokable_functions)); + + // Confluence Module Functions + // get user invokable modules that have additional exposure endpoints. + // ie macros has config and export fields on top of resolver fields that are functions + self.content_action + .iter() + .for_each(|content_action| content_action.append_functions(&mut invokable_functions)); + + self.content_by_line_item.iter().for_each(|by_line_item| { + by_line_item + .common_keys + .append_functions(&mut invokable_functions); + invokable_functions.extend(by_line_item.dynamic_properties.function) + }); + + self.macros.iter().for_each(|macros| { + macros + .common_keys + .append_functions(&mut invokable_functions); + + invokable_functions.extend(macros.config.function); + invokable_functions.extend(macros.export.function); + }); + + self.access_import_type.iter().for_each(|access| { + access + .common_keys + .append_functions(&mut invokable_functions); + invokable_functions.extend(access.one_delete_import.function); + invokable_functions.extend(access.stop_import.function); + invokable_functions.extend(access.start_import.function); + invokable_functions.extend(access.import_status.function); }); + // Jira module functons self.custom_field.iter().for_each(|customfield| { invokable_functions.extend(customfield.value); invokable_functions.extend(customfield.search_suggestions); @@ -325,6 +436,10 @@ impl<'a> ForgeModules<'a> { .common_keys .append_functions(&mut invokable_functions); }); + self.issue_glance.iter().for_each(|issue| { + issue.common_keys.append_functions(&mut invokable_functions); + invokable_functions.extend(issue.dynamic_properties.function); + }); self.ui_modifications.iter().for_each(|ui| { ui.common_keys.append_functions(&mut invokable_functions); @@ -344,32 +459,6 @@ impl<'a> ForgeModules<'a> { .append_functions(&mut invokable_functions); }); - // get user invokable modules that have additional exposure endpoints. - // ie macros has config and export fields on top of resolver fields that are functions - self.macros.iter().for_each(|macros| { - invokable_functions.insert(macros.common_keys.key); - macros - .common_keys - .append_functions(&mut invokable_functions); - - invokable_functions.extend(macros.config); - invokable_functions.extend(macros.export); - }); - - self.issue_glance.iter().for_each(|issue| { - issue.common_keys.append_functions(&mut invokable_functions); - invokable_functions.extend(issue.dynamic_properties); - }); - - self.access_import_type.iter().for_each(|access| { - access - .common_keys - .append_functions(&mut invokable_functions); - invokable_functions.extend(access.one_delete_import); - invokable_functions.extend(access.stop_import); - invokable_functions.extend(access.start_import); - invokable_functions.extend(access.import_status); - }); self.functions.into_iter().flat_map(move |func| { let web_trigger = self .webtriggers @@ -379,7 +468,7 @@ impl<'a> ForgeModules<'a> { // this checks whether the funton being scanned is being used in an admin module. Rn it only checks for jira_admin page module. // optionally: compass:adminPage could also be considered. let admin = self - .jira_admin + .jira_admin_page .iter() .any(|admin_function| admin_function.function == func.key); Ok::<_, Error>(Entrypoint { From ac8c09aad8d24fa263d0d0b690841d774fce6055 Mon Sep 17 00:00:00 2001 From: Alina Wang Date: Mon, 5 Feb 2024 22:02:10 -0500 Subject: [PATCH 328/517] feat: added rest of jira modules. Updated into_analyzable_functions to accomodate functions from new modules. --- crates/forge_loader/src/manifest.rs | 150 ++++++++++++++++++++++++---- 1 file changed, 132 insertions(+), 18 deletions(-) diff --git a/crates/forge_loader/src/manifest.rs b/crates/forge_loader/src/manifest.rs index bc5150dc..bab4555e 100644 --- a/crates/forge_loader/src/manifest.rs +++ b/crates/forge_loader/src/manifest.rs @@ -176,8 +176,24 @@ pub struct CustomField<'a> { edit: Option<&'a str>, } +#[derive(Debug, Clone, PartialEq, Eq, Deserialize)] +pub struct CustomFieldType<'a> { + #[serde(flatten, borrow)] + common_keys: CommonKey<'a>, + value: Option<&'a str>, + edit: Option<&'a str>, + context_config: Option<&'a str>, +} + #[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] -struct IssueGlance<'a> { +pub struct DashboardGadget<'a> { + #[serde(flatten, borrow)] + common_keys: CommonKey<'a>, + edit: Option<&'a str>, +} + +#[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] +struct IssueClass<'a> { #[serde(flatten, borrow)] common_keys: CommonKey<'a>, dynamic_properties: JustFunc<'a>, @@ -243,7 +259,7 @@ pub struct ForgeModules<'a> { #[serde(rename = "confluence:contentByLineItem", default, borrow)] content_by_line_item: Vec>, #[serde(rename = "confluence:contextMenu", default, borrow)] - context_action: Vec>, + context_menu: Vec>, #[serde(rename = "confluence:globalPage", default, borrow)] confluence_global_page: Vec>, #[serde(rename = "confluence:homepageFeed", default, borrow)] @@ -255,10 +271,34 @@ pub struct ForgeModules<'a> { #[serde(rename = "macro", default, borrow)] macros: Vec>, // jira modules + #[serde(rename = "jira:adminPage", default, borrow)] + pub jira_admin_page: Vec>, #[serde(rename = "jira:customField", default, borrow)] pub custom_field: Vec>, + #[serde(rename = "jira:customFieldType", default, borrow)] + custom_field_type: Vec>, + #[serde(rename = "jira:dashboardBackgroundScript", default, borrow)] + dashboard_background_script: Vec>, + #[serde(rename = "jira:dashboardGadget", default, borrow)] + dashboard_gadget: Vec>, + #[serde(rename = "jira:globalPage", default, borrow)] + jira_global_page: Vec>, + #[serde(rename = "jira:issueAction", default, borrow)] + issue_action: Vec>, + #[serde(rename = "jira:issueContext", default, borrow)] + issue_context: Vec>, #[serde(rename = "jira:issueGlance", default, borrow)] - issue_glance: Vec>, + issue_glance: Vec>, + #[serde(rename = "jira:issuePanel", default, borrow)] + issue_panel: Vec>, + #[serde(rename = "jira:issueViewBackgroundScript", default, borrow)] + issue_view_background_script: Vec>, + #[serde(rename = "jira:jqlFunction", default, borrow)] + jql_function: Vec>, + #[serde(rename = "jira:projectPage", default, borrow)] + project_page: Vec>, + #[serde(rename = "jira:projectSettingsPage", default, borrow)] + project_settings_page: Vec>, #[serde(rename = "jira:uiModificatons", default, borrow)] pub ui_modifications: Vec>, #[serde(rename = "jira:workflowValidator", default, borrow)] @@ -267,10 +307,8 @@ pub struct ForgeModules<'a> { pub workflow_post_function: Vec>, // Jira Service Management Modules #[serde(rename = "jiraServiceManagement:assetsImportType", default, borrow)] - access_import_type: Vec>, + assets_import_type: Vec>, // deserializing admin pages - #[serde(rename = "jira:adminPage", default, borrow)] - pub jira_admin_page: Vec>, #[serde(flatten)] extra: FxHashMap>>, } @@ -408,6 +446,26 @@ impl<'a> ForgeModules<'a> { invokable_functions.extend(by_line_item.dynamic_properties.function) }); + self.context_menu + .iter() + .for_each(|context_menu| context_menu.append_functions(&mut invokable_functions)); + + self.confluence_global_page + .iter() + .for_each(|global_page| global_page.append_functions(&mut invokable_functions)); + + self.homepage_feed + .iter() + .for_each(|homepage_feed| homepage_feed.append_functions(&mut invokable_functions)); + + self.space_page + .iter() + .for_each(|space_page| space_page.append_functions(&mut invokable_functions)); + + self.space_settings + .iter() + .for_each(|space_settings| space_settings.append_functions(&mut invokable_functions)); + self.macros.iter().for_each(|macros| { macros .common_keys @@ -417,16 +475,6 @@ impl<'a> ForgeModules<'a> { invokable_functions.extend(macros.export.function); }); - self.access_import_type.iter().for_each(|access| { - access - .common_keys - .append_functions(&mut invokable_functions); - invokable_functions.extend(access.one_delete_import.function); - invokable_functions.extend(access.stop_import.function); - invokable_functions.extend(access.start_import.function); - invokable_functions.extend(access.import_status.function); - }); - // Jira module functons self.custom_field.iter().for_each(|customfield| { invokable_functions.extend(customfield.value); @@ -436,11 +484,66 @@ impl<'a> ForgeModules<'a> { .common_keys .append_functions(&mut invokable_functions); }); + + self.custom_field_type.iter().for_each(|custom_field_type| { + invokable_functions.extend(custom_field_type.value); + invokable_functions.extend(custom_field_type.edit); + invokable_functions.extend(custom_field_type.context_config); + + custom_field_type + .common_keys + .append_functions(&mut invokable_functions); + }); + + self.dashboard_background_script + .iter() + .for_each(|dbs| dbs.append_functions(&mut invokable_functions)); + + self.dashboard_gadget.iter().for_each(|gadget| { + invokable_functions.extend(gadget.edit); + gadget + .common_keys + .append_functions(&mut invokable_functions) + }); + + self.jira_global_page + .iter() + .for_each(|global_page| global_page.append_functions(&mut invokable_functions)); + + self.issue_action + .iter() + .for_each(|issue| issue.append_functions(&mut invokable_functions)); + + self.issue_context.iter().for_each(|issue| { + invokable_functions.extend(issue.dynamic_properties.function); + issue.common_keys.append_functions(&mut invokable_functions) + }); + self.issue_glance.iter().for_each(|issue| { issue.common_keys.append_functions(&mut invokable_functions); invokable_functions.extend(issue.dynamic_properties.function); }); + self.issue_panel + .iter() + .for_each(|issue| issue.append_functions(&mut invokable_functions)); + + self.issue_view_background_script + .iter() + .for_each(|issue| issue.append_functions(&mut invokable_functions)); + + self.jql_function + .iter() + .for_each(|item| item.append_functions(&mut invokable_functions)); + + self.project_page + .iter() + .for_each(|item| item.append_functions(&mut invokable_functions)); + + self.project_settings_page + .iter() + .for_each(|item| item.append_functions(&mut invokable_functions)); + self.ui_modifications.iter().for_each(|ui| { ui.common_keys.append_functions(&mut invokable_functions); }); @@ -459,6 +562,17 @@ impl<'a> ForgeModules<'a> { .append_functions(&mut invokable_functions); }); + // JSM modules + self.assets_import_type.iter().for_each(|access| { + access + .common_keys + .append_functions(&mut invokable_functions); + invokable_functions.extend(access.one_delete_import.function); + invokable_functions.extend(access.stop_import.function); + invokable_functions.extend(access.start_import.function); + invokable_functions.extend(access.import_status.function); + }); + self.functions.into_iter().flat_map(move |func| { let web_trigger = self .webtriggers @@ -693,11 +807,11 @@ mod tests { if let Some(string) = resolver.function { assert_eq!(string, "Catch-me-if-you-can1"); } - if let Some(func) = manifest.modules.macros[0].config { + if let Some(func) = manifest.modules.macros[0].config.function { assert_eq!(func, "Catch-me-if-you-can2"); } - if let Some(func) = manifest.modules.macros[0].export { + if let Some(func) = manifest.modules.macros[0].export.function { assert_eq!(func, "Catch-me-if-you-can3"); } } From 44ae29fde1fd9b2a8dfcad288455bcc99f9ae57f Mon Sep 17 00:00:00 2001 From: Alina Wang Date: Tue, 6 Feb 2024 11:50:06 -0500 Subject: [PATCH 329/517] resolve: destructured ForgeModule struct to address comment on tracking all and new modules. Added compass_admin_page check to module function check --- crates/forge_loader/src/manifest.rs | 133 ++++++++++++++++++---------- 1 file changed, 88 insertions(+), 45 deletions(-) diff --git a/crates/forge_loader/src/manifest.rs b/crates/forge_loader/src/manifest.rs index bab4555e..8f8efc1e 100644 --- a/crates/forge_loader/src/manifest.rs +++ b/crates/forge_loader/src/manifest.rs @@ -402,71 +402,111 @@ impl AsRef for FunctionTy { impl<'a> ForgeModules<'a> { // TODO: function returns iterator where each item is some specified type. - pub fn into_analyzable_functions(mut self) -> impl Iterator> { + pub fn into_analyzable_functions(self) -> impl Iterator> { + // destructuring ForgeModules to remember to add new modules to this + let Self { + mut webtriggers, + custom_field, + consumers: _, + functions, + event_triggers: _, + scheduled_triggers: _, + compass_admin_page, + component_page, + data_provider, + compass_global_page, + team_page, + content_action, + content_by_line_item, + context_menu, + confluence_global_page, + homepage_feed, + space_page, + space_settings, + macros, + jira_admin_page, + custom_field_type, + dashboard_background_script, + dashboard_gadget, + jira_global_page, + issue_action, + issue_context, + issue_glance, + issue_panel, + issue_view_background_script, + jql_function, + project_page, + project_settings_page, + ui_modifications, + workflow_validator, + workflow_post_function, + assets_import_type, + extra: _, + } = self; + // number of webtriggers are usually low, so it's better to just sort them and reuse - self.webtriggers - .sort_unstable_by_key(|trigger| trigger.function); + webtriggers.sort_unstable_by_key(|trigger| trigger.function); // Get all the Triggers and represent them as a new struct thing where "webtrigger" attribute is true // for all trigger things let mut invokable_functions = BTreeSet::new(); // Compass Module Functions - self.compass_admin_page + compass_admin_page .iter() .for_each(|compass_admin| compass_admin.append_functions(&mut invokable_functions)); - self.component_page + component_page .iter() .for_each(|component_page| component_page.append_functions(&mut invokable_functions)); - self.data_provider.iter().for_each(|dataprovider| { + data_provider.iter().for_each(|dataprovider| { invokable_functions.extend(dataprovider.callback.function); }); - self.compass_global_page + compass_global_page .iter() .for_each(|global_page| global_page.append_functions(&mut invokable_functions)); - self.team_page + team_page .iter() .for_each(|team_page| team_page.append_functions(&mut invokable_functions)); // Confluence Module Functions // get user invokable modules that have additional exposure endpoints. // ie macros has config and export fields on top of resolver fields that are functions - self.content_action + content_action .iter() .for_each(|content_action| content_action.append_functions(&mut invokable_functions)); - self.content_by_line_item.iter().for_each(|by_line_item| { + content_by_line_item.iter().for_each(|by_line_item| { by_line_item .common_keys .append_functions(&mut invokable_functions); invokable_functions.extend(by_line_item.dynamic_properties.function) }); - self.context_menu + context_menu .iter() .for_each(|context_menu| context_menu.append_functions(&mut invokable_functions)); - self.confluence_global_page + confluence_global_page .iter() .for_each(|global_page| global_page.append_functions(&mut invokable_functions)); - self.homepage_feed + homepage_feed .iter() .for_each(|homepage_feed| homepage_feed.append_functions(&mut invokable_functions)); - self.space_page + space_page .iter() .for_each(|space_page| space_page.append_functions(&mut invokable_functions)); - self.space_settings + space_settings .iter() .for_each(|space_settings| space_settings.append_functions(&mut invokable_functions)); - self.macros.iter().for_each(|macros| { + macros.iter().for_each(|macros| { macros .common_keys .append_functions(&mut invokable_functions); @@ -476,7 +516,7 @@ impl<'a> ForgeModules<'a> { }); // Jira module functons - self.custom_field.iter().for_each(|customfield| { + custom_field.iter().for_each(|customfield| { invokable_functions.extend(customfield.value); invokable_functions.extend(customfield.search_suggestions); invokable_functions.extend(customfield.edit); @@ -485,7 +525,7 @@ impl<'a> ForgeModules<'a> { .append_functions(&mut invokable_functions); }); - self.custom_field_type.iter().for_each(|custom_field_type| { + custom_field_type.iter().for_each(|custom_field_type| { invokable_functions.extend(custom_field_type.value); invokable_functions.extend(custom_field_type.edit); invokable_functions.extend(custom_field_type.context_config); @@ -495,75 +535,73 @@ impl<'a> ForgeModules<'a> { .append_functions(&mut invokable_functions); }); - self.dashboard_background_script + dashboard_background_script .iter() .for_each(|dbs| dbs.append_functions(&mut invokable_functions)); - self.dashboard_gadget.iter().for_each(|gadget| { + dashboard_gadget.iter().for_each(|gadget| { invokable_functions.extend(gadget.edit); gadget .common_keys .append_functions(&mut invokable_functions) }); - self.jira_global_page + jira_global_page .iter() .for_each(|global_page| global_page.append_functions(&mut invokable_functions)); - self.issue_action + issue_action .iter() .for_each(|issue| issue.append_functions(&mut invokable_functions)); - self.issue_context.iter().for_each(|issue| { + issue_context.iter().for_each(|issue| { invokable_functions.extend(issue.dynamic_properties.function); issue.common_keys.append_functions(&mut invokable_functions) }); - self.issue_glance.iter().for_each(|issue| { + issue_glance.iter().for_each(|issue| { issue.common_keys.append_functions(&mut invokable_functions); invokable_functions.extend(issue.dynamic_properties.function); }); - self.issue_panel + issue_panel .iter() .for_each(|issue| issue.append_functions(&mut invokable_functions)); - self.issue_view_background_script + issue_view_background_script .iter() .for_each(|issue| issue.append_functions(&mut invokable_functions)); - self.jql_function + jql_function .iter() .for_each(|item| item.append_functions(&mut invokable_functions)); - self.project_page + project_page .iter() .for_each(|item| item.append_functions(&mut invokable_functions)); - self.project_settings_page + project_settings_page .iter() .for_each(|item| item.append_functions(&mut invokable_functions)); - self.ui_modifications.iter().for_each(|ui| { + ui_modifications.iter().for_each(|ui| { ui.common_keys.append_functions(&mut invokable_functions); }); - self.workflow_validator.iter().for_each(|validator| { + workflow_validator.iter().for_each(|validator| { validator .common_keys .append_functions(&mut invokable_functions) }); - self.workflow_post_function - .iter() - .for_each(|post_function| { - post_function - .common_keys - .append_functions(&mut invokable_functions); - }); + workflow_post_function.iter().for_each(|post_function| { + post_function + .common_keys + .append_functions(&mut invokable_functions); + }); // JSM modules - self.assets_import_type.iter().for_each(|access| { + assets_import_type.iter().for_each(|access| { access .common_keys .append_functions(&mut invokable_functions); @@ -573,18 +611,23 @@ impl<'a> ForgeModules<'a> { invokable_functions.extend(access.import_status.function); }); - self.functions.into_iter().flat_map(move |func| { - let web_trigger = self - .webtriggers + functions.into_iter().flat_map(move |func| { + let web_trigger = webtriggers .binary_search_by_key(&func.key, |trigger| &trigger.function) .is_ok(); let invokable = invokable_functions.contains(func.key); // this checks whether the funton being scanned is being used in an admin module. Rn it only checks for jira_admin page module. // optionally: compass:adminPage could also be considered. - let admin = self - .jira_admin_page + let admin = jira_admin_page .iter() - .any(|admin_function| admin_function.function == func.key); + .any(|admin_function| admin_function.function == func.key) + || compass_admin_page.iter().any(|admin_function| { + let Some(string) = admin_function.function else { + return false; + }; + return string == func.key; + }); + Ok::<_, Error>(Entrypoint { function: FunctionRef::try_from(func)?, invokable, From 7aecd635f7481411a46d9343fe5c8595c0652831 Mon Sep 17 00:00:00 2001 From: Alina Wang Date: Tue, 6 Feb 2024 15:05:11 -0500 Subject: [PATCH 330/517] fixx addressed most comments. Updated fields to be optional or a struct. removed data_provider. Todo: resolve copy issue and add JSM modules --- crates/forge_loader/src/manifest.rs | 150 +++++++++++++++++----------- 1 file changed, 90 insertions(+), 60 deletions(-) diff --git a/crates/forge_loader/src/manifest.rs b/crates/forge_loader/src/manifest.rs index 8f8efc1e..28d9d653 100644 --- a/crates/forge_loader/src/manifest.rs +++ b/crates/forge_loader/src/manifest.rs @@ -20,7 +20,7 @@ struct AuthProviders<'a> { // Abstracting away key, function, and resolver into a single struct for reuse whoo! // And helper functions for ease of use -#[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] +#[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize, Copy)] struct CommonKey<'a> { key: &'a str, function: Option<&'a str>, @@ -41,7 +41,7 @@ impl<'a> CommonKey<'a> { } } } -#[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] +#[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize, Copy)] pub struct Resolver<'a> { pub function: Option<&'a str>, pub method: Option<&'a str>, @@ -50,7 +50,7 @@ pub struct Resolver<'a> { // Implementing a struct for structs with 1 value (function) -#[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] +#[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize, Copy)] pub struct JustFunc<'a> { pub function: Option<&'a str>, } @@ -117,12 +117,12 @@ pub struct ComponentPage<'a> { #[serde(flatten, borrow)] common_keys: CommonKey<'a>, } -#[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] -pub struct DataProvider<'a> { - #[serde(flatten, borrow)] - common_keys: CommonKey<'a>, - callback: JustFunc<'a>, -} +// #[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] +// pub struct DataProvider<'a> { +// #[serde(flatten, borrow)] +// common_keys: CommonKey<'a>, +// callback: Option>, +// } #[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] pub struct CompassGlobalPage<'a> { @@ -146,7 +146,7 @@ struct ContentAction<'a> { struct ContentByLineItem<'a> { #[serde(flatten, borrow)] common_keys: CommonKey<'a>, - #[serde(borrow)] + #[serde(borrow, rename = "dynamicProperties")] dynamic_properties: JustFunc<'a>, } @@ -154,49 +154,48 @@ struct ContentByLineItem<'a> { struct MacroMod<'a> { #[serde(flatten, borrow)] common_keys: CommonKey<'a>, - config: JustFunc<'a>, - export: JustFunc<'a>, + config: Option>, + export: Option>, } // Jira Modules #[derive(Debug, Clone, PartialEq, Eq, Deserialize)] pub struct JiraAdminPage<'a> { - key: &'a str, - function: &'a str, title: &'a str, - resolver: Resolver<'a>, + #[serde(flatten, borrow)] + common_keys: CommonKey<'a>, } #[derive(Debug, Clone, PartialEq, Eq, Deserialize)] pub struct CustomField<'a> { #[serde(flatten, borrow)] common_keys: CommonKey<'a>, - value: Option<&'a str>, - search_suggestions: Option<&'a str>, - edit: Option<&'a str>, + value: Option>, + edit: Option>, } #[derive(Debug, Clone, PartialEq, Eq, Deserialize)] pub struct CustomFieldType<'a> { #[serde(flatten, borrow)] common_keys: CommonKey<'a>, - value: Option<&'a str>, - edit: Option<&'a str>, - context_config: Option<&'a str>, + value: Option>, + edit: Option>, + context_config: Option>, } #[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] pub struct DashboardGadget<'a> { #[serde(flatten, borrow)] common_keys: CommonKey<'a>, - edit: Option<&'a str>, + edit: Option>, } #[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] +#[serde(rename_all = "camelCase")] struct IssueClass<'a> { #[serde(flatten, borrow)] common_keys: CommonKey<'a>, - dynamic_properties: JustFunc<'a>, + dynamic_properties: Option>, } #[derive(Debug, Clone, PartialEq, Eq, Deserialize)] @@ -217,11 +216,12 @@ pub struct WorkflowPostFunction<'a> { common_keys: CommonKey<'a>, } // Jira Service Management Modules -#[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] +#[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize, Copy)] +#[serde(rename_all = "camelCase")] struct AssetsImportType<'a> { #[serde(flatten, borrow)] common_keys: CommonKey<'a>, - one_delete_import: JustFunc<'a>, + on_delete_import: Option>, start_import: JustFunc<'a>, stop_import: JustFunc<'a>, import_status: JustFunc<'a>, @@ -247,8 +247,6 @@ pub struct ForgeModules<'a> { compass_admin_page: Vec>, #[serde(rename = "compass:componentPage", default, borrow)] component_page: Vec>, - #[serde(rename = "compass:dataProvider", default, borrow)] - pub data_provider: Vec>, #[serde(rename = "compass:globalPage", default, borrow)] compass_global_page: Vec>, #[serde(rename = "compass:teamPage", default, borrow)] @@ -413,7 +411,6 @@ impl<'a> ForgeModules<'a> { scheduled_triggers: _, compass_admin_page, component_page, - data_provider, compass_global_page, team_page, content_action, @@ -460,10 +457,6 @@ impl<'a> ForgeModules<'a> { .iter() .for_each(|component_page| component_page.append_functions(&mut invokable_functions)); - data_provider.iter().for_each(|dataprovider| { - invokable_functions.extend(dataprovider.callback.function); - }); - compass_global_page .iter() .for_each(|global_page| global_page.append_functions(&mut invokable_functions)); @@ -506,29 +499,43 @@ impl<'a> ForgeModules<'a> { .iter() .for_each(|space_settings| space_settings.append_functions(&mut invokable_functions)); - macros.iter().for_each(|macros| { - macros - .common_keys - .append_functions(&mut invokable_functions); - - invokable_functions.extend(macros.config.function); - invokable_functions.extend(macros.export.function); + macros.iter().for_each(|mac| { + mac.common_keys.append_functions(&mut invokable_functions); + self.clone() + .add_optional(mac.config, &mut invokable_functions); + self.clone() + .add_optional(mac.export, &mut invokable_functions); }); // Jira module functons custom_field.iter().for_each(|customfield| { - invokable_functions.extend(customfield.value); - invokable_functions.extend(customfield.search_suggestions); - invokable_functions.extend(customfield.edit); + // let Some(func) = customfield.value; + // invokable_functions.extend(func.function); + self.clone() + .add_optional(customfield.value, &mut invokable_functions); + + // let Some(func) = customfield.edit; + // invokable_functions.extend(func.function); + self.clone() + .add_optional(customfield.edit, &mut invokable_functions); + customfield .common_keys .append_functions(&mut invokable_functions); }); custom_field_type.iter().for_each(|custom_field_type| { - invokable_functions.extend(custom_field_type.value); - invokable_functions.extend(custom_field_type.edit); - invokable_functions.extend(custom_field_type.context_config); + // invokable_functions.extend(custom_field_type.value); + self.clone() + .add_optional(custom_field_type.value, &mut invokable_functions); + + // invokable_functions.extend(custom_field_type.edit); + self.clone() + .add_optional(custom_field_type.edit, &mut invokable_functions); + + // invokable_functions.extend(custom_field_type.context_config); + self.clone() + .add_optional(custom_field_type.context_config, &mut invokable_functions); custom_field_type .common_keys @@ -540,7 +547,10 @@ impl<'a> ForgeModules<'a> { .for_each(|dbs| dbs.append_functions(&mut invokable_functions)); dashboard_gadget.iter().for_each(|gadget| { - invokable_functions.extend(gadget.edit); + // invokable_functions.extend(gadget.edit); + self.clone() + .add_optional(gadget.edit, &mut invokable_functions); + gadget .common_keys .append_functions(&mut invokable_functions) @@ -555,13 +565,20 @@ impl<'a> ForgeModules<'a> { .for_each(|issue| issue.append_functions(&mut invokable_functions)); issue_context.iter().for_each(|issue| { - invokable_functions.extend(issue.dynamic_properties.function); + // let Some(func) = issue.dynamic_properties; + // invokable_functions.extend(func.function); + self.clone() + .add_optional(issue.dynamic_properties, &mut invokable_functions); + issue.common_keys.append_functions(&mut invokable_functions) }); issue_glance.iter().for_each(|issue| { issue.common_keys.append_functions(&mut invokable_functions); - invokable_functions.extend(issue.dynamic_properties.function); + // let Some(func) = issue.dynamic_properties; + // invokable_functions.extend(func.function); + self.clone() + .add_optional(issue.dynamic_properties, &mut invokable_functions); }); issue_panel @@ -605,7 +622,11 @@ impl<'a> ForgeModules<'a> { access .common_keys .append_functions(&mut invokable_functions); - invokable_functions.extend(access.one_delete_import.function); + // let Some(func) = access.on_delete_import; + // invokable_functions.extend(func.function); + self.clone() + .add_optional(access.on_delete_import, &mut invokable_functions); + invokable_functions.extend(access.stop_import.function); invokable_functions.extend(access.start_import.function); invokable_functions.extend(access.import_status.function); @@ -618,15 +639,17 @@ impl<'a> ForgeModules<'a> { let invokable = invokable_functions.contains(func.key); // this checks whether the funton being scanned is being used in an admin module. Rn it only checks for jira_admin page module. // optionally: compass:adminPage could also be considered. - let admin = jira_admin_page - .iter() - .any(|admin_function| admin_function.function == func.key) - || compass_admin_page.iter().any(|admin_function| { - let Some(string) = admin_function.function else { - return false; - }; - return string == func.key; - }); + let admin = jira_admin_page.iter().any(|admin_function| { + let Some(string) = admin_function.common_keys.function else { + return false; + }; + return string == func.key; + }) || compass_admin_page.iter().any(|admin_function| { + let Some(string) = admin_function.function else { + return false; + }; + return string == func.key; + }); Ok::<_, Error>(Entrypoint { function: FunctionRef::try_from(func)?, @@ -636,6 +659,11 @@ impl<'a> ForgeModules<'a> { }) }) } + + pub fn add_optional(self, optional: Option>, iter: &mut BTreeSet<&str>) { + let Some(func) = optional; + iter.extend(func.function); + } } impl FunctionRef<'_, S> { @@ -850,11 +878,13 @@ mod tests { if let Some(string) = resolver.function { assert_eq!(string, "Catch-me-if-you-can1"); } - if let Some(func) = manifest.modules.macros[0].config.function { + if let Some(justfunc) = manifest.modules.macros[0].config { + let Some(func) = justfunc.function; assert_eq!(func, "Catch-me-if-you-can2"); } - if let Some(func) = manifest.modules.macros[0].export.function { + if let Some(justfunc) = manifest.modules.macros[0].export { + let Some(func) = justfunc.function; assert_eq!(func, "Catch-me-if-you-can3"); } } From 8ae768713cc16f20db41f66b3fd306c0989c0f69 Mon Sep 17 00:00:00 2001 From: Alina Wang Date: Tue, 6 Feb 2024 18:07:11 -0500 Subject: [PATCH 331/517] resolve: uadded JSM modules with functions. And updated into_analyzable_functions to destructure self as refs and handle optional properties in modules that could hold functions. TODO: resolve trait requirement error. --- crates/forge_loader/src/manifest.rs | 174 ++++++++++++++++++++-------- 1 file changed, 127 insertions(+), 47 deletions(-) diff --git a/crates/forge_loader/src/manifest.rs b/crates/forge_loader/src/manifest.rs index 28d9d653..2b159fb3 100644 --- a/crates/forge_loader/src/manifest.rs +++ b/crates/forge_loader/src/manifest.rs @@ -20,7 +20,7 @@ struct AuthProviders<'a> { // Abstracting away key, function, and resolver into a single struct for reuse whoo! // And helper functions for ease of use -#[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize, Copy)] +#[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] struct CommonKey<'a> { key: &'a str, function: Option<&'a str>, @@ -41,7 +41,7 @@ impl<'a> CommonKey<'a> { } } } -#[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize, Copy)] +#[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] pub struct Resolver<'a> { pub function: Option<&'a str>, pub method: Option<&'a str>, @@ -50,7 +50,7 @@ pub struct Resolver<'a> { // Implementing a struct for structs with 1 value (function) -#[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize, Copy)] +#[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] pub struct JustFunc<'a> { pub function: Option<&'a str>, } @@ -151,7 +151,7 @@ struct ContentByLineItem<'a> { } #[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] -struct MacroMod<'a> { +pub struct MacroMod<'a> { #[serde(flatten, borrow)] common_keys: CommonKey<'a>, config: Option>, @@ -216,7 +216,7 @@ pub struct WorkflowPostFunction<'a> { common_keys: CommonKey<'a>, } // Jira Service Management Modules -#[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize, Copy)] +#[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] #[serde(rename_all = "camelCase")] struct AssetsImportType<'a> { #[serde(flatten, borrow)] @@ -306,6 +306,41 @@ pub struct ForgeModules<'a> { // Jira Service Management Modules #[serde(rename = "jiraServiceManagement:assetsImportType", default, borrow)] assets_import_type: Vec>, + #[serde(rename = "jiraServiceManagement:organizationPanel", default, borrow)] + org_panel: Vec>, + #[serde(rename = "jiraServiceManagement:portalFooter", default, borrow)] + portal_footer: Vec>, + #[serde(rename = "jiraServiceManagement:portalHeader", default, borrow)] + portal_header: Vec>, + #[serde(rename = "jiraServiceManagement:portalProfilePanel", default, borrow)] + portal_profile_panel: Vec>, + #[serde( + rename = "jiraServiceManagement:portalRequestCreatePropertyPanel", + default, + borrow + )] + portal_req: Vec>, + #[serde(rename = "jiraServiceManagement:portalRequestDetail", default, borrow)] + portal_request_detail: Vec>, + #[serde( + rename = "jiraServiceManagement:portalRequestDetailPanel", + default, + borrow + )] + portal_request_detail_panel: Vec>, + #[serde( + rename = "jiraServiceManagement:portalRequestViewAction", + default, + borrow + )] + portal_request_view_action: Vec>, + #[serde(rename = "jiraServiceManagement:portalSubheader", default, borrow)] + portal_subheader: Vec>, + #[serde(rename = "jiraServiceManagement:portalUserMenuAction", default, borrow)] + portal_header_menu_action: Vec>, + #[serde(rename = "jiraServiceManagement:queuePage", default, borrow)] + queue_page: Vec>, + // deserializing admin pages #[serde(flatten)] extra: FxHashMap>>, @@ -404,41 +439,52 @@ impl<'a> ForgeModules<'a> { // destructuring ForgeModules to remember to add new modules to this let Self { mut webtriggers, - custom_field, + ref custom_field, consumers: _, - functions, + ref functions, event_triggers: _, scheduled_triggers: _, - compass_admin_page, - component_page, - compass_global_page, - team_page, - content_action, - content_by_line_item, - context_menu, - confluence_global_page, - homepage_feed, - space_page, - space_settings, - macros, - jira_admin_page, - custom_field_type, - dashboard_background_script, - dashboard_gadget, - jira_global_page, - issue_action, - issue_context, - issue_glance, - issue_panel, - issue_view_background_script, - jql_function, - project_page, - project_settings_page, - ui_modifications, - workflow_validator, - workflow_post_function, - assets_import_type, + ref compass_admin_page, + ref component_page, + ref compass_global_page, + ref team_page, + ref content_action, + ref content_by_line_item, + ref context_menu, + ref confluence_global_page, + ref homepage_feed, + ref space_page, + ref space_settings, + ref macros, + ref jira_admin_page, + ref custom_field_type, + ref dashboard_background_script, + ref dashboard_gadget, + ref jira_global_page, + ref issue_action, + ref issue_context, + ref issue_glance, + ref issue_panel, + ref issue_view_background_script, + ref jql_function, + ref project_page, + ref project_settings_page, + ref ui_modifications, + ref workflow_validator, + ref workflow_post_function, + ref assets_import_type, extra: _, + ref org_panel, + ref portal_footer, + ref portal_header, + ref portal_profile_panel, + ref portal_req, + ref portal_request_detail, + ref portal_request_detail_panel, + ref portal_request_view_action, + ref portal_subheader, + ref queue_page, + ref portal_header_menu_action, } = self; // number of webtriggers are usually low, so it's better to just sort them and reuse @@ -499,23 +545,19 @@ impl<'a> ForgeModules<'a> { .iter() .for_each(|space_settings| space_settings.append_functions(&mut invokable_functions)); - macros.iter().for_each(|mac| { - mac.common_keys.append_functions(&mut invokable_functions); + macros.clone().iter().for_each(|mac| { self.clone() .add_optional(mac.config, &mut invokable_functions); self.clone() .add_optional(mac.export, &mut invokable_functions); + mac.common_keys.append_functions(&mut invokable_functions); }); // Jira module functons custom_field.iter().for_each(|customfield| { - // let Some(func) = customfield.value; - // invokable_functions.extend(func.function); self.clone() .add_optional(customfield.value, &mut invokable_functions); - // let Some(func) = customfield.edit; - // invokable_functions.extend(func.function); self.clone() .add_optional(customfield.edit, &mut invokable_functions); @@ -565,8 +607,6 @@ impl<'a> ForgeModules<'a> { .for_each(|issue| issue.append_functions(&mut invokable_functions)); issue_context.iter().for_each(|issue| { - // let Some(func) = issue.dynamic_properties; - // invokable_functions.extend(func.function); self.clone() .add_optional(issue.dynamic_properties, &mut invokable_functions); @@ -575,8 +615,6 @@ impl<'a> ForgeModules<'a> { issue_glance.iter().for_each(|issue| { issue.common_keys.append_functions(&mut invokable_functions); - // let Some(func) = issue.dynamic_properties; - // invokable_functions.extend(func.function); self.clone() .add_optional(issue.dynamic_properties, &mut invokable_functions); }); @@ -631,6 +669,48 @@ impl<'a> ForgeModules<'a> { invokable_functions.extend(access.start_import.function); invokable_functions.extend(access.import_status.function); }); + org_panel + .iter() + .for_each(|panel| panel.append_functions(&mut invokable_functions)); + + portal_footer + .iter() + .for_each(|footer| footer.append_functions(&mut invokable_functions)); + + portal_header + .iter() + .for_each(|header| header.append_functions(&mut invokable_functions)); + + portal_profile_panel + .iter() + .for_each(|profile| profile.append_functions(&mut invokable_functions)); + + portal_req + .iter() + .for_each(|req| req.append_functions(&mut invokable_functions)); + + portal_request_detail + .iter() + .for_each(|req| req.append_functions(&mut invokable_functions)); + + portal_request_detail_panel + .iter() + .for_each(|req| req.append_functions(&mut invokable_functions)); + + portal_request_view_action + .iter() + .for_each(|req| req.append_functions(&mut invokable_functions)); + + portal_subheader + .iter() + .for_each(|subheader| subheader.append_functions(&mut invokable_functions)); + portal_header_menu_action + .iter() + .for_each(|action| action.append_functions(&mut invokable_functions)); + + queue_page + .iter() + .for_each(|page| page.append_functions(&mut invokable_functions)); functions.into_iter().flat_map(move |func| { let web_trigger = webtriggers @@ -705,7 +785,7 @@ impl<'a, Resolved> FunctionRef<'a, Resolved> { } } -impl<'a> TryFrom> for FunctionRef<'a> { +impl<'a> TryFrom> for &'a FunctionRef<'a> { type Error = Error; fn try_from(func_handler: FunctionMod<'a>) -> Result { From ee2e747461fc70a0cda57ba89fb16b8b2158a2ed Mon Sep 17 00:00:00 2001 From: Alina Wang Date: Wed, 7 Feb 2024 15:55:13 -0500 Subject: [PATCH 332/517] feat: new trait implemented to append functions. Updated into_analyzable_functions to use trait. --- crates/forge_loader/src/manifest.rs | 391 +++++++++++++--------------- 1 file changed, 177 insertions(+), 214 deletions(-) diff --git a/crates/forge_loader/src/manifest.rs b/crates/forge_loader/src/manifest.rs index 2b159fb3..cbccb3a6 100644 --- a/crates/forge_loader/src/manifest.rs +++ b/crates/forge_loader/src/manifest.rs @@ -20,14 +20,18 @@ struct AuthProviders<'a> { // Abstracting away key, function, and resolver into a single struct for reuse whoo! // And helper functions for ease of use -#[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] +#[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize, Copy)] struct CommonKey<'a> { key: &'a str, function: Option<&'a str>, resolver: Option>, } -impl<'a> CommonKey<'a> { +trait HasFunctions<'a> { + fn append_functions>(&self, funcs: &mut I); +} + +impl<'a> HasFunctions<'a> for CommonKey<'a> { fn append_functions>(&self, funcs: &mut I) { funcs.extend(self.function); @@ -41,7 +45,25 @@ impl<'a> CommonKey<'a> { } } } -#[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] + +impl<'a> HasFunctions<'a> for JustFunc<'a> { + fn append_functions>(&self, funcs: &mut I) { + funcs.extend(self.function); + } +} + +impl<'a, I, E: HasFunctions<'a>> HasFunctions<'a> for I +where + for<'c> &'c I: IntoIterator, +{ + fn append_functions>(&self, funcs: &mut B) { + // iterating over &I + for e in self { + e.append_functions(funcs); + } + } +} +#[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize, Copy)] pub struct Resolver<'a> { pub function: Option<&'a str>, pub method: Option<&'a str>, @@ -50,7 +72,7 @@ pub struct Resolver<'a> { // Implementing a struct for structs with 1 value (function) -#[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] +#[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize, Copy)] pub struct JustFunc<'a> { pub function: Option<&'a str>, } @@ -90,7 +112,7 @@ struct ScheduledTrigger<'a> { } // Maps to Web Trigger under Common Modules -#[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] +#[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize, Copy)] struct RawTrigger<'a> { key: &'a str, function: &'a str, @@ -150,7 +172,7 @@ struct ContentByLineItem<'a> { dynamic_properties: JustFunc<'a>, } -#[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] +#[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize, Copy)] pub struct MacroMod<'a> { #[serde(flatten, borrow)] common_keys: CommonKey<'a>, @@ -159,7 +181,7 @@ pub struct MacroMod<'a> { } // Jira Modules -#[derive(Debug, Clone, PartialEq, Eq, Deserialize)] +#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Copy)] pub struct JiraAdminPage<'a> { title: &'a str, #[serde(flatten, borrow)] @@ -439,52 +461,52 @@ impl<'a> ForgeModules<'a> { // destructuring ForgeModules to remember to add new modules to this let Self { mut webtriggers, - ref custom_field, + custom_field, consumers: _, - ref functions, + functions, event_triggers: _, scheduled_triggers: _, - ref compass_admin_page, - ref component_page, - ref compass_global_page, - ref team_page, - ref content_action, - ref content_by_line_item, - ref context_menu, - ref confluence_global_page, - ref homepage_feed, - ref space_page, - ref space_settings, - ref macros, - ref jira_admin_page, - ref custom_field_type, - ref dashboard_background_script, - ref dashboard_gadget, - ref jira_global_page, - ref issue_action, - ref issue_context, - ref issue_glance, - ref issue_panel, - ref issue_view_background_script, - ref jql_function, - ref project_page, - ref project_settings_page, - ref ui_modifications, - ref workflow_validator, - ref workflow_post_function, - ref assets_import_type, + compass_admin_page, + component_page, + compass_global_page, + team_page, + content_action, + content_by_line_item, + context_menu, + confluence_global_page, + homepage_feed, + space_page, + space_settings, + macros, + jira_admin_page, + custom_field_type, + dashboard_background_script, + dashboard_gadget, + jira_global_page, + issue_action, + issue_context, + issue_glance, + issue_panel, + issue_view_background_script, + jql_function, + project_page, + project_settings_page, + ui_modifications, + workflow_validator, + workflow_post_function, + assets_import_type, extra: _, - ref org_panel, - ref portal_footer, - ref portal_header, - ref portal_profile_panel, - ref portal_req, - ref portal_request_detail, - ref portal_request_detail_panel, - ref portal_request_view_action, - ref portal_subheader, - ref queue_page, - ref portal_header_menu_action, + org_panel, + portal_footer, + portal_header, + portal_profile_panel, + portal_req, + portal_request_detail, + portal_request_detail_panel, + portal_request_view_action, + portal_subheader, + queue_page, + portal_header_menu_action, } = self; // number of webtriggers are usually low, so it's better to just sort them and reuse @@ -495,21 +517,12 @@ impl<'a> ForgeModules<'a> { let mut invokable_functions = BTreeSet::new(); // Compass Module Functions - compass_admin_page - .iter() - .for_each(|compass_admin| compass_admin.append_functions(&mut invokable_functions)); + compass_admin_page.append_functions(&mut invokable_functions); - component_page - .iter() - .for_each(|component_page| component_page.append_functions(&mut invokable_functions)); - - compass_global_page - .iter() - .for_each(|global_page| global_page.append_functions(&mut invokable_functions)); + component_page.append_functions(&mut invokable_functions); - team_page - .iter() - .for_each(|team_page| team_page.append_functions(&mut invokable_functions)); + compass_global_page.append_functions(&mut invokable_functions); + team_page.append_functions(&mut invokable_functions); // Confluence Module Functions // get user invokable modules that have additional exposure endpoints. @@ -525,206 +538,157 @@ impl<'a> ForgeModules<'a> { invokable_functions.extend(by_line_item.dynamic_properties.function) }); - context_menu - .iter() - .for_each(|context_menu| context_menu.append_functions(&mut invokable_functions)); + context_menu.append_functions(&mut invokable_functions); - confluence_global_page - .iter() - .for_each(|global_page| global_page.append_functions(&mut invokable_functions)); + confluence_global_page.append_functions(&mut invokable_functions); - homepage_feed - .iter() - .for_each(|homepage_feed| homepage_feed.append_functions(&mut invokable_functions)); + homepage_feed.append_functions(&mut invokable_functions); - space_page - .iter() - .for_each(|space_page| space_page.append_functions(&mut invokable_functions)); + space_page.append_functions(&mut invokable_functions); - space_settings - .iter() - .for_each(|space_settings| space_settings.append_functions(&mut invokable_functions)); - - macros.clone().iter().for_each(|mac| { - self.clone() - .add_optional(mac.config, &mut invokable_functions); - self.clone() - .add_optional(mac.export, &mut invokable_functions); - mac.common_keys.append_functions(&mut invokable_functions); - }); + space_settings.append_functions(&mut invokable_functions); - // Jira module functons - custom_field.iter().for_each(|customfield| { - self.clone() - .add_optional(customfield.value, &mut invokable_functions); + for m in macros { + m.common_keys.append_functions(&mut invokable_functions); + m.config.append_functions(&mut invokable_functions); + m.export.append_functions(&mut invokable_functions); + } - self.clone() - .add_optional(customfield.edit, &mut invokable_functions); + // Jira module functons + custom_field.into_iter().for_each(|customfield| { + customfield.value.append_functions(&mut invokable_functions); + customfield.value.append_functions(&mut invokable_functions); customfield .common_keys .append_functions(&mut invokable_functions); }); - custom_field_type.iter().for_each(|custom_field_type| { - // invokable_functions.extend(custom_field_type.value); - self.clone() - .add_optional(custom_field_type.value, &mut invokable_functions); - - // invokable_functions.extend(custom_field_type.edit); - self.clone() - .add_optional(custom_field_type.edit, &mut invokable_functions); + custom_field_type.into_iter().for_each(|custom_field_type| { + custom_field_type + .common_keys + .append_functions(&mut invokable_functions); - // invokable_functions.extend(custom_field_type.context_config); - self.clone() - .add_optional(custom_field_type.context_config, &mut invokable_functions); + custom_field_type + .edit + .append_functions(&mut invokable_functions); + custom_field_type + .context_config + .append_functions(&mut invokable_functions); custom_field_type - .common_keys + .value .append_functions(&mut invokable_functions); }); - dashboard_background_script - .iter() - .for_each(|dbs| dbs.append_functions(&mut invokable_functions)); - - dashboard_gadget.iter().for_each(|gadget| { - // invokable_functions.extend(gadget.edit); - self.clone() - .add_optional(gadget.edit, &mut invokable_functions); + dashboard_background_script.append_functions(&mut invokable_functions); + for gadget in dashboard_gadget { gadget .common_keys - .append_functions(&mut invokable_functions) - }); + .append_functions(&mut invokable_functions); + gadget.edit.append_functions(&mut invokable_functions); + } - jira_global_page - .iter() - .for_each(|global_page| global_page.append_functions(&mut invokable_functions)); + jira_global_page.append_functions(&mut invokable_functions); - issue_action - .iter() - .for_each(|issue| issue.append_functions(&mut invokable_functions)); + issue_action.append_functions(&mut invokable_functions); - issue_context.iter().for_each(|issue| { - self.clone() - .add_optional(issue.dynamic_properties, &mut invokable_functions); - - issue.common_keys.append_functions(&mut invokable_functions) - }); + for issue in issue_context { + issue.common_keys.append_functions(&mut invokable_functions); + issue + .dynamic_properties + .append_functions(&mut invokable_functions); + } - issue_glance.iter().for_each(|issue| { + for issue in issue_glance { issue.common_keys.append_functions(&mut invokable_functions); - self.clone() - .add_optional(issue.dynamic_properties, &mut invokable_functions); - }); + issue + .dynamic_properties + .append_functions(&mut invokable_functions); + } - issue_panel - .iter() - .for_each(|issue| issue.append_functions(&mut invokable_functions)); + issue_panel.append_functions(&mut invokable_functions); - issue_view_background_script - .iter() - .for_each(|issue| issue.append_functions(&mut invokable_functions)); + issue_view_background_script.append_functions(&mut invokable_functions); - jql_function - .iter() - .for_each(|item| item.append_functions(&mut invokable_functions)); + jql_function.append_functions(&mut invokable_functions); - project_page - .iter() - .for_each(|item| item.append_functions(&mut invokable_functions)); + project_page.append_functions(&mut invokable_functions); - project_settings_page - .iter() - .for_each(|item| item.append_functions(&mut invokable_functions)); + project_settings_page.append_functions(&mut invokable_functions); - ui_modifications.iter().for_each(|ui| { + for ui in ui_modifications { ui.common_keys.append_functions(&mut invokable_functions); - }); + } - workflow_validator.iter().for_each(|validator| { - validator - .common_keys - .append_functions(&mut invokable_functions) - }); + for valid in workflow_validator { + valid.common_keys.append_functions(&mut invokable_functions); + } - workflow_post_function.iter().for_each(|post_function| { - post_function - .common_keys - .append_functions(&mut invokable_functions); - }); + for post in workflow_post_function { + post.common_keys.append_functions(&mut invokable_functions); + } // JSM modules - assets_import_type.iter().for_each(|access| { - access + for assets in assets_import_type { + assets .common_keys .append_functions(&mut invokable_functions); - // let Some(func) = access.on_delete_import; - // invokable_functions.extend(func.function); - self.clone() - .add_optional(access.on_delete_import, &mut invokable_functions); - - invokable_functions.extend(access.stop_import.function); - invokable_functions.extend(access.start_import.function); - invokable_functions.extend(access.import_status.function); - }); + + assets + .on_delete_import + .append_functions(&mut invokable_functions); + + assets + .stop_import + .append_functions(&mut invokable_functions); + + assets + .start_import + .append_functions(&mut invokable_functions); + + assets + .import_status + .append_functions(&mut invokable_functions); + } org_panel .iter() .for_each(|panel| panel.append_functions(&mut invokable_functions)); - portal_footer - .iter() - .for_each(|footer| footer.append_functions(&mut invokable_functions)); + org_panel.append_functions(&mut invokable_functions); - portal_header - .iter() - .for_each(|header| header.append_functions(&mut invokable_functions)); + portal_footer.append_functions(&mut invokable_functions); + portal_header.append_functions(&mut invokable_functions); + portal_profile_panel.append_functions(&mut invokable_functions); - portal_profile_panel - .iter() - .for_each(|profile| profile.append_functions(&mut invokable_functions)); + portal_req.append_functions(&mut invokable_functions); - portal_req - .iter() - .for_each(|req| req.append_functions(&mut invokable_functions)); + portal_request_detail.append_functions(&mut invokable_functions); - portal_request_detail - .iter() - .for_each(|req| req.append_functions(&mut invokable_functions)); + portal_request_detail_panel.append_functions(&mut invokable_functions); - portal_request_detail_panel - .iter() - .for_each(|req| req.append_functions(&mut invokable_functions)); + portal_request_view_action.append_functions(&mut invokable_functions); - portal_request_view_action - .iter() - .for_each(|req| req.append_functions(&mut invokable_functions)); + portal_subheader.append_functions(&mut invokable_functions); - portal_subheader - .iter() - .for_each(|subheader| subheader.append_functions(&mut invokable_functions)); - portal_header_menu_action - .iter() - .for_each(|action| action.append_functions(&mut invokable_functions)); + portal_header_menu_action.append_functions(&mut invokable_functions); - queue_page - .iter() - .for_each(|page| page.append_functions(&mut invokable_functions)); + queue_page.append_functions(&mut invokable_functions); - functions.into_iter().flat_map(move |func| { + functions.clone().into_iter().flat_map(move |func| { let web_trigger = webtriggers .binary_search_by_key(&func.key, |trigger| &trigger.function) .is_ok(); let invokable = invokable_functions.contains(func.key); // this checks whether the funton being scanned is being used in an admin module. Rn it only checks for jira_admin page module. // optionally: compass:adminPage could also be considered. - let admin = jira_admin_page.iter().any(|admin_function| { + let admin = jira_admin_page.clone().iter().any(|admin_function| { let Some(string) = admin_function.common_keys.function else { return false; }; return string == func.key; - }) || compass_admin_page.iter().any(|admin_function| { + }) || compass_admin_page.clone().iter().any(|admin_function| { let Some(string) = admin_function.function else { return false; }; @@ -739,11 +703,6 @@ impl<'a> ForgeModules<'a> { }) }) } - - pub fn add_optional(self, optional: Option>, iter: &mut BTreeSet<&str>) { - let Some(func) = optional; - iter.extend(func.function); - } } impl FunctionRef<'_, S> { @@ -785,7 +744,7 @@ impl<'a, Resolved> FunctionRef<'a, Resolved> { } } -impl<'a> TryFrom> for &'a FunctionRef<'a> { +impl<'a> TryFrom> for FunctionRef<'a> { type Error = Error; fn try_from(func_handler: FunctionMod<'a>) -> Result { @@ -882,7 +841,7 @@ mod tests { auth: vec!["my-auth-provider"], }), }; - let func_ref: FunctionRef = func_handler.try_into().unwrap(); + let func_ref: FunctionRef = FunctionRef::try_from(func_handler).unwrap(); assert_eq!( func_ref, FunctionRef { @@ -908,11 +867,15 @@ mod tests { "key": "my-macro", "title": "My Macro", "function": "Catch-me-if-you-can0", - "resolver": [ - "function": "Catch-me-if-you-can1", - ] - "config": "Catch-me-if-you-can2", - "export": "Catch-me-if-you-can3" + "resolver": { + "function": "Catch-me-if-you-can1" + }, + "config": { + "function": "Catch-me-if-you-can2" + }, + "export": { + "function": "Catch-me-if-you-can3" + } } ], "function": [ @@ -959,12 +922,12 @@ mod tests { assert_eq!(string, "Catch-me-if-you-can1"); } if let Some(justfunc) = manifest.modules.macros[0].config { - let Some(func) = justfunc.function; + let func = justfunc.function.unwrap(); assert_eq!(func, "Catch-me-if-you-can2"); } if let Some(justfunc) = manifest.modules.macros[0].export { - let Some(func) = justfunc.function; + let func = justfunc.function.unwrap(); assert_eq!(func, "Catch-me-if-you-can3"); } } From 41c84037431ae34c4a3c5ceaa7c0a9488c260030 Mon Sep 17 00:00:00 2001 From: Alina Wang Date: Wed, 10 Jan 2024 18:10:50 -0800 Subject: [PATCH 333/517] resolving merge conflicts for rebase --- crates/forge_loader/src/manifest.rs | 132 ++++++++++++++++++++++++++-- 1 file changed, 125 insertions(+), 7 deletions(-) diff --git a/crates/forge_loader/src/manifest.rs b/crates/forge_loader/src/manifest.rs index f980e78e..41256df8 100644 --- a/crates/forge_loader/src/manifest.rs +++ b/crates/forge_loader/src/manifest.rs @@ -66,18 +66,56 @@ struct ScheduledTrigger<'a> { interval: Interval, } +#[derive(Debug, Clone, PartialEq, Eq, Deserialize)] +struct DataProvider<'a> { + key: &'a str, + #[serde(flatten, borrow)] + callback: Callback<'a>, +} + +#[derive(Debug, Clone, PartialEq, Eq, Deserialize)] +pub struct Callback<'a> { + pub function: &'a str, +} + +// Struct for Custom field Module. Check that search suggestion gets read in correctly. +#[derive(Debug, Clone, PartialEq, Eq, Deserialize)] +struct CustomField<'a> { + #[serde(flatten, borrow)] + key: &'a str, + search_suggestion: &'a str, +} + +#[derive(Debug, Clone, PartialEq, Eq, Deserialize)] +struct UiModificatons<'a> { + #[serde(flatten, borrow)] + key: &'a str, + resolver: Callback<'a>, +} + +#[derive(Debug, Clone, PartialEq, Eq, Deserialize)] +struct WorkflowValidator<'a> { + #[serde(flatten, borrow)] + key: &'a str, + functon: &'a str, + resolver: Callback<'a>, +} + #[derive(Debug, Clone, PartialEq, Eq, Deserialize)] struct WorkflowPostFunction<'a> { #[serde(flatten, borrow)] - raw: RawTrigger<'a>, + key: &'a str, + function: &'a str, } +// Add more structs here for deserializing forge modules #[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] pub struct ForgeModules<'a> { #[serde(rename = "macro", default, borrow)] macros: Vec>, #[serde(rename = "function", default, borrow)] pub functions: Vec>, + // deserializing non user-invocable modules #[serde(rename = "webtrigger", default, borrow)] webtriggers: Vec>, #[serde(rename = "trigger", default, borrow)] @@ -86,8 +124,17 @@ pub struct ForgeModules<'a> { scheduled_triggers: Vec>, #[serde(rename = "consumer", default, borrow)] pub consumers: Vec>, + #[serde(rename = "compass:dataProvider", default, borrow)] + pub data_provider: Vec>, + #[serde(rename = "jira:customField", default, borrow)] + pub custom_field: Vec>, + #[serde(rename = "jira:uiModificatons", default, borrow)] + pub ui_modifications: Vec>, + #[serde(rename = "jira:workflowValidator", default, borrow)] + pub workflow_validator: Vec>, #[serde(rename = "jira:workflowPostFunction", default, borrow)] - workflow_post_functions: Vec>, + pub workflow_post_function: Vec>, + // deserializing user invokable module functions #[serde(flatten)] extra: FxHashMap>>, } @@ -166,12 +213,24 @@ pub struct FunctionRef<'a, S = Unresolved> { status: S, } +// Add an extra variant to the FunctionTy enum for non user invocable functions +// Indirect: functions indirectly invoked by user :O So kewl. +// TODO: change this to struct with bools #[derive(Debug, Clone, PartialEq, Eq)] pub enum FunctionTy { Invokable(T), WebTrigger(T), } +// Struct used for tracking what scan a funtion requires. +#[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] +pub struct Entrypoints<'a> { + function: Vec>, + invokable: bool, + web_trigger: bool, +} + +// Helper functions that help filter out which functions are what. impl FunctionTy { pub fn map(self, f: impl FnOnce(T) -> O) -> FunctionTy { match self { @@ -215,11 +274,9 @@ impl<'a> ForgeModules<'a> { //is not invoked by a user-invocable module. // number of webtriggers are usually low, so it's better to just sort them and reuse - // the vec's storage instead of using a HashSet self.webtriggers .sort_unstable_by_key(|trigger| trigger.function); - // same rational for using a BTreeSet let mut ignored_functions: BTreeSet<_> = self .scheduled_triggers .into_iter() @@ -230,13 +287,14 @@ impl<'a> ForgeModules<'a> { .map(|trigger| trigger.raw.function), ) .chain( - self.workflow_post_functions + self.workflow_post_function .into_iter() - .map(|pf| pf.raw.function), + .map(|pf| pf.function), ) .collect(); - let mut alternate_functions = vec![]; + // make alternate_functions all user-invokable functions + let mut alternate_functions: Vec<&str> = Vec::new(); for module in self.extra.into_values().flatten() { alternate_functions.extend(module.function); if let Some(resolver) = module.resolver { @@ -244,12 +302,19 @@ impl<'a> ForgeModules<'a> { } } + // Iterate over Consumers and check that if consumers isn't in alternate functions, add consumer funtion to be ignored + // assuming that alternate functions already has all user invokable functions. self.consumers.iter().for_each(|consumer| { if !alternate_functions.contains(&consumer.resolver.function) { ignored_functions.insert(consumer.resolver.function); } }); + // TODO: Iterate through all deserialized entrypoints that are represented as a struct when deserialized + // Update Struct values to be true or not. If any part true, then scan. + // This solution fixes the problem that we only check known user invokable modules and also acccounts for non-invokable module entry points + + // return non-user invokable functions self.functions.into_iter().filter_map(move |func| { if ignored_functions.contains(&func.key) { return None; @@ -416,4 +481,57 @@ mod tests { } ); } + + // Modified specific deserialization schemes for modules. Checking that new schemes can deserialize function values. + #[test] + fn test_new_deserialize() { + let json = r#"{ + "app": { + "name": "My App", + "id": "my-app" + }, + "modules": { + "macro": [ + { + "key": "my-macro", + "title": "My Macro", + "resolver": { + "function": Catch-me-if-you-can1 + }, + "config": { + "function": Catch-me-if-you-can2 + } + } + ], + "function": [ + { + "key": "my-function", + "handler": "my-function-handler", + "providers": { + "auth": ["my-auth-provider"] + } + } + ], + "webtrigger": [ + { + "key": "my-webtrigger", + "function": "my-webtrigger-handler" + } + ] + }, + "permissions": { + "scopes": [ + "my-scope" + ], + "content": { + "scripts": [ + "my-script.js" + ], + "styles": [ + "my-style.css" + ] + } + } + }"#; + } } From 8a8a0ae1a2020041486d2c1521f9027d4c031361 Mon Sep 17 00:00:00 2001 From: Alina Wang Date: Wed, 10 Jan 2024 18:10:50 -0800 Subject: [PATCH 334/517] added more todo statements and cleaned up struct definitions --- crates/forge_loader/src/manifest.rs | 21 +++++++++++++++------ crates/fsrt/src/main.rs | 2 ++ 2 files changed, 17 insertions(+), 6 deletions(-) diff --git a/crates/forge_loader/src/manifest.rs b/crates/forge_loader/src/manifest.rs index 41256df8..8cf22a68 100644 --- a/crates/forge_loader/src/manifest.rs +++ b/crates/forge_loader/src/manifest.rs @@ -16,7 +16,7 @@ struct AuthProviders<'a> { #[serde(borrow)] auth: Vec<&'a str>, } - +// Maps the Functions Module in common Modules #[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] pub struct FunctionMod<'a> { key: &'a str, @@ -25,24 +25,30 @@ pub struct FunctionMod<'a> { providers: Option>, } +// Modified #[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] struct ModInfo<'a> { - key: &'a str, - title: &'a str, + function: &'a str, } #[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] -struct MacroMod<'a> { - #[serde(flatten, borrow)] - info: ModInfo<'a>, +struct MacroMod<'a> { + #[serde(flatten, borrow)] + key: &'a str, + function: &'a str, + resolver: ModInfo<'a>, + config: ModInfo<'a>, + export: ModInfo<'a>, } +// WebTrigger => RawTrigger; WHY IS THIS NAMED DIFFERENTLY !? WHO CHANGED NAMES #[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] struct RawTrigger<'a> { key: &'a str, function: &'a str, } +// Trigger => EventTriger; WHY IS THIS NAMED DIFFERENTLY !? WHO CHANGED NAMES #[derive(Debug, Clone, PartialEq, Eq, Deserialize)] struct EventTrigger<'a> { #[serde(flatten, borrow)] @@ -59,6 +65,7 @@ enum Interval { Week, } +// Thank you to whomeever kept this one the same. T.T #[derive(Debug, Clone, PartialEq, Eq, Deserialize)] struct ScheduledTrigger<'a> { #[serde(flatten, borrow)] @@ -66,6 +73,7 @@ struct ScheduledTrigger<'a> { interval: Interval, } +// compass DataProvider module #[derive(Debug, Clone, PartialEq, Eq, Deserialize)] struct DataProvider<'a> { key: &'a str, @@ -73,6 +81,7 @@ struct DataProvider<'a> { callback: Callback<'a>, } +// Struct for mapping functions defined one more level in whose value is {function: string}. Used to represent resolver types. #[derive(Debug, Clone, PartialEq, Eq, Deserialize)] pub struct Callback<'a> { pub function: &'a str, diff --git a/crates/fsrt/src/main.rs b/crates/fsrt/src/main.rs index 3594ba3a..f8703d1a 100644 --- a/crates/fsrt/src/main.rs +++ b/crates/fsrt/src/main.rs @@ -298,6 +298,8 @@ fn scan_directory(dir: PathBuf, function: Option<&str>, opts: &Args) -> Result { let mut runner = DefintionAnalysisRunner::new(); From 172644c099d1db534e59cd65782e8362b4c427b1 Mon Sep 17 00:00:00 2001 From: Alina Wang Date: Wed, 10 Jan 2024 18:10:51 -0800 Subject: [PATCH 335/517] resolving merge conflicts for rebase --- crates/forge_loader/src/manifest.rs | 221 ++++++++++++++----- crates/forge_permission_resolver/src/test.rs | 7 + 2 files changed, 168 insertions(+), 60 deletions(-) diff --git a/crates/forge_loader/src/manifest.rs b/crates/forge_loader/src/manifest.rs index 8cf22a68..1ef7ca6d 100644 --- a/crates/forge_loader/src/manifest.rs +++ b/crates/forge_loader/src/manifest.rs @@ -9,6 +9,7 @@ use crate::{forgepermissions::ForgePermissions, Error}; use forge_utils::FxHashMap; use itertools::{Either, Itertools}; use serde::Deserialize; +use serde_json::map::Entry; use tracing::trace; #[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] @@ -16,7 +17,7 @@ struct AuthProviders<'a> { #[serde(borrow)] auth: Vec<&'a str>, } -// Maps the Functions Module in common Modules +// Maps the Functions Module in common Modules #[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] pub struct FunctionMod<'a> { key: &'a str, @@ -25,15 +26,15 @@ pub struct FunctionMod<'a> { providers: Option>, } -// Modified +// Modified #[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] struct ModInfo<'a> { function: &'a str, } #[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] -struct MacroMod<'a> { - #[serde(flatten, borrow)] +struct MacroMod<'a> { + #[serde(flatten, borrow)] key: &'a str, function: &'a str, resolver: ModInfo<'a>, @@ -41,14 +42,14 @@ struct MacroMod<'a> { export: ModInfo<'a>, } -// WebTrigger => RawTrigger; WHY IS THIS NAMED DIFFERENTLY !? WHO CHANGED NAMES +// WebTrigger => RawTrigger; WHY IS THIS NAMED DIFFERENTLY !? WHO CHANGED NAMES #[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] struct RawTrigger<'a> { key: &'a str, function: &'a str, } -// Trigger => EventTriger; WHY IS THIS NAMED DIFFERENTLY !? WHO CHANGED NAMES +// Trigger => EventTriger; WHY IS THIS NAMED DIFFERENTLY !? WHO CHANGED NAMES #[derive(Debug, Clone, PartialEq, Eq, Deserialize)] struct EventTrigger<'a> { #[serde(flatten, borrow)] @@ -65,7 +66,7 @@ enum Interval { Week, } -// Thank you to whomeever kept this one the same. T.T +// Thank you to whomeever kept this one the same. T.T #[derive(Debug, Clone, PartialEq, Eq, Deserialize)] struct ScheduledTrigger<'a> { #[serde(flatten, borrow)] @@ -75,7 +76,7 @@ struct ScheduledTrigger<'a> { // compass DataProvider module #[derive(Debug, Clone, PartialEq, Eq, Deserialize)] -struct DataProvider<'a> { +pub struct DataProvider<'a> { key: &'a str, #[serde(flatten, borrow)] callback: Callback<'a>, @@ -89,21 +90,21 @@ pub struct Callback<'a> { // Struct for Custom field Module. Check that search suggestion gets read in correctly. #[derive(Debug, Clone, PartialEq, Eq, Deserialize)] -struct CustomField<'a> { +pub struct CustomField<'a> { #[serde(flatten, borrow)] key: &'a str, search_suggestion: &'a str, } #[derive(Debug, Clone, PartialEq, Eq, Deserialize)] -struct UiModificatons<'a> { +pub struct UiModificatons<'a> { #[serde(flatten, borrow)] key: &'a str, resolver: Callback<'a>, } #[derive(Debug, Clone, PartialEq, Eq, Deserialize)] -struct WorkflowValidator<'a> { +pub struct WorkflowValidator<'a> { #[serde(flatten, borrow)] key: &'a str, functon: &'a str, @@ -111,7 +112,7 @@ struct WorkflowValidator<'a> { } #[derive(Debug, Clone, PartialEq, Eq, Deserialize)] -struct WorkflowPostFunction<'a> { +pub struct WorkflowPostFunction<'a> { #[serde(flatten, borrow)] key: &'a str, function: &'a str, @@ -232,9 +233,9 @@ pub enum FunctionTy { } // Struct used for tracking what scan a funtion requires. -#[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] +#[derive(Debug, Clone, PartialEq, Eq, Deserialize)] pub struct Entrypoints<'a> { - function: Vec>, + function: &'a str, invokable: bool, web_trigger: bool, } @@ -276,70 +277,170 @@ impl AsRef for FunctionTy { } impl<'a> ForgeModules<'a> { - pub fn into_analyzable_functions( - mut self, - ) -> impl Iterator>> { - //FIXME: The logic here is incorrect, a function should be in the ignored function IIF it - //is not invoked by a user-invocable module. - + // TODO: fix return type whop + pub fn into_analyzable_functions(mut self) -> impl Iterator> { // number of webtriggers are usually low, so it's better to just sort them and reuse self.webtriggers .sort_unstable_by_key(|trigger| trigger.function); - let mut ignored_functions: BTreeSet<_> = self + // Get all the Triggers and represent them as a new struct thing where "webtrigger" attribute is true + // for all trigger things + let web = self.webtriggers.iter().for_each(|webtriggers| { + Entrypoints { + function: webtriggers.function, + invokable: false, + web_trigger: true, + }; + }); + let event = self.event_triggers.iter().for_each(|event_triggers| { + Entrypoints { + function: event_triggers.raw.function, + invokable: false, + web_trigger: true, + }; + }); + let schedule = self .scheduled_triggers - .into_iter() - .map(|trigger| trigger.raw.function) - .chain( - self.event_triggers - .into_iter() - .map(|trigger| trigger.raw.function), - ) - .chain( - self.workflow_post_function - .into_iter() - .map(|pf| pf.function), - ) - .collect(); + .iter() + .for_each(|schedule_triggers| { + Entrypoints { + function: schedule_triggers.raw.function, + invokable: false, + web_trigger: true, + }; + }); + + // create arrays representing functions that expose user non-invokable functions + let consumer = self.consumers.iter().for_each(|consumers| { + Entrypoints { + function: consumers.resolver.function, + invokable: true, + web_trigger: false, + }; + }); + + let data_provider = self.data_provider.iter().for_each(|dataprovider| { + Entrypoints { + function: dataprovider.callback.function, + invokable: true, + web_trigger: false, + }; + }); + + let custom_field = self.custom_field.iter().for_each(|customfield| { + Entrypoints { + function: customfield.search_suggestion, + invokable: true, + web_trigger: false, + }; + }); + let ui_mod = self.ui_modifications.iter().for_each(|ui| { + Entrypoints { + function: ui.resolver.function, + invokable: true, + web_trigger: false, + }; + }); + + let workflow_validator = self.workflow_validator.iter().for_each(|validator| { + Entrypoints { + function: validator.resolver.function, + invokable: true, + web_trigger: false, + }; + }); + + let workflow_post = self + .workflow_post_function + .iter() + .for_each(|post_function| { + Entrypoints { + function: post_function.function, + invokable: true, + web_trigger: false, + }; + }); + + // let user_invokable = self.extra.into_values().flatten().into_iter().for_each(|invokable| { + + // if invokable.resolver != None { + // Entrypoints { + // function: invokable.resolver, + // invokable: true, + // web_trigger: false, + // }; + + // } + // Entrypoints { + // function: invokable.function, + // invokable: true, + // web_trigger: false, + // }; + // }); + + // let mut ignored_functions: BTreeSet<_> = self + // .scheduled_triggers + // .into_iter() + // .map(|trigger| trigger.raw.function) + // .chain( + // self.event_triggers + // .into_iter() + // .map(|trigger| trigger.raw.function), + // ) + // .collect(); + + // get array for user invokable module functions // make alternate_functions all user-invokable functions - let mut alternate_functions: Vec<&str> = Vec::new(); + let mut alternate_functions = Vec::new(); for module in self.extra.into_values().flatten() { - alternate_functions.extend(module.function); + if let Some(mod_function) = module.function { + alternate_functions.push(Entrypoints { + function: mod_function, + invokable: true, + web_trigger: false, + }); + } + if let Some(resolver) = module.resolver { - alternate_functions.push(resolver.function); + alternate_functions.push(Entrypoints { + function: resolver.function, + invokable: true, + web_trigger: false, + }); } } + workflow_post // Iterate over Consumers and check that if consumers isn't in alternate functions, add consumer funtion to be ignored // assuming that alternate functions already has all user invokable functions. - self.consumers.iter().for_each(|consumer| { - if !alternate_functions.contains(&consumer.resolver.function) { - ignored_functions.insert(consumer.resolver.function); - } - }); + // self.consumers.iter().for_each(|consumer| { + // if !alternate_functions.contains(&consumer.resolver.function) { + // ignored_functions.insert(consumer.resolver.function); + // } + // }); // TODO: Iterate through all deserialized entrypoints that are represented as a struct when deserialized // Update Struct values to be true or not. If any part true, then scan. // This solution fixes the problem that we only check known user invokable modules and also acccounts for non-invokable module entry points // return non-user invokable functions - self.functions.into_iter().filter_map(move |func| { - if ignored_functions.contains(&func.key) { - return None; - } - Some( - if self - .webtriggers - .binary_search_by_key(&func.key, |trigger| trigger.function) - .is_ok() - { - FunctionTy::WebTrigger(func) - } else { - FunctionTy::Invokable(func) - }, - ) - }) + // self.functions.into_iter().filter_map(move |func| { + // if ignored_functions.contains(&func.key) { + // return None; + // } + // Some( + // if self + // .webtriggers + // .binary_search_by_key(&func.key, |trigger| trigger.function) + // .is_ok() + // { + // FunctionTy::WebTrigger(func) + // } else { + // FunctionTy::Invokable(func) + // }, + // ) + // }) } } @@ -455,8 +556,8 @@ mod tests { assert_eq!(manifest.app.name, Some("My App")); assert_eq!(manifest.app.id, "my-app"); assert_eq!(manifest.modules.macros.len(), 1); - assert_eq!(manifest.modules.macros[0].info.title, "My Macro"); - assert_eq!(manifest.modules.macros[0].info.key, "my-macro"); + assert_eq!(manifest.modules.macros[0].key, "My Macro"); + assert_eq!(manifest.modules.macros[0].function, "my-macro"); assert_eq!(manifest.modules.functions.len(), 1); assert_eq!( manifest.modules.functions[0], diff --git a/crates/forge_permission_resolver/src/test.rs b/crates/forge_permission_resolver/src/test.rs index e9b8a01e..a0e6ad08 100644 --- a/crates/forge_permission_resolver/src/test.rs +++ b/crates/forge_permission_resolver/src/test.rs @@ -75,4 +75,11 @@ mod tests { assert_eq!(result, expected_permission); } + + + // TODO: Add a test case using a manifest that has a function exposed through both a non user invocable module and a user invocable module + #[test] + fn test_catch_indirect_func_invoke() { + assert_eq!(0, 0); + } } From 2c4cd220d44283a772eeef3426992c9dc732578f Mon Sep 17 00:00:00 2001 From: Alina Wang Date: Wed, 10 Jan 2024 18:10:51 -0800 Subject: [PATCH 336/517] resolving merge conflicts --- crates/forge_loader/src/manifest.rs | 76 ++++++++++++++--------------- 1 file changed, 38 insertions(+), 38 deletions(-) diff --git a/crates/forge_loader/src/manifest.rs b/crates/forge_loader/src/manifest.rs index 1ef7ca6d..7cc0b617 100644 --- a/crates/forge_loader/src/manifest.rs +++ b/crates/forge_loader/src/manifest.rs @@ -277,89 +277,89 @@ impl AsRef for FunctionTy { } impl<'a> ForgeModules<'a> { - // TODO: fix return type whop - pub fn into_analyzable_functions(mut self) -> impl Iterator> { + // TODO: function returns iterator where each item is some specified type. + pub fn into_analyzable_functions(mut self) { // number of webtriggers are usually low, so it's better to just sort them and reuse - self.webtriggers - .sort_unstable_by_key(|trigger| trigger.function); + // self.webtriggers + // .sort_unstable_by_key(|trigger| trigger.function); // Get all the Triggers and represent them as a new struct thing where "webtrigger" attribute is true // for all trigger things - let web = self.webtriggers.iter().for_each(|webtriggers| { - Entrypoints { + + let mut functions_to_scan = Vec::new(); + self.webtriggers.iter().for_each(|webtriggers| { + functions_to_scan.push(Entrypoints { function: webtriggers.function, invokable: false, web_trigger: true, - }; + }); }); - let event = self.event_triggers.iter().for_each(|event_triggers| { - Entrypoints { + self.event_triggers.iter().for_each(|event_triggers| { + functions_to_scan.push(Entrypoints { function: event_triggers.raw.function, invokable: false, web_trigger: true, - }; + }); }); - let schedule = self - .scheduled_triggers + self.scheduled_triggers .iter() .for_each(|schedule_triggers| { - Entrypoints { + functions_to_scan.push(Entrypoints { function: schedule_triggers.raw.function, invokable: false, web_trigger: true, - }; + }) }); // create arrays representing functions that expose user non-invokable functions - let consumer = self.consumers.iter().for_each(|consumers| { - Entrypoints { + self.consumers.iter().for_each(|consumers| { + functions_to_scan.push(Entrypoints { function: consumers.resolver.function, invokable: true, web_trigger: false, - }; + }) }); - let data_provider = self.data_provider.iter().for_each(|dataprovider| { - Entrypoints { + self.data_provider.iter().for_each(|dataprovider| { + functions_to_scan.push(Entrypoints { function: dataprovider.callback.function, invokable: true, web_trigger: false, - }; + }) }); - let custom_field = self.custom_field.iter().for_each(|customfield| { - Entrypoints { + self.custom_field.iter().for_each(|customfield| { + functions_to_scan.push(Entrypoints { function: customfield.search_suggestion, invokable: true, web_trigger: false, - }; + }) }); - let ui_mod = self.ui_modifications.iter().for_each(|ui| { - Entrypoints { + self.ui_modifications.iter().for_each(|ui| { + functions_to_scan.push(Entrypoints { function: ui.resolver.function, invokable: true, web_trigger: false, - }; + }) }); - let workflow_validator = self.workflow_validator.iter().for_each(|validator| { - Entrypoints { + self.workflow_validator.iter().for_each(|validator| { + functions_to_scan.push(Entrypoints { function: validator.resolver.function, invokable: true, web_trigger: false, - }; + }) }); - let workflow_post = self - .workflow_post_function + self.workflow_post_function .iter() .for_each(|post_function| { - Entrypoints { + functions_to_scan.push(Entrypoints { function: post_function.function, invokable: true, web_trigger: false, - }; + }) }); // let user_invokable = self.extra.into_values().flatten().into_iter().for_each(|invokable| { @@ -392,10 +392,10 @@ impl<'a> ForgeModules<'a> { // get array for user invokable module functions // make alternate_functions all user-invokable functions - let mut alternate_functions = Vec::new(); + // let mut alternate_functions = Vec::new(); for module in self.extra.into_values().flatten() { if let Some(mod_function) = module.function { - alternate_functions.push(Entrypoints { + functions_to_scan.push(Entrypoints { function: mod_function, invokable: true, web_trigger: false, @@ -403,15 +403,15 @@ impl<'a> ForgeModules<'a> { } if let Some(resolver) = module.resolver { - alternate_functions.push(Entrypoints { + functions_to_scan.push(Entrypoints { function: resolver.function, invokable: true, web_trigger: false, }); } } - - workflow_post + functions_to_scan.into_iter(); + // alternate_functions.into_iter(); // Iterate over Consumers and check that if consumers isn't in alternate functions, add consumer funtion to be ignored // assuming that alternate functions already has all user invokable functions. // self.consumers.iter().for_each(|consumer| { From 569c1dffa27a59643a16d412fd3ff9e64b345d0b Mon Sep 17 00:00:00 2001 From: Alina Wang Date: Wed, 10 Jan 2024 18:10:51 -0800 Subject: [PATCH 337/517] resolving merge conflicts --- crates/forge_loader/src/manifest.rs | 53 ++++++++++++++--------------- 1 file changed, 26 insertions(+), 27 deletions(-) diff --git a/crates/forge_loader/src/manifest.rs b/crates/forge_loader/src/manifest.rs index 7cc0b617..52384ffe 100644 --- a/crates/forge_loader/src/manifest.rs +++ b/crates/forge_loader/src/manifest.rs @@ -278,7 +278,7 @@ impl AsRef for FunctionTy { impl<'a> ForgeModules<'a> { // TODO: function returns iterator where each item is some specified type. - pub fn into_analyzable_functions(mut self) { + pub fn into_analyzable_functions(self) { // number of webtriggers are usually low, so it's better to just sort them and reuse // self.webtriggers // .sort_unstable_by_key(|trigger| trigger.function); @@ -362,33 +362,32 @@ impl<'a> ForgeModules<'a> { }) }); - // let user_invokable = self.extra.into_values().flatten().into_iter().for_each(|invokable| { - - // if invokable.resolver != None { - // Entrypoints { - // function: invokable.resolver, - // invokable: true, - // web_trigger: false, - // }; - - // } - // Entrypoints { - // function: invokable.function, - // invokable: true, - // web_trigger: false, - // }; - // }); + // get user invokable modules that have additional exposure endpoints. + // ie macros has config and export fields on top of resolver fields that are functions + for macros in self.macros { + if let Some(resolver) = Some(macros.resolver) { + functions_to_scan.push(Entrypoints { + function: resolver.function, + invokable: true, + web_trigger: false, + }) + } - // let mut ignored_functions: BTreeSet<_> = self - // .scheduled_triggers - // .into_iter() - // .map(|trigger| trigger.raw.function) - // .chain( - // self.event_triggers - // .into_iter() - // .map(|trigger| trigger.raw.function), - // ) - // .collect(); + if let Some(config) = Some(macros.config) { + functions_to_scan.push(Entrypoints { + function: config.function, + invokable: true, + web_trigger: false, + }) + } + if let Some(export) = Some(macros.export) { + functions_to_scan.push(Entrypoints { + function: export.function, + invokable: true, + web_trigger: false, + }) + } + } // get array for user invokable module functions // make alternate_functions all user-invokable functions From 40cdf4f7b416c4d9d39f1ede0dc8485c1fb2c13b Mon Sep 17 00:00:00 2001 From: Alina Wang Date: Wed, 10 Jan 2024 18:10:51 -0800 Subject: [PATCH 338/517] resolving merge conflicts --- crates/forge_loader/src/manifest.rs | 42 ++++++++++++++++++++++------- 1 file changed, 32 insertions(+), 10 deletions(-) diff --git a/crates/forge_loader/src/manifest.rs b/crates/forge_loader/src/manifest.rs index 52384ffe..e7965b72 100644 --- a/crates/forge_loader/src/manifest.rs +++ b/crates/forge_loader/src/manifest.rs @@ -34,12 +34,15 @@ struct ModInfo<'a> { #[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] struct MacroMod<'a> { - #[serde(flatten, borrow)] + // #[serde(flatten, borrow)] key: &'a str, function: &'a str, - resolver: ModInfo<'a>, - config: ModInfo<'a>, - export: ModInfo<'a>, + #[serde(borrow)] + resolver: Option>, + #[serde(borrow)] + config: Option>, + #[serde(borrow)] + export: Option>, } // WebTrigger => RawTrigger; WHY IS THIS NAMED DIFFERENTLY !? WHO CHANGED NAMES @@ -365,7 +368,7 @@ impl<'a> ForgeModules<'a> { // get user invokable modules that have additional exposure endpoints. // ie macros has config and export fields on top of resolver fields that are functions for macros in self.macros { - if let Some(resolver) = Some(macros.resolver) { + if let Some(resolver) = macros.resolver { functions_to_scan.push(Entrypoints { function: resolver.function, invokable: true, @@ -373,14 +376,14 @@ impl<'a> ForgeModules<'a> { }) } - if let Some(config) = Some(macros.config) { + if let Some(config) = macros.config { functions_to_scan.push(Entrypoints { function: config.function, invokable: true, web_trigger: false, }) } - if let Some(export) = Some(macros.export) { + if let Some(export) = macros.export { functions_to_scan.push(Entrypoints { function: export.function, invokable: true, @@ -518,7 +521,7 @@ mod tests { "macro": [ { "key": "my-macro", - "title": "My Macro" + "function": "My Macro" } ], "function": [ @@ -604,11 +607,15 @@ mod tests { { "key": "my-macro", "title": "My Macro", + "function": "Catch-me-if-you-can0", "resolver": { - "function": Catch-me-if-you-can1 + "function": "Catch-me-if-you-can1" }, "config": { - "function": Catch-me-if-you-can2 + "function": "Catch-me-if-you-can2" + }, + "export": { + "function": "Catch-me-if-you-can3" } } ], @@ -642,5 +649,20 @@ mod tests { } } }"#; + let manifest: ForgeManifest = serde_json::from_str(json).unwrap(); + assert_eq!(manifest.modules.macros.len(), 1); + assert_eq!(manifest.modules.macros[0].function, "Catch-me-if-you-can0"); + + if let Some(func) = &manifest.modules.macros[0].resolver { + assert_eq!(func.function, "Catch-me-if-you-can1"); + } + + if let Some(func) = &manifest.modules.macros[0].config { + assert_eq!(func.function, "Catch-me-if-you-can2"); + } + + if let Some(func) = &manifest.modules.macros[0].export { + assert_eq!(func.function, "Catch-me-if-you-can3"); + } } } From 94acd6d0d769cdb2fd45735fe99a0e7af6113e14 Mon Sep 17 00:00:00 2001 From: Alina Wang Date: Wed, 10 Jan 2024 18:10:51 -0800 Subject: [PATCH 339/517] resolving merge conflicts --- crates/forge_loader/src/manifest.rs | 42 +++------------- crates/fsrt/src/main.rs | 76 +++++++++++++++++++++++++---- 2 files changed, 73 insertions(+), 45 deletions(-) diff --git a/crates/forge_loader/src/manifest.rs b/crates/forge_loader/src/manifest.rs index e7965b72..bc19201d 100644 --- a/crates/forge_loader/src/manifest.rs +++ b/crates/forge_loader/src/manifest.rs @@ -238,9 +238,9 @@ pub enum FunctionTy { // Struct used for tracking what scan a funtion requires. #[derive(Debug, Clone, PartialEq, Eq, Deserialize)] pub struct Entrypoints<'a> { - function: &'a str, - invokable: bool, - web_trigger: bool, + pub function: &'a str, + pub invokable: bool, + pub web_trigger: bool, } // Helper functions that help filter out which functions are what. @@ -281,7 +281,7 @@ impl AsRef for FunctionTy { impl<'a> ForgeModules<'a> { // TODO: function returns iterator where each item is some specified type. - pub fn into_analyzable_functions(self) { + pub fn into_analyzable_functions(self) -> Vec> { // number of webtriggers are usually low, so it's better to just sort them and reuse // self.webtriggers // .sort_unstable_by_key(|trigger| trigger.function); @@ -394,7 +394,6 @@ impl<'a> ForgeModules<'a> { // get array for user invokable module functions // make alternate_functions all user-invokable functions - // let mut alternate_functions = Vec::new(); for module in self.extra.into_values().flatten() { if let Some(mod_function) = module.function { functions_to_scan.push(Entrypoints { @@ -412,37 +411,8 @@ impl<'a> ForgeModules<'a> { }); } } - functions_to_scan.into_iter(); - // alternate_functions.into_iter(); - // Iterate over Consumers and check that if consumers isn't in alternate functions, add consumer funtion to be ignored - // assuming that alternate functions already has all user invokable functions. - // self.consumers.iter().for_each(|consumer| { - // if !alternate_functions.contains(&consumer.resolver.function) { - // ignored_functions.insert(consumer.resolver.function); - // } - // }); - - // TODO: Iterate through all deserialized entrypoints that are represented as a struct when deserialized - // Update Struct values to be true or not. If any part true, then scan. - // This solution fixes the problem that we only check known user invokable modules and also acccounts for non-invokable module entry points - - // return non-user invokable functions - // self.functions.into_iter().filter_map(move |func| { - // if ignored_functions.contains(&func.key) { - // return None; - // } - // Some( - // if self - // .webtriggers - // .binary_search_by_key(&func.key, |trigger| trigger.function) - // .is_ok() - // { - // FunctionTy::WebTrigger(func) - // } else { - // FunctionTy::Invokable(func) - // }, - // ) - // }) + + return functions_to_scan; } } diff --git a/crates/fsrt/src/main.rs b/crates/fsrt/src/main.rs index f8703d1a..92969c16 100644 --- a/crates/fsrt/src/main.rs +++ b/crates/fsrt/src/main.rs @@ -13,6 +13,13 @@ use std::{ sync::Arc, }; +<<<<<<< HEAD +======= +use clap::{Parser, ValueHint}; +use miette::{IntoDiagnostic, Result}; + +use serde_json::map::Entry; +>>>>>>> cd2ed7b (edited main to iterate over vector of functions. TODO: edit add_funcs in main.rs and test into_analyzble_function use case in main.rs) use swc_core::{ common::{Globals, Mark, SourceMap, GLOBALS}, ecma::{ @@ -38,7 +45,7 @@ use forge_analyzer::{ resolver::resolve_calls, }; -use forge_loader::manifest::{ForgeManifest, FunctionRef, FunctionTy}; +use forge_loader::manifest::{ForgeManifest, FunctionRef, FunctionTy, Entrypoints}; use walkdir::WalkDir; #[derive(Parser, Debug)] @@ -86,7 +93,12 @@ struct ForgeProject { sm: Arc, ctx: AppCtx, env: Environment, +<<<<<<< HEAD funcs: Vec>, +======= + funcs: Vec>, + opts: Opts, +>>>>>>> cd2ed7b (edited main to iterate over vector of functions. TODO: edit add_funcs in main.rs and test into_analyzble_function use case in main.rs) } impl ForgeProject { @@ -136,8 +148,8 @@ impl ForgeProject { funcs: vec![], } } - - fn add_funcs<'a, I: IntoIterator>>(&mut self, iter: I) { +// TODO: edit to work with new iterator that not FUNCTIONTY + fn add_funcs<'a, I: IntoIterator>>(&mut self, iter: I) { self.funcs.extend(iter.into_iter().flat_map(|ftype| { ftype.sequence(|(func_name, path)| { let modid = self.ctx.modid_from_path(&path)?; @@ -206,12 +218,14 @@ fn scan_directory(dir: PathBuf, function: Option<&str>, opts: &Args) -> Result(resolved_func.into_func_path()) - }) - }); + let funcrefs = &manifest.modules.into_analyzable_functions(); + + // .flat_map(|f| { + // f.sequence(|fmod| { + // let resolved_func = FunctionRef::try_from(fmod)?.try_resolve(&paths, &dir)?; + // Ok::<_, forge_loader::Error>(resolved_func.into_func_path()) + // }) + // }); let src_root = dir.join("src"); let mut proj = ForgeProject::with_files_and_sourceroot(src_root, paths.clone(), secret_packages); @@ -300,6 +314,7 @@ fn scan_directory(dir: PathBuf, function: Option<&str>, opts: &Args) -> Result { let mut runner = DefintionAnalysisRunner::new(); @@ -413,7 +428,50 @@ fn scan_directory(dir: PathBuf, function: Option<&str>, opts: &Args) -> Result { + // let mut checker = AuthZChecker::new(); + // debug!("checking {func} at {path:?}"); + // if let Err(err) = interp.run_checker(def, &mut checker, path.clone(), func.clone()) + // { + // warn!("error while scanning {func} in {path:?}: {err}"); + // } + // reporter.add_vulnerabilities(checker.into_vulns()); + // } + // FunctionTy::WebTrigger((ref func, ref path, _, def)) => { + // let mut checker = AuthenticateChecker::new(); + // debug!("checking webtrigger {func} at {path:?}"); + // if let Err(err) = + // authn_interp.run_checker(def, &mut checker, path.clone(), func.clone()) + // { + // warn!("error while scanning {func} in {path:?}: {err}"); + // } + // reporter.add_vulnerabilities(checker.into_vulns()); + // } + // } + + if func.invokable { + let mut checker = AuthZChecker::new(); + debug!("checking {func} at {path:?}"); + if let Err(err) = interp.run_checker(def, &mut checker, path.clone(), func.clone()) + { + warn!("error while scanning {func} in {path:?}: {err}"); + } + reporter.add_vulnerabilities(checker.into_vulns()); + + } else if func.web_trigger { + let mut checker = AuthenticateChecker::new(); + debug!("checking webtrigger {func} at {path:?}"); + if let Err(err) = + authn_interp.run_checker(def, &mut checker, path.clone(), func.clone()) + { + warn!("error while scanning {func} in {path:?}: {err}"); +>>>>>>> cd2ed7b (edited main to iterate over vector of functions. TODO: edit add_funcs in main.rs and test into_analyzble_function use case in main.rs) } + reporter.add_vulnerabilities(checker.into_vulns()); + + } } From 0cbf1a6a2af588bcb54aa8c01ef4d55a44919141 Mon Sep 17 00:00:00 2001 From: Alina Wang Date: Wed, 10 Jan 2024 18:10:51 -0800 Subject: [PATCH 340/517] added new modules for additional endpoints in user invokable modules --- crates/forge_loader/src/manifest.rs | 50 +++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) diff --git a/crates/forge_loader/src/manifest.rs b/crates/forge_loader/src/manifest.rs index bc19201d..5b26f378 100644 --- a/crates/forge_loader/src/manifest.rs +++ b/crates/forge_loader/src/manifest.rs @@ -33,8 +33,12 @@ struct ModInfo<'a> { } #[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] +<<<<<<< HEAD struct MacroMod<'a> { // #[serde(flatten, borrow)] +======= +struct MacroMod<'a> { +>>>>>>> 31a3048 (added new modules for additional endpoints in user invokable modules) key: &'a str, function: &'a str, #[serde(borrow)] @@ -45,7 +49,47 @@ struct MacroMod<'a> { export: Option>, } +<<<<<<< HEAD // WebTrigger => RawTrigger; WHY IS THIS NAMED DIFFERENTLY !? WHO CHANGED NAMES +======= +#[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] +struct ContentByLineItem<'a> { + key: &'a str, + function: &'a str, + #[serde(borrow)] + resolver: Option>, + #[serde(borrow)] + dynamic_properties: Option>, +} + +#[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] +struct IssueGlance<'a> { + key: &'a str, + function: &'a str, + #[serde(borrow)] + resolver: Option>, + #[serde(borrow)] + dynamic_properties: Option>, + +} + +#[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] +struct AccessImportType<'a> { + key: &'a str, + function: &'a str, + #[serde(borrow)] + one_delete_import: Option>, + #[serde(borrow)] + start_import: Option>, + #[serde(borrow)] + stop_import: Option>, + #[serde(borrow)] + import_status: Option>, + +} + +// WebTrigger => RawTrigger; WHY IS THIS NAMED DIFFERENTLY !? WHO CHANGED NAMES +>>>>>>> 31a3048 (added new modules for additional endpoints in user invokable modules) #[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] struct RawTrigger<'a> { key: &'a str, @@ -128,6 +172,12 @@ pub struct ForgeModules<'a> { macros: Vec>, #[serde(rename = "function", default, borrow)] pub functions: Vec>, + #[serde(rename = "contentByLineItem", default, borrow)] + content_by_line_item: Vec>, + #[serde(rename = "jira:issueGlance", default, borrow)] + issue_glance: Vec>, + #[serde(rename = "jira:accessImportType", default, borrow)] + access_import_type: Vec>, // deserializing non user-invocable modules #[serde(rename = "webtrigger", default, borrow)] webtriggers: Vec>, From a84775f327fd6cbd181af6052ba552279b30a228 Mon Sep 17 00:00:00 2001 From: Alina Wang Date: Wed, 10 Jan 2024 18:10:51 -0800 Subject: [PATCH 341/517] added methods for adding additional user invokable endpoints to vector for scanning. --- crates/forge_loader/src/manifest.rs | 120 ++++++++++++++++++++++------ 1 file changed, 97 insertions(+), 23 deletions(-) diff --git a/crates/forge_loader/src/manifest.rs b/crates/forge_loader/src/manifest.rs index 5b26f378..270b16b1 100644 --- a/crates/forge_loader/src/manifest.rs +++ b/crates/forge_loader/src/manifest.rs @@ -33,12 +33,7 @@ struct ModInfo<'a> { } #[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] -<<<<<<< HEAD struct MacroMod<'a> { - // #[serde(flatten, borrow)] -======= -struct MacroMod<'a> { ->>>>>>> 31a3048 (added new modules for additional endpoints in user invokable modules) key: &'a str, function: &'a str, #[serde(borrow)] @@ -49,47 +44,41 @@ struct MacroMod<'a> { export: Option>, } -<<<<<<< HEAD -// WebTrigger => RawTrigger; WHY IS THIS NAMED DIFFERENTLY !? WHO CHANGED NAMES -======= #[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] -struct ContentByLineItem<'a> { +struct ContentByLineItem<'a> { key: &'a str, function: &'a str, - #[serde(borrow)] + #[serde(borrow)] resolver: Option>, - #[serde(borrow)] + #[serde(borrow)] dynamic_properties: Option>, } #[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] -struct IssueGlance<'a> { +struct IssueGlance<'a> { key: &'a str, function: &'a str, - #[serde(borrow)] + #[serde(borrow)] resolver: Option>, - #[serde(borrow)] + #[serde(borrow)] dynamic_properties: Option>, - } #[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] -struct AccessImportType<'a> { +struct AccessImportType<'a> { key: &'a str, function: &'a str, - #[serde(borrow)] + #[serde(borrow)] one_delete_import: Option>, - #[serde(borrow)] + #[serde(borrow)] start_import: Option>, - #[serde(borrow)] + #[serde(borrow)] stop_import: Option>, - #[serde(borrow)] + #[serde(borrow)] import_status: Option>, - } -// WebTrigger => RawTrigger; WHY IS THIS NAMED DIFFERENTLY !? WHO CHANGED NAMES ->>>>>>> 31a3048 (added new modules for additional endpoints in user invokable modules) +// WebTrigger => RawTrigger; WHY IS THIS NAMED DIFFERENTLY !? WHO CHANGED NAMES #[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] struct RawTrigger<'a> { key: &'a str, @@ -442,6 +431,91 @@ impl<'a> ForgeModules<'a> { } } + for contentitem in self.content_by_line_item { + functions_to_scan.push(Entrypoints { + function: contentitem.function, + invokable: true, + web_trigger: false, + }); + if let Some(resolver) = contentitem.resolver { + functions_to_scan.push(Entrypoints { + function: resolver.function, + invokable: true, + web_trigger: false, + }) + } + + if let Some(dynamic_properties) = contentitem.dynamic_properties { + functions_to_scan.push(Entrypoints { + function: dynamic_properties.function, + invokable: true, + web_trigger: false, + }) + } + } + + for issue in self.issue_glance { + functions_to_scan.push(Entrypoints { + function: issue.function, + invokable: true, + web_trigger: false, + }); + if let Some(resolver) = issue.resolver { + functions_to_scan.push(Entrypoints { + function: resolver.function, + invokable: true, + web_trigger: false, + }) + } + + if let Some(dynamic_properties) = issue.dynamic_properties { + functions_to_scan.push(Entrypoints { + function: dynamic_properties.function, + invokable: true, + web_trigger: false, + }) + } + } + + for access in self.access_import_type { + functions_to_scan.push(Entrypoints { + function: access.function, + invokable: true, + web_trigger: false, + }); + if let Some(delete) = access.one_delete_import { + functions_to_scan.push(Entrypoints { + function: delete.function, + invokable: true, + web_trigger: false, + }) + } + + if let Some(start) = access.start_import { + functions_to_scan.push(Entrypoints { + function: start.function, + invokable: true, + web_trigger: false, + }) + } + + if let Some(stop) = access.stop_import { + functions_to_scan.push(Entrypoints { + function: stop.function, + invokable: true, + web_trigger: false, + }) + } + + if let Some(status) = access.import_status { + functions_to_scan.push(Entrypoints { + function: status.function, + invokable: true, + web_trigger: false, + }) + } + } + // get array for user invokable module functions // make alternate_functions all user-invokable functions for module in self.extra.into_values().flatten() { From 14ddd61c9f4b5558a7f7bd676816a52c60ca5018 Mon Sep 17 00:00:00 2001 From: Alina Wang Date: Wed, 10 Jan 2024 18:10:51 -0800 Subject: [PATCH 342/517] resolving merge conflicts --- crates/forge_loader/src/manifest.rs | 44 +++++++++++++++++++++-------- 1 file changed, 32 insertions(+), 12 deletions(-) diff --git a/crates/forge_loader/src/manifest.rs b/crates/forge_loader/src/manifest.rs index 270b16b1..b5783e61 100644 --- a/crates/forge_loader/src/manifest.rs +++ b/crates/forge_loader/src/manifest.rs @@ -115,13 +115,7 @@ struct ScheduledTrigger<'a> { pub struct DataProvider<'a> { key: &'a str, #[serde(flatten, borrow)] - callback: Callback<'a>, -} - -// Struct for mapping functions defined one more level in whose value is {function: string}. Used to represent resolver types. -#[derive(Debug, Clone, PartialEq, Eq, Deserialize)] -pub struct Callback<'a> { - pub function: &'a str, + callback: ModInfo<'a>, } // Struct for Custom field Module. Check that search suggestion gets read in correctly. @@ -129,22 +123,27 @@ pub struct Callback<'a> { pub struct CustomField<'a> { #[serde(flatten, borrow)] key: &'a str, + // all attributes below involve function calls + value: &'a str, search_suggestion: &'a str, + function: &'a str, + edit: &'a str, + resolver: ModInfo<'a>, } #[derive(Debug, Clone, PartialEq, Eq, Deserialize)] pub struct UiModificatons<'a> { - #[serde(flatten, borrow)] key: &'a str, - resolver: Callback<'a>, + #[serde(flatten, borrow)] + resolver: ModInfo<'a>, } #[derive(Debug, Clone, PartialEq, Eq, Deserialize)] pub struct WorkflowValidator<'a> { - #[serde(flatten, borrow)] key: &'a str, functon: &'a str, - resolver: Callback<'a>, + #[serde(flatten, borrow)] + resolver: ModInfo<'a>, } #[derive(Debug, Clone, PartialEq, Eq, Deserialize)] @@ -371,11 +370,32 @@ impl<'a> ForgeModules<'a> { }); self.custom_field.iter().for_each(|customfield| { + functions_to_scan.push(Entrypoints { + function: customfield.value, + invokable: true, + web_trigger: false, + }); functions_to_scan.push(Entrypoints { function: customfield.search_suggestion, invokable: true, web_trigger: false, - }) + }); + functions_to_scan.push(Entrypoints { + function: customfield.function, + invokable: true, + web_trigger: false, + }); + functions_to_scan.push(Entrypoints { + function: customfield.edit, + invokable: true, + web_trigger: false, + }); + + functions_to_scan.push(Entrypoints { + function: customfield.resolver.function, + invokable: true, + web_trigger: false, + }); }); self.ui_modifications.iter().for_each(|ui| { From a125465c7433a7427979931d6ac37792afc6f095 Mon Sep 17 00:00:00 2001 From: Alina Wang Date: Wed, 10 Jan 2024 18:10:51 -0800 Subject: [PATCH 343/517] changed entrypoints to entrypoint. Working on other comments --- crates/forge_loader/src/manifest.rs | 233 ++++++++++++++++------------ crates/fsrt/src/main.rs | 41 +++-- 2 files changed, 153 insertions(+), 121 deletions(-) diff --git a/crates/forge_loader/src/manifest.rs b/crates/forge_loader/src/manifest.rs index b5783e61..df0db8db 100644 --- a/crates/forge_loader/src/manifest.rs +++ b/crates/forge_loader/src/manifest.rs @@ -140,10 +140,10 @@ pub struct UiModificatons<'a> { #[derive(Debug, Clone, PartialEq, Eq, Deserialize)] pub struct WorkflowValidator<'a> { - key: &'a str, - functon: &'a str, #[serde(flatten, borrow)] - resolver: ModInfo<'a>, + key: &'a str, + function: &'a str, + resolver: ModInfo<'a> } #[derive(Debug, Clone, PartialEq, Eq, Deserialize)] @@ -275,7 +275,7 @@ pub enum FunctionTy { // Struct used for tracking what scan a funtion requires. #[derive(Debug, Clone, PartialEq, Eq, Deserialize)] -pub struct Entrypoints<'a> { +pub struct Entrypoint<'a> { pub function: &'a str, pub invokable: bool, pub web_trigger: bool, @@ -318,34 +318,41 @@ impl AsRef for FunctionTy { } impl<'a> ForgeModules<'a> { - // TODO: function returns iterator where each item is some specified type. - pub fn into_analyzable_functions(self) -> Vec> { + +// TODO: function returns iterator where each item is some specified type. + pub fn into_analyzable_functions ( + self, + ) -> Vec>{ // number of webtriggers are usually low, so it's better to just sort them and reuse - // self.webtriggers - // .sort_unstable_by_key(|trigger| trigger.function); + self.webtriggers.iter(). + .sort_unstable_by_key(|trigger| trigger.function); // Get all the Triggers and represent them as a new struct thing where "webtrigger" attribute is true // for all trigger things let mut functions_to_scan = Vec::new(); self.webtriggers.iter().for_each(|webtriggers| { - functions_to_scan.push(Entrypoints { - function: webtriggers.function, - invokable: false, - web_trigger: true, - }); + functions_to_scan.push( + Entrypoint { + function: webtriggers.function, + invokable: false, + web_trigger: true, + } + ); + }); self.event_triggers.iter().for_each(|event_triggers| { - functions_to_scan.push(Entrypoints { - function: event_triggers.raw.function, - invokable: false, - web_trigger: true, - }); + functions_to_scan.push( + Entrypoint { + function: event_triggers.raw.function, + invokable: false, + web_trigger: true, + } + ); }); - self.scheduled_triggers - .iter() - .for_each(|schedule_triggers| { - functions_to_scan.push(Entrypoints { + self.scheduled_triggers.iter().for_each(|schedule_triggers| { + functions_to_scan.push( + Entrypoint { function: schedule_triggers.raw.function, invokable: false, web_trigger: true, @@ -354,70 +361,96 @@ impl<'a> ForgeModules<'a> { // create arrays representing functions that expose user non-invokable functions self.consumers.iter().for_each(|consumers| { - functions_to_scan.push(Entrypoints { - function: consumers.resolver.function, - invokable: true, - web_trigger: false, - }) + functions_to_scan.push( + Entrypoint { + function: consumers.resolver.function, + invokable: true, + web_trigger: false, + } + ) }); self.data_provider.iter().for_each(|dataprovider| { - functions_to_scan.push(Entrypoints { - function: dataprovider.callback.function, - invokable: true, - web_trigger: false, - }) + functions_to_scan.push( + Entrypoint { + function: dataprovider.callback.function, + invokable: true, + web_trigger: false, + } + ) }); self.custom_field.iter().for_each(|customfield| { - functions_to_scan.push(Entrypoints { - function: customfield.value, - invokable: true, - web_trigger: false, - }); - functions_to_scan.push(Entrypoints { - function: customfield.search_suggestion, - invokable: true, - web_trigger: false, - }); - functions_to_scan.push(Entrypoints { - function: customfield.function, - invokable: true, - web_trigger: false, - }); - functions_to_scan.push(Entrypoints { - function: customfield.edit, - invokable: true, - web_trigger: false, - }); + functions_to_scan.push( + Entrypoint { + function: customfield.value, + invokable: true, + web_trigger: false, + } + ); + functions_to_scan.push( + Entrypoint { + function: customfield.search_suggestion, + invokable: true, + web_trigger: false, + } + ); + functions_to_scan.push( + Entrypoint { + function: customfield.function, + invokable: true, + web_trigger: false, + } + ); + functions_to_scan.push( + Entrypoint { + function: customfield.edit, + invokable: true, + web_trigger: false, + } + ); + + functions_to_scan.push( + Entrypoint { + function: customfield.resolver.function, + invokable: true, + web_trigger: false, + } + ); - functions_to_scan.push(Entrypoints { - function: customfield.resolver.function, - invokable: true, - web_trigger: false, - }); }); self.ui_modifications.iter().for_each(|ui| { - functions_to_scan.push(Entrypoints { - function: ui.resolver.function, - invokable: true, - web_trigger: false, - }) + functions_to_scan.push( + Entrypoint { + function: ui.resolver.function, + invokable: true, + web_trigger: false, + } + ) }); self.workflow_validator.iter().for_each(|validator| { - functions_to_scan.push(Entrypoints { - function: validator.resolver.function, - invokable: true, - web_trigger: false, - }) - }); + functions_to_scan.push( + Entrypoint { + function: validator.function, + invokable: true, + web_trigger: false, + } + ); - self.workflow_post_function - .iter() - .for_each(|post_function| { - functions_to_scan.push(Entrypoints { + functions_to_scan.push( + Entrypoint { + function: validator.resolver.function, + invokable: true, + web_trigger: false, + } + ); + }); + + self.workflow_post_function.iter().for_each(|post_function| { + functions_to_scan.push( + Entrypoint { function: post_function.function, invokable: true, web_trigger: false, @@ -427,23 +460,23 @@ impl<'a> ForgeModules<'a> { // get user invokable modules that have additional exposure endpoints. // ie macros has config and export fields on top of resolver fields that are functions for macros in self.macros { - if let Some(resolver) = macros.resolver { - functions_to_scan.push(Entrypoints { + if let Some(resolver)= macros.resolver { + functions_to_scan.push(Entrypoint { function: resolver.function, invokable: true, web_trigger: false, }) } - if let Some(config) = macros.config { - functions_to_scan.push(Entrypoints { + if let Some(config)= macros.config { + functions_to_scan.push(Entrypoint { function: config.function, invokable: true, web_trigger: false, }) } - if let Some(export) = macros.export { - functions_to_scan.push(Entrypoints { + if let Some(export)= macros.export { + functions_to_scan.push(Entrypoint { function: export.function, invokable: true, web_trigger: false, @@ -452,21 +485,21 @@ impl<'a> ForgeModules<'a> { } for contentitem in self.content_by_line_item { - functions_to_scan.push(Entrypoints { + functions_to_scan.push(Entrypoint { function: contentitem.function, invokable: true, web_trigger: false, }); - if let Some(resolver) = contentitem.resolver { - functions_to_scan.push(Entrypoints { + if let Some(resolver)= contentitem.resolver { + functions_to_scan.push(Entrypoint { function: resolver.function, invokable: true, web_trigger: false, }) } - if let Some(dynamic_properties) = contentitem.dynamic_properties { - functions_to_scan.push(Entrypoints { + if let Some(dynamic_properties)= contentitem.dynamic_properties { + functions_to_scan.push(Entrypoint { function: dynamic_properties.function, invokable: true, web_trigger: false, @@ -475,21 +508,21 @@ impl<'a> ForgeModules<'a> { } for issue in self.issue_glance { - functions_to_scan.push(Entrypoints { + functions_to_scan.push(Entrypoint { function: issue.function, invokable: true, web_trigger: false, }); - if let Some(resolver) = issue.resolver { - functions_to_scan.push(Entrypoints { + if let Some(resolver)= issue.resolver { + functions_to_scan.push(Entrypoint { function: resolver.function, invokable: true, web_trigger: false, }) } - if let Some(dynamic_properties) = issue.dynamic_properties { - functions_to_scan.push(Entrypoints { + if let Some(dynamic_properties)= issue.dynamic_properties { + functions_to_scan.push(Entrypoint { function: dynamic_properties.function, invokable: true, web_trigger: false, @@ -498,37 +531,37 @@ impl<'a> ForgeModules<'a> { } for access in self.access_import_type { - functions_to_scan.push(Entrypoints { + functions_to_scan.push(Entrypoint { function: access.function, invokable: true, web_trigger: false, }); if let Some(delete) = access.one_delete_import { - functions_to_scan.push(Entrypoints { + functions_to_scan.push(Entrypoint { function: delete.function, invokable: true, web_trigger: false, }) } - if let Some(start) = access.start_import { - functions_to_scan.push(Entrypoints { + if let Some(start)= access.start_import { + functions_to_scan.push(Entrypoint { function: start.function, invokable: true, web_trigger: false, }) } - if let Some(stop) = access.stop_import { - functions_to_scan.push(Entrypoints { + if let Some(stop)= access.stop_import { + functions_to_scan.push(Entrypoint { function: stop.function, invokable: true, web_trigger: false, }) } - if let Some(status) = access.import_status { - functions_to_scan.push(Entrypoints { + if let Some(status)= access.import_status { + functions_to_scan.push(Entrypoint { function: status.function, invokable: true, web_trigger: false, @@ -540,7 +573,7 @@ impl<'a> ForgeModules<'a> { // make alternate_functions all user-invokable functions for module in self.extra.into_values().flatten() { if let Some(mod_function) = module.function { - functions_to_scan.push(Entrypoints { + functions_to_scan.push(Entrypoint { function: mod_function, invokable: true, web_trigger: false, @@ -548,15 +581,15 @@ impl<'a> ForgeModules<'a> { } if let Some(resolver) = module.resolver { - functions_to_scan.push(Entrypoints { + functions_to_scan.push(Entrypoint { function: resolver.function, invokable: true, web_trigger: false, }); } } - - return functions_to_scan; + + functions_to_scan } } diff --git a/crates/fsrt/src/main.rs b/crates/fsrt/src/main.rs index 92969c16..2fa930d1 100644 --- a/crates/fsrt/src/main.rs +++ b/crates/fsrt/src/main.rs @@ -45,7 +45,7 @@ use forge_analyzer::{ resolver::resolve_calls, }; -use forge_loader::manifest::{ForgeManifest, FunctionRef, FunctionTy, Entrypoints}; +use forge_loader::manifest::{ForgeManifest, FunctionRef, FunctionTy, Entrypoint}; use walkdir::WalkDir; #[derive(Parser, Debug)] @@ -93,10 +93,7 @@ struct ForgeProject { sm: Arc, ctx: AppCtx, env: Environment, -<<<<<<< HEAD - funcs: Vec>, -======= - funcs: Vec>, + funcs: Vec>, opts: Opts, >>>>>>> cd2ed7b (edited main to iterate over vector of functions. TODO: edit add_funcs in main.rs and test into_analyzble_function use case in main.rs) } @@ -149,17 +146,20 @@ impl ForgeProject { } } // TODO: edit to work with new iterator that not FUNCTIONTY - fn add_funcs<'a, I: IntoIterator>>(&mut self, iter: I) { - self.funcs.extend(iter.into_iter().flat_map(|ftype| { - ftype.sequence(|(func_name, path)| { - let modid = self.ctx.modid_from_path(&path)?; - let func = self.env.module_export(modid, func_name)?; - Some((func_name.to_owned(), path, modid, func)) - }) - })); + fn add_funcs<'a, I: IntoIterator>>(&mut self, iter: I) { + self.funcs.extend(iter); } } + +// self.funcs.extend(iter.into_iter().flat_map(|ftype| { +// ftype.sequence(|(func_name, path)| { +// let modid = self.ctx.modid_from_path(&path)?; +// let func = self.env.module_export(modid, func_name)?; +// Some((func_name.to_owned(), path, modid, func)) +// }) +// })); + fn is_js_file>(path: P) -> bool { matches!( path.as_ref().extension().map(|s| s.as_bytes()), @@ -218,7 +218,7 @@ fn scan_directory(dir: PathBuf, function: Option<&str>, opts: &Args) -> Result, opts: &Args) -> Result>>>>>> cd2ed7b (edited main to iterate over vector of functions. TODO: edit add_funcs in main.rs and test into_analyzble_function use case in main.rs) + warn!("error while scanning {:?} in {path:?}: {err}", func.function); } reporter.add_vulnerabilities(checker.into_vulns()); From 02939ce4c92b998f6f52542ae3b077094366335a Mon Sep 17 00:00:00 2001 From: Alina Wang Date: Wed, 10 Jan 2024 18:10:51 -0800 Subject: [PATCH 344/517] resolving merge conflicts --- crates/forge_loader/src/manifest.rs | 214 +++++++++++++--------------- 1 file changed, 100 insertions(+), 114 deletions(-) diff --git a/crates/forge_loader/src/manifest.rs b/crates/forge_loader/src/manifest.rs index df0db8db..fca0670f 100644 --- a/crates/forge_loader/src/manifest.rs +++ b/crates/forge_loader/src/manifest.rs @@ -35,7 +35,7 @@ struct ModInfo<'a> { #[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] struct MacroMod<'a> { key: &'a str, - function: &'a str, + function: Option<&'a str>, #[serde(borrow)] resolver: Option>, #[serde(borrow)] @@ -124,11 +124,11 @@ pub struct CustomField<'a> { #[serde(flatten, borrow)] key: &'a str, // all attributes below involve function calls - value: &'a str, - search_suggestion: &'a str, - function: &'a str, - edit: &'a str, - resolver: ModInfo<'a>, + value: Option<&'a str>, + search_suggestions: &'a str, + function: Option<&'a str>, + edit: Option<&'a str>, + resolver: Option>, } #[derive(Debug, Clone, PartialEq, Eq, Deserialize)] @@ -143,12 +143,11 @@ pub struct WorkflowValidator<'a> { #[serde(flatten, borrow)] key: &'a str, function: &'a str, - resolver: ModInfo<'a> + resolver: ModInfo<'a>, } #[derive(Debug, Clone, PartialEq, Eq, Deserialize)] pub struct WorkflowPostFunction<'a> { - #[serde(flatten, borrow)] key: &'a str, function: &'a str, } @@ -274,7 +273,7 @@ pub enum FunctionTy { } // Struct used for tracking what scan a funtion requires. -#[derive(Debug, Clone, PartialEq, Eq, Deserialize)] +#[derive(Debug, Clone, PartialEq, Eq)] pub struct Entrypoint<'a> { pub function: &'a str, pub invokable: bool, @@ -318,41 +317,34 @@ impl AsRef for FunctionTy { } impl<'a> ForgeModules<'a> { - -// TODO: function returns iterator where each item is some specified type. - pub fn into_analyzable_functions ( - self, - ) -> Vec>{ + // TODO: function returns iterator where each item is some specified type. + pub fn into_analyzable_functions(self) -> Vec> { // number of webtriggers are usually low, so it's better to just sort them and reuse - self.webtriggers.iter(). - .sort_unstable_by_key(|trigger| trigger.function); + // self.webtriggers.iter(). + // .sort_unstable_by_key(|trigger| trigger.function); // Get all the Triggers and represent them as a new struct thing where "webtrigger" attribute is true // for all trigger things let mut functions_to_scan = Vec::new(); self.webtriggers.iter().for_each(|webtriggers| { - functions_to_scan.push( - Entrypoint { - function: webtriggers.function, - invokable: false, - web_trigger: true, - } - ); - + functions_to_scan.push(Entrypoint { + function: webtriggers.function, + invokable: false, + web_trigger: true, + }); }); self.event_triggers.iter().for_each(|event_triggers| { - functions_to_scan.push( - Entrypoint { - function: event_triggers.raw.function, - invokable: false, - web_trigger: true, - } - ); + functions_to_scan.push(Entrypoint { + function: event_triggers.raw.function, + invokable: false, + web_trigger: true, + }); }); - self.scheduled_triggers.iter().for_each(|schedule_triggers| { - functions_to_scan.push( - Entrypoint { + self.scheduled_triggers + .iter() + .for_each(|schedule_triggers| { + functions_to_scan.push(Entrypoint { function: schedule_triggers.raw.function, invokable: false, web_trigger: true, @@ -361,96 +353,87 @@ impl<'a> ForgeModules<'a> { // create arrays representing functions that expose user non-invokable functions self.consumers.iter().for_each(|consumers| { - functions_to_scan.push( - Entrypoint { - function: consumers.resolver.function, - invokable: true, - web_trigger: false, - } - ) + functions_to_scan.push(Entrypoint { + function: consumers.resolver.function, + invokable: true, + web_trigger: false, + }) }); self.data_provider.iter().for_each(|dataprovider| { - functions_to_scan.push( - Entrypoint { - function: dataprovider.callback.function, - invokable: true, - web_trigger: false, - } - ) + functions_to_scan.push(Entrypoint { + function: dataprovider.callback.function, + invokable: true, + web_trigger: false, + }) }); self.custom_field.iter().for_each(|customfield| { - functions_to_scan.push( - Entrypoint { - function: customfield.value, - invokable: true, - web_trigger: false, - } - ); - functions_to_scan.push( - Entrypoint { - function: customfield.search_suggestion, + if let Some(value) = customfield.value { + functions_to_scan.push(Entrypoint { + function: value, invokable: true, web_trigger: false, - } - ); - functions_to_scan.push( - Entrypoint { - function: customfield.function, + }); + } + + functions_to_scan.push(Entrypoint { + function: customfield.search_suggestions, + invokable: true, + web_trigger: false, + }); + + if let Some(func) = customfield.function { + functions_to_scan.push(Entrypoint { + function: func, invokable: true, web_trigger: false, - } - ); - functions_to_scan.push( - Entrypoint { - function: customfield.edit, + }); + } + + if let Some(edit) = customfield.edit { + functions_to_scan.push(Entrypoint { + function: edit, invokable: true, web_trigger: false, - } - ); + }); + } - functions_to_scan.push( - Entrypoint { - function: customfield.resolver.function, + if let Some(resolver) = &customfield.resolver { + functions_to_scan.push(Entrypoint { + function: resolver.function, invokable: true, web_trigger: false, - } - ); - + }); + } }); self.ui_modifications.iter().for_each(|ui| { - functions_to_scan.push( - Entrypoint { - function: ui.resolver.function, - invokable: true, - web_trigger: false, - } - ) + functions_to_scan.push(Entrypoint { + function: ui.resolver.function, + invokable: true, + web_trigger: false, + }) }); self.workflow_validator.iter().for_each(|validator| { - functions_to_scan.push( - Entrypoint { - function: validator.function, - invokable: true, - web_trigger: false, - } - ); + functions_to_scan.push(Entrypoint { + function: validator.function, + invokable: true, + web_trigger: false, + }); - functions_to_scan.push( - Entrypoint { - function: validator.resolver.function, - invokable: true, - web_trigger: false, - } - ); + functions_to_scan.push(Entrypoint { + function: validator.resolver.function, + invokable: true, + web_trigger: false, + }); }); - - self.workflow_post_function.iter().for_each(|post_function| { - functions_to_scan.push( - Entrypoint { + + self.workflow_post_function + .iter() + .for_each(|post_function| { + functions_to_scan.push(Entrypoint { function: post_function.function, invokable: true, web_trigger: false, @@ -460,7 +443,7 @@ impl<'a> ForgeModules<'a> { // get user invokable modules that have additional exposure endpoints. // ie macros has config and export fields on top of resolver fields that are functions for macros in self.macros { - if let Some(resolver)= macros.resolver { + if let Some(resolver) = macros.resolver { functions_to_scan.push(Entrypoint { function: resolver.function, invokable: true, @@ -468,14 +451,14 @@ impl<'a> ForgeModules<'a> { }) } - if let Some(config)= macros.config { + if let Some(config) = macros.config { functions_to_scan.push(Entrypoint { function: config.function, invokable: true, web_trigger: false, }) } - if let Some(export)= macros.export { + if let Some(export) = macros.export { functions_to_scan.push(Entrypoint { function: export.function, invokable: true, @@ -490,7 +473,7 @@ impl<'a> ForgeModules<'a> { invokable: true, web_trigger: false, }); - if let Some(resolver)= contentitem.resolver { + if let Some(resolver) = contentitem.resolver { functions_to_scan.push(Entrypoint { function: resolver.function, invokable: true, @@ -498,7 +481,7 @@ impl<'a> ForgeModules<'a> { }) } - if let Some(dynamic_properties)= contentitem.dynamic_properties { + if let Some(dynamic_properties) = contentitem.dynamic_properties { functions_to_scan.push(Entrypoint { function: dynamic_properties.function, invokable: true, @@ -513,7 +496,7 @@ impl<'a> ForgeModules<'a> { invokable: true, web_trigger: false, }); - if let Some(resolver)= issue.resolver { + if let Some(resolver) = issue.resolver { functions_to_scan.push(Entrypoint { function: resolver.function, invokable: true, @@ -521,7 +504,7 @@ impl<'a> ForgeModules<'a> { }) } - if let Some(dynamic_properties)= issue.dynamic_properties { + if let Some(dynamic_properties) = issue.dynamic_properties { functions_to_scan.push(Entrypoint { function: dynamic_properties.function, invokable: true, @@ -544,7 +527,7 @@ impl<'a> ForgeModules<'a> { }) } - if let Some(start)= access.start_import { + if let Some(start) = access.start_import { functions_to_scan.push(Entrypoint { function: start.function, invokable: true, @@ -552,7 +535,7 @@ impl<'a> ForgeModules<'a> { }) } - if let Some(stop)= access.stop_import { + if let Some(stop) = access.stop_import { functions_to_scan.push(Entrypoint { function: stop.function, invokable: true, @@ -560,7 +543,7 @@ impl<'a> ForgeModules<'a> { }) } - if let Some(status)= access.import_status { + if let Some(status) = access.import_status { functions_to_scan.push(Entrypoint { function: status.function, invokable: true, @@ -588,7 +571,7 @@ impl<'a> ForgeModules<'a> { }); } } - + functions_to_scan } } @@ -706,7 +689,7 @@ mod tests { assert_eq!(manifest.app.id, "my-app"); assert_eq!(manifest.modules.macros.len(), 1); assert_eq!(manifest.modules.macros[0].key, "My Macro"); - assert_eq!(manifest.modules.macros[0].function, "my-macro"); + // assert_eq!(manifest.modules.macros[0].function, "my-macro"); assert_eq!(manifest.modules.functions.len(), 1); assert_eq!( manifest.modules.functions[0], @@ -798,7 +781,10 @@ mod tests { }"#; let manifest: ForgeManifest = serde_json::from_str(json).unwrap(); assert_eq!(manifest.modules.macros.len(), 1); - assert_eq!(manifest.modules.macros[0].function, "Catch-me-if-you-can0"); + + if let Some(func) = manifest.modules.macros[0].function { + assert_eq!(func, "Catch-me-if-you-can0"); + } if let Some(func) = &manifest.modules.macros[0].resolver { assert_eq!(func.function, "Catch-me-if-you-can1"); From d2fe24873c110de7a06561f83dda0f569a860217 Mon Sep 17 00:00:00 2001 From: Alina Wang Date: Wed, 10 Jan 2024 18:10:51 -0800 Subject: [PATCH 345/517] resolving merge conflicts --- crates/forge_loader/src/manifest.rs | 129 ++++++++++++++-------------- crates/fsrt/src/main.rs | 14 ++- 2 files changed, 76 insertions(+), 67 deletions(-) diff --git a/crates/forge_loader/src/manifest.rs b/crates/forge_loader/src/manifest.rs index fca0670f..a7b5bbdb 100644 --- a/crates/forge_loader/src/manifest.rs +++ b/crates/forge_loader/src/manifest.rs @@ -1,6 +1,6 @@ use std::{ borrow::Borrow, - collections::{BTreeSet, HashSet}, + collections::HashSet, hash::Hash, path::{Path, PathBuf}, }; @@ -9,7 +9,7 @@ use crate::{forgepermissions::ForgePermissions, Error}; use forge_utils::FxHashMap; use itertools::{Either, Itertools}; use serde::Deserialize; -use serde_json::map::Entry; +use std::collections::BTreeMap; use tracing::trace; #[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] @@ -26,56 +26,44 @@ pub struct FunctionMod<'a> { providers: Option>, } -// Modified +// Abstracting away key, function, and resolver into a single struct for reuse whoo! #[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] -struct ModInfo<'a> { +struct CommonKey<'a> { + key: &'a str, function: &'a str, + resolver: Option<&'a str>, } #[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] struct MacroMod<'a> { - key: &'a str, - function: Option<&'a str>, - #[serde(borrow)] - resolver: Option>, - #[serde(borrow)] - config: Option>, - #[serde(borrow)] - export: Option>, + #[serde(flatten, borrow)] + common_keys: CommonKey<'a>, + config: Option<&'a str>, + export: Option<&'a str>, } #[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] struct ContentByLineItem<'a> { - key: &'a str, - function: &'a str, - #[serde(borrow)] - resolver: Option>, + #[serde(flatten, borrow)] + common_keys: CommonKey<'a>, #[serde(borrow)] - dynamic_properties: Option>, + dynamic_properties: Option<&'a str>, } #[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] struct IssueGlance<'a> { - key: &'a str, - function: &'a str, - #[serde(borrow)] - resolver: Option>, - #[serde(borrow)] - dynamic_properties: Option>, + #[serde(flatten, borrow)] + common_keys: CommonKey<'a>, + dynamic_properties: Option<&'a str>, } - #[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] struct AccessImportType<'a> { - key: &'a str, - function: &'a str, - #[serde(borrow)] - one_delete_import: Option>, - #[serde(borrow)] - start_import: Option>, - #[serde(borrow)] - stop_import: Option>, - #[serde(borrow)] - import_status: Option>, + #[serde(flatten, borrow)] + common_keys: CommonKey<'a>, + one_delete_import: Option<&'a str>, + start_import: Option<&'a str>, + stop_import: Option<&'a str>, + import_status: Option<&'a str>, } // WebTrigger => RawTrigger; WHY IS THIS NAMED DIFFERENTLY !? WHO CHANGED NAMES @@ -113,43 +101,38 @@ struct ScheduledTrigger<'a> { // compass DataProvider module #[derive(Debug, Clone, PartialEq, Eq, Deserialize)] pub struct DataProvider<'a> { - key: &'a str, #[serde(flatten, borrow)] - callback: ModInfo<'a>, + key: &'a str, + callback: Option<&'a str>, } // Struct for Custom field Module. Check that search suggestion gets read in correctly. #[derive(Debug, Clone, PartialEq, Eq, Deserialize)] pub struct CustomField<'a> { - #[serde(flatten, borrow)] - key: &'a str, // all attributes below involve function calls + #[serde(flatten, borrow)] + common_keys: CommonKey<'a>, value: Option<&'a str>, search_suggestions: &'a str, - function: Option<&'a str>, edit: Option<&'a str>, - resolver: Option>, } #[derive(Debug, Clone, PartialEq, Eq, Deserialize)] pub struct UiModificatons<'a> { - key: &'a str, #[serde(flatten, borrow)] - resolver: ModInfo<'a>, + common_key: CommonKey<'a>, } #[derive(Debug, Clone, PartialEq, Eq, Deserialize)] pub struct WorkflowValidator<'a> { #[serde(flatten, borrow)] - key: &'a str, - function: &'a str, - resolver: ModInfo<'a>, + common_keys: CommonKey<'a>, } #[derive(Debug, Clone, PartialEq, Eq, Deserialize)] pub struct WorkflowPostFunction<'a> { - key: &'a str, - function: &'a str, + #[serde(flatten, borrow)] + common_keys: CommonKey<'a>, } // Add more structs here for deserializing forge modules @@ -318,37 +301,51 @@ impl AsRef for FunctionTy { impl<'a> ForgeModules<'a> { // TODO: function returns iterator where each item is some specified type. - pub fn into_analyzable_functions(self) -> Vec> { + pub fn into_analyzable_functions(&mut self) -> Vec> { // number of webtriggers are usually low, so it's better to just sort them and reuse - // self.webtriggers.iter(). - // .sort_unstable_by_key(|trigger| trigger.function); + self.webtriggers + .sort_unstable_by_key(|trigger| trigger.function); // Get all the Triggers and represent them as a new struct thing where "webtrigger" attribute is true // for all trigger things - let mut functions_to_scan = Vec::new(); + let mut functions_to_scan = BTreeMap::new(); + + // Get all functions for module from manifest.yml + self.functions.iter().for_each(|func| { + functions_to_scan.insert( + func.handler, + Entrypoint { + function: func.handler, + invokable: false, + web_trigger: false, + }, + ); + }); + self.webtriggers.iter().for_each(|webtriggers| { - functions_to_scan.push(Entrypoint { - function: webtriggers.function, - invokable: false, - web_trigger: true, - }); + if functions_to_scan.contains_key(webtriggers.function) { + if let Some(entry) = functions_to_scan.get_mut(webtriggers.function) { + entry.web_trigger = true; + } + } }); + self.event_triggers.iter().for_each(|event_triggers| { - functions_to_scan.push(Entrypoint { - function: event_triggers.raw.function, - invokable: false, - web_trigger: true, - }); + if functions_to_scan.contains_key(event_triggers.raw.function) { + if let Some(entry) = functions_to_scan.get_mut(event_triggers.raw.function) { + entry.web_trigger = true; + } + } }); self.scheduled_triggers .iter() .for_each(|schedule_triggers| { - functions_to_scan.push(Entrypoint { - function: schedule_triggers.raw.function, - invokable: false, - web_trigger: true, - }) + if functions_to_scan.contains_key(schedule_triggers.raw.function) { + if let Some(entry) = functions_to_scan.get_mut(schedule_triggers.raw.function) { + entry.web_trigger = true; + } + } }); // create arrays representing functions that expose user non-invokable functions diff --git a/crates/fsrt/src/main.rs b/crates/fsrt/src/main.rs index 2fa930d1..f0e585cf 100644 --- a/crates/fsrt/src/main.rs +++ b/crates/fsrt/src/main.rs @@ -88,7 +88,19 @@ struct Args { dirs: Vec, } +<<<<<<< HEAD struct ForgeProject { +======= +#[derive(Debug, Clone, Default)] +struct Opts { + dump_cfg: bool, + dump_callgraph: bool, + appkey: Option, + out: Option, +} + +struct ForgeProject<'a> { +>>>>>>> 7e86f46 (abstracted structs to use a CommonKeys struct that holds: key, function, and resolver for less code duplication. Updated into_analayzable method to update values based on functions specified in function mod. TODO: Update rest of non trigger modules to update mapping to functions to scan from function mod) #[allow(dead_code)] sm: Arc, ctx: AppCtx, @@ -98,7 +110,7 @@ struct ForgeProject { >>>>>>> cd2ed7b (edited main to iterate over vector of functions. TODO: edit add_funcs in main.rs and test into_analyzble_function use case in main.rs) } -impl ForgeProject { +impl ForgeProject<'_> { #[instrument(skip(src, iter))] fn with_files_and_sourceroot, I: IntoIterator>( src: P, From 2ce0e9f882a271ccead33a4b94ae57570a5100fd Mon Sep 17 00:00:00 2001 From: Alina Wang Date: Wed, 10 Jan 2024 18:10:51 -0800 Subject: [PATCH 346/517] finished updating methods to check functions_to_scan and update entrypoint struct when needed. TODO: test and toggle function call in main.rs --- crates/forge_loader/src/manifest.rs | 304 +++++++++++++--------------- crates/fsrt/src/main.rs | 4 +- 2 files changed, 140 insertions(+), 168 deletions(-) diff --git a/crates/forge_loader/src/manifest.rs b/crates/forge_loader/src/manifest.rs index a7b5bbdb..a971e388 100644 --- a/crates/forge_loader/src/manifest.rs +++ b/crates/forge_loader/src/manifest.rs @@ -120,7 +120,7 @@ pub struct CustomField<'a> { #[derive(Debug, Clone, PartialEq, Eq, Deserialize)] pub struct UiModificatons<'a> { #[serde(flatten, borrow)] - common_key: CommonKey<'a>, + common_keys: CommonKey<'a>, } #[derive(Debug, Clone, PartialEq, Eq, Deserialize)] @@ -301,7 +301,7 @@ impl AsRef for FunctionTy { impl<'a> ForgeModules<'a> { // TODO: function returns iterator where each item is some specified type. - pub fn into_analyzable_functions(&mut self) -> Vec> { + pub fn into_analyzable_functions(&mut self) -> BTreeMap<&'a str, Entrypoint<'a>> { // number of webtriggers are usually low, so it's better to just sort them and reuse self.webtriggers .sort_unstable_by_key(|trigger| trigger.function); @@ -314,9 +314,9 @@ impl<'a> ForgeModules<'a> { // Get all functions for module from manifest.yml self.functions.iter().for_each(|func| { functions_to_scan.insert( - func.handler, + func.key, Entrypoint { - function: func.handler, + function: func.key, invokable: false, web_trigger: false, }, @@ -349,223 +349,195 @@ impl<'a> ForgeModules<'a> { }); // create arrays representing functions that expose user non-invokable functions - self.consumers.iter().for_each(|consumers| { - functions_to_scan.push(Entrypoint { - function: consumers.resolver.function, - invokable: true, - web_trigger: false, - }) + self.consumers.iter().for_each(|consumer| { + if functions_to_scan.contains_key(consumer.resolver.function) { + if let Some(entry) = functions_to_scan.get_mut(consumer.resolver.function) { + entry.invokable = true; + } + } }); self.data_provider.iter().for_each(|dataprovider| { - functions_to_scan.push(Entrypoint { - function: dataprovider.callback.function, - invokable: true, - web_trigger: false, - }) + if let Some(call) = dataprovider.callback { + if let Some(entry) = functions_to_scan.get_mut(call) { + entry.invokable = true; + } + } }); self.custom_field.iter().for_each(|customfield| { if let Some(value) = customfield.value { - functions_to_scan.push(Entrypoint { - function: value, - invokable: true, - web_trigger: false, - }); + if let Some(entry) = functions_to_scan.get_mut(value) { + entry.invokable = true; + } } - functions_to_scan.push(Entrypoint { - function: customfield.search_suggestions, - invokable: true, - web_trigger: false, - }); + if let Some(entry) = functions_to_scan.get_mut(customfield.search_suggestions) { + entry.invokable = true; + } - if let Some(func) = customfield.function { - functions_to_scan.push(Entrypoint { - function: func, - invokable: true, - web_trigger: false, - }); + if let Some(entry) = functions_to_scan.get_mut(customfield.common_keys.function) { + entry.invokable = true; } - if let Some(edit) = customfield.edit { - functions_to_scan.push(Entrypoint { - function: edit, - invokable: true, - web_trigger: false, - }); + if let Some(resolver) = customfield.common_keys.resolver { + if let Some(entry) = functions_to_scan.get_mut(resolver) { + entry.invokable = true; + } } - if let Some(resolver) = &customfield.resolver { - functions_to_scan.push(Entrypoint { - function: resolver.function, - invokable: true, - web_trigger: false, - }); + if let Some(edit) = customfield.edit { + if let Some(entry) = functions_to_scan.get_mut(edit) { + entry.invokable = true; + } } }); self.ui_modifications.iter().for_each(|ui| { - functions_to_scan.push(Entrypoint { - function: ui.resolver.function, - invokable: true, - web_trigger: false, - }) + if let Some(entry) = functions_to_scan.get_mut(ui.common_keys.function) { + entry.invokable = true; + } + + if let Some(resolver) = ui.common_keys.resolver { + if let Some(entry) = functions_to_scan.get_mut(resolver) { + entry.invokable = true; + } + } }); self.workflow_validator.iter().for_each(|validator| { - functions_to_scan.push(Entrypoint { - function: validator.function, - invokable: true, - web_trigger: false, - }); + if let Some(entry) = functions_to_scan.get_mut(validator.common_keys.function) { + entry.invokable = true; + } - functions_to_scan.push(Entrypoint { - function: validator.resolver.function, - invokable: true, - web_trigger: false, - }); + if let Some(resolver) = validator.common_keys.resolver { + if let Some(entry) = functions_to_scan.get_mut(resolver) { + entry.invokable = true; + } + } }); self.workflow_post_function .iter() .for_each(|post_function| { - functions_to_scan.push(Entrypoint { - function: post_function.function, - invokable: true, - web_trigger: false, - }) + if let Some(entry) = functions_to_scan.get_mut(post_function.common_keys.function) { + entry.invokable = true; + } + + if let Some(resolver) = post_function.common_keys.resolver { + if let Some(entry) = functions_to_scan.get_mut(resolver) { + entry.invokable = true; + } + } }); // get user invokable modules that have additional exposure endpoints. // ie macros has config and export fields on top of resolver fields that are functions - for macros in self.macros { - if let Some(resolver) = macros.resolver { - functions_to_scan.push(Entrypoint { - function: resolver.function, - invokable: true, - web_trigger: false, - }) + self.macros.iter().for_each(|macros| { + if let Some(resolver) = macros.common_keys.resolver { + if let Some(entry) = functions_to_scan.get_mut(resolver) { + entry.invokable = true; + } } if let Some(config) = macros.config { - functions_to_scan.push(Entrypoint { - function: config.function, - invokable: true, - web_trigger: false, - }) + if let Some(entry) = functions_to_scan.get_mut(config) { + entry.invokable = true; + } } + if let Some(export) = macros.export { - functions_to_scan.push(Entrypoint { - function: export.function, - invokable: true, - web_trigger: false, - }) + if let Some(entry) = functions_to_scan.get_mut(export) { + entry.invokable = true; + } } - } + }); - for contentitem in self.content_by_line_item { - functions_to_scan.push(Entrypoint { - function: contentitem.function, - invokable: true, - web_trigger: false, - }); - if let Some(resolver) = contentitem.resolver { - functions_to_scan.push(Entrypoint { - function: resolver.function, - invokable: true, - web_trigger: false, - }) + self.content_by_line_item.iter().for_each(|contentitem| { + if let Some(entry) = functions_to_scan.get_mut(contentitem.common_keys.function) { + entry.invokable = true; + } + + if let Some(resolver) = contentitem.common_keys.resolver { + if let Some(entry) = functions_to_scan.get_mut(resolver) { + entry.invokable = true; + } } if let Some(dynamic_properties) = contentitem.dynamic_properties { - functions_to_scan.push(Entrypoint { - function: dynamic_properties.function, - invokable: true, - web_trigger: false, - }) + if let Some(entry) = functions_to_scan.get_mut(dynamic_properties) { + entry.invokable = true; + } } - } + }); - for issue in self.issue_glance { - functions_to_scan.push(Entrypoint { - function: issue.function, - invokable: true, - web_trigger: false, - }); - if let Some(resolver) = issue.resolver { - functions_to_scan.push(Entrypoint { - function: resolver.function, - invokable: true, - web_trigger: false, - }) + self.issue_glance.iter().for_each(|issue| { + if let Some(entry) = functions_to_scan.get_mut(issue.common_keys.function) { + entry.invokable = true; + } + + if let Some(resolver) = issue.common_keys.resolver { + if let Some(entry) = functions_to_scan.get_mut(resolver) { + entry.invokable = true; + } } if let Some(dynamic_properties) = issue.dynamic_properties { - functions_to_scan.push(Entrypoint { - function: dynamic_properties.function, - invokable: true, - web_trigger: false, - }) + if let Some(entry) = functions_to_scan.get_mut(dynamic_properties) { + entry.invokable = true; + } + } + }); + + self.access_import_type.iter().for_each(|access| { + if let Some(entry) = functions_to_scan.get_mut(access.common_keys.function) { + entry.invokable = true; + } + + if let Some(resolver) = access.common_keys.resolver { + if let Some(entry) = functions_to_scan.get_mut(resolver) { + entry.invokable = true; + } } - } - for access in self.access_import_type { - functions_to_scan.push(Entrypoint { - function: access.function, - invokable: true, - web_trigger: false, - }); if let Some(delete) = access.one_delete_import { - functions_to_scan.push(Entrypoint { - function: delete.function, - invokable: true, - web_trigger: false, - }) + if let Some(entry) = functions_to_scan.get_mut(delete) { + entry.invokable = true; + } } if let Some(start) = access.start_import { - functions_to_scan.push(Entrypoint { - function: start.function, - invokable: true, - web_trigger: false, - }) + if let Some(entry) = functions_to_scan.get_mut(start) { + entry.invokable = true; + } } if let Some(stop) = access.stop_import { - functions_to_scan.push(Entrypoint { - function: stop.function, - invokable: true, - web_trigger: false, - }) + if let Some(entry) = functions_to_scan.get_mut(stop) { + entry.invokable = true; + } } if let Some(status) = access.import_status { - functions_to_scan.push(Entrypoint { - function: status.function, - invokable: true, - web_trigger: false, - }) + if let Some(entry) = functions_to_scan.get_mut(status) { + entry.invokable = true; + } } - } + }); // get array for user invokable module functions // make alternate_functions all user-invokable functions - for module in self.extra.into_values().flatten() { + for module in self.extra.clone().into_values().flatten() { if let Some(mod_function) = module.function { - functions_to_scan.push(Entrypoint { - function: mod_function, - invokable: true, - web_trigger: false, - }); + if let Some(entry) = functions_to_scan.get_mut(mod_function) { + entry.invokable = true; + } } if let Some(resolver) = module.resolver { - functions_to_scan.push(Entrypoint { - function: resolver.function, - invokable: true, - web_trigger: false, - }); + if let Some(entry) = functions_to_scan.get_mut(resolver.function) { + entry.invokable = true; + } } } @@ -685,7 +657,7 @@ mod tests { assert_eq!(manifest.app.name, Some("My App")); assert_eq!(manifest.app.id, "my-app"); assert_eq!(manifest.modules.macros.len(), 1); - assert_eq!(manifest.modules.macros[0].key, "My Macro"); + assert_eq!(manifest.modules.macros[0].common_keys.key, "My Macro"); // assert_eq!(manifest.modules.macros[0].function, "my-macro"); assert_eq!(manifest.modules.functions.len(), 1); assert_eq!( @@ -778,21 +750,21 @@ mod tests { }"#; let manifest: ForgeManifest = serde_json::from_str(json).unwrap(); assert_eq!(manifest.modules.macros.len(), 1); + assert_eq!( + manifest.modules.macros[0].common_keys.function, + "Catch-me-if-you-can0" + ); - if let Some(func) = manifest.modules.macros[0].function { - assert_eq!(func, "Catch-me-if-you-can0"); - } - - if let Some(func) = &manifest.modules.macros[0].resolver { - assert_eq!(func.function, "Catch-me-if-you-can1"); + if let Some(func) = manifest.modules.macros[0].common_keys.resolver { + assert_eq!(func, "Catch-me-if-you-can1"); } - if let Some(func) = &manifest.modules.macros[0].config { - assert_eq!(func.function, "Catch-me-if-you-can2"); + if let Some(func) = manifest.modules.macros[0].config { + assert_eq!(func, "Catch-me-if-you-can2"); } - if let Some(func) = &manifest.modules.macros[0].export { - assert_eq!(func.function, "Catch-me-if-you-can3"); + if let Some(func) = manifest.modules.macros[0].export { + assert_eq!(func, "Catch-me-if-you-can3"); } } } diff --git a/crates/fsrt/src/main.rs b/crates/fsrt/src/main.rs index f0e585cf..e670cea2 100644 --- a/crates/fsrt/src/main.rs +++ b/crates/fsrt/src/main.rs @@ -5,7 +5,7 @@ use forge_permission_resolver::permissions_resolver::{ }; use miette::{IntoDiagnostic, Result}; use std::{ - collections::HashSet, + collections::{HashSet, BTreeMap, hash_map::Entry}, convert::TryFrom, fs, os::unix::prelude::OsStrExt, @@ -105,7 +105,7 @@ struct ForgeProject<'a> { sm: Arc, ctx: AppCtx, env: Environment, - funcs: Vec>, + funcs:BTreeMap<&'a str, Entrypoint<'a>>, opts: Opts, >>>>>>> cd2ed7b (edited main to iterate over vector of functions. TODO: edit add_funcs in main.rs and test into_analyzble_function use case in main.rs) } From d15caeca68946d7ed4eb063674dc099dfa293169 Mon Sep 17 00:00:00 2001 From: Alina Wang Date: Wed, 10 Jan 2024 18:10:51 -0800 Subject: [PATCH 347/517] resolving merge conflicts --- crates/forge_loader/src/manifest.rs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/crates/forge_loader/src/manifest.rs b/crates/forge_loader/src/manifest.rs index a971e388..9ff3811e 100644 --- a/crates/forge_loader/src/manifest.rs +++ b/crates/forge_loader/src/manifest.rs @@ -349,13 +349,13 @@ impl<'a> ForgeModules<'a> { }); // create arrays representing functions that expose user non-invokable functions - self.consumers.iter().for_each(|consumer| { - if functions_to_scan.contains_key(consumer.resolver.function) { - if let Some(entry) = functions_to_scan.get_mut(consumer.resolver.function) { - entry.invokable = true; - } - } - }); + // self.consumers.iter().for_each(|consumer| { + // if functions_to_scan.contains_key(consumer.resolver.function) { + // if let Some(entry) = functions_to_scan.get_mut(consumer.resolver.function) { + // entry.invokable = true; + // } + // } + // }); self.data_provider.iter().for_each(|dataprovider| { if let Some(call) = dataprovider.callback { From 2a1ae05dda62f1782979f33957580c25f6b85555 Mon Sep 17 00:00:00 2001 From: Alina Wang Date: Wed, 10 Jan 2024 18:10:51 -0800 Subject: [PATCH 348/517] updated search_suggestion in customfield to be an optional value. --- crates/forge_loader/src/manifest.rs | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/crates/forge_loader/src/manifest.rs b/crates/forge_loader/src/manifest.rs index 9ff3811e..f5a2d52b 100644 --- a/crates/forge_loader/src/manifest.rs +++ b/crates/forge_loader/src/manifest.rs @@ -3,6 +3,7 @@ use std::{ collections::HashSet, hash::Hash, path::{Path, PathBuf}, + str::pattern::SearchStep, }; use crate::{forgepermissions::ForgePermissions, Error}; @@ -113,7 +114,7 @@ pub struct CustomField<'a> { #[serde(flatten, borrow)] common_keys: CommonKey<'a>, value: Option<&'a str>, - search_suggestions: &'a str, + search_suggestions: Option<&'a str>, edit: Option<&'a str>, } @@ -372,8 +373,10 @@ impl<'a> ForgeModules<'a> { } } - if let Some(entry) = functions_to_scan.get_mut(customfield.search_suggestions) { - entry.invokable = true; + if let Some(search) = customfield.search_suggestions { + if let Some(entry) = functions_to_scan.get_mut(search) { + entry.invokable = true; + } } if let Some(entry) = functions_to_scan.get_mut(customfield.common_keys.function) { From e29d5fcd7f8a7c3887440ab04af05f43da540877 Mon Sep 17 00:00:00 2001 From: Alina Wang Date: Wed, 10 Jan 2024 18:10:51 -0800 Subject: [PATCH 349/517] modified into_analyzable_functions with Josh and implementation in main.rs --- crates/forge_loader/src/manifest.rs | 310 +++++++--------------------- crates/fsrt/src/main.rs | 149 +++++++++---- 2 files changed, 189 insertions(+), 270 deletions(-) diff --git a/crates/forge_loader/src/manifest.rs b/crates/forge_loader/src/manifest.rs index f5a2d52b..8c26a3d6 100644 --- a/crates/forge_loader/src/manifest.rs +++ b/crates/forge_loader/src/manifest.rs @@ -1,16 +1,16 @@ use std::{ borrow::Borrow, - collections::HashSet, + collections::{BTreeSet, HashSet}, hash::Hash, path::{Path, PathBuf}, - str::pattern::SearchStep, + sync::Arc, }; use crate::{forgepermissions::ForgePermissions, Error}; use forge_utils::FxHashMap; use itertools::{Either, Itertools}; use serde::Deserialize; -use std::collections::BTreeMap; +use serde_json::map::Entry; use tracing::trace; #[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] @@ -258,38 +258,39 @@ pub enum FunctionTy { // Struct used for tracking what scan a funtion requires. #[derive(Debug, Clone, PartialEq, Eq)] -pub struct Entrypoint<'a> { - pub function: &'a str, +pub struct Entrypoint<'a, S = Unresolved> { + pub function: FunctionRef<'a, S>, pub invokable: bool, pub web_trigger: bool, } // Helper functions that help filter out which functions are what. -impl FunctionTy { - pub fn map(self, f: impl FnOnce(T) -> O) -> FunctionTy { - match self { - Self::Invokable(t) => FunctionTy::Invokable(f(t)), - Self::WebTrigger(t) => FunctionTy::WebTrigger(f(t)), - } - } - - #[inline] - pub fn into_inner(self) -> T { - match self { - FunctionTy::Invokable(t) | FunctionTy::WebTrigger(t) => t, - } - } - - pub fn sequence( - self, - f: impl FnOnce(T) -> I, - ) -> impl Iterator> { - match self { - Self::Invokable(t) => Either::Left(f(t).into_iter().map(FunctionTy::Invokable)), - Self::WebTrigger(t) => Either::Right(f(t).into_iter().map(FunctionTy::WebTrigger)), - } - } -} +// original code that's commented out to modify methods. Here for reference +// impl FunctionTy { +// pub fn map(self, f: impl FnOnce(T) -> O) -> FunctionTy { +// match self { +// Self::Invokable(t) => FunctionTy::Invokable(f(t)), +// Self::WebTrigger(t) => FunctionTy::WebTrigger(f(t)), +// } +// } + +// #[inline] +// pub fn into_inner(self) -> T { +// match self { +// FunctionTy::Invokable(t) | FunctionTy::WebTrigger(t) => t, +// } +// } + +// pub fn sequence( +// self, +// f: impl FnOnce(T) -> I, +// ) -> impl Iterator> { +// match self { +// Self::Invokable(t) => Either::Left(f(t).into_iter().map(FunctionTy::Invokable)), +// Self::WebTrigger(t) => Either::Right(f(t).into_iter().map(FunctionTy::WebTrigger)), +// } +// } +// } impl AsRef for FunctionTy { #[inline] @@ -302,249 +303,96 @@ impl AsRef for FunctionTy { impl<'a> ForgeModules<'a> { // TODO: function returns iterator where each item is some specified type. - pub fn into_analyzable_functions(&mut self) -> BTreeMap<&'a str, Entrypoint<'a>> { + pub fn into_analyzable_functions(mut self) -> impl Iterator> { // number of webtriggers are usually low, so it's better to just sort them and reuse self.webtriggers .sort_unstable_by_key(|trigger| trigger.function); + self.webtriggers + .sort_unstable_by_key(|trigger| trigger.function); // Get all the Triggers and represent them as a new struct thing where "webtrigger" attribute is true // for all trigger things + // Get all the Triggers and represent them as a new struct thing where "webtrigger" attribute is true + // for all trigger things - let mut functions_to_scan = BTreeMap::new(); - - // Get all functions for module from manifest.yml - self.functions.iter().for_each(|func| { - functions_to_scan.insert( - func.key, - Entrypoint { - function: func.key, - invokable: false, - web_trigger: false, - }, - ); - }); - - self.webtriggers.iter().for_each(|webtriggers| { - if functions_to_scan.contains_key(webtriggers.function) { - if let Some(entry) = functions_to_scan.get_mut(webtriggers.function) { - entry.web_trigger = true; - } - } - }); - - self.event_triggers.iter().for_each(|event_triggers| { - if functions_to_scan.contains_key(event_triggers.raw.function) { - if let Some(entry) = functions_to_scan.get_mut(event_triggers.raw.function) { - entry.web_trigger = true; - } - } - }); - self.scheduled_triggers - .iter() - .for_each(|schedule_triggers| { - if functions_to_scan.contains_key(schedule_triggers.raw.function) { - if let Some(entry) = functions_to_scan.get_mut(schedule_triggers.raw.function) { - entry.web_trigger = true; - } - } - }); - - // create arrays representing functions that expose user non-invokable functions - // self.consumers.iter().for_each(|consumer| { - // if functions_to_scan.contains_key(consumer.resolver.function) { - // if let Some(entry) = functions_to_scan.get_mut(consumer.resolver.function) { - // entry.invokable = true; - // } - // } - // }); + let mut invokable_functions = BTreeSet::new(); self.data_provider.iter().for_each(|dataprovider| { - if let Some(call) = dataprovider.callback { - if let Some(entry) = functions_to_scan.get_mut(call) { - entry.invokable = true; - } - } + invokable_functions.extend(dataprovider.callback); }); self.custom_field.iter().for_each(|customfield| { - if let Some(value) = customfield.value { - if let Some(entry) = functions_to_scan.get_mut(value) { - entry.invokable = true; - } - } - - if let Some(search) = customfield.search_suggestions { - if let Some(entry) = functions_to_scan.get_mut(search) { - entry.invokable = true; - } - } - - if let Some(entry) = functions_to_scan.get_mut(customfield.common_keys.function) { - entry.invokable = true; - } + invokable_functions.extend(customfield.value); + invokable_functions.extend(customfield.search_suggestions); + invokable_functions.extend(customfield.edit); - if let Some(resolver) = customfield.common_keys.resolver { - if let Some(entry) = functions_to_scan.get_mut(resolver) { - entry.invokable = true; - } - } - - if let Some(edit) = customfield.edit { - if let Some(entry) = functions_to_scan.get_mut(edit) { - entry.invokable = true; - } - } + invokable_functions.insert(customfield.common_keys.function); + invokable_functions.extend(customfield.common_keys.resolver); }); self.ui_modifications.iter().for_each(|ui| { - if let Some(entry) = functions_to_scan.get_mut(ui.common_keys.function) { - entry.invokable = true; - } - - if let Some(resolver) = ui.common_keys.resolver { - if let Some(entry) = functions_to_scan.get_mut(resolver) { - entry.invokable = true; - } - } + invokable_functions.insert(ui.common_keys.function); + invokable_functions.extend(ui.common_keys.resolver); }); self.workflow_validator.iter().for_each(|validator| { - if let Some(entry) = functions_to_scan.get_mut(validator.common_keys.function) { - entry.invokable = true; - } + invokable_functions.insert(validator.common_keys.key); - if let Some(resolver) = validator.common_keys.resolver { - if let Some(entry) = functions_to_scan.get_mut(resolver) { - entry.invokable = true; - } - } + invokable_functions.insert(validator.common_keys.function); + + invokable_functions.extend(validator.common_keys.resolver); }); self.workflow_post_function .iter() .for_each(|post_function| { - if let Some(entry) = functions_to_scan.get_mut(post_function.common_keys.function) { - entry.invokable = true; - } + invokable_functions.insert(post_function.common_keys.key); - if let Some(resolver) = post_function.common_keys.resolver { - if let Some(entry) = functions_to_scan.get_mut(resolver) { - entry.invokable = true; - } - } + invokable_functions.insert(post_function.common_keys.function); + + invokable_functions.extend(post_function.common_keys.resolver); }); // get user invokable modules that have additional exposure endpoints. // ie macros has config and export fields on top of resolver fields that are functions self.macros.iter().for_each(|macros| { - if let Some(resolver) = macros.common_keys.resolver { - if let Some(entry) = functions_to_scan.get_mut(resolver) { - entry.invokable = true; - } - } + invokable_functions.insert(macros.common_keys.key); - if let Some(config) = macros.config { - if let Some(entry) = functions_to_scan.get_mut(config) { - entry.invokable = true; - } - } + invokable_functions.insert(macros.common_keys.function); + invokable_functions.extend(macros.common_keys.resolver); - if let Some(export) = macros.export { - if let Some(entry) = functions_to_scan.get_mut(export) { - entry.invokable = true; - } - } - }); - - self.content_by_line_item.iter().for_each(|contentitem| { - if let Some(entry) = functions_to_scan.get_mut(contentitem.common_keys.function) { - entry.invokable = true; - } - - if let Some(resolver) = contentitem.common_keys.resolver { - if let Some(entry) = functions_to_scan.get_mut(resolver) { - entry.invokable = true; - } - } - - if let Some(dynamic_properties) = contentitem.dynamic_properties { - if let Some(entry) = functions_to_scan.get_mut(dynamic_properties) { - entry.invokable = true; - } - } + invokable_functions.extend(macros.config); + invokable_functions.extend(macros.export); }); self.issue_glance.iter().for_each(|issue| { - if let Some(entry) = functions_to_scan.get_mut(issue.common_keys.function) { - entry.invokable = true; - } - - if let Some(resolver) = issue.common_keys.resolver { - if let Some(entry) = functions_to_scan.get_mut(resolver) { - entry.invokable = true; - } - } - - if let Some(dynamic_properties) = issue.dynamic_properties { - if let Some(entry) = functions_to_scan.get_mut(dynamic_properties) { - entry.invokable = true; - } - } + invokable_functions.insert(issue.common_keys.function); + invokable_functions.extend(issue.common_keys.resolver); + invokable_functions.extend(issue.dynamic_properties); }); self.access_import_type.iter().for_each(|access| { - if let Some(entry) = functions_to_scan.get_mut(access.common_keys.function) { - entry.invokable = true; - } - - if let Some(resolver) = access.common_keys.resolver { - if let Some(entry) = functions_to_scan.get_mut(resolver) { - entry.invokable = true; - } - } - - if let Some(delete) = access.one_delete_import { - if let Some(entry) = functions_to_scan.get_mut(delete) { - entry.invokable = true; - } - } - - if let Some(start) = access.start_import { - if let Some(entry) = functions_to_scan.get_mut(start) { - entry.invokable = true; - } - } - - if let Some(stop) = access.stop_import { - if let Some(entry) = functions_to_scan.get_mut(stop) { - entry.invokable = true; - } - } + invokable_functions.insert(access.common_keys.function); + invokable_functions.extend(access.common_keys.resolver); - if let Some(status) = access.import_status { - if let Some(entry) = functions_to_scan.get_mut(status) { - entry.invokable = true; - } - } + invokable_functions.extend(access.one_delete_import); + invokable_functions.extend(access.stop_import); + invokable_functions.extend(access.start_import); + invokable_functions.extend(access.import_status); }); - // get array for user invokable module functions - // make alternate_functions all user-invokable functions - for module in self.extra.clone().into_values().flatten() { - if let Some(mod_function) = module.function { - if let Some(entry) = functions_to_scan.get_mut(mod_function) { - entry.invokable = true; - } - } - - if let Some(resolver) = module.resolver { - if let Some(entry) = functions_to_scan.get_mut(resolver.function) { - entry.invokable = true; - } - } - } - - functions_to_scan + self.functions.into_iter().flat_map(move |func| { + let web_trigger = self + .webtriggers + .binary_search_by_key(&func.key, |trigger| &trigger.function) + .is_ok(); + let invokable = invokable_functions.contains(func.key); + Ok::<_, Error>(Entrypoint { + function: FunctionRef::try_from(func)?, + invokable, + web_trigger, + }) + }) } } diff --git a/crates/fsrt/src/main.rs b/crates/fsrt/src/main.rs index e670cea2..f72da6c2 100644 --- a/crates/fsrt/src/main.rs +++ b/crates/fsrt/src/main.rs @@ -5,8 +5,7 @@ use forge_permission_resolver::permissions_resolver::{ }; use miette::{IntoDiagnostic, Result}; use std::{ - collections::{HashSet, BTreeMap, hash_map::Entry}, - convert::TryFrom, + collections::HashSet, fs, os::unix::prelude::OsStrExt, path::{Path, PathBuf}, @@ -18,8 +17,11 @@ use std::{ use clap::{Parser, ValueHint}; use miette::{IntoDiagnostic, Result}; +<<<<<<< HEAD use serde_json::map::Entry; >>>>>>> cd2ed7b (edited main to iterate over vector of functions. TODO: edit add_funcs in main.rs and test into_analyzble_function use case in main.rs) +======= +>>>>>>> 29c38d6 (modified into_analyzable_functions with Josh and implementation in main.rs) use swc_core::{ common::{Globals, Mark, SourceMap, GLOBALS}, ecma::{ @@ -45,7 +47,7 @@ use forge_analyzer::{ resolver::resolve_calls, }; -use forge_loader::manifest::{ForgeManifest, FunctionRef, FunctionTy, Entrypoint}; +use forge_loader::manifest::{Entrypoint, ForgeManifest, Resolved}; use walkdir::WalkDir; #[derive(Parser, Debug)] @@ -99,18 +101,28 @@ struct Opts { out: Option, } +#[derive(Debug, Clone)] +struct ResolvedEntryPoint<'a> { + func_name: &'a str, + path: PathBuf, + module: ModId, + def_id: DefId, + webtrigger: bool, + invokable: bool, +} + struct ForgeProject<'a> { >>>>>>> 7e86f46 (abstracted structs to use a CommonKeys struct that holds: key, function, and resolver for less code duplication. Updated into_analayzable method to update values based on functions specified in function mod. TODO: Update rest of non trigger modules to update mapping to functions to scan from function mod) #[allow(dead_code)] sm: Arc, ctx: AppCtx, env: Environment, - funcs:BTreeMap<&'a str, Entrypoint<'a>>, + funcs: Vec>, opts: Opts, >>>>>>> cd2ed7b (edited main to iterate over vector of functions. TODO: edit add_funcs in main.rs and test into_analyzble_function use case in main.rs) } -impl ForgeProject<'_> { +impl<'a> ForgeProject<'a> { #[instrument(skip(src, iter))] fn with_files_and_sourceroot, I: IntoIterator>( src: P, @@ -157,21 +169,24 @@ impl ForgeProject<'_> { funcs: vec![], } } -// TODO: edit to work with new iterator that not FUNCTIONTY - fn add_funcs<'a, I: IntoIterator>>(&mut self, iter: I) { - self.funcs.extend(iter); + // TODO: edit to work with new iterator that not FUNCTIONTY + fn add_funcs>>(&mut self, iter: I) { + self.funcs.extend(iter.into_iter().filter_map(|entrypoint| { + let (func_name, path) = entrypoint.function.into_func_path(); + let module = self.ctx.modid_from_path(&path)?; + let def_id = self.env.module_export(module, func_name)?; + Some(ResolvedEntryPoint { + func_name, + path, + module, + def_id, + invokable: entrypoint.invokable, + webtrigger: entrypoint.web_trigger, + }) + })); } } - -// self.funcs.extend(iter.into_iter().flat_map(|ftype| { -// ftype.sequence(|(func_name, path)| { -// let modid = self.ctx.modid_from_path(&path)?; -// let func = self.env.module_export(modid, func_name)?; -// Some((func_name.to_owned(), path, modid, func)) -// }) -// })); - fn is_js_file>(path: P) -> bool { matches!( path.as_ref().extension().map(|s| s.as_bytes()), @@ -191,7 +206,11 @@ fn collect_sourcefiles>(root: P) -> impl Iterator } #[tracing::instrument(level = "debug")] +<<<<<<< HEAD fn scan_directory(dir: PathBuf, function: Option<&str>, opts: &Args) -> Result { +======= +fn scan_directory(dir: PathBuf, function: Option<&str>, opts: Opts) -> Result<()> { +>>>>>>> 29c38d6 (modified into_analyzable_functions with Josh and implementation in main.rs) let mut manifest_file = dir.clone(); manifest_file.push("manifest.yaml"); if !manifest_file.exists() { @@ -230,19 +249,26 @@ fn scan_directory(dir: PathBuf, function: Option<&str>, opts: &Args) -> Result(resolved_func.into_func_path()) - // }) - // }); + let funcrefs = manifest + .modules + .into_analyzable_functions() + .flat_map(|entrypoint| { + Ok::<_, forge_loader::Error>(Entrypoint { + function: entrypoint.function.try_resolve(&paths, &dir)?, + invokable: entrypoint.invokable, + web_trigger: entrypoint.web_trigger, + }) + }); + let src_root = dir.join("src"); let mut proj = ForgeProject::with_files_and_sourceroot(src_root, paths.clone(), secret_packages); if transpiled_async { warn!("Unable to scan due to transpiled async"); +<<<<<<< HEAD +======= + return Ok(()); +>>>>>>> 29c38d6 (modified into_analyzable_functions with Josh and implementation in main.rs) } proj.add_funcs(funcrefs); resolve_calls(&mut proj.ctx); @@ -326,6 +352,7 @@ fn scan_directory(dir: PathBuf, function: Option<&str>, opts: &Args) -> Result { @@ -462,27 +489,67 @@ fn scan_directory(dir: PathBuf, function: Option<&str>, opts: &Args) -> Result { + // let mut checker = AuthZChecker::new(); + // debug!("checking {func} at {path:?}"); + // if let Err(err) = interp.run_checker(def, &mut checker, path.clone(), func.clone()) + // { + // warn!("error while scanning {func} in {path:?}: {err}"); + // } + // reporter.add_vulnerabilities(checker.into_vulns()); + // } + // FunctionTy::WebTrigger((ref func, ref path, _, def)) => { + // let mut checker = AuthenticateChecker::new(); + // debug!("checking webtrigger {func} at {path:?}"); + // if let Err(err) = + // authn_interp.run_checker(def, &mut checker, path.clone(), func.clone()) + // { + // warn!("error while scanning {func} in {path:?}: {err}"); + // } + // reporter.add_vulnerabilities(checker.into_vulns()); + // } + // } +>>>>>>> 29c38d6 (modified into_analyzable_functions with Josh and implementation in main.rs) + + // Get entrypoint value from tuple + // Logic for performing scans. + // If it's invokable, then run invokable scan. If web_trigger, then trigger scan. + // And if it's both, run both scans. if func.invokable { let mut checker = AuthZChecker::new(); - debug!("checking {:?} at {path:?}", func.function); - if let Err(err) = interp.run_checker(def, &mut checker, path.clone(), func.function.to_string()) - { - warn!("error while scanning {:?} in {path:?}: {err}", func.function); + debug!("checking {:?} at {:?}", func.func_name, &func.path); + if let Err(err) = interp.run_checker( + func.def_id, + &mut checker, + func.path.clone(), + func.func_name.to_string(), + ) { + warn!( + "error while scanning {:?} in {:?}: {err}", + func.func_name, func.path, + ); } reporter.add_vulnerabilities(checker.into_vulns()); - - } else if func.web_trigger { + } else if func.webtrigger { let mut checker = AuthenticateChecker::new(); - debug!("checking webtrigger {:?} at {path:?}", func.function); - if let Err(err) = - authn_interp.run_checker(def, &mut checker, path.clone(), func.function.to_string()) - { - warn!("error while scanning {:?} in {path:?}: {err}", func.function); + debug!( + "checking webtrigger {:?} at {:?}", + func.func_name, func.path, + ); + if let Err(err) = authn_interp.run_checker( + func.def_id, + &mut checker, + func.path.clone(), + func.func_name.to_string(), + ) { + warn!( + "error while scanning {:?} in {:?}: {err}", + func.func_name, func.path, + ); } reporter.add_vulnerabilities(checker.into_vulns()); - - } } @@ -502,8 +569,12 @@ fn scan_directory(dir: PathBuf, function: Option<&str>, opts: &Args) -> Result println!("{report}"), } +<<<<<<< HEAD Ok(proj) +======= + Ok(()) +>>>>>>> 29c38d6 (modified into_analyzable_functions with Josh and implementation in main.rs) } fn main() -> Result<()> { From 48b20eaa81f7d813e0bea60d4d6ab4f8a0d2af09 Mon Sep 17 00:00:00 2001 From: Alina Wang Date: Wed, 10 Jan 2024 18:12:12 -0800 Subject: [PATCH 350/517] added new structs for user non-invokable modules --- crates/forge_loader/src/manifest.rs | 248 ++++++++++++---------------- 1 file changed, 104 insertions(+), 144 deletions(-) diff --git a/crates/forge_loader/src/manifest.rs b/crates/forge_loader/src/manifest.rs index 8c26a3d6..a47b2338 100644 --- a/crates/forge_loader/src/manifest.rs +++ b/crates/forge_loader/src/manifest.rs @@ -99,41 +99,47 @@ struct ScheduledTrigger<'a> { interval: Interval, } -// compass DataProvider module #[derive(Debug, Clone, PartialEq, Eq, Deserialize)] -pub struct DataProvider<'a> { - #[serde(flatten, borrow)] +struct DataProvider<'a> { key: &'a str, - callback: Option<&'a str>, + #[serde(flatten, borrow)] + callback: Callback<'a>, +} + +#[derive(Debug, Clone, PartialEq, Eq, Deserialize)] +pub struct Callback<'a> { + pub function: &'a str, } -// Struct for Custom field Module. Check that search suggestion gets read in correctly. +// Struct for Custom field Module. Check that search suggestion gets read in correctly. #[derive(Debug, Clone, PartialEq, Eq, Deserialize)] -pub struct CustomField<'a> { - // all attributes below involve function calls +struct CustomField<'a> { #[serde(flatten, borrow)] - common_keys: CommonKey<'a>, - value: Option<&'a str>, - search_suggestions: Option<&'a str>, - edit: Option<&'a str>, + key: &'a str, + search_suggestion: &'a str, } + #[derive(Debug, Clone, PartialEq, Eq, Deserialize)] -pub struct UiModificatons<'a> { +struct UiModificatons<'a> { #[serde(flatten, borrow)] - common_keys: CommonKey<'a>, + key: &'a str, + resolver: Callback<'a>, } #[derive(Debug, Clone, PartialEq, Eq, Deserialize)] -pub struct WorkflowValidator<'a> { +struct WorkflowValidator<'a> { #[serde(flatten, borrow)] - common_keys: CommonKey<'a>, + key: &'a str, + functon: &'a str, + resolver: Callback<'a> } #[derive(Debug, Clone, PartialEq, Eq, Deserialize)] -pub struct WorkflowPostFunction<'a> { +struct WorkflowPostFunction<'a> { #[serde(flatten, borrow)] - common_keys: CommonKey<'a>, + key: &'a str, + functon: &'a str, } // Add more structs here for deserializing forge modules @@ -143,12 +149,6 @@ pub struct ForgeModules<'a> { macros: Vec>, #[serde(rename = "function", default, borrow)] pub functions: Vec>, - #[serde(rename = "contentByLineItem", default, borrow)] - content_by_line_item: Vec>, - #[serde(rename = "jira:issueGlance", default, borrow)] - issue_glance: Vec>, - #[serde(rename = "jira:accessImportType", default, borrow)] - access_import_type: Vec>, // deserializing non user-invocable modules #[serde(rename = "webtrigger", default, borrow)] webtriggers: Vec>, @@ -169,6 +169,17 @@ pub struct ForgeModules<'a> { #[serde(rename = "jira:workflowPostFunction", default, borrow)] pub workflow_post_function: Vec>, // deserializing user invokable module functions + #[serde(rename = "compass:dataProvider", default, borrow)] + pub data_provider: Vec>, + #[serde(rename = "jira:customField", default, borrow)] + pub custom_field: Vec>, + #[serde(rename = "jira:uiModificatons", default, borrow)] + pub ui_modifications: Vec>, + #[serde(rename = "jira:workflowValidator", default, borrow)] + pub workflow_validator: Vec>, + #[serde(rename = "jira:workflowPostFunction", default, borrow)] + pub workflow_post_function: Vec>, + // deserializing user invokable module functions #[serde(flatten)] extra: FxHashMap>>, } @@ -247,9 +258,10 @@ pub struct FunctionRef<'a, S = Unresolved> { status: S, } + // Add an extra variant to the FunctionTy enum for non user invocable functions -// Indirect: functions indirectly invoked by user :O So kewl. -// TODO: change this to struct with bools +// Indirect: functions indirectly invoked by user :O So kewl. +// TODO: change this to struct with bools #[derive(Debug, Clone, PartialEq, Eq)] pub enum FunctionTy { Invokable(T), @@ -257,22 +269,21 @@ pub enum FunctionTy { } // Struct used for tracking what scan a funtion requires. -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct Entrypoint<'a, S = Unresolved> { - pub function: FunctionRef<'a, S>, - pub invokable: bool, - pub web_trigger: bool, +#[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] +pub struct Entrypoints<'a> { + function:Vec>, + invokable: bool, + web_trigger: bool, } -// Helper functions that help filter out which functions are what. -// original code that's commented out to modify methods. Here for reference -// impl FunctionTy { -// pub fn map(self, f: impl FnOnce(T) -> O) -> FunctionTy { -// match self { -// Self::Invokable(t) => FunctionTy::Invokable(f(t)), -// Self::WebTrigger(t) => FunctionTy::WebTrigger(f(t)), -// } -// } +// Helper functions that help filter out which functions are what. +impl FunctionTy { + pub fn map(self, f: impl FnOnce(T) -> O) -> FunctionTy { + match self { + Self::Invokable(t) => FunctionTy::Invokable(f(t)), + Self::WebTrigger(t) => FunctionTy::WebTrigger(f(t)), + } + } // #[inline] // pub fn into_inner(self) -> T { @@ -301,97 +312,66 @@ impl AsRef for FunctionTy { } } + impl<'a> ForgeModules<'a> { - // TODO: function returns iterator where each item is some specified type. - pub fn into_analyzable_functions(mut self) -> impl Iterator> { + + + pub fn into_analyzable_functions( + mut self, + ) -> impl Iterator>> { // number of webtriggers are usually low, so it's better to just sort them and reuse self.webtriggers .sort_unstable_by_key(|trigger| trigger.function); - self.webtriggers - .sort_unstable_by_key(|trigger| trigger.function); - - // Get all the Triggers and represent them as a new struct thing where "webtrigger" attribute is true - // for all trigger things - // Get all the Triggers and represent them as a new struct thing where "webtrigger" attribute is true - // for all trigger things - - let mut invokable_functions = BTreeSet::new(); - - self.data_provider.iter().for_each(|dataprovider| { - invokable_functions.extend(dataprovider.callback); - }); - - self.custom_field.iter().for_each(|customfield| { - invokable_functions.extend(customfield.value); - invokable_functions.extend(customfield.search_suggestions); - invokable_functions.extend(customfield.edit); - - invokable_functions.insert(customfield.common_keys.function); - invokable_functions.extend(customfield.common_keys.resolver); - }); - - self.ui_modifications.iter().for_each(|ui| { - invokable_functions.insert(ui.common_keys.function); - invokable_functions.extend(ui.common_keys.resolver); - }); - - self.workflow_validator.iter().for_each(|validator| { - invokable_functions.insert(validator.common_keys.key); - - invokable_functions.insert(validator.common_keys.function); - - invokable_functions.extend(validator.common_keys.resolver); - }); - - self.workflow_post_function - .iter() - .for_each(|post_function| { - invokable_functions.insert(post_function.common_keys.key); - - invokable_functions.insert(post_function.common_keys.function); - - invokable_functions.extend(post_function.common_keys.resolver); - }); - - // get user invokable modules that have additional exposure endpoints. - // ie macros has config and export fields on top of resolver fields that are functions - self.macros.iter().for_each(|macros| { - invokable_functions.insert(macros.common_keys.key); - invokable_functions.insert(macros.common_keys.function); - invokable_functions.extend(macros.common_keys.resolver); - invokable_functions.extend(macros.config); - invokable_functions.extend(macros.export); - }); + let mut ignored_functions: BTreeSet<_> = self + .scheduled_triggers + .into_iter() + .map(|trigger| trigger.raw.function) + .chain( + self.event_triggers + .into_iter() + .map(|trigger| trigger.raw.function), + ) + .collect(); + + // make alternate_functions all user-invokable functions + let mut alternate_functions: Vec<&str> = Vec::new(); + for module in self.extra.into_values().flatten() { + alternate_functions.extend(module.function); + if let Some(resolver) = module.resolver { + alternate_functions.push(resolver.function); + } + } - self.issue_glance.iter().for_each(|issue| { - invokable_functions.insert(issue.common_keys.function); - invokable_functions.extend(issue.common_keys.resolver); - invokable_functions.extend(issue.dynamic_properties); + // Iterate over Consumers and check that if consumers isn't in alternate functions, add consumer funtion to be ignored + // assuming that alternate functions already has all user invokable functions. + self.consumers.iter().for_each(|consumer| { + if !alternate_functions.contains(&consumer.resolver.function) { + ignored_functions.insert(consumer.resolver.function); + } }); - self.access_import_type.iter().for_each(|access| { - invokable_functions.insert(access.common_keys.function); - invokable_functions.extend(access.common_keys.resolver); - - invokable_functions.extend(access.one_delete_import); - invokable_functions.extend(access.stop_import); - invokable_functions.extend(access.start_import); - invokable_functions.extend(access.import_status); - }); + // TODO: Iterate through all deserialized entrypoints that are represented as a struct when deserialized + // Update Struct values to be true or not. If any part true, then scan. + // This solution fixes the problem that we only check known user invokable modules and also acccounts for non-invokable module entry points - self.functions.into_iter().flat_map(move |func| { - let web_trigger = self - .webtriggers - .binary_search_by_key(&func.key, |trigger| &trigger.function) - .is_ok(); - let invokable = invokable_functions.contains(func.key); - Ok::<_, Error>(Entrypoint { - function: FunctionRef::try_from(func)?, - invokable, - web_trigger, - }) + // return non-user invokable functions + self.functions.into_iter().filter_map(move |func| { + if ignored_functions.contains(&func.key) { + return None; + } + Some( + if self + .webtriggers + .binary_search_by_key(&func.key, |trigger| trigger.function) + .is_ok() + { + FunctionTy::WebTrigger(func) + } else { + FunctionTy::Invokable(func) + }, + ) }) } } @@ -544,9 +524,10 @@ mod tests { ); } - // Modified specific deserialization schemes for modules. Checking that new schemes can deserialize function values. + // Modified specific deserialization schemes for modules. Checking that new schemes can deserialize function values. #[test] fn test_new_deserialize() { + let json = r#"{ "app": { "name": "My App", @@ -557,15 +538,11 @@ mod tests { { "key": "my-macro", "title": "My Macro", - "function": "Catch-me-if-you-can0", "resolver": { - "function": "Catch-me-if-you-can1" + "function": Catch-me-if-you-can1 }, "config": { - "function": "Catch-me-if-you-can2" - }, - "export": { - "function": "Catch-me-if-you-can3" + "function": Catch-me-if-you-can2 } } ], @@ -599,23 +576,6 @@ mod tests { } } }"#; - let manifest: ForgeManifest = serde_json::from_str(json).unwrap(); - assert_eq!(manifest.modules.macros.len(), 1); - assert_eq!( - manifest.modules.macros[0].common_keys.function, - "Catch-me-if-you-can0" - ); - if let Some(func) = manifest.modules.macros[0].common_keys.resolver { - assert_eq!(func, "Catch-me-if-you-can1"); - } - - if let Some(func) = manifest.modules.macros[0].config { - assert_eq!(func, "Catch-me-if-you-can2"); - } - - if let Some(func) = manifest.modules.macros[0].export { - assert_eq!(func, "Catch-me-if-you-can3"); - } } } From f88b71f94d06fd306a92de357951cda0b36a10db Mon Sep 17 00:00:00 2001 From: Alina Wang Date: Wed, 10 Jan 2024 18:13:11 -0800 Subject: [PATCH 351/517] added more todo statements and cleaned up struct definitions --- crates/forge_loader/src/manifest.rs | 52 +++++++++-------------------- crates/fsrt/src/main.rs | 2 -- 2 files changed, 15 insertions(+), 39 deletions(-) diff --git a/crates/forge_loader/src/manifest.rs b/crates/forge_loader/src/manifest.rs index a47b2338..7250233e 100644 --- a/crates/forge_loader/src/manifest.rs +++ b/crates/forge_loader/src/manifest.rs @@ -18,7 +18,7 @@ struct AuthProviders<'a> { #[serde(borrow)] auth: Vec<&'a str>, } -// Maps the Functions Module in common Modules +// Maps the Functions Module in common Modules #[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] pub struct FunctionMod<'a> { key: &'a str, @@ -27,54 +27,30 @@ pub struct FunctionMod<'a> { providers: Option>, } -// Abstracting away key, function, and resolver into a single struct for reuse whoo! +// Modified #[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] -struct CommonKey<'a> { - key: &'a str, +struct ModInfo<'a> { function: &'a str, - resolver: Option<&'a str>, -} - -#[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] -struct MacroMod<'a> { - #[serde(flatten, borrow)] - common_keys: CommonKey<'a>, - config: Option<&'a str>, - export: Option<&'a str>, -} - -#[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] -struct ContentByLineItem<'a> { - #[serde(flatten, borrow)] - common_keys: CommonKey<'a>, - #[serde(borrow)] - dynamic_properties: Option<&'a str>, } #[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] -struct IssueGlance<'a> { - #[serde(flatten, borrow)] - common_keys: CommonKey<'a>, - dynamic_properties: Option<&'a str>, -} -#[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] -struct AccessImportType<'a> { - #[serde(flatten, borrow)] - common_keys: CommonKey<'a>, - one_delete_import: Option<&'a str>, - start_import: Option<&'a str>, - stop_import: Option<&'a str>, - import_status: Option<&'a str>, +struct MacroMod<'a> { + #[serde(flatten, borrow)] + key: &'a str, + function: &'a str, + resolver: ModInfo<'a>, + config: ModInfo<'a>, + export: ModInfo<'a>, } -// WebTrigger => RawTrigger; WHY IS THIS NAMED DIFFERENTLY !? WHO CHANGED NAMES +// WebTrigger => RawTrigger; WHY IS THIS NAMED DIFFERENTLY !? WHO CHANGED NAMES #[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] struct RawTrigger<'a> { key: &'a str, function: &'a str, } -// Trigger => EventTriger; WHY IS THIS NAMED DIFFERENTLY !? WHO CHANGED NAMES +// Trigger => EventTriger; WHY IS THIS NAMED DIFFERENTLY !? WHO CHANGED NAMES #[derive(Debug, Clone, PartialEq, Eq, Deserialize)] struct EventTrigger<'a> { #[serde(flatten, borrow)] @@ -91,7 +67,7 @@ enum Interval { Week, } -// Thank you to whomeever kept this one the same. T.T +// Thank you to whomeever kept this one the same. T.T #[derive(Debug, Clone, PartialEq, Eq, Deserialize)] struct ScheduledTrigger<'a> { #[serde(flatten, borrow)] @@ -99,6 +75,7 @@ struct ScheduledTrigger<'a> { interval: Interval, } +// compass DataProvider module #[derive(Debug, Clone, PartialEq, Eq, Deserialize)] struct DataProvider<'a> { key: &'a str, @@ -106,6 +83,7 @@ struct DataProvider<'a> { callback: Callback<'a>, } +// Struct for mapping functions defined one more level in whose value is {function: string}. Used to represent resolver types. #[derive(Debug, Clone, PartialEq, Eq, Deserialize)] pub struct Callback<'a> { pub function: &'a str, diff --git a/crates/fsrt/src/main.rs b/crates/fsrt/src/main.rs index f72da6c2..49f76f56 100644 --- a/crates/fsrt/src/main.rs +++ b/crates/fsrt/src/main.rs @@ -352,8 +352,6 @@ fn scan_directory(dir: PathBuf, function: Option<&str>, opts: Opts) -> Result<() for func in &proj.funcs { // TODO: Update operations in for loop to scan functions. // idea: iterate over each func which should be struct that tracks the function to be scanned. And performs scans according to bool. -<<<<<<< HEAD -<<<<<<< HEAD match *func { FunctionTy::Invokable((ref func, ref path, _, def)) => { let mut runner = DefintionAnalysisRunner::new(); From abf5103065a6985212024ff3ab90d8a4bfe07bf5 Mon Sep 17 00:00:00 2001 From: Alina Wang Date: Wed, 10 Jan 2024 18:13:29 -0800 Subject: [PATCH 352/517] created iterators of all known user non-invokable functions to be scanned. And updated alternate_functions to track using entrypoints struct. Todo: capture additional entrypoints for user invokable functions(like macros), write tests + debug, and change main.rs to work with new data struct --- crates/forge_loader/src/manifest.rs | 196 +++++++++++++++++++++------- 1 file changed, 150 insertions(+), 46 deletions(-) diff --git a/crates/forge_loader/src/manifest.rs b/crates/forge_loader/src/manifest.rs index 7250233e..ca4975f5 100644 --- a/crates/forge_loader/src/manifest.rs +++ b/crates/forge_loader/src/manifest.rs @@ -11,6 +11,7 @@ use forge_utils::FxHashMap; use itertools::{Either, Itertools}; use serde::Deserialize; use serde_json::map::Entry; +use serde_json::map::Entry; use tracing::trace; #[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] @@ -77,7 +78,7 @@ struct ScheduledTrigger<'a> { // compass DataProvider module #[derive(Debug, Clone, PartialEq, Eq, Deserialize)] -struct DataProvider<'a> { +pub struct DataProvider<'a> { key: &'a str, #[serde(flatten, borrow)] callback: Callback<'a>, @@ -91,7 +92,7 @@ pub struct Callback<'a> { // Struct for Custom field Module. Check that search suggestion gets read in correctly. #[derive(Debug, Clone, PartialEq, Eq, Deserialize)] -struct CustomField<'a> { +pub struct CustomField<'a> { #[serde(flatten, borrow)] key: &'a str, search_suggestion: &'a str, @@ -99,14 +100,14 @@ struct CustomField<'a> { #[derive(Debug, Clone, PartialEq, Eq, Deserialize)] -struct UiModificatons<'a> { +pub struct UiModificatons<'a> { #[serde(flatten, borrow)] key: &'a str, resolver: Callback<'a>, } #[derive(Debug, Clone, PartialEq, Eq, Deserialize)] -struct WorkflowValidator<'a> { +pub struct WorkflowValidator<'a> { #[serde(flatten, borrow)] key: &'a str, functon: &'a str, @@ -114,10 +115,10 @@ struct WorkflowValidator<'a> { } #[derive(Debug, Clone, PartialEq, Eq, Deserialize)] -struct WorkflowPostFunction<'a> { +pub struct WorkflowPostFunction<'a> { #[serde(flatten, borrow)] key: &'a str, - functon: &'a str, + function: &'a str, } // Add more structs here for deserializing forge modules @@ -247,9 +248,9 @@ pub enum FunctionTy { } // Struct used for tracking what scan a funtion requires. -#[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] +#[derive(Debug, Clone, PartialEq, Eq, Deserialize)] pub struct Entrypoints<'a> { - function:Vec>, + function: &'a str, invokable: bool, web_trigger: bool, } @@ -293,64 +294,167 @@ impl AsRef for FunctionTy { impl<'a> ForgeModules<'a> { - +// TODO: fix return type whop pub fn into_analyzable_functions( mut self, - ) -> impl Iterator>> { + ) -> impl Iterator> { // number of webtriggers are usually low, so it's better to just sort them and reuse self.webtriggers .sort_unstable_by_key(|trigger| trigger.function); + // Get all the Triggers and represent them as a new struct thing where "webtrigger" attribute is true + // for all trigger things + let web = self.webtriggers.iter().for_each(|webtriggers| { + Entrypoints { + function: webtriggers.function, + invokable: false, + web_trigger: true, + }; + }); + let event = self.event_triggers.iter().for_each(|event_triggers| { + Entrypoints { + function: event_triggers.raw.function, + invokable: false, + web_trigger: true, + }; + }); + let schedule = self.scheduled_triggers.iter().for_each(|schedule_triggers| { + Entrypoints { + function: schedule_triggers.raw.function, + invokable: false, + web_trigger: true, + }; + }); - let mut ignored_functions: BTreeSet<_> = self - .scheduled_triggers - .into_iter() - .map(|trigger| trigger.raw.function) - .chain( - self.event_triggers - .into_iter() - .map(|trigger| trigger.raw.function), - ) - .collect(); + // create arrays representing functions that expose user non-invokable functions + let consumer = self.consumers.iter().for_each(|consumers| { + Entrypoints { + function: consumers.resolver.function, + invokable: true, + web_trigger: false, + }; + }); + let data_provider = self.data_provider.iter().for_each(|dataprovider| { + Entrypoints { + function: dataprovider.callback.function, + invokable: true, + web_trigger: false, + }; + }); + + let custom_field = self.custom_field.iter().for_each(|customfield| { + Entrypoints { + function: customfield.search_suggestion, + invokable: true, + web_trigger: false, + }; + }); + + let ui_mod = self.ui_modifications.iter().for_each(|ui| { + Entrypoints { + function: ui.resolver.function, + invokable: true, + web_trigger: false, + }; + }); + + let workflow_validator = self.workflow_validator.iter().for_each(|validator| { + Entrypoints { + function: validator.resolver.function, + invokable: true, + web_trigger: false, + }; + }); + + let workflow_post = self.workflow_post_function.iter().for_each(|post_function| { + Entrypoints { + function: post_function.function, + invokable: true, + web_trigger: false, + }; + }); + + + // let user_invokable = self.extra.into_values().flatten().into_iter().for_each(|invokable| { + + // if invokable.resolver != None { + // Entrypoints { + // function: invokable.resolver, + // invokable: true, + // web_trigger: false, + // }; + + // } + // Entrypoints { + // function: invokable.function, + // invokable: true, + // web_trigger: false, + // }; + // }); + + // let mut ignored_functions: BTreeSet<_> = self + // .scheduled_triggers + // .into_iter() + // .map(|trigger| trigger.raw.function) + // .chain( + // self.event_triggers + // .into_iter() + // .map(|trigger| trigger.raw.function), + // ) + // .collect(); + + // get array for user invokable module functions // make alternate_functions all user-invokable functions - let mut alternate_functions: Vec<&str> = Vec::new(); + let mut alternate_functions = Vec::new(); for module in self.extra.into_values().flatten() { - alternate_functions.extend(module.function); + if let Some(mod_function) = module.function { + alternate_functions.push(Entrypoints { + function: mod_function, + invokable: true, + web_trigger: false + }); + } + if let Some(resolver) = module.resolver { - alternate_functions.push(resolver.function); + alternate_functions.push(Entrypoints { + function: resolver.function, + invokable: true, + web_trigger: false + }); } } + workflow_post // Iterate over Consumers and check that if consumers isn't in alternate functions, add consumer funtion to be ignored // assuming that alternate functions already has all user invokable functions. - self.consumers.iter().for_each(|consumer| { - if !alternate_functions.contains(&consumer.resolver.function) { - ignored_functions.insert(consumer.resolver.function); - } - }); + // self.consumers.iter().for_each(|consumer| { + // if !alternate_functions.contains(&consumer.resolver.function) { + // ignored_functions.insert(consumer.resolver.function); + // } + // }); // TODO: Iterate through all deserialized entrypoints that are represented as a struct when deserialized // Update Struct values to be true or not. If any part true, then scan. // This solution fixes the problem that we only check known user invokable modules and also acccounts for non-invokable module entry points // return non-user invokable functions - self.functions.into_iter().filter_map(move |func| { - if ignored_functions.contains(&func.key) { - return None; - } - Some( - if self - .webtriggers - .binary_search_by_key(&func.key, |trigger| trigger.function) - .is_ok() - { - FunctionTy::WebTrigger(func) - } else { - FunctionTy::Invokable(func) - }, - ) - }) + // self.functions.into_iter().filter_map(move |func| { + // if ignored_functions.contains(&func.key) { + // return None; + // } + // Some( + // if self + // .webtriggers + // .binary_search_by_key(&func.key, |trigger| trigger.function) + // .is_ok() + // { + // FunctionTy::WebTrigger(func) + // } else { + // FunctionTy::Invokable(func) + // }, + // ) + // }) } } @@ -466,8 +570,8 @@ mod tests { assert_eq!(manifest.app.name, Some("My App")); assert_eq!(manifest.app.id, "my-app"); assert_eq!(manifest.modules.macros.len(), 1); - assert_eq!(manifest.modules.macros[0].common_keys.key, "My Macro"); - // assert_eq!(manifest.modules.macros[0].function, "my-macro"); + assert_eq!(manifest.modules.macros[0].key, "My Macro"); + assert_eq!(manifest.modules.macros[0].function, "my-macro"); assert_eq!(manifest.modules.functions.len(), 1); assert_eq!( manifest.modules.functions[0], From 1327fcd0856eb48d9fffd131d6ec39f5ea04d585 Mon Sep 17 00:00:00 2001 From: Alina Wang Date: Wed, 10 Jan 2024 18:13:29 -0800 Subject: [PATCH 353/517] fixed all the red squigglies --- crates/forge_loader/src/manifest.rs | 149 ++++++++++++++++------------ 1 file changed, 85 insertions(+), 64 deletions(-) diff --git a/crates/forge_loader/src/manifest.rs b/crates/forge_loader/src/manifest.rs index ca4975f5..1df9da19 100644 --- a/crates/forge_loader/src/manifest.rs +++ b/crates/forge_loader/src/manifest.rs @@ -294,85 +294,106 @@ impl AsRef for FunctionTy { impl<'a> ForgeModules<'a> { -// TODO: fix return type whop +// TODO: function returns iterator where each item is some specified type. pub fn into_analyzable_functions( mut self, - ) -> impl Iterator> { + ) { // number of webtriggers are usually low, so it's better to just sort them and reuse - self.webtriggers - .sort_unstable_by_key(|trigger| trigger.function); + // self.webtriggers + // .sort_unstable_by_key(|trigger| trigger.function); // Get all the Triggers and represent them as a new struct thing where "webtrigger" attribute is true // for all trigger things - let web = self.webtriggers.iter().for_each(|webtriggers| { - Entrypoints { - function: webtriggers.function, - invokable: false, - web_trigger: true, - }; + + let mut functions_to_scan = Vec::new(); + self.webtriggers.iter().for_each(|webtriggers| { + functions_to_scan.push( + Entrypoints { + function: webtriggers.function, + invokable: false, + web_trigger: true, + } + ); + }); - let event = self.event_triggers.iter().for_each(|event_triggers| { - Entrypoints { - function: event_triggers.raw.function, - invokable: false, - web_trigger: true, - }; + self.event_triggers.iter().for_each(|event_triggers| { + functions_to_scan.push( + Entrypoints { + function: event_triggers.raw.function, + invokable: false, + web_trigger: true, + } + ); }); - let schedule = self.scheduled_triggers.iter().for_each(|schedule_triggers| { - Entrypoints { - function: schedule_triggers.raw.function, - invokable: false, - web_trigger: true, - }; + self.scheduled_triggers.iter().for_each(|schedule_triggers| { + functions_to_scan.push( + Entrypoints { + function: schedule_triggers.raw.function, + invokable: false, + web_trigger: true, + } + ) }); // create arrays representing functions that expose user non-invokable functions - let consumer = self.consumers.iter().for_each(|consumers| { - Entrypoints { - function: consumers.resolver.function, - invokable: true, - web_trigger: false, - }; + self.consumers.iter().for_each(|consumers| { + functions_to_scan.push( + Entrypoints { + function: consumers.resolver.function, + invokable: true, + web_trigger: false, + } + ) }); - let data_provider = self.data_provider.iter().for_each(|dataprovider| { - Entrypoints { - function: dataprovider.callback.function, - invokable: true, - web_trigger: false, - }; + self.data_provider.iter().for_each(|dataprovider| { + functions_to_scan.push( + Entrypoints { + function: dataprovider.callback.function, + invokable: true, + web_trigger: false, + } + ) }); - let custom_field = self.custom_field.iter().for_each(|customfield| { - Entrypoints { - function: customfield.search_suggestion, - invokable: true, - web_trigger: false, - }; + self.custom_field.iter().for_each(|customfield| { + functions_to_scan.push( + Entrypoints { + function: customfield.search_suggestion, + invokable: true, + web_trigger: false, + } + ) }); - let ui_mod = self.ui_modifications.iter().for_each(|ui| { - Entrypoints { - function: ui.resolver.function, - invokable: true, - web_trigger: false, - }; + self.ui_modifications.iter().for_each(|ui| { + functions_to_scan.push( + Entrypoints { + function: ui.resolver.function, + invokable: true, + web_trigger: false, + } + ) }); - let workflow_validator = self.workflow_validator.iter().for_each(|validator| { - Entrypoints { - function: validator.resolver.function, - invokable: true, - web_trigger: false, - }; + self.workflow_validator.iter().for_each(|validator| { + functions_to_scan.push( + Entrypoints { + function: validator.resolver.function, + invokable: true, + web_trigger: false, + } + ) }); - - let workflow_post = self.workflow_post_function.iter().for_each(|post_function| { - Entrypoints { - function: post_function.function, - invokable: true, - web_trigger: false, - }; + + self.workflow_post_function.iter().for_each(|post_function| { + functions_to_scan.push( + Entrypoints { + function: post_function.function, + invokable: true, + web_trigger: false, + } + ) }); @@ -406,10 +427,10 @@ impl<'a> ForgeModules<'a> { // get array for user invokable module functions // make alternate_functions all user-invokable functions - let mut alternate_functions = Vec::new(); + // let mut alternate_functions = Vec::new(); for module in self.extra.into_values().flatten() { if let Some(mod_function) = module.function { - alternate_functions.push(Entrypoints { + functions_to_scan.push(Entrypoints { function: mod_function, invokable: true, web_trigger: false @@ -417,15 +438,15 @@ impl<'a> ForgeModules<'a> { } if let Some(resolver) = module.resolver { - alternate_functions.push(Entrypoints { + functions_to_scan.push(Entrypoints { function: resolver.function, invokable: true, web_trigger: false }); } } - - workflow_post + functions_to_scan.into_iter(); + // alternate_functions.into_iter(); // Iterate over Consumers and check that if consumers isn't in alternate functions, add consumer funtion to be ignored // assuming that alternate functions already has all user invokable functions. // self.consumers.iter().for_each(|consumer| { From e693381472bbc97ff68847d912de4550c927aa9b Mon Sep 17 00:00:00 2001 From: Alina Wang Date: Wed, 10 Jan 2024 18:13:29 -0800 Subject: [PATCH 354/517] added user-invokable module with additional entry points for macros. Adding more modules. Writing tests next? --- crates/forge_loader/src/manifest.rs | 56 ++++++++++++++--------------- 1 file changed, 28 insertions(+), 28 deletions(-) diff --git a/crates/forge_loader/src/manifest.rs b/crates/forge_loader/src/manifest.rs index 1df9da19..bb64246a 100644 --- a/crates/forge_loader/src/manifest.rs +++ b/crates/forge_loader/src/manifest.rs @@ -296,7 +296,7 @@ impl<'a> ForgeModules<'a> { // TODO: function returns iterator where each item is some specified type. pub fn into_analyzable_functions( - mut self, + self, ) { // number of webtriggers are usually low, so it's better to just sort them and reuse // self.webtriggers @@ -396,35 +396,35 @@ impl<'a> ForgeModules<'a> { ) }); - - // let user_invokable = self.extra.into_values().flatten().into_iter().for_each(|invokable| { + // get user invokable modules that have additional exposure endpoints. + // ie macros has config and export fields on top of resolver fields that are functions + for macros in self.macros { + if let Some(resolver)= Some(macros.resolver) { + functions_to_scan.push(Entrypoints { + function: resolver.function, + invokable: true, + web_trigger: false + }) + } - // if invokable.resolver != None { - // Entrypoints { - // function: invokable.resolver, - // invokable: true, - // web_trigger: false, - // }; + if let Some(config)= Some(macros.config) { + functions_to_scan.push(Entrypoints { + function: config.function, + invokable: true, + web_trigger: false + }) + } + if let Some(export)= Some(macros.export) { + functions_to_scan.push(Entrypoints { + function: export.function, + invokable: true, + web_trigger: false + }) + } - // } - // Entrypoints { - // function: invokable.function, - // invokable: true, - // web_trigger: false, - // }; - // }); - - // let mut ignored_functions: BTreeSet<_> = self - // .scheduled_triggers - // .into_iter() - // .map(|trigger| trigger.raw.function) - // .chain( - // self.event_triggers - // .into_iter() - // .map(|trigger| trigger.raw.function), - // ) - // .collect(); + } + // get array for user invokable module functions // make alternate_functions all user-invokable functions // let mut alternate_functions = Vec::new(); @@ -443,7 +443,7 @@ impl<'a> ForgeModules<'a> { invokable: true, web_trigger: false }); - } + } } functions_to_scan.into_iter(); // alternate_functions.into_iter(); From 2b8ff7c861d49c025f1310f52176717fc6b2fd41 Mon Sep 17 00:00:00 2001 From: Alina Wang Date: Wed, 10 Jan 2024 18:13:29 -0800 Subject: [PATCH 355/517] added test to deserialize macros with additional entry points like config and export --- crates/forge_loader/src/manifest.rs | 43 +++++++++++++++++++++++------ 1 file changed, 34 insertions(+), 9 deletions(-) diff --git a/crates/forge_loader/src/manifest.rs b/crates/forge_loader/src/manifest.rs index bb64246a..0efe2cb5 100644 --- a/crates/forge_loader/src/manifest.rs +++ b/crates/forge_loader/src/manifest.rs @@ -36,12 +36,15 @@ struct ModInfo<'a> { #[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] struct MacroMod<'a> { - #[serde(flatten, borrow)] + // #[serde(flatten, borrow)] key: &'a str, function: &'a str, - resolver: ModInfo<'a>, - config: ModInfo<'a>, - export: ModInfo<'a>, + #[serde(borrow)] + resolver: Option>, + #[serde(borrow)] + config: Option>, + #[serde(borrow)] + export: Option>, } // WebTrigger => RawTrigger; WHY IS THIS NAMED DIFFERENTLY !? WHO CHANGED NAMES @@ -399,7 +402,7 @@ impl<'a> ForgeModules<'a> { // get user invokable modules that have additional exposure endpoints. // ie macros has config and export fields on top of resolver fields that are functions for macros in self.macros { - if let Some(resolver)= Some(macros.resolver) { + if let Some(resolver)= macros.resolver { functions_to_scan.push(Entrypoints { function: resolver.function, invokable: true, @@ -407,14 +410,14 @@ impl<'a> ForgeModules<'a> { }) } - if let Some(config)= Some(macros.config) { + if let Some(config)= macros.config { functions_to_scan.push(Entrypoints { function: config.function, invokable: true, web_trigger: false }) } - if let Some(export)= Some(macros.export) { + if let Some(export)= macros.export { functions_to_scan.push(Entrypoints { function: export.function, invokable: true, @@ -641,11 +644,15 @@ mod tests { { "key": "my-macro", "title": "My Macro", + "function": "Catch-me-if-you-can0", "resolver": { - "function": Catch-me-if-you-can1 + "function": "Catch-me-if-you-can1" }, "config": { - "function": Catch-me-if-you-can2 + "function": "Catch-me-if-you-can2" + }, + "export": { + "function": "Catch-me-if-you-can3" } } ], @@ -679,6 +686,24 @@ mod tests { } } }"#; + let manifest: ForgeManifest = serde_json::from_str(json).unwrap(); + assert_eq!(manifest.modules.macros.len(), 1); + assert_eq!(manifest.modules.macros[0].function, "Catch-me-if-you-can0"); + + if let Some(func) = &manifest.modules.macros[0].resolver { + assert_eq!(func.function, "Catch-me-if-you-can1"); + + } + + if let Some(func) = &manifest.modules.macros[0].config { + assert_eq!(func.function, "Catch-me-if-you-can2"); + + } + + if let Some(func) = &manifest.modules.macros[0].export { + assert_eq!(func.function, "Catch-me-if-you-can3"); + + } } } From 02509c9e9782fe3731c0def9031304dd84104050 Mon Sep 17 00:00:00 2001 From: Alina Wang Date: Wed, 10 Jan 2024 18:13:53 -0800 Subject: [PATCH 356/517] edited main to iterate over vector of functions. TODO: edit add_funcs in main.rs and test into_analyzble_function use case in main.rs --- crates/forge_loader/src/manifest.rs | 45 +----- crates/fsrt/src/main.rs | 237 ++++------------------------ 2 files changed, 38 insertions(+), 244 deletions(-) diff --git a/crates/forge_loader/src/manifest.rs b/crates/forge_loader/src/manifest.rs index 0efe2cb5..6a4dccc6 100644 --- a/crates/forge_loader/src/manifest.rs +++ b/crates/forge_loader/src/manifest.rs @@ -253,9 +253,9 @@ pub enum FunctionTy { // Struct used for tracking what scan a funtion requires. #[derive(Debug, Clone, PartialEq, Eq, Deserialize)] pub struct Entrypoints<'a> { - function: &'a str, - invokable: bool, - web_trigger: bool, + pub function: &'a str, + pub invokable: bool, + pub web_trigger: bool, } // Helper functions that help filter out which functions are what. @@ -298,9 +298,9 @@ impl AsRef for FunctionTy { impl<'a> ForgeModules<'a> { // TODO: function returns iterator where each item is some specified type. - pub fn into_analyzable_functions( + pub fn into_analyzable_functions ( self, - ) { + ) -> Vec>{ // number of webtriggers are usually low, so it's better to just sort them and reuse // self.webtriggers // .sort_unstable_by_key(|trigger| trigger.function); @@ -426,11 +426,9 @@ impl<'a> ForgeModules<'a> { } } - // get array for user invokable module functions // make alternate_functions all user-invokable functions - // let mut alternate_functions = Vec::new(); for module in self.extra.into_values().flatten() { if let Some(mod_function) = module.function { functions_to_scan.push(Entrypoints { @@ -448,37 +446,8 @@ impl<'a> ForgeModules<'a> { }); } } - functions_to_scan.into_iter(); - // alternate_functions.into_iter(); - // Iterate over Consumers and check that if consumers isn't in alternate functions, add consumer funtion to be ignored - // assuming that alternate functions already has all user invokable functions. - // self.consumers.iter().for_each(|consumer| { - // if !alternate_functions.contains(&consumer.resolver.function) { - // ignored_functions.insert(consumer.resolver.function); - // } - // }); - - // TODO: Iterate through all deserialized entrypoints that are represented as a struct when deserialized - // Update Struct values to be true or not. If any part true, then scan. - // This solution fixes the problem that we only check known user invokable modules and also acccounts for non-invokable module entry points - - // return non-user invokable functions - // self.functions.into_iter().filter_map(move |func| { - // if ignored_functions.contains(&func.key) { - // return None; - // } - // Some( - // if self - // .webtriggers - // .binary_search_by_key(&func.key, |trigger| trigger.function) - // .is_ok() - // { - // FunctionTy::WebTrigger(func) - // } else { - // FunctionTy::Invokable(func) - // }, - // ) - // }) + + return functions_to_scan; } } diff --git a/crates/fsrt/src/main.rs b/crates/fsrt/src/main.rs index 49f76f56..ab72222b 100644 --- a/crates/fsrt/src/main.rs +++ b/crates/fsrt/src/main.rs @@ -17,11 +17,7 @@ use std::{ use clap::{Parser, ValueHint}; use miette::{IntoDiagnostic, Result}; -<<<<<<< HEAD use serde_json::map::Entry; ->>>>>>> cd2ed7b (edited main to iterate over vector of functions. TODO: edit add_funcs in main.rs and test into_analyzble_function use case in main.rs) -======= ->>>>>>> 29c38d6 (modified into_analyzable_functions with Josh and implementation in main.rs) use swc_core::{ common::{Globals, Mark, SourceMap, GLOBALS}, ecma::{ @@ -47,7 +43,7 @@ use forge_analyzer::{ resolver::resolve_calls, }; -use forge_loader::manifest::{Entrypoint, ForgeManifest, Resolved}; +use forge_loader::manifest::{ForgeManifest, FunctionRef, FunctionTy, Entrypoints}; use walkdir::WalkDir; #[derive(Parser, Debug)] @@ -117,7 +113,7 @@ struct ForgeProject<'a> { sm: Arc, ctx: AppCtx, env: Environment, - funcs: Vec>, + funcs: Vec>, opts: Opts, >>>>>>> cd2ed7b (edited main to iterate over vector of functions. TODO: edit add_funcs in main.rs and test into_analyzble_function use case in main.rs) } @@ -167,21 +163,16 @@ impl<'a> ForgeProject<'a> { ctx, env, funcs: vec![], + opts: Opts::default(), } } - // TODO: edit to work with new iterator that not FUNCTIONTY - fn add_funcs>>(&mut self, iter: I) { - self.funcs.extend(iter.into_iter().filter_map(|entrypoint| { - let (func_name, path) = entrypoint.function.into_func_path(); - let module = self.ctx.modid_from_path(&path)?; - let def_id = self.env.module_export(module, func_name)?; - Some(ResolvedEntryPoint { - func_name, - path, - module, - def_id, - invokable: entrypoint.invokable, - webtrigger: entrypoint.web_trigger, +// TODO: edit to work with new iterator that not FUNCTIONTY + fn add_funcs<'a, I: IntoIterator>>(&mut self, iter: I) { + self.funcs.extend(iter.into_iter().flat_map(|ftype| { + ftype.sequence(|(func_name, path)| { + let modid = self.ctx.modid_from_path(&path)?; + let func = self.env.module_export(modid, func_name)?; + Some((func_name.to_owned(), path, modid, func)) }) })); } @@ -249,17 +240,14 @@ fn scan_directory(dir: PathBuf, function: Option<&str>, opts: Opts) -> Result<() }); let run_permission_checker = opts.check_permissions && !transpiled_async; - let funcrefs = manifest - .modules - .into_analyzable_functions() - .flat_map(|entrypoint| { - Ok::<_, forge_loader::Error>(Entrypoint { - function: entrypoint.function.try_resolve(&paths, &dir)?, - invokable: entrypoint.invokable, - web_trigger: entrypoint.web_trigger, - }) - }); - + let funcrefs = &manifest.modules.into_analyzable_functions(); + + // .flat_map(|f| { + // f.sequence(|fmod| { + // let resolved_func = FunctionRef::try_from(fmod)?.try_resolve(&paths, &dir)?; + // Ok::<_, forge_loader::Error>(resolved_func.into_func_path()) + // }) + // }); let src_root = dir.join("src"); let mut proj = ForgeProject::with_files_and_sourceroot(src_root, paths.clone(), secret_packages); @@ -352,120 +340,6 @@ fn scan_directory(dir: PathBuf, function: Option<&str>, opts: Opts) -> Result<() for func in &proj.funcs { // TODO: Update operations in for loop to scan functions. // idea: iterate over each func which should be struct that tracks the function to be scanned. And performs scans according to bool. - match *func { - FunctionTy::Invokable((ref func, ref path, _, def)) => { - let mut runner = DefintionAnalysisRunner::new(); - debug!("checking Invokable {func} at {path:?}"); - if let Err(err) = defintion_analysis_interp.run_checker( - def, - &mut runner, - path.clone(), - func.clone(), - ) { - warn!("error while getting definition analysis {func} in {path:?}: {err}"); - } - let mut checker = AuthZChecker::new(); - debug!("Authorization Scaner on Invokable FunctionTy: checking {func} at {path:?}"); - if let Err(err) = interp.run_checker(def, &mut checker, path.clone(), func.clone()) - { - warn!("error while scanning {func} in {path:?}: {err}"); - } - reporter.add_vulnerabilities(checker.into_vulns()); - - let mut checker2 = SecretChecker::new(); - secret_interp.value_manager.varid_to_value = defintion_analysis_interp.get_defs(); - secret_interp.value_manager.defid_to_value = defintion_analysis_interp - .value_manager - .defid_to_value - .clone(); - debug!("Secret Scanner on Invokable FunctionTy: checking {func} at {path:?}"); - if let Err(err) = - secret_interp.run_checker(def, &mut checker2, path.clone(), func.clone()) - { - warn!("error while scanning {func} in {path:?}: {err}"); - } - reporter.add_vulnerabilities(checker2.into_vulns()); - - debug!("Permission Scanners on Invokable FunctionTy: checking {func} at {path:?}"); - if run_permission_checker { - perm_interp.value_manager.varid_to_value = defintion_analysis_interp.get_defs(); - perm_interp.value_manager.defid_to_value = defintion_analysis_interp - .value_manager - .defid_to_value - .clone(); - let mut checker2 = PermissionChecker::new(); - if let Err(err) = - perm_interp.run_checker(def, &mut checker2, path.clone(), func.clone()) - { - warn!("error while scanning {func} in {path:?}: {err}"); - } - } - pp_interp.value_manager.varid_to_value = defintion_analysis_interp.get_defs(); - pp_interp.value_manager.defid_to_value = defintion_analysis_interp - .value_manager - .defid_to_value - .clone(); - if let Err(e) = pp_interp.run_checker( - def, - &mut PrototypePollutionChecker, - path.clone(), - func.clone(), - ) { - warn!("error while scanning {func} in {path:?}: {e}"); - } - } - FunctionTy::WebTrigger((ref func, ref path, _, def)) => { - let mut runner = DefintionAnalysisRunner::new(); - debug!("checking Web Trigger {func} at {path:?}"); - if let Err(err) = defintion_analysis_interp.run_checker( - def, - &mut runner, - path.clone(), - func.clone(), - ) { - warn!("error while getting definition analysis {func} in {path:?}: {err}"); - } - - let mut checker2 = SecretChecker::new(); - secret_interp.value_manager.varid_to_value = defintion_analysis_interp.get_defs(); - secret_interp.value_manager.defid_to_value = defintion_analysis_interp - .value_manager - .defid_to_value - .clone(); - debug!("Secret Scanner on Web Triggers: checking {func} at {path:?}"); - if let Err(err) = - secret_interp.run_checker(def, &mut checker2, path.clone(), func.clone()) - { - warn!("error while scanning {func} in {path:?}: {err}"); - } - reporter.add_vulnerabilities(checker2.into_vulns()); - - let mut checker = AuthenticateChecker::new(); - debug!("Authentication Checker on Web Triggers: checking webtrigger {func} at {path:?}"); - if let Err(err) = - authn_interp.run_checker(def, &mut checker, path.clone(), func.clone()) - { - warn!("error while scanning {func} in {path:?}: {err}"); - } - reporter.add_vulnerabilities(checker.into_vulns()); - - debug!( - "Permission Checker on Web Triggers: checking webtrigger {func} at {path:?}" - ); - if run_permission_checker { - perm_interp.value_manager.varid_to_value = defintion_analysis_interp.get_defs(); - perm_interp.value_manager.defid_to_value = defintion_analysis_interp - .value_manager - .defid_to_value - .clone(); - let mut checker2 = PermissionChecker::new(); - if let Err(err) = - perm_interp.run_checker(def, &mut checker2, path.clone(), func.clone()) - { - warn!("error while scanning {func} in {path:?}: {err}"); - } - } -======= // match *func { // FunctionTy::Invokable((ref func, ref path, _, def)) => { // let mut checker = AuthZChecker::new(); @@ -487,78 +361,29 @@ fn scan_directory(dir: PathBuf, function: Option<&str>, opts: Opts) -> Result<() // reporter.add_vulnerabilities(checker.into_vulns()); // } // } -======= - // match *func { - // FunctionTy::Invokable((ref func, ref path, _, def)) => { - // let mut checker = AuthZChecker::new(); - // debug!("checking {func} at {path:?}"); - // if let Err(err) = interp.run_checker(def, &mut checker, path.clone(), func.clone()) - // { - // warn!("error while scanning {func} in {path:?}: {err}"); - // } - // reporter.add_vulnerabilities(checker.into_vulns()); - // } - // FunctionTy::WebTrigger((ref func, ref path, _, def)) => { - // let mut checker = AuthenticateChecker::new(); - // debug!("checking webtrigger {func} at {path:?}"); - // if let Err(err) = - // authn_interp.run_checker(def, &mut checker, path.clone(), func.clone()) - // { - // warn!("error while scanning {func} in {path:?}: {err}"); - // } - // reporter.add_vulnerabilities(checker.into_vulns()); - // } - // } ->>>>>>> 29c38d6 (modified into_analyzable_functions with Josh and implementation in main.rs) - // Get entrypoint value from tuple - // Logic for performing scans. - // If it's invokable, then run invokable scan. If web_trigger, then trigger scan. - // And if it's both, run both scans. if func.invokable { let mut checker = AuthZChecker::new(); - debug!("checking {:?} at {:?}", func.func_name, &func.path); - if let Err(err) = interp.run_checker( - func.def_id, - &mut checker, - func.path.clone(), - func.func_name.to_string(), - ) { - warn!( - "error while scanning {:?} in {:?}: {err}", - func.func_name, func.path, - ); + debug!("checking {func} at {path:?}"); + if let Err(err) = interp.run_checker(def, &mut checker, path.clone(), func.clone()) + { + warn!("error while scanning {func} in {path:?}: {err}"); } reporter.add_vulnerabilities(checker.into_vulns()); - } else if func.webtrigger { + + } else if func.web_trigger { let mut checker = AuthenticateChecker::new(); - debug!( - "checking webtrigger {:?} at {:?}", - func.func_name, func.path, - ); - if let Err(err) = authn_interp.run_checker( - func.def_id, - &mut checker, - func.path.clone(), - func.func_name.to_string(), - ) { - warn!( - "error while scanning {:?} in {:?}: {err}", - func.func_name, func.path, - ); + debug!("checking webtrigger {func} at {path:?}"); + if let Err(err) = + authn_interp.run_checker(def, &mut checker, path.clone(), func.clone()) + { + warn!("error while scanning {func} in {path:?}: {err}"); } reporter.add_vulnerabilities(checker.into_vulns()); + + } } - - if run_permission_checker { - if perm_interp.permissions.len() > 0 { - reporter.add_vulnerabilities( - vec![PermissionVuln::new(perm_interp.permissions)].into_iter(), - ); - } - } - let report = serde_json::to_string(&reporter.into_report()).into_diagnostic()?; debug!("On the debug layer: Writing Report"); match &opts.out { From 2e5c288160e6a66d5afda8ca211ea02d1269a319 Mon Sep 17 00:00:00 2001 From: Alina Wang Date: Wed, 10 Jan 2024 18:13:53 -0800 Subject: [PATCH 357/517] added new modules for additional endpoints in user invokable modules --- crates/forge_loader/src/manifest.rs | 43 ++++++++++++++++++++++++++++- 1 file changed, 42 insertions(+), 1 deletion(-) diff --git a/crates/forge_loader/src/manifest.rs b/crates/forge_loader/src/manifest.rs index 6a4dccc6..b1e1fbe5 100644 --- a/crates/forge_loader/src/manifest.rs +++ b/crates/forge_loader/src/manifest.rs @@ -36,7 +36,6 @@ struct ModInfo<'a> { #[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] struct MacroMod<'a> { - // #[serde(flatten, borrow)] key: &'a str, function: &'a str, #[serde(borrow)] @@ -47,6 +46,42 @@ struct MacroMod<'a> { export: Option>, } +#[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] +struct ContentByLineItem<'a> { + key: &'a str, + function: &'a str, + #[serde(borrow)] + resolver: Option>, + #[serde(borrow)] + dynamic_properties: Option>, +} + +#[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] +struct IssueGlance<'a> { + key: &'a str, + function: &'a str, + #[serde(borrow)] + resolver: Option>, + #[serde(borrow)] + dynamic_properties: Option>, + +} + +#[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] +struct AccessImportType<'a> { + key: &'a str, + function: &'a str, + #[serde(borrow)] + one_delete_import: Option>, + #[serde(borrow)] + start_import: Option>, + #[serde(borrow)] + stop_import: Option>, + #[serde(borrow)] + import_status: Option>, + +} + // WebTrigger => RawTrigger; WHY IS THIS NAMED DIFFERENTLY !? WHO CHANGED NAMES #[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] struct RawTrigger<'a> { @@ -131,6 +166,12 @@ pub struct ForgeModules<'a> { macros: Vec>, #[serde(rename = "function", default, borrow)] pub functions: Vec>, + #[serde(rename = "contentByLineItem", default, borrow)] + content_by_line_item: Vec>, + #[serde(rename = "jira:issueGlance", default, borrow)] + issue_glance: Vec>, + #[serde(rename = "jira:accessImportType", default, borrow)] + access_import_type: Vec>, // deserializing non user-invocable modules #[serde(rename = "webtrigger", default, borrow)] webtriggers: Vec>, From 244ffe708a7a725518d1ab1600717dbb645c835c Mon Sep 17 00:00:00 2001 From: Alina Wang Date: Wed, 10 Jan 2024 18:13:53 -0800 Subject: [PATCH 358/517] added methods for adding additional user invokable endpoints to vector for scanning. --- crates/forge_loader/src/manifest.rs | 88 +++++++++++++++++++++++++++++ 1 file changed, 88 insertions(+) diff --git a/crates/forge_loader/src/manifest.rs b/crates/forge_loader/src/manifest.rs index b1e1fbe5..ba72463a 100644 --- a/crates/forge_loader/src/manifest.rs +++ b/crates/forge_loader/src/manifest.rs @@ -467,6 +467,94 @@ impl<'a> ForgeModules<'a> { } } + + for contentitem in self.content_by_line_item { + functions_to_scan.push(Entrypoints { + function: contentitem.function, + invokable: true, + web_trigger: false + }); + if let Some(resolver)= contentitem.resolver { + functions_to_scan.push(Entrypoints { + function: resolver.function, + invokable: true, + web_trigger: false + }) + } + + if let Some(dynamic_properties)= contentitem.dynamic_properties { + functions_to_scan.push(Entrypoints { + function: dynamic_properties.function, + invokable: true, + web_trigger: false + }) + } + + } + + for issue in self.issue_glance { + functions_to_scan.push(Entrypoints { + function: issue.function, + invokable: true, + web_trigger: false + }); + if let Some(resolver)= issue.resolver { + functions_to_scan.push(Entrypoints { + function: resolver.function, + invokable: true, + web_trigger: false + }) + } + + if let Some(dynamic_properties)= issue.dynamic_properties { + functions_to_scan.push(Entrypoints { + function: dynamic_properties.function, + invokable: true, + web_trigger: false + }) + } + + } + + for access in self.access_import_type { + functions_to_scan.push(Entrypoints { + function: access.function, + invokable: true, + web_trigger: false + }); + if let Some(delete) = access.one_delete_import { + functions_to_scan.push(Entrypoints { + function: delete.function, + invokable: true, + web_trigger: false + }) + } + + if let Some(start)= access.start_import { + functions_to_scan.push(Entrypoints { + function: start.function, + invokable: true, + web_trigger: false + }) + } + + if let Some(stop)= access.stop_import { + functions_to_scan.push(Entrypoints { + function: stop.function, + invokable: true, + web_trigger: false + }) + } + + if let Some(status)= access.import_status { + functions_to_scan.push(Entrypoints { + function: status.function, + invokable: true, + web_trigger: false + }) + } + + } // get array for user invokable module functions // make alternate_functions all user-invokable functions From e22c912c2363c838545f35a442d7611faf3aaf88 Mon Sep 17 00:00:00 2001 From: Alina Wang Date: Wed, 10 Jan 2024 18:13:53 -0800 Subject: [PATCH 359/517] removed callback mod and abstracted structs to use MacroMod to represent {function: str} entry points. Added missing entrypoints on jira:customField and implmented scanning for those --- crates/forge_loader/src/manifest.rs | 53 ++++++++++++++++++++++------- 1 file changed, 41 insertions(+), 12 deletions(-) diff --git a/crates/forge_loader/src/manifest.rs b/crates/forge_loader/src/manifest.rs index ba72463a..fb6d9d5b 100644 --- a/crates/forge_loader/src/manifest.rs +++ b/crates/forge_loader/src/manifest.rs @@ -119,13 +119,7 @@ struct ScheduledTrigger<'a> { pub struct DataProvider<'a> { key: &'a str, #[serde(flatten, borrow)] - callback: Callback<'a>, -} - -// Struct for mapping functions defined one more level in whose value is {function: string}. Used to represent resolver types. -#[derive(Debug, Clone, PartialEq, Eq, Deserialize)] -pub struct Callback<'a> { - pub function: &'a str, + callback: ModInfo<'a>, } // Struct for Custom field Module. Check that search suggestion gets read in correctly. @@ -133,23 +127,28 @@ pub struct Callback<'a> { pub struct CustomField<'a> { #[serde(flatten, borrow)] key: &'a str, + // all attributes below involve function calls + value: &'a str, search_suggestion: &'a str, + function: &'a str, + edit: &'a str, + resolver: ModInfo<'a> } #[derive(Debug, Clone, PartialEq, Eq, Deserialize)] pub struct UiModificatons<'a> { - #[serde(flatten, borrow)] key: &'a str, - resolver: Callback<'a>, + #[serde(flatten, borrow)] + resolver: ModInfo<'a>, } #[derive(Debug, Clone, PartialEq, Eq, Deserialize)] pub struct WorkflowValidator<'a> { - #[serde(flatten, borrow)] key: &'a str, functon: &'a str, - resolver: Callback<'a> + #[serde(flatten, borrow)] + resolver: ModInfo<'a> } #[derive(Debug, Clone, PartialEq, Eq, Deserialize)] @@ -401,13 +400,43 @@ impl<'a> ForgeModules<'a> { }); self.custom_field.iter().for_each(|customfield| { + functions_to_scan.push( + Entrypoints { + function: customfield.value, + invokable: true, + web_trigger: false, + } + ); functions_to_scan.push( Entrypoints { function: customfield.search_suggestion, invokable: true, web_trigger: false, } - ) + ); + functions_to_scan.push( + Entrypoints { + function: customfield.function, + invokable: true, + web_trigger: false, + } + ); + functions_to_scan.push( + Entrypoints { + function: customfield.edit, + invokable: true, + web_trigger: false, + } + ); + + functions_to_scan.push( + Entrypoints { + function: customfield.resolver.function, + invokable: true, + web_trigger: false, + } + ); + }); self.ui_modifications.iter().for_each(|ui| { From b21dec0c54d1d69e223a48690ee82e72044eaa8c Mon Sep 17 00:00:00 2001 From: Alina Wang Date: Wed, 10 Jan 2024 18:13:53 -0800 Subject: [PATCH 360/517] changed entrypoints to entrypoint. Working on other comments --- crates/forge_loader/src/manifest.rs | 82 ++++++++++++++++------------- crates/fsrt/src/main.rs | 37 +++++++------ 2 files changed, 65 insertions(+), 54 deletions(-) diff --git a/crates/forge_loader/src/manifest.rs b/crates/forge_loader/src/manifest.rs index fb6d9d5b..362d8075 100644 --- a/crates/forge_loader/src/manifest.rs +++ b/crates/forge_loader/src/manifest.rs @@ -145,9 +145,9 @@ pub struct UiModificatons<'a> { #[derive(Debug, Clone, PartialEq, Eq, Deserialize)] pub struct WorkflowValidator<'a> { - key: &'a str, - functon: &'a str, #[serde(flatten, borrow)] + key: &'a str, + function: &'a str, resolver: ModInfo<'a> } @@ -292,7 +292,7 @@ pub enum FunctionTy { // Struct used for tracking what scan a funtion requires. #[derive(Debug, Clone, PartialEq, Eq, Deserialize)] -pub struct Entrypoints<'a> { +pub struct Entrypoint<'a> { pub function: &'a str, pub invokable: bool, pub web_trigger: bool, @@ -340,10 +340,10 @@ impl<'a> ForgeModules<'a> { // TODO: function returns iterator where each item is some specified type. pub fn into_analyzable_functions ( self, - ) -> Vec>{ + ) -> Vec>{ // number of webtriggers are usually low, so it's better to just sort them and reuse - // self.webtriggers - // .sort_unstable_by_key(|trigger| trigger.function); + self.webtriggers.iter(). + .sort_unstable_by_key(|trigger| trigger.function); // Get all the Triggers and represent them as a new struct thing where "webtrigger" attribute is true // for all trigger things @@ -351,7 +351,7 @@ impl<'a> ForgeModules<'a> { let mut functions_to_scan = Vec::new(); self.webtriggers.iter().for_each(|webtriggers| { functions_to_scan.push( - Entrypoints { + Entrypoint { function: webtriggers.function, invokable: false, web_trigger: true, @@ -361,7 +361,7 @@ impl<'a> ForgeModules<'a> { }); self.event_triggers.iter().for_each(|event_triggers| { functions_to_scan.push( - Entrypoints { + Entrypoint { function: event_triggers.raw.function, invokable: false, web_trigger: true, @@ -370,7 +370,7 @@ impl<'a> ForgeModules<'a> { }); self.scheduled_triggers.iter().for_each(|schedule_triggers| { functions_to_scan.push( - Entrypoints { + Entrypoint { function: schedule_triggers.raw.function, invokable: false, web_trigger: true, @@ -381,7 +381,7 @@ impl<'a> ForgeModules<'a> { // create arrays representing functions that expose user non-invokable functions self.consumers.iter().for_each(|consumers| { functions_to_scan.push( - Entrypoints { + Entrypoint { function: consumers.resolver.function, invokable: true, web_trigger: false, @@ -391,7 +391,7 @@ impl<'a> ForgeModules<'a> { self.data_provider.iter().for_each(|dataprovider| { functions_to_scan.push( - Entrypoints { + Entrypoint { function: dataprovider.callback.function, invokable: true, web_trigger: false, @@ -401,28 +401,28 @@ impl<'a> ForgeModules<'a> { self.custom_field.iter().for_each(|customfield| { functions_to_scan.push( - Entrypoints { + Entrypoint { function: customfield.value, invokable: true, web_trigger: false, } ); functions_to_scan.push( - Entrypoints { + Entrypoint { function: customfield.search_suggestion, invokable: true, web_trigger: false, } ); functions_to_scan.push( - Entrypoints { + Entrypoint { function: customfield.function, invokable: true, web_trigger: false, } ); functions_to_scan.push( - Entrypoints { + Entrypoint { function: customfield.edit, invokable: true, web_trigger: false, @@ -430,7 +430,7 @@ impl<'a> ForgeModules<'a> { ); functions_to_scan.push( - Entrypoints { + Entrypoint { function: customfield.resolver.function, invokable: true, web_trigger: false, @@ -441,7 +441,7 @@ impl<'a> ForgeModules<'a> { self.ui_modifications.iter().for_each(|ui| { functions_to_scan.push( - Entrypoints { + Entrypoint { function: ui.resolver.function, invokable: true, web_trigger: false, @@ -451,17 +451,25 @@ impl<'a> ForgeModules<'a> { self.workflow_validator.iter().for_each(|validator| { functions_to_scan.push( - Entrypoints { + Entrypoint { + function: validator.function, + invokable: true, + web_trigger: false, + } + ); + + functions_to_scan.push( + Entrypoint { function: validator.resolver.function, invokable: true, web_trigger: false, } - ) + ); }); self.workflow_post_function.iter().for_each(|post_function| { functions_to_scan.push( - Entrypoints { + Entrypoint { function: post_function.function, invokable: true, web_trigger: false, @@ -473,7 +481,7 @@ impl<'a> ForgeModules<'a> { // ie macros has config and export fields on top of resolver fields that are functions for macros in self.macros { if let Some(resolver)= macros.resolver { - functions_to_scan.push(Entrypoints { + functions_to_scan.push(Entrypoint { function: resolver.function, invokable: true, web_trigger: false @@ -481,14 +489,14 @@ impl<'a> ForgeModules<'a> { } if let Some(config)= macros.config { - functions_to_scan.push(Entrypoints { + functions_to_scan.push(Entrypoint { function: config.function, invokable: true, web_trigger: false }) } if let Some(export)= macros.export { - functions_to_scan.push(Entrypoints { + functions_to_scan.push(Entrypoint { function: export.function, invokable: true, web_trigger: false @@ -498,13 +506,13 @@ impl<'a> ForgeModules<'a> { } for contentitem in self.content_by_line_item { - functions_to_scan.push(Entrypoints { + functions_to_scan.push(Entrypoint { function: contentitem.function, invokable: true, web_trigger: false }); if let Some(resolver)= contentitem.resolver { - functions_to_scan.push(Entrypoints { + functions_to_scan.push(Entrypoint { function: resolver.function, invokable: true, web_trigger: false @@ -512,7 +520,7 @@ impl<'a> ForgeModules<'a> { } if let Some(dynamic_properties)= contentitem.dynamic_properties { - functions_to_scan.push(Entrypoints { + functions_to_scan.push(Entrypoint { function: dynamic_properties.function, invokable: true, web_trigger: false @@ -522,13 +530,13 @@ impl<'a> ForgeModules<'a> { } for issue in self.issue_glance { - functions_to_scan.push(Entrypoints { + functions_to_scan.push(Entrypoint { function: issue.function, invokable: true, web_trigger: false }); if let Some(resolver)= issue.resolver { - functions_to_scan.push(Entrypoints { + functions_to_scan.push(Entrypoint { function: resolver.function, invokable: true, web_trigger: false @@ -536,7 +544,7 @@ impl<'a> ForgeModules<'a> { } if let Some(dynamic_properties)= issue.dynamic_properties { - functions_to_scan.push(Entrypoints { + functions_to_scan.push(Entrypoint { function: dynamic_properties.function, invokable: true, web_trigger: false @@ -546,13 +554,13 @@ impl<'a> ForgeModules<'a> { } for access in self.access_import_type { - functions_to_scan.push(Entrypoints { + functions_to_scan.push(Entrypoint { function: access.function, invokable: true, web_trigger: false }); if let Some(delete) = access.one_delete_import { - functions_to_scan.push(Entrypoints { + functions_to_scan.push(Entrypoint { function: delete.function, invokable: true, web_trigger: false @@ -560,7 +568,7 @@ impl<'a> ForgeModules<'a> { } if let Some(start)= access.start_import { - functions_to_scan.push(Entrypoints { + functions_to_scan.push(Entrypoint { function: start.function, invokable: true, web_trigger: false @@ -568,7 +576,7 @@ impl<'a> ForgeModules<'a> { } if let Some(stop)= access.stop_import { - functions_to_scan.push(Entrypoints { + functions_to_scan.push(Entrypoint { function: stop.function, invokable: true, web_trigger: false @@ -576,7 +584,7 @@ impl<'a> ForgeModules<'a> { } if let Some(status)= access.import_status { - functions_to_scan.push(Entrypoints { + functions_to_scan.push(Entrypoint { function: status.function, invokable: true, web_trigger: false @@ -589,7 +597,7 @@ impl<'a> ForgeModules<'a> { // make alternate_functions all user-invokable functions for module in self.extra.into_values().flatten() { if let Some(mod_function) = module.function { - functions_to_scan.push(Entrypoints { + functions_to_scan.push(Entrypoint { function: mod_function, invokable: true, web_trigger: false @@ -597,7 +605,7 @@ impl<'a> ForgeModules<'a> { } if let Some(resolver) = module.resolver { - functions_to_scan.push(Entrypoints { + functions_to_scan.push(Entrypoint { function: resolver.function, invokable: true, web_trigger: false @@ -605,7 +613,7 @@ impl<'a> ForgeModules<'a> { } } - return functions_to_scan; + functions_to_scan } } diff --git a/crates/fsrt/src/main.rs b/crates/fsrt/src/main.rs index ab72222b..0c8f0ba8 100644 --- a/crates/fsrt/src/main.rs +++ b/crates/fsrt/src/main.rs @@ -43,7 +43,7 @@ use forge_analyzer::{ resolver::resolve_calls, }; -use forge_loader::manifest::{ForgeManifest, FunctionRef, FunctionTy, Entrypoints}; +use forge_loader::manifest::{ForgeManifest, FunctionRef, FunctionTy, Entrypoint}; use walkdir::WalkDir; #[derive(Parser, Debug)] @@ -113,7 +113,7 @@ struct ForgeProject<'a> { sm: Arc, ctx: AppCtx, env: Environment, - funcs: Vec>, + funcs: Vec>, opts: Opts, >>>>>>> cd2ed7b (edited main to iterate over vector of functions. TODO: edit add_funcs in main.rs and test into_analyzble_function use case in main.rs) } @@ -167,17 +167,20 @@ impl<'a> ForgeProject<'a> { } } // TODO: edit to work with new iterator that not FUNCTIONTY - fn add_funcs<'a, I: IntoIterator>>(&mut self, iter: I) { - self.funcs.extend(iter.into_iter().flat_map(|ftype| { - ftype.sequence(|(func_name, path)| { - let modid = self.ctx.modid_from_path(&path)?; - let func = self.env.module_export(modid, func_name)?; - Some((func_name.to_owned(), path, modid, func)) - }) - })); + fn add_funcs<'a, I: IntoIterator>>(&mut self, iter: I) { + self.funcs.extend(iter); } } + +// self.funcs.extend(iter.into_iter().flat_map(|ftype| { +// ftype.sequence(|(func_name, path)| { +// let modid = self.ctx.modid_from_path(&path)?; +// let func = self.env.module_export(modid, func_name)?; +// Some((func_name.to_owned(), path, modid, func)) +// }) +// })); + fn is_js_file>(path: P) -> bool { matches!( path.as_ref().extension().map(|s| s.as_bytes()), @@ -240,7 +243,7 @@ fn scan_directory(dir: PathBuf, function: Option<&str>, opts: Opts) -> Result<() }); let run_permission_checker = opts.check_permissions && !transpiled_async; - let funcrefs = &manifest.modules.into_analyzable_functions(); + let funcrefs = manifest.modules.into_analyzable_functions(); // .flat_map(|f| { // f.sequence(|fmod| { @@ -364,20 +367,20 @@ fn scan_directory(dir: PathBuf, function: Option<&str>, opts: Opts) -> Result<() if func.invokable { let mut checker = AuthZChecker::new(); - debug!("checking {func} at {path:?}"); - if let Err(err) = interp.run_checker(def, &mut checker, path.clone(), func.clone()) + debug!("checking {:?} at {path:?}", func.function); + if let Err(err) = interp.run_checker(def, &mut checker, path.clone(), func.function.to_string()) { - warn!("error while scanning {func} in {path:?}: {err}"); + warn!("error while scanning {:?} in {path:?}: {err}", func.function); } reporter.add_vulnerabilities(checker.into_vulns()); } else if func.web_trigger { let mut checker = AuthenticateChecker::new(); - debug!("checking webtrigger {func} at {path:?}"); + debug!("checking webtrigger {:?} at {path:?}", func.function); if let Err(err) = - authn_interp.run_checker(def, &mut checker, path.clone(), func.clone()) + authn_interp.run_checker(def, &mut checker, path.clone(), func.function.to_string()) { - warn!("error while scanning {func} in {path:?}: {err}"); + warn!("error while scanning {:?} in {path:?}: {err}", func.function); } reporter.add_vulnerabilities(checker.into_vulns()); From 6c770fefb22082d7c7e0dd65487bbd3f1cc2238e Mon Sep 17 00:00:00 2001 From: Alina Wang Date: Wed, 10 Jan 2024 18:13:53 -0800 Subject: [PATCH 361/517] updated struct serde features and rust attributes. Updated customField with optional values --- crates/forge_loader/src/manifest.rs | 98 +++++++++++++++++------------ 1 file changed, 57 insertions(+), 41 deletions(-) diff --git a/crates/forge_loader/src/manifest.rs b/crates/forge_loader/src/manifest.rs index 362d8075..c0177740 100644 --- a/crates/forge_loader/src/manifest.rs +++ b/crates/forge_loader/src/manifest.rs @@ -37,7 +37,7 @@ struct ModInfo<'a> { #[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] struct MacroMod<'a> { key: &'a str, - function: &'a str, + function: Option<&'a str>, #[serde(borrow)] resolver: Option>, #[serde(borrow)] @@ -128,11 +128,11 @@ pub struct CustomField<'a> { #[serde(flatten, borrow)] key: &'a str, // all attributes below involve function calls - value: &'a str, - search_suggestion: &'a str, - function: &'a str, - edit: &'a str, - resolver: ModInfo<'a> + value: Option<&'a str>, + search_suggestions: &'a str, + function: Option<&'a str>, + edit: Option<&'a str>, + resolver: Option>, } @@ -153,7 +153,6 @@ pub struct WorkflowValidator<'a> { #[derive(Debug, Clone, PartialEq, Eq, Deserialize)] pub struct WorkflowPostFunction<'a> { - #[serde(flatten, borrow)] key: &'a str, function: &'a str, } @@ -291,7 +290,7 @@ pub enum FunctionTy { } // Struct used for tracking what scan a funtion requires. -#[derive(Debug, Clone, PartialEq, Eq, Deserialize)] +#[derive(Debug, Clone, PartialEq, Eq)] pub struct Entrypoint<'a> { pub function: &'a str, pub invokable: bool, @@ -342,8 +341,8 @@ impl<'a> ForgeModules<'a> { self, ) -> Vec>{ // number of webtriggers are usually low, so it's better to just sort them and reuse - self.webtriggers.iter(). - .sort_unstable_by_key(|trigger| trigger.function); + // self.webtriggers.iter(). + // .sort_unstable_by_key(|trigger| trigger.function); // Get all the Triggers and represent them as a new struct thing where "webtrigger" attribute is true // for all trigger things @@ -400,43 +399,55 @@ impl<'a> ForgeModules<'a> { }); self.custom_field.iter().for_each(|customfield| { - functions_to_scan.push( - Entrypoint { - function: customfield.value, - invokable: true, - web_trigger: false, - } - ); - functions_to_scan.push( - Entrypoint { - function: customfield.search_suggestion, - invokable: true, - web_trigger: false, - } - ); - functions_to_scan.push( - Entrypoint { - function: customfield.function, - invokable: true, - web_trigger: false, - } - ); - functions_to_scan.push( - Entrypoint { - function: customfield.edit, - invokable: true, - web_trigger: false, - } - ); + if let Some(value)= customfield.value { + functions_to_scan.push( + Entrypoint { + function: value, + invokable: true, + web_trigger: false, + } + ); + } + functions_to_scan.push( Entrypoint { - function: customfield.resolver.function, + function: customfield.search_suggestions, invokable: true, web_trigger: false, } ); + if let Some(func)= customfield.function { + functions_to_scan.push( + Entrypoint { + function: func, + invokable: true, + web_trigger: false, + } + ); + } + + if let Some(edit)= customfield.edit{ + functions_to_scan.push( + Entrypoint { + function: edit, + invokable: true, + web_trigger: false, + } + ); + } + + if let Some(resolver)= &customfield.resolver { + functions_to_scan.push( + Entrypoint { + function: resolver.function, + invokable: true, + web_trigger: false, + } + ); + } + }); self.ui_modifications.iter().for_each(|ui| { @@ -730,7 +741,7 @@ mod tests { assert_eq!(manifest.app.id, "my-app"); assert_eq!(manifest.modules.macros.len(), 1); assert_eq!(manifest.modules.macros[0].key, "My Macro"); - assert_eq!(manifest.modules.macros[0].function, "my-macro"); + // assert_eq!(manifest.modules.macros[0].function, "my-macro"); assert_eq!(manifest.modules.functions.len(), 1); assert_eq!( manifest.modules.functions[0], @@ -823,7 +834,12 @@ mod tests { }"#; let manifest: ForgeManifest = serde_json::from_str(json).unwrap(); assert_eq!(manifest.modules.macros.len(), 1); - assert_eq!(manifest.modules.macros[0].function, "Catch-me-if-you-can0"); + + if let Some(func) = manifest.modules.macros[0].function { + assert_eq!(func, "Catch-me-if-you-can0"); + + } + if let Some(func) = &manifest.modules.macros[0].resolver { assert_eq!(func.function, "Catch-me-if-you-can1"); From 6f70f2d1c5b7fef8f9482791662a4c6ad9b3b781 Mon Sep 17 00:00:00 2001 From: Alina Wang Date: Wed, 10 Jan 2024 18:15:11 -0800 Subject: [PATCH 362/517] abstracted structs to use a CommonKeys struct that holds: key, function, and resolver for less code duplication. Updated into_analayzable method to update values based on functions specified in function mod. TODO: Update rest of non trigger modules to update mapping to functions to scan from function mod --- crates/forge_loader/src/manifest.rs | 148 ++++++++++++++-------------- crates/fsrt/src/main.rs | 13 +-- 2 files changed, 75 insertions(+), 86 deletions(-) diff --git a/crates/forge_loader/src/manifest.rs b/crates/forge_loader/src/manifest.rs index c0177740..c36da18d 100644 --- a/crates/forge_loader/src/manifest.rs +++ b/crates/forge_loader/src/manifest.rs @@ -1,6 +1,6 @@ use std::{ borrow::Borrow, - collections::{BTreeSet, HashSet}, + collections::{HashSet}, hash::Hash, path::{Path, PathBuf}, sync::Arc, @@ -10,8 +10,7 @@ use crate::{forgepermissions::ForgePermissions, Error}; use forge_utils::FxHashMap; use itertools::{Either, Itertools}; use serde::Deserialize; -use serde_json::map::Entry; -use serde_json::map::Entry; +use std::collections::BTreeMap; use tracing::trace; #[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] @@ -28,57 +27,46 @@ pub struct FunctionMod<'a> { providers: Option>, } -// Modified +// Abstracting away key, function, and resolver into a single struct for reuse whoo! #[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] -struct ModInfo<'a> { +struct CommonKey<'a> { + key: &'a str, function: &'a str, + resolver: Option<&'a str>, + } #[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] struct MacroMod<'a> { - key: &'a str, - function: Option<&'a str>, - #[serde(borrow)] - resolver: Option>, - #[serde(borrow)] - config: Option>, - #[serde(borrow)] - export: Option>, + #[serde(flatten, borrow)] + common_keys: CommonKey<'a>, + config: Option<&'a str>, + export: Option<&'a str>, } #[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] struct ContentByLineItem<'a> { - key: &'a str, - function: &'a str, + #[serde(flatten, borrow)] + common_keys: CommonKey<'a>, #[serde(borrow)] - resolver: Option>, - #[serde(borrow)] - dynamic_properties: Option>, + dynamic_properties: Option<&'a str>, } #[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] struct IssueGlance<'a> { - key: &'a str, - function: &'a str, - #[serde(borrow)] - resolver: Option>, - #[serde(borrow)] - dynamic_properties: Option>, + #[serde(flatten, borrow)] + common_keys: CommonKey<'a>, + dynamic_properties: Option<&'a str>, } - #[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] struct AccessImportType<'a> { - key: &'a str, - function: &'a str, - #[serde(borrow)] - one_delete_import: Option>, - #[serde(borrow)] - start_import: Option>, - #[serde(borrow)] - stop_import: Option>, - #[serde(borrow)] - import_status: Option>, + #[serde(flatten, borrow)] + common_keys: CommonKey<'a>, + one_delete_import: Option<&'a str>, + start_import: Option<&'a str>, + stop_import: Option<&'a str>, + import_status: Option<&'a str>, } @@ -117,44 +105,39 @@ struct ScheduledTrigger<'a> { // compass DataProvider module #[derive(Debug, Clone, PartialEq, Eq, Deserialize)] pub struct DataProvider<'a> { - key: &'a str, #[serde(flatten, borrow)] - callback: ModInfo<'a>, + key: &'a str, + callback: Option<&'a str>, } // Struct for Custom field Module. Check that search suggestion gets read in correctly. #[derive(Debug, Clone, PartialEq, Eq, Deserialize)] -pub struct CustomField<'a> { - #[serde(flatten, borrow)] - key: &'a str, +pub struct CustomField<'a> { // all attributes below involve function calls + #[serde(flatten, borrow)] + common_keys: CommonKey<'a>, value: Option<&'a str>, search_suggestions: &'a str, - function: Option<&'a str>, edit: Option<&'a str>, - resolver: Option>, } #[derive(Debug, Clone, PartialEq, Eq, Deserialize)] pub struct UiModificatons<'a> { - key: &'a str, #[serde(flatten, borrow)] - resolver: ModInfo<'a>, + common_key: CommonKey<'a> } #[derive(Debug, Clone, PartialEq, Eq, Deserialize)] pub struct WorkflowValidator<'a> { #[serde(flatten, borrow)] - key: &'a str, - function: &'a str, - resolver: ModInfo<'a> + common_keys: CommonKey<'a> } #[derive(Debug, Clone, PartialEq, Eq, Deserialize)] pub struct WorkflowPostFunction<'a> { - key: &'a str, - function: &'a str, + #[serde(flatten, borrow)] + common_keys: CommonKey<'a> } // Add more structs here for deserializing forge modules @@ -297,6 +280,7 @@ pub struct Entrypoint<'a> { pub web_trigger: bool, } + // Helper functions that help filter out which functions are what. impl FunctionTy { pub fn map(self, f: impl FnOnce(T) -> O) -> FunctionTy { @@ -338,43 +322,59 @@ impl<'a> ForgeModules<'a> { // TODO: function returns iterator where each item is some specified type. pub fn into_analyzable_functions ( - self, + &mut self, ) -> Vec>{ // number of webtriggers are usually low, so it's better to just sort them and reuse - // self.webtriggers.iter(). - // .sort_unstable_by_key(|trigger| trigger.function); + self.webtriggers.sort_unstable_by_key(|trigger| trigger.function); // Get all the Triggers and represent them as a new struct thing where "webtrigger" attribute is true // for all trigger things - let mut functions_to_scan = Vec::new(); + let mut functions_to_scan = BTreeMap::new(); + + // Get all functions for module from manifest.yml + self.functions.iter().for_each(|func| { + functions_to_scan.insert(func.handler, Entrypoint { + function: func.handler, + invokable: false, + web_trigger: false, + }); + + }); + + self.webtriggers.iter().for_each(|webtriggers| { - functions_to_scan.push( - Entrypoint { - function: webtriggers.function, - invokable: false, - web_trigger: true, - } - ); + if functions_to_scan.contains_key(webtriggers.function) { + if let Some(entry) = functions_to_scan.get_mut(webtriggers.function) { + entry.web_trigger = true; + } + } + + }); + + self.webtriggers.iter().for_each(|webtriggers| { + if functions_to_scan.contains_key(webtriggers.function) { + if let Some(entry) = functions_to_scan.get_mut(webtriggers.function) { + entry.web_trigger = true; + } + } }); + + self.event_triggers.iter().for_each(|event_triggers| { - functions_to_scan.push( - Entrypoint { - function: event_triggers.raw.function, - invokable: false, - web_trigger: true, - } - ); + if functions_to_scan.contains_key(event_triggers.raw.function) { + if let Some(entry) = functions_to_scan.get_mut(event_triggers.raw.function) { + entry.web_trigger = true; + } + } }); self.scheduled_triggers.iter().for_each(|schedule_triggers| { - functions_to_scan.push( - Entrypoint { - function: schedule_triggers.raw.function, - invokable: false, - web_trigger: true, - } - ) + if functions_to_scan.contains_key(schedule_triggers.raw.function) { + if let Some(entry) = functions_to_scan.get_mut(schedule_triggers.raw.function) { + entry.web_trigger = true; + } + } }); // create arrays representing functions that expose user non-invokable functions diff --git a/crates/fsrt/src/main.rs b/crates/fsrt/src/main.rs index 0c8f0ba8..3935f736 100644 --- a/crates/fsrt/src/main.rs +++ b/crates/fsrt/src/main.rs @@ -97,18 +97,7 @@ struct Opts { out: Option, } -#[derive(Debug, Clone)] -struct ResolvedEntryPoint<'a> { - func_name: &'a str, - path: PathBuf, - module: ModId, - def_id: DefId, - webtrigger: bool, - invokable: bool, -} - struct ForgeProject<'a> { ->>>>>>> 7e86f46 (abstracted structs to use a CommonKeys struct that holds: key, function, and resolver for less code duplication. Updated into_analayzable method to update values based on functions specified in function mod. TODO: Update rest of non trigger modules to update mapping to functions to scan from function mod) #[allow(dead_code)] sm: Arc, ctx: AppCtx, @@ -118,7 +107,7 @@ struct ForgeProject<'a> { >>>>>>> cd2ed7b (edited main to iterate over vector of functions. TODO: edit add_funcs in main.rs and test into_analyzble_function use case in main.rs) } -impl<'a> ForgeProject<'a> { +impl ForgeProject<'_> { #[instrument(skip(src, iter))] fn with_files_and_sourceroot, I: IntoIterator>( src: P, From f40fa88934a86cd3f25ae3bcc9692c1920732fda Mon Sep 17 00:00:00 2001 From: Alina Wang Date: Wed, 10 Jan 2024 18:15:32 -0800 Subject: [PATCH 363/517] finished updating methods to check functions_to_scan and update entrypoint struct when needed. TODO: test and toggle function call in main.rs --- crates/forge_loader/src/manifest.rs | 331 ++++++++++++---------------- crates/fsrt/src/main.rs | 5 +- 2 files changed, 143 insertions(+), 193 deletions(-) diff --git a/crates/forge_loader/src/manifest.rs b/crates/forge_loader/src/manifest.rs index c36da18d..1f466340 100644 --- a/crates/forge_loader/src/manifest.rs +++ b/crates/forge_loader/src/manifest.rs @@ -125,7 +125,7 @@ pub struct CustomField<'a> { #[derive(Debug, Clone, PartialEq, Eq, Deserialize)] pub struct UiModificatons<'a> { #[serde(flatten, borrow)] - common_key: CommonKey<'a> + common_keys: CommonKey<'a> } #[derive(Debug, Clone, PartialEq, Eq, Deserialize)] @@ -323,7 +323,7 @@ impl<'a> ForgeModules<'a> { // TODO: function returns iterator where each item is some specified type. pub fn into_analyzable_functions ( &mut self, - ) -> Vec>{ + ) -> BTreeMap<&'a str, Entrypoint<'a>>{ // number of webtriggers are usually low, so it's better to just sort them and reuse self.webtriggers.sort_unstable_by_key(|trigger| trigger.function); @@ -334,8 +334,8 @@ impl<'a> ForgeModules<'a> { // Get all functions for module from manifest.yml self.functions.iter().for_each(|func| { - functions_to_scan.insert(func.handler, Entrypoint { - function: func.handler, + functions_to_scan.insert(func.key, Entrypoint { + function: func.key, invokable: false, web_trigger: false, }); @@ -378,249 +378,202 @@ impl<'a> ForgeModules<'a> { }); // create arrays representing functions that expose user non-invokable functions - self.consumers.iter().for_each(|consumers| { - functions_to_scan.push( - Entrypoint { - function: consumers.resolver.function, - invokable: true, - web_trigger: false, - } - ) + self.consumers.iter().for_each(|consumer| { + if functions_to_scan.contains_key(consumer.resolver.function) { + if let Some(entry) = functions_to_scan.get_mut(consumer.resolver.function) { + entry.invokable = true; + } + } }); self.data_provider.iter().for_each(|dataprovider| { - functions_to_scan.push( - Entrypoint { - function: dataprovider.callback.function, - invokable: true, - web_trigger: false, - } - ) + if let Some(call) = dataprovider.callback { + if let Some(entry) = functions_to_scan.get_mut(call) { + entry.invokable = true; + } + } + }); self.custom_field.iter().for_each(|customfield| { - if let Some(value)= customfield.value { - functions_to_scan.push( - Entrypoint { - function: value, - invokable: true, - web_trigger: false, - } - ); + if let Some(entry) = functions_to_scan.get_mut(value) { + entry.invokable = true; + } } - functions_to_scan.push( - Entrypoint { - function: customfield.search_suggestions, - invokable: true, - web_trigger: false, - } - ); - - if let Some(func)= customfield.function { - functions_to_scan.push( - Entrypoint { - function: func, - invokable: true, - web_trigger: false, - } - ); + if let Some(entry) = functions_to_scan.get_mut(customfield.search_suggestions) { + entry.invokable = true; + } + + if let Some(entry) = functions_to_scan.get_mut(customfield.common_keys.function) { + entry.invokable = true; + } + + if let Some(resolver) = customfield.common_keys.resolver { + if let Some(entry) = functions_to_scan.get_mut(resolver) { + entry.invokable = true; + } } - if let Some(edit)= customfield.edit{ - functions_to_scan.push( - Entrypoint { - function: edit, - invokable: true, - web_trigger: false, - } - ); - } + if let Some(edit) = customfield.edit { + if let Some(entry) = functions_to_scan.get_mut(edit) { + entry.invokable = true; + } - if let Some(resolver)= &customfield.resolver { - functions_to_scan.push( - Entrypoint { - function: resolver.function, - invokable: true, - web_trigger: false, - } - ); } }); self.ui_modifications.iter().for_each(|ui| { - functions_to_scan.push( - Entrypoint { - function: ui.resolver.function, - invokable: true, - web_trigger: false, + if let Some(entry) = functions_to_scan.get_mut(ui.common_keys.function) { + entry.invokable = true; + } + + if let Some(resolver) = ui.common_keys.resolver { + if let Some(entry) = functions_to_scan.get_mut(resolver) { + entry.invokable = true; } - ) + } }); self.workflow_validator.iter().for_each(|validator| { - functions_to_scan.push( - Entrypoint { - function: validator.function, - invokable: true, - web_trigger: false, - } - ); + if let Some(entry) = functions_to_scan.get_mut(validator.common_keys.function) { + entry.invokable = true; + } - functions_to_scan.push( - Entrypoint { - function: validator.resolver.function, - invokable: true, - web_trigger: false, + if let Some(resolver) = validator.common_keys.resolver { + if let Some(entry) = functions_to_scan.get_mut(resolver) { + entry.invokable = true; } - ); + } }); self.workflow_post_function.iter().for_each(|post_function| { - functions_to_scan.push( - Entrypoint { - function: post_function.function, - invokable: true, - web_trigger: false, + if let Some(entry) = functions_to_scan.get_mut(post_function.common_keys.function) { + entry.invokable = true; + } + + if let Some(resolver) = post_function.common_keys.resolver { + if let Some(entry) = functions_to_scan.get_mut(resolver) { + entry.invokable = true; } - ) + } }); // get user invokable modules that have additional exposure endpoints. // ie macros has config and export fields on top of resolver fields that are functions - for macros in self.macros { - if let Some(resolver)= macros.resolver { - functions_to_scan.push(Entrypoint { - function: resolver.function, - invokable: true, - web_trigger: false - }) + self.macros.iter().for_each(|macros| { + if let Some(resolver)= macros.common_keys.resolver { + if let Some(entry) = functions_to_scan.get_mut(resolver) { + entry.invokable = true; + } } if let Some(config)= macros.config { - functions_to_scan.push(Entrypoint { - function: config.function, - invokable: true, - web_trigger: false - }) + if let Some(entry) = functions_to_scan.get_mut(config) { + entry.invokable = true; + } } + if let Some(export)= macros.export { - functions_to_scan.push(Entrypoint { - function: export.function, - invokable: true, - web_trigger: false - }) + if let Some(entry) = functions_to_scan.get_mut(export) { + entry.invokable = true; + } } - } + }); - for contentitem in self.content_by_line_item { - functions_to_scan.push(Entrypoint { - function: contentitem.function, - invokable: true, - web_trigger: false - }); - if let Some(resolver)= contentitem.resolver { - functions_to_scan.push(Entrypoint { - function: resolver.function, - invokable: true, - web_trigger: false - }) + self.content_by_line_item.iter().for_each(|contentitem| { + if let Some(entry) = functions_to_scan.get_mut(contentitem.common_keys.function) { + entry.invokable = true; + } + + + if let Some(resolver)= contentitem.common_keys.resolver { + if let Some(entry) = functions_to_scan.get_mut(resolver) { + entry.invokable = true; + } } if let Some(dynamic_properties)= contentitem.dynamic_properties { - functions_to_scan.push(Entrypoint { - function: dynamic_properties.function, - invokable: true, - web_trigger: false - }) + if let Some(entry) = functions_to_scan.get_mut(dynamic_properties) { + entry.invokable = true; + } } - } + }); - for issue in self.issue_glance { - functions_to_scan.push(Entrypoint { - function: issue.function, - invokable: true, - web_trigger: false - }); - if let Some(resolver)= issue.resolver { - functions_to_scan.push(Entrypoint { - function: resolver.function, - invokable: true, - web_trigger: false - }) + self.issue_glance.iter().for_each(|issue| { + if let Some(entry) = functions_to_scan.get_mut(issue.common_keys.function) { + entry.invokable = true; + } + + + if let Some(resolver)= issue.common_keys.resolver { + if let Some(entry) = functions_to_scan.get_mut(resolver) { + entry.invokable = true; + } } if let Some(dynamic_properties)= issue.dynamic_properties { - functions_to_scan.push(Entrypoint { - function: dynamic_properties.function, - invokable: true, - web_trigger: false - }) + if let Some(entry) = functions_to_scan.get_mut(dynamic_properties) { + entry.invokable = true; + } } - } + }); + + self.access_import_type.iter().for_each(|access| { + if let Some(entry) = functions_to_scan.get_mut(access.common_keys.function) { + entry.invokable = true; + } + + if let Some(resolver) = access.common_keys.resolver { + if let Some(entry) = functions_to_scan.get_mut(resolver) { + entry.invokable = true; + } + } - for access in self.access_import_type { - functions_to_scan.push(Entrypoint { - function: access.function, - invokable: true, - web_trigger: false - }); if let Some(delete) = access.one_delete_import { - functions_to_scan.push(Entrypoint { - function: delete.function, - invokable: true, - web_trigger: false - }) + if let Some(entry) = functions_to_scan.get_mut(delete) { + entry.invokable = true; + } } - if let Some(start)= access.start_import { - functions_to_scan.push(Entrypoint { - function: start.function, - invokable: true, - web_trigger: false - }) + if let Some(start) = access.start_import { + if let Some(entry) = functions_to_scan.get_mut(start) { + entry.invokable = true; + } } - if let Some(stop)= access.stop_import { - functions_to_scan.push(Entrypoint { - function: stop.function, - invokable: true, - web_trigger: false - }) + if let Some(stop) = access.stop_import { + if let Some(entry) = functions_to_scan.get_mut(stop) { + entry.invokable = true; + } } if let Some(status)= access.import_status { - functions_to_scan.push(Entrypoint { - function: status.function, - invokable: true, - web_trigger: false - }) + if let Some(entry) = functions_to_scan.get_mut(status) { + entry.invokable = true; + } } - } + }); // get array for user invokable module functions // make alternate_functions all user-invokable functions - for module in self.extra.into_values().flatten() { + for module in self.extra.clone().into_values().flatten() { if let Some(mod_function) = module.function { - functions_to_scan.push(Entrypoint { - function: mod_function, - invokable: true, - web_trigger: false - }); + if let Some(entry) = functions_to_scan.get_mut(mod_function) { + entry.invokable = true; + } } if let Some(resolver) = module.resolver { - functions_to_scan.push(Entrypoint { - function: resolver.function, - invokable: true, - web_trigger: false - }); + if let Some(entry) = functions_to_scan.get_mut(resolver.function) { + entry.invokable = true; + } } } @@ -740,7 +693,7 @@ mod tests { assert_eq!(manifest.app.name, Some("My App")); assert_eq!(manifest.app.id, "my-app"); assert_eq!(manifest.modules.macros.len(), 1); - assert_eq!(manifest.modules.macros[0].key, "My Macro"); + assert_eq!(manifest.modules.macros[0].common_keys.key, "My Macro"); // assert_eq!(manifest.modules.macros[0].function, "my-macro"); assert_eq!(manifest.modules.functions.len(), 1); assert_eq!( @@ -834,25 +787,21 @@ mod tests { }"#; let manifest: ForgeManifest = serde_json::from_str(json).unwrap(); assert_eq!(manifest.modules.macros.len(), 1); - - if let Some(func) = manifest.modules.macros[0].function { - assert_eq!(func, "Catch-me-if-you-can0"); - - } + assert_eq!(manifest.modules.macros[0].common_keys.function, "Catch-me-if-you-can0"); - if let Some(func) = &manifest.modules.macros[0].resolver { - assert_eq!(func.function, "Catch-me-if-you-can1"); + if let Some(func) = manifest.modules.macros[0].common_keys.resolver { + assert_eq!(func, "Catch-me-if-you-can1"); } - if let Some(func) = &manifest.modules.macros[0].config { - assert_eq!(func.function, "Catch-me-if-you-can2"); + if let Some(func) = manifest.modules.macros[0].config { + assert_eq!(func, "Catch-me-if-you-can2"); } - if let Some(func) = &manifest.modules.macros[0].export { - assert_eq!(func.function, "Catch-me-if-you-can3"); + if let Some(func) = manifest.modules.macros[0].export { + assert_eq!(func, "Catch-me-if-you-can3"); } diff --git a/crates/fsrt/src/main.rs b/crates/fsrt/src/main.rs index 3935f736..5a0dc292 100644 --- a/crates/fsrt/src/main.rs +++ b/crates/fsrt/src/main.rs @@ -5,7 +5,8 @@ use forge_permission_resolver::permissions_resolver::{ }; use miette::{IntoDiagnostic, Result}; use std::{ - collections::HashSet, + collections::{HashSet, BTreeMap, hash_map::Entry}, + convert::TryFrom, fs, os::unix::prelude::OsStrExt, path::{Path, PathBuf}, @@ -102,7 +103,7 @@ struct ForgeProject<'a> { sm: Arc, ctx: AppCtx, env: Environment, - funcs: Vec>, + funcs:BTreeMap<&'a str, Entrypoint<'a>>, opts: Opts, >>>>>>> cd2ed7b (edited main to iterate over vector of functions. TODO: edit add_funcs in main.rs and test into_analyzble_function use case in main.rs) } From 0f0649f0870c405f9eac586655f17d68cf3a91a4 Mon Sep 17 00:00:00 2001 From: Alina Wang Date: Wed, 10 Jan 2024 18:15:32 -0800 Subject: [PATCH 364/517] commented out consumer filter --- crates/forge_loader/src/manifest.rs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/crates/forge_loader/src/manifest.rs b/crates/forge_loader/src/manifest.rs index 1f466340..3ab6267f 100644 --- a/crates/forge_loader/src/manifest.rs +++ b/crates/forge_loader/src/manifest.rs @@ -378,13 +378,13 @@ impl<'a> ForgeModules<'a> { }); // create arrays representing functions that expose user non-invokable functions - self.consumers.iter().for_each(|consumer| { - if functions_to_scan.contains_key(consumer.resolver.function) { - if let Some(entry) = functions_to_scan.get_mut(consumer.resolver.function) { - entry.invokable = true; - } - } - }); + // self.consumers.iter().for_each(|consumer| { + // if functions_to_scan.contains_key(consumer.resolver.function) { + // if let Some(entry) = functions_to_scan.get_mut(consumer.resolver.function) { + // entry.invokable = true; + // } + // } + // }); self.data_provider.iter().for_each(|dataprovider| { if let Some(call) = dataprovider.callback { From b0d32c63e1921901053aa3e7423ac4bc88dc0ef7 Mon Sep 17 00:00:00 2001 From: Alina Wang Date: Wed, 10 Jan 2024 18:15:40 -0800 Subject: [PATCH 365/517] updated search_suggestion in customfield to be an optional value. --- crates/forge_loader/src/manifest.rs | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/crates/forge_loader/src/manifest.rs b/crates/forge_loader/src/manifest.rs index 3ab6267f..efe8ea34 100644 --- a/crates/forge_loader/src/manifest.rs +++ b/crates/forge_loader/src/manifest.rs @@ -2,8 +2,7 @@ use std::{ borrow::Borrow, collections::{HashSet}, hash::Hash, - path::{Path, PathBuf}, - sync::Arc, + path::{Path, PathBuf}, str::pattern::SearchStep, }; use crate::{forgepermissions::ForgePermissions, Error}; @@ -117,7 +116,7 @@ pub struct CustomField<'a> { #[serde(flatten, borrow)] common_keys: CommonKey<'a>, value: Option<&'a str>, - search_suggestions: &'a str, + search_suggestions: Option<&'a str>, edit: Option<&'a str>, } @@ -402,8 +401,11 @@ impl<'a> ForgeModules<'a> { } } - if let Some(entry) = functions_to_scan.get_mut(customfield.search_suggestions) { - entry.invokable = true; + if let Some(search) = customfield.search_suggestions { + if let Some(entry) = functions_to_scan.get_mut(search) { + entry.invokable = true; + } + } if let Some(entry) = functions_to_scan.get_mut(customfield.common_keys.function) { From 9814698252e548426515f4140544652dca35fb4b Mon Sep 17 00:00:00 2001 From: Alina Wang Date: Wed, 10 Jan 2024 18:16:28 -0800 Subject: [PATCH 366/517] modified into_analyzable_functions with Josh and implementation in main.rs --- crates/forge_loader/src/manifest.rs | 383 ++++++++-------------------- crates/fsrt/src/main.rs | 165 ++++++------ 2 files changed, 195 insertions(+), 353 deletions(-) diff --git a/crates/forge_loader/src/manifest.rs b/crates/forge_loader/src/manifest.rs index efe8ea34..8915d1d2 100644 --- a/crates/forge_loader/src/manifest.rs +++ b/crates/forge_loader/src/manifest.rs @@ -1,15 +1,16 @@ use std::{ borrow::Borrow, - collections::{HashSet}, + collections::{BTreeSet, HashSet}, hash::Hash, - path::{Path, PathBuf}, str::pattern::SearchStep, + path::{Path, PathBuf}, + sync::Arc, }; use crate::{forgepermissions::ForgePermissions, Error}; use forge_utils::FxHashMap; use itertools::{Either, Itertools}; use serde::Deserialize; -use std::collections::BTreeMap; +use serde_json::map::Entry; use tracing::trace; #[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] @@ -17,7 +18,7 @@ struct AuthProviders<'a> { #[serde(borrow)] auth: Vec<&'a str>, } -// Maps the Functions Module in common Modules +// Maps the Functions Module in common Modules #[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] pub struct FunctionMod<'a> { key: &'a str, @@ -32,51 +33,48 @@ struct CommonKey<'a> { key: &'a str, function: &'a str, resolver: Option<&'a str>, - } #[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] -struct MacroMod<'a> { - #[serde(flatten, borrow)] +struct MacroMod<'a> { + #[serde(flatten, borrow)] common_keys: CommonKey<'a>, config: Option<&'a str>, export: Option<&'a str>, } #[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] -struct ContentByLineItem<'a> { - #[serde(flatten, borrow)] +struct ContentByLineItem<'a> { + #[serde(flatten, borrow)] common_keys: CommonKey<'a>, - #[serde(borrow)] + #[serde(borrow)] dynamic_properties: Option<&'a str>, } #[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] -struct IssueGlance<'a> { - #[serde(flatten, borrow)] +struct IssueGlance<'a> { + #[serde(flatten, borrow)] common_keys: CommonKey<'a>, dynamic_properties: Option<&'a str>, - } #[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] -struct AccessImportType<'a> { - #[serde(flatten, borrow)] +struct AccessImportType<'a> { + #[serde(flatten, borrow)] common_keys: CommonKey<'a>, one_delete_import: Option<&'a str>, start_import: Option<&'a str>, stop_import: Option<&'a str>, import_status: Option<&'a str>, - } -// WebTrigger => RawTrigger; WHY IS THIS NAMED DIFFERENTLY !? WHO CHANGED NAMES +// WebTrigger => RawTrigger; WHY IS THIS NAMED DIFFERENTLY !? WHO CHANGED NAMES #[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] struct RawTrigger<'a> { key: &'a str, function: &'a str, } -// Trigger => EventTriger; WHY IS THIS NAMED DIFFERENTLY !? WHO CHANGED NAMES +// Trigger => EventTriger; WHY IS THIS NAMED DIFFERENTLY !? WHO CHANGED NAMES #[derive(Debug, Clone, PartialEq, Eq, Deserialize)] struct EventTrigger<'a> { #[serde(flatten, borrow)] @@ -93,7 +91,7 @@ enum Interval { Week, } -// Thank you to whomeever kept this one the same. T.T +// Thank you to whomeever kept this one the same. T.T #[derive(Debug, Clone, PartialEq, Eq, Deserialize)] struct ScheduledTrigger<'a> { #[serde(flatten, borrow)] @@ -105,13 +103,13 @@ struct ScheduledTrigger<'a> { #[derive(Debug, Clone, PartialEq, Eq, Deserialize)] pub struct DataProvider<'a> { #[serde(flatten, borrow)] - key: &'a str, + key: &'a str, callback: Option<&'a str>, } -// Struct for Custom field Module. Check that search suggestion gets read in correctly. +// Struct for Custom field Module. Check that search suggestion gets read in correctly. #[derive(Debug, Clone, PartialEq, Eq, Deserialize)] -pub struct CustomField<'a> { +pub struct CustomField<'a> { // all attributes below involve function calls #[serde(flatten, borrow)] common_keys: CommonKey<'a>, @@ -120,23 +118,22 @@ pub struct CustomField<'a> { edit: Option<&'a str>, } - #[derive(Debug, Clone, PartialEq, Eq, Deserialize)] pub struct UiModificatons<'a> { #[serde(flatten, borrow)] - common_keys: CommonKey<'a> + common_keys: CommonKey<'a>, } #[derive(Debug, Clone, PartialEq, Eq, Deserialize)] pub struct WorkflowValidator<'a> { #[serde(flatten, borrow)] - common_keys: CommonKey<'a> + common_keys: CommonKey<'a>, } #[derive(Debug, Clone, PartialEq, Eq, Deserialize)] pub struct WorkflowPostFunction<'a> { #[serde(flatten, borrow)] - common_keys: CommonKey<'a> + common_keys: CommonKey<'a>, } // Add more structs here for deserializing forge modules @@ -261,10 +258,9 @@ pub struct FunctionRef<'a, S = Unresolved> { status: S, } - // Add an extra variant to the FunctionTy enum for non user invocable functions -// Indirect: functions indirectly invoked by user :O So kewl. -// TODO: change this to struct with bools +// Indirect: functions indirectly invoked by user :O So kewl. +// TODO: change this to struct with bools #[derive(Debug, Clone, PartialEq, Eq)] pub enum FunctionTy { Invokable(T), @@ -273,22 +269,28 @@ pub enum FunctionTy { // Struct used for tracking what scan a funtion requires. #[derive(Debug, Clone, PartialEq, Eq)] -pub struct Entrypoint<'a> { - pub function: &'a str, +pub struct Entrypoint<'a, S = Unresolved> { + pub function: FunctionRef<'a, S>, pub invokable: bool, pub web_trigger: bool, } +// Helper functions that help filter out which functions are what. +// original code that's commented out to modify methods. Here for reference +// impl FunctionTy { +// pub fn map(self, f: impl FnOnce(T) -> O) -> FunctionTy { +// match self { +// Self::Invokable(t) => FunctionTy::Invokable(f(t)), +// Self::WebTrigger(t) => FunctionTy::WebTrigger(f(t)), +// } +// } -// Helper functions that help filter out which functions are what. -impl FunctionTy { - pub fn map(self, f: impl FnOnce(T) -> O) -> FunctionTy { - match self { - Self::Invokable(t) => FunctionTy::Invokable(f(t)), - Self::WebTrigger(t) => FunctionTy::WebTrigger(f(t)), - } - } - +// #[inline] +// pub fn into_inner(self) -> T { +// match self { +// FunctionTy::Invokable(t) | FunctionTy::WebTrigger(t) => t, +// } +// } // #[inline] // pub fn into_inner(self) -> T { // match self { @@ -316,270 +318,94 @@ impl AsRef for FunctionTy { } } - impl<'a> ForgeModules<'a> { - -// TODO: function returns iterator where each item is some specified type. - pub fn into_analyzable_functions ( - &mut self, - ) -> BTreeMap<&'a str, Entrypoint<'a>>{ + // TODO: function returns iterator where each item is some specified type. + pub fn into_analyzable_functions(mut self) -> impl Iterator> { // number of webtriggers are usually low, so it's better to just sort them and reuse - self.webtriggers.sort_unstable_by_key(|trigger| trigger.function); - - // Get all the Triggers and represent them as a new struct thing where "webtrigger" attribute is true - // for all trigger things + self.webtriggers + .sort_unstable_by_key(|trigger| trigger.function); - let mut functions_to_scan = BTreeMap::new(); + // Get all the Triggers and represent them as a new struct thing where "webtrigger" attribute is true + // for all trigger things - // Get all functions for module from manifest.yml - self.functions.iter().for_each(|func| { - functions_to_scan.insert(func.key, Entrypoint { - function: func.key, - invokable: false, - web_trigger: false, - }); - - }); - - - self.webtriggers.iter().for_each(|webtriggers| { - if functions_to_scan.contains_key(webtriggers.function) { - if let Some(entry) = functions_to_scan.get_mut(webtriggers.function) { - entry.web_trigger = true; - } - } - - }); - - self.webtriggers.iter().for_each(|webtriggers| { - if functions_to_scan.contains_key(webtriggers.function) { - if let Some(entry) = functions_to_scan.get_mut(webtriggers.function) { - entry.web_trigger = true; - } - } - - }); - - - self.event_triggers.iter().for_each(|event_triggers| { - if functions_to_scan.contains_key(event_triggers.raw.function) { - if let Some(entry) = functions_to_scan.get_mut(event_triggers.raw.function) { - entry.web_trigger = true; - } - } - }); - self.scheduled_triggers.iter().for_each(|schedule_triggers| { - if functions_to_scan.contains_key(schedule_triggers.raw.function) { - if let Some(entry) = functions_to_scan.get_mut(schedule_triggers.raw.function) { - entry.web_trigger = true; - } - } - }); - - // create arrays representing functions that expose user non-invokable functions - // self.consumers.iter().for_each(|consumer| { - // if functions_to_scan.contains_key(consumer.resolver.function) { - // if let Some(entry) = functions_to_scan.get_mut(consumer.resolver.function) { - // entry.invokable = true; - // } - // } - // }); + let mut invokable_functions = BTreeSet::new(); self.data_provider.iter().for_each(|dataprovider| { - if let Some(call) = dataprovider.callback { - if let Some(entry) = functions_to_scan.get_mut(call) { - entry.invokable = true; - } - } - + invokable_functions.extend(dataprovider.callback); }); self.custom_field.iter().for_each(|customfield| { - if let Some(value)= customfield.value { - if let Some(entry) = functions_to_scan.get_mut(value) { - entry.invokable = true; - } - } - - if let Some(search) = customfield.search_suggestions { - if let Some(entry) = functions_to_scan.get_mut(search) { - entry.invokable = true; - } - - } - - if let Some(entry) = functions_to_scan.get_mut(customfield.common_keys.function) { - entry.invokable = true; - } - - if let Some(resolver) = customfield.common_keys.resolver { - if let Some(entry) = functions_to_scan.get_mut(resolver) { - entry.invokable = true; - } - } - - if let Some(edit) = customfield.edit { - if let Some(entry) = functions_to_scan.get_mut(edit) { - entry.invokable = true; - } - - } + invokable_functions.extend(customfield.value); + invokable_functions.extend(customfield.search_suggestions); + invokable_functions.extend(customfield.edit); + invokable_functions.insert(customfield.common_keys.function); + invokable_functions.extend(customfield.common_keys.resolver); }); self.ui_modifications.iter().for_each(|ui| { - if let Some(entry) = functions_to_scan.get_mut(ui.common_keys.function) { - entry.invokable = true; - } - - if let Some(resolver) = ui.common_keys.resolver { - if let Some(entry) = functions_to_scan.get_mut(resolver) { - entry.invokable = true; - } - } + invokable_functions.insert(ui.common_keys.function); + invokable_functions.extend(ui.common_keys.resolver); }); self.workflow_validator.iter().for_each(|validator| { - if let Some(entry) = functions_to_scan.get_mut(validator.common_keys.function) { - entry.invokable = true; - } + invokable_functions.insert(validator.common_keys.key); - if let Some(resolver) = validator.common_keys.resolver { - if let Some(entry) = functions_to_scan.get_mut(resolver) { - entry.invokable = true; - } - } - }); - - self.workflow_post_function.iter().for_each(|post_function| { - if let Some(entry) = functions_to_scan.get_mut(post_function.common_keys.function) { - entry.invokable = true; - } + invokable_functions.insert(validator.common_keys.function); - if let Some(resolver) = post_function.common_keys.resolver { - if let Some(entry) = functions_to_scan.get_mut(resolver) { - entry.invokable = true; - } - } + invokable_functions.extend(validator.common_keys.resolver); }); - // get user invokable modules that have additional exposure endpoints. - // ie macros has config and export fields on top of resolver fields that are functions - self.macros.iter().for_each(|macros| { - if let Some(resolver)= macros.common_keys.resolver { - if let Some(entry) = functions_to_scan.get_mut(resolver) { - entry.invokable = true; - } - } - - if let Some(config)= macros.config { - if let Some(entry) = functions_to_scan.get_mut(config) { - entry.invokable = true; - } - } - - if let Some(export)= macros.export { - if let Some(entry) = functions_to_scan.get_mut(export) { - entry.invokable = true; - } - } - - }); + self.workflow_post_function + .iter() + .for_each(|post_function| { + invokable_functions.insert(post_function.common_keys.key); - self.content_by_line_item.iter().for_each(|contentitem| { - if let Some(entry) = functions_to_scan.get_mut(contentitem.common_keys.function) { - entry.invokable = true; - } + invokable_functions.insert(post_function.common_keys.function); + invokable_functions.extend(post_function.common_keys.resolver); + }); - if let Some(resolver)= contentitem.common_keys.resolver { - if let Some(entry) = functions_to_scan.get_mut(resolver) { - entry.invokable = true; - } - } + // get user invokable modules that have additional exposure endpoints. + // ie macros has config and export fields on top of resolver fields that are functions + self.macros.iter().for_each(|macros| { + invokable_functions.insert(macros.common_keys.key); - if let Some(dynamic_properties)= contentitem.dynamic_properties { - if let Some(entry) = functions_to_scan.get_mut(dynamic_properties) { - entry.invokable = true; - } - } + invokable_functions.insert(macros.common_keys.function); + invokable_functions.extend(macros.common_keys.resolver); + invokable_functions.extend(macros.config); + invokable_functions.extend(macros.export); }); self.issue_glance.iter().for_each(|issue| { - if let Some(entry) = functions_to_scan.get_mut(issue.common_keys.function) { - entry.invokable = true; - } - - - if let Some(resolver)= issue.common_keys.resolver { - if let Some(entry) = functions_to_scan.get_mut(resolver) { - entry.invokable = true; - } - } - - if let Some(dynamic_properties)= issue.dynamic_properties { - if let Some(entry) = functions_to_scan.get_mut(dynamic_properties) { - entry.invokable = true; - } - } - + invokable_functions.insert(issue.common_keys.function); + invokable_functions.extend(issue.common_keys.resolver); + invokable_functions.extend(issue.dynamic_properties); }); self.access_import_type.iter().for_each(|access| { - if let Some(entry) = functions_to_scan.get_mut(access.common_keys.function) { - entry.invokable = true; - } - - if let Some(resolver) = access.common_keys.resolver { - if let Some(entry) = functions_to_scan.get_mut(resolver) { - entry.invokable = true; - } - } - - if let Some(delete) = access.one_delete_import { - if let Some(entry) = functions_to_scan.get_mut(delete) { - entry.invokable = true; - } - } - - if let Some(start) = access.start_import { - if let Some(entry) = functions_to_scan.get_mut(start) { - entry.invokable = true; - } - } - - if let Some(stop) = access.stop_import { - if let Some(entry) = functions_to_scan.get_mut(stop) { - entry.invokable = true; - } - } - - if let Some(status)= access.import_status { - if let Some(entry) = functions_to_scan.get_mut(status) { - entry.invokable = true; - } - } + invokable_functions.insert(access.common_keys.function); + invokable_functions.extend(access.common_keys.resolver); + invokable_functions.extend(access.one_delete_import); + invokable_functions.extend(access.stop_import); + invokable_functions.extend(access.start_import); + invokable_functions.extend(access.import_status); }); - - // get array for user invokable module functions - // make alternate_functions all user-invokable functions - for module in self.extra.clone().into_values().flatten() { - if let Some(mod_function) = module.function { - if let Some(entry) = functions_to_scan.get_mut(mod_function) { - entry.invokable = true; - } - } - - if let Some(resolver) = module.resolver { - if let Some(entry) = functions_to_scan.get_mut(resolver.function) { - entry.invokable = true; - } - } - } - - functions_to_scan + + self.functions.into_iter().flat_map(move |func| { + let web_trigger = self + .webtriggers + .binary_search_by_key(&func.key, |trigger| &trigger.function) + .is_ok(); + let invokable = invokable_functions.contains(func.key); + Ok::<_, Error>(Entrypoint { + function: FunctionRef::try_from(func)?, + invokable, + web_trigger, + }) + }) } } @@ -731,10 +557,9 @@ mod tests { ); } - // Modified specific deserialization schemes for modules. Checking that new schemes can deserialize function values. + // Modified specific deserialization schemes for modules. Checking that new schemes can deserialize function values. #[test] fn test_new_deserialize() { - let json = r#"{ "app": { "name": "My App", @@ -789,23 +614,21 @@ mod tests { }"#; let manifest: ForgeManifest = serde_json::from_str(json).unwrap(); assert_eq!(manifest.modules.macros.len(), 1); - assert_eq!(manifest.modules.macros[0].common_keys.function, "Catch-me-if-you-can0"); - + assert_eq!( + manifest.modules.macros[0].common_keys.function, + "Catch-me-if-you-can0" + ); if let Some(func) = manifest.modules.macros[0].common_keys.resolver { assert_eq!(func, "Catch-me-if-you-can1"); - } if let Some(func) = manifest.modules.macros[0].config { assert_eq!(func, "Catch-me-if-you-can2"); - } if let Some(func) = manifest.modules.macros[0].export { assert_eq!(func, "Catch-me-if-you-can3"); - - } - + } } } diff --git a/crates/fsrt/src/main.rs b/crates/fsrt/src/main.rs index 5a0dc292..b06347d6 100644 --- a/crates/fsrt/src/main.rs +++ b/crates/fsrt/src/main.rs @@ -5,8 +5,7 @@ use forge_permission_resolver::permissions_resolver::{ }; use miette::{IntoDiagnostic, Result}; use std::{ - collections::{HashSet, BTreeMap, hash_map::Entry}, - convert::TryFrom, + collections::HashSet, fs, os::unix::prelude::OsStrExt, path::{Path, PathBuf}, @@ -18,7 +17,6 @@ use std::{ use clap::{Parser, ValueHint}; use miette::{IntoDiagnostic, Result}; -use serde_json::map::Entry; use swc_core::{ common::{Globals, Mark, SourceMap, GLOBALS}, ecma::{ @@ -44,7 +42,7 @@ use forge_analyzer::{ resolver::resolve_calls, }; -use forge_loader::manifest::{ForgeManifest, FunctionRef, FunctionTy, Entrypoint}; +use forge_loader::manifest::{Entrypoint, ForgeManifest, Resolved}; use walkdir::WalkDir; #[derive(Parser, Debug)] @@ -98,17 +96,27 @@ struct Opts { out: Option, } +#[derive(Debug, Clone)] +struct ResolvedEntryPoint<'a> { + func_name: &'a str, + path: PathBuf, + module: ModId, + def_id: DefId, + webtrigger: bool, + invokable: bool, +} + struct ForgeProject<'a> { #[allow(dead_code)] sm: Arc, ctx: AppCtx, env: Environment, - funcs:BTreeMap<&'a str, Entrypoint<'a>>, + funcs: Vec>, opts: Opts, >>>>>>> cd2ed7b (edited main to iterate over vector of functions. TODO: edit add_funcs in main.rs and test into_analyzble_function use case in main.rs) } -impl ForgeProject<'_> { +impl<'a> ForgeProject<'a> { #[instrument(skip(src, iter))] fn with_files_and_sourceroot, I: IntoIterator>( src: P, @@ -156,21 +164,24 @@ impl ForgeProject<'_> { opts: Opts::default(), } } -// TODO: edit to work with new iterator that not FUNCTIONTY - fn add_funcs<'a, I: IntoIterator>>(&mut self, iter: I) { - self.funcs.extend(iter); + // TODO: edit to work with new iterator that not FUNCTIONTY + fn add_funcs>>(&mut self, iter: I) { + self.funcs.extend(iter.into_iter().filter_map(|entrypoint| { + let (func_name, path) = entrypoint.function.into_func_path(); + let module = self.ctx.modid_from_path(&path)?; + let def_id = self.env.module_export(module, func_name)?; + Some(ResolvedEntryPoint { + func_name, + path, + module, + def_id, + invokable: entrypoint.invokable, + webtrigger: entrypoint.web_trigger, + }) + })); } } - -// self.funcs.extend(iter.into_iter().flat_map(|ftype| { -// ftype.sequence(|(func_name, path)| { -// let modid = self.ctx.modid_from_path(&path)?; -// let func = self.env.module_export(modid, func_name)?; -// Some((func_name.to_owned(), path, modid, func)) -// }) -// })); - fn is_js_file>(path: P) -> bool { matches!( path.as_ref().extension().map(|s| s.as_bytes()), @@ -190,11 +201,7 @@ fn collect_sourcefiles>(root: P) -> impl Iterator } #[tracing::instrument(level = "debug")] -<<<<<<< HEAD -fn scan_directory(dir: PathBuf, function: Option<&str>, opts: &Args) -> Result { -======= fn scan_directory(dir: PathBuf, function: Option<&str>, opts: Opts) -> Result<()> { ->>>>>>> 29c38d6 (modified into_analyzable_functions with Josh and implementation in main.rs) let mut manifest_file = dir.clone(); manifest_file.push("manifest.yaml"); if !manifest_file.exists() { @@ -233,23 +240,23 @@ fn scan_directory(dir: PathBuf, function: Option<&str>, opts: Opts) -> Result<() }); let run_permission_checker = opts.check_permissions && !transpiled_async; - let funcrefs = manifest.modules.into_analyzable_functions(); - - // .flat_map(|f| { - // f.sequence(|fmod| { - // let resolved_func = FunctionRef::try_from(fmod)?.try_resolve(&paths, &dir)?; - // Ok::<_, forge_loader::Error>(resolved_func.into_func_path()) - // }) - // }); + let funcrefs = manifest + .modules + .into_analyzable_functions() + .flat_map(|entrypoint| { + Ok::<_, forge_loader::Error>(Entrypoint { + function: entrypoint.function.try_resolve(&paths, &dir)?, + invokable: entrypoint.invokable, + web_trigger: entrypoint.web_trigger, + }) + }); + let src_root = dir.join("src"); let mut proj = ForgeProject::with_files_and_sourceroot(src_root, paths.clone(), secret_packages); if transpiled_async { warn!("Unable to scan due to transpiled async"); -<<<<<<< HEAD -======= return Ok(()); ->>>>>>> 29c38d6 (modified into_analyzable_functions with Josh and implementation in main.rs) } proj.add_funcs(funcrefs); resolve_calls(&mut proj.ctx); @@ -333,48 +340,65 @@ fn scan_directory(dir: PathBuf, function: Option<&str>, opts: Opts) -> Result<() for func in &proj.funcs { // TODO: Update operations in for loop to scan functions. // idea: iterate over each func which should be struct that tracks the function to be scanned. And performs scans according to bool. - // match *func { - // FunctionTy::Invokable((ref func, ref path, _, def)) => { - // let mut checker = AuthZChecker::new(); - // debug!("checking {func} at {path:?}"); - // if let Err(err) = interp.run_checker(def, &mut checker, path.clone(), func.clone()) - // { - // warn!("error while scanning {func} in {path:?}: {err}"); - // } - // reporter.add_vulnerabilities(checker.into_vulns()); - // } - // FunctionTy::WebTrigger((ref func, ref path, _, def)) => { - // let mut checker = AuthenticateChecker::new(); - // debug!("checking webtrigger {func} at {path:?}"); - // if let Err(err) = - // authn_interp.run_checker(def, &mut checker, path.clone(), func.clone()) - // { - // warn!("error while scanning {func} in {path:?}: {err}"); - // } - // reporter.add_vulnerabilities(checker.into_vulns()); - // } - // } - + // match *func { + // FunctionTy::Invokable((ref func, ref path, _, def)) => { + // let mut checker = AuthZChecker::new(); + // debug!("checking {func} at {path:?}"); + // if let Err(err) = interp.run_checker(def, &mut checker, path.clone(), func.clone()) + // { + // warn!("error while scanning {func} in {path:?}: {err}"); + // } + // reporter.add_vulnerabilities(checker.into_vulns()); + // } + // FunctionTy::WebTrigger((ref func, ref path, _, def)) => { + // let mut checker = AuthenticateChecker::new(); + // debug!("checking webtrigger {func} at {path:?}"); + // if let Err(err) = + // authn_interp.run_checker(def, &mut checker, path.clone(), func.clone()) + // { + // warn!("error while scanning {func} in {path:?}: {err}"); + // } + // reporter.add_vulnerabilities(checker.into_vulns()); + // } + // } + + // Get entrypoint value from tuple + // Logic for performing scans. + // If it's invokable, then run invokable scan. If web_trigger, then trigger scan. + // And if it's both, run both scans. if func.invokable { let mut checker = AuthZChecker::new(); - debug!("checking {:?} at {path:?}", func.function); - if let Err(err) = interp.run_checker(def, &mut checker, path.clone(), func.function.to_string()) - { - warn!("error while scanning {:?} in {path:?}: {err}", func.function); + debug!("checking {:?} at {:?}", func.func_name, &func.path); + if let Err(err) = interp.run_checker( + func.def_id, + &mut checker, + func.path.clone(), + func.func_name.to_string(), + ) { + warn!( + "error while scanning {:?} in {:?}: {err}", + func.func_name, func.path, + ); } reporter.add_vulnerabilities(checker.into_vulns()); - - } else if func.web_trigger { + } else if func.webtrigger { let mut checker = AuthenticateChecker::new(); - debug!("checking webtrigger {:?} at {path:?}", func.function); - if let Err(err) = - authn_interp.run_checker(def, &mut checker, path.clone(), func.function.to_string()) - { - warn!("error while scanning {:?} in {path:?}: {err}", func.function); + debug!( + "checking webtrigger {:?} at {:?}", + func.func_name, func.path, + ); + if let Err(err) = authn_interp.run_checker( + func.def_id, + &mut checker, + func.path.clone(), + func.func_name.to_string(), + ) { + warn!( + "error while scanning {:?} in {:?}: {err}", + func.func_name, func.path, + ); } reporter.add_vulnerabilities(checker.into_vulns()); - - } } let report = serde_json::to_string(&reporter.into_report()).into_diagnostic()?; @@ -385,12 +409,7 @@ fn scan_directory(dir: PathBuf, function: Option<&str>, opts: Opts) -> Result<() } None => println!("{report}"), } -<<<<<<< HEAD - - Ok(proj) -======= Ok(()) ->>>>>>> 29c38d6 (modified into_analyzable_functions with Josh and implementation in main.rs) } fn main() -> Result<()> { From 8ec6866bdc231ecf9972316a3b06cc99c81f4e87 Mon Sep 17 00:00:00 2001 From: Alina Wang Date: Wed, 10 Jan 2024 18:16:46 -0800 Subject: [PATCH 367/517] rebased EAS-1893 --- crates/forge_loader/src/manifest.rs | 25 +++++++++++++++++++++++-- 1 file changed, 23 insertions(+), 2 deletions(-) diff --git a/crates/forge_loader/src/manifest.rs b/crates/forge_loader/src/manifest.rs index 8915d1d2..7dd374fb 100644 --- a/crates/forge_loader/src/manifest.rs +++ b/crates/forge_loader/src/manifest.rs @@ -37,7 +37,6 @@ struct CommonKey<'a> { #[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] struct MacroMod<'a> { - #[serde(flatten, borrow)] common_keys: CommonKey<'a>, config: Option<&'a str>, export: Option<&'a str>, @@ -394,6 +393,28 @@ impl<'a> ForgeModules<'a> { invokable_functions.extend(access.import_status); }); + self.functions.into_iter().flat_map(move |func| { + let web_trigger = self + .webtriggers + .binary_search_by_key(&func.key, |trigger| &trigger.function) + .is_ok(); + let invokable = invokable_functions.contains(func.key); + Ok::<_, Error>(Entrypoint { + function: FunctionRef::try_from(func)?, + invokable, + web_trigger, + }) + }); + self.access_import_type.iter().for_each(|access| { + invokable_functions.insert(access.common_keys.function); + invokable_functions.extend(access.common_keys.resolver); + + invokable_functions.extend(access.one_delete_import); + invokable_functions.extend(access.stop_import); + invokable_functions.extend(access.start_import); + invokable_functions.extend(access.import_status); + }); + self.functions.into_iter().flat_map(move |func| { let web_trigger = self .webtriggers @@ -631,4 +652,4 @@ mod tests { assert_eq!(func, "Catch-me-if-you-can3"); } } -} +}} From db608eaa9ffbcea880589781d4ee3501ad62c84b Mon Sep 17 00:00:00 2001 From: Alina Wang Date: Wed, 10 Jan 2024 18:17:09 -0800 Subject: [PATCH 368/517] chore: updated main.rs file to remove vscode notes --- crates/fsrt/src/main.rs | 127 +++++++++++++++++++++------------------- 1 file changed, 67 insertions(+), 60 deletions(-) diff --git a/crates/fsrt/src/main.rs b/crates/fsrt/src/main.rs index b06347d6..44322ec9 100644 --- a/crates/fsrt/src/main.rs +++ b/crates/fsrt/src/main.rs @@ -12,11 +12,6 @@ use std::{ sync::Arc, }; -<<<<<<< HEAD -======= -use clap::{Parser, ValueHint}; -use miette::{IntoDiagnostic, Result}; - use swc_core::{ common::{Globals, Mark, SourceMap, GLOBALS}, ecma::{ @@ -85,17 +80,6 @@ struct Args { dirs: Vec, } -<<<<<<< HEAD -struct ForgeProject { -======= -#[derive(Debug, Clone, Default)] -struct Opts { - dump_cfg: bool, - dump_callgraph: bool, - appkey: Option, - out: Option, -} - #[derive(Debug, Clone)] struct ResolvedEntryPoint<'a> { func_name: &'a str, @@ -112,8 +96,6 @@ struct ForgeProject<'a> { ctx: AppCtx, env: Environment, funcs: Vec>, - opts: Opts, ->>>>>>> cd2ed7b (edited main to iterate over vector of functions. TODO: edit add_funcs in main.rs and test into_analyzble_function use case in main.rs) } impl<'a> ForgeProject<'a> { @@ -201,7 +183,7 @@ fn collect_sourcefiles>(root: P) -> impl Iterator } #[tracing::instrument(level = "debug")] -fn scan_directory(dir: PathBuf, function: Option<&str>, opts: Opts) -> Result<()> { +fn scan_directory(dir: PathBuf, function: Option<&str>, opts: &Args) -> Result<()> { let mut manifest_file = dir.clone(); manifest_file.push("manifest.yaml"); if !manifest_file.exists() { @@ -263,7 +245,7 @@ fn scan_directory(dir: PathBuf, function: Option<&str>, opts: Opts) -> Result<() if let Some(func) = opts.dump_ir.as_ref() { let mut lock = std::io::stdout().lock(); proj.env.dump_function(&mut lock, func); - return Ok(proj); + return Ok(()); } let permissions = Vec::from_iter(permissions_declared.iter().cloned()); @@ -272,7 +254,7 @@ fn scan_directory(dir: PathBuf, function: Option<&str>, opts: Opts) -> Result<() let (confluence_permission_resolver, confluence_regex_map) = get_permission_resolver_confluence(); - let mut defintion_analysis_interp = Interp::new( + let mut definition_analysis_interp = Interp::::new( &proj.env, false, true, @@ -303,18 +285,8 @@ fn scan_directory(dir: PathBuf, function: Option<&str>, opts: Opts) -> Result<() &confluence_permission_resolver, &confluence_regex_map, ); - let mut perm_interp = Interp::new( - &proj.env, - false, - true, - permissions.clone(), - &jira_permission_resolver, - &jira_regex_map, - &confluence_permission_resolver, - &confluence_regex_map, - ); let mut reporter = Reporter::new(); - let mut secret_interp = Interp::new( + let mut secret_interp = Interp::::new( &proj.env, false, false, @@ -324,43 +296,72 @@ fn scan_directory(dir: PathBuf, function: Option<&str>, opts: Opts) -> Result<() &confluence_permission_resolver, &confluence_regex_map, ); - let mut pp_interp = Interp::new( + reporter.add_app(opts.appkey.clone().unwrap_or_default(), name.to_owned()); + //let mut all_used_permissions = HashSet::default(); + + let mut perm_interp = Interp::::new( &proj.env, false, - false, + true, permissions.clone(), &jira_permission_resolver, &jira_regex_map, &confluence_permission_resolver, &confluence_regex_map, ); - reporter.add_app(opts.appkey.clone().unwrap_or_default(), name.to_owned()); - //let mut all_used_permissions = HashSet::default(); - for func in &proj.funcs { - // TODO: Update operations in for loop to scan functions. - // idea: iterate over each func which should be struct that tracks the function to be scanned. And performs scans according to bool. - // match *func { - // FunctionTy::Invokable((ref func, ref path, _, def)) => { - // let mut checker = AuthZChecker::new(); - // debug!("checking {func} at {path:?}"); - // if let Err(err) = interp.run_checker(def, &mut checker, path.clone(), func.clone()) - // { - // warn!("error while scanning {func} in {path:?}: {err}"); - // } - // reporter.add_vulnerabilities(checker.into_vulns()); - // } - // FunctionTy::WebTrigger((ref func, ref path, _, def)) => { - // let mut checker = AuthenticateChecker::new(); - // debug!("checking webtrigger {func} at {path:?}"); - // if let Err(err) = - // authn_interp.run_checker(def, &mut checker, path.clone(), func.clone()) - // { - // warn!("error while scanning {func} in {path:?}: {err}"); - // } - // reporter.add_vulnerabilities(checker.into_vulns()); - // } - // } + let mut def_checker = DefintionAnalysisRunner::new(); + if let Err(err) = definition_analysis_interp.run_checker( + func.def_id, + &mut def_checker, + func.path.clone(), + func.func_name.to_string(), + ) { + warn!( + "error while scanning {:?} in {:?}: {err}", + func.func_name, func.path, + ); + } + + if run_permission_checker { + let mut checker = PermissionChecker::new(); + perm_interp.value_manager.varid_to_value = + definition_analysis_interp.value_manager.varid_to_value; + perm_interp.value_manager.defid_to_value = + definition_analysis_interp.value_manager.defid_to_value; + if let Err(err) = perm_interp.run_checker( + func.def_id, + &mut checker, + func.path.clone(), + func.func_name.to_owned(), + ) { + warn!("error while running permission checker: {err}"); + } + definition_analysis_interp.value_manager.varid_to_value = + perm_interp.value_manager.varid_to_value; + definition_analysis_interp.value_manager.defid_to_value = + perm_interp.value_manager.defid_to_value; + } + + let mut checker = SecretChecker::new(); + secret_interp.value_manager.varid_to_value = + definition_analysis_interp.value_manager.varid_to_value; + secret_interp.value_manager.defid_to_value = + definition_analysis_interp.value_manager.defid_to_value; + if let Err(err) = secret_interp.run_checker( + func.def_id, + &mut checker, + func.path.clone(), + func.func_name.to_owned(), + ) { + warn!("error while running secret checker: {err}"); + } else { + reporter.add_vulnerabilities(checker.into_vulns()); + } + definition_analysis_interp.value_manager.varid_to_value = + secret_interp.value_manager.varid_to_value; + definition_analysis_interp.value_manager.defid_to_value = + secret_interp.value_manager.defid_to_value; // Get entrypoint value from tuple // Logic for performing scans. @@ -401,6 +402,11 @@ fn scan_directory(dir: PathBuf, function: Option<&str>, opts: Opts) -> Result<() reporter.add_vulnerabilities(checker.into_vulns()); } } + + if perm_interp.permissions.len() > 0 { + reporter + .add_vulnerabilities(vec![PermissionVuln::new(perm_interp.permissions)].into_iter()); + } let report = serde_json::to_string(&reporter.into_report()).into_diagnostic()?; debug!("On the debug layer: Writing Report"); match &opts.out { @@ -409,6 +415,7 @@ fn scan_directory(dir: PathBuf, function: Option<&str>, opts: Opts) -> Result<() } None => println!("{report}"), } + Ok(()) } From 42a283153fcb430077e9e9b79ccaa0e2efe3d543 Mon Sep 17 00:00:00 2001 From: Alina Wang Date: Mon, 8 Jan 2024 12:06:54 -0800 Subject: [PATCH 369/517] resolving merge conflicts for rebase --- crates/forge_loader/src/manifest.rs | 25 ++++++++++++------------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/crates/forge_loader/src/manifest.rs b/crates/forge_loader/src/manifest.rs index 7dd374fb..fa09d66e 100644 --- a/crates/forge_loader/src/manifest.rs +++ b/crates/forge_loader/src/manifest.rs @@ -267,22 +267,21 @@ pub enum FunctionTy { } // Struct used for tracking what scan a funtion requires. -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct Entrypoint<'a, S = Unresolved> { - pub function: FunctionRef<'a, S>, - pub invokable: bool, - pub web_trigger: bool, +#[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] +pub struct Entrypoints<'a> { + function: Vec>, + invokable: bool, + web_trigger: bool, } // Helper functions that help filter out which functions are what. -// original code that's commented out to modify methods. Here for reference -// impl FunctionTy { -// pub fn map(self, f: impl FnOnce(T) -> O) -> FunctionTy { -// match self { -// Self::Invokable(t) => FunctionTy::Invokable(f(t)), -// Self::WebTrigger(t) => FunctionTy::WebTrigger(f(t)), -// } -// } +impl FunctionTy { + pub fn map(self, f: impl FnOnce(T) -> O) -> FunctionTy { + match self { + Self::Invokable(t) => FunctionTy::Invokable(f(t)), + Self::WebTrigger(t) => FunctionTy::WebTrigger(f(t)), + } + } // #[inline] // pub fn into_inner(self) -> T { From a95d2f0c5f5e7fccec057adb0d4d32e1aeba79a2 Mon Sep 17 00:00:00 2001 From: Alina Wang Date: Mon, 11 Sep 2023 15:50:57 -0400 Subject: [PATCH 370/517] added more todo statements and cleaned up struct definitions --- crates/forge_loader/src/manifest.rs | 56 -------- crates/fsrt/src/main.rs | 209 ++++++++++++++++------------ 2 files changed, 119 insertions(+), 146 deletions(-) diff --git a/crates/forge_loader/src/manifest.rs b/crates/forge_loader/src/manifest.rs index fa09d66e..8ac5792a 100644 --- a/crates/forge_loader/src/manifest.rs +++ b/crates/forge_loader/src/manifest.rs @@ -66,14 +66,12 @@ struct AccessImportType<'a> { import_status: Option<&'a str>, } -// WebTrigger => RawTrigger; WHY IS THIS NAMED DIFFERENTLY !? WHO CHANGED NAMES #[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] struct RawTrigger<'a> { key: &'a str, function: &'a str, } -// Trigger => EventTriger; WHY IS THIS NAMED DIFFERENTLY !? WHO CHANGED NAMES #[derive(Debug, Clone, PartialEq, Eq, Deserialize)] struct EventTrigger<'a> { #[serde(flatten, borrow)] @@ -90,7 +88,6 @@ enum Interval { Week, } -// Thank you to whomeever kept this one the same. T.T #[derive(Debug, Clone, PartialEq, Eq, Deserialize)] struct ScheduledTrigger<'a> { #[serde(flatten, borrow)] @@ -168,17 +165,6 @@ pub struct ForgeModules<'a> { #[serde(rename = "jira:workflowPostFunction", default, borrow)] pub workflow_post_function: Vec>, // deserializing user invokable module functions - #[serde(rename = "compass:dataProvider", default, borrow)] - pub data_provider: Vec>, - #[serde(rename = "jira:customField", default, borrow)] - pub custom_field: Vec>, - #[serde(rename = "jira:uiModificatons", default, borrow)] - pub ui_modifications: Vec>, - #[serde(rename = "jira:workflowValidator", default, borrow)] - pub workflow_validator: Vec>, - #[serde(rename = "jira:workflowPostFunction", default, borrow)] - pub workflow_post_function: Vec>, - // deserializing user invokable module functions #[serde(flatten)] extra: FxHashMap>>, } @@ -274,48 +260,6 @@ pub struct Entrypoints<'a> { web_trigger: bool, } -// Helper functions that help filter out which functions are what. -impl FunctionTy { - pub fn map(self, f: impl FnOnce(T) -> O) -> FunctionTy { - match self { - Self::Invokable(t) => FunctionTy::Invokable(f(t)), - Self::WebTrigger(t) => FunctionTy::WebTrigger(f(t)), - } - } - -// #[inline] -// pub fn into_inner(self) -> T { -// match self { -// FunctionTy::Invokable(t) | FunctionTy::WebTrigger(t) => t, -// } -// } -// #[inline] -// pub fn into_inner(self) -> T { -// match self { -// FunctionTy::Invokable(t) | FunctionTy::WebTrigger(t) => t, -// } -// } - -// pub fn sequence( -// self, -// f: impl FnOnce(T) -> I, -// ) -> impl Iterator> { -// match self { -// Self::Invokable(t) => Either::Left(f(t).into_iter().map(FunctionTy::Invokable)), -// Self::WebTrigger(t) => Either::Right(f(t).into_iter().map(FunctionTy::WebTrigger)), -// } -// } -// } - -impl AsRef for FunctionTy { - #[inline] - fn as_ref(&self) -> &T { - match self { - FunctionTy::Invokable(t) | FunctionTy::WebTrigger(t) => t, - } - } -} - impl<'a> ForgeModules<'a> { // TODO: function returns iterator where each item is some specified type. pub fn into_analyzable_functions(mut self) -> impl Iterator> { diff --git a/crates/fsrt/src/main.rs b/crates/fsrt/src/main.rs index 44322ec9..bb8e523e 100644 --- a/crates/fsrt/src/main.rs +++ b/crates/fsrt/src/main.rs @@ -310,102 +310,131 @@ fn scan_directory(dir: PathBuf, function: Option<&str>, opts: &Args) -> Result<( &confluence_regex_map, ); for func in &proj.funcs { - let mut def_checker = DefintionAnalysisRunner::new(); - if let Err(err) = definition_analysis_interp.run_checker( - func.def_id, - &mut def_checker, - func.path.clone(), - func.func_name.to_string(), - ) { - warn!( - "error while scanning {:?} in {:?}: {err}", - func.func_name, func.path, - ); - } - - if run_permission_checker { - let mut checker = PermissionChecker::new(); - perm_interp.value_manager.varid_to_value = - definition_analysis_interp.value_manager.varid_to_value; - perm_interp.value_manager.defid_to_value = - definition_analysis_interp.value_manager.defid_to_value; - if let Err(err) = perm_interp.run_checker( - func.def_id, - &mut checker, - func.path.clone(), - func.func_name.to_owned(), - ) { - warn!("error while running permission checker: {err}"); - } - definition_analysis_interp.value_manager.varid_to_value = - perm_interp.value_manager.varid_to_value; - definition_analysis_interp.value_manager.defid_to_value = - perm_interp.value_manager.defid_to_value; - } - - let mut checker = SecretChecker::new(); - secret_interp.value_manager.varid_to_value = - definition_analysis_interp.value_manager.varid_to_value; - secret_interp.value_manager.defid_to_value = - definition_analysis_interp.value_manager.defid_to_value; - if let Err(err) = secret_interp.run_checker( - func.def_id, - &mut checker, - func.path.clone(), - func.func_name.to_owned(), - ) { - warn!("error while running secret checker: {err}"); - } else { - reporter.add_vulnerabilities(checker.into_vulns()); - } - definition_analysis_interp.value_manager.varid_to_value = - secret_interp.value_manager.varid_to_value; - definition_analysis_interp.value_manager.defid_to_value = - secret_interp.value_manager.defid_to_value; - - // Get entrypoint value from tuple - // Logic for performing scans. - // If it's invokable, then run invokable scan. If web_trigger, then trigger scan. - // And if it's both, run both scans. - if func.invokable { - let mut checker = AuthZChecker::new(); - debug!("checking {:?} at {:?}", func.func_name, &func.path); - if let Err(err) = interp.run_checker( - func.def_id, - &mut checker, - func.path.clone(), - func.func_name.to_string(), - ) { - warn!( - "error while scanning {:?} in {:?}: {err}", - func.func_name, func.path, - ); + // TODO: Update operations in for loop to scan functions. + // idea: iterate over each func which should be struct that tracks the function to be scanned. And performs scans according to bool. + match *func { + FunctionTy::Invokable((ref func, ref path, _, def)) => { + let mut runner = DefintionAnalysisRunner::new(); + debug!("checking Invokable {func} at {path:?}"); + if let Err(err) = defintion_analysis_interp.run_checker( + def, + &mut runner, + path.clone(), + func.clone(), + ) { + warn!("error while getting definition analysis {func} in {path:?}: {err}"); + } + let mut checker = AuthZChecker::new(); + debug!("Authorization Scaner on Invokable FunctionTy: checking {func} at {path:?}"); + if let Err(err) = interp.run_checker(def, &mut checker, path.clone(), func.clone()) + { + warn!("error while scanning {func} in {path:?}: {err}"); + } + reporter.add_vulnerabilities(checker.into_vulns()); + + let mut checker2 = SecretChecker::new(); + secret_interp.value_manager.varid_to_value = defintion_analysis_interp.get_defs(); + secret_interp.value_manager.defid_to_value = defintion_analysis_interp + .value_manager + .defid_to_value + .clone(); + debug!("Secret Scanner on Invokable FunctionTy: checking {func} at {path:?}"); + if let Err(err) = + secret_interp.run_checker(def, &mut checker2, path.clone(), func.clone()) + { + warn!("error while scanning {func} in {path:?}: {err}"); + } + reporter.add_vulnerabilities(checker2.into_vulns()); + + debug!("Permission Scanners on Invokable FunctionTy: checking {func} at {path:?}"); + if run_permission_checker { + perm_interp.value_manager.varid_to_value = defintion_analysis_interp.get_defs(); + perm_interp.value_manager.defid_to_value = defintion_analysis_interp + .value_manager + .defid_to_value + .clone(); + let mut checker2 = PermissionChecker::new(); + if let Err(err) = + perm_interp.run_checker(def, &mut checker2, path.clone(), func.clone()) + { + warn!("error while scanning {func} in {path:?}: {err}"); + } + } + pp_interp.value_manager.varid_to_value = defintion_analysis_interp.get_defs(); + pp_interp.value_manager.defid_to_value = defintion_analysis_interp + .value_manager + .defid_to_value + .clone(); + if let Err(e) = pp_interp.run_checker( + def, + &mut PrototypePollutionChecker, + path.clone(), + func.clone(), + ) { + warn!("error while scanning {func} in {path:?}: {e}"); + } } - reporter.add_vulnerabilities(checker.into_vulns()); - } else if func.webtrigger { - let mut checker = AuthenticateChecker::new(); - debug!( - "checking webtrigger {:?} at {:?}", - func.func_name, func.path, - ); - if let Err(err) = authn_interp.run_checker( - func.def_id, - &mut checker, - func.path.clone(), - func.func_name.to_string(), - ) { - warn!( - "error while scanning {:?} in {:?}: {err}", - func.func_name, func.path, + FunctionTy::WebTrigger((ref func, ref path, _, def)) => { + let mut runner = DefintionAnalysisRunner::new(); + debug!("checking Web Trigger {func} at {path:?}"); + if let Err(err) = defintion_analysis_interp.run_checker( + def, + &mut runner, + path.clone(), + func.clone(), + ) { + warn!("error while getting definition analysis {func} in {path:?}: {err}"); + } + + let mut checker2 = SecretChecker::new(); + secret_interp.value_manager.varid_to_value = defintion_analysis_interp.get_defs(); + secret_interp.value_manager.defid_to_value = defintion_analysis_interp + .value_manager + .defid_to_value + .clone(); + debug!("Secret Scanner on Web Triggers: checking {func} at {path:?}"); + if let Err(err) = + secret_interp.run_checker(def, &mut checker2, path.clone(), func.clone()) + { + warn!("error while scanning {func} in {path:?}: {err}"); + } + reporter.add_vulnerabilities(checker2.into_vulns()); + + let mut checker = AuthenticateChecker::new(); + debug!("Authentication Checker on Web Triggers: checking webtrigger {func} at {path:?}"); + if let Err(err) = + authn_interp.run_checker(def, &mut checker, path.clone(), func.clone()) + { + warn!("error while scanning {func} in {path:?}: {err}"); + } + reporter.add_vulnerabilities(checker.into_vulns()); + + debug!( + "Permission Checker on Web Triggers: checking webtrigger {func} at {path:?}" ); + if run_permission_checker { + perm_interp.value_manager.varid_to_value = defintion_analysis_interp.get_defs(); + perm_interp.value_manager.defid_to_value = defintion_analysis_interp + .value_manager + .defid_to_value + .clone(); + let mut checker2 = PermissionChecker::new(); + if let Err(err) = + perm_interp.run_checker(def, &mut checker2, path.clone(), func.clone()) + { + warn!("error while scanning {func} in {path:?}: {err}"); + } + } } - reporter.add_vulnerabilities(checker.into_vulns()); } } - if perm_interp.permissions.len() > 0 { - reporter - .add_vulnerabilities(vec![PermissionVuln::new(perm_interp.permissions)].into_iter()); + if run_permission_checker { + if perm_interp.permissions.len() > 0 { + reporter.add_vulnerabilities( + vec![PermissionVuln::new(perm_interp.permissions)].into_iter(), + ); + } } let report = serde_json::to_string(&reporter.into_report()).into_diagnostic()?; debug!("On the debug layer: Writing Report"); From 05e15721f7dba4dfa4a1d8c1b3d9441bfb4ed177 Mon Sep 17 00:00:00 2001 From: Alina Wang Date: Mon, 8 Jan 2024 12:09:16 -0800 Subject: [PATCH 371/517] resolving merge conflicts for rebase --- crates/forge_loader/src/manifest.rs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/crates/forge_loader/src/manifest.rs b/crates/forge_loader/src/manifest.rs index 8ac5792a..b88cd2c3 100644 --- a/crates/forge_loader/src/manifest.rs +++ b/crates/forge_loader/src/manifest.rs @@ -27,7 +27,7 @@ pub struct FunctionMod<'a> { providers: Option>, } -// Abstracting away key, function, and resolver into a single struct for reuse whoo! +// Modified #[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] struct CommonKey<'a> { key: &'a str, @@ -106,7 +106,6 @@ pub struct DataProvider<'a> { // Struct for Custom field Module. Check that search suggestion gets read in correctly. #[derive(Debug, Clone, PartialEq, Eq, Deserialize)] pub struct CustomField<'a> { - // all attributes below involve function calls #[serde(flatten, borrow)] common_keys: CommonKey<'a>, value: Option<&'a str>, @@ -253,9 +252,9 @@ pub enum FunctionTy { } // Struct used for tracking what scan a funtion requires. -#[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] +#[derive(Debug, Clone, PartialEq, Eq, Deserialize)] pub struct Entrypoints<'a> { - function: Vec>, + function: &'a str, invokable: bool, web_trigger: bool, } From c85ca77e4722a264ede82450e2a29f1730e31114 Mon Sep 17 00:00:00 2001 From: Alina Wang Date: Mon, 8 Jan 2024 12:10:04 -0800 Subject: [PATCH 372/517] resolving merge conflicts --- crates/forge_loader/src/manifest.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/forge_loader/src/manifest.rs b/crates/forge_loader/src/manifest.rs index b88cd2c3..610e6fac 100644 --- a/crates/forge_loader/src/manifest.rs +++ b/crates/forge_loader/src/manifest.rs @@ -263,8 +263,8 @@ impl<'a> ForgeModules<'a> { // TODO: function returns iterator where each item is some specified type. pub fn into_analyzable_functions(mut self) -> impl Iterator> { // number of webtriggers are usually low, so it's better to just sort them and reuse - self.webtriggers - .sort_unstable_by_key(|trigger| trigger.function); + // self.webtriggers + // .sort_unstable_by_key(|trigger| trigger.function); // Get all the Triggers and represent them as a new struct thing where "webtrigger" attribute is true // for all trigger things From cab1b8debcbefa6925569d9f3d120e0ff9f1d62f Mon Sep 17 00:00:00 2001 From: Alina Wang Date: Mon, 8 Jan 2024 12:11:22 -0800 Subject: [PATCH 373/517] resolving merge conflicts --- crates/fsrt/src/main.rs | 51 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 51 insertions(+) diff --git a/crates/fsrt/src/main.rs b/crates/fsrt/src/main.rs index bb8e523e..f6b98ddc 100644 --- a/crates/fsrt/src/main.rs +++ b/crates/fsrt/src/main.rs @@ -12,6 +12,13 @@ use std::{ sync::Arc, }; +<<<<<<< HEAD +======= +use clap::{Parser, ValueHint}; +use miette::{IntoDiagnostic, Result}; + +use serde_json::map::Entry; +>>>>>>> cd2ed7b (edited main to iterate over vector of functions. TODO: edit add_funcs in main.rs and test into_analyzble_function use case in main.rs) use swc_core::{ common::{Globals, Mark, SourceMap, GLOBALS}, ecma::{ @@ -312,6 +319,7 @@ fn scan_directory(dir: PathBuf, function: Option<&str>, opts: &Args) -> Result<( for func in &proj.funcs { // TODO: Update operations in for loop to scan functions. // idea: iterate over each func which should be struct that tracks the function to be scanned. And performs scans according to bool. +<<<<<<< HEAD match *func { FunctionTy::Invokable((ref func, ref path, _, def)) => { let mut runner = DefintionAnalysisRunner::new(); @@ -425,7 +433,50 @@ fn scan_directory(dir: PathBuf, function: Option<&str>, opts: &Args) -> Result<( warn!("error while scanning {func} in {path:?}: {err}"); } } +======= + // match *func { + // FunctionTy::Invokable((ref func, ref path, _, def)) => { + // let mut checker = AuthZChecker::new(); + // debug!("checking {func} at {path:?}"); + // if let Err(err) = interp.run_checker(def, &mut checker, path.clone(), func.clone()) + // { + // warn!("error while scanning {func} in {path:?}: {err}"); + // } + // reporter.add_vulnerabilities(checker.into_vulns()); + // } + // FunctionTy::WebTrigger((ref func, ref path, _, def)) => { + // let mut checker = AuthenticateChecker::new(); + // debug!("checking webtrigger {func} at {path:?}"); + // if let Err(err) = + // authn_interp.run_checker(def, &mut checker, path.clone(), func.clone()) + // { + // warn!("error while scanning {func} in {path:?}: {err}"); + // } + // reporter.add_vulnerabilities(checker.into_vulns()); + // } + // } + + if func.invokable { + let mut checker = AuthZChecker::new(); + debug!("checking {func} at {path:?}"); + if let Err(err) = interp.run_checker(def, &mut checker, path.clone(), func.clone()) + { + warn!("error while scanning {func} in {path:?}: {err}"); + } + reporter.add_vulnerabilities(checker.into_vulns()); + + } else if func.web_trigger { + let mut checker = AuthenticateChecker::new(); + debug!("checking webtrigger {func} at {path:?}"); + if let Err(err) = + authn_interp.run_checker(def, &mut checker, path.clone(), func.clone()) + { + warn!("error while scanning {func} in {path:?}: {err}"); +>>>>>>> cd2ed7b (edited main to iterate over vector of functions. TODO: edit add_funcs in main.rs and test into_analyzble_function use case in main.rs) } + reporter.add_vulnerabilities(checker.into_vulns()); + + } } From 07bea429ca9c79c5d64b7fb0a06d48f1609939fd Mon Sep 17 00:00:00 2001 From: Alina Wang Date: Mon, 8 Jan 2024 12:12:59 -0800 Subject: [PATCH 374/517] resolving merge conflicts --- crates/forge_loader/src/manifest.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/crates/forge_loader/src/manifest.rs b/crates/forge_loader/src/manifest.rs index 610e6fac..eb135db3 100644 --- a/crates/forge_loader/src/manifest.rs +++ b/crates/forge_loader/src/manifest.rs @@ -115,6 +115,7 @@ pub struct CustomField<'a> { #[derive(Debug, Clone, PartialEq, Eq, Deserialize)] pub struct UiModificatons<'a> { + key: &'a str, #[serde(flatten, borrow)] common_keys: CommonKey<'a>, } From b229ad1b57ea70b0b514cd12f575d8e38e7ba548 Mon Sep 17 00:00:00 2001 From: Alina Wang Date: Mon, 18 Sep 2023 18:21:10 -0400 Subject: [PATCH 375/517] changed entrypoints to entrypoint. Working on other comments --- crates/forge_loader/src/manifest.rs | 4 ++-- crates/fsrt/src/main.rs | 13 ++++++------- 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/crates/forge_loader/src/manifest.rs b/crates/forge_loader/src/manifest.rs index eb135db3..b74971e9 100644 --- a/crates/forge_loader/src/manifest.rs +++ b/crates/forge_loader/src/manifest.rs @@ -264,8 +264,8 @@ impl<'a> ForgeModules<'a> { // TODO: function returns iterator where each item is some specified type. pub fn into_analyzable_functions(mut self) -> impl Iterator> { // number of webtriggers are usually low, so it's better to just sort them and reuse - // self.webtriggers - // .sort_unstable_by_key(|trigger| trigger.function); + self.webtriggers.iter(). + .sort_unstable_by_key(|trigger| trigger.function); // Get all the Triggers and represent them as a new struct thing where "webtrigger" attribute is true // for all trigger things diff --git a/crates/fsrt/src/main.rs b/crates/fsrt/src/main.rs index f6b98ddc..fa6ffcb5 100644 --- a/crates/fsrt/src/main.rs +++ b/crates/fsrt/src/main.rs @@ -458,21 +458,20 @@ fn scan_directory(dir: PathBuf, function: Option<&str>, opts: &Args) -> Result<( if func.invokable { let mut checker = AuthZChecker::new(); - debug!("checking {func} at {path:?}"); - if let Err(err) = interp.run_checker(def, &mut checker, path.clone(), func.clone()) + debug!("checking {:?} at {path:?}", func.function); + if let Err(err) = interp.run_checker(def, &mut checker, path.clone(), func.function.to_string()) { - warn!("error while scanning {func} in {path:?}: {err}"); + warn!("error while scanning {:?} in {path:?}: {err}", func.function); } reporter.add_vulnerabilities(checker.into_vulns()); } else if func.web_trigger { let mut checker = AuthenticateChecker::new(); - debug!("checking webtrigger {func} at {path:?}"); + debug!("checking webtrigger {:?} at {path:?}", func.function); if let Err(err) = - authn_interp.run_checker(def, &mut checker, path.clone(), func.clone()) + authn_interp.run_checker(def, &mut checker, path.clone(), func.function.to_string()) { - warn!("error while scanning {func} in {path:?}: {err}"); ->>>>>>> cd2ed7b (edited main to iterate over vector of functions. TODO: edit add_funcs in main.rs and test into_analyzble_function use case in main.rs) + warn!("error while scanning {:?} in {path:?}: {err}", func.function); } reporter.add_vulnerabilities(checker.into_vulns()); From 0475adb1ed427aba092e7b2d9767573aadf5ff3d Mon Sep 17 00:00:00 2001 From: Alina Wang Date: Mon, 8 Jan 2024 12:14:30 -0800 Subject: [PATCH 376/517] resolving merge conflicts --- crates/forge_loader/src/manifest.rs | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/crates/forge_loader/src/manifest.rs b/crates/forge_loader/src/manifest.rs index b74971e9..85eb597f 100644 --- a/crates/forge_loader/src/manifest.rs +++ b/crates/forge_loader/src/manifest.rs @@ -264,12 +264,7 @@ impl<'a> ForgeModules<'a> { // TODO: function returns iterator where each item is some specified type. pub fn into_analyzable_functions(mut self) -> impl Iterator> { // number of webtriggers are usually low, so it's better to just sort them and reuse - self.webtriggers.iter(). - .sort_unstable_by_key(|trigger| trigger.function); - - // Get all the Triggers and represent them as a new struct thing where "webtrigger" attribute is true - // for all trigger things - + self.webtriggers.iter().sort_unstable_by_key(|trigger| trigger.function); let mut invokable_functions = BTreeSet::new(); self.data_provider.iter().for_each(|dataprovider| { From 9fb82e8f9b122222a4b598f89903e93de8e91247 Mon Sep 17 00:00:00 2001 From: Alina Wang Date: Mon, 8 Jan 2024 12:15:17 -0800 Subject: [PATCH 377/517] resolving merge conflicts --- crates/forge_loader/src/manifest.rs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/crates/forge_loader/src/manifest.rs b/crates/forge_loader/src/manifest.rs index 85eb597f..e57d6c56 100644 --- a/crates/forge_loader/src/manifest.rs +++ b/crates/forge_loader/src/manifest.rs @@ -1,6 +1,6 @@ use std::{ borrow::Borrow, - collections::{BTreeSet, HashSet}, + collections::HashSet, hash::Hash, path::{Path, PathBuf}, sync::Arc, @@ -10,7 +10,7 @@ use crate::{forgepermissions::ForgePermissions, Error}; use forge_utils::FxHashMap; use itertools::{Either, Itertools}; use serde::Deserialize; -use serde_json::map::Entry; +use std::collections::BTreeMap; use tracing::trace; #[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] @@ -27,7 +27,7 @@ pub struct FunctionMod<'a> { providers: Option>, } -// Modified +// Abstracting away key, function, and resolver into a single struct for reuse whoo! #[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] struct CommonKey<'a> { key: &'a str, @@ -115,7 +115,6 @@ pub struct CustomField<'a> { #[derive(Debug, Clone, PartialEq, Eq, Deserialize)] pub struct UiModificatons<'a> { - key: &'a str, #[serde(flatten, borrow)] common_keys: CommonKey<'a>, } From 7572b164ffefe34615583b2c6a0cbd1243fd0653 Mon Sep 17 00:00:00 2001 From: Alina Wang Date: Wed, 20 Sep 2023 17:59:39 -0400 Subject: [PATCH 378/517] finished updating methods to check functions_to_scan and update entrypoint struct when needed. TODO: test and toggle function call in main.rs --- crates/fsrt/src/main.rs | 249 ++++++++++++++-------------------------- 1 file changed, 85 insertions(+), 164 deletions(-) diff --git a/crates/fsrt/src/main.rs b/crates/fsrt/src/main.rs index fa6ffcb5..06693c45 100644 --- a/crates/fsrt/src/main.rs +++ b/crates/fsrt/src/main.rs @@ -12,13 +12,6 @@ use std::{ sync::Arc, }; -<<<<<<< HEAD -======= -use clap::{Parser, ValueHint}; -use miette::{IntoDiagnostic, Result}; - -use serde_json::map::Entry; ->>>>>>> cd2ed7b (edited main to iterate over vector of functions. TODO: edit add_funcs in main.rs and test into_analyzble_function use case in main.rs) use swc_core::{ common::{Globals, Mark, SourceMap, GLOBALS}, ecma::{ @@ -83,7 +76,7 @@ struct Args { /// The directory to scan. Assumes there is a `manifest.ya?ml` file in the top level /// directory, and that the source code is located in `src/` - #[arg(name = "DIRS", default_values_os_t = std::env::current_dir(), value_hint = ValueHint::DirPath)] + #[arg(name = "DIRS", value_hint = ValueHint::DirPath)] dirs: Vec, } @@ -317,174 +310,102 @@ fn scan_directory(dir: PathBuf, function: Option<&str>, opts: &Args) -> Result<( &confluence_regex_map, ); for func in &proj.funcs { - // TODO: Update operations in for loop to scan functions. - // idea: iterate over each func which should be struct that tracks the function to be scanned. And performs scans according to bool. -<<<<<<< HEAD - match *func { - FunctionTy::Invokable((ref func, ref path, _, def)) => { - let mut runner = DefintionAnalysisRunner::new(); - debug!("checking Invokable {func} at {path:?}"); - if let Err(err) = defintion_analysis_interp.run_checker( - def, - &mut runner, - path.clone(), - func.clone(), - ) { - warn!("error while getting definition analysis {func} in {path:?}: {err}"); - } - let mut checker = AuthZChecker::new(); - debug!("Authorization Scaner on Invokable FunctionTy: checking {func} at {path:?}"); - if let Err(err) = interp.run_checker(def, &mut checker, path.clone(), func.clone()) - { - warn!("error while scanning {func} in {path:?}: {err}"); - } - reporter.add_vulnerabilities(checker.into_vulns()); - - let mut checker2 = SecretChecker::new(); - secret_interp.value_manager.varid_to_value = defintion_analysis_interp.get_defs(); - secret_interp.value_manager.defid_to_value = defintion_analysis_interp - .value_manager - .defid_to_value - .clone(); - debug!("Secret Scanner on Invokable FunctionTy: checking {func} at {path:?}"); - if let Err(err) = - secret_interp.run_checker(def, &mut checker2, path.clone(), func.clone()) - { - warn!("error while scanning {func} in {path:?}: {err}"); - } - reporter.add_vulnerabilities(checker2.into_vulns()); - - debug!("Permission Scanners on Invokable FunctionTy: checking {func} at {path:?}"); - if run_permission_checker { - perm_interp.value_manager.varid_to_value = defintion_analysis_interp.get_defs(); - perm_interp.value_manager.defid_to_value = defintion_analysis_interp - .value_manager - .defid_to_value - .clone(); - let mut checker2 = PermissionChecker::new(); - if let Err(err) = - perm_interp.run_checker(def, &mut checker2, path.clone(), func.clone()) - { - warn!("error while scanning {func} in {path:?}: {err}"); - } - } - pp_interp.value_manager.varid_to_value = defintion_analysis_interp.get_defs(); - pp_interp.value_manager.defid_to_value = defintion_analysis_interp - .value_manager - .defid_to_value - .clone(); - if let Err(e) = pp_interp.run_checker( - def, - &mut PrototypePollutionChecker, - path.clone(), - func.clone(), - ) { - warn!("error while scanning {func} in {path:?}: {e}"); - } + let mut def_checker = DefintionAnalysisRunner::new(); + if let Err(err) = definition_analysis_interp.run_checker( + func.def_id, + &mut def_checker, + func.path.clone(), + func.func_name.to_string(), + ) { + warn!( + "error while scanning {:?} in {:?}: {err}", + func.func_name, func.path, + ); + } + + if run_permission_checker { + let mut checker = PermissionChecker::new(); + perm_interp.value_manager.varid_to_value = + definition_analysis_interp.value_manager.varid_to_value; + perm_interp.value_manager.defid_to_value = + definition_analysis_interp.value_manager.defid_to_value; + if let Err(err) = perm_interp.run_checker( + func.def_id, + &mut checker, + func.path.clone(), + func.func_name.to_owned(), + ) { + warn!("error while running permission checker: {err}"); } - FunctionTy::WebTrigger((ref func, ref path, _, def)) => { - let mut runner = DefintionAnalysisRunner::new(); - debug!("checking Web Trigger {func} at {path:?}"); - if let Err(err) = defintion_analysis_interp.run_checker( - def, - &mut runner, - path.clone(), - func.clone(), - ) { - warn!("error while getting definition analysis {func} in {path:?}: {err}"); - } - - let mut checker2 = SecretChecker::new(); - secret_interp.value_manager.varid_to_value = defintion_analysis_interp.get_defs(); - secret_interp.value_manager.defid_to_value = defintion_analysis_interp - .value_manager - .defid_to_value - .clone(); - debug!("Secret Scanner on Web Triggers: checking {func} at {path:?}"); - if let Err(err) = - secret_interp.run_checker(def, &mut checker2, path.clone(), func.clone()) - { - warn!("error while scanning {func} in {path:?}: {err}"); - } - reporter.add_vulnerabilities(checker2.into_vulns()); - - let mut checker = AuthenticateChecker::new(); - debug!("Authentication Checker on Web Triggers: checking webtrigger {func} at {path:?}"); - if let Err(err) = - authn_interp.run_checker(def, &mut checker, path.clone(), func.clone()) - { - warn!("error while scanning {func} in {path:?}: {err}"); - } - reporter.add_vulnerabilities(checker.into_vulns()); - - debug!( - "Permission Checker on Web Triggers: checking webtrigger {func} at {path:?}" - ); - if run_permission_checker { - perm_interp.value_manager.varid_to_value = defintion_analysis_interp.get_defs(); - perm_interp.value_manager.defid_to_value = defintion_analysis_interp - .value_manager - .defid_to_value - .clone(); - let mut checker2 = PermissionChecker::new(); - if let Err(err) = - perm_interp.run_checker(def, &mut checker2, path.clone(), func.clone()) - { - warn!("error while scanning {func} in {path:?}: {err}"); - } - } -======= - // match *func { - // FunctionTy::Invokable((ref func, ref path, _, def)) => { - // let mut checker = AuthZChecker::new(); - // debug!("checking {func} at {path:?}"); - // if let Err(err) = interp.run_checker(def, &mut checker, path.clone(), func.clone()) - // { - // warn!("error while scanning {func} in {path:?}: {err}"); - // } - // reporter.add_vulnerabilities(checker.into_vulns()); - // } - // FunctionTy::WebTrigger((ref func, ref path, _, def)) => { - // let mut checker = AuthenticateChecker::new(); - // debug!("checking webtrigger {func} at {path:?}"); - // if let Err(err) = - // authn_interp.run_checker(def, &mut checker, path.clone(), func.clone()) - // { - // warn!("error while scanning {func} in {path:?}: {err}"); - // } - // reporter.add_vulnerabilities(checker.into_vulns()); - // } - // } + definition_analysis_interp.value_manager.varid_to_value = + perm_interp.value_manager.varid_to_value; + definition_analysis_interp.value_manager.defid_to_value = + perm_interp.value_manager.defid_to_value; + } + let mut checker = SecretChecker::new(); + secret_interp.value_manager.varid_to_value = + definition_analysis_interp.value_manager.varid_to_value; + secret_interp.value_manager.defid_to_value = + definition_analysis_interp.value_manager.defid_to_value; + if let Err(err) = secret_interp.run_checker( + func.def_id, + &mut checker, + func.path.clone(), + func.func_name.to_owned(), + ) { + warn!("error while running secret checker: {err}"); + } else { + reporter.add_vulnerabilities(checker.into_vulns()); + } + definition_analysis_interp.value_manager.varid_to_value = + secret_interp.value_manager.varid_to_value; + definition_analysis_interp.value_manager.defid_to_value = + secret_interp.value_manager.defid_to_value; + + // Get entrypoint value from tuple + // Logic for performing scans. + // If it's invokable, then run invokable scan. If web_trigger, then trigger scan. + // And if it's both, run both scans. if func.invokable { let mut checker = AuthZChecker::new(); - debug!("checking {:?} at {path:?}", func.function); - if let Err(err) = interp.run_checker(def, &mut checker, path.clone(), func.function.to_string()) - { - warn!("error while scanning {:?} in {path:?}: {err}", func.function); + debug!("checking {:?} at {:?}", func.func_name, &func.path); + if let Err(err) = interp.run_checker( + func.def_id, + &mut checker, + func.path.clone(), + func.func_name.to_string(), + ) { + warn!( + "error while scanning {:?} in {:?}: {err}", + func.func_name, func.path, + ); } reporter.add_vulnerabilities(checker.into_vulns()); - - } else if func.web_trigger { + } else if func.webtrigger { let mut checker = AuthenticateChecker::new(); - debug!("checking webtrigger {:?} at {path:?}", func.function); - if let Err(err) = - authn_interp.run_checker(def, &mut checker, path.clone(), func.function.to_string()) - { - warn!("error while scanning {:?} in {path:?}: {err}", func.function); + debug!( + "checking webtrigger {:?} at {:?}", + func.func_name, func.path, + ); + if let Err(err) = authn_interp.run_checker( + func.def_id, + &mut checker, + func.path.clone(), + func.func_name.to_string(), + ) { + warn!( + "error while scanning {:?} in {:?}: {err}", + func.func_name, func.path, + ); } reporter.add_vulnerabilities(checker.into_vulns()); - - } } - if run_permission_checker { - if perm_interp.permissions.len() > 0 { - reporter.add_vulnerabilities( - vec![PermissionVuln::new(perm_interp.permissions)].into_iter(), - ); - } + if perm_interp.permissions.len() > 0 { + reporter + .add_vulnerabilities(vec![PermissionVuln::new(perm_interp.permissions)].into_iter()); } let report = serde_json::to_string(&reporter.into_report()).into_diagnostic()?; debug!("On the debug layer: Writing Report"); From de569cb884db94f0f07d7407ee74db7aad39d222 Mon Sep 17 00:00:00 2001 From: Alina Wang Date: Fri, 9 Feb 2024 16:35:36 -0500 Subject: [PATCH 379/517] manually resolved some thigns --- crates/forge_loader/src/manifest.rs | 33 ++++++----------------------- crates/fsrt/src/main.rs | 11 +++++++++- 2 files changed, 16 insertions(+), 28 deletions(-) diff --git a/crates/forge_loader/src/manifest.rs b/crates/forge_loader/src/manifest.rs index e57d6c56..d1015883 100644 --- a/crates/forge_loader/src/manifest.rs +++ b/crates/forge_loader/src/manifest.rs @@ -1,6 +1,6 @@ use std::{ borrow::Borrow, - collections::HashSet, + collections::{BTreeSet, HashSet}, hash::Hash, path::{Path, PathBuf}, sync::Arc, @@ -10,7 +10,7 @@ use crate::{forgepermissions::ForgePermissions, Error}; use forge_utils::FxHashMap; use itertools::{Either, Itertools}; use serde::Deserialize; -use std::collections::BTreeMap; +use serde_json::map::Entry; use tracing::trace; #[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] @@ -253,7 +253,7 @@ pub enum FunctionTy { // Struct used for tracking what scan a funtion requires. #[derive(Debug, Clone, PartialEq, Eq, Deserialize)] -pub struct Entrypoints<'a> { +pub struct Entrypoint<'a> { function: &'a str, invokable: bool, web_trigger: bool, @@ -263,7 +263,8 @@ impl<'a> ForgeModules<'a> { // TODO: function returns iterator where each item is some specified type. pub fn into_analyzable_functions(mut self) -> impl Iterator> { // number of webtriggers are usually low, so it's better to just sort them and reuse - self.webtriggers.iter().sort_unstable_by_key(|trigger| trigger.function); + self.webtriggers + .sort_unstable_by_key(|trigger| trigger.function); let mut invokable_functions = BTreeSet::new(); self.data_provider.iter().for_each(|dataprovider| { @@ -342,28 +343,6 @@ impl<'a> ForgeModules<'a> { web_trigger, }) }); - self.access_import_type.iter().for_each(|access| { - invokable_functions.insert(access.common_keys.function); - invokable_functions.extend(access.common_keys.resolver); - - invokable_functions.extend(access.one_delete_import); - invokable_functions.extend(access.stop_import); - invokable_functions.extend(access.start_import); - invokable_functions.extend(access.import_status); - }); - - self.functions.into_iter().flat_map(move |func| { - let web_trigger = self - .webtriggers - .binary_search_by_key(&func.key, |trigger| &trigger.function) - .is_ok(); - let invokable = invokable_functions.contains(func.key); - Ok::<_, Error>(Entrypoint { - function: FunctionRef::try_from(func)?, - invokable, - web_trigger, - }) - }) } } @@ -589,4 +568,4 @@ mod tests { assert_eq!(func, "Catch-me-if-you-can3"); } } -}} +} diff --git a/crates/fsrt/src/main.rs b/crates/fsrt/src/main.rs index 06693c45..ac169697 100644 --- a/crates/fsrt/src/main.rs +++ b/crates/fsrt/src/main.rs @@ -90,6 +90,16 @@ struct ResolvedEntryPoint<'a> { invokable: bool, } +#[derive(Debug, Clone)] +struct ResolvedEntryPoint<'a> { + func_name: &'a str, + path: PathBuf, + module: ModId, + def_id: DefId, + webtrigger: bool, + invokable: bool, +} + struct ForgeProject<'a> { #[allow(dead_code)] sm: Arc, @@ -143,7 +153,6 @@ impl<'a> ForgeProject<'a> { ctx, env, funcs: vec![], - opts: Opts::default(), } } // TODO: edit to work with new iterator that not FUNCTIONTY From 9bb3a76fb833bcd377d88e5c9ba01350af427955 Mon Sep 17 00:00:00 2001 From: Alina Wang Date: Mon, 11 Sep 2023 15:01:45 -0400 Subject: [PATCH 380/517] added new structs for user non-invokable modules --- crates/forge_loader/src/manifest.rs | 3 --- 1 file changed, 3 deletions(-) diff --git a/crates/forge_loader/src/manifest.rs b/crates/forge_loader/src/manifest.rs index d1015883..94dad11d 100644 --- a/crates/forge_loader/src/manifest.rs +++ b/crates/forge_loader/src/manifest.rs @@ -242,9 +242,6 @@ pub struct FunctionRef<'a, S = Unresolved> { status: S, } -// Add an extra variant to the FunctionTy enum for non user invocable functions -// Indirect: functions indirectly invoked by user :O So kewl. -// TODO: change this to struct with bools #[derive(Debug, Clone, PartialEq, Eq)] pub enum FunctionTy { Invokable(T), From 4738a804e39f68a49ddebea6b2067971e62128d6 Mon Sep 17 00:00:00 2001 From: Alina Wang Date: Mon, 11 Sep 2023 15:50:57 -0400 Subject: [PATCH 381/517] added more todo statements and cleaned up struct definitions --- crates/forge_loader/src/manifest.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/crates/forge_loader/src/manifest.rs b/crates/forge_loader/src/manifest.rs index 94dad11d..8379391d 100644 --- a/crates/forge_loader/src/manifest.rs +++ b/crates/forge_loader/src/manifest.rs @@ -66,12 +66,14 @@ struct AccessImportType<'a> { import_status: Option<&'a str>, } +// WebTrigger => RawTrigger; WHY IS THIS NAMED DIFFERENTLY !? WHO CHANGED NAMES #[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] struct RawTrigger<'a> { key: &'a str, function: &'a str, } +// Trigger => EventTriger; WHY IS THIS NAMED DIFFERENTLY !? WHO CHANGED NAMES #[derive(Debug, Clone, PartialEq, Eq, Deserialize)] struct EventTrigger<'a> { #[serde(flatten, borrow)] @@ -88,6 +90,7 @@ enum Interval { Week, } +// Thank you to whomeever kept this one the same. T.T #[derive(Debug, Clone, PartialEq, Eq, Deserialize)] struct ScheduledTrigger<'a> { #[serde(flatten, borrow)] From dad24aac642be9b35187f7e21ee67d2a3d5b0f8c Mon Sep 17 00:00:00 2001 From: Alina Wang Date: Fri, 15 Sep 2023 17:20:10 -0400 Subject: [PATCH 382/517] edited main to iterate over vector of functions. TODO: edit add_funcs in main.rs and test into_analyzble_function use case in main.rs --- crates/fsrt/src/main.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/crates/fsrt/src/main.rs b/crates/fsrt/src/main.rs index ac169697..d106046a 100644 --- a/crates/fsrt/src/main.rs +++ b/crates/fsrt/src/main.rs @@ -1,9 +1,11 @@ #![allow(clippy::type_complexity)] use clap::{Parser, ValueHint}; +use clap::{Parser, ValueHint}; use forge_permission_resolver::permissions_resolver::{ get_permission_resolver_confluence, get_permission_resolver_jira, }; use miette::{IntoDiagnostic, Result}; +use miette::{IntoDiagnostic, Result}; use std::{ collections::HashSet, fs, From c7d2a3e38d1b57dfd1b32bc2159f4ea0e5bd87ca Mon Sep 17 00:00:00 2001 From: Alina Wang Date: Fri, 9 Feb 2024 16:46:34 -0500 Subject: [PATCH 383/517] resolving more confliicts T.T' --- crates/forge_loader/src/manifest.rs | 112 +++++++++++++++++++--------- 1 file changed, 75 insertions(+), 37 deletions(-) diff --git a/crates/forge_loader/src/manifest.rs b/crates/forge_loader/src/manifest.rs index 8379391d..f9cb44da 100644 --- a/crates/forge_loader/src/manifest.rs +++ b/crates/forge_loader/src/manifest.rs @@ -253,7 +253,7 @@ pub enum FunctionTy { // Struct used for tracking what scan a funtion requires. #[derive(Debug, Clone, PartialEq, Eq, Deserialize)] -pub struct Entrypoint<'a> { +pub struct Entrypoints<'a> { function: &'a str, invokable: bool, web_trigger: bool, @@ -261,7 +261,7 @@ pub struct Entrypoint<'a> { impl<'a> ForgeModules<'a> { // TODO: function returns iterator where each item is some specified type. - pub fn into_analyzable_functions(mut self) -> impl Iterator> { + pub fn into_analyzable_functions(self) { // number of webtriggers are usually low, so it's better to just sort them and reuse self.webtriggers .sort_unstable_by_key(|trigger| trigger.function); @@ -305,44 +305,82 @@ impl<'a> ForgeModules<'a> { // get user invokable modules that have additional exposure endpoints. // ie macros has config and export fields on top of resolver fields that are functions - self.macros.iter().for_each(|macros| { - invokable_functions.insert(macros.common_keys.key); - - invokable_functions.insert(macros.common_keys.function); - invokable_functions.extend(macros.common_keys.resolver); - - invokable_functions.extend(macros.config); - invokable_functions.extend(macros.export); - }); - - self.issue_glance.iter().for_each(|issue| { - invokable_functions.insert(issue.common_keys.function); - invokable_functions.extend(issue.common_keys.resolver); - invokable_functions.extend(issue.dynamic_properties); - }); + for macros in self.macros { + if let Some(resolver) = macros.resolver { + functions_to_scan.push(Entrypoints { + function: resolver.function, + invokable: true, + web_trigger: false, + }) + } - self.access_import_type.iter().for_each(|access| { - invokable_functions.insert(access.common_keys.function); - invokable_functions.extend(access.common_keys.resolver); + if let Some(config) = macros.config { + functions_to_scan.push(Entrypoints { + function: config.function, + invokable: true, + web_trigger: false, + }) + } + if let Some(export) = macros.export { + functions_to_scan.push(Entrypoints { + function: export.function, + invokable: true, + web_trigger: false, + }) + } + } - invokable_functions.extend(access.one_delete_import); - invokable_functions.extend(access.stop_import); - invokable_functions.extend(access.start_import); - invokable_functions.extend(access.import_status); - }); + // get array for user invokable module functions + // make alternate_functions all user-invokable functions + // let mut alternate_functions = Vec::new(); + for module in self.extra.into_values().flatten() { + if let Some(mod_function) = module.function { + functions_to_scan.push(Entrypoints { + function: mod_function, + invokable: true, + web_trigger: false, + }); + } - self.functions.into_iter().flat_map(move |func| { - let web_trigger = self - .webtriggers - .binary_search_by_key(&func.key, |trigger| &trigger.function) - .is_ok(); - let invokable = invokable_functions.contains(func.key); - Ok::<_, Error>(Entrypoint { - function: FunctionRef::try_from(func)?, - invokable, - web_trigger, - }) - }); + if let Some(resolver) = module.resolver { + functions_to_scan.push(Entrypoints { + function: resolver.function, + invokable: true, + web_trigger: false, + }); + } + } + functions_to_scan.into_iter(); + // alternate_functions.into_iter(); + // Iterate over Consumers and check that if consumers isn't in alternate functions, add consumer funtion to be ignored + // assuming that alternate functions already has all user invokable functions. + // self.consumers.iter().for_each(|consumer| { + // if !alternate_functions.contains(&consumer.resolver.function) { + // ignored_functions.insert(consumer.resolver.function); + // } + // }); + + // TODO: Iterate through all deserialized entrypoints that are represented as a struct when deserialized + // Update Struct values to be true or not. If any part true, then scan. + // This solution fixes the problem that we only check known user invokable modules and also acccounts for non-invokable module entry points + + // return non-user invokable functions + // self.functions.into_iter().filter_map(move |func| { + // if ignored_functions.contains(&func.key) { + // return None; + // } + // Some( + // if self + // .webtriggers + // .binary_search_by_key(&func.key, |trigger| trigger.function) + // .is_ok() + // { + // FunctionTy::WebTrigger(func) + // } else { + // FunctionTy::Invokable(func) + // }, + // ) + // }) } } From aa6893dab5ab18070eeea2baac7c92797ad71404 Mon Sep 17 00:00:00 2001 From: Alina Wang Date: Fri, 9 Feb 2024 16:51:43 -0500 Subject: [PATCH 384/517] resolving more confliicts T.T' --- crates/forge_loader/src/manifest.rs | 110 +++++++++------------------- 1 file changed, 36 insertions(+), 74 deletions(-) diff --git a/crates/forge_loader/src/manifest.rs b/crates/forge_loader/src/manifest.rs index f9cb44da..6a301079 100644 --- a/crates/forge_loader/src/manifest.rs +++ b/crates/forge_loader/src/manifest.rs @@ -253,7 +253,7 @@ pub enum FunctionTy { // Struct used for tracking what scan a funtion requires. #[derive(Debug, Clone, PartialEq, Eq, Deserialize)] -pub struct Entrypoints<'a> { +pub struct Entrypoint<'a> { function: &'a str, invokable: bool, web_trigger: bool, @@ -305,82 +305,44 @@ impl<'a> ForgeModules<'a> { // get user invokable modules that have additional exposure endpoints. // ie macros has config and export fields on top of resolver fields that are functions - for macros in self.macros { - if let Some(resolver) = macros.resolver { - functions_to_scan.push(Entrypoints { - function: resolver.function, - invokable: true, - web_trigger: false, - }) - } + self.macros.iter().for_each(|macros| { + invokable_functions.insert(macros.common_keys.key); - if let Some(config) = macros.config { - functions_to_scan.push(Entrypoints { - function: config.function, - invokable: true, - web_trigger: false, - }) - } - if let Some(export) = macros.export { - functions_to_scan.push(Entrypoints { - function: export.function, - invokable: true, - web_trigger: false, - }) - } - } + invokable_functions.insert(macros.common_keys.function); + invokable_functions.extend(macros.common_keys.resolver); - // get array for user invokable module functions - // make alternate_functions all user-invokable functions - // let mut alternate_functions = Vec::new(); - for module in self.extra.into_values().flatten() { - if let Some(mod_function) = module.function { - functions_to_scan.push(Entrypoints { - function: mod_function, - invokable: true, - web_trigger: false, - }); - } + invokable_functions.extend(macros.config); + invokable_functions.extend(macros.export); + }); - if let Some(resolver) = module.resolver { - functions_to_scan.push(Entrypoints { - function: resolver.function, - invokable: true, - web_trigger: false, - }); - } - } - functions_to_scan.into_iter(); - // alternate_functions.into_iter(); - // Iterate over Consumers and check that if consumers isn't in alternate functions, add consumer funtion to be ignored - // assuming that alternate functions already has all user invokable functions. - // self.consumers.iter().for_each(|consumer| { - // if !alternate_functions.contains(&consumer.resolver.function) { - // ignored_functions.insert(consumer.resolver.function); - // } - // }); - - // TODO: Iterate through all deserialized entrypoints that are represented as a struct when deserialized - // Update Struct values to be true or not. If any part true, then scan. - // This solution fixes the problem that we only check known user invokable modules and also acccounts for non-invokable module entry points - - // return non-user invokable functions - // self.functions.into_iter().filter_map(move |func| { - // if ignored_functions.contains(&func.key) { - // return None; - // } - // Some( - // if self - // .webtriggers - // .binary_search_by_key(&func.key, |trigger| trigger.function) - // .is_ok() - // { - // FunctionTy::WebTrigger(func) - // } else { - // FunctionTy::Invokable(func) - // }, - // ) - // }) + self.issue_glance.iter().for_each(|issue| { + invokable_functions.insert(issue.common_keys.function); + invokable_functions.extend(issue.common_keys.resolver); + invokable_functions.extend(issue.dynamic_properties); + }); + + self.access_import_type.iter().for_each(|access| { + invokable_functions.insert(access.common_keys.function); + invokable_functions.extend(access.common_keys.resolver); + + invokable_functions.extend(access.one_delete_import); + invokable_functions.extend(access.stop_import); + invokable_functions.extend(access.start_import); + invokable_functions.extend(access.import_status); + }); + + self.functions.into_iter().flat_map(move |func| { + let web_trigger = self + .webtriggers + .binary_search_by_key(&func.key, |trigger| &trigger.function) + .is_ok(); + let invokable = invokable_functions.contains(func.key); + Ok::<_, Error>(Entrypoint { + function: FunctionRef::try_from(func)?, + invokable, + web_trigger, + }) + }); } } From fe30d12cbffe0f36a1baeb1abcda3ceb53e2c9eb Mon Sep 17 00:00:00 2001 From: Alina Wang Date: Mon, 18 Sep 2023 11:50:09 -0400 Subject: [PATCH 385/517] removed callback mod and abstracted structs to use MacroMod to represent {function: str} entry points. Added missing entrypoints on jira:customField and implmented scanning for those --- crates/forge_loader/src/manifest.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/crates/forge_loader/src/manifest.rs b/crates/forge_loader/src/manifest.rs index 6a301079..530e4a3f 100644 --- a/crates/forge_loader/src/manifest.rs +++ b/crates/forge_loader/src/manifest.rs @@ -118,6 +118,7 @@ pub struct CustomField<'a> { #[derive(Debug, Clone, PartialEq, Eq, Deserialize)] pub struct UiModificatons<'a> { + key: &'a str, #[serde(flatten, borrow)] common_keys: CommonKey<'a>, } From 0eba6206ab2102e77962418fd59b420c5f5d25a2 Mon Sep 17 00:00:00 2001 From: Alina Wang Date: Tue, 19 Sep 2023 11:42:46 -0400 Subject: [PATCH 386/517] updated struct serde features and rust attributes. Updated customField with optional values --- crates/forge_loader/src/manifest.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/forge_loader/src/manifest.rs b/crates/forge_loader/src/manifest.rs index 530e4a3f..2f96ebde 100644 --- a/crates/forge_loader/src/manifest.rs +++ b/crates/forge_loader/src/manifest.rs @@ -253,7 +253,7 @@ pub enum FunctionTy { } // Struct used for tracking what scan a funtion requires. -#[derive(Debug, Clone, PartialEq, Eq, Deserialize)] +#[derive(Debug, Clone, PartialEq, Eq)] pub struct Entrypoint<'a> { function: &'a str, invokable: bool, From d13df576ce36ab282dda31f567b4617ef0f7e934 Mon Sep 17 00:00:00 2001 From: Alina Wang Date: Wed, 20 Sep 2023 12:01:46 -0400 Subject: [PATCH 387/517] abstracted structs to use a CommonKeys struct that holds: key, function, and resolver for less code duplication. Updated into_analayzable method to update values based on functions specified in function mod. TODO: Update rest of non trigger modules to update mapping to functions to scan from function mod --- crates/forge_loader/src/manifest.rs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/crates/forge_loader/src/manifest.rs b/crates/forge_loader/src/manifest.rs index 2f96ebde..fbb5a4d8 100644 --- a/crates/forge_loader/src/manifest.rs +++ b/crates/forge_loader/src/manifest.rs @@ -1,6 +1,6 @@ use std::{ borrow::Borrow, - collections::{BTreeSet, HashSet}, + collections::HashSet, hash::Hash, path::{Path, PathBuf}, sync::Arc, @@ -10,7 +10,7 @@ use crate::{forgepermissions::ForgePermissions, Error}; use forge_utils::FxHashMap; use itertools::{Either, Itertools}; use serde::Deserialize; -use serde_json::map::Entry; +use std::collections::BTreeMap; use tracing::trace; #[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] @@ -118,7 +118,6 @@ pub struct CustomField<'a> { #[derive(Debug, Clone, PartialEq, Eq, Deserialize)] pub struct UiModificatons<'a> { - key: &'a str, #[serde(flatten, borrow)] common_keys: CommonKey<'a>, } From f9a282c956b412eb48ed917b866b79aae35b699c Mon Sep 17 00:00:00 2001 From: Alina Wang Date: Thu, 5 Oct 2023 18:06:29 -0400 Subject: [PATCH 388/517] modified into_analyzable_functions with Josh and implementation in main.rs --- crates/forge_loader/src/manifest.rs | 2 +- crates/fsrt/src/main.rs | 92 ++++++++--------------------- 2 files changed, 26 insertions(+), 68 deletions(-) diff --git a/crates/forge_loader/src/manifest.rs b/crates/forge_loader/src/manifest.rs index fbb5a4d8..e50a86e1 100644 --- a/crates/forge_loader/src/manifest.rs +++ b/crates/forge_loader/src/manifest.rs @@ -10,7 +10,7 @@ use crate::{forgepermissions::ForgePermissions, Error}; use forge_utils::FxHashMap; use itertools::{Either, Itertools}; use serde::Deserialize; -use std::collections::BTreeMap; +use serde_json::map::Entry; use tracing::trace; #[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] diff --git a/crates/fsrt/src/main.rs b/crates/fsrt/src/main.rs index d106046a..f935a918 100644 --- a/crates/fsrt/src/main.rs +++ b/crates/fsrt/src/main.rs @@ -92,22 +92,13 @@ struct ResolvedEntryPoint<'a> { invokable: bool, } -#[derive(Debug, Clone)] -struct ResolvedEntryPoint<'a> { - func_name: &'a str, - path: PathBuf, - module: ModId, - def_id: DefId, - webtrigger: bool, - invokable: bool, -} - struct ForgeProject<'a> { #[allow(dead_code)] sm: Arc, ctx: AppCtx, env: Environment, - funcs: Vec>, + funcs: BTreeMap<&'a str, Entrypoint<'a>>, + opts: Opts, } impl<'a> ForgeProject<'a> { @@ -321,63 +312,30 @@ fn scan_directory(dir: PathBuf, function: Option<&str>, opts: &Args) -> Result<( &confluence_regex_map, ); for func in &proj.funcs { - let mut def_checker = DefintionAnalysisRunner::new(); - if let Err(err) = definition_analysis_interp.run_checker( - func.def_id, - &mut def_checker, - func.path.clone(), - func.func_name.to_string(), - ) { - warn!( - "error while scanning {:?} in {:?}: {err}", - func.func_name, func.path, - ); - } + // TODO: Update operations in for loop to scan functions. + // idea: iterate over each func which should be struct that tracks the function to be scanned. And performs scans according to bool. + // match *func { + // FunctionTy::Invokable((ref func, ref path, _, def)) => { + // let mut checker = AuthZChecker::new(); + // debug!("checking {func} at {path:?}"); + // if let Err(err) = interp.run_checker(def, &mut checker, path.clone(), func.clone()) + // { + // warn!("error while scanning {func} in {path:?}: {err}"); + // } + // reporter.add_vulnerabilities(checker.into_vulns()); + // } + // FunctionTy::WebTrigger((ref func, ref path, _, def)) => { + // let mut checker = AuthenticateChecker::new(); + // debug!("checking webtrigger {func} at {path:?}"); + // if let Err(err) = + // authn_interp.run_checker(def, &mut checker, path.clone(), func.clone()) + // { + // warn!("error while scanning {func} in {path:?}: {err}"); + // } + // reporter.add_vulnerabilities(checker.into_vulns()); + // } + // } - if run_permission_checker { - let mut checker = PermissionChecker::new(); - perm_interp.value_manager.varid_to_value = - definition_analysis_interp.value_manager.varid_to_value; - perm_interp.value_manager.defid_to_value = - definition_analysis_interp.value_manager.defid_to_value; - if let Err(err) = perm_interp.run_checker( - func.def_id, - &mut checker, - func.path.clone(), - func.func_name.to_owned(), - ) { - warn!("error while running permission checker: {err}"); - } - definition_analysis_interp.value_manager.varid_to_value = - perm_interp.value_manager.varid_to_value; - definition_analysis_interp.value_manager.defid_to_value = - perm_interp.value_manager.defid_to_value; - } - - let mut checker = SecretChecker::new(); - secret_interp.value_manager.varid_to_value = - definition_analysis_interp.value_manager.varid_to_value; - secret_interp.value_manager.defid_to_value = - definition_analysis_interp.value_manager.defid_to_value; - if let Err(err) = secret_interp.run_checker( - func.def_id, - &mut checker, - func.path.clone(), - func.func_name.to_owned(), - ) { - warn!("error while running secret checker: {err}"); - } else { - reporter.add_vulnerabilities(checker.into_vulns()); - } - definition_analysis_interp.value_manager.varid_to_value = - secret_interp.value_manager.varid_to_value; - definition_analysis_interp.value_manager.defid_to_value = - secret_interp.value_manager.defid_to_value; - - // Get entrypoint value from tuple - // Logic for performing scans. - // If it's invokable, then run invokable scan. If web_trigger, then trigger scan. - // And if it's both, run both scans. if func.invokable { let mut checker = AuthZChecker::new(); debug!("checking {:?} at {:?}", func.func_name, &func.path); From 57c64cd7c8aff6b42e941814be4b49fe1c768679 Mon Sep 17 00:00:00 2001 From: Alina Wang Date: Mon, 8 Jan 2024 13:09:36 -0800 Subject: [PATCH 389/517] rebased EAS-1893 - changing author attempt --- crates/forge_loader/src/manifest.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/forge_loader/src/manifest.rs b/crates/forge_loader/src/manifest.rs index e50a86e1..0870dfb1 100644 --- a/crates/forge_loader/src/manifest.rs +++ b/crates/forge_loader/src/manifest.rs @@ -568,4 +568,4 @@ mod tests { assert_eq!(func, "Catch-me-if-you-can3"); } } -} +}} From 116bf18837db2870f8833f5283041881cea5d6b9 Mon Sep 17 00:00:00 2001 From: Alina Wang Date: Thu, 11 Jan 2024 09:46:10 -0800 Subject: [PATCH 390/517] chore: tried rebasing to change all author's on commits. Partially worked --- crates/forge_loader/src/manifest.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/forge_loader/src/manifest.rs b/crates/forge_loader/src/manifest.rs index 0870dfb1..e50a86e1 100644 --- a/crates/forge_loader/src/manifest.rs +++ b/crates/forge_loader/src/manifest.rs @@ -568,4 +568,4 @@ mod tests { assert_eq!(func, "Catch-me-if-you-can3"); } } -}} +} From fa3b5d4c49afea5adf30fa3d4bfc51a92b0b4e1a Mon Sep 17 00:00:00 2001 From: Joshua Wong Date: Fri, 11 Nov 2022 15:09:51 -0600 Subject: [PATCH 391/517] docs: update example help --- README.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 8802676f..ff6f3c22 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ A static analysis tool for finding common [Forge][1] vulnerabilities. -[1]: "Forge platform" +[1]: https://developer.atlassian.com/platform/forge "Forge platform" ## Usage @@ -31,9 +31,9 @@ latest stable release, and adding the toolchain [^1]: Cargo is technically not required if you want to download every dependency, invoke `rustc`, and link everything manually. However, I wouldn't recommend doing this unless you're extremely bored. -[Rust]: -[Rustup]: "Rustup" -[Cargo]: +[Rust]: https://www.rust-lang.org/ +[Rustup]: https://github.com/rust-lang/rustup "Rustup" +[Cargo]: https://github.com/rust-lang/cargo Installing from source: @@ -70,7 +70,7 @@ Contributions to FSRT are welcome! Please see [CONTRIBUTING.md](CONTRIBUTING.md) ## License -Copyright (c) 2022 Atlassian and others. +Copyright (c) 2022 Atlassian and others. FSRT is dual licensed under the MIT and Apache 2.0 licenses. From f3148ed21790351d1b107320853ebbfa45818a0b Mon Sep 17 00:00:00 2001 From: Joshua Wong Date: Thu, 24 Nov 2022 23:29:58 -0600 Subject: [PATCH 392/517] feat: add new resolver --- crates/forge_analyzer/src/definitions.rs | 33 ++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/crates/forge_analyzer/src/definitions.rs b/crates/forge_analyzer/src/definitions.rs index a57974d4..61d80b3f 100644 --- a/crates/forge_analyzer/src/definitions.rs +++ b/crates/forge_analyzer/src/definitions.rs @@ -3801,6 +3801,39 @@ impl fmt::Display for ForeignItem { } } +impl fmt::Display for DefKind { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + DefKind::Class(_) => write!(f, "class"), + DefKind::Resolver(_) => write!(f, "resolver"), + DefKind::ObjLit(_) => write!(f, "object literal"), + DefKind::Function(_) => write!(f, "function"), + DefKind::Global(_) => write!(f, "global"), + DefKind::ExportAlias(_) => write!(f, "export alias"), + DefKind::ResolverHandler(_) => write!(f, "resolver handler"), + DefKind::ModuleNs(_) => write!(f, "module namespace"), + DefKind::Foreign(_) => write!(f, "foreign"), + DefKind::Undefined => write!(f, "undefined"), + } + } +} + +impl fmt::Display for ImportKind { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + ImportKind::Star => write!(f, "*"), + ImportKind::Default => write!(f, "default"), + ImportKind::Named(sym) => write!(f, "{}", &**sym), + } + } +} + +impl fmt::Display for ForeignItem { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "import {} from {}", self.kind, &*self.module_name) + } +} + impl From for DefKey { #[inline] fn from(value: ObjKind) -> Self { From 78d01c10f541565d875a22fe1cb3f237d67aa4dd Mon Sep 17 00:00:00 2001 From: Joshua Wong Date: Fri, 25 Nov 2022 00:46:18 -0600 Subject: [PATCH 393/517] chore: bump deps --- .DS_Store | Bin 0 -> 6148 bytes test-apps/.DS_Store | Bin 0 -> 6148 bytes 2 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 .DS_Store create mode 100644 test-apps/.DS_Store diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..01c0ca53b4db4de5b281e7b7e20f9358d7d46e2d GIT binary patch literal 6148 zcmeHK%}T>S5Z-O8O(;SR3OxqAR;;xa#7l_v1&ruHr6we3FlI}WnnNk%tS{t~_&m<+ zZp6|GoL4Trhyh|?T^P{&pV3;E1=Bi-0b=0C4B-A?gCaT_ONDal0E3^8m~SAWfRA?xL|dby zu~Y~d5UyGQRV&w546fQCZtFTnW2sQJGcIR_=jfTazM*hAJH%~S&bXtHEHOX~EHY46 zLkrLUv+wW!i%m2l28e-w#Q<*%y`c+B(r4?^;_$4sLGM6Ou&z}2*#eF{ilG*d;x?!h Zh}-A@IvPub5CNeN0Z9WHV&GR9_y89FO!WW& literal 0 HcmV?d00001 diff --git a/test-apps/.DS_Store b/test-apps/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..5008ddfcf53c02e82d7eee2e57c38e5672ef89f6 GIT binary patch literal 6148 zcmeH~Jr2S!425mzP>H1@V-^m;4Wg<&0T*E43hX&L&p$$qDprKhvt+--jT7}7np#A3 zem<@ulZcFPQ@L2!n>{z**++&mCkOWA81W14cNZlEfg7;MkzE(HCqgga^y>{tEnwC%0;vJ&^%eQ zLs35+`xjp>T0 Date: Sat, 26 Nov 2022 19:31:05 -0600 Subject: [PATCH 394/517] wip --- crates/forge_analyzer/src/definitions.rs | 102 ++++++++++++++++++++++- crates/forge_analyzer/src/ir.rs | 23 +++++ 2 files changed, 124 insertions(+), 1 deletion(-) diff --git a/crates/forge_analyzer/src/definitions.rs b/crates/forge_analyzer/src/definitions.rs index 61d80b3f..60ebb789 100644 --- a/crates/forge_analyzer/src/definitions.rs +++ b/crates/forge_analyzer/src/definitions.rs @@ -805,6 +805,107 @@ struct FunctionCollector<'cx> { parent: Option, } +struct FunctionAnalyzer<'cx> { + res: &'cx mut Resolver, + defs: &'cx mut Definitions, + module: ModId, + current_def: DefId, + body: Body, + block: BasicBlockId, +} + +impl<'cx> FunctionAnalyzer<'cx> { + #[inline] + fn set_curr_terminator(&mut self, term: Terminator) { + self.body.set_terminator(self.block, term); + } + + #[inline] + fn push_curr_inst(&mut self, inst: Inst) { + self.body.push_inst(self.block, inst); + } +} + +impl Visit for FunctionAnalyzer<'_> { + fn visit_member_expr(&mut self, n: &MemberExpr) {} + + fn visit_expr(&mut self, n: &Expr) { + match n { + Expr::This(_) => todo!(), + Expr::Array(_) => todo!(), + Expr::Object(_) => todo!(), + Expr::Fn(_) => todo!(), + Expr::Unary(_) => todo!(), + Expr::Update(_) => todo!(), + Expr::Bin(_) => todo!(), + Expr::Assign(_) => todo!(), + Expr::Member(_) => todo!(), + Expr::SuperProp(_) => todo!(), + Expr::Cond(_) => todo!(), + Expr::Call(_) => todo!(), + Expr::New(_) => todo!(), + Expr::Seq(_) => todo!(), + Expr::Ident(_) => todo!(), + Expr::Lit(_) => todo!(), + Expr::Tpl(_) => todo!(), + Expr::TaggedTpl(_) => todo!(), + Expr::Arrow(_) => todo!(), + Expr::Class(_) => todo!(), + Expr::Yield(_) => todo!(), + Expr::MetaProp(_) => todo!(), + Expr::Await(_) => todo!(), + Expr::Paren(_) => todo!(), + Expr::JSXMember(_) => todo!(), + Expr::JSXNamespacedName(_) => todo!(), + Expr::JSXEmpty(_) => todo!(), + Expr::JSXElement(_) => todo!(), + Expr::JSXFragment(_) => todo!(), + Expr::TsTypeAssertion(_) => todo!(), + Expr::TsConstAssertion(_) => todo!(), + Expr::TsNonNull(_) => todo!(), + Expr::TsAs(_) => todo!(), + Expr::TsInstantiation(_) => todo!(), + Expr::TsSatisfies(_) => todo!(), + Expr::PrivateName(_) => todo!(), + Expr::OptChain(_) => todo!(), + Expr::Invalid(_) => todo!(), + } + } + fn visit_stmt(&mut self, n: &Stmt) { + match n { + Stmt::Block(_) => todo!(), + Stmt::Empty(_) => todo!(), + Stmt::Debugger(_) => todo!(), + Stmt::With(_) => todo!(), + Stmt::Return(_) => todo!(), + Stmt::Labeled(_) => todo!(), + Stmt::Break(_) => todo!(), + Stmt::Continue(_) => todo!(), + Stmt::If(_) => todo!(), + Stmt::Switch(_) => todo!(), + Stmt::Throw(_) => todo!(), + Stmt::Try(_) => todo!(), + Stmt::While(_) => todo!(), + Stmt::DoWhile(_) => todo!(), + Stmt::For(_) => todo!(), + Stmt::ForIn(_) => todo!(), + Stmt::ForOf(_) => todo!(), + Stmt::Decl(_) => todo!(), + Stmt::Expr(_) => todo!(), + } + } +} + +impl Visit for FunctionCollector<'_> { + fn visit_fn_decl(&mut self, n: &FnDecl) { + let id = n.ident.to_id(); + let def = self.res.get_or_insert_sym(id, self.module); + self.parent = Some(def); + n.function.visit_children_with(self); + self.parent = None; + } +} + struct FunctionAnalyzer<'cx> { pub res: &'cx mut Environment, module: ModId, @@ -3808,7 +3909,6 @@ impl fmt::Display for DefKind { DefKind::Resolver(_) => write!(f, "resolver"), DefKind::ObjLit(_) => write!(f, "object literal"), DefKind::Function(_) => write!(f, "function"), - DefKind::Global(_) => write!(f, "global"), DefKind::ExportAlias(_) => write!(f, "export alias"), DefKind::ResolverHandler(_) => write!(f, "resolver handler"), DefKind::ModuleNs(_) => write!(f, "module namespace"), diff --git a/crates/forge_analyzer/src/ir.rs b/crates/forge_analyzer/src/ir.rs index b25a01e7..56863c5d 100644 --- a/crates/forge_analyzer/src/ir.rs +++ b/crates/forge_analyzer/src/ir.rs @@ -911,6 +911,29 @@ impl Default for Body { fn default() -> Self { Self::new() } + + #[inline] + pub(crate) fn new_block(&mut self) -> BasicBlockId { + self.blocks.push_and_get_key(BasicBlock::default()) + } + + #[inline] + pub(crate) fn new_block_with_terminator(&mut self, term: Terminator) -> BasicBlockId { + self.blocks.push_and_get_key(BasicBlock { + term, + ..Default::default() + }) + } + + #[inline] + pub(crate) fn set_terminator(&mut self, bb: BasicBlockId, term: Terminator) -> Terminator { + mem::replace(&mut self.blocks[bb].term, term) + } + + #[inline] + pub(crate) fn push_inst(&mut self, bb: BasicBlockId, inst: Inst) { + self.blocks[bb].insts.push(inst); + } } impl PartialEq for Literal { From 93a445ff3146c5d2670435b471d0456163a8b3ac Mon Sep 17 00:00:00 2001 From: Joshua Wong Date: Mon, 28 Nov 2022 19:56:19 -0600 Subject: [PATCH 395/517] feat: detect resolver definitions --- crates/forge_analyzer/src/definitions.rs | 248 +++++++++++++++++------ crates/forge_analyzer/src/ir.rs | 24 +-- 2 files changed, 185 insertions(+), 87 deletions(-) diff --git a/crates/forge_analyzer/src/definitions.rs b/crates/forge_analyzer/src/definitions.rs index 60ebb789..85280901 100644 --- a/crates/forge_analyzer/src/definitions.rs +++ b/crates/forge_analyzer/src/definitions.rs @@ -806,12 +806,13 @@ struct FunctionCollector<'cx> { } struct FunctionAnalyzer<'cx> { - res: &'cx mut Resolver, - defs: &'cx mut Definitions, + res: &'cx mut Environment, module: ModId, current_def: DefId, body: Body, block: BasicBlockId, + operand_stack: Vec, + in_rhs: bool, } impl<'cx> FunctionAnalyzer<'cx> { @@ -827,81 +828,191 @@ impl<'cx> FunctionAnalyzer<'cx> { } impl Visit for FunctionAnalyzer<'_> { - fn visit_member_expr(&mut self, n: &MemberExpr) {} + fn visit_call_expr(&mut self, n: &CallExpr) { + n.visit_children_with(self); + let _props = normalize_callee_expr(&n.callee, self.res, self.module); + match _props.first() { + Some(&PropPath::Def(id)) => { + debug!("call from: {}", self.res.def_name(id)); + debug!("call expr: {:?}", _props); + } + Some(PropPath::Unknown(id)) => { + debug!("call from: {}", id.0); + debug!("call expr: {:?}", _props); + } + _ => (), + } + } +} + +struct ArgDefiner<'cx> { + res: &'cx mut Environment, + module: ModId, + func: DefId, + body: Body, +} - fn visit_expr(&mut self, n: &Expr) { +impl Visit for ArgDefiner<'_> { + fn visit_ident(&mut self, n: &Ident) { + let id = n.to_id(); + let defid = self + .res + .get_or_overwrite_sym(id.clone(), self.module, DefRes::Arg); + self.res.add_parent(defid, self.func); + self.body.add_arg(defid, id); + } + + fn visit_object_pat_prop(&mut self, n: &ObjectPatProp) { match n { - Expr::This(_) => todo!(), - Expr::Array(_) => todo!(), - Expr::Object(_) => todo!(), - Expr::Fn(_) => todo!(), - Expr::Unary(_) => todo!(), - Expr::Update(_) => todo!(), - Expr::Bin(_) => todo!(), - Expr::Assign(_) => todo!(), - Expr::Member(_) => todo!(), - Expr::SuperProp(_) => todo!(), - Expr::Cond(_) => todo!(), - Expr::Call(_) => todo!(), - Expr::New(_) => todo!(), - Expr::Seq(_) => todo!(), - Expr::Ident(_) => todo!(), - Expr::Lit(_) => todo!(), - Expr::Tpl(_) => todo!(), - Expr::TaggedTpl(_) => todo!(), - Expr::Arrow(_) => todo!(), - Expr::Class(_) => todo!(), - Expr::Yield(_) => todo!(), - Expr::MetaProp(_) => todo!(), - Expr::Await(_) => todo!(), - Expr::Paren(_) => todo!(), - Expr::JSXMember(_) => todo!(), - Expr::JSXNamespacedName(_) => todo!(), - Expr::JSXEmpty(_) => todo!(), - Expr::JSXElement(_) => todo!(), - Expr::JSXFragment(_) => todo!(), - Expr::TsTypeAssertion(_) => todo!(), - Expr::TsConstAssertion(_) => todo!(), - Expr::TsNonNull(_) => todo!(), - Expr::TsAs(_) => todo!(), - Expr::TsInstantiation(_) => todo!(), - Expr::TsSatisfies(_) => todo!(), - Expr::PrivateName(_) => todo!(), - Expr::OptChain(_) => todo!(), - Expr::Invalid(_) => todo!(), - } - } - fn visit_stmt(&mut self, n: &Stmt) { + ObjectPatProp::KeyValue(KeyValuePatProp { key, .. }) => key.visit_with(self), + ObjectPatProp::Assign(AssignPatProp { key, .. }) => self.visit_ident(key), + ObjectPatProp::Rest(_) => {} + } + } + + fn visit_pat(&mut self, n: &Pat) { match n { - Stmt::Block(_) => todo!(), - Stmt::Empty(_) => todo!(), - Stmt::Debugger(_) => todo!(), - Stmt::With(_) => todo!(), - Stmt::Return(_) => todo!(), - Stmt::Labeled(_) => todo!(), - Stmt::Break(_) => todo!(), - Stmt::Continue(_) => todo!(), - Stmt::If(_) => todo!(), - Stmt::Switch(_) => todo!(), - Stmt::Throw(_) => todo!(), - Stmt::Try(_) => todo!(), - Stmt::While(_) => todo!(), - Stmt::DoWhile(_) => todo!(), - Stmt::For(_) => todo!(), - Stmt::ForIn(_) => todo!(), - Stmt::ForOf(_) => todo!(), - Stmt::Decl(_) => todo!(), - Stmt::Expr(_) => todo!(), + Pat::Ident(_) | Pat::Array(_) => n.visit_children_with(self), + Pat::Object(ObjectPat { props, .. }) => props.visit_children_with(self), + Pat::Assign(AssignPat { left, .. }) => left.visit_with(self), + Pat::Expr(id) => { + if let Expr::Ident(id) = &**id { + id.visit_with(self); + } + } + Pat::Invalid(_) => {} + Pat::Rest(_) => {} + Pat::Invalid(_) => {} } } } +struct LocalDefiner<'cx> { + res: &'cx mut Environment, + module: ModId, + func: DefId, + body: Body, +} + +impl Visit for LocalDefiner<'_> { + fn visit_ident(&mut self, n: &Ident) { + let id = n.to_id(); + let defid = self.res.get_or_insert_sym(id.clone(), self.module); + self.res.try_add_parent(defid, self.func); + self.body.add_local_def(defid, id); + } + + fn visit_var_declarator(&mut self, n: &VarDeclarator) { + n.name.visit_with(self); + } + + fn visit_decl(&mut self, n: &Decl) { + match n { + Decl::Class(_) => {} + Decl::Fn(FnDecl { ident, .. }) => { + ident.visit_with(self); + } + Decl::Var(vars) => vars.visit_children_with(self), + Decl::TsInterface(_) | Decl::TsTypeAlias(_) | Decl::TsEnum(_) | Decl::TsModule(_) => {} + } + } + + fn visit_arrow_expr(&mut self, _: &ArrowExpr) {} + fn visit_fn_decl(&mut self, _: &FnDecl) {} +} + impl Visit for FunctionCollector<'_> { + fn visit_function(&mut self, n: &Function) { + n.visit_children_with(self); + let owner = self.parent.unwrap_or_else(|| { + self.res + .add_anonymous("__UNKNOWN", AnonType::Closure, self.module) + }); + let mut argdef = ArgDefiner { + res: self.res, + module: self.module, + func: owner, + body: Body::with_owner(owner), + }; + n.params.visit_children_with(&mut argdef); + let body = argdef.body; + let mut localdef = LocalDefiner { + res: self.res, + module: self.module, + func: owner, + body, + }; + n.body.visit_children_with(&mut localdef); + let body = localdef.body; + let mut analyzer = FunctionAnalyzer { + res: self.res, + module: self.module, + current_def: owner, + body, + block: BasicBlockId::default(), + operand_stack: vec![], + in_rhs: false, + }; + n.body.visit_with(&mut analyzer); + } + + fn visit_var_declarator(&mut self, n: &VarDeclarator) { + n.visit_children_with(self); + let Some(BindingIdent { id, .. }) = n.name.as_ident() else { + return; + }; + let id = id.to_id(); + match n.init.as_deref() { + Some(Expr::Fn(f)) => { + let defid = self + .res + .get_or_overwrite_sym(id, self.module, DefKind::Function(())); + let old_parent = self.parent.replace(defid); + f.visit_with(self); + self.parent = old_parent; + } + Some(Expr::Arrow(f)) => { + let defid = self + .res + .get_or_overwrite_sym(id, self.module, DefKind::Function(())); + let old_parent = self.parent.replace(defid); + f.visit_with(self); + self.parent = old_parent; + } + _ => {} + } + } + + fn visit_call_expr(&mut self, n: &CallExpr) { + n.visit_children_with(self); + if let Some((def_id, propname, expr)) = as_resolver_def(n, self.res, self.module) { + info!("found possible resolver: {propname}"); + match self.res.lookup_prop(def_id, propname) { + Some(def) => { + info!("analyzing resolver def: {def:?}"); + let mut analyzer = FunctionAnalyzer { + res: self.res, + module: self.module, + current_def: def, + body: Body::with_owner(def), + block: BasicBlockId::default(), + operand_stack: vec![], + in_rhs: false, + }; + expr.visit_with(&mut analyzer); + } + None => { + warn!("resolver def not found"); + } + } + } + } + fn visit_fn_decl(&mut self, n: &FnDecl) { let id = n.ident.to_id(); let def = self.res.get_or_insert_sym(id, self.module); self.parent = Some(def); - n.function.visit_children_with(self); + n.function.visit_with(self); self.parent = None; } } @@ -3902,13 +4013,16 @@ impl fmt::Display for ForeignItem { } } -impl fmt::Display for DefKind { +impl fmt::Display for DefKind { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { DefKind::Class(_) => write!(f, "class"), + DefKind::ResolverDef(_) => write!(f, "resolver def"), DefKind::Resolver(_) => write!(f, "resolver"), - DefKind::ObjLit(_) => write!(f, "object literal"), + DefKind::Arg => write!(f, "argument"), + DefKind::GlobalObj(_) => write!(f, "object literal"), DefKind::Function(_) => write!(f, "function"), + DefKind::Closure(_) => write!(f, "closure"), DefKind::ExportAlias(_) => write!(f, "export alias"), DefKind::ResolverHandler(_) => write!(f, "resolver handler"), DefKind::ModuleNs(_) => write!(f, "module namespace"), diff --git a/crates/forge_analyzer/src/ir.rs b/crates/forge_analyzer/src/ir.rs index 56863c5d..20ce0b81 100644 --- a/crates/forge_analyzer/src/ir.rs +++ b/crates/forge_analyzer/src/ir.rs @@ -911,28 +911,12 @@ impl Default for Body { fn default() -> Self { Self::new() } +} +impl Default for Body { #[inline] - pub(crate) fn new_block(&mut self) -> BasicBlockId { - self.blocks.push_and_get_key(BasicBlock::default()) - } - - #[inline] - pub(crate) fn new_block_with_terminator(&mut self, term: Terminator) -> BasicBlockId { - self.blocks.push_and_get_key(BasicBlock { - term, - ..Default::default() - }) - } - - #[inline] - pub(crate) fn set_terminator(&mut self, bb: BasicBlockId, term: Terminator) -> Terminator { - mem::replace(&mut self.blocks[bb].term, term) - } - - #[inline] - pub(crate) fn push_inst(&mut self, bb: BasicBlockId, inst: Inst) { - self.blocks[bb].insts.push(inst); + fn default() -> Self { + Self::new() } } From f9898e45eec31f340e1deaec3df813aa3ad47682 Mon Sep 17 00:00:00 2001 From: Joshua Wong Date: Wed, 30 Nov 2022 11:55:43 -0600 Subject: [PATCH 396/517] feat: lower to IR 2.0 --- crates/forge_analyzer/src/definitions.rs | 269 +++++++++++++++++++++-- crates/forge_analyzer/src/ir.rs | 11 +- 2 files changed, 263 insertions(+), 17 deletions(-) diff --git a/crates/forge_analyzer/src/definitions.rs b/crates/forge_analyzer/src/definitions.rs index 85280901..56f3efa2 100644 --- a/crates/forge_analyzer/src/definitions.rs +++ b/crates/forge_analyzer/src/definitions.rs @@ -809,10 +809,11 @@ struct FunctionAnalyzer<'cx> { res: &'cx mut Environment, module: ModId, current_def: DefId, + assigning_to: Option, body: Body, block: BasicBlockId, operand_stack: Vec, - in_rhs: bool, + in_lhs: bool, } impl<'cx> FunctionAnalyzer<'cx> { @@ -825,22 +826,256 @@ impl<'cx> FunctionAnalyzer<'cx> { fn push_curr_inst(&mut self, inst: Inst) { self.body.push_inst(self.block, inst); } -} -impl Visit for FunctionAnalyzer<'_> { - fn visit_call_expr(&mut self, n: &CallExpr) { - n.visit_children_with(self); - let _props = normalize_callee_expr(&n.callee, self.res, self.module); - match _props.first() { - Some(&PropPath::Def(id)) => { - debug!("call from: {}", self.res.def_name(id)); - debug!("call expr: {:?}", _props); + fn lower_member(&mut self, obj: &Expr, prop: &MemberProp) -> Operand { + let obj = self.lower_expr(obj); + let Operand::Var(mut var) = obj else { + // FIXME: handle literals + return obj; + }; + match prop { + MemberProp::Ident(id) | MemberProp::PrivateName(PrivateName { id, .. }) => { + let id = id.to_id(); + var.projections.push(Projection::Known(id.0)); + } + MemberProp::Computed(ComputedPropName { expr, .. }) => { + let opnd = self.lower_expr(expr); + var.projections + .push(self.body.resolve_prop(self.block, opnd)); + } + } + Operand::Var(var) + } + + // TODO: This can probably be made into a trait + fn lower_expr(&mut self, n: &Expr) -> Operand { + match n { + Expr::This(_) => Operand::Var(Variable::THIS), + Expr::Array(ArrayLit { elems, .. }) => { + let array_lit: Vec<_> = elems + .iter() + .map(|e| { + e.as_ref() + .map_or(Operand::UNDEF, |ExprOrSpread { spread, expr }| { + self.lower_expr(expr) + }) + }) + .collect(); + Operand::UNDEF + } + Expr::Object(ObjectLit { span, props }) => { + // TODO: lower object literals + Operand::UNDEF + } + Expr::Fn(_) => Operand::UNDEF, + Expr::Unary(UnaryExpr { op, arg, .. }) => { + let arg = self.lower_expr(arg); + let tmp = self + .body + .push_tmp(self.block, Rvalue::Unary(op.into(), arg), None); + Operand::with_var(tmp) + } + Expr::Update(UpdateExpr { + op, prefix, arg, .. + }) => { + // FIXME: Handle op + self.lower_expr(arg) + } + Expr::Bin(BinExpr { + op, left, right, .. + }) => { + let left = self.lower_expr(left); + let right = self.lower_expr(right); + let tmp = self + .body + .push_tmp(self.block, Rvalue::Bin(op.into(), left, right), None); + Operand::with_var(tmp) + } + + Expr::SuperProp(SuperPropExpr { obj, prop, .. }) => { + let mut super_var = Variable::SUPER; + match prop { + SuperProp::Ident(id) => { + let id = id.to_id().0; + super_var.projections.push(Projection::Known(id)); + } + SuperProp::Computed(ComputedPropName { expr, .. }) => { + let opnd = self.lower_expr(expr); + let prop = self.body.resolve_prop(self.block, opnd); + super_var.projections.push(prop); + } + } + Operand::Var(super_var) + } + Expr::Assign(AssignExpr { + op, left, right, .. + }) => match left { + PatOrExpr::Expr(_) => todo!(), + PatOrExpr::Pat(_) => todo!(), + }, + Expr::Member(MemberExpr { obj, prop, .. }) => self.lower_member(obj, prop), + Expr::Cond(CondExpr { + test, cons, alt, .. + }) => self.lower_expr(test), + Expr::Call(n) => { + let mut args = Vec::with_capacity(n.args.len()); + for ExprOrSpread { spread, expr } in &n.args { + let arg = self.lower_expr(expr); + args.push(arg); + } + let callee = match &n.callee { + Callee::Super(_) => Operand::Var(Variable::SUPER), + Callee::Import(_) => Operand::UNDEF, + Callee::Expr(expr) => self.lower_expr(expr), + }; + let props = normalize_callee_expr(&n.callee, self.res, self.module); + match props.first() { + Some(&PropPath::Def(id)) => { + debug!("call from: {}", self.res.def_name(id)); + debug!("call expr: {:?}", props); + } + Some(PropPath::Unknown(id)) => { + debug!("call from: {}", id.0); + debug!("call expr: {:?}", props); + } + _ => (), + } + todo!() } - Some(PropPath::Unknown(id)) => { - debug!("call from: {}", id.0); - debug!("call expr: {:?}", _props); + Expr::New(NewExpr { callee, args, .. }) => Operand::UNDEF, + Expr::Seq(SeqExpr { exprs, .. }) => { + if let Some((last, rest)) = exprs.split_last() { + for expr in rest { + let opnd = self.lower_expr(expr); + self.body.push_expr(self.block, Rvalue::Read(opnd)); + } + self.lower_expr(last) + } else { + Literal::Undef.into() + } } - _ => (), + Expr::Ident(id) => { + let id = id.to_id(); + let Some(def) = self.res.sym_to_id(id.clone(), self.module) else { + warn!("unknown symbol: {}", id.0); + return Literal::Undef.into(); + }; + let var = self.body.get_or_insert_global(def); + Operand::with_var(var) + } + Expr::Lit(lit) => lit.clone().into(), + Expr::Tpl(Tpl { exprs, quasis, .. }) => todo!(), + Expr::TaggedTpl(TaggedTpl { tag, tpl, .. }) => todo!(), + Expr::Arrow(_) => Operand::UNDEF, + Expr::Class(_) => Operand::UNDEF, + Expr::Yield(YieldExpr { arg, .. }) => arg + .as_deref() + .map_or(Operand::UNDEF, |expr| self.lower_expr(expr)), + Expr::MetaProp(_) => Operand::UNDEF, + Expr::Await(AwaitExpr { arg, .. }) => self.lower_expr(arg), + Expr::Paren(ParenExpr { expr, .. }) => self.lower_expr(expr), + Expr::JSXMember(_) => todo!(), + Expr::JSXNamespacedName(_) => todo!(), + Expr::JSXEmpty(_) => todo!(), + Expr::JSXElement(_) => todo!(), + Expr::JSXFragment(_) => todo!(), + Expr::TsTypeAssertion(TsTypeAssertion { expr, .. }) + | Expr::TsConstAssertion(TsConstAssertion { expr, .. }) + | Expr::TsNonNull(TsNonNullExpr { expr, .. }) + | Expr::TsAs(TsAsExpr { expr, .. }) + | Expr::TsInstantiation(TsInstantiation { expr, .. }) + | Expr::TsSatisfies(TsSatisfiesExpr { expr, .. }) => self.lower_expr(expr), + Expr::PrivateName(PrivateName { id, .. }) => todo!(), + Expr::OptChain(OptChainExpr { base, .. }) => match base { + OptChainBase::Call(OptCall { callee, args, .. }) => todo!(), + OptChainBase::Member(MemberExpr { obj, prop, .. }) => { + // TODO: create separate basic blocks + self.lower_member(obj, prop) + } + }, + Expr::Invalid(_) => Operand::UNDEF, + } + } + + fn lower_stmt(&mut self, n: &Stmt) { + match n { + Stmt::Block(BlockStmt { stmts, .. }) => todo!(), + Stmt::Empty(_) => todo!(), + Stmt::Debugger(_) => todo!(), + Stmt::With(WithStmt { obj, body, .. }) => todo!(), + Stmt::Return(ReturnStmt { arg, .. }) => todo!(), + Stmt::Labeled(LabeledStmt { label, body, .. }) => todo!(), + Stmt::Break(BreakStmt { label, .. }) => todo!(), + Stmt::Continue(ContinueStmt { label, .. }) => todo!(), + Stmt::If(IfStmt { + test, cons, alt, .. + }) => todo!(), + Stmt::Switch(SwitchStmt { + discriminant, + cases, + .. + }) => todo!(), + Stmt::Throw(ThrowStmt { arg, .. }) => todo!(), + Stmt::Try(stmt) => { + let TryStmt { + block, + handler, + finalizer, + .. + } = &**stmt; + todo!() + } + Stmt::While(WhileStmt { test, body, .. }) => todo!(), + Stmt::DoWhile(DoWhileStmt { test, body, .. }) => todo!(), + Stmt::For(ForStmt { + init, + test, + update, + body, + .. + }) => todo!(), + Stmt::ForIn(ForInStmt { + left, right, body, .. + }) => todo!(), + Stmt::ForOf(ForOfStmt { + left, right, body, .. + }) => todo!(), + Stmt::Decl(decl) => match decl { + Decl::Class(_) => todo!(), + Decl::Fn(_) => todo!(), + Decl::Var(_) => todo!(), + Decl::TsInterface(_) => todo!(), + Decl::TsTypeAlias(_) => todo!(), + Decl::TsEnum(_) => todo!(), + Decl::TsModule(_) => todo!(), + }, + Stmt::Expr(ExprStmt { expr, .. }) => todo!(), + } + } +} + +impl Visit for FunctionAnalyzer<'_> { + fn visit_stmt(&mut self, n: &Stmt) { + match n { + Stmt::Block(_) => todo!(), + Stmt::Empty(_) => todo!(), + Stmt::Debugger(_) => todo!(), + Stmt::With(_) => todo!(), + Stmt::Return(_) => todo!(), + Stmt::Labeled(_) => todo!(), + Stmt::Break(_) => todo!(), + Stmt::Continue(_) => todo!(), + Stmt::If(_) => todo!(), + Stmt::Switch(_) => todo!(), + Stmt::Throw(_) => todo!(), + Stmt::Try(_) => todo!(), + Stmt::While(_) => todo!(), + Stmt::DoWhile(_) => todo!(), + Stmt::For(_) => todo!(), + Stmt::ForIn(_) => todo!(), + Stmt::ForOf(_) => todo!(), + Stmt::Decl(_) => todo!(), + Stmt::Expr(_) => todo!(), } } } @@ -948,10 +1183,11 @@ impl Visit for FunctionCollector<'_> { res: self.res, module: self.module, current_def: owner, + assigning_to: None, body, block: BasicBlockId::default(), operand_stack: vec![], - in_rhs: false, + in_lhs: false, }; n.body.visit_with(&mut analyzer); } @@ -994,10 +1230,11 @@ impl Visit for FunctionCollector<'_> { res: self.res, module: self.module, current_def: def, + assigning_to: None, body: Body::with_owner(def), block: BasicBlockId::default(), operand_stack: vec![], - in_rhs: false, + in_lhs: false, }; expr.visit_with(&mut analyzer); } diff --git a/crates/forge_analyzer/src/ir.rs b/crates/forge_analyzer/src/ir.rs index 20ce0b81..6688a425 100644 --- a/crates/forge_analyzer/src/ir.rs +++ b/crates/forge_analyzer/src/ir.rs @@ -259,7 +259,6 @@ pub(crate) enum Successors { One(BasicBlockId), Two(BasicBlockId, BasicBlockId), } - impl BasicBlock { #[inline] pub(crate) fn iter(&self) -> impl DoubleEndedIterator + ExactSizeIterator { @@ -283,6 +282,16 @@ impl BasicBlock { } } +impl fmt::Display for Base { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match *self { + Base::This => write!(f, "this"), + Base::Super => write!(f, "super"), + Base::Var(id) => write!(f, "{}", id), + } + } +} + impl Body { #[inline] fn new() -> Self { From 8e8a1e7c42b789fcaa8cc5f681732a8cb10e2cc6 Mon Sep 17 00:00:00 2001 From: Joshua Wong Date: Wed, 30 Nov 2022 11:56:18 -0600 Subject: [PATCH 397/517] chore: add test case for issue #1 --- test-apps/issue-1-resolver/src/index.js | 5766 ++++++++++++----------- 1 file changed, 2916 insertions(+), 2850 deletions(-) diff --git a/test-apps/issue-1-resolver/src/index.js b/test-apps/issue-1-resolver/src/index.js index 5f0e970f..dde286ee 100644 --- a/test-apps/issue-1-resolver/src/index.js +++ b/test-apps/issue-1-resolver/src/index.js @@ -1,2942 +1,3008 @@ // src/index.ts -import Resolver from '@forge/resolver'; +import Resolver from "@forge/resolver"; // src/lib/get-text.ts function getText({ text }) { - return 'Hello, world!\n' + text; + return "Hello, world!\n" + text; } // src/lib/permissions.ts -import { authorize } from '@forge/api'; -var administerPermission = 'ADMINISTER'; +import { authorize } from "@forge/api"; +var administerPermission = "ADMINISTER"; function isGlobalAdminPermission(permission) { - return permission.permission === administerPermission; + return permission.permission === administerPermission; } async function isJiraGlobalAdmin() { - const permissions = await authorize().onJira([ - { permissions: [administerPermission] }, - ]); - return permissions.every(isGlobalAdminPermission); + const permissions = await authorize().onJira([ + { permissions: [administerPermission] }, + ]); + return permissions.every(isGlobalAdminPermission); } // node_modules/zod/lib/index.mjs var util; (function (util2) { - util2.assertEqual = (val) => val; - function assertIs(_arg) {} - util2.assertIs = assertIs; - function assertNever(_x) { - throw new Error(); - } - util2.assertNever = assertNever; - util2.arrayToEnum = (items) => { - const obj = {}; - for (const item of items) { - obj[item] = item; - } - return obj; - }; - util2.getValidEnumValues = (obj) => { - const validKeys = util2 - .objectKeys(obj) - .filter((k) => typeof obj[obj[k]] !== 'number'); - const filtered = {}; - for (const k of validKeys) { - filtered[k] = obj[k]; - } - return util2.objectValues(filtered); - }; - util2.objectValues = (obj) => { - return util2.objectKeys(obj).map(function (e) { - return obj[e]; - }); - }; - util2.objectKeys = - typeof Object.keys === 'function' - ? (obj) => Object.keys(obj) - : (object) => { - const keys = []; - for (const key in object) { - if (Object.prototype.hasOwnProperty.call(object, key)) { - keys.push(key); - } - } - return keys; - }; - util2.find = (arr, checker) => { - for (const item of arr) { - if (checker(item)) return item; - } - return void 0; - }; - util2.isInteger = - typeof Number.isInteger === 'function' - ? (val) => Number.isInteger(val) - : (val) => - typeof val === 'number' && isFinite(val) && Math.floor(val) === val; - function joinValues(array, separator = ' | ') { - return array - .map((val) => (typeof val === 'string' ? `'${val}'` : val)) - .join(separator); - } - util2.joinValues = joinValues; - util2.jsonStringifyReplacer = (_, value) => { - if (typeof value === 'bigint') { - return value.toString(); - } - return value; - }; + util2.assertEqual = (val) => val; + function assertIs(_arg) {} + util2.assertIs = assertIs; + function assertNever(_x) { + throw new Error(); + } + util2.assertNever = assertNever; + util2.arrayToEnum = (items) => { + const obj = {}; + for (const item of items) { + obj[item] = item; + } + return obj; + }; + util2.getValidEnumValues = (obj) => { + const validKeys = util2 + .objectKeys(obj) + .filter((k) => typeof obj[obj[k]] !== "number"); + const filtered = {}; + for (const k of validKeys) { + filtered[k] = obj[k]; + } + return util2.objectValues(filtered); + }; + util2.objectValues = (obj) => { + return util2.objectKeys(obj).map(function (e) { + return obj[e]; + }); + }; + util2.objectKeys = + typeof Object.keys === "function" + ? (obj) => Object.keys(obj) + : (object) => { + const keys = []; + for (const key in object) { + if (Object.prototype.hasOwnProperty.call(object, key)) { + keys.push(key); + } + } + return keys; + }; + util2.find = (arr, checker) => { + for (const item of arr) { + if (checker(item)) return item; + } + return void 0; + }; + util2.isInteger = + typeof Number.isInteger === "function" + ? (val) => Number.isInteger(val) + : (val) => + typeof val === "number" && + isFinite(val) && + Math.floor(val) === val; + function joinValues(array, separator = " | ") { + return array + .map((val) => (typeof val === "string" ? `'${val}'` : val)) + .join(separator); + } + util2.joinValues = joinValues; + util2.jsonStringifyReplacer = (_, value) => { + if (typeof value === "bigint") { + return value.toString(); + } + return value; + }; })(util || (util = {})); var ZodParsedType = util.arrayToEnum([ - 'string', - 'nan', - 'number', - 'integer', - 'float', - 'boolean', - 'date', - 'bigint', - 'symbol', - 'function', - 'undefined', - 'null', - 'array', - 'object', - 'unknown', - 'promise', - 'void', - 'never', - 'map', - 'set', + "string", + "nan", + "number", + "integer", + "float", + "boolean", + "date", + "bigint", + "symbol", + "function", + "undefined", + "null", + "array", + "object", + "unknown", + "promise", + "void", + "never", + "map", + "set", ]); var getParsedType = (data) => { - const t = typeof data; - switch (t) { - case 'undefined': - return ZodParsedType.undefined; - case 'string': - return ZodParsedType.string; - case 'number': - return isNaN(data) ? ZodParsedType.nan : ZodParsedType.number; - case 'boolean': - return ZodParsedType.boolean; - case 'function': - return ZodParsedType.function; - case 'bigint': - return ZodParsedType.bigint; - case 'object': - if (Array.isArray(data)) { - return ZodParsedType.array; - } - if (data === null) { - return ZodParsedType.null; - } - if ( - data.then && - typeof data.then === 'function' && - data.catch && - typeof data.catch === 'function' - ) { - return ZodParsedType.promise; - } - if (typeof Map !== 'undefined' && data instanceof Map) { - return ZodParsedType.map; - } - if (typeof Set !== 'undefined' && data instanceof Set) { - return ZodParsedType.set; - } - if (typeof Date !== 'undefined' && data instanceof Date) { - return ZodParsedType.date; - } - return ZodParsedType.object; - default: - return ZodParsedType.unknown; - } + const t = typeof data; + switch (t) { + case "undefined": + return ZodParsedType.undefined; + case "string": + return ZodParsedType.string; + case "number": + return isNaN(data) ? ZodParsedType.nan : ZodParsedType.number; + case "boolean": + return ZodParsedType.boolean; + case "function": + return ZodParsedType.function; + case "bigint": + return ZodParsedType.bigint; + case "object": + if (Array.isArray(data)) { + return ZodParsedType.array; + } + if (data === null) { + return ZodParsedType.null; + } + if ( + data.then && + typeof data.then === "function" && + data.catch && + typeof data.catch === "function" + ) { + return ZodParsedType.promise; + } + if (typeof Map !== "undefined" && data instanceof Map) { + return ZodParsedType.map; + } + if (typeof Set !== "undefined" && data instanceof Set) { + return ZodParsedType.set; + } + if (typeof Date !== "undefined" && data instanceof Date) { + return ZodParsedType.date; + } + return ZodParsedType.object; + default: + return ZodParsedType.unknown; + } }; var ZodIssueCode = util.arrayToEnum([ - 'invalid_type', - 'invalid_literal', - 'custom', - 'invalid_union', - 'invalid_union_discriminator', - 'invalid_enum_value', - 'unrecognized_keys', - 'invalid_arguments', - 'invalid_return_type', - 'invalid_date', - 'invalid_string', - 'too_small', - 'too_big', - 'invalid_intersection_types', - 'not_multiple_of', + "invalid_type", + "invalid_literal", + "custom", + "invalid_union", + "invalid_union_discriminator", + "invalid_enum_value", + "unrecognized_keys", + "invalid_arguments", + "invalid_return_type", + "invalid_date", + "invalid_string", + "too_small", + "too_big", + "invalid_intersection_types", + "not_multiple_of", ]); var quotelessJson = (obj) => { - const json = JSON.stringify(obj, null, 2); - return json.replace(/"([^"]+)":/g, '$1:'); + const json = JSON.stringify(obj, null, 2); + return json.replace(/"([^"]+)":/g, "$1:"); }; var ZodError = class extends Error { - constructor(issues) { - super(); - this.issues = []; - this.addIssue = (sub) => { - this.issues = [...this.issues, sub]; - }; - this.addIssues = (subs = []) => { - this.issues = [...this.issues, ...subs]; - }; - const actualProto = new.target.prototype; - if (Object.setPrototypeOf) { - Object.setPrototypeOf(this, actualProto); - } else { - this.__proto__ = actualProto; - } - this.name = 'ZodError'; - this.issues = issues; - } - get errors() { - return this.issues; - } - format(_mapper) { - const mapper = - _mapper || - function (issue) { - return issue.message; - }; - const fieldErrors = { _errors: [] }; - const processError = (error) => { - for (const issue of error.issues) { - if (issue.code === 'invalid_union') { - issue.unionErrors.map(processError); - } else if (issue.code === 'invalid_return_type') { - processError(issue.returnTypeError); - } else if (issue.code === 'invalid_arguments') { - processError(issue.argumentsError); - } else if (issue.path.length === 0) { - fieldErrors._errors.push(mapper(issue)); - } else { - let curr = fieldErrors; - let i = 0; - while (i < issue.path.length) { - const el = issue.path[i]; - const terminal = i === issue.path.length - 1; - if (!terminal) { - curr[el] = curr[el] || { _errors: [] }; - } else { - curr[el] = curr[el] || { _errors: [] }; - curr[el]._errors.push(mapper(issue)); - } - curr = curr[el]; - i++; - } - } - } - }; - processError(this); - return fieldErrors; - } - toString() { - return this.message; - } - get message() { - return JSON.stringify(this.issues, util.jsonStringifyReplacer, 2); - } - get isEmpty() { - return this.issues.length === 0; - } - flatten(mapper = (issue) => issue.message) { - const fieldErrors = {}; - const formErrors = []; - for (const sub of this.issues) { - if (sub.path.length > 0) { - fieldErrors[sub.path[0]] = fieldErrors[sub.path[0]] || []; - fieldErrors[sub.path[0]].push(mapper(sub)); - } else { - formErrors.push(mapper(sub)); - } - } - return { formErrors, fieldErrors }; - } - get formErrors() { - return this.flatten(); - } + constructor(issues) { + super(); + this.issues = []; + this.addIssue = (sub) => { + this.issues = [...this.issues, sub]; + }; + this.addIssues = (subs = []) => { + this.issues = [...this.issues, ...subs]; + }; + const actualProto = new.target.prototype; + if (Object.setPrototypeOf) { + Object.setPrototypeOf(this, actualProto); + } else { + this.__proto__ = actualProto; + } + this.name = "ZodError"; + this.issues = issues; + } + get errors() { + return this.issues; + } + format(_mapper) { + const mapper = + _mapper || + function (issue) { + return issue.message; + }; + const fieldErrors = { _errors: [] }; + const processError = (error) => { + for (const issue of error.issues) { + if (issue.code === "invalid_union") { + issue.unionErrors.map(processError); + } else if (issue.code === "invalid_return_type") { + processError(issue.returnTypeError); + } else if (issue.code === "invalid_arguments") { + processError(issue.argumentsError); + } else if (issue.path.length === 0) { + fieldErrors._errors.push(mapper(issue)); + } else { + let curr = fieldErrors; + let i = 0; + while (i < issue.path.length) { + const el = issue.path[i]; + const terminal = i === issue.path.length - 1; + if (!terminal) { + curr[el] = curr[el] || { _errors: [] }; + } else { + curr[el] = curr[el] || { _errors: [] }; + curr[el]._errors.push(mapper(issue)); + } + curr = curr[el]; + i++; + } + } + } + }; + processError(this); + return fieldErrors; + } + toString() { + return this.message; + } + get message() { + return JSON.stringify(this.issues, util.jsonStringifyReplacer, 2); + } + get isEmpty() { + return this.issues.length === 0; + } + flatten(mapper = (issue) => issue.message) { + const fieldErrors = {}; + const formErrors = []; + for (const sub of this.issues) { + if (sub.path.length > 0) { + fieldErrors[sub.path[0]] = fieldErrors[sub.path[0]] || []; + fieldErrors[sub.path[0]].push(mapper(sub)); + } else { + formErrors.push(mapper(sub)); + } + } + return { formErrors, fieldErrors }; + } + get formErrors() { + return this.flatten(); + } }; ZodError.create = (issues) => { - const error = new ZodError(issues); - return error; + const error = new ZodError(issues); + return error; }; var errorMap = (issue, _ctx) => { - let message; - switch (issue.code) { - case ZodIssueCode.invalid_type: - if (issue.received === ZodParsedType.undefined) { - message = 'Required'; - } else { - message = `Expected ${issue.expected}, received ${issue.received}`; - } - break; - case ZodIssueCode.invalid_literal: - message = `Invalid literal value, expected ${JSON.stringify( - issue.expected, - util.jsonStringifyReplacer - )}`; - break; - case ZodIssueCode.unrecognized_keys: - message = `Unrecognized key(s) in object: ${util.joinValues( - issue.keys, - ', ' - )}`; - break; - case ZodIssueCode.invalid_union: - message = `Invalid input`; - break; - case ZodIssueCode.invalid_union_discriminator: - message = `Invalid discriminator value. Expected ${util.joinValues( - issue.options - )}`; - break; - case ZodIssueCode.invalid_enum_value: - message = `Invalid enum value. Expected ${util.joinValues( - issue.options - )}, received '${issue.received}'`; - break; - case ZodIssueCode.invalid_arguments: - message = `Invalid function arguments`; - break; - case ZodIssueCode.invalid_return_type: - message = `Invalid function return type`; - break; - case ZodIssueCode.invalid_date: - message = `Invalid date`; - break; - case ZodIssueCode.invalid_string: - if (typeof issue.validation === 'object') { - if ('startsWith' in issue.validation) { - message = `Invalid input: must start with "${issue.validation.startsWith}"`; - } else if ('endsWith' in issue.validation) { - message = `Invalid input: must end with "${issue.validation.endsWith}"`; - } else { - util.assertNever(issue.validation); - } - } else if (issue.validation !== 'regex') { - message = `Invalid ${issue.validation}`; - } else { - message = 'Invalid'; - } - break; - case ZodIssueCode.too_small: - if (issue.type === 'array') - message = `Array must contain ${ - issue.inclusive ? `at least` : `more than` - } ${issue.minimum} element(s)`; - else if (issue.type === 'string') - message = `String must contain ${ - issue.inclusive ? `at least` : `over` - } ${issue.minimum} character(s)`; - else if (issue.type === 'number') - message = `Number must be greater than ${ - issue.inclusive ? `or equal to ` : `` - }${issue.minimum}`; - else if (issue.type === 'date') - message = `Date must be greater than ${ - issue.inclusive ? `or equal to ` : `` - }${new Date(issue.minimum)}`; - else message = 'Invalid input'; - break; - case ZodIssueCode.too_big: - if (issue.type === 'array') - message = `Array must contain ${ - issue.inclusive ? `at most` : `less than` - } ${issue.maximum} element(s)`; - else if (issue.type === 'string') - message = `String must contain ${ - issue.inclusive ? `at most` : `under` - } ${issue.maximum} character(s)`; - else if (issue.type === 'number') - message = `Number must be less than ${ - issue.inclusive ? `or equal to ` : `` - }${issue.maximum}`; - else if (issue.type === 'date') - message = `Date must be smaller than ${ - issue.inclusive ? `or equal to ` : `` - }${new Date(issue.maximum)}`; - else message = 'Invalid input'; - break; - case ZodIssueCode.custom: - message = `Invalid input`; - break; - case ZodIssueCode.invalid_intersection_types: - message = `Intersection results could not be merged`; - break; - case ZodIssueCode.not_multiple_of: - message = `Number must be a multiple of ${issue.multipleOf}`; - break; - default: - message = _ctx.defaultError; - util.assertNever(issue); - } - return { message }; + let message; + switch (issue.code) { + case ZodIssueCode.invalid_type: + if (issue.received === ZodParsedType.undefined) { + message = "Required"; + } else { + message = `Expected ${issue.expected}, received ${issue.received}`; + } + break; + case ZodIssueCode.invalid_literal: + message = `Invalid literal value, expected ${JSON.stringify( + issue.expected, + util.jsonStringifyReplacer + )}`; + break; + case ZodIssueCode.unrecognized_keys: + message = `Unrecognized key(s) in object: ${util.joinValues( + issue.keys, + ", " + )}`; + break; + case ZodIssueCode.invalid_union: + message = `Invalid input`; + break; + case ZodIssueCode.invalid_union_discriminator: + message = `Invalid discriminator value. Expected ${util.joinValues( + issue.options + )}`; + break; + case ZodIssueCode.invalid_enum_value: + message = `Invalid enum value. Expected ${util.joinValues( + issue.options + )}, received '${issue.received}'`; + break; + case ZodIssueCode.invalid_arguments: + message = `Invalid function arguments`; + break; + case ZodIssueCode.invalid_return_type: + message = `Invalid function return type`; + break; + case ZodIssueCode.invalid_date: + message = `Invalid date`; + break; + case ZodIssueCode.invalid_string: + if (typeof issue.validation === "object") { + if ("startsWith" in issue.validation) { + message = `Invalid input: must start with "${issue.validation.startsWith}"`; + } else if ("endsWith" in issue.validation) { + message = `Invalid input: must end with "${issue.validation.endsWith}"`; + } else { + util.assertNever(issue.validation); + } + } else if (issue.validation !== "regex") { + message = `Invalid ${issue.validation}`; + } else { + message = "Invalid"; + } + break; + case ZodIssueCode.too_small: + if (issue.type === "array") + message = `Array must contain ${ + issue.inclusive ? `at least` : `more than` + } ${issue.minimum} element(s)`; + else if (issue.type === "string") + message = `String must contain ${ + issue.inclusive ? `at least` : `over` + } ${issue.minimum} character(s)`; + else if (issue.type === "number") + message = `Number must be greater than ${ + issue.inclusive ? `or equal to ` : `` + }${issue.minimum}`; + else if (issue.type === "date") + message = `Date must be greater than ${ + issue.inclusive ? `or equal to ` : `` + }${new Date(issue.minimum)}`; + else message = "Invalid input"; + break; + case ZodIssueCode.too_big: + if (issue.type === "array") + message = `Array must contain ${ + issue.inclusive ? `at most` : `less than` + } ${issue.maximum} element(s)`; + else if (issue.type === "string") + message = `String must contain ${ + issue.inclusive ? `at most` : `under` + } ${issue.maximum} character(s)`; + else if (issue.type === "number") + message = `Number must be less than ${ + issue.inclusive ? `or equal to ` : `` + }${issue.maximum}`; + else if (issue.type === "date") + message = `Date must be smaller than ${ + issue.inclusive ? `or equal to ` : `` + }${new Date(issue.maximum)}`; + else message = "Invalid input"; + break; + case ZodIssueCode.custom: + message = `Invalid input`; + break; + case ZodIssueCode.invalid_intersection_types: + message = `Intersection results could not be merged`; + break; + case ZodIssueCode.not_multiple_of: + message = `Number must be a multiple of ${issue.multipleOf}`; + break; + default: + message = _ctx.defaultError; + util.assertNever(issue); + } + return { message }; }; var overrideErrorMap = errorMap; function setErrorMap(map) { - overrideErrorMap = map; + overrideErrorMap = map; } function getErrorMap() { - return overrideErrorMap; + return overrideErrorMap; } var makeIssue = (params) => { - const { data, path, errorMaps, issueData } = params; - const fullPath = [...path, ...(issueData.path || [])]; - const fullIssue = { - ...issueData, - path: fullPath, - }; - let errorMessage = ''; - const maps = errorMaps - .filter((m) => !!m) - .slice() - .reverse(); - for (const map of maps) { - errorMessage = map(fullIssue, { data, defaultError: errorMessage }).message; - } - return { - ...issueData, - path: fullPath, - message: issueData.message || errorMessage, - }; + const { data, path, errorMaps, issueData } = params; + const fullPath = [...path, ...(issueData.path || [])]; + const fullIssue = { + ...issueData, + path: fullPath, + }; + let errorMessage = ""; + const maps = errorMaps + .filter((m) => !!m) + .slice() + .reverse(); + for (const map of maps) { + errorMessage = map(fullIssue, { + data, + defaultError: errorMessage, + }).message; + } + return { + ...issueData, + path: fullPath, + message: issueData.message || errorMessage, + }; }; var EMPTY_PATH = []; function addIssueToContext(ctx, issueData) { - const issue = makeIssue({ - issueData, - data: ctx.data, - path: ctx.path, - errorMaps: [ - ctx.common.contextualErrorMap, - ctx.schemaErrorMap, - getErrorMap(), - errorMap, - ].filter((x) => !!x), - }); - ctx.common.issues.push(issue); + const issue = makeIssue({ + issueData, + data: ctx.data, + path: ctx.path, + errorMaps: [ + ctx.common.contextualErrorMap, + ctx.schemaErrorMap, + getErrorMap(), + errorMap, + ].filter((x) => !!x), + }); + ctx.common.issues.push(issue); } var ParseStatus = class { - constructor() { - this.value = 'valid'; - } - dirty() { - if (this.value === 'valid') this.value = 'dirty'; - } - abort() { - if (this.value !== 'aborted') this.value = 'aborted'; - } - static mergeArray(status, results) { - const arrayValue = []; - for (const s of results) { - if (s.status === 'aborted') return INVALID; - if (s.status === 'dirty') status.dirty(); - arrayValue.push(s.value); - } - return { status: status.value, value: arrayValue }; - } - static async mergeObjectAsync(status, pairs) { - const syncPairs = []; - for (const pair of pairs) { - syncPairs.push({ - key: await pair.key, - value: await pair.value, - }); - } - return ParseStatus.mergeObjectSync(status, syncPairs); - } - static mergeObjectSync(status, pairs) { - const finalObject = {}; - for (const pair of pairs) { - const { key, value } = pair; - if (key.status === 'aborted') return INVALID; - if (value.status === 'aborted') return INVALID; - if (key.status === 'dirty') status.dirty(); - if (value.status === 'dirty') status.dirty(); - if (typeof value.value !== 'undefined' || pair.alwaysSet) { - finalObject[key.value] = value.value; - } - } - return { status: status.value, value: finalObject }; - } + constructor() { + this.value = "valid"; + } + dirty() { + if (this.value === "valid") this.value = "dirty"; + } + abort() { + if (this.value !== "aborted") this.value = "aborted"; + } + static mergeArray(status, results) { + const arrayValue = []; + for (const s of results) { + if (s.status === "aborted") return INVALID; + if (s.status === "dirty") status.dirty(); + arrayValue.push(s.value); + } + return { status: status.value, value: arrayValue }; + } + static async mergeObjectAsync(status, pairs) { + const syncPairs = []; + for (const pair of pairs) { + syncPairs.push({ + key: await pair.key, + value: await pair.value, + }); + } + return ParseStatus.mergeObjectSync(status, syncPairs); + } + static mergeObjectSync(status, pairs) { + const finalObject = {}; + for (const pair of pairs) { + const { key, value } = pair; + if (key.status === "aborted") return INVALID; + if (value.status === "aborted") return INVALID; + if (key.status === "dirty") status.dirty(); + if (value.status === "dirty") status.dirty(); + if (typeof value.value !== "undefined" || pair.alwaysSet) { + finalObject[key.value] = value.value; + } + } + return { status: status.value, value: finalObject }; + } }; var INVALID = Object.freeze({ - status: 'aborted', + status: "aborted", }); -var DIRTY = (value) => ({ status: 'dirty', value }); -var OK = (value) => ({ status: 'valid', value }); -var isAborted = (x) => x.status === 'aborted'; -var isDirty = (x) => x.status === 'dirty'; -var isValid = (x) => x.status === 'valid'; +var DIRTY = (value) => ({ status: "dirty", value }); +var OK = (value) => ({ status: "valid", value }); +var isAborted = (x) => x.status === "aborted"; +var isDirty = (x) => x.status === "dirty"; +var isValid = (x) => x.status === "valid"; var isAsync = (x) => typeof Promise !== void 0 && x instanceof Promise; var errorUtil; (function (errorUtil2) { - errorUtil2.errToObj = (message) => - typeof message === 'string' ? { message } : message || {}; - errorUtil2.toString = (message) => - typeof message === 'string' - ? message - : message === null || message === void 0 - ? void 0 - : message.message; + errorUtil2.errToObj = (message) => + typeof message === "string" ? { message } : message || {}; + errorUtil2.toString = (message) => + typeof message === "string" + ? message + : message === null || message === void 0 + ? void 0 + : message.message; })(errorUtil || (errorUtil = {})); var ParseInputLazyPath = class { - constructor(parent, value, path, key) { - this.parent = parent; - this.data = value; - this._path = path; - this._key = key; - } - get path() { - return this._path.concat(this._key); - } + constructor(parent, value, path, key) { + this.parent = parent; + this.data = value; + this._path = path; + this._key = key; + } + get path() { + return this._path.concat(this._key); + } }; var handleResult = (ctx, result) => { - if (isValid(result)) { - return { success: true, data: result.value }; - } else { - if (!ctx.common.issues.length) { - throw new Error('Validation failed but no issues detected.'); - } - const error = new ZodError(ctx.common.issues); - return { success: false, error }; - } + if (isValid(result)) { + return { success: true, data: result.value }; + } else { + if (!ctx.common.issues.length) { + throw new Error("Validation failed but no issues detected."); + } + const error = new ZodError(ctx.common.issues); + return { success: false, error }; + } }; function processCreateParams(params) { - if (!params) return {}; - const { - errorMap: errorMap2, - invalid_type_error, - required_error, - description, - } = params; - if (errorMap2 && (invalid_type_error || required_error)) { - throw new Error( - `Can't use "invalid_type_error" or "required_error" in conjunction with custom error map.` - ); - } - if (errorMap2) return { errorMap: errorMap2, description }; - const customMap = (iss, ctx) => { - if (iss.code !== 'invalid_type') return { message: ctx.defaultError }; - if (typeof ctx.data === 'undefined') { - return { - message: - required_error !== null && required_error !== void 0 - ? required_error - : ctx.defaultError, - }; - } - return { - message: - invalid_type_error !== null && invalid_type_error !== void 0 - ? invalid_type_error - : ctx.defaultError, - }; - }; - return { errorMap: customMap, description }; + if (!params) return {}; + const { + errorMap: errorMap2, + invalid_type_error, + required_error, + description, + } = params; + if (errorMap2 && (invalid_type_error || required_error)) { + throw new Error( + `Can't use "invalid_type_error" or "required_error" in conjunction with custom error map.` + ); + } + if (errorMap2) return { errorMap: errorMap2, description }; + const customMap = (iss, ctx) => { + if (iss.code !== "invalid_type") return { message: ctx.defaultError }; + if (typeof ctx.data === "undefined") { + return { + message: + required_error !== null && required_error !== void 0 + ? required_error + : ctx.defaultError, + }; + } + return { + message: + invalid_type_error !== null && invalid_type_error !== void 0 + ? invalid_type_error + : ctx.defaultError, + }; + }; + return { errorMap: customMap, description }; } var ZodType = class { - constructor(def) { - this.spa = this.safeParseAsync; - this.superRefine = this._refinement; - this._def = def; - this.parse = this.parse.bind(this); - this.safeParse = this.safeParse.bind(this); - this.parseAsync = this.parseAsync.bind(this); - this.safeParseAsync = this.safeParseAsync.bind(this); - this.spa = this.spa.bind(this); - this.refine = this.refine.bind(this); - this.refinement = this.refinement.bind(this); - this.superRefine = this.superRefine.bind(this); - this.optional = this.optional.bind(this); - this.nullable = this.nullable.bind(this); - this.nullish = this.nullish.bind(this); - this.array = this.array.bind(this); - this.promise = this.promise.bind(this); - this.or = this.or.bind(this); - this.and = this.and.bind(this); - this.transform = this.transform.bind(this); - this.default = this.default.bind(this); - this.describe = this.describe.bind(this); - this.isNullable = this.isNullable.bind(this); - this.isOptional = this.isOptional.bind(this); - } - get description() { - return this._def.description; - } - _getType(input) { - return getParsedType(input.data); - } - _getOrReturnCtx(input, ctx) { - return ( - ctx || { - common: input.parent.common, - data: input.data, - parsedType: getParsedType(input.data), - schemaErrorMap: this._def.errorMap, - path: input.path, - parent: input.parent, - } - ); - } - _processInputParams(input) { - return { - status: new ParseStatus(), - ctx: { - common: input.parent.common, - data: input.data, - parsedType: getParsedType(input.data), - schemaErrorMap: this._def.errorMap, - path: input.path, - parent: input.parent, - }, - }; - } - _parseSync(input) { - const result = this._parse(input); - if (isAsync(result)) { - throw new Error('Synchronous parse encountered promise.'); - } - return result; - } - _parseAsync(input) { - const result = this._parse(input); - return Promise.resolve(result); - } - parse(data, params) { - const result = this.safeParse(data, params); - if (result.success) return result.data; - throw result.error; - } - safeParse(data, params) { - var _a; - const ctx = { - common: { - issues: [], - async: - (_a = - params === null || params === void 0 ? void 0 : params.async) !== - null && _a !== void 0 - ? _a - : false, - contextualErrorMap: - params === null || params === void 0 ? void 0 : params.errorMap, - }, - path: (params === null || params === void 0 ? void 0 : params.path) || [], - schemaErrorMap: this._def.errorMap, - parent: null, - data, - parsedType: getParsedType(data), - }; - const result = this._parseSync({ data, path: ctx.path, parent: ctx }); - return handleResult(ctx, result); - } - async parseAsync(data, params) { - const result = await this.safeParseAsync(data, params); - if (result.success) return result.data; - throw result.error; - } - async safeParseAsync(data, params) { - const ctx = { - common: { - issues: [], - contextualErrorMap: - params === null || params === void 0 ? void 0 : params.errorMap, - async: true, - }, - path: (params === null || params === void 0 ? void 0 : params.path) || [], - schemaErrorMap: this._def.errorMap, - parent: null, - data, - parsedType: getParsedType(data), - }; - const maybeAsyncResult = this._parse({ data, path: [], parent: ctx }); - const result = await (isAsync(maybeAsyncResult) - ? maybeAsyncResult - : Promise.resolve(maybeAsyncResult)); - return handleResult(ctx, result); - } - refine(check, message) { - const getIssueProperties = (val) => { - if (typeof message === 'string' || typeof message === 'undefined') { - return { message }; - } else if (typeof message === 'function') { - return message(val); - } else { - return message; - } - }; - return this._refinement((val, ctx) => { - const result = check(val); - const setError = () => - ctx.addIssue({ - code: ZodIssueCode.custom, - ...getIssueProperties(val), - }); - if (typeof Promise !== 'undefined' && result instanceof Promise) { - return result.then((data) => { - if (!data) { - setError(); - return false; - } else { - return true; - } - }); - } - if (!result) { - setError(); - return false; - } else { - return true; - } - }); - } - refinement(check, refinementData) { - return this._refinement((val, ctx) => { - if (!check(val)) { - ctx.addIssue( - typeof refinementData === 'function' - ? refinementData(val, ctx) - : refinementData - ); - return false; - } else { - return true; - } - }); - } - _refinement(refinement) { - return new ZodEffects({ - schema: this, - typeName: ZodFirstPartyTypeKind.ZodEffects, - effect: { type: 'refinement', refinement }, - }); - } - optional() { - return ZodOptional.create(this); - } - nullable() { - return ZodNullable.create(this); - } - nullish() { - return this.optional().nullable(); - } - array() { - return ZodArray.create(this); - } - promise() { - return ZodPromise.create(this); - } - or(option) { - return ZodUnion.create([this, option]); - } - and(incoming) { - return ZodIntersection.create(this, incoming); - } - transform(transform) { - return new ZodEffects({ - schema: this, - typeName: ZodFirstPartyTypeKind.ZodEffects, - effect: { type: 'transform', transform }, - }); - } - default(def) { - const defaultValueFunc = typeof def === 'function' ? def : () => def; - return new ZodDefault({ - innerType: this, - defaultValue: defaultValueFunc, - typeName: ZodFirstPartyTypeKind.ZodDefault, - }); - } - brand() { - return new ZodBranded({ - typeName: ZodFirstPartyTypeKind.ZodBranded, - type: this, - ...processCreateParams(void 0), - }); - } - describe(description) { - const This = this.constructor; - return new This({ - ...this._def, - description, - }); - } - isOptional() { - return this.safeParse(void 0).success; - } - isNullable() { - return this.safeParse(null).success; - } + constructor(def) { + this.spa = this.safeParseAsync; + this.superRefine = this._refinement; + this._def = def; + this.parse = this.parse.bind(this); + this.safeParse = this.safeParse.bind(this); + this.parseAsync = this.parseAsync.bind(this); + this.safeParseAsync = this.safeParseAsync.bind(this); + this.spa = this.spa.bind(this); + this.refine = this.refine.bind(this); + this.refinement = this.refinement.bind(this); + this.superRefine = this.superRefine.bind(this); + this.optional = this.optional.bind(this); + this.nullable = this.nullable.bind(this); + this.nullish = this.nullish.bind(this); + this.array = this.array.bind(this); + this.promise = this.promise.bind(this); + this.or = this.or.bind(this); + this.and = this.and.bind(this); + this.transform = this.transform.bind(this); + this.default = this.default.bind(this); + this.describe = this.describe.bind(this); + this.isNullable = this.isNullable.bind(this); + this.isOptional = this.isOptional.bind(this); + } + get description() { + return this._def.description; + } + _getType(input) { + return getParsedType(input.data); + } + _getOrReturnCtx(input, ctx) { + return ( + ctx || { + common: input.parent.common, + data: input.data, + parsedType: getParsedType(input.data), + schemaErrorMap: this._def.errorMap, + path: input.path, + parent: input.parent, + } + ); + } + _processInputParams(input) { + return { + status: new ParseStatus(), + ctx: { + common: input.parent.common, + data: input.data, + parsedType: getParsedType(input.data), + schemaErrorMap: this._def.errorMap, + path: input.path, + parent: input.parent, + }, + }; + } + _parseSync(input) { + const result = this._parse(input); + if (isAsync(result)) { + throw new Error("Synchronous parse encountered promise."); + } + return result; + } + _parseAsync(input) { + const result = this._parse(input); + return Promise.resolve(result); + } + parse(data, params) { + const result = this.safeParse(data, params); + if (result.success) return result.data; + throw result.error; + } + safeParse(data, params) { + var _a; + const ctx = { + common: { + issues: [], + async: + (_a = + params === null || params === void 0 + ? void 0 + : params.async) !== null && _a !== void 0 + ? _a + : false, + contextualErrorMap: + params === null || params === void 0 + ? void 0 + : params.errorMap, + }, + path: + (params === null || params === void 0 ? void 0 : params.path) || + [], + schemaErrorMap: this._def.errorMap, + parent: null, + data, + parsedType: getParsedType(data), + }; + const result = this._parseSync({ data, path: ctx.path, parent: ctx }); + return handleResult(ctx, result); + } + async parseAsync(data, params) { + const result = await this.safeParseAsync(data, params); + if (result.success) return result.data; + throw result.error; + } + async safeParseAsync(data, params) { + const ctx = { + common: { + issues: [], + contextualErrorMap: + params === null || params === void 0 + ? void 0 + : params.errorMap, + async: true, + }, + path: + (params === null || params === void 0 ? void 0 : params.path) || + [], + schemaErrorMap: this._def.errorMap, + parent: null, + data, + parsedType: getParsedType(data), + }; + const maybeAsyncResult = this._parse({ data, path: [], parent: ctx }); + const result = await (isAsync(maybeAsyncResult) + ? maybeAsyncResult + : Promise.resolve(maybeAsyncResult)); + return handleResult(ctx, result); + } + refine(check, message) { + const getIssueProperties = (val) => { + if (typeof message === "string" || typeof message === "undefined") { + return { message }; + } else if (typeof message === "function") { + return message(val); + } else { + return message; + } + }; + return this._refinement((val, ctx) => { + const result = check(val); + const setError = () => + ctx.addIssue({ + code: ZodIssueCode.custom, + ...getIssueProperties(val), + }); + if (typeof Promise !== "undefined" && result instanceof Promise) { + return result.then((data) => { + if (!data) { + setError(); + return false; + } else { + return true; + } + }); + } + if (!result) { + setError(); + return false; + } else { + return true; + } + }); + } + refinement(check, refinementData) { + return this._refinement((val, ctx) => { + if (!check(val)) { + ctx.addIssue( + typeof refinementData === "function" + ? refinementData(val, ctx) + : refinementData + ); + return false; + } else { + return true; + } + }); + } + _refinement(refinement) { + return new ZodEffects({ + schema: this, + typeName: ZodFirstPartyTypeKind.ZodEffects, + effect: { type: "refinement", refinement }, + }); + } + optional() { + return ZodOptional.create(this); + } + nullable() { + return ZodNullable.create(this); + } + nullish() { + return this.optional().nullable(); + } + array() { + return ZodArray.create(this); + } + promise() { + return ZodPromise.create(this); + } + or(option) { + return ZodUnion.create([this, option]); + } + and(incoming) { + return ZodIntersection.create(this, incoming); + } + transform(transform) { + return new ZodEffects({ + schema: this, + typeName: ZodFirstPartyTypeKind.ZodEffects, + effect: { type: "transform", transform }, + }); + } + default(def) { + const defaultValueFunc = typeof def === "function" ? def : () => def; + return new ZodDefault({ + innerType: this, + defaultValue: defaultValueFunc, + typeName: ZodFirstPartyTypeKind.ZodDefault, + }); + } + brand() { + return new ZodBranded({ + typeName: ZodFirstPartyTypeKind.ZodBranded, + type: this, + ...processCreateParams(void 0), + }); + } + describe(description) { + const This = this.constructor; + return new This({ + ...this._def, + description, + }); + } + isOptional() { + return this.safeParse(void 0).success; + } + isNullable() { + return this.safeParse(null).success; + } }; var cuidRegex = /^c[^\s-]{8,}$/i; var uuidRegex = - /^([a-f0-9]{8}-[a-f0-9]{4}-[1-5][a-f0-9]{3}-[a-f0-9]{4}-[a-f0-9]{12}|00000000-0000-0000-0000-000000000000)$/i; + /^([a-f0-9]{8}-[a-f0-9]{4}-[1-5][a-f0-9]{3}-[a-f0-9]{4}-[a-f0-9]{12}|00000000-0000-0000-0000-000000000000)$/i; var emailRegex = - /^(([^<>()[\]\.,;:\s@\"]+(\.[^<>()[\]\.,;:\s@\"]+)*)|(\".+\"))@(([^<>()[\]\.,;:\s@\"]+\.)+[^<>()[\]\.,;:\s@\"]{2,})$/i; + /^(([^<>()[\]\.,;:\s@\"]+(\.[^<>()[\]\.,;:\s@\"]+)*)|(\".+\"))@(([^<>()[\]\.,;:\s@\"]+\.)+[^<>()[\]\.,;:\s@\"]{2,})$/i; var ZodString = class extends ZodType { - constructor() { - super(...arguments); - this._regex = (regex, validation, message) => - this.refinement((data) => regex.test(data), { - validation, - code: ZodIssueCode.invalid_string, - ...errorUtil.errToObj(message), - }); - this.nonempty = (message) => this.min(1, errorUtil.errToObj(message)); - this.trim = () => - new ZodString({ - ...this._def, - checks: [...this._def.checks, { kind: 'trim' }], - }); - } - _parse(input) { - const parsedType = this._getType(input); - if (parsedType !== ZodParsedType.string) { - const ctx2 = this._getOrReturnCtx(input); - addIssueToContext(ctx2, { - code: ZodIssueCode.invalid_type, - expected: ZodParsedType.string, - received: ctx2.parsedType, - }); - return INVALID; - } - const status = new ParseStatus(); - let ctx = void 0; - for (const check of this._def.checks) { - if (check.kind === 'min') { - if (input.data.length < check.value) { - ctx = this._getOrReturnCtx(input, ctx); - addIssueToContext(ctx, { - code: ZodIssueCode.too_small, - minimum: check.value, - type: 'string', - inclusive: true, - message: check.message, - }); - status.dirty(); - } - } else if (check.kind === 'max') { - if (input.data.length > check.value) { - ctx = this._getOrReturnCtx(input, ctx); - addIssueToContext(ctx, { - code: ZodIssueCode.too_big, - maximum: check.value, - type: 'string', - inclusive: true, - message: check.message, - }); - status.dirty(); - } - } else if (check.kind === 'email') { - if (!emailRegex.test(input.data)) { - ctx = this._getOrReturnCtx(input, ctx); - addIssueToContext(ctx, { - validation: 'email', - code: ZodIssueCode.invalid_string, - message: check.message, - }); - status.dirty(); - } - } else if (check.kind === 'uuid') { - if (!uuidRegex.test(input.data)) { - ctx = this._getOrReturnCtx(input, ctx); - addIssueToContext(ctx, { - validation: 'uuid', - code: ZodIssueCode.invalid_string, - message: check.message, - }); - status.dirty(); - } - } else if (check.kind === 'cuid') { - if (!cuidRegex.test(input.data)) { - ctx = this._getOrReturnCtx(input, ctx); - addIssueToContext(ctx, { - validation: 'cuid', - code: ZodIssueCode.invalid_string, - message: check.message, - }); - status.dirty(); - } - } else if (check.kind === 'url') { - try { - new URL(input.data); - } catch (_a) { - ctx = this._getOrReturnCtx(input, ctx); - addIssueToContext(ctx, { - validation: 'url', - code: ZodIssueCode.invalid_string, - message: check.message, - }); - status.dirty(); - } - } else if (check.kind === 'regex') { - check.regex.lastIndex = 0; - const testResult = check.regex.test(input.data); - if (!testResult) { - ctx = this._getOrReturnCtx(input, ctx); - addIssueToContext(ctx, { - validation: 'regex', - code: ZodIssueCode.invalid_string, - message: check.message, - }); - status.dirty(); - } - } else if (check.kind === 'trim') { - input.data = input.data.trim(); - } else if (check.kind === 'startsWith') { - if (!input.data.startsWith(check.value)) { - ctx = this._getOrReturnCtx(input, ctx); - addIssueToContext(ctx, { - code: ZodIssueCode.invalid_string, - validation: { startsWith: check.value }, - message: check.message, - }); - status.dirty(); - } - } else if (check.kind === 'endsWith') { - if (!input.data.endsWith(check.value)) { - ctx = this._getOrReturnCtx(input, ctx); - addIssueToContext(ctx, { - code: ZodIssueCode.invalid_string, - validation: { endsWith: check.value }, - message: check.message, - }); - status.dirty(); - } - } else { - util.assertNever(check); - } - } - return { status: status.value, value: input.data }; - } - _addCheck(check) { - return new ZodString({ - ...this._def, - checks: [...this._def.checks, check], - }); - } - email(message) { - return this._addCheck({ kind: 'email', ...errorUtil.errToObj(message) }); - } - url(message) { - return this._addCheck({ kind: 'url', ...errorUtil.errToObj(message) }); - } - uuid(message) { - return this._addCheck({ kind: 'uuid', ...errorUtil.errToObj(message) }); - } - cuid(message) { - return this._addCheck({ kind: 'cuid', ...errorUtil.errToObj(message) }); - } - regex(regex, message) { - return this._addCheck({ - kind: 'regex', - regex, - ...errorUtil.errToObj(message), - }); - } - startsWith(value, message) { - return this._addCheck({ - kind: 'startsWith', - value, - ...errorUtil.errToObj(message), - }); - } - endsWith(value, message) { - return this._addCheck({ - kind: 'endsWith', - value, - ...errorUtil.errToObj(message), - }); - } - min(minLength, message) { - return this._addCheck({ - kind: 'min', - value: minLength, - ...errorUtil.errToObj(message), - }); - } - max(maxLength, message) { - return this._addCheck({ - kind: 'max', - value: maxLength, - ...errorUtil.errToObj(message), - }); - } - length(len, message) { - return this.min(len, message).max(len, message); - } - get isEmail() { - return !!this._def.checks.find((ch) => ch.kind === 'email'); - } - get isURL() { - return !!this._def.checks.find((ch) => ch.kind === 'url'); - } - get isUUID() { - return !!this._def.checks.find((ch) => ch.kind === 'uuid'); - } - get isCUID() { - return !!this._def.checks.find((ch) => ch.kind === 'cuid'); - } - get minLength() { - let min = null; - for (const ch of this._def.checks) { - if (ch.kind === 'min') { - if (min === null || ch.value > min) min = ch.value; - } - } - return min; - } - get maxLength() { - let max = null; - for (const ch of this._def.checks) { - if (ch.kind === 'max') { - if (max === null || ch.value < max) max = ch.value; - } - } - return max; - } + constructor() { + super(...arguments); + this._regex = (regex, validation, message) => + this.refinement((data) => regex.test(data), { + validation, + code: ZodIssueCode.invalid_string, + ...errorUtil.errToObj(message), + }); + this.nonempty = (message) => this.min(1, errorUtil.errToObj(message)); + this.trim = () => + new ZodString({ + ...this._def, + checks: [...this._def.checks, { kind: "trim" }], + }); + } + _parse(input) { + const parsedType = this._getType(input); + if (parsedType !== ZodParsedType.string) { + const ctx2 = this._getOrReturnCtx(input); + addIssueToContext(ctx2, { + code: ZodIssueCode.invalid_type, + expected: ZodParsedType.string, + received: ctx2.parsedType, + }); + return INVALID; + } + const status = new ParseStatus(); + let ctx = void 0; + for (const check of this._def.checks) { + if (check.kind === "min") { + if (input.data.length < check.value) { + ctx = this._getOrReturnCtx(input, ctx); + addIssueToContext(ctx, { + code: ZodIssueCode.too_small, + minimum: check.value, + type: "string", + inclusive: true, + message: check.message, + }); + status.dirty(); + } + } else if (check.kind === "max") { + if (input.data.length > check.value) { + ctx = this._getOrReturnCtx(input, ctx); + addIssueToContext(ctx, { + code: ZodIssueCode.too_big, + maximum: check.value, + type: "string", + inclusive: true, + message: check.message, + }); + status.dirty(); + } + } else if (check.kind === "email") { + if (!emailRegex.test(input.data)) { + ctx = this._getOrReturnCtx(input, ctx); + addIssueToContext(ctx, { + validation: "email", + code: ZodIssueCode.invalid_string, + message: check.message, + }); + status.dirty(); + } + } else if (check.kind === "uuid") { + if (!uuidRegex.test(input.data)) { + ctx = this._getOrReturnCtx(input, ctx); + addIssueToContext(ctx, { + validation: "uuid", + code: ZodIssueCode.invalid_string, + message: check.message, + }); + status.dirty(); + } + } else if (check.kind === "cuid") { + if (!cuidRegex.test(input.data)) { + ctx = this._getOrReturnCtx(input, ctx); + addIssueToContext(ctx, { + validation: "cuid", + code: ZodIssueCode.invalid_string, + message: check.message, + }); + status.dirty(); + } + } else if (check.kind === "url") { + try { + new URL(input.data); + } catch (_a) { + ctx = this._getOrReturnCtx(input, ctx); + addIssueToContext(ctx, { + validation: "url", + code: ZodIssueCode.invalid_string, + message: check.message, + }); + status.dirty(); + } + } else if (check.kind === "regex") { + check.regex.lastIndex = 0; + const testResult = check.regex.test(input.data); + if (!testResult) { + ctx = this._getOrReturnCtx(input, ctx); + addIssueToContext(ctx, { + validation: "regex", + code: ZodIssueCode.invalid_string, + message: check.message, + }); + status.dirty(); + } + } else if (check.kind === "trim") { + input.data = input.data.trim(); + } else if (check.kind === "startsWith") { + if (!input.data.startsWith(check.value)) { + ctx = this._getOrReturnCtx(input, ctx); + addIssueToContext(ctx, { + code: ZodIssueCode.invalid_string, + validation: { startsWith: check.value }, + message: check.message, + }); + status.dirty(); + } + } else if (check.kind === "endsWith") { + if (!input.data.endsWith(check.value)) { + ctx = this._getOrReturnCtx(input, ctx); + addIssueToContext(ctx, { + code: ZodIssueCode.invalid_string, + validation: { endsWith: check.value }, + message: check.message, + }); + status.dirty(); + } + } else { + util.assertNever(check); + } + } + return { status: status.value, value: input.data }; + } + _addCheck(check) { + return new ZodString({ + ...this._def, + checks: [...this._def.checks, check], + }); + } + email(message) { + return this._addCheck({ + kind: "email", + ...errorUtil.errToObj(message), + }); + } + url(message) { + return this._addCheck({ kind: "url", ...errorUtil.errToObj(message) }); + } + uuid(message) { + return this._addCheck({ kind: "uuid", ...errorUtil.errToObj(message) }); + } + cuid(message) { + return this._addCheck({ kind: "cuid", ...errorUtil.errToObj(message) }); + } + regex(regex, message) { + return this._addCheck({ + kind: "regex", + regex, + ...errorUtil.errToObj(message), + }); + } + startsWith(value, message) { + return this._addCheck({ + kind: "startsWith", + value, + ...errorUtil.errToObj(message), + }); + } + endsWith(value, message) { + return this._addCheck({ + kind: "endsWith", + value, + ...errorUtil.errToObj(message), + }); + } + min(minLength, message) { + return this._addCheck({ + kind: "min", + value: minLength, + ...errorUtil.errToObj(message), + }); + } + max(maxLength, message) { + return this._addCheck({ + kind: "max", + value: maxLength, + ...errorUtil.errToObj(message), + }); + } + length(len, message) { + return this.min(len, message).max(len, message); + } + get isEmail() { + return !!this._def.checks.find((ch) => ch.kind === "email"); + } + get isURL() { + return !!this._def.checks.find((ch) => ch.kind === "url"); + } + get isUUID() { + return !!this._def.checks.find((ch) => ch.kind === "uuid"); + } + get isCUID() { + return !!this._def.checks.find((ch) => ch.kind === "cuid"); + } + get minLength() { + let min = null; + for (const ch of this._def.checks) { + if (ch.kind === "min") { + if (min === null || ch.value > min) min = ch.value; + } + } + return min; + } + get maxLength() { + let max = null; + for (const ch of this._def.checks) { + if (ch.kind === "max") { + if (max === null || ch.value < max) max = ch.value; + } + } + return max; + } }; ZodString.create = (params) => { - return new ZodString({ - checks: [], - typeName: ZodFirstPartyTypeKind.ZodString, - ...processCreateParams(params), - }); + return new ZodString({ + checks: [], + typeName: ZodFirstPartyTypeKind.ZodString, + ...processCreateParams(params), + }); }; function floatSafeRemainder(val, step) { - const valDecCount = (val.toString().split('.')[1] || '').length; - const stepDecCount = (step.toString().split('.')[1] || '').length; - const decCount = valDecCount > stepDecCount ? valDecCount : stepDecCount; - const valInt = parseInt(val.toFixed(decCount).replace('.', '')); - const stepInt = parseInt(step.toFixed(decCount).replace('.', '')); - return (valInt % stepInt) / Math.pow(10, decCount); + const valDecCount = (val.toString().split(".")[1] || "").length; + const stepDecCount = (step.toString().split(".")[1] || "").length; + const decCount = valDecCount > stepDecCount ? valDecCount : stepDecCount; + const valInt = parseInt(val.toFixed(decCount).replace(".", "")); + const stepInt = parseInt(step.toFixed(decCount).replace(".", "")); + return (valInt % stepInt) / Math.pow(10, decCount); } var ZodNumber = class extends ZodType { - constructor() { - super(...arguments); - this.min = this.gte; - this.max = this.lte; - this.step = this.multipleOf; - } - _parse(input) { - const parsedType = this._getType(input); - if (parsedType !== ZodParsedType.number) { - const ctx2 = this._getOrReturnCtx(input); - addIssueToContext(ctx2, { - code: ZodIssueCode.invalid_type, - expected: ZodParsedType.number, - received: ctx2.parsedType, - }); - return INVALID; - } - let ctx = void 0; - const status = new ParseStatus(); - for (const check of this._def.checks) { - if (check.kind === 'int') { - if (!util.isInteger(input.data)) { - ctx = this._getOrReturnCtx(input, ctx); - addIssueToContext(ctx, { - code: ZodIssueCode.invalid_type, - expected: 'integer', - received: 'float', - message: check.message, - }); - status.dirty(); - } - } else if (check.kind === 'min') { - const tooSmall = check.inclusive - ? input.data < check.value - : input.data <= check.value; - if (tooSmall) { - ctx = this._getOrReturnCtx(input, ctx); - addIssueToContext(ctx, { - code: ZodIssueCode.too_small, - minimum: check.value, - type: 'number', - inclusive: check.inclusive, - message: check.message, - }); - status.dirty(); - } - } else if (check.kind === 'max') { - const tooBig = check.inclusive - ? input.data > check.value - : input.data >= check.value; - if (tooBig) { - ctx = this._getOrReturnCtx(input, ctx); - addIssueToContext(ctx, { - code: ZodIssueCode.too_big, - maximum: check.value, - type: 'number', - inclusive: check.inclusive, - message: check.message, - }); - status.dirty(); - } - } else if (check.kind === 'multipleOf') { - if (floatSafeRemainder(input.data, check.value) !== 0) { - ctx = this._getOrReturnCtx(input, ctx); - addIssueToContext(ctx, { - code: ZodIssueCode.not_multiple_of, - multipleOf: check.value, - message: check.message, - }); - status.dirty(); - } - } else { - util.assertNever(check); - } - } - return { status: status.value, value: input.data }; - } - gte(value, message) { - return this.setLimit('min', value, true, errorUtil.toString(message)); - } - gt(value, message) { - return this.setLimit('min', value, false, errorUtil.toString(message)); - } - lte(value, message) { - return this.setLimit('max', value, true, errorUtil.toString(message)); - } - lt(value, message) { - return this.setLimit('max', value, false, errorUtil.toString(message)); - } - setLimit(kind, value, inclusive, message) { - return new ZodNumber({ - ...this._def, - checks: [ - ...this._def.checks, - { - kind, - value, - inclusive, - message: errorUtil.toString(message), - }, - ], - }); - } - _addCheck(check) { - return new ZodNumber({ - ...this._def, - checks: [...this._def.checks, check], - }); - } - int(message) { - return this._addCheck({ - kind: 'int', - message: errorUtil.toString(message), - }); - } - positive(message) { - return this._addCheck({ - kind: 'min', - value: 0, - inclusive: false, - message: errorUtil.toString(message), - }); - } - negative(message) { - return this._addCheck({ - kind: 'max', - value: 0, - inclusive: false, - message: errorUtil.toString(message), - }); - } - nonpositive(message) { - return this._addCheck({ - kind: 'max', - value: 0, - inclusive: true, - message: errorUtil.toString(message), - }); - } - nonnegative(message) { - return this._addCheck({ - kind: 'min', - value: 0, - inclusive: true, - message: errorUtil.toString(message), - }); - } - multipleOf(value, message) { - return this._addCheck({ - kind: 'multipleOf', - value, - message: errorUtil.toString(message), - }); - } - get minValue() { - let min = null; - for (const ch of this._def.checks) { - if (ch.kind === 'min') { - if (min === null || ch.value > min) min = ch.value; - } - } - return min; - } - get maxValue() { - let max = null; - for (const ch of this._def.checks) { - if (ch.kind === 'max') { - if (max === null || ch.value < max) max = ch.value; - } - } - return max; - } - get isInt() { - return !!this._def.checks.find((ch) => ch.kind === 'int'); - } + constructor() { + super(...arguments); + this.min = this.gte; + this.max = this.lte; + this.step = this.multipleOf; + } + _parse(input) { + const parsedType = this._getType(input); + if (parsedType !== ZodParsedType.number) { + const ctx2 = this._getOrReturnCtx(input); + addIssueToContext(ctx2, { + code: ZodIssueCode.invalid_type, + expected: ZodParsedType.number, + received: ctx2.parsedType, + }); + return INVALID; + } + let ctx = void 0; + const status = new ParseStatus(); + for (const check of this._def.checks) { + if (check.kind === "int") { + if (!util.isInteger(input.data)) { + ctx = this._getOrReturnCtx(input, ctx); + addIssueToContext(ctx, { + code: ZodIssueCode.invalid_type, + expected: "integer", + received: "float", + message: check.message, + }); + status.dirty(); + } + } else if (check.kind === "min") { + const tooSmall = check.inclusive + ? input.data < check.value + : input.data <= check.value; + if (tooSmall) { + ctx = this._getOrReturnCtx(input, ctx); + addIssueToContext(ctx, { + code: ZodIssueCode.too_small, + minimum: check.value, + type: "number", + inclusive: check.inclusive, + message: check.message, + }); + status.dirty(); + } + } else if (check.kind === "max") { + const tooBig = check.inclusive + ? input.data > check.value + : input.data >= check.value; + if (tooBig) { + ctx = this._getOrReturnCtx(input, ctx); + addIssueToContext(ctx, { + code: ZodIssueCode.too_big, + maximum: check.value, + type: "number", + inclusive: check.inclusive, + message: check.message, + }); + status.dirty(); + } + } else if (check.kind === "multipleOf") { + if (floatSafeRemainder(input.data, check.value) !== 0) { + ctx = this._getOrReturnCtx(input, ctx); + addIssueToContext(ctx, { + code: ZodIssueCode.not_multiple_of, + multipleOf: check.value, + message: check.message, + }); + status.dirty(); + } + } else { + util.assertNever(check); + } + } + return { status: status.value, value: input.data }; + } + gte(value, message) { + return this.setLimit("min", value, true, errorUtil.toString(message)); + } + gt(value, message) { + return this.setLimit("min", value, false, errorUtil.toString(message)); + } + lte(value, message) { + return this.setLimit("max", value, true, errorUtil.toString(message)); + } + lt(value, message) { + return this.setLimit("max", value, false, errorUtil.toString(message)); + } + setLimit(kind, value, inclusive, message) { + return new ZodNumber({ + ...this._def, + checks: [ + ...this._def.checks, + { + kind, + value, + inclusive, + message: errorUtil.toString(message), + }, + ], + }); + } + _addCheck(check) { + return new ZodNumber({ + ...this._def, + checks: [...this._def.checks, check], + }); + } + int(message) { + return this._addCheck({ + kind: "int", + message: errorUtil.toString(message), + }); + } + positive(message) { + return this._addCheck({ + kind: "min", + value: 0, + inclusive: false, + message: errorUtil.toString(message), + }); + } + negative(message) { + return this._addCheck({ + kind: "max", + value: 0, + inclusive: false, + message: errorUtil.toString(message), + }); + } + nonpositive(message) { + return this._addCheck({ + kind: "max", + value: 0, + inclusive: true, + message: errorUtil.toString(message), + }); + } + nonnegative(message) { + return this._addCheck({ + kind: "min", + value: 0, + inclusive: true, + message: errorUtil.toString(message), + }); + } + multipleOf(value, message) { + return this._addCheck({ + kind: "multipleOf", + value, + message: errorUtil.toString(message), + }); + } + get minValue() { + let min = null; + for (const ch of this._def.checks) { + if (ch.kind === "min") { + if (min === null || ch.value > min) min = ch.value; + } + } + return min; + } + get maxValue() { + let max = null; + for (const ch of this._def.checks) { + if (ch.kind === "max") { + if (max === null || ch.value < max) max = ch.value; + } + } + return max; + } + get isInt() { + return !!this._def.checks.find((ch) => ch.kind === "int"); + } }; ZodNumber.create = (params) => { - return new ZodNumber({ - checks: [], - typeName: ZodFirstPartyTypeKind.ZodNumber, - ...processCreateParams(params), - }); + return new ZodNumber({ + checks: [], + typeName: ZodFirstPartyTypeKind.ZodNumber, + ...processCreateParams(params), + }); }; var ZodBigInt = class extends ZodType { - _parse(input) { - const parsedType = this._getType(input); - if (parsedType !== ZodParsedType.bigint) { - const ctx = this._getOrReturnCtx(input); - addIssueToContext(ctx, { - code: ZodIssueCode.invalid_type, - expected: ZodParsedType.bigint, - received: ctx.parsedType, - }); - return INVALID; - } - return OK(input.data); - } + _parse(input) { + const parsedType = this._getType(input); + if (parsedType !== ZodParsedType.bigint) { + const ctx = this._getOrReturnCtx(input); + addIssueToContext(ctx, { + code: ZodIssueCode.invalid_type, + expected: ZodParsedType.bigint, + received: ctx.parsedType, + }); + return INVALID; + } + return OK(input.data); + } }; ZodBigInt.create = (params) => { - return new ZodBigInt({ - typeName: ZodFirstPartyTypeKind.ZodBigInt, - ...processCreateParams(params), - }); + return new ZodBigInt({ + typeName: ZodFirstPartyTypeKind.ZodBigInt, + ...processCreateParams(params), + }); }; var ZodBoolean = class extends ZodType { - _parse(input) { - const parsedType = this._getType(input); - if (parsedType !== ZodParsedType.boolean) { - const ctx = this._getOrReturnCtx(input); - addIssueToContext(ctx, { - code: ZodIssueCode.invalid_type, - expected: ZodParsedType.boolean, - received: ctx.parsedType, - }); - return INVALID; - } - return OK(input.data); - } + _parse(input) { + const parsedType = this._getType(input); + if (parsedType !== ZodParsedType.boolean) { + const ctx = this._getOrReturnCtx(input); + addIssueToContext(ctx, { + code: ZodIssueCode.invalid_type, + expected: ZodParsedType.boolean, + received: ctx.parsedType, + }); + return INVALID; + } + return OK(input.data); + } }; ZodBoolean.create = (params) => { - return new ZodBoolean({ - typeName: ZodFirstPartyTypeKind.ZodBoolean, - ...processCreateParams(params), - }); + return new ZodBoolean({ + typeName: ZodFirstPartyTypeKind.ZodBoolean, + ...processCreateParams(params), + }); }; var ZodDate = class extends ZodType { - _parse(input) { - const parsedType = this._getType(input); - if (parsedType !== ZodParsedType.date) { - const ctx2 = this._getOrReturnCtx(input); - addIssueToContext(ctx2, { - code: ZodIssueCode.invalid_type, - expected: ZodParsedType.date, - received: ctx2.parsedType, - }); - return INVALID; - } - if (isNaN(input.data.getTime())) { - const ctx2 = this._getOrReturnCtx(input); - addIssueToContext(ctx2, { - code: ZodIssueCode.invalid_date, - }); - return INVALID; - } - const status = new ParseStatus(); - let ctx = void 0; - for (const check of this._def.checks) { - if (check.kind === 'min') { - if (input.data.getTime() < check.value) { - ctx = this._getOrReturnCtx(input, ctx); - addIssueToContext(ctx, { - code: ZodIssueCode.too_small, - message: check.message, - inclusive: true, - minimum: check.value, - type: 'date', - }); - status.dirty(); - } - } else if (check.kind === 'max') { - if (input.data.getTime() > check.value) { - ctx = this._getOrReturnCtx(input, ctx); - addIssueToContext(ctx, { - code: ZodIssueCode.too_big, - message: check.message, - inclusive: true, - maximum: check.value, - type: 'date', - }); - status.dirty(); - } - } else { - util.assertNever(check); - } - } - return { - status: status.value, - value: new Date(input.data.getTime()), - }; - } - _addCheck(check) { - return new ZodDate({ - ...this._def, - checks: [...this._def.checks, check], - }); - } - min(minDate, message) { - return this._addCheck({ - kind: 'min', - value: minDate.getTime(), - message: errorUtil.toString(message), - }); - } - max(maxDate, message) { - return this._addCheck({ - kind: 'max', - value: maxDate.getTime(), - message: errorUtil.toString(message), - }); - } - get minDate() { - let min = null; - for (const ch of this._def.checks) { - if (ch.kind === 'min') { - if (min === null || ch.value > min) min = ch.value; - } - } - return min != null ? new Date(min) : null; - } - get maxDate() { - let max = null; - for (const ch of this._def.checks) { - if (ch.kind === 'max') { - if (max === null || ch.value < max) max = ch.value; - } - } - return max != null ? new Date(max) : null; - } + _parse(input) { + const parsedType = this._getType(input); + if (parsedType !== ZodParsedType.date) { + const ctx2 = this._getOrReturnCtx(input); + addIssueToContext(ctx2, { + code: ZodIssueCode.invalid_type, + expected: ZodParsedType.date, + received: ctx2.parsedType, + }); + return INVALID; + } + if (isNaN(input.data.getTime())) { + const ctx2 = this._getOrReturnCtx(input); + addIssueToContext(ctx2, { + code: ZodIssueCode.invalid_date, + }); + return INVALID; + } + const status = new ParseStatus(); + let ctx = void 0; + for (const check of this._def.checks) { + if (check.kind === "min") { + if (input.data.getTime() < check.value) { + ctx = this._getOrReturnCtx(input, ctx); + addIssueToContext(ctx, { + code: ZodIssueCode.too_small, + message: check.message, + inclusive: true, + minimum: check.value, + type: "date", + }); + status.dirty(); + } + } else if (check.kind === "max") { + if (input.data.getTime() > check.value) { + ctx = this._getOrReturnCtx(input, ctx); + addIssueToContext(ctx, { + code: ZodIssueCode.too_big, + message: check.message, + inclusive: true, + maximum: check.value, + type: "date", + }); + status.dirty(); + } + } else { + util.assertNever(check); + } + } + return { + status: status.value, + value: new Date(input.data.getTime()), + }; + } + _addCheck(check) { + return new ZodDate({ + ...this._def, + checks: [...this._def.checks, check], + }); + } + min(minDate, message) { + return this._addCheck({ + kind: "min", + value: minDate.getTime(), + message: errorUtil.toString(message), + }); + } + max(maxDate, message) { + return this._addCheck({ + kind: "max", + value: maxDate.getTime(), + message: errorUtil.toString(message), + }); + } + get minDate() { + let min = null; + for (const ch of this._def.checks) { + if (ch.kind === "min") { + if (min === null || ch.value > min) min = ch.value; + } + } + return min != null ? new Date(min) : null; + } + get maxDate() { + let max = null; + for (const ch of this._def.checks) { + if (ch.kind === "max") { + if (max === null || ch.value < max) max = ch.value; + } + } + return max != null ? new Date(max) : null; + } }; ZodDate.create = (params) => { - return new ZodDate({ - checks: [], - typeName: ZodFirstPartyTypeKind.ZodDate, - ...processCreateParams(params), - }); + return new ZodDate({ + checks: [], + typeName: ZodFirstPartyTypeKind.ZodDate, + ...processCreateParams(params), + }); }; var ZodUndefined = class extends ZodType { - _parse(input) { - const parsedType = this._getType(input); - if (parsedType !== ZodParsedType.undefined) { - const ctx = this._getOrReturnCtx(input); - addIssueToContext(ctx, { - code: ZodIssueCode.invalid_type, - expected: ZodParsedType.undefined, - received: ctx.parsedType, - }); - return INVALID; - } - return OK(input.data); - } + _parse(input) { + const parsedType = this._getType(input); + if (parsedType !== ZodParsedType.undefined) { + const ctx = this._getOrReturnCtx(input); + addIssueToContext(ctx, { + code: ZodIssueCode.invalid_type, + expected: ZodParsedType.undefined, + received: ctx.parsedType, + }); + return INVALID; + } + return OK(input.data); + } }; ZodUndefined.create = (params) => { - return new ZodUndefined({ - typeName: ZodFirstPartyTypeKind.ZodUndefined, - ...processCreateParams(params), - }); + return new ZodUndefined({ + typeName: ZodFirstPartyTypeKind.ZodUndefined, + ...processCreateParams(params), + }); }; var ZodNull = class extends ZodType { - _parse(input) { - const parsedType = this._getType(input); - if (parsedType !== ZodParsedType.null) { - const ctx = this._getOrReturnCtx(input); - addIssueToContext(ctx, { - code: ZodIssueCode.invalid_type, - expected: ZodParsedType.null, - received: ctx.parsedType, - }); - return INVALID; - } - return OK(input.data); - } + _parse(input) { + const parsedType = this._getType(input); + if (parsedType !== ZodParsedType.null) { + const ctx = this._getOrReturnCtx(input); + addIssueToContext(ctx, { + code: ZodIssueCode.invalid_type, + expected: ZodParsedType.null, + received: ctx.parsedType, + }); + return INVALID; + } + return OK(input.data); + } }; ZodNull.create = (params) => { - return new ZodNull({ - typeName: ZodFirstPartyTypeKind.ZodNull, - ...processCreateParams(params), - }); + return new ZodNull({ + typeName: ZodFirstPartyTypeKind.ZodNull, + ...processCreateParams(params), + }); }; var ZodAny = class extends ZodType { - constructor() { - super(...arguments); - this._any = true; - } - _parse(input) { - return OK(input.data); - } + constructor() { + super(...arguments); + this._any = true; + } + _parse(input) { + return OK(input.data); + } }; ZodAny.create = (params) => { - return new ZodAny({ - typeName: ZodFirstPartyTypeKind.ZodAny, - ...processCreateParams(params), - }); + return new ZodAny({ + typeName: ZodFirstPartyTypeKind.ZodAny, + ...processCreateParams(params), + }); }; var ZodUnknown = class extends ZodType { - constructor() { - super(...arguments); - this._unknown = true; - } - _parse(input) { - return OK(input.data); - } + constructor() { + super(...arguments); + this._unknown = true; + } + _parse(input) { + return OK(input.data); + } }; ZodUnknown.create = (params) => { - return new ZodUnknown({ - typeName: ZodFirstPartyTypeKind.ZodUnknown, - ...processCreateParams(params), - }); + return new ZodUnknown({ + typeName: ZodFirstPartyTypeKind.ZodUnknown, + ...processCreateParams(params), + }); }; var ZodNever = class extends ZodType { - _parse(input) { - const ctx = this._getOrReturnCtx(input); - addIssueToContext(ctx, { - code: ZodIssueCode.invalid_type, - expected: ZodParsedType.never, - received: ctx.parsedType, - }); - return INVALID; - } + _parse(input) { + const ctx = this._getOrReturnCtx(input); + addIssueToContext(ctx, { + code: ZodIssueCode.invalid_type, + expected: ZodParsedType.never, + received: ctx.parsedType, + }); + return INVALID; + } }; ZodNever.create = (params) => { - return new ZodNever({ - typeName: ZodFirstPartyTypeKind.ZodNever, - ...processCreateParams(params), - }); + return new ZodNever({ + typeName: ZodFirstPartyTypeKind.ZodNever, + ...processCreateParams(params), + }); }; var ZodVoid = class extends ZodType { - _parse(input) { - const parsedType = this._getType(input); - if (parsedType !== ZodParsedType.undefined) { - const ctx = this._getOrReturnCtx(input); - addIssueToContext(ctx, { - code: ZodIssueCode.invalid_type, - expected: ZodParsedType.void, - received: ctx.parsedType, - }); - return INVALID; - } - return OK(input.data); - } + _parse(input) { + const parsedType = this._getType(input); + if (parsedType !== ZodParsedType.undefined) { + const ctx = this._getOrReturnCtx(input); + addIssueToContext(ctx, { + code: ZodIssueCode.invalid_type, + expected: ZodParsedType.void, + received: ctx.parsedType, + }); + return INVALID; + } + return OK(input.data); + } }; ZodVoid.create = (params) => { - return new ZodVoid({ - typeName: ZodFirstPartyTypeKind.ZodVoid, - ...processCreateParams(params), - }); + return new ZodVoid({ + typeName: ZodFirstPartyTypeKind.ZodVoid, + ...processCreateParams(params), + }); }; var ZodArray = class extends ZodType { - _parse(input) { - const { ctx, status } = this._processInputParams(input); - const def = this._def; - if (ctx.parsedType !== ZodParsedType.array) { - addIssueToContext(ctx, { - code: ZodIssueCode.invalid_type, - expected: ZodParsedType.array, - received: ctx.parsedType, - }); - return INVALID; - } - if (def.minLength !== null) { - if (ctx.data.length < def.minLength.value) { - addIssueToContext(ctx, { - code: ZodIssueCode.too_small, - minimum: def.minLength.value, - type: 'array', - inclusive: true, - message: def.minLength.message, - }); - status.dirty(); - } - } - if (def.maxLength !== null) { - if (ctx.data.length > def.maxLength.value) { - addIssueToContext(ctx, { - code: ZodIssueCode.too_big, - maximum: def.maxLength.value, - type: 'array', - inclusive: true, - message: def.maxLength.message, - }); - status.dirty(); - } - } - if (ctx.common.async) { - return Promise.all( - ctx.data.map((item, i) => { - return def.type._parseAsync( - new ParseInputLazyPath(ctx, item, ctx.path, i) - ); - }) - ).then((result2) => { - return ParseStatus.mergeArray(status, result2); - }); - } - const result = ctx.data.map((item, i) => { - return def.type._parseSync( - new ParseInputLazyPath(ctx, item, ctx.path, i) - ); - }); - return ParseStatus.mergeArray(status, result); - } - get element() { - return this._def.type; - } - min(minLength, message) { - return new ZodArray({ - ...this._def, - minLength: { value: minLength, message: errorUtil.toString(message) }, - }); - } - max(maxLength, message) { - return new ZodArray({ - ...this._def, - maxLength: { value: maxLength, message: errorUtil.toString(message) }, - }); - } - length(len, message) { - return this.min(len, message).max(len, message); - } - nonempty(message) { - return this.min(1, message); - } + _parse(input) { + const { ctx, status } = this._processInputParams(input); + const def = this._def; + if (ctx.parsedType !== ZodParsedType.array) { + addIssueToContext(ctx, { + code: ZodIssueCode.invalid_type, + expected: ZodParsedType.array, + received: ctx.parsedType, + }); + return INVALID; + } + if (def.minLength !== null) { + if (ctx.data.length < def.minLength.value) { + addIssueToContext(ctx, { + code: ZodIssueCode.too_small, + minimum: def.minLength.value, + type: "array", + inclusive: true, + message: def.minLength.message, + }); + status.dirty(); + } + } + if (def.maxLength !== null) { + if (ctx.data.length > def.maxLength.value) { + addIssueToContext(ctx, { + code: ZodIssueCode.too_big, + maximum: def.maxLength.value, + type: "array", + inclusive: true, + message: def.maxLength.message, + }); + status.dirty(); + } + } + if (ctx.common.async) { + return Promise.all( + ctx.data.map((item, i) => { + return def.type._parseAsync( + new ParseInputLazyPath(ctx, item, ctx.path, i) + ); + }) + ).then((result2) => { + return ParseStatus.mergeArray(status, result2); + }); + } + const result = ctx.data.map((item, i) => { + return def.type._parseSync( + new ParseInputLazyPath(ctx, item, ctx.path, i) + ); + }); + return ParseStatus.mergeArray(status, result); + } + get element() { + return this._def.type; + } + min(minLength, message) { + return new ZodArray({ + ...this._def, + minLength: { + value: minLength, + message: errorUtil.toString(message), + }, + }); + } + max(maxLength, message) { + return new ZodArray({ + ...this._def, + maxLength: { + value: maxLength, + message: errorUtil.toString(message), + }, + }); + } + length(len, message) { + return this.min(len, message).max(len, message); + } + nonempty(message) { + return this.min(1, message); + } }; ZodArray.create = (schema, params) => { - return new ZodArray({ - type: schema, - minLength: null, - maxLength: null, - typeName: ZodFirstPartyTypeKind.ZodArray, - ...processCreateParams(params), - }); + return new ZodArray({ + type: schema, + minLength: null, + maxLength: null, + typeName: ZodFirstPartyTypeKind.ZodArray, + ...processCreateParams(params), + }); }; var objectUtil; (function (objectUtil2) { - objectUtil2.mergeShapes = (first, second) => { - return { - ...first, - ...second, - }; - }; + objectUtil2.mergeShapes = (first, second) => { + return { + ...first, + ...second, + }; + }; })(objectUtil || (objectUtil = {})); var AugmentFactory = (def) => (augmentation) => { - return new ZodObject({ - ...def, - shape: () => ({ - ...def.shape(), - ...augmentation, - }), - }); + return new ZodObject({ + ...def, + shape: () => ({ + ...def.shape(), + ...augmentation, + }), + }); }; function deepPartialify(schema) { - if (schema instanceof ZodObject) { - const newShape = {}; - for (const key in schema.shape) { - const fieldSchema = schema.shape[key]; - newShape[key] = ZodOptional.create(deepPartialify(fieldSchema)); - } - return new ZodObject({ - ...schema._def, - shape: () => newShape, - }); - } else if (schema instanceof ZodArray) { - return ZodArray.create(deepPartialify(schema.element)); - } else if (schema instanceof ZodOptional) { - return ZodOptional.create(deepPartialify(schema.unwrap())); - } else if (schema instanceof ZodNullable) { - return ZodNullable.create(deepPartialify(schema.unwrap())); - } else if (schema instanceof ZodTuple) { - return ZodTuple.create(schema.items.map((item) => deepPartialify(item))); - } else { - return schema; - } + if (schema instanceof ZodObject) { + const newShape = {}; + for (const key in schema.shape) { + const fieldSchema = schema.shape[key]; + newShape[key] = ZodOptional.create(deepPartialify(fieldSchema)); + } + return new ZodObject({ + ...schema._def, + shape: () => newShape, + }); + } else if (schema instanceof ZodArray) { + return ZodArray.create(deepPartialify(schema.element)); + } else if (schema instanceof ZodOptional) { + return ZodOptional.create(deepPartialify(schema.unwrap())); + } else if (schema instanceof ZodNullable) { + return ZodNullable.create(deepPartialify(schema.unwrap())); + } else if (schema instanceof ZodTuple) { + return ZodTuple.create( + schema.items.map((item) => deepPartialify(item)) + ); + } else { + return schema; + } } var ZodObject = class extends ZodType { - constructor() { - super(...arguments); - this._cached = null; - this.nonstrict = this.passthrough; - this.augment = AugmentFactory(this._def); - this.extend = AugmentFactory(this._def); - } - _getCached() { - if (this._cached !== null) return this._cached; - const shape = this._def.shape(); - const keys = util.objectKeys(shape); - return (this._cached = { shape, keys }); - } - _parse(input) { - const parsedType = this._getType(input); - if (parsedType !== ZodParsedType.object) { - const ctx2 = this._getOrReturnCtx(input); - addIssueToContext(ctx2, { - code: ZodIssueCode.invalid_type, - expected: ZodParsedType.object, - received: ctx2.parsedType, - }); - return INVALID; - } - const { status, ctx } = this._processInputParams(input); - const { shape, keys: shapeKeys } = this._getCached(); - const extraKeys = []; - if ( - !( - this._def.catchall instanceof ZodNever && - this._def.unknownKeys === 'strip' - ) - ) { - for (const key in ctx.data) { - if (!shapeKeys.includes(key)) { - extraKeys.push(key); - } - } - } - const pairs = []; - for (const key of shapeKeys) { - const keyValidator = shape[key]; - const value = ctx.data[key]; - pairs.push({ - key: { status: 'valid', value: key }, - value: keyValidator._parse( - new ParseInputLazyPath(ctx, value, ctx.path, key) - ), - alwaysSet: key in ctx.data, - }); - } - if (this._def.catchall instanceof ZodNever) { - const unknownKeys = this._def.unknownKeys; - if (unknownKeys === 'passthrough') { - for (const key of extraKeys) { - pairs.push({ - key: { status: 'valid', value: key }, - value: { status: 'valid', value: ctx.data[key] }, - }); - } - } else if (unknownKeys === 'strict') { - if (extraKeys.length > 0) { - addIssueToContext(ctx, { - code: ZodIssueCode.unrecognized_keys, - keys: extraKeys, - }); - status.dirty(); - } - } else if (unknownKeys === 'strip'); - else { - throw new Error(`Internal ZodObject error: invalid unknownKeys value.`); - } - } else { - const catchall = this._def.catchall; - for (const key of extraKeys) { - const value = ctx.data[key]; - pairs.push({ - key: { status: 'valid', value: key }, - value: catchall._parse( - new ParseInputLazyPath(ctx, value, ctx.path, key) - ), - alwaysSet: key in ctx.data, - }); - } - } - if (ctx.common.async) { - return Promise.resolve() - .then(async () => { - const syncPairs = []; - for (const pair of pairs) { - const key = await pair.key; - syncPairs.push({ - key, - value: await pair.value, - alwaysSet: pair.alwaysSet, - }); - } - return syncPairs; - }) - .then((syncPairs) => { - return ParseStatus.mergeObjectSync(status, syncPairs); - }); - } else { - return ParseStatus.mergeObjectSync(status, pairs); - } - } - get shape() { - return this._def.shape(); - } - strict(message) { - errorUtil.errToObj; - return new ZodObject({ - ...this._def, - unknownKeys: 'strict', - ...(message !== void 0 - ? { - errorMap: (issue, ctx) => { - var _a, _b, _c, _d; - const defaultError = - (_c = - (_b = (_a = this._def).errorMap) === null || _b === void 0 - ? void 0 - : _b.call(_a, issue, ctx).message) !== null && _c !== void 0 - ? _c - : ctx.defaultError; - if (issue.code === 'unrecognized_keys') - return { - message: - (_d = errorUtil.errToObj(message).message) !== null && - _d !== void 0 - ? _d - : defaultError, - }; - return { - message: defaultError, - }; - }, - } - : {}), - }); - } - strip() { - return new ZodObject({ - ...this._def, - unknownKeys: 'strip', - }); - } - passthrough() { - return new ZodObject({ - ...this._def, - unknownKeys: 'passthrough', - }); - } - setKey(key, schema) { - return this.augment({ [key]: schema }); - } - merge(merging) { - const merged = new ZodObject({ - unknownKeys: merging._def.unknownKeys, - catchall: merging._def.catchall, - shape: () => - objectUtil.mergeShapes(this._def.shape(), merging._def.shape()), - typeName: ZodFirstPartyTypeKind.ZodObject, - }); - return merged; - } - catchall(index) { - return new ZodObject({ - ...this._def, - catchall: index, - }); - } - pick(mask) { - const shape = {}; - util.objectKeys(mask).map((key) => { - if (this.shape[key]) shape[key] = this.shape[key]; - }); - return new ZodObject({ - ...this._def, - shape: () => shape, - }); - } - omit(mask) { - const shape = {}; - util.objectKeys(this.shape).map((key) => { - if (util.objectKeys(mask).indexOf(key) === -1) { - shape[key] = this.shape[key]; - } - }); - return new ZodObject({ - ...this._def, - shape: () => shape, - }); - } - deepPartial() { - return deepPartialify(this); - } - partial(mask) { - const newShape = {}; - if (mask) { - util.objectKeys(this.shape).map((key) => { - if (util.objectKeys(mask).indexOf(key) === -1) { - newShape[key] = this.shape[key]; - } else { - newShape[key] = this.shape[key].optional(); - } - }); - return new ZodObject({ - ...this._def, - shape: () => newShape, - }); - } else { - for (const key in this.shape) { - const fieldSchema = this.shape[key]; - newShape[key] = fieldSchema.optional(); - } - } - return new ZodObject({ - ...this._def, - shape: () => newShape, - }); - } - required() { - const newShape = {}; - for (const key in this.shape) { - const fieldSchema = this.shape[key]; - let newField = fieldSchema; - while (newField instanceof ZodOptional) { - newField = newField._def.innerType; - } - newShape[key] = newField; - } - return new ZodObject({ - ...this._def, - shape: () => newShape, - }); - } - keyof() { - return createZodEnum(util.objectKeys(this.shape)); - } + constructor() { + super(...arguments); + this._cached = null; + this.nonstrict = this.passthrough; + this.augment = AugmentFactory(this._def); + this.extend = AugmentFactory(this._def); + } + _getCached() { + if (this._cached !== null) return this._cached; + const shape = this._def.shape(); + const keys = util.objectKeys(shape); + return (this._cached = { shape, keys }); + } + _parse(input) { + const parsedType = this._getType(input); + if (parsedType !== ZodParsedType.object) { + const ctx2 = this._getOrReturnCtx(input); + addIssueToContext(ctx2, { + code: ZodIssueCode.invalid_type, + expected: ZodParsedType.object, + received: ctx2.parsedType, + }); + return INVALID; + } + const { status, ctx } = this._processInputParams(input); + const { shape, keys: shapeKeys } = this._getCached(); + const extraKeys = []; + if ( + !( + this._def.catchall instanceof ZodNever && + this._def.unknownKeys === "strip" + ) + ) { + for (const key in ctx.data) { + if (!shapeKeys.includes(key)) { + extraKeys.push(key); + } + } + } + const pairs = []; + for (const key of shapeKeys) { + const keyValidator = shape[key]; + const value = ctx.data[key]; + pairs.push({ + key: { status: "valid", value: key }, + value: keyValidator._parse( + new ParseInputLazyPath(ctx, value, ctx.path, key) + ), + alwaysSet: key in ctx.data, + }); + } + if (this._def.catchall instanceof ZodNever) { + const unknownKeys = this._def.unknownKeys; + if (unknownKeys === "passthrough") { + for (const key of extraKeys) { + pairs.push({ + key: { status: "valid", value: key }, + value: { status: "valid", value: ctx.data[key] }, + }); + } + } else if (unknownKeys === "strict") { + if (extraKeys.length > 0) { + addIssueToContext(ctx, { + code: ZodIssueCode.unrecognized_keys, + keys: extraKeys, + }); + status.dirty(); + } + } else if (unknownKeys === "strip"); + else { + throw new Error( + `Internal ZodObject error: invalid unknownKeys value.` + ); + } + } else { + const catchall = this._def.catchall; + for (const key of extraKeys) { + const value = ctx.data[key]; + pairs.push({ + key: { status: "valid", value: key }, + value: catchall._parse( + new ParseInputLazyPath(ctx, value, ctx.path, key) + ), + alwaysSet: key in ctx.data, + }); + } + } + if (ctx.common.async) { + return Promise.resolve() + .then(async () => { + const syncPairs = []; + for (const pair of pairs) { + const key = await pair.key; + syncPairs.push({ + key, + value: await pair.value, + alwaysSet: pair.alwaysSet, + }); + } + return syncPairs; + }) + .then((syncPairs) => { + return ParseStatus.mergeObjectSync(status, syncPairs); + }); + } else { + return ParseStatus.mergeObjectSync(status, pairs); + } + } + get shape() { + return this._def.shape(); + } + strict(message) { + errorUtil.errToObj; + return new ZodObject({ + ...this._def, + unknownKeys: "strict", + ...(message !== void 0 + ? { + errorMap: (issue, ctx) => { + var _a, _b, _c, _d; + const defaultError = + (_c = + (_b = (_a = this._def).errorMap) === null || + _b === void 0 + ? void 0 + : _b.call(_a, issue, ctx).message) !== + null && _c !== void 0 + ? _c + : ctx.defaultError; + if (issue.code === "unrecognized_keys") + return { + message: + (_d = + errorUtil.errToObj( + message + ).message) !== null && _d !== void 0 + ? _d + : defaultError, + }; + return { + message: defaultError, + }; + }, + } + : {}), + }); + } + strip() { + return new ZodObject({ + ...this._def, + unknownKeys: "strip", + }); + } + passthrough() { + return new ZodObject({ + ...this._def, + unknownKeys: "passthrough", + }); + } + setKey(key, schema) { + return this.augment({ [key]: schema }); + } + merge(merging) { + const merged = new ZodObject({ + unknownKeys: merging._def.unknownKeys, + catchall: merging._def.catchall, + shape: () => + objectUtil.mergeShapes(this._def.shape(), merging._def.shape()), + typeName: ZodFirstPartyTypeKind.ZodObject, + }); + return merged; + } + catchall(index) { + return new ZodObject({ + ...this._def, + catchall: index, + }); + } + pick(mask) { + const shape = {}; + util.objectKeys(mask).map((key) => { + if (this.shape[key]) shape[key] = this.shape[key]; + }); + return new ZodObject({ + ...this._def, + shape: () => shape, + }); + } + omit(mask) { + const shape = {}; + util.objectKeys(this.shape).map((key) => { + if (util.objectKeys(mask).indexOf(key) === -1) { + shape[key] = this.shape[key]; + } + }); + return new ZodObject({ + ...this._def, + shape: () => shape, + }); + } + deepPartial() { + return deepPartialify(this); + } + partial(mask) { + const newShape = {}; + if (mask) { + util.objectKeys(this.shape).map((key) => { + if (util.objectKeys(mask).indexOf(key) === -1) { + newShape[key] = this.shape[key]; + } else { + newShape[key] = this.shape[key].optional(); + } + }); + return new ZodObject({ + ...this._def, + shape: () => newShape, + }); + } else { + for (const key in this.shape) { + const fieldSchema = this.shape[key]; + newShape[key] = fieldSchema.optional(); + } + } + return new ZodObject({ + ...this._def, + shape: () => newShape, + }); + } + required() { + const newShape = {}; + for (const key in this.shape) { + const fieldSchema = this.shape[key]; + let newField = fieldSchema; + while (newField instanceof ZodOptional) { + newField = newField._def.innerType; + } + newShape[key] = newField; + } + return new ZodObject({ + ...this._def, + shape: () => newShape, + }); + } + keyof() { + return createZodEnum(util.objectKeys(this.shape)); + } }; ZodObject.create = (shape, params) => { - return new ZodObject({ - shape: () => shape, - unknownKeys: 'strip', - catchall: ZodNever.create(), - typeName: ZodFirstPartyTypeKind.ZodObject, - ...processCreateParams(params), - }); + return new ZodObject({ + shape: () => shape, + unknownKeys: "strip", + catchall: ZodNever.create(), + typeName: ZodFirstPartyTypeKind.ZodObject, + ...processCreateParams(params), + }); }; ZodObject.strictCreate = (shape, params) => { - return new ZodObject({ - shape: () => shape, - unknownKeys: 'strict', - catchall: ZodNever.create(), - typeName: ZodFirstPartyTypeKind.ZodObject, - ...processCreateParams(params), - }); + return new ZodObject({ + shape: () => shape, + unknownKeys: "strict", + catchall: ZodNever.create(), + typeName: ZodFirstPartyTypeKind.ZodObject, + ...processCreateParams(params), + }); }; ZodObject.lazycreate = (shape, params) => { - return new ZodObject({ - shape, - unknownKeys: 'strip', - catchall: ZodNever.create(), - typeName: ZodFirstPartyTypeKind.ZodObject, - ...processCreateParams(params), - }); + return new ZodObject({ + shape, + unknownKeys: "strip", + catchall: ZodNever.create(), + typeName: ZodFirstPartyTypeKind.ZodObject, + ...processCreateParams(params), + }); }; var ZodUnion = class extends ZodType { - _parse(input) { - const { ctx } = this._processInputParams(input); - const options = this._def.options; - function handleResults(results) { - for (const result of results) { - if (result.result.status === 'valid') { - return result.result; - } - } - for (const result of results) { - if (result.result.status === 'dirty') { - ctx.common.issues.push(...result.ctx.common.issues); - return result.result; - } - } - const unionErrors = results.map( - (result) => new ZodError(result.ctx.common.issues) - ); - addIssueToContext(ctx, { - code: ZodIssueCode.invalid_union, - unionErrors, - }); - return INVALID; - } - if (ctx.common.async) { - return Promise.all( - options.map(async (option) => { - const childCtx = { - ...ctx, - common: { - ...ctx.common, - issues: [], - }, - parent: null, - }; - return { - result: await option._parseAsync({ - data: ctx.data, - path: ctx.path, - parent: childCtx, - }), - ctx: childCtx, - }; - }) - ).then(handleResults); - } else { - let dirty = void 0; - const issues = []; - for (const option of options) { - const childCtx = { - ...ctx, - common: { - ...ctx.common, - issues: [], - }, - parent: null, - }; - const result = option._parseSync({ - data: ctx.data, - path: ctx.path, - parent: childCtx, - }); - if (result.status === 'valid') { - return result; - } else if (result.status === 'dirty' && !dirty) { - dirty = { result, ctx: childCtx }; - } - if (childCtx.common.issues.length) { - issues.push(childCtx.common.issues); - } - } - if (dirty) { - ctx.common.issues.push(...dirty.ctx.common.issues); - return dirty.result; - } - const unionErrors = issues.map((issues2) => new ZodError(issues2)); - addIssueToContext(ctx, { - code: ZodIssueCode.invalid_union, - unionErrors, - }); - return INVALID; - } - } - get options() { - return this._def.options; - } + _parse(input) { + const { ctx } = this._processInputParams(input); + const options = this._def.options; + function handleResults(results) { + for (const result of results) { + if (result.result.status === "valid") { + return result.result; + } + } + for (const result of results) { + if (result.result.status === "dirty") { + ctx.common.issues.push(...result.ctx.common.issues); + return result.result; + } + } + const unionErrors = results.map( + (result) => new ZodError(result.ctx.common.issues) + ); + addIssueToContext(ctx, { + code: ZodIssueCode.invalid_union, + unionErrors, + }); + return INVALID; + } + if (ctx.common.async) { + return Promise.all( + options.map(async (option) => { + const childCtx = { + ...ctx, + common: { + ...ctx.common, + issues: [], + }, + parent: null, + }; + return { + result: await option._parseAsync({ + data: ctx.data, + path: ctx.path, + parent: childCtx, + }), + ctx: childCtx, + }; + }) + ).then(handleResults); + } else { + let dirty = void 0; + const issues = []; + for (const option of options) { + const childCtx = { + ...ctx, + common: { + ...ctx.common, + issues: [], + }, + parent: null, + }; + const result = option._parseSync({ + data: ctx.data, + path: ctx.path, + parent: childCtx, + }); + if (result.status === "valid") { + return result; + } else if (result.status === "dirty" && !dirty) { + dirty = { result, ctx: childCtx }; + } + if (childCtx.common.issues.length) { + issues.push(childCtx.common.issues); + } + } + if (dirty) { + ctx.common.issues.push(...dirty.ctx.common.issues); + return dirty.result; + } + const unionErrors = issues.map((issues2) => new ZodError(issues2)); + addIssueToContext(ctx, { + code: ZodIssueCode.invalid_union, + unionErrors, + }); + return INVALID; + } + } + get options() { + return this._def.options; + } }; ZodUnion.create = (types, params) => { - return new ZodUnion({ - options: types, - typeName: ZodFirstPartyTypeKind.ZodUnion, - ...processCreateParams(params), - }); + return new ZodUnion({ + options: types, + typeName: ZodFirstPartyTypeKind.ZodUnion, + ...processCreateParams(params), + }); }; var ZodDiscriminatedUnion = class extends ZodType { - _parse(input) { - const { ctx } = this._processInputParams(input); - if (ctx.parsedType !== ZodParsedType.object) { - addIssueToContext(ctx, { - code: ZodIssueCode.invalid_type, - expected: ZodParsedType.object, - received: ctx.parsedType, - }); - return INVALID; - } - const discriminator = this.discriminator; - const discriminatorValue = ctx.data[discriminator]; - const option = this.options.get(discriminatorValue); - if (!option) { - addIssueToContext(ctx, { - code: ZodIssueCode.invalid_union_discriminator, - options: this.validDiscriminatorValues, - path: [discriminator], - }); - return INVALID; - } - if (ctx.common.async) { - return option._parseAsync({ - data: ctx.data, - path: ctx.path, - parent: ctx, - }); - } else { - return option._parseSync({ - data: ctx.data, - path: ctx.path, - parent: ctx, - }); - } - } - get discriminator() { - return this._def.discriminator; - } - get validDiscriminatorValues() { - return Array.from(this.options.keys()); - } - get options() { - return this._def.options; - } - static create(discriminator, types, params) { - const options = /* @__PURE__ */ new Map(); - try { - types.forEach((type) => { - const discriminatorValue = type.shape[discriminator].value; - options.set(discriminatorValue, type); - }); - } catch (e) { - throw new Error( - 'The discriminator value could not be extracted from all the provided schemas' - ); - } - if (options.size !== types.length) { - throw new Error('Some of the discriminator values are not unique'); - } - return new ZodDiscriminatedUnion({ - typeName: ZodFirstPartyTypeKind.ZodDiscriminatedUnion, - discriminator, - options, - ...processCreateParams(params), - }); - } + _parse(input) { + const { ctx } = this._processInputParams(input); + if (ctx.parsedType !== ZodParsedType.object) { + addIssueToContext(ctx, { + code: ZodIssueCode.invalid_type, + expected: ZodParsedType.object, + received: ctx.parsedType, + }); + return INVALID; + } + const discriminator = this.discriminator; + const discriminatorValue = ctx.data[discriminator]; + const option = this.options.get(discriminatorValue); + if (!option) { + addIssueToContext(ctx, { + code: ZodIssueCode.invalid_union_discriminator, + options: this.validDiscriminatorValues, + path: [discriminator], + }); + return INVALID; + } + if (ctx.common.async) { + return option._parseAsync({ + data: ctx.data, + path: ctx.path, + parent: ctx, + }); + } else { + return option._parseSync({ + data: ctx.data, + path: ctx.path, + parent: ctx, + }); + } + } + get discriminator() { + return this._def.discriminator; + } + get validDiscriminatorValues() { + return Array.from(this.options.keys()); + } + get options() { + return this._def.options; + } + static create(discriminator, types, params) { + const options = /* @__PURE__ */ new Map(); + try { + types.forEach((type) => { + const discriminatorValue = type.shape[discriminator].value; + options.set(discriminatorValue, type); + }); + } catch (e) { + throw new Error( + "The discriminator value could not be extracted from all the provided schemas" + ); + } + if (options.size !== types.length) { + throw new Error("Some of the discriminator values are not unique"); + } + return new ZodDiscriminatedUnion({ + typeName: ZodFirstPartyTypeKind.ZodDiscriminatedUnion, + discriminator, + options, + ...processCreateParams(params), + }); + } }; function mergeValues(a, b) { - const aType = getParsedType(a); - const bType = getParsedType(b); - if (a === b) { - return { valid: true, data: a }; - } else if (aType === ZodParsedType.object && bType === ZodParsedType.object) { - const bKeys = util.objectKeys(b); - const sharedKeys = util - .objectKeys(a) - .filter((key) => bKeys.indexOf(key) !== -1); - const newObj = { ...a, ...b }; - for (const key of sharedKeys) { - const sharedValue = mergeValues(a[key], b[key]); - if (!sharedValue.valid) { - return { valid: false }; - } - newObj[key] = sharedValue.data; - } - return { valid: true, data: newObj }; - } else if (aType === ZodParsedType.array && bType === ZodParsedType.array) { - if (a.length !== b.length) { - return { valid: false }; - } - const newArray = []; - for (let index = 0; index < a.length; index++) { - const itemA = a[index]; - const itemB = b[index]; - const sharedValue = mergeValues(itemA, itemB); - if (!sharedValue.valid) { - return { valid: false }; - } - newArray.push(sharedValue.data); - } - return { valid: true, data: newArray }; - } else if ( - aType === ZodParsedType.date && - bType === ZodParsedType.date && - +a === +b - ) { - return { valid: true, data: a }; - } else { - return { valid: false }; - } + const aType = getParsedType(a); + const bType = getParsedType(b); + if (a === b) { + return { valid: true, data: a }; + } else if ( + aType === ZodParsedType.object && + bType === ZodParsedType.object + ) { + const bKeys = util.objectKeys(b); + const sharedKeys = util + .objectKeys(a) + .filter((key) => bKeys.indexOf(key) !== -1); + const newObj = { ...a, ...b }; + for (const key of sharedKeys) { + const sharedValue = mergeValues(a[key], b[key]); + if (!sharedValue.valid) { + return { valid: false }; + } + newObj[key] = sharedValue.data; + } + return { valid: true, data: newObj }; + } else if (aType === ZodParsedType.array && bType === ZodParsedType.array) { + if (a.length !== b.length) { + return { valid: false }; + } + const newArray = []; + for (let index = 0; index < a.length; index++) { + const itemA = a[index]; + const itemB = b[index]; + const sharedValue = mergeValues(itemA, itemB); + if (!sharedValue.valid) { + return { valid: false }; + } + newArray.push(sharedValue.data); + } + return { valid: true, data: newArray }; + } else if ( + aType === ZodParsedType.date && + bType === ZodParsedType.date && + +a === +b + ) { + return { valid: true, data: a }; + } else { + return { valid: false }; + } } var ZodIntersection = class extends ZodType { - _parse(input) { - const { status, ctx } = this._processInputParams(input); - const handleParsed = (parsedLeft, parsedRight) => { - if (isAborted(parsedLeft) || isAborted(parsedRight)) { - return INVALID; - } - const merged = mergeValues(parsedLeft.value, parsedRight.value); - if (!merged.valid) { - addIssueToContext(ctx, { - code: ZodIssueCode.invalid_intersection_types, - }); - return INVALID; - } - if (isDirty(parsedLeft) || isDirty(parsedRight)) { - status.dirty(); - } - return { status: status.value, value: merged.data }; - }; - if (ctx.common.async) { - return Promise.all([ - this._def.left._parseAsync({ - data: ctx.data, - path: ctx.path, - parent: ctx, - }), - this._def.right._parseAsync({ - data: ctx.data, - path: ctx.path, - parent: ctx, - }), - ]).then(([left, right]) => handleParsed(left, right)); - } else { - return handleParsed( - this._def.left._parseSync({ - data: ctx.data, - path: ctx.path, - parent: ctx, - }), - this._def.right._parseSync({ - data: ctx.data, - path: ctx.path, - parent: ctx, - }) - ); - } - } + _parse(input) { + const { status, ctx } = this._processInputParams(input); + const handleParsed = (parsedLeft, parsedRight) => { + if (isAborted(parsedLeft) || isAborted(parsedRight)) { + return INVALID; + } + const merged = mergeValues(parsedLeft.value, parsedRight.value); + if (!merged.valid) { + addIssueToContext(ctx, { + code: ZodIssueCode.invalid_intersection_types, + }); + return INVALID; + } + if (isDirty(parsedLeft) || isDirty(parsedRight)) { + status.dirty(); + } + return { status: status.value, value: merged.data }; + }; + if (ctx.common.async) { + return Promise.all([ + this._def.left._parseAsync({ + data: ctx.data, + path: ctx.path, + parent: ctx, + }), + this._def.right._parseAsync({ + data: ctx.data, + path: ctx.path, + parent: ctx, + }), + ]).then(([left, right]) => handleParsed(left, right)); + } else { + return handleParsed( + this._def.left._parseSync({ + data: ctx.data, + path: ctx.path, + parent: ctx, + }), + this._def.right._parseSync({ + data: ctx.data, + path: ctx.path, + parent: ctx, + }) + ); + } + } }; ZodIntersection.create = (left, right, params) => { - return new ZodIntersection({ - left, - right, - typeName: ZodFirstPartyTypeKind.ZodIntersection, - ...processCreateParams(params), - }); + return new ZodIntersection({ + left, + right, + typeName: ZodFirstPartyTypeKind.ZodIntersection, + ...processCreateParams(params), + }); }; var ZodTuple = class extends ZodType { - _parse(input) { - const { status, ctx } = this._processInputParams(input); - if (ctx.parsedType !== ZodParsedType.array) { - addIssueToContext(ctx, { - code: ZodIssueCode.invalid_type, - expected: ZodParsedType.array, - received: ctx.parsedType, - }); - return INVALID; - } - if (ctx.data.length < this._def.items.length) { - addIssueToContext(ctx, { - code: ZodIssueCode.too_small, - minimum: this._def.items.length, - inclusive: true, - type: 'array', - }); - return INVALID; - } - const rest = this._def.rest; - if (!rest && ctx.data.length > this._def.items.length) { - addIssueToContext(ctx, { - code: ZodIssueCode.too_big, - maximum: this._def.items.length, - inclusive: true, - type: 'array', - }); - status.dirty(); - } - const items = ctx.data - .map((item, itemIndex) => { - const schema = this._def.items[itemIndex] || this._def.rest; - if (!schema) return null; - return schema._parse( - new ParseInputLazyPath(ctx, item, ctx.path, itemIndex) - ); - }) - .filter((x) => !!x); - if (ctx.common.async) { - return Promise.all(items).then((results) => { - return ParseStatus.mergeArray(status, results); - }); - } else { - return ParseStatus.mergeArray(status, items); - } - } - get items() { - return this._def.items; - } - rest(rest) { - return new ZodTuple({ - ...this._def, - rest, - }); - } + _parse(input) { + const { status, ctx } = this._processInputParams(input); + if (ctx.parsedType !== ZodParsedType.array) { + addIssueToContext(ctx, { + code: ZodIssueCode.invalid_type, + expected: ZodParsedType.array, + received: ctx.parsedType, + }); + return INVALID; + } + if (ctx.data.length < this._def.items.length) { + addIssueToContext(ctx, { + code: ZodIssueCode.too_small, + minimum: this._def.items.length, + inclusive: true, + type: "array", + }); + return INVALID; + } + const rest = this._def.rest; + if (!rest && ctx.data.length > this._def.items.length) { + addIssueToContext(ctx, { + code: ZodIssueCode.too_big, + maximum: this._def.items.length, + inclusive: true, + type: "array", + }); + status.dirty(); + } + const items = ctx.data + .map((item, itemIndex) => { + const schema = this._def.items[itemIndex] || this._def.rest; + if (!schema) return null; + return schema._parse( + new ParseInputLazyPath(ctx, item, ctx.path, itemIndex) + ); + }) + .filter((x) => !!x); + if (ctx.common.async) { + return Promise.all(items).then((results) => { + return ParseStatus.mergeArray(status, results); + }); + } else { + return ParseStatus.mergeArray(status, items); + } + } + get items() { + return this._def.items; + } + rest(rest) { + return new ZodTuple({ + ...this._def, + rest, + }); + } }; ZodTuple.create = (schemas, params) => { - if (!Array.isArray(schemas)) { - throw new Error('You must pass an array of schemas to z.tuple([ ... ])'); - } - return new ZodTuple({ - items: schemas, - typeName: ZodFirstPartyTypeKind.ZodTuple, - rest: null, - ...processCreateParams(params), - }); + if (!Array.isArray(schemas)) { + throw new Error( + "You must pass an array of schemas to z.tuple([ ... ])" + ); + } + return new ZodTuple({ + items: schemas, + typeName: ZodFirstPartyTypeKind.ZodTuple, + rest: null, + ...processCreateParams(params), + }); }; var ZodRecord = class extends ZodType { - get keySchema() { - return this._def.keyType; - } - get valueSchema() { - return this._def.valueType; - } - _parse(input) { - const { status, ctx } = this._processInputParams(input); - if (ctx.parsedType !== ZodParsedType.object) { - addIssueToContext(ctx, { - code: ZodIssueCode.invalid_type, - expected: ZodParsedType.object, - received: ctx.parsedType, - }); - return INVALID; - } - const pairs = []; - const keyType = this._def.keyType; - const valueType = this._def.valueType; - for (const key in ctx.data) { - pairs.push({ - key: keyType._parse(new ParseInputLazyPath(ctx, key, ctx.path, key)), - value: valueType._parse( - new ParseInputLazyPath(ctx, ctx.data[key], ctx.path, key) - ), - }); - } - if (ctx.common.async) { - return ParseStatus.mergeObjectAsync(status, pairs); - } else { - return ParseStatus.mergeObjectSync(status, pairs); - } - } - get element() { - return this._def.valueType; - } - static create(first, second, third) { - if (second instanceof ZodType) { - return new ZodRecord({ - keyType: first, - valueType: second, - typeName: ZodFirstPartyTypeKind.ZodRecord, - ...processCreateParams(third), - }); - } - return new ZodRecord({ - keyType: ZodString.create(), - valueType: first, - typeName: ZodFirstPartyTypeKind.ZodRecord, - ...processCreateParams(second), - }); - } + get keySchema() { + return this._def.keyType; + } + get valueSchema() { + return this._def.valueType; + } + _parse(input) { + const { status, ctx } = this._processInputParams(input); + if (ctx.parsedType !== ZodParsedType.object) { + addIssueToContext(ctx, { + code: ZodIssueCode.invalid_type, + expected: ZodParsedType.object, + received: ctx.parsedType, + }); + return INVALID; + } + const pairs = []; + const keyType = this._def.keyType; + const valueType = this._def.valueType; + for (const key in ctx.data) { + pairs.push({ + key: keyType._parse( + new ParseInputLazyPath(ctx, key, ctx.path, key) + ), + value: valueType._parse( + new ParseInputLazyPath(ctx, ctx.data[key], ctx.path, key) + ), + }); + } + if (ctx.common.async) { + return ParseStatus.mergeObjectAsync(status, pairs); + } else { + return ParseStatus.mergeObjectSync(status, pairs); + } + } + get element() { + return this._def.valueType; + } + static create(first, second, third) { + if (second instanceof ZodType) { + return new ZodRecord({ + keyType: first, + valueType: second, + typeName: ZodFirstPartyTypeKind.ZodRecord, + ...processCreateParams(third), + }); + } + return new ZodRecord({ + keyType: ZodString.create(), + valueType: first, + typeName: ZodFirstPartyTypeKind.ZodRecord, + ...processCreateParams(second), + }); + } }; var ZodMap = class extends ZodType { - _parse(input) { - const { status, ctx } = this._processInputParams(input); - if (ctx.parsedType !== ZodParsedType.map) { - addIssueToContext(ctx, { - code: ZodIssueCode.invalid_type, - expected: ZodParsedType.map, - received: ctx.parsedType, - }); - return INVALID; - } - const keyType = this._def.keyType; - const valueType = this._def.valueType; - const pairs = [...ctx.data.entries()].map(([key, value], index) => { - return { - key: keyType._parse( - new ParseInputLazyPath(ctx, key, ctx.path, [index, 'key']) - ), - value: valueType._parse( - new ParseInputLazyPath(ctx, value, ctx.path, [index, 'value']) - ), - }; - }); - if (ctx.common.async) { - const finalMap = /* @__PURE__ */ new Map(); - return Promise.resolve().then(async () => { - for (const pair of pairs) { - const key = await pair.key; - const value = await pair.value; - if (key.status === 'aborted' || value.status === 'aborted') { - return INVALID; - } - if (key.status === 'dirty' || value.status === 'dirty') { - status.dirty(); - } - finalMap.set(key.value, value.value); - } - return { status: status.value, value: finalMap }; - }); - } else { - const finalMap = /* @__PURE__ */ new Map(); - for (const pair of pairs) { - const key = pair.key; - const value = pair.value; - if (key.status === 'aborted' || value.status === 'aborted') { - return INVALID; - } - if (key.status === 'dirty' || value.status === 'dirty') { - status.dirty(); - } - finalMap.set(key.value, value.value); - } - return { status: status.value, value: finalMap }; - } - } + _parse(input) { + const { status, ctx } = this._processInputParams(input); + if (ctx.parsedType !== ZodParsedType.map) { + addIssueToContext(ctx, { + code: ZodIssueCode.invalid_type, + expected: ZodParsedType.map, + received: ctx.parsedType, + }); + return INVALID; + } + const keyType = this._def.keyType; + const valueType = this._def.valueType; + const pairs = [...ctx.data.entries()].map(([key, value], index) => { + return { + key: keyType._parse( + new ParseInputLazyPath(ctx, key, ctx.path, [index, "key"]) + ), + value: valueType._parse( + new ParseInputLazyPath(ctx, value, ctx.path, [ + index, + "value", + ]) + ), + }; + }); + if (ctx.common.async) { + const finalMap = /* @__PURE__ */ new Map(); + return Promise.resolve().then(async () => { + for (const pair of pairs) { + const key = await pair.key; + const value = await pair.value; + if ( + key.status === "aborted" || + value.status === "aborted" + ) { + return INVALID; + } + if (key.status === "dirty" || value.status === "dirty") { + status.dirty(); + } + finalMap.set(key.value, value.value); + } + return { status: status.value, value: finalMap }; + }); + } else { + const finalMap = /* @__PURE__ */ new Map(); + for (const pair of pairs) { + const key = pair.key; + const value = pair.value; + if (key.status === "aborted" || value.status === "aborted") { + return INVALID; + } + if (key.status === "dirty" || value.status === "dirty") { + status.dirty(); + } + finalMap.set(key.value, value.value); + } + return { status: status.value, value: finalMap }; + } + } }; ZodMap.create = (keyType, valueType, params) => { - return new ZodMap({ - valueType, - keyType, - typeName: ZodFirstPartyTypeKind.ZodMap, - ...processCreateParams(params), - }); + return new ZodMap({ + valueType, + keyType, + typeName: ZodFirstPartyTypeKind.ZodMap, + ...processCreateParams(params), + }); }; var ZodSet = class extends ZodType { - _parse(input) { - const { status, ctx } = this._processInputParams(input); - if (ctx.parsedType !== ZodParsedType.set) { - addIssueToContext(ctx, { - code: ZodIssueCode.invalid_type, - expected: ZodParsedType.set, - received: ctx.parsedType, - }); - return INVALID; - } - const def = this._def; - if (def.minSize !== null) { - if (ctx.data.size < def.minSize.value) { - addIssueToContext(ctx, { - code: ZodIssueCode.too_small, - minimum: def.minSize.value, - type: 'set', - inclusive: true, - message: def.minSize.message, - }); - status.dirty(); - } - } - if (def.maxSize !== null) { - if (ctx.data.size > def.maxSize.value) { - addIssueToContext(ctx, { - code: ZodIssueCode.too_big, - maximum: def.maxSize.value, - type: 'set', - inclusive: true, - message: def.maxSize.message, - }); - status.dirty(); - } - } - const valueType = this._def.valueType; - function finalizeSet(elements2) { - const parsedSet = /* @__PURE__ */ new Set(); - for (const element of elements2) { - if (element.status === 'aborted') return INVALID; - if (element.status === 'dirty') status.dirty(); - parsedSet.add(element.value); - } - return { status: status.value, value: parsedSet }; - } - const elements = [...ctx.data.values()].map((item, i) => - valueType._parse(new ParseInputLazyPath(ctx, item, ctx.path, i)) - ); - if (ctx.common.async) { - return Promise.all(elements).then((elements2) => finalizeSet(elements2)); - } else { - return finalizeSet(elements); - } - } - min(minSize, message) { - return new ZodSet({ - ...this._def, - minSize: { value: minSize, message: errorUtil.toString(message) }, - }); - } - max(maxSize, message) { - return new ZodSet({ - ...this._def, - maxSize: { value: maxSize, message: errorUtil.toString(message) }, - }); - } - size(size, message) { - return this.min(size, message).max(size, message); - } - nonempty(message) { - return this.min(1, message); - } + _parse(input) { + const { status, ctx } = this._processInputParams(input); + if (ctx.parsedType !== ZodParsedType.set) { + addIssueToContext(ctx, { + code: ZodIssueCode.invalid_type, + expected: ZodParsedType.set, + received: ctx.parsedType, + }); + return INVALID; + } + const def = this._def; + if (def.minSize !== null) { + if (ctx.data.size < def.minSize.value) { + addIssueToContext(ctx, { + code: ZodIssueCode.too_small, + minimum: def.minSize.value, + type: "set", + inclusive: true, + message: def.minSize.message, + }); + status.dirty(); + } + } + if (def.maxSize !== null) { + if (ctx.data.size > def.maxSize.value) { + addIssueToContext(ctx, { + code: ZodIssueCode.too_big, + maximum: def.maxSize.value, + type: "set", + inclusive: true, + message: def.maxSize.message, + }); + status.dirty(); + } + } + const valueType = this._def.valueType; + function finalizeSet(elements2) { + const parsedSet = /* @__PURE__ */ new Set(); + for (const element of elements2) { + if (element.status === "aborted") return INVALID; + if (element.status === "dirty") status.dirty(); + parsedSet.add(element.value); + } + return { status: status.value, value: parsedSet }; + } + const elements = [...ctx.data.values()].map((item, i) => + valueType._parse(new ParseInputLazyPath(ctx, item, ctx.path, i)) + ); + if (ctx.common.async) { + return Promise.all(elements).then((elements2) => + finalizeSet(elements2) + ); + } else { + return finalizeSet(elements); + } + } + min(minSize, message) { + return new ZodSet({ + ...this._def, + minSize: { value: minSize, message: errorUtil.toString(message) }, + }); + } + max(maxSize, message) { + return new ZodSet({ + ...this._def, + maxSize: { value: maxSize, message: errorUtil.toString(message) }, + }); + } + size(size, message) { + return this.min(size, message).max(size, message); + } + nonempty(message) { + return this.min(1, message); + } }; ZodSet.create = (valueType, params) => { - return new ZodSet({ - valueType, - minSize: null, - maxSize: null, - typeName: ZodFirstPartyTypeKind.ZodSet, - ...processCreateParams(params), - }); + return new ZodSet({ + valueType, + minSize: null, + maxSize: null, + typeName: ZodFirstPartyTypeKind.ZodSet, + ...processCreateParams(params), + }); }; var ZodFunction = class extends ZodType { - constructor() { - super(...arguments); - this.validate = this.implement; - } - _parse(input) { - const { ctx } = this._processInputParams(input); - if (ctx.parsedType !== ZodParsedType.function) { - addIssueToContext(ctx, { - code: ZodIssueCode.invalid_type, - expected: ZodParsedType.function, - received: ctx.parsedType, - }); - return INVALID; - } - function makeArgsIssue(args, error) { - return makeIssue({ - data: args, - path: ctx.path, - errorMaps: [ - ctx.common.contextualErrorMap, - ctx.schemaErrorMap, - getErrorMap(), - errorMap, - ].filter((x) => !!x), - issueData: { - code: ZodIssueCode.invalid_arguments, - argumentsError: error, - }, - }); - } - function makeReturnsIssue(returns, error) { - return makeIssue({ - data: returns, - path: ctx.path, - errorMaps: [ - ctx.common.contextualErrorMap, - ctx.schemaErrorMap, - getErrorMap(), - errorMap, - ].filter((x) => !!x), - issueData: { - code: ZodIssueCode.invalid_return_type, - returnTypeError: error, - }, - }); - } - const params = { errorMap: ctx.common.contextualErrorMap }; - const fn = ctx.data; - if (this._def.returns instanceof ZodPromise) { - return OK(async (...args) => { - const error = new ZodError([]); - const parsedArgs = await this._def.args - .parseAsync(args, params) - .catch((e) => { - error.addIssue(makeArgsIssue(args, e)); - throw error; - }); - const result = await fn(...parsedArgs); - const parsedReturns = await this._def.returns._def.type - .parseAsync(result, params) - .catch((e) => { - error.addIssue(makeReturnsIssue(result, e)); - throw error; - }); - return parsedReturns; - }); - } else { - return OK((...args) => { - const parsedArgs = this._def.args.safeParse(args, params); - if (!parsedArgs.success) { - throw new ZodError([makeArgsIssue(args, parsedArgs.error)]); - } - const result = fn(...parsedArgs.data); - const parsedReturns = this._def.returns.safeParse(result, params); - if (!parsedReturns.success) { - throw new ZodError([makeReturnsIssue(result, parsedReturns.error)]); - } - return parsedReturns.data; - }); - } - } - parameters() { - return this._def.args; - } - returnType() { - return this._def.returns; - } - args(...items) { - return new ZodFunction({ - ...this._def, - args: ZodTuple.create(items).rest(ZodUnknown.create()), - }); - } - returns(returnType) { - return new ZodFunction({ - ...this._def, - returns: returnType, - }); - } - implement(func) { - const validatedFunc = this.parse(func); - return validatedFunc; - } - strictImplement(func) { - const validatedFunc = this.parse(func); - return validatedFunc; - } - static create(args, returns, params) { - return new ZodFunction({ - args: args ? args : ZodTuple.create([]).rest(ZodUnknown.create()), - returns: returns || ZodUnknown.create(), - typeName: ZodFirstPartyTypeKind.ZodFunction, - ...processCreateParams(params), - }); - } + constructor() { + super(...arguments); + this.validate = this.implement; + } + _parse(input) { + const { ctx } = this._processInputParams(input); + if (ctx.parsedType !== ZodParsedType.function) { + addIssueToContext(ctx, { + code: ZodIssueCode.invalid_type, + expected: ZodParsedType.function, + received: ctx.parsedType, + }); + return INVALID; + } + function makeArgsIssue(args, error) { + return makeIssue({ + data: args, + path: ctx.path, + errorMaps: [ + ctx.common.contextualErrorMap, + ctx.schemaErrorMap, + getErrorMap(), + errorMap, + ].filter((x) => !!x), + issueData: { + code: ZodIssueCode.invalid_arguments, + argumentsError: error, + }, + }); + } + function makeReturnsIssue(returns, error) { + return makeIssue({ + data: returns, + path: ctx.path, + errorMaps: [ + ctx.common.contextualErrorMap, + ctx.schemaErrorMap, + getErrorMap(), + errorMap, + ].filter((x) => !!x), + issueData: { + code: ZodIssueCode.invalid_return_type, + returnTypeError: error, + }, + }); + } + const params = { errorMap: ctx.common.contextualErrorMap }; + const fn = ctx.data; + if (this._def.returns instanceof ZodPromise) { + return OK(async (...args) => { + const error = new ZodError([]); + const parsedArgs = await this._def.args + .parseAsync(args, params) + .catch((e) => { + error.addIssue(makeArgsIssue(args, e)); + throw error; + }); + const result = await fn(...parsedArgs); + const parsedReturns = await this._def.returns._def.type + .parseAsync(result, params) + .catch((e) => { + error.addIssue(makeReturnsIssue(result, e)); + throw error; + }); + return parsedReturns; + }); + } else { + return OK((...args) => { + const parsedArgs = this._def.args.safeParse(args, params); + if (!parsedArgs.success) { + throw new ZodError([makeArgsIssue(args, parsedArgs.error)]); + } + const result = fn(...parsedArgs.data); + const parsedReturns = this._def.returns.safeParse( + result, + params + ); + if (!parsedReturns.success) { + throw new ZodError([ + makeReturnsIssue(result, parsedReturns.error), + ]); + } + return parsedReturns.data; + }); + } + } + parameters() { + return this._def.args; + } + returnType() { + return this._def.returns; + } + args(...items) { + return new ZodFunction({ + ...this._def, + args: ZodTuple.create(items).rest(ZodUnknown.create()), + }); + } + returns(returnType) { + return new ZodFunction({ + ...this._def, + returns: returnType, + }); + } + implement(func) { + const validatedFunc = this.parse(func); + return validatedFunc; + } + strictImplement(func) { + const validatedFunc = this.parse(func); + return validatedFunc; + } + static create(args, returns, params) { + return new ZodFunction({ + args: args ? args : ZodTuple.create([]).rest(ZodUnknown.create()), + returns: returns || ZodUnknown.create(), + typeName: ZodFirstPartyTypeKind.ZodFunction, + ...processCreateParams(params), + }); + } }; var ZodLazy = class extends ZodType { - get schema() { - return this._def.getter(); - } - _parse(input) { - const { ctx } = this._processInputParams(input); - const lazySchema = this._def.getter(); - return lazySchema._parse({ data: ctx.data, path: ctx.path, parent: ctx }); - } + get schema() { + return this._def.getter(); + } + _parse(input) { + const { ctx } = this._processInputParams(input); + const lazySchema = this._def.getter(); + return lazySchema._parse({ + data: ctx.data, + path: ctx.path, + parent: ctx, + }); + } }; ZodLazy.create = (getter, params) => { - return new ZodLazy({ - getter, - typeName: ZodFirstPartyTypeKind.ZodLazy, - ...processCreateParams(params), - }); + return new ZodLazy({ + getter, + typeName: ZodFirstPartyTypeKind.ZodLazy, + ...processCreateParams(params), + }); }; var ZodLiteral = class extends ZodType { - _parse(input) { - if (input.data !== this._def.value) { - const ctx = this._getOrReturnCtx(input); - addIssueToContext(ctx, { - code: ZodIssueCode.invalid_literal, - expected: this._def.value, - }); - return INVALID; - } - return { status: 'valid', value: input.data }; - } - get value() { - return this._def.value; - } + _parse(input) { + if (input.data !== this._def.value) { + const ctx = this._getOrReturnCtx(input); + addIssueToContext(ctx, { + code: ZodIssueCode.invalid_literal, + expected: this._def.value, + }); + return INVALID; + } + return { status: "valid", value: input.data }; + } + get value() { + return this._def.value; + } }; ZodLiteral.create = (value, params) => { - return new ZodLiteral({ - value, - typeName: ZodFirstPartyTypeKind.ZodLiteral, - ...processCreateParams(params), - }); + return new ZodLiteral({ + value, + typeName: ZodFirstPartyTypeKind.ZodLiteral, + ...processCreateParams(params), + }); }; function createZodEnum(values, params) { - return new ZodEnum({ - values, - typeName: ZodFirstPartyTypeKind.ZodEnum, - ...processCreateParams(params), - }); + return new ZodEnum({ + values, + typeName: ZodFirstPartyTypeKind.ZodEnum, + ...processCreateParams(params), + }); } var ZodEnum = class extends ZodType { - _parse(input) { - if (typeof input.data !== 'string') { - const ctx = this._getOrReturnCtx(input); - const expectedValues = this._def.values; - addIssueToContext(ctx, { - expected: util.joinValues(expectedValues), - received: ctx.parsedType, - code: ZodIssueCode.invalid_type, - }); - return INVALID; - } - if (this._def.values.indexOf(input.data) === -1) { - const ctx = this._getOrReturnCtx(input); - const expectedValues = this._def.values; - addIssueToContext(ctx, { - received: ctx.data, - code: ZodIssueCode.invalid_enum_value, - options: expectedValues, - }); - return INVALID; - } - return OK(input.data); - } - get options() { - return this._def.values; - } - get enum() { - const enumValues = {}; - for (const val of this._def.values) { - enumValues[val] = val; - } - return enumValues; - } - get Values() { - const enumValues = {}; - for (const val of this._def.values) { - enumValues[val] = val; - } - return enumValues; - } - get Enum() { - const enumValues = {}; - for (const val of this._def.values) { - enumValues[val] = val; - } - return enumValues; - } + _parse(input) { + if (typeof input.data !== "string") { + const ctx = this._getOrReturnCtx(input); + const expectedValues = this._def.values; + addIssueToContext(ctx, { + expected: util.joinValues(expectedValues), + received: ctx.parsedType, + code: ZodIssueCode.invalid_type, + }); + return INVALID; + } + if (this._def.values.indexOf(input.data) === -1) { + const ctx = this._getOrReturnCtx(input); + const expectedValues = this._def.values; + addIssueToContext(ctx, { + received: ctx.data, + code: ZodIssueCode.invalid_enum_value, + options: expectedValues, + }); + return INVALID; + } + return OK(input.data); + } + get options() { + return this._def.values; + } + get enum() { + const enumValues = {}; + for (const val of this._def.values) { + enumValues[val] = val; + } + return enumValues; + } + get Values() { + const enumValues = {}; + for (const val of this._def.values) { + enumValues[val] = val; + } + return enumValues; + } + get Enum() { + const enumValues = {}; + for (const val of this._def.values) { + enumValues[val] = val; + } + return enumValues; + } }; ZodEnum.create = createZodEnum; var ZodNativeEnum = class extends ZodType { - _parse(input) { - const nativeEnumValues = util.getValidEnumValues(this._def.values); - const ctx = this._getOrReturnCtx(input); - if ( - ctx.parsedType !== ZodParsedType.string && - ctx.parsedType !== ZodParsedType.number - ) { - const expectedValues = util.objectValues(nativeEnumValues); - addIssueToContext(ctx, { - expected: util.joinValues(expectedValues), - received: ctx.parsedType, - code: ZodIssueCode.invalid_type, - }); - return INVALID; - } - if (nativeEnumValues.indexOf(input.data) === -1) { - const expectedValues = util.objectValues(nativeEnumValues); - addIssueToContext(ctx, { - received: ctx.data, - code: ZodIssueCode.invalid_enum_value, - options: expectedValues, - }); - return INVALID; - } - return OK(input.data); - } - get enum() { - return this._def.values; - } + _parse(input) { + const nativeEnumValues = util.getValidEnumValues(this._def.values); + const ctx = this._getOrReturnCtx(input); + if ( + ctx.parsedType !== ZodParsedType.string && + ctx.parsedType !== ZodParsedType.number + ) { + const expectedValues = util.objectValues(nativeEnumValues); + addIssueToContext(ctx, { + expected: util.joinValues(expectedValues), + received: ctx.parsedType, + code: ZodIssueCode.invalid_type, + }); + return INVALID; + } + if (nativeEnumValues.indexOf(input.data) === -1) { + const expectedValues = util.objectValues(nativeEnumValues); + addIssueToContext(ctx, { + received: ctx.data, + code: ZodIssueCode.invalid_enum_value, + options: expectedValues, + }); + return INVALID; + } + return OK(input.data); + } + get enum() { + return this._def.values; + } }; ZodNativeEnum.create = (values, params) => { - return new ZodNativeEnum({ - values, - typeName: ZodFirstPartyTypeKind.ZodNativeEnum, - ...processCreateParams(params), - }); + return new ZodNativeEnum({ + values, + typeName: ZodFirstPartyTypeKind.ZodNativeEnum, + ...processCreateParams(params), + }); }; var ZodPromise = class extends ZodType { - _parse(input) { - const { ctx } = this._processInputParams(input); - if ( - ctx.parsedType !== ZodParsedType.promise && - ctx.common.async === false - ) { - addIssueToContext(ctx, { - code: ZodIssueCode.invalid_type, - expected: ZodParsedType.promise, - received: ctx.parsedType, - }); - return INVALID; - } - const promisified = - ctx.parsedType === ZodParsedType.promise - ? ctx.data - : Promise.resolve(ctx.data); - return OK( - promisified.then((data) => { - return this._def.type.parseAsync(data, { - path: ctx.path, - errorMap: ctx.common.contextualErrorMap, - }); - }) - ); - } + _parse(input) { + const { ctx } = this._processInputParams(input); + if ( + ctx.parsedType !== ZodParsedType.promise && + ctx.common.async === false + ) { + addIssueToContext(ctx, { + code: ZodIssueCode.invalid_type, + expected: ZodParsedType.promise, + received: ctx.parsedType, + }); + return INVALID; + } + const promisified = + ctx.parsedType === ZodParsedType.promise + ? ctx.data + : Promise.resolve(ctx.data); + return OK( + promisified.then((data) => { + return this._def.type.parseAsync(data, { + path: ctx.path, + errorMap: ctx.common.contextualErrorMap, + }); + }) + ); + } }; ZodPromise.create = (schema, params) => { - return new ZodPromise({ - type: schema, - typeName: ZodFirstPartyTypeKind.ZodPromise, - ...processCreateParams(params), - }); + return new ZodPromise({ + type: schema, + typeName: ZodFirstPartyTypeKind.ZodPromise, + ...processCreateParams(params), + }); }; var ZodEffects = class extends ZodType { - innerType() { - return this._def.schema; - } - _parse(input) { - const { status, ctx } = this._processInputParams(input); - const effect = this._def.effect || null; - if (effect.type === 'preprocess') { - const processed = effect.transform(ctx.data); - if (ctx.common.async) { - return Promise.resolve(processed).then((processed2) => { - return this._def.schema._parseAsync({ - data: processed2, - path: ctx.path, - parent: ctx, - }); - }); - } else { - return this._def.schema._parseSync({ - data: processed, - path: ctx.path, - parent: ctx, - }); - } - } - const checkCtx = { - addIssue: (arg) => { - addIssueToContext(ctx, arg); - if (arg.fatal) { - status.abort(); - } else { - status.dirty(); - } - }, - get path() { - return ctx.path; - }, - }; - checkCtx.addIssue = checkCtx.addIssue.bind(checkCtx); - if (effect.type === 'refinement') { - const executeRefinement = (acc) => { - const result = effect.refinement(acc, checkCtx); - if (ctx.common.async) { - return Promise.resolve(result); - } - if (result instanceof Promise) { - throw new Error( - 'Async refinement encountered during synchronous parse operation. Use .parseAsync instead.' - ); - } - return acc; - }; - if (ctx.common.async === false) { - const inner = this._def.schema._parseSync({ - data: ctx.data, - path: ctx.path, - parent: ctx, - }); - if (inner.status === 'aborted') return INVALID; - if (inner.status === 'dirty') status.dirty(); - executeRefinement(inner.value); - return { status: status.value, value: inner.value }; - } else { - return this._def.schema - ._parseAsync({ data: ctx.data, path: ctx.path, parent: ctx }) - .then((inner) => { - if (inner.status === 'aborted') return INVALID; - if (inner.status === 'dirty') status.dirty(); - return executeRefinement(inner.value).then(() => { - return { status: status.value, value: inner.value }; - }); - }); - } - } - if (effect.type === 'transform') { - if (ctx.common.async === false) { - const base = this._def.schema._parseSync({ - data: ctx.data, - path: ctx.path, - parent: ctx, - }); - if (!isValid(base)) return base; - const result = effect.transform(base.value, checkCtx); - if (result instanceof Promise) { - throw new Error( - `Asynchronous transform encountered during synchronous parse operation. Use .parseAsync instead.` - ); - } - return { status: status.value, value: result }; - } else { - return this._def.schema - ._parseAsync({ data: ctx.data, path: ctx.path, parent: ctx }) - .then((base) => { - if (!isValid(base)) return base; - return Promise.resolve(effect.transform(base.value, checkCtx)).then( - (result) => ({ status: status.value, value: result }) - ); - }); - } - } - util.assertNever(effect); - } + innerType() { + return this._def.schema; + } + _parse(input) { + const { status, ctx } = this._processInputParams(input); + const effect = this._def.effect || null; + if (effect.type === "preprocess") { + const processed = effect.transform(ctx.data); + if (ctx.common.async) { + return Promise.resolve(processed).then((processed2) => { + return this._def.schema._parseAsync({ + data: processed2, + path: ctx.path, + parent: ctx, + }); + }); + } else { + return this._def.schema._parseSync({ + data: processed, + path: ctx.path, + parent: ctx, + }); + } + } + const checkCtx = { + addIssue: (arg) => { + addIssueToContext(ctx, arg); + if (arg.fatal) { + status.abort(); + } else { + status.dirty(); + } + }, + get path() { + return ctx.path; + }, + }; + checkCtx.addIssue = checkCtx.addIssue.bind(checkCtx); + if (effect.type === "refinement") { + const executeRefinement = (acc) => { + const result = effect.refinement(acc, checkCtx); + if (ctx.common.async) { + return Promise.resolve(result); + } + if (result instanceof Promise) { + throw new Error( + "Async refinement encountered during synchronous parse operation. Use .parseAsync instead." + ); + } + return acc; + }; + if (ctx.common.async === false) { + const inner = this._def.schema._parseSync({ + data: ctx.data, + path: ctx.path, + parent: ctx, + }); + if (inner.status === "aborted") return INVALID; + if (inner.status === "dirty") status.dirty(); + executeRefinement(inner.value); + return { status: status.value, value: inner.value }; + } else { + return this._def.schema + ._parseAsync({ + data: ctx.data, + path: ctx.path, + parent: ctx, + }) + .then((inner) => { + if (inner.status === "aborted") return INVALID; + if (inner.status === "dirty") status.dirty(); + return executeRefinement(inner.value).then(() => { + return { status: status.value, value: inner.value }; + }); + }); + } + } + if (effect.type === "transform") { + if (ctx.common.async === false) { + const base = this._def.schema._parseSync({ + data: ctx.data, + path: ctx.path, + parent: ctx, + }); + if (!isValid(base)) return base; + const result = effect.transform(base.value, checkCtx); + if (result instanceof Promise) { + throw new Error( + `Asynchronous transform encountered during synchronous parse operation. Use .parseAsync instead.` + ); + } + return { status: status.value, value: result }; + } else { + return this._def.schema + ._parseAsync({ + data: ctx.data, + path: ctx.path, + parent: ctx, + }) + .then((base) => { + if (!isValid(base)) return base; + return Promise.resolve( + effect.transform(base.value, checkCtx) + ).then((result) => ({ + status: status.value, + value: result, + })); + }); + } + } + util.assertNever(effect); + } }; ZodEffects.create = (schema, effect, params) => { - return new ZodEffects({ - schema, - typeName: ZodFirstPartyTypeKind.ZodEffects, - effect, - ...processCreateParams(params), - }); + return new ZodEffects({ + schema, + typeName: ZodFirstPartyTypeKind.ZodEffects, + effect, + ...processCreateParams(params), + }); }; ZodEffects.createWithPreprocess = (preprocess, schema, params) => { - return new ZodEffects({ - schema, - effect: { type: 'preprocess', transform: preprocess }, - typeName: ZodFirstPartyTypeKind.ZodEffects, - ...processCreateParams(params), - }); + return new ZodEffects({ + schema, + effect: { type: "preprocess", transform: preprocess }, + typeName: ZodFirstPartyTypeKind.ZodEffects, + ...processCreateParams(params), + }); }; var ZodOptional = class extends ZodType { - _parse(input) { - const parsedType = this._getType(input); - if (parsedType === ZodParsedType.undefined) { - return OK(void 0); - } - return this._def.innerType._parse(input); - } - unwrap() { - return this._def.innerType; - } + _parse(input) { + const parsedType = this._getType(input); + if (parsedType === ZodParsedType.undefined) { + return OK(void 0); + } + return this._def.innerType._parse(input); + } + unwrap() { + return this._def.innerType; + } }; ZodOptional.create = (type, params) => { - return new ZodOptional({ - innerType: type, - typeName: ZodFirstPartyTypeKind.ZodOptional, - ...processCreateParams(params), - }); + return new ZodOptional({ + innerType: type, + typeName: ZodFirstPartyTypeKind.ZodOptional, + ...processCreateParams(params), + }); }; var ZodNullable = class extends ZodType { - _parse(input) { - const parsedType = this._getType(input); - if (parsedType === ZodParsedType.null) { - return OK(null); - } - return this._def.innerType._parse(input); - } - unwrap() { - return this._def.innerType; - } + _parse(input) { + const parsedType = this._getType(input); + if (parsedType === ZodParsedType.null) { + return OK(null); + } + return this._def.innerType._parse(input); + } + unwrap() { + return this._def.innerType; + } }; ZodNullable.create = (type, params) => { - return new ZodNullable({ - innerType: type, - typeName: ZodFirstPartyTypeKind.ZodNullable, - ...processCreateParams(params), - }); + return new ZodNullable({ + innerType: type, + typeName: ZodFirstPartyTypeKind.ZodNullable, + ...processCreateParams(params), + }); }; var ZodDefault = class extends ZodType { - _parse(input) { - const { ctx } = this._processInputParams(input); - let data = ctx.data; - if (ctx.parsedType === ZodParsedType.undefined) { - data = this._def.defaultValue(); - } - return this._def.innerType._parse({ - data, - path: ctx.path, - parent: ctx, - }); - } - removeDefault() { - return this._def.innerType; - } + _parse(input) { + const { ctx } = this._processInputParams(input); + let data = ctx.data; + if (ctx.parsedType === ZodParsedType.undefined) { + data = this._def.defaultValue(); + } + return this._def.innerType._parse({ + data, + path: ctx.path, + parent: ctx, + }); + } + removeDefault() { + return this._def.innerType; + } }; ZodDefault.create = (type, params) => { - return new ZodOptional({ - innerType: type, - typeName: ZodFirstPartyTypeKind.ZodOptional, - ...processCreateParams(params), - }); + return new ZodOptional({ + innerType: type, + typeName: ZodFirstPartyTypeKind.ZodOptional, + ...processCreateParams(params), + }); }; var ZodNaN = class extends ZodType { - _parse(input) { - const parsedType = this._getType(input); - if (parsedType !== ZodParsedType.nan) { - const ctx = this._getOrReturnCtx(input); - addIssueToContext(ctx, { - code: ZodIssueCode.invalid_type, - expected: ZodParsedType.nan, - received: ctx.parsedType, - }); - return INVALID; - } - return { status: 'valid', value: input.data }; - } + _parse(input) { + const parsedType = this._getType(input); + if (parsedType !== ZodParsedType.nan) { + const ctx = this._getOrReturnCtx(input); + addIssueToContext(ctx, { + code: ZodIssueCode.invalid_type, + expected: ZodParsedType.nan, + received: ctx.parsedType, + }); + return INVALID; + } + return { status: "valid", value: input.data }; + } }; ZodNaN.create = (params) => { - return new ZodNaN({ - typeName: ZodFirstPartyTypeKind.ZodNaN, - ...processCreateParams(params), - }); + return new ZodNaN({ + typeName: ZodFirstPartyTypeKind.ZodNaN, + ...processCreateParams(params), + }); }; -var BRAND = Symbol('zod_brand'); +var BRAND = Symbol("zod_brand"); var ZodBranded = class extends ZodType { - _parse(input) { - const { ctx } = this._processInputParams(input); - const data = ctx.data; - return this._def.type._parse({ - data, - path: ctx.path, - parent: ctx, - }); - } - unwrap() { - return this._def.type; - } + _parse(input) { + const { ctx } = this._processInputParams(input); + const data = ctx.data; + return this._def.type._parse({ + data, + path: ctx.path, + parent: ctx, + }); + } + unwrap() { + return this._def.type; + } }; var custom = (check, params = {}, fatal) => { - if (check) - return ZodAny.create().superRefine((data, ctx) => { - if (!check(data)) { - const p = typeof params === 'function' ? params(data) : params; - const p2 = typeof p === 'string' ? { message: p } : p; - ctx.addIssue({ code: 'custom', ...p2, fatal }); - } - }); - return ZodAny.create(); + if (check) + return ZodAny.create().superRefine((data, ctx) => { + if (!check(data)) { + const p = typeof params === "function" ? params(data) : params; + const p2 = typeof p === "string" ? { message: p } : p; + ctx.addIssue({ code: "custom", ...p2, fatal }); + } + }); + return ZodAny.create(); }; var late = { - object: ZodObject.lazycreate, + object: ZodObject.lazycreate, }; var ZodFirstPartyTypeKind; (function (ZodFirstPartyTypeKind2) { - ZodFirstPartyTypeKind2['ZodString'] = 'ZodString'; - ZodFirstPartyTypeKind2['ZodNumber'] = 'ZodNumber'; - ZodFirstPartyTypeKind2['ZodNaN'] = 'ZodNaN'; - ZodFirstPartyTypeKind2['ZodBigInt'] = 'ZodBigInt'; - ZodFirstPartyTypeKind2['ZodBoolean'] = 'ZodBoolean'; - ZodFirstPartyTypeKind2['ZodDate'] = 'ZodDate'; - ZodFirstPartyTypeKind2['ZodUndefined'] = 'ZodUndefined'; - ZodFirstPartyTypeKind2['ZodNull'] = 'ZodNull'; - ZodFirstPartyTypeKind2['ZodAny'] = 'ZodAny'; - ZodFirstPartyTypeKind2['ZodUnknown'] = 'ZodUnknown'; - ZodFirstPartyTypeKind2['ZodNever'] = 'ZodNever'; - ZodFirstPartyTypeKind2['ZodVoid'] = 'ZodVoid'; - ZodFirstPartyTypeKind2['ZodArray'] = 'ZodArray'; - ZodFirstPartyTypeKind2['ZodObject'] = 'ZodObject'; - ZodFirstPartyTypeKind2['ZodUnion'] = 'ZodUnion'; - ZodFirstPartyTypeKind2['ZodDiscriminatedUnion'] = 'ZodDiscriminatedUnion'; - ZodFirstPartyTypeKind2['ZodIntersection'] = 'ZodIntersection'; - ZodFirstPartyTypeKind2['ZodTuple'] = 'ZodTuple'; - ZodFirstPartyTypeKind2['ZodRecord'] = 'ZodRecord'; - ZodFirstPartyTypeKind2['ZodMap'] = 'ZodMap'; - ZodFirstPartyTypeKind2['ZodSet'] = 'ZodSet'; - ZodFirstPartyTypeKind2['ZodFunction'] = 'ZodFunction'; - ZodFirstPartyTypeKind2['ZodLazy'] = 'ZodLazy'; - ZodFirstPartyTypeKind2['ZodLiteral'] = 'ZodLiteral'; - ZodFirstPartyTypeKind2['ZodEnum'] = 'ZodEnum'; - ZodFirstPartyTypeKind2['ZodEffects'] = 'ZodEffects'; - ZodFirstPartyTypeKind2['ZodNativeEnum'] = 'ZodNativeEnum'; - ZodFirstPartyTypeKind2['ZodOptional'] = 'ZodOptional'; - ZodFirstPartyTypeKind2['ZodNullable'] = 'ZodNullable'; - ZodFirstPartyTypeKind2['ZodDefault'] = 'ZodDefault'; - ZodFirstPartyTypeKind2['ZodPromise'] = 'ZodPromise'; - ZodFirstPartyTypeKind2['ZodBranded'] = 'ZodBranded'; + ZodFirstPartyTypeKind2["ZodString"] = "ZodString"; + ZodFirstPartyTypeKind2["ZodNumber"] = "ZodNumber"; + ZodFirstPartyTypeKind2["ZodNaN"] = "ZodNaN"; + ZodFirstPartyTypeKind2["ZodBigInt"] = "ZodBigInt"; + ZodFirstPartyTypeKind2["ZodBoolean"] = "ZodBoolean"; + ZodFirstPartyTypeKind2["ZodDate"] = "ZodDate"; + ZodFirstPartyTypeKind2["ZodUndefined"] = "ZodUndefined"; + ZodFirstPartyTypeKind2["ZodNull"] = "ZodNull"; + ZodFirstPartyTypeKind2["ZodAny"] = "ZodAny"; + ZodFirstPartyTypeKind2["ZodUnknown"] = "ZodUnknown"; + ZodFirstPartyTypeKind2["ZodNever"] = "ZodNever"; + ZodFirstPartyTypeKind2["ZodVoid"] = "ZodVoid"; + ZodFirstPartyTypeKind2["ZodArray"] = "ZodArray"; + ZodFirstPartyTypeKind2["ZodObject"] = "ZodObject"; + ZodFirstPartyTypeKind2["ZodUnion"] = "ZodUnion"; + ZodFirstPartyTypeKind2["ZodDiscriminatedUnion"] = "ZodDiscriminatedUnion"; + ZodFirstPartyTypeKind2["ZodIntersection"] = "ZodIntersection"; + ZodFirstPartyTypeKind2["ZodTuple"] = "ZodTuple"; + ZodFirstPartyTypeKind2["ZodRecord"] = "ZodRecord"; + ZodFirstPartyTypeKind2["ZodMap"] = "ZodMap"; + ZodFirstPartyTypeKind2["ZodSet"] = "ZodSet"; + ZodFirstPartyTypeKind2["ZodFunction"] = "ZodFunction"; + ZodFirstPartyTypeKind2["ZodLazy"] = "ZodLazy"; + ZodFirstPartyTypeKind2["ZodLiteral"] = "ZodLiteral"; + ZodFirstPartyTypeKind2["ZodEnum"] = "ZodEnum"; + ZodFirstPartyTypeKind2["ZodEffects"] = "ZodEffects"; + ZodFirstPartyTypeKind2["ZodNativeEnum"] = "ZodNativeEnum"; + ZodFirstPartyTypeKind2["ZodOptional"] = "ZodOptional"; + ZodFirstPartyTypeKind2["ZodNullable"] = "ZodNullable"; + ZodFirstPartyTypeKind2["ZodDefault"] = "ZodDefault"; + ZodFirstPartyTypeKind2["ZodPromise"] = "ZodPromise"; + ZodFirstPartyTypeKind2["ZodBranded"] = "ZodBranded"; })(ZodFirstPartyTypeKind || (ZodFirstPartyTypeKind = {})); var instanceOfType = ( - cls, - params = { - message: `Input not instance of ${cls.name}`, - } + cls, + params = { + message: `Input not instance of ${cls.name}`, + } ) => custom((data) => data instanceof cls, params, true); var stringType = ZodString.create; var numberType = ZodNumber.create; @@ -2975,134 +3041,134 @@ var onumber = () => numberType().optional(); var oboolean = () => booleanType().optional(); var NEVER = INVALID; var mod = /* @__PURE__ */ Object.freeze({ - __proto__: null, - getParsedType, - ZodParsedType, - defaultErrorMap: errorMap, - setErrorMap, - getErrorMap, - makeIssue, - EMPTY_PATH, - addIssueToContext, - ParseStatus, - INVALID, - DIRTY, - OK, - isAborted, - isDirty, - isValid, - isAsync, - ZodType, - ZodString, - ZodNumber, - ZodBigInt, - ZodBoolean, - ZodDate, - ZodUndefined, - ZodNull, - ZodAny, - ZodUnknown, - ZodNever, - ZodVoid, - ZodArray, - get objectUtil() { - return objectUtil; - }, - ZodObject, - ZodUnion, - ZodDiscriminatedUnion, - ZodIntersection, - ZodTuple, - ZodRecord, - ZodMap, - ZodSet, - ZodFunction, - ZodLazy, - ZodLiteral, - ZodEnum, - ZodNativeEnum, - ZodPromise, - ZodEffects, - ZodTransformer: ZodEffects, - ZodOptional, - ZodNullable, - ZodDefault, - ZodNaN, - BRAND, - ZodBranded, - custom, - Schema: ZodType, - ZodSchema: ZodType, - late, - get ZodFirstPartyTypeKind() { - return ZodFirstPartyTypeKind; - }, - any: anyType, - array: arrayType, - bigint: bigIntType, - boolean: booleanType, - date: dateType, - discriminatedUnion: discriminatedUnionType, - effect: effectsType, - enum: enumType, - function: functionType, - instanceof: instanceOfType, - intersection: intersectionType, - lazy: lazyType, - literal: literalType, - map: mapType, - nan: nanType, - nativeEnum: nativeEnumType, - never: neverType, - null: nullType, - nullable: nullableType, - number: numberType, - object: objectType, - oboolean, - onumber, - optional: optionalType, - ostring, - preprocess: preprocessType, - promise: promiseType, - record: recordType, - set: setType, - strictObject: strictObjectType, - string: stringType, - transformer: effectsType, - tuple: tupleType, - undefined: undefinedType, - union: unionType, - unknown: unknownType, - void: voidType, - NEVER, - ZodIssueCode, - quotelessJson, - ZodError, + __proto__: null, + getParsedType, + ZodParsedType, + defaultErrorMap: errorMap, + setErrorMap, + getErrorMap, + makeIssue, + EMPTY_PATH, + addIssueToContext, + ParseStatus, + INVALID, + DIRTY, + OK, + isAborted, + isDirty, + isValid, + isAsync, + ZodType, + ZodString, + ZodNumber, + ZodBigInt, + ZodBoolean, + ZodDate, + ZodUndefined, + ZodNull, + ZodAny, + ZodUnknown, + ZodNever, + ZodVoid, + ZodArray, + get objectUtil() { + return objectUtil; + }, + ZodObject, + ZodUnion, + ZodDiscriminatedUnion, + ZodIntersection, + ZodTuple, + ZodRecord, + ZodMap, + ZodSet, + ZodFunction, + ZodLazy, + ZodLiteral, + ZodEnum, + ZodNativeEnum, + ZodPromise, + ZodEffects, + ZodTransformer: ZodEffects, + ZodOptional, + ZodNullable, + ZodDefault, + ZodNaN, + BRAND, + ZodBranded, + custom, + Schema: ZodType, + ZodSchema: ZodType, + late, + get ZodFirstPartyTypeKind() { + return ZodFirstPartyTypeKind; + }, + any: anyType, + array: arrayType, + bigint: bigIntType, + boolean: booleanType, + date: dateType, + discriminatedUnion: discriminatedUnionType, + effect: effectsType, + enum: enumType, + function: functionType, + instanceof: instanceOfType, + intersection: intersectionType, + lazy: lazyType, + literal: literalType, + map: mapType, + nan: nanType, + nativeEnum: nativeEnumType, + never: neverType, + null: nullType, + nullable: nullableType, + number: numberType, + object: objectType, + oboolean, + onumber, + optional: optionalType, + ostring, + preprocess: preprocessType, + promise: promiseType, + record: recordType, + set: setType, + strictObject: strictObjectType, + string: stringType, + transformer: effectsType, + tuple: tupleType, + undefined: undefinedType, + union: unionType, + unknown: unknownType, + void: voidType, + NEVER, + ZodIssueCode, + quotelessJson, + ZodError, }); // src/lib/get-text-schema.ts var getTextSchema = mod.object({ - text: mod.string().min(2).max(255), + text: mod.string().min(2).max(255), }); // src/index.ts var resolver = new Resolver(); -resolver.define('getText' /* getText */, async (req) => { - console.log('called getText()'); - //await requireAccess({ req }); - const accountId = requireAccountId(req); - const payload = getTextSchema.parse(req.payload); - console.log('accessed getText()'); - return getText({ ...payload, accountId }); +resolver.define("getText" /* getText */, async (req) => { + console.log("called getText()"); + //await requireAccess({ req }); + const accountId = requireAccountId(req); + const payload = getTextSchema.parse(req.payload); + console.log("accessed getText()"); + return getText({ ...payload, accountId }); }); async function requireAccess({ req }) { - const isAdmin = await isJiraGlobalAdmin(); - if (!isAdmin) { - throw new Error('not permitted'); - } + const isAdmin = await isJiraGlobalAdmin(); + if (!isAdmin) { + throw new Error("not permitted"); + } } function requireAccountId(req) { - return mod.string().parse(req.context.accountId); + return mod.string().parse(req.context.accountId); } var handler = resolver.getDefinitions(); export { handler }; From 632dc97cca1f02bb400924acf6129339f556aac6 Mon Sep 17 00:00:00 2001 From: Joshua Wong Date: Thu, 8 Dec 2022 13:10:08 -0600 Subject: [PATCH 398/517] feat: interp v2 --- crates/forge_analyzer/src/definitions.rs | 510 +---------------------- crates/forge_analyzer/src/ir.rs | 1 + 2 files changed, 7 insertions(+), 504 deletions(-) diff --git a/crates/forge_analyzer/src/definitions.rs b/crates/forge_analyzer/src/definitions.rs index 56f3efa2..22349d2d 100644 --- a/crates/forge_analyzer/src/definitions.rs +++ b/crates/forge_analyzer/src/definitions.rs @@ -805,455 +805,6 @@ struct FunctionCollector<'cx> { parent: Option, } -struct FunctionAnalyzer<'cx> { - res: &'cx mut Environment, - module: ModId, - current_def: DefId, - assigning_to: Option, - body: Body, - block: BasicBlockId, - operand_stack: Vec, - in_lhs: bool, -} - -impl<'cx> FunctionAnalyzer<'cx> { - #[inline] - fn set_curr_terminator(&mut self, term: Terminator) { - self.body.set_terminator(self.block, term); - } - - #[inline] - fn push_curr_inst(&mut self, inst: Inst) { - self.body.push_inst(self.block, inst); - } - - fn lower_member(&mut self, obj: &Expr, prop: &MemberProp) -> Operand { - let obj = self.lower_expr(obj); - let Operand::Var(mut var) = obj else { - // FIXME: handle literals - return obj; - }; - match prop { - MemberProp::Ident(id) | MemberProp::PrivateName(PrivateName { id, .. }) => { - let id = id.to_id(); - var.projections.push(Projection::Known(id.0)); - } - MemberProp::Computed(ComputedPropName { expr, .. }) => { - let opnd = self.lower_expr(expr); - var.projections - .push(self.body.resolve_prop(self.block, opnd)); - } - } - Operand::Var(var) - } - - // TODO: This can probably be made into a trait - fn lower_expr(&mut self, n: &Expr) -> Operand { - match n { - Expr::This(_) => Operand::Var(Variable::THIS), - Expr::Array(ArrayLit { elems, .. }) => { - let array_lit: Vec<_> = elems - .iter() - .map(|e| { - e.as_ref() - .map_or(Operand::UNDEF, |ExprOrSpread { spread, expr }| { - self.lower_expr(expr) - }) - }) - .collect(); - Operand::UNDEF - } - Expr::Object(ObjectLit { span, props }) => { - // TODO: lower object literals - Operand::UNDEF - } - Expr::Fn(_) => Operand::UNDEF, - Expr::Unary(UnaryExpr { op, arg, .. }) => { - let arg = self.lower_expr(arg); - let tmp = self - .body - .push_tmp(self.block, Rvalue::Unary(op.into(), arg), None); - Operand::with_var(tmp) - } - Expr::Update(UpdateExpr { - op, prefix, arg, .. - }) => { - // FIXME: Handle op - self.lower_expr(arg) - } - Expr::Bin(BinExpr { - op, left, right, .. - }) => { - let left = self.lower_expr(left); - let right = self.lower_expr(right); - let tmp = self - .body - .push_tmp(self.block, Rvalue::Bin(op.into(), left, right), None); - Operand::with_var(tmp) - } - - Expr::SuperProp(SuperPropExpr { obj, prop, .. }) => { - let mut super_var = Variable::SUPER; - match prop { - SuperProp::Ident(id) => { - let id = id.to_id().0; - super_var.projections.push(Projection::Known(id)); - } - SuperProp::Computed(ComputedPropName { expr, .. }) => { - let opnd = self.lower_expr(expr); - let prop = self.body.resolve_prop(self.block, opnd); - super_var.projections.push(prop); - } - } - Operand::Var(super_var) - } - Expr::Assign(AssignExpr { - op, left, right, .. - }) => match left { - PatOrExpr::Expr(_) => todo!(), - PatOrExpr::Pat(_) => todo!(), - }, - Expr::Member(MemberExpr { obj, prop, .. }) => self.lower_member(obj, prop), - Expr::Cond(CondExpr { - test, cons, alt, .. - }) => self.lower_expr(test), - Expr::Call(n) => { - let mut args = Vec::with_capacity(n.args.len()); - for ExprOrSpread { spread, expr } in &n.args { - let arg = self.lower_expr(expr); - args.push(arg); - } - let callee = match &n.callee { - Callee::Super(_) => Operand::Var(Variable::SUPER), - Callee::Import(_) => Operand::UNDEF, - Callee::Expr(expr) => self.lower_expr(expr), - }; - let props = normalize_callee_expr(&n.callee, self.res, self.module); - match props.first() { - Some(&PropPath::Def(id)) => { - debug!("call from: {}", self.res.def_name(id)); - debug!("call expr: {:?}", props); - } - Some(PropPath::Unknown(id)) => { - debug!("call from: {}", id.0); - debug!("call expr: {:?}", props); - } - _ => (), - } - todo!() - } - Expr::New(NewExpr { callee, args, .. }) => Operand::UNDEF, - Expr::Seq(SeqExpr { exprs, .. }) => { - if let Some((last, rest)) = exprs.split_last() { - for expr in rest { - let opnd = self.lower_expr(expr); - self.body.push_expr(self.block, Rvalue::Read(opnd)); - } - self.lower_expr(last) - } else { - Literal::Undef.into() - } - } - Expr::Ident(id) => { - let id = id.to_id(); - let Some(def) = self.res.sym_to_id(id.clone(), self.module) else { - warn!("unknown symbol: {}", id.0); - return Literal::Undef.into(); - }; - let var = self.body.get_or_insert_global(def); - Operand::with_var(var) - } - Expr::Lit(lit) => lit.clone().into(), - Expr::Tpl(Tpl { exprs, quasis, .. }) => todo!(), - Expr::TaggedTpl(TaggedTpl { tag, tpl, .. }) => todo!(), - Expr::Arrow(_) => Operand::UNDEF, - Expr::Class(_) => Operand::UNDEF, - Expr::Yield(YieldExpr { arg, .. }) => arg - .as_deref() - .map_or(Operand::UNDEF, |expr| self.lower_expr(expr)), - Expr::MetaProp(_) => Operand::UNDEF, - Expr::Await(AwaitExpr { arg, .. }) => self.lower_expr(arg), - Expr::Paren(ParenExpr { expr, .. }) => self.lower_expr(expr), - Expr::JSXMember(_) => todo!(), - Expr::JSXNamespacedName(_) => todo!(), - Expr::JSXEmpty(_) => todo!(), - Expr::JSXElement(_) => todo!(), - Expr::JSXFragment(_) => todo!(), - Expr::TsTypeAssertion(TsTypeAssertion { expr, .. }) - | Expr::TsConstAssertion(TsConstAssertion { expr, .. }) - | Expr::TsNonNull(TsNonNullExpr { expr, .. }) - | Expr::TsAs(TsAsExpr { expr, .. }) - | Expr::TsInstantiation(TsInstantiation { expr, .. }) - | Expr::TsSatisfies(TsSatisfiesExpr { expr, .. }) => self.lower_expr(expr), - Expr::PrivateName(PrivateName { id, .. }) => todo!(), - Expr::OptChain(OptChainExpr { base, .. }) => match base { - OptChainBase::Call(OptCall { callee, args, .. }) => todo!(), - OptChainBase::Member(MemberExpr { obj, prop, .. }) => { - // TODO: create separate basic blocks - self.lower_member(obj, prop) - } - }, - Expr::Invalid(_) => Operand::UNDEF, - } - } - - fn lower_stmt(&mut self, n: &Stmt) { - match n { - Stmt::Block(BlockStmt { stmts, .. }) => todo!(), - Stmt::Empty(_) => todo!(), - Stmt::Debugger(_) => todo!(), - Stmt::With(WithStmt { obj, body, .. }) => todo!(), - Stmt::Return(ReturnStmt { arg, .. }) => todo!(), - Stmt::Labeled(LabeledStmt { label, body, .. }) => todo!(), - Stmt::Break(BreakStmt { label, .. }) => todo!(), - Stmt::Continue(ContinueStmt { label, .. }) => todo!(), - Stmt::If(IfStmt { - test, cons, alt, .. - }) => todo!(), - Stmt::Switch(SwitchStmt { - discriminant, - cases, - .. - }) => todo!(), - Stmt::Throw(ThrowStmt { arg, .. }) => todo!(), - Stmt::Try(stmt) => { - let TryStmt { - block, - handler, - finalizer, - .. - } = &**stmt; - todo!() - } - Stmt::While(WhileStmt { test, body, .. }) => todo!(), - Stmt::DoWhile(DoWhileStmt { test, body, .. }) => todo!(), - Stmt::For(ForStmt { - init, - test, - update, - body, - .. - }) => todo!(), - Stmt::ForIn(ForInStmt { - left, right, body, .. - }) => todo!(), - Stmt::ForOf(ForOfStmt { - left, right, body, .. - }) => todo!(), - Stmt::Decl(decl) => match decl { - Decl::Class(_) => todo!(), - Decl::Fn(_) => todo!(), - Decl::Var(_) => todo!(), - Decl::TsInterface(_) => todo!(), - Decl::TsTypeAlias(_) => todo!(), - Decl::TsEnum(_) => todo!(), - Decl::TsModule(_) => todo!(), - }, - Stmt::Expr(ExprStmt { expr, .. }) => todo!(), - } - } -} - -impl Visit for FunctionAnalyzer<'_> { - fn visit_stmt(&mut self, n: &Stmt) { - match n { - Stmt::Block(_) => todo!(), - Stmt::Empty(_) => todo!(), - Stmt::Debugger(_) => todo!(), - Stmt::With(_) => todo!(), - Stmt::Return(_) => todo!(), - Stmt::Labeled(_) => todo!(), - Stmt::Break(_) => todo!(), - Stmt::Continue(_) => todo!(), - Stmt::If(_) => todo!(), - Stmt::Switch(_) => todo!(), - Stmt::Throw(_) => todo!(), - Stmt::Try(_) => todo!(), - Stmt::While(_) => todo!(), - Stmt::DoWhile(_) => todo!(), - Stmt::For(_) => todo!(), - Stmt::ForIn(_) => todo!(), - Stmt::ForOf(_) => todo!(), - Stmt::Decl(_) => todo!(), - Stmt::Expr(_) => todo!(), - } - } -} - -struct ArgDefiner<'cx> { - res: &'cx mut Environment, - module: ModId, - func: DefId, - body: Body, -} - -impl Visit for ArgDefiner<'_> { - fn visit_ident(&mut self, n: &Ident) { - let id = n.to_id(); - let defid = self - .res - .get_or_overwrite_sym(id.clone(), self.module, DefRes::Arg); - self.res.add_parent(defid, self.func); - self.body.add_arg(defid, id); - } - - fn visit_object_pat_prop(&mut self, n: &ObjectPatProp) { - match n { - ObjectPatProp::KeyValue(KeyValuePatProp { key, .. }) => key.visit_with(self), - ObjectPatProp::Assign(AssignPatProp { key, .. }) => self.visit_ident(key), - ObjectPatProp::Rest(_) => {} - } - } - - fn visit_pat(&mut self, n: &Pat) { - match n { - Pat::Ident(_) | Pat::Array(_) => n.visit_children_with(self), - Pat::Object(ObjectPat { props, .. }) => props.visit_children_with(self), - Pat::Assign(AssignPat { left, .. }) => left.visit_with(self), - Pat::Expr(id) => { - if let Expr::Ident(id) = &**id { - id.visit_with(self); - } - } - Pat::Invalid(_) => {} - Pat::Rest(_) => {} - Pat::Invalid(_) => {} - } - } -} - -struct LocalDefiner<'cx> { - res: &'cx mut Environment, - module: ModId, - func: DefId, - body: Body, -} - -impl Visit for LocalDefiner<'_> { - fn visit_ident(&mut self, n: &Ident) { - let id = n.to_id(); - let defid = self.res.get_or_insert_sym(id.clone(), self.module); - self.res.try_add_parent(defid, self.func); - self.body.add_local_def(defid, id); - } - - fn visit_var_declarator(&mut self, n: &VarDeclarator) { - n.name.visit_with(self); - } - - fn visit_decl(&mut self, n: &Decl) { - match n { - Decl::Class(_) => {} - Decl::Fn(FnDecl { ident, .. }) => { - ident.visit_with(self); - } - Decl::Var(vars) => vars.visit_children_with(self), - Decl::TsInterface(_) | Decl::TsTypeAlias(_) | Decl::TsEnum(_) | Decl::TsModule(_) => {} - } - } - - fn visit_arrow_expr(&mut self, _: &ArrowExpr) {} - fn visit_fn_decl(&mut self, _: &FnDecl) {} -} - -impl Visit for FunctionCollector<'_> { - fn visit_function(&mut self, n: &Function) { - n.visit_children_with(self); - let owner = self.parent.unwrap_or_else(|| { - self.res - .add_anonymous("__UNKNOWN", AnonType::Closure, self.module) - }); - let mut argdef = ArgDefiner { - res: self.res, - module: self.module, - func: owner, - body: Body::with_owner(owner), - }; - n.params.visit_children_with(&mut argdef); - let body = argdef.body; - let mut localdef = LocalDefiner { - res: self.res, - module: self.module, - func: owner, - body, - }; - n.body.visit_children_with(&mut localdef); - let body = localdef.body; - let mut analyzer = FunctionAnalyzer { - res: self.res, - module: self.module, - current_def: owner, - assigning_to: None, - body, - block: BasicBlockId::default(), - operand_stack: vec![], - in_lhs: false, - }; - n.body.visit_with(&mut analyzer); - } - - fn visit_var_declarator(&mut self, n: &VarDeclarator) { - n.visit_children_with(self); - let Some(BindingIdent { id, .. }) = n.name.as_ident() else { - return; - }; - let id = id.to_id(); - match n.init.as_deref() { - Some(Expr::Fn(f)) => { - let defid = self - .res - .get_or_overwrite_sym(id, self.module, DefKind::Function(())); - let old_parent = self.parent.replace(defid); - f.visit_with(self); - self.parent = old_parent; - } - Some(Expr::Arrow(f)) => { - let defid = self - .res - .get_or_overwrite_sym(id, self.module, DefKind::Function(())); - let old_parent = self.parent.replace(defid); - f.visit_with(self); - self.parent = old_parent; - } - _ => {} - } - } - - fn visit_call_expr(&mut self, n: &CallExpr) { - n.visit_children_with(self); - if let Some((def_id, propname, expr)) = as_resolver_def(n, self.res, self.module) { - info!("found possible resolver: {propname}"); - match self.res.lookup_prop(def_id, propname) { - Some(def) => { - info!("analyzing resolver def: {def:?}"); - let mut analyzer = FunctionAnalyzer { - res: self.res, - module: self.module, - current_def: def, - assigning_to: None, - body: Body::with_owner(def), - block: BasicBlockId::default(), - operand_stack: vec![], - in_lhs: false, - }; - expr.visit_with(&mut analyzer); - } - None => { - warn!("resolver def not found"); - } - } - } - } - - fn visit_fn_decl(&mut self, n: &FnDecl) { - let id = n.ident.to_id(); - let def = self.res.get_or_insert_sym(id, self.module); - self.parent = Some(def); - n.function.visit_with(self); - self.parent = None; - } -} - struct FunctionAnalyzer<'cx> { pub res: &'cx mut Environment, module: ModId, @@ -1352,6 +903,7 @@ impl<'cx> FunctionAnalyzer<'cx> { fn is_storage_read(prop: &JsWord) -> bool { *prop == *"get" || *prop == *"getSecret" || *prop == *"query" } + match *callee { [PropPath::Unknown((ref name, ..))] if *name == *"fetch" => Some(Intrinsic::Fetch), [PropPath::Def(def), ref authn @ .., PropPath::Static(ref last)] @@ -1401,20 +953,16 @@ impl<'cx> FunctionAnalyzer<'cx> { _ => None, } } - [PropPath::Def(def), ..] if self.res.is_imported_from(def, "@forge/api").is_some() => { if let Some(ImportKind::Named(ref name)) = self.res.is_imported_from(def, "@forge/api") { if *name == *"authorize" { - Some(Intrinsic::Authorize(IntrinsicName::Other)); - } else { - return None; + return Some(Intrinsic::Authorize(IntrinsicName::Other)); } } None } - //[PropPath::Def(def), PropPath::Static(a), PropPath::Static(b)] // 1. Star, identifier, method [PropPath::Def(def), PropPath::Static(ref identifier), PropPath::Static(ref method)] => { @@ -1674,6 +1222,7 @@ impl<'cx> FunctionAnalyzer<'cx> { } fn lower_call(&mut self, callee: CalleeRef<'_>, args: &[ExprOrSpread]) -> Operand { + // debug!("in da lower call"); let props = normalize_callee_expr(callee, self.res, self.module); if let Some(&PropPath::Def(id)) = props.first() { if self.res.is_imported_from(id, "@forge/ui").map_or( @@ -1686,7 +1235,6 @@ impl<'cx> FunctionAnalyzer<'cx> { || calls_method(callee, "catch") { if let [ExprOrSpread { expr, .. }] = args { - debug!("found useState/then/map/foreach/filter"); match &**expr { Expr::Arrow(ArrowExpr { body, .. }) => match &**body { BlockStmtOrExpr::BlockStmt(stmt) => { @@ -1726,15 +1274,10 @@ impl<'cx> FunctionAnalyzer<'cx> { let first_arg = args.first().map(|expr| &*expr.expr); let intrinsic = self.as_intrinsic(&props, first_arg); - debug!("HELLO INTRINSIC!? {intrinsic:?}"); - let call = match self.as_intrinsic(&props, first_arg) { - Some(int) => { - debug!("this should be working T.T"); - Rvalue::Intrinsic(int, lowered_args) - } + let call = match intrinsic { + Some(int) => Rvalue::Intrinsic(int, lowered_args), None => Rvalue::Call(callee, lowered_args), }; - let res = self.body.push_tmp(self.block, call, None); Operand::with_var(res) } @@ -2087,10 +1630,7 @@ impl<'cx> FunctionAnalyzer<'cx> { ); Operand::with_var(phi) } - Expr::Call(CallExpr { callee, args, .. }) => { - debug!("Did this lower_call get called?"); - self.lower_call(callee.into(), args) - } + Expr::Call(CallExpr { callee, args, .. }) => self.lower_call(callee.into(), args), Expr::New(NewExpr { callee, args, .. }) => { if let Expr::Ident(ident) = &**callee { // remove the clone @@ -2353,7 +1893,6 @@ impl<'cx> FunctionAnalyzer<'cx> { | Decl::TsModule(_) => {} }, Stmt::Expr(ExprStmt { expr, .. }) => { - debug!("guessing it's getting called here? In expression statement"); let opnd = self.lower_expr(expr, None); self.body.push_expr(self.block, Rvalue::Read(opnd)); } @@ -2959,7 +2498,6 @@ impl Visit for FunctionCollector<'_> { impl FunctionCollector<'_> { fn handle_function(&mut self, n: &Function, owner: Option) { - debug!("When does this function get called T.T"); let owner = self.parent.unwrap_or_else(|| { if let Some(defid) = owner { defid @@ -3169,7 +2707,6 @@ impl Visit for Lowerer<'_> { } fn visit_call_expr(&mut self, n: &CallExpr) { - debug!("checking function call~!~"); if let Some(expr) = n.callee.as_expr() { if let Some((objid, ResolverDef::FnDef)) = as_resolver(expr, self.res, self.curr_mod) { if let [ExprOrSpread { expr: name, .. }, ExprOrSpread { expr: args, .. }] = &*n.args @@ -4250,41 +3787,6 @@ impl fmt::Display for ForeignItem { } } -impl fmt::Display for DefKind { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - DefKind::Class(_) => write!(f, "class"), - DefKind::ResolverDef(_) => write!(f, "resolver def"), - DefKind::Resolver(_) => write!(f, "resolver"), - DefKind::Arg => write!(f, "argument"), - DefKind::GlobalObj(_) => write!(f, "object literal"), - DefKind::Function(_) => write!(f, "function"), - DefKind::Closure(_) => write!(f, "closure"), - DefKind::ExportAlias(_) => write!(f, "export alias"), - DefKind::ResolverHandler(_) => write!(f, "resolver handler"), - DefKind::ModuleNs(_) => write!(f, "module namespace"), - DefKind::Foreign(_) => write!(f, "foreign"), - DefKind::Undefined => write!(f, "undefined"), - } - } -} - -impl fmt::Display for ImportKind { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - ImportKind::Star => write!(f, "*"), - ImportKind::Default => write!(f, "default"), - ImportKind::Named(sym) => write!(f, "{}", &**sym), - } - } -} - -impl fmt::Display for ForeignItem { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "import {} from {}", self.kind, &*self.module_name) - } -} - impl From for DefKey { #[inline] fn from(value: ObjKind) -> Self { diff --git a/crates/forge_analyzer/src/ir.rs b/crates/forge_analyzer/src/ir.rs index 6688a425..352972aa 100644 --- a/crates/forge_analyzer/src/ir.rs +++ b/crates/forge_analyzer/src/ir.rs @@ -96,6 +96,7 @@ pub struct Template { pub enum Rvalue { Unary(UnOp, Operand), Bin(BinOp, Operand, Operand), + Call { callee: Operand, args: Vec }, Read(Operand), Call(Operand, SmallVec<[Operand; 4]>), Intrinsic(Intrinsic, SmallVec<[Operand; 4]>), From 0054b6a39e8a5f987225455035a8b977b0488c57 Mon Sep 17 00:00:00 2001 From: Joshua Wong Date: Sun, 18 Dec 2022 11:34:30 -0600 Subject: [PATCH 399/517] chore: bump deps and add time crate --- Cargo.lock | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e7b274b7..be437e6c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1128,26 +1128,18 @@ dependencies = [ name = "num_threads" version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2819ce041d2ee131036f4fc9d6ae7ae125a3a40e97ba64d04fe799ad9dabbb44" -dependencies = [ - "libc", -] - -[[package]] -name = "object" -version = "0.30.1" -source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8d864c91689fdc196779b98dba0aceac6118594c2df6ee5d943eb6a8df4d107a" dependencies = [ - "memchr", + "libc", ] [[package]] -name = "once_cell" +name = "num_threads" version = "1.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" + [[package]] name = "overload" version = "0.1.1" From 54f2fba53270ad4319348095a74807c6e57efb11 Mon Sep 17 00:00:00 2001 From: Joshua Wong Date: Sun, 18 Dec 2022 11:35:08 -0600 Subject: [PATCH 400/517] feat: migrate to IR v2 --- crates/forge_analyzer/src/definitions.rs | 2 +- crates/forge_analyzer/src/ir.rs | 2 -- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/crates/forge_analyzer/src/definitions.rs b/crates/forge_analyzer/src/definitions.rs index 22349d2d..c2365b90 100644 --- a/crates/forge_analyzer/src/definitions.rs +++ b/crates/forge_analyzer/src/definitions.rs @@ -1222,7 +1222,7 @@ impl<'cx> FunctionAnalyzer<'cx> { } fn lower_call(&mut self, callee: CalleeRef<'_>, args: &[ExprOrSpread]) -> Operand { - // debug!("in da lower call"); + let args = args.iter().map(|arg| self.lower_expr(&arg.expr)).collect(); let props = normalize_callee_expr(callee, self.res, self.module); if let Some(&PropPath::Def(id)) = props.first() { if self.res.is_imported_from(id, "@forge/ui").map_or( diff --git a/crates/forge_analyzer/src/ir.rs b/crates/forge_analyzer/src/ir.rs index 352972aa..ddcb6dde 100644 --- a/crates/forge_analyzer/src/ir.rs +++ b/crates/forge_analyzer/src/ir.rs @@ -96,7 +96,6 @@ pub struct Template { pub enum Rvalue { Unary(UnOp, Operand), Bin(BinOp, Operand, Operand), - Call { callee: Operand, args: Vec }, Read(Operand), Call(Operand, SmallVec<[Operand; 4]>), Intrinsic(Intrinsic, SmallVec<[Operand; 4]>), @@ -468,7 +467,6 @@ impl Body { _ => None, } } - pub(crate) fn coerce_to_lval( &mut self, bb: BasicBlockId, From ef6a76b456422b27e355a33a4ede42c3b60f00a4 Mon Sep 17 00:00:00 2001 From: Joshua Wong Date: Tue, 20 Dec 2022 19:35:24 -0600 Subject: [PATCH 401/517] feat: serialize AuthZVuln --- crates/forge_analyzer/src/checkers.rs | 273 ++------------------------ 1 file changed, 14 insertions(+), 259 deletions(-) diff --git a/crates/forge_analyzer/src/checkers.rs b/crates/forge_analyzer/src/checkers.rs index 10212f82..38a184e7 100644 --- a/crates/forge_analyzer/src/checkers.rs +++ b/crates/forge_analyzer/src/checkers.rs @@ -1,34 +1,12 @@ use core::fmt; -use forge_permission_resolver::permissions_resolver::{check_url_for_permissions, RequestType}; -use forge_utils::FxHashMap; -use itertools::Itertools; -use smallvec::SmallVec; -use std::{ - cmp::max, - iter::{self, zip}, - mem, - ops::ControlFlow, - path::PathBuf, -}; +use std::{cmp::max, mem, ops::ControlFlow}; use tracing::{debug, info, warn}; use crate::{ - definitions::{Const, DefId, Environment, IntrinsicName, Value}, - interp::{ - Checker, Dataflow, EntryKind, EntryPoint, Frame, Interp, JoinSemiLattice, Runner, - WithCallStack, - }, - ir::{ - Base, BasicBlock, BasicBlockId, BinOp, Inst, Intrinsic, Literal, Location, Operand, - Projection, Rvalue, VarId, VarKind, Variable, - }, - reporter::{IntoVuln, Reporter, Severity, Vulnerability}, - utils::{ - add_const_to_val_vec, add_elements_to_intrinsic_struct, convert_operand_to_raw, - get_defid_from_varkind, get_prev_value, get_str_from_operand, return_value_from_string, - translate_request_type, - }, + definitions::DefId, + interp::{Checker, Dataflow, Frame, Interp, JoinSemiLattice, WithCallStack}, + ir::{BasicBlock, BasicBlockId, Intrinsic, Location}, worklist::WorkList, }; @@ -387,6 +365,11 @@ impl AuthZChecker { // TODO: make this an associated function on the Checker trait. self.vulns.into_iter() } + + pub fn into_vulns(self) -> impl IntoIterator { + // TODO: make this an associated function on the Checker trait. + self.vulns.into_iter() + } } impl Default for AuthZChecker { @@ -397,38 +380,7 @@ impl Default for AuthZChecker { #[derive(Debug)] pub struct AuthZVuln { - stack: String, - entry_func: String, - file: PathBuf, -} - -impl AuthZVuln { - fn new(callstack: Vec, env: &Environment, entry: &EntryPoint) -> Self { - let entry_func = match &entry.kind { - EntryKind::Function(func) => func.clone(), - EntryKind::Resolver(res, prop) => format!("{res}.{prop}"), - EntryKind::Empty => { - warn!("empty function"); - String::new() - } - }; - let file = entry.file.clone(); - let stack = Itertools::intersperse( - iter::once(&*entry_func).chain( - callstack - .into_iter() - .rev() - .map(|frame| env.def_name(frame.calling_function)), - ), - " -> ", - ) - .collect(); - Self { - stack, - entry_func, - file, - } - } + stackframe: Vec, } impl fmt::Display for AuthZVuln { @@ -437,31 +389,6 @@ impl fmt::Display for AuthZVuln { } } -impl IntoVuln for AuthZVuln { - fn into_vuln(self, reporter: &Reporter) -> Vulnerability { - use std::collections::hash_map::DefaultHasher; - use std::hash::{Hash, Hasher}; - - let mut hasher = DefaultHasher::new(); - self.file - .iter() - .skip_while(|comp| *comp != "src") - .for_each(|comp| comp.hash(&mut hasher)); - self.entry_func.hash(&mut hasher); - self.stack.hash(&mut hasher); - Vulnerability { - check_name: format!("Custom-Check-Authorization-{}", hasher.finish()), - description: format!("Authorization bypass detected through {} in {:?}.", self.entry_func, self.file), - recommendation: "Use the authorize API _https://developer.atlassian.com/platform/forge/runtime-reference/authorize-api/_ or manually authorize the user via the product REST APIs.", - proof: format!("Unauthorized API call via asApp() found via {}", self.stack), - severity: Severity::High, - app_key: reporter.app_key().to_owned(), - app_name: reporter.app_name().to_owned(), - date: reporter.current_date(), - } - } -} - impl WithCallStack for AuthZVuln { fn add_call_stack(&mut self, _stack: Vec) {} } @@ -486,182 +413,10 @@ impl<'cx> Runner<'cx> for AuthZChecker { ControlFlow::Continue(AuthorizeState::Yes) } Intrinsic::Fetch => ControlFlow::Continue(*state), - Intrinsic::ApiCall(_) if *state != AuthorizeState::Yes => { - let vuln = AuthZVuln::new(interp.callstack(), interp.env(), interp.entry()); - info!("Found a vuln!"); - self.vulns.push(vuln); - ControlFlow::Break(()) - } - Intrinsic::ApiCustomField if *state < AuthorizeState::CustomFieldOnly => { - let vuln = AuthZVuln::new(interp.callstack(), interp.env(), interp.entry()); - info!("Found a vuln!"); - self.vulns.push(vuln); - ControlFlow::Break(()) - } - Intrinsic::SecretFunction(_) - | Intrinsic::ApiCall(_) - | Intrinsic::SafeCall(_) - | Intrinsic::EnvRead - | Intrinsic::UserFieldAccess - | Intrinsic::ApiCustomField - | Intrinsic::StorageRead => ControlFlow::Continue(*state), - } - } -} - -impl<'cx> Checker<'cx> for AuthZChecker { - type Vuln = AuthZVuln; -} - -#[derive(Debug, PartialEq, Eq, Clone, Copy, PartialOrd, Ord)] -pub enum Authenticated { - No, - Yes, -} - -impl JoinSemiLattice for Authenticated { - const BOTTOM: Self = Self::No; - - #[inline] - fn join_changed(&mut self, other: &Self) -> bool { - let old = mem::replace(self, max(*other, *self)); - old == *self - } - - #[inline] - fn join(&self, other: &Self) -> Self { - max(*other, *self) - } -} - -pub struct AuthenticateDataflow { - needs_call: Vec, -} - -impl<'cx> Dataflow<'cx> for AuthenticateDataflow { - type State = Authenticated; - - fn with_interp>( - _interp: &Interp<'cx, C>, - ) -> Self { - Self { needs_call: vec![] } - } - - fn transfer_intrinsic>( - &mut self, - _interp: &mut Interp<'cx, C>, - _def: DefId, - _loc: Location, - _block: &'cx BasicBlock, - intrinsic: &'cx Intrinsic, - initial_state: Self::State, - operands: SmallVec<[crate::ir::Operand; 4]>, - ) -> Self::State { - match *intrinsic { - Intrinsic::Authorize(_) => initial_state, - Intrinsic::Fetch | Intrinsic::EnvRead | Intrinsic::StorageRead => { - debug!("authenticated"); - Authenticated::Yes - } - Intrinsic::SecretFunction(_) - | Intrinsic::ApiCall(_) - | Intrinsic::ApiCustomField - | Intrinsic::UserFieldAccess - | Intrinsic::SafeCall(_) => initial_state, - } - } - - fn transfer_call>( - &mut self, - interp: &Interp<'cx, C>, - def: DefId, - loc: Location, - _block: &'cx BasicBlock, - callee: &'cx crate::ir::Operand, - initial_state: Self::State, - operands: SmallVec<[crate::ir::Operand; 4]>, - ) -> Self::State { - let Some((callee_def, _body)) = self.resolve_call(interp, callee) else { - return initial_state; - }; - match interp.func_state(callee_def) { - Some(state) => { - if state == Authenticated::Yes { - debug!("Found call to authenticate at {def:?} {loc:?}"); - } - initial_state.join(&state) - } - None => { - let callee_name = interp.env().def_name(callee_def); - let caller_name = interp.env().def_name(def); - debug!("Found call to {callee_name} at {def:?} {caller_name}"); - self.needs_call.push(callee_def); - initial_state - } - } - } - - fn join_term>( - &mut self, - interp: &mut Interp<'cx, C>, - def: DefId, - block: &'cx BasicBlock, - state: Self::State, - worklist: &mut WorkList, - ) { - self.super_join_term(interp, def, block, state, worklist); - for def in self.needs_call.drain(..) { - worklist.push_front_blocks(interp.env(), def, interp.call_all); - } - } -} - -pub struct AuthenticateChecker { - visit: bool, - vulns: Vec, -} - -impl AuthenticateChecker { - pub fn new() -> Self { - Self { - visit: false, - vulns: vec![], - } - } - - pub fn into_vulns(self) -> impl IntoIterator { - self.vulns.into_iter() - } -} - -impl Default for AuthenticateChecker { - fn default() -> Self { - Self::new() - } -} - -impl<'cx> Runner<'cx> for AuthenticateChecker { - type State = Authenticated; - type Dataflow = AuthenticateDataflow; - - const NAME: &'static str = "Authentication"; - - fn visit_intrinsic( - &mut self, - interp: &Interp<'cx, Self>, - intrinsic: &'cx Intrinsic, - def: DefId, - state: &Self::State, - operands: Option>, - ) -> ControlFlow<(), Self::State> { - match *intrinsic { - Intrinsic::Authorize(_) => ControlFlow::Continue(*state), - Intrinsic::Fetch | Intrinsic::EnvRead | Intrinsic::StorageRead => { - debug!("authenticated"); - ControlFlow::Continue(Authenticated::Yes) - } - Intrinsic::ApiCall(_) | Intrinsic::ApiCustomField if *state == Authenticated::No => { - let vuln = AuthNVuln::new(interp.callstack(), interp.env(), interp.entry()); + Intrinsic::ApiCall if *state == AuthorizeState::No => { + let vuln = AuthZVuln { + stackframe: interp.callstack(), + }; info!("Found a vuln!"); self.vulns.push(vuln); ControlFlow::Break(()) From 6de69578a110fd843099170bde53014aa371671d Mon Sep 17 00:00:00 2001 From: Joshua Wong Date: Wed, 21 Dec 2022 13:53:08 -0600 Subject: [PATCH 402/517] feat: use IR 2.0 with AuthN checker --- crates/forge_analyzer/src/checkers.rs | 213 ++++++++++++++++++++++++++ crates/forge_analyzer/src/interp.rs | 19 +++ crates/fsrt/src/main.rs | 2 +- 3 files changed, 233 insertions(+), 1 deletion(-) diff --git a/crates/forge_analyzer/src/checkers.rs b/crates/forge_analyzer/src/checkers.rs index 38a184e7..21a0dc36 100644 --- a/crates/forge_analyzer/src/checkers.rs +++ b/crates/forge_analyzer/src/checkers.rs @@ -1627,3 +1627,216 @@ impl<'cx> Dataflow<'cx> for DefintionAnalysisRunner { vec![None] } } + +#[derive(Debug, PartialEq, Eq, Clone, Copy, PartialOrd, Ord)] +pub enum Authenticated { + No, + Yes, +} + +impl JoinSemiLattice for Authenticated { + const BOTTOM: Self = Self::No; + + #[inline] + fn join_changed(&mut self, other: &Self) -> bool { + let old = mem::replace(self, max(*other, *self)); + old == *self + } + + #[inline] + fn join(&self, other: &Self) -> Self { + max(*other, *self) + } +} + +pub struct AuthenticateDataflow { + needs_call: Vec, +} + +impl<'cx> Dataflow<'cx> for AuthenticateDataflow { + type State = Authenticated; + + fn with_interp>( + interp: &Interp<'cx, C>, + ) -> Self { + Self { needs_call: vec![] } + } + + fn transfer_intrinsic>( + &mut self, + _interp: &Interp<'cx, C>, + _def: DefId, + _loc: Location, + _block: &'cx BasicBlock, + intrinsic: &'cx Intrinsic, + initial_state: Self::State, + ) -> Self::State { + match *intrinsic { + Intrinsic::Authorize => initial_state, + Intrinsic::Fetch | Intrinsic::EnvRead | Intrinsic::StorageRead => { + debug!("authenticated"); + Authenticated::Yes + } + Intrinsic::ApiCall => initial_state, + Intrinsic::SafeCall => initial_state, + } + } + + fn transfer_call>( + &mut self, + interp: &Interp<'cx, C>, + def: DefId, + loc: Location, + block: &'cx BasicBlock, + callee: &'cx crate::ir::Operand, + initial_state: Self::State, + ) -> Self::State { + let Some((callee_def, body)) = self.resolve_call(interp, callee) else { + return initial_state; + }; + match interp.func_state(callee_def) { + Some(state) => { + if state == Authenticated::Yes { + debug!("Found call to authenticate at {def:?} {loc:?}"); + } + initial_state.join(&state) + } + None => { + let callee_name = interp.env().def_name(callee_def); + let caller_name = interp.env().def_name(def); + debug!("Found call to {callee_name} at {def:?} {caller_name}"); + self.needs_call.push(callee_def); + initial_state + } + } + } + + fn join_term>( + &mut self, + interp: &Interp<'cx, C>, + def: DefId, + block: &'cx BasicBlock, + state: Self::State, + worklist: &mut WorkList, + ) { + self.super_join_term(interp, def, block, state, worklist); + for def in self.needs_call.drain(..) { + worklist.push_front_blocks(interp.env(), def); + } + } +} + +pub struct AuthenticateChecker { + vulns: Vec, +} + +impl AuthenticateChecker { + pub fn new() -> Self { + Self { vulns: vec![] } + } + + pub fn into_vulns(self) -> impl IntoIterator { + self.vulns.into_iter() + } +} + +impl Default for AuthenticateChecker { + fn default() -> Self { + Self::new() + } +} + +impl<'cx> Checker<'cx> for AuthenticateChecker { + type State = Authenticated; + type Dataflow = AuthenticateDataflow; + type Vuln = AuthNVuln; + + fn visit_intrinsic( + &mut self, + interp: &Interp<'cx, Self>, + intrinsic: &'cx Intrinsic, + state: &Self::State, + ) -> ControlFlow<(), Self::State> { + match *intrinsic { + Intrinsic::Authorize => ControlFlow::Continue(*state), + Intrinsic::Fetch | Intrinsic::EnvRead | Intrinsic::StorageRead => { + debug!("authenticated"); + ControlFlow::Continue(Authenticated::Yes) + } + Intrinsic::ApiCall if *state == Authenticated::No => { + let vuln = AuthNVuln::new(interp.callstack(), interp.env(), interp.entry()); + info!("Found a vuln!"); + self.vulns.push(vuln); + ControlFlow::Break(()) + } + Intrinsic::ApiCall => ControlFlow::Continue(*state), + Intrinsic::SafeCall => ControlFlow::Continue(*state), + } + } +} + +#[derive(Debug)] +pub struct AuthNVuln { + stack: String, + entry_func: String, + file: PathBuf, +} + +impl AuthNVuln { + fn new(mut callstack: Vec, env: &Environment, entry: &EntryPoint) -> Self { + let entry_func = match &entry.kind { + EntryKind::Function(func) => func.clone(), + EntryKind::Resolver(res, prop) => format!("{res}.{prop}"), + EntryKind::Empty => { + warn!("empty function"); + String::new() + } + }; + let file = entry.file.clone(); + let stack = iter::once(&*entry_func) + .chain( + callstack + .into_iter() + .rev() + .map(|frame| env.def_name(frame.calling_function)), + ) + .intersperse(" -> ") + .collect(); + Self { + stack, + entry_func, + file, + } + } +} + +impl fmt::Display for AuthNVuln { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "Authentication vulnerability") + } +} + +impl IntoVuln for AuthNVuln { + fn into_vuln(self, reporter: &Reporter) -> Vulnerability { + use std::collections::hash_map::DefaultHasher; + use std::hash::{Hash, Hasher}; + + let mut hasher = DefaultHasher::new(); + self.file.hash(&mut hasher); + self.entry_func.hash(&mut hasher); + Vulnerability { + check_name: format!("Custom-Check-Authentication-{}", hasher.finish()), + description: format!("Insufficient Authentication through webhook {} in {:?}.", self.entry_func, self.file), + recommendation: "Properly authenticate incoming webhooks and ensure that any shared secrets are stored in Forge Secure Storage.", + proof: format!("Unauthenticated API call via asApp() found via {}", self.stack), + severity: Severity::High, + app_key: reporter.app_key().to_owned(), + app_name: reporter.app_name().to_owned(), + date: reporter.current_date(), + } + } +} + +impl WithCallStack for AuthNVuln { + fn add_call_stack(&mut self, stack: Vec) {} +} diff --git a/crates/forge_analyzer/src/interp.rs b/crates/forge_analyzer/src/interp.rs index 32dc3611..2aafa870 100644 --- a/crates/forge_analyzer/src/interp.rs +++ b/crates/forge_analyzer/src/interp.rs @@ -484,6 +484,20 @@ pub(crate) struct EntryPoint { pub(crate) kind: EntryKind, } +#[derive(Clone, Debug, PartialEq, Eq, Default)] +pub(crate) enum EntryKind { + Function(String), + Resolver(String, JsWord), + #[default] + Empty, +} + +#[derive(Clone, Debug, PartialEq, Eq, Default)] +pub(crate) struct EntryPoint { + pub(crate) file: PathBuf, + pub(crate) kind: EntryKind, +} + #[derive(Debug)] pub struct Interp<'cx, C: Runner<'cx>> { pub env: &'cx Environment, @@ -734,6 +748,11 @@ impl<'cx, C: Runner<'cx>> Interp<'cx, C> { .map(|(&(_, callee), &loc)| (callee, loc)) } + #[inline] + pub(crate) fn entry(&self) -> &EntryPoint { + &self.entry + } + fn run(&mut self, func_def: DefId) { if self.dataflow_visited.contains(&func_def) { return; diff --git a/crates/fsrt/src/main.rs b/crates/fsrt/src/main.rs index c5a289cc..67258f48 100644 --- a/crates/fsrt/src/main.rs +++ b/crates/fsrt/src/main.rs @@ -23,7 +23,7 @@ use swc_core::{ visit::FoldWith, }, }; -use tracing::{debug, instrument, warn}; +use tracing::{debug, warn}; use tracing_subscriber::{prelude::*, EnvFilter}; use tracing_tree::HierarchicalLayer; From e2bf1e2e1371cf51db98f72004dc21fd3e2021ce Mon Sep 17 00:00:00 2001 From: Joshua Wong Date: Wed, 21 Dec 2022 17:38:42 -0600 Subject: [PATCH 403/517] refactor(analyzer): remove dead code --- crates/forge_analyzer/src/checkers.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/crates/forge_analyzer/src/checkers.rs b/crates/forge_analyzer/src/checkers.rs index 21a0dc36..37e780f1 100644 --- a/crates/forge_analyzer/src/checkers.rs +++ b/crates/forge_analyzer/src/checkers.rs @@ -1657,7 +1657,7 @@ impl<'cx> Dataflow<'cx> for AuthenticateDataflow { type State = Authenticated; fn with_interp>( - interp: &Interp<'cx, C>, + _interp: &Interp<'cx, C>, ) -> Self { Self { needs_call: vec![] } } @@ -1687,7 +1687,7 @@ impl<'cx> Dataflow<'cx> for AuthenticateDataflow { interp: &Interp<'cx, C>, def: DefId, loc: Location, - block: &'cx BasicBlock, + _block: &'cx BasicBlock, callee: &'cx crate::ir::Operand, initial_state: Self::State, ) -> Self::State { @@ -1783,7 +1783,7 @@ pub struct AuthNVuln { } impl AuthNVuln { - fn new(mut callstack: Vec, env: &Environment, entry: &EntryPoint) -> Self { + fn new(callstack: Vec, env: &Environment, entry: &EntryPoint) -> Self { let entry_func = match &entry.kind { EntryKind::Function(func) => func.clone(), EntryKind::Resolver(res, prop) => format!("{res}.{prop}"), @@ -1838,5 +1838,5 @@ impl IntoVuln for AuthNVuln { } impl WithCallStack for AuthNVuln { - fn add_call_stack(&mut self, stack: Vec) {} + fn add_call_stack(&mut self, _stack: Vec) {} } From a0d5016ff8a4ec25da14209ad03a3eec30ce2cd1 Mon Sep 17 00:00:00 2001 From: Joshua Wong Date: Wed, 21 Dec 2022 17:52:44 -0600 Subject: [PATCH 404/517] refactor: clean up unused code --- crates/forge_analyzer/src/checkers.rs | 11 ++++++----- crates/forge_analyzer/src/interp.rs | 11 +++++++++++ 2 files changed, 17 insertions(+), 5 deletions(-) diff --git a/crates/forge_analyzer/src/checkers.rs b/crates/forge_analyzer/src/checkers.rs index 37e780f1..d0a7c60f 100644 --- a/crates/forge_analyzer/src/checkers.rs +++ b/crates/forge_analyzer/src/checkers.rs @@ -1793,15 +1793,16 @@ impl AuthNVuln { } }; let file = entry.file.clone(); - let stack = iter::once(&*entry_func) - .chain( + let stack = Itertools::intersperse( + iter::once(&*entry_func).chain( callstack .into_iter() .rev() .map(|frame| env.def_name(frame.calling_function)), - ) - .intersperse(" -> ") - .collect(); + ), + " -> ", + ) + .collect(); Self { stack, entry_func, diff --git a/crates/forge_analyzer/src/interp.rs b/crates/forge_analyzer/src/interp.rs index 2aafa870..d73cc9cc 100644 --- a/crates/forge_analyzer/src/interp.rs +++ b/crates/forge_analyzer/src/interp.rs @@ -753,6 +753,17 @@ impl<'cx, C: Runner<'cx>> Interp<'cx, C> { &self.entry } + #[inline] + pub fn callees( + &self, + caller: DefId, + ) -> impl DoubleEndedIterator + '_ { + self.call_graph + .callgraph + .range((caller, DefId::new(0))..(caller, DefId::new(u32::MAX))) + .map(|(&(_, callee), &loc)| (callee, loc)) + } + fn run(&mut self, func_def: DefId) { if self.dataflow_visited.contains(&func_def) { return; From 62a276c009972e9fcc65aba41db04b5a753c266c Mon Sep 17 00:00:00 2001 From: Joshua Wong Date: Wed, 18 Jan 2023 14:02:27 -0600 Subject: [PATCH 405/517] fix(parser): parse files with decorators Closes #9 --- crates/fsrt/src/main.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/fsrt/src/main.rs b/crates/fsrt/src/main.rs index 67258f48..c5a289cc 100644 --- a/crates/fsrt/src/main.rs +++ b/crates/fsrt/src/main.rs @@ -23,7 +23,7 @@ use swc_core::{ visit::FoldWith, }, }; -use tracing::{debug, warn}; +use tracing::{debug, instrument, warn}; use tracing_subscriber::{prelude::*, EnvFilter}; use tracing_tree::HierarchicalLayer; From c8842f9c28179e699110b58ed8a23e3e594894fc Mon Sep 17 00:00:00 2001 From: James Gersbach Date: Wed, 7 Jun 2023 10:22:47 -0700 Subject: [PATCH 406/517] feat: EAS-1592 mistakingly classifies resolvers exposed from consumers as user reachable --- .gitignore | 1 + .vscode/launch.json | 120 ++++++++++++++++++++++++++++++++++++++++ crates/fsrt/src/main.rs | 3 + 3 files changed, 124 insertions(+) create mode 100644 .vscode/launch.json diff --git a/.gitignore b/.gitignore index ea8c4bf7..831f1db6 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ /target +/test-apps diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 00000000..871af283 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,120 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "type": "lldb", + "request": "launch", + "name": "Debug unit tests in library 'forge_analyzer'", + "cargo": { + "args": [ + "test", + "--no-run", + "--lib", + "--package=forge_analyzer" + ], + "filter": { + "name": "forge_analyzer", + "kind": "lib" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug unit tests in library 'forge_file_resolver'", + "cargo": { + "args": [ + "test", + "--no-run", + "--lib", + "--package=forge_file_resolver" + ], + "filter": { + "name": "forge_file_resolver", + "kind": "lib" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug unit tests in library 'forge_utils'", + "cargo": { + "args": [ + "test", + "--no-run", + "--lib", + "--package=forge_utils" + ], + "filter": { + "name": "forge_utils", + "kind": "lib" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug unit tests in library 'forge_loader'", + "cargo": { + "args": [ + "test", + "--no-run", + "--lib", + "--package=forge_loader" + ], + "filter": { + "name": "forge_loader", + "kind": "lib" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug executable 'fsrt'", + "cargo": { + "args": [ + "build", + "--bin=fsrt", + "--package=fsrt" + ], + "filter": { + "name": "fsrt", + "kind": "bin" + } + }, + "args": ["./test-apps/jira-damn-vulnerable-forge-app"], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug unit tests in executable 'fsrt'", + "cargo": { + "args": [ + "test", + "--no-run", + "--bin=fsrt", + "--package=fsrt" + ], + "filter": { + "name": "fsrt", + "kind": "bin" + } + }, + "cwd": "${workspaceFolder}" + } + ] +} \ No newline at end of file diff --git a/crates/fsrt/src/main.rs b/crates/fsrt/src/main.rs index c5a289cc..fbc3a36b 100644 --- a/crates/fsrt/src/main.rs +++ b/crates/fsrt/src/main.rs @@ -197,6 +197,7 @@ fn scan_directory(dir: PathBuf, function: Option<&str>, opts: &Args) -> Result<( let manifest: ForgeManifest = serde_yaml::from_str(&manifest).into_diagnostic()?; let name = manifest.app.name.unwrap_or_default(); +<<<<<<< HEAD let requested_permissions = manifest.permissions; let permission_scopes = requested_permissions.scopes; let permissions_declared: HashSet = @@ -211,6 +212,8 @@ fn scan_directory(dir: PathBuf, function: Option<&str>, opts: &Args) -> Result<( vec![] }; +======= +>>>>>>> 32556bb (feat: EAS-1592 mistakingly classifies resolvers exposed from consumers as user reachable) let paths = collect_sourcefiles(dir.join("src/")).collect::>(); let transpiled_async = paths.iter().any(|path| { From 7b78e0b6594793b0b72721529ce9104b125f0693 Mon Sep 17 00:00:00 2001 From: James Gersbach Date: Tue, 13 Jun 2023 10:40:57 -0700 Subject: [PATCH 407/517] removing changes to files --- crates/forge_analyzer/src/definitions.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/forge_analyzer/src/definitions.rs b/crates/forge_analyzer/src/definitions.rs index c2365b90..f9cbf915 100644 --- a/crates/forge_analyzer/src/definitions.rs +++ b/crates/forge_analyzer/src/definitions.rs @@ -810,7 +810,7 @@ struct FunctionAnalyzer<'cx> { module: ModId, current_def: DefId, assigning_to: Option, - pub body: Body, + body: Body, block: BasicBlockId, secret_packages: Vec, operand_stack: Vec, From 375b5741dca08176c3328ba57d1b68d510f46905 Mon Sep 17 00:00:00 2001 From: James Gersbach Date: Tue, 13 Jun 2023 10:41:36 -0700 Subject: [PATCH 408/517] removing changes to files --- crates/forge_analyzer/src/ir.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/forge_analyzer/src/ir.rs b/crates/forge_analyzer/src/ir.rs index ddcb6dde..759f0719 100644 --- a/crates/forge_analyzer/src/ir.rs +++ b/crates/forge_analyzer/src/ir.rs @@ -116,7 +116,7 @@ pub struct Location { } #[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub enum VarKind { +pub(crate) enum VarKind { LocalDef(DefId), GlobalRef(DefId), Temp { parent: Option }, From 47005532f0706d1d6a9bf1ba75e7e295c933a529 Mon Sep 17 00:00:00 2001 From: James Gersbach Date: Wed, 14 Jun 2023 12:09:15 -0700 Subject: [PATCH 409/517] adding ir for insering symbols --- crates/forge_analyzer/src/interp.rs | 1 + crates/forge_analyzer/src/ir.rs | 4 ++++ 2 files changed, 5 insertions(+) diff --git a/crates/forge_analyzer/src/interp.rs b/crates/forge_analyzer/src/interp.rs index d73cc9cc..4d7b50cb 100644 --- a/crates/forge_analyzer/src/interp.rs +++ b/crates/forge_analyzer/src/interp.rs @@ -420,6 +420,7 @@ pub trait Runner<'cx>: Sized { Rvalue::Unary(_, _) | Rvalue::Bin(_, _, _) | Rvalue::Read(_) + | Rvalue::ClassAssignment(_, _, _) | Rvalue::Phi(_) | Rvalue::Template(_) => ControlFlow::Continue(curr_state.clone()), } diff --git a/crates/forge_analyzer/src/ir.rs b/crates/forge_analyzer/src/ir.rs index 759f0719..25e49b23 100644 --- a/crates/forge_analyzer/src/ir.rs +++ b/crates/forge_analyzer/src/ir.rs @@ -98,6 +98,7 @@ pub enum Rvalue { Bin(BinOp, Operand, Operand), Read(Operand), Call(Operand, SmallVec<[Operand; 4]>), + ClassAssignment(VarId, DefId, VarId), Intrinsic(Intrinsic, SmallVec<[Operand; 4]>), Phi(Vec<(VarId, BasicBlockId)>), Template(Template), @@ -810,6 +811,9 @@ impl fmt::Display for Rvalue { } write!(f, ")") } + Rvalue::ClassAssignment(class_id, def_id, prop_id) => { + write!(f, "{class_id}.insert({def_id:?}, {prop_id})") + } Rvalue::Intrinsic(ref intrinsic, ref args) => { write!(f, "{intrinsic}(")?; for arg in args { From 6904df343fd45b98a1592ea7a185bf7cd29c8339 Mon Sep 17 00:00:00 2001 From: James Gersbach Date: Wed, 14 Jun 2023 22:37:41 -0700 Subject: [PATCH 410/517] adding projections instead of assignment --- crates/forge_analyzer/src/definitions.rs | 1 + crates/forge_analyzer/src/interp.rs | 1 - crates/forge_analyzer/src/ir.rs | 6 +----- test-apps/jira-damn-vulnerable-forge-app/src/utils.js | 2 +- 4 files changed, 3 insertions(+), 7 deletions(-) diff --git a/crates/forge_analyzer/src/definitions.rs b/crates/forge_analyzer/src/definitions.rs index f9cbf915..ae40a1e8 100644 --- a/crates/forge_analyzer/src/definitions.rs +++ b/crates/forge_analyzer/src/definitions.rs @@ -1465,6 +1465,7 @@ impl<'cx> FunctionAnalyzer<'cx> { .res .add_anonymous("__UNKNOWN", AnonType::Obj, self.module); let class_var_id = self.body.add_var(VarKind::LocalDef((def_id))); + let mut var = Variable::new(class_var_id); if let DefKind::GlobalObj(class_id) = self.res.defs.defs[def_id] { props.iter().for_each(|prop_or_spread| { let mut var = Variable::new(class_var_id); diff --git a/crates/forge_analyzer/src/interp.rs b/crates/forge_analyzer/src/interp.rs index 4d7b50cb..d73cc9cc 100644 --- a/crates/forge_analyzer/src/interp.rs +++ b/crates/forge_analyzer/src/interp.rs @@ -420,7 +420,6 @@ pub trait Runner<'cx>: Sized { Rvalue::Unary(_, _) | Rvalue::Bin(_, _, _) | Rvalue::Read(_) - | Rvalue::ClassAssignment(_, _, _) | Rvalue::Phi(_) | Rvalue::Template(_) => ControlFlow::Continue(curr_state.clone()), } diff --git a/crates/forge_analyzer/src/ir.rs b/crates/forge_analyzer/src/ir.rs index 25e49b23..ddcb6dde 100644 --- a/crates/forge_analyzer/src/ir.rs +++ b/crates/forge_analyzer/src/ir.rs @@ -98,7 +98,6 @@ pub enum Rvalue { Bin(BinOp, Operand, Operand), Read(Operand), Call(Operand, SmallVec<[Operand; 4]>), - ClassAssignment(VarId, DefId, VarId), Intrinsic(Intrinsic, SmallVec<[Operand; 4]>), Phi(Vec<(VarId, BasicBlockId)>), Template(Template), @@ -117,7 +116,7 @@ pub struct Location { } #[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub(crate) enum VarKind { +pub enum VarKind { LocalDef(DefId), GlobalRef(DefId), Temp { parent: Option }, @@ -811,9 +810,6 @@ impl fmt::Display for Rvalue { } write!(f, ")") } - Rvalue::ClassAssignment(class_id, def_id, prop_id) => { - write!(f, "{class_id}.insert({def_id:?}, {prop_id})") - } Rvalue::Intrinsic(ref intrinsic, ref args) => { write!(f, "{intrinsic}(")?; for arg in args { diff --git a/test-apps/jira-damn-vulnerable-forge-app/src/utils.js b/test-apps/jira-damn-vulnerable-forge-app/src/utils.js index 025fed04..ba05d92e 100755 --- a/test-apps/jira-damn-vulnerable-forge-app/src/utils.js +++ b/test-apps/jira-damn-vulnerable-forge-app/src/utils.js @@ -104,7 +104,7 @@ export async function writeComment(issueIdOrKey, comment) { const resp = await api .asApp() .requestJira(route`/rest/api/3/issue/${issueIdOrKey}/comment`, { - method: 'POST', + method, headers: { Accept: 'application/json', 'Content-Type': 'application/json', From 0047856064b097ac1f1783e96e7a44d382bca12a Mon Sep 17 00:00:00 2001 From: James Gersbach Date: Thu, 15 Jun 2023 08:23:58 -0700 Subject: [PATCH 411/517] reverting changes that are unneeded --- .gitignore | 1 - test-apps/jira-damn-vulnerable-forge-app/src/utils.js | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/.gitignore b/.gitignore index 831f1db6..ea8c4bf7 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1 @@ /target -/test-apps diff --git a/test-apps/jira-damn-vulnerable-forge-app/src/utils.js b/test-apps/jira-damn-vulnerable-forge-app/src/utils.js index ba05d92e..025fed04 100755 --- a/test-apps/jira-damn-vulnerable-forge-app/src/utils.js +++ b/test-apps/jira-damn-vulnerable-forge-app/src/utils.js @@ -104,7 +104,7 @@ export async function writeComment(issueIdOrKey, comment) { const resp = await api .asApp() .requestJira(route`/rest/api/3/issue/${issueIdOrKey}/comment`, { - method, + method: 'POST', headers: { Accept: 'application/json', 'Content-Type': 'application/json', From 1e851e678fe586290c9ce76336359675f37b3d94 Mon Sep 17 00:00:00 2001 From: James Gersbach Date: Fri, 16 Jun 2023 10:30:42 -0700 Subject: [PATCH 412/517] basic checker --- crates/forge_analyzer/src/checkers.rs | 228 ++++++++++++++++++ crates/forge_analyzer/src/ctx.rs | 5 +- crates/forge_analyzer/src/definitions.rs | 2 +- crates/forge_analyzer/src/engine.rs | 1 - crates/forge_analyzer/src/ir.rs | 1 + .../forge_analyzer/src/permission_checker.rs | 0 crates/fsrt/src/main.rs | 2 + 7 files changed, 235 insertions(+), 4 deletions(-) create mode 100644 crates/forge_analyzer/src/permission_checker.rs diff --git a/crates/forge_analyzer/src/checkers.rs b/crates/forge_analyzer/src/checkers.rs index d0a7c60f..e5b11238 100644 --- a/crates/forge_analyzer/src/checkers.rs +++ b/crates/forge_analyzer/src/checkers.rs @@ -1841,3 +1841,231 @@ impl IntoVuln for AuthNVuln { impl WithCallStack for AuthNVuln { fn add_call_stack(&mut self, _stack: Vec) {} } + +pub struct PermisisionDataflow { + needs_call: Vec, + variables: FxHashMap, +} + +impl WithCallStack for PermissionVuln { + fn add_call_stack(&mut self, _stack: Vec) {} +} + +impl<'cx> Dataflow<'cx> for PermisisionDataflow { + type State = PermissionTest; + + fn with_interp>( + _interp: &Interp<'cx, C>, + ) -> Self { + Self { + needs_call: vec![], + variables: FxHashMap::default(), + } + } + + fn transfer_intrinsic>( + &mut self, + _interp: &Interp<'cx, C>, + _def: DefId, + _loc: Location, + _block: &'cx BasicBlock, + intrinsic: &'cx Intrinsic, + initial_state: Self::State, + ) -> Self::State { + match *intrinsic { + Intrinsic::Authorize => { + debug!("authorize intrinsic found"); + PermissionTest::Yes + } + Intrinsic::Fetch => initial_state, + Intrinsic::ApiCall => initial_state, + Intrinsic::SafeCall => initial_state, + Intrinsic::EnvRead => initial_state, + Intrinsic::StorageRead => initial_state, + } + } + + fn transfer_call>( + &mut self, + interp: &Interp<'cx, C>, + def: DefId, + loc: Location, + _block: &'cx BasicBlock, + callee: &'cx crate::ir::Operand, + initial_state: Self::State, + ) -> Self::State { + for inst in &_block.insts { + match inst { + Inst::Assign(variable, rvalue) => match variable.base { + Base::Var(varid) => { + match rvalue { + Rvalue::Read(operand) => { + // case not phi + if self.variables.contains_key(&varid) { + // currently assuming prev value is not phi + let prev_vars = &self.variables[&varid]; + match prev_vars { + Value::Const(prev_var_const) => { + let var_vec = vec![ + prev_var_const.clone(), + Const::Literal(operand.clone()), + ]; + + self.variables + .insert(varid, Value::Phi(Vec::from(var_vec))); + } + _ => {} + } + } else { + self.variables.insert( + varid, + Value::Const(Const::Literal(operand.clone())), + ); + } + } + _ => {} + } + } + _ => {} + }, + _ => {} + } + } + + let Some((callee_def, _body)) = self.resolve_call(interp, callee) else { + return initial_state; + }; + match interp.func_state(callee_def) { + Some(state) => { + if state == PermissionTest::Yes { + debug!("Found call to authorize at {def:?} {loc:?}"); + } + initial_state.join(&state) + } + None => { + let callee_name = interp.env().def_name(callee_def); + let caller_name = interp.env().def_name(def); + debug!("Found call to {callee_name} at {def:?} {caller_name}"); + self.needs_call.push(callee_def); + initial_state + } + } + } + + fn join_term>( + &mut self, + interp: &Interp<'cx, C>, + def: DefId, + block: &'cx BasicBlock, + state: Self::State, + worklist: &mut WorkList, + ) { + self.super_join_term(interp, def, block, state, worklist); + for def in self.needs_call.drain(..) { + worklist.push_front_blocks(interp.env(), def); + } + } +} + +pub struct PermissionChecker { + vulns: Vec, +} + +impl PermissionChecker { + pub fn new() -> Self { + Self { vulns: vec![] } + } + + pub fn into_vulns(self) -> impl IntoIterator { + self.vulns.into_iter() + } +} + +impl Default for PermissionChecker { + fn default() -> Self { + Self::new() + } +} + +impl fmt::Display for PermissionVuln { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "Authentication vulnerability") + } +} + +#[derive(Debug, PartialEq, Eq, Clone, Copy, PartialOrd, Ord)] +pub enum PermissionTest { + Yes, +} + +impl JoinSemiLattice for PermissionTest { + const BOTTOM: Self = Self::Yes; + + #[inline] + fn join_changed(&mut self, other: &Self) -> bool { + let old = mem::replace(self, max(*other, *self)); + old == *self + } + + #[inline] + fn join(&self, other: &Self) -> Self { + max(*other, *self) + } +} + +impl<'cx> Checker<'cx> for PermissionChecker { + type State = PermissionTest; + type Dataflow = PermisisionDataflow; + type Vuln = PermissionVuln; + + fn visit_intrinsic( + &mut self, + interp: &Interp<'cx, Self>, + intrinsic: &'cx Intrinsic, + state: &Self::State, + ) -> ControlFlow<(), Self::State> { + match *intrinsic { + Intrinsic::Authorize => ControlFlow::Continue(*state), + Intrinsic::Fetch | Intrinsic::EnvRead | Intrinsic::StorageRead => { + debug!("authenticated"); + ControlFlow::Continue(PermissionTest::Yes) + } + Intrinsic::ApiCall if *state == PermissionTest::Yes => { + let vuln = PermissionVuln::new(); + ControlFlow::Break(()) + } + Intrinsic::ApiCall => ControlFlow::Continue(*state), + Intrinsic::SafeCall => ControlFlow::Continue(*state), + } + } +} + +#[derive(Debug)] +pub struct PermissionVuln { + // unused_permissions: HashSet, +} + +impl PermissionVuln { + pub fn new(/*unused_permissions: HashSet */) -> PermissionVuln { + PermissionVuln { /*unused_permissions*/ } + } +} + +impl IntoVuln for PermissionVuln { + fn into_vuln(self, reporter: &Reporter) -> Vulnerability { + Vulnerability { + check_name: format!("Least-Privilege"), + description: format!( + "Unused permissions listed in manifest file:.", + // self.unused_permissions.into_iter().join(", ") + ), + // unused_permissions: Some(self.unused_permissions), + recommendation: "Remove permissions in manifest file that are not needed.", + proof: format!("Unused permissions found in manifest.yml"), + severity: Severity::Low, + app_key: reporter.app_key().to_string(), + app_name: reporter.app_name().to_string(), + date: reporter.current_date(), + } + } +} diff --git a/crates/forge_analyzer/src/ctx.rs b/crates/forge_analyzer/src/ctx.rs index aba5080f..1c7c3ab8 100644 --- a/crates/forge_analyzer/src/ctx.rs +++ b/crates/forge_analyzer/src/ctx.rs @@ -191,8 +191,8 @@ impl AppCtx { }) }); debug!(?input, "transfer in"); - let next = funcs.blocks[block].stmts.iter_enumerated().fold( - (None, false), + let next = funcs.blocks[block].stmts.iter_enumerated().fold( + (None, false), |(call, curr), (_stmt_id, val)| match val { IrStmt::Call(ref call_id) => { if let Some(meta) = self.func(call_id) { @@ -235,6 +235,7 @@ impl AppCtx { #[inline] pub(crate) fn func_mut(&mut self, func: &ModItem) -> Option<&mut FunctionMeta> { + self.modctx .get_mut(func.mod_id)? .functions diff --git a/crates/forge_analyzer/src/definitions.rs b/crates/forge_analyzer/src/definitions.rs index ae40a1e8..fa271b6c 100644 --- a/crates/forge_analyzer/src/definitions.rs +++ b/crates/forge_analyzer/src/definitions.rs @@ -810,7 +810,7 @@ struct FunctionAnalyzer<'cx> { module: ModId, current_def: DefId, assigning_to: Option, - body: Body, + pub body: Body, block: BasicBlockId, secret_packages: Vec, operand_stack: Vec, diff --git a/crates/forge_analyzer/src/engine.rs b/crates/forge_analyzer/src/engine.rs index 43204fba..65655ef5 100644 --- a/crates/forge_analyzer/src/engine.rs +++ b/crates/forge_analyzer/src/engine.rs @@ -133,7 +133,6 @@ impl<'ctx> Machine<'ctx> { let result = self.app.func_res(&orig_func); info!(?result, "analysis complete"); let fname: &str = &orig_func.ident.0; - println!("Result of analyzing {fname}:"); match result { AuthZVal::Unauthorized => { println!("FAIL: Unauthorized call detected from handler: {fname}") diff --git a/crates/forge_analyzer/src/ir.rs b/crates/forge_analyzer/src/ir.rs index ddcb6dde..b3efe602 100644 --- a/crates/forge_analyzer/src/ir.rs +++ b/crates/forge_analyzer/src/ir.rs @@ -304,6 +304,7 @@ impl Body { class_instantiations: Default::default(), ident_to_local: Default::default(), def_id_to_vars: Default::default(), + var_id_to_value: Default::default(), predecessors: Default::default(), } } diff --git a/crates/forge_analyzer/src/permission_checker.rs b/crates/forge_analyzer/src/permission_checker.rs new file mode 100644 index 00000000..e69de29b diff --git a/crates/fsrt/src/main.rs b/crates/fsrt/src/main.rs index fbc3a36b..f1665e45 100644 --- a/crates/fsrt/src/main.rs +++ b/crates/fsrt/src/main.rs @@ -246,6 +246,7 @@ fn scan_directory(dir: PathBuf, function: Option<&str>, opts: &Args) -> Result<( return Ok(()); } proj.add_funcs(funcrefs); + resolve_calls(&mut proj.ctx); if let Some(func) = opts.dump_ir.as_ref() { let mut lock = std::io::stdout().lock(); @@ -412,6 +413,7 @@ fn scan_directory(dir: PathBuf, function: Option<&str>, opts: &Args) -> Result<( reporter .add_vulnerabilities(vec![PermissionVuln::new(perm_interp.permissions)].into_iter()); } + let report = serde_json::to_string(&reporter.into_report()).into_diagnostic()?; debug!("On the debug layer: Writing Report"); match &opts.out { From fe5af2ef2c5e5c75205e91759bd0fbc4e1035e9b Mon Sep 17 00:00:00 2001 From: James Gersbach Date: Fri, 16 Jun 2023 11:55:06 -0700 Subject: [PATCH 413/517] moving inst checking to the transfer block --- crates/forge_analyzer/src/checkers.rs | 23 ++++++----------------- 1 file changed, 6 insertions(+), 17 deletions(-) diff --git a/crates/forge_analyzer/src/checkers.rs b/crates/forge_analyzer/src/checkers.rs index e5b11238..58ab3a36 100644 --- a/crates/forge_analyzer/src/checkers.rs +++ b/crates/forge_analyzer/src/checkers.rs @@ -1932,24 +1932,13 @@ impl<'cx> Dataflow<'cx> for PermisisionDataflow { } } - let Some((callee_def, _body)) = self.resolve_call(interp, callee) else { - return initial_state; - }; - match interp.func_state(callee_def) { - Some(state) => { - if state == PermissionTest::Yes { - debug!("Found call to authorize at {def:?} {loc:?}"); - } - initial_state.join(&state) - } - None => { - let callee_name = interp.env().def_name(callee_def); - let caller_name = interp.env().def_name(def); - debug!("Found call to {callee_name} at {def:?} {caller_name}"); - self.needs_call.push(callee_def); - initial_state - } + // println!("self.variables {:#?}", self.variables); + + for (stmt, inst) in block.iter().enumerate() { + let loc = Location::new(bb, stmt as u32); + state = self.transfer_inst(interp, def, loc, block, inst, state); } + state } fn join_term>( From 0c6ee69ac41539cb35b03cb4132606e243c21c10 Mon Sep 17 00:00:00 2001 From: James Gersbach Date: Thu, 22 Jun 2023 22:24:10 -0700 Subject: [PATCH 414/517] wip --- crates/forge_analyzer/src/checkers.rs | 150 +++++++++++++++++++++----- crates/forge_analyzer/src/ctx.rs | 5 +- crates/forge_analyzer/src/interp.rs | 10 +- crates/forge_analyzer/src/ir.rs | 3 +- crates/forge_analyzer/src/worklist.rs | 16 +-- test-apps/basic/src/index.tsx | 33 +++--- 6 files changed, 157 insertions(+), 60 deletions(-) diff --git a/crates/forge_analyzer/src/checkers.rs b/crates/forge_analyzer/src/checkers.rs index 58ab3a36..60c43b76 100644 --- a/crates/forge_analyzer/src/checkers.rs +++ b/crates/forge_analyzer/src/checkers.rs @@ -1670,6 +1670,7 @@ impl<'cx> Dataflow<'cx> for AuthenticateDataflow { _block: &'cx BasicBlock, intrinsic: &'cx Intrinsic, initial_state: Self::State, + operands: SmallVec<[crate::ir::Operand; 4]>, ) -> Self::State { match *intrinsic { Intrinsic::Authorize => initial_state, @@ -1690,6 +1691,7 @@ impl<'cx> Dataflow<'cx> for AuthenticateDataflow { _block: &'cx BasicBlock, callee: &'cx crate::ir::Operand, initial_state: Self::State, + operands: SmallVec<[crate::ir::Operand; 4]>, ) -> Self::State { let Some((callee_def, body)) = self.resolve_call(interp, callee) else { return initial_state; @@ -1721,7 +1723,7 @@ impl<'cx> Dataflow<'cx> for AuthenticateDataflow { ) { self.super_join_term(interp, def, block, state, worklist); for def in self.needs_call.drain(..) { - worklist.push_front_blocks(interp.env(), def); + worklist.push_front_blocks(interp.env(), def, vec![]); } } } @@ -1756,6 +1758,7 @@ impl<'cx> Checker<'cx> for AuthenticateChecker { interp: &Interp<'cx, Self>, intrinsic: &'cx Intrinsic, state: &Self::State, + operands: Option>, ) -> ControlFlow<(), Self::State> { match *intrinsic { Intrinsic::Authorize => ControlFlow::Continue(*state), @@ -1767,7 +1770,7 @@ impl<'cx> Checker<'cx> for AuthenticateChecker { let vuln = AuthNVuln::new(interp.callstack(), interp.env(), interp.entry()); info!("Found a vuln!"); self.vulns.push(vuln); - ControlFlow::Break(()) + ControlFlow::Continue(*state) } Intrinsic::ApiCall => ControlFlow::Continue(*state), Intrinsic::SafeCall => ControlFlow::Continue(*state), @@ -1851,6 +1854,44 @@ impl WithCallStack for PermissionVuln { fn add_call_stack(&mut self, _stack: Vec) {} } +impl PermisisionDataflow { + fn add_variables(&mut self, rvalue: &Rvalue, defid: &DefId) { + match rvalue { + Rvalue::Read(operand) => { + if self.variables_from_defid.contains_key(&defid) + && self.variables_from_defid.get(&defid).unwrap() + != &Value::Const(Const::Literal(operand.clone())) + { + // currently assuming prev value is not phi + let prev_vars = &self.variables_from_defid[&defid]; + match prev_vars { + Value::Const(prev_var_const) => { + match operand { + Operand::Lit(_) => {} + Operand::Var(var) => match var.base { + Base::Var(var_id) => {} + _ => {} + }, + } + let var_vec = + vec![prev_var_const.clone(), Const::Literal(operand.clone())]; + + self.variables_from_defid + .insert(*defid, Value::Phi(Vec::from(var_vec))); + } + _ => {} + } + } else { + let value = Value::Const(Const::Literal(operand.clone())); + self.variables_from_defid.insert(*defid, value.clone()); + } + } + Rvalue::Template(template) => {} + _ => {} + } + } +} + impl<'cx> Dataflow<'cx> for PermisisionDataflow { type State = PermissionTest; @@ -1871,12 +1912,85 @@ impl<'cx> Dataflow<'cx> for PermisisionDataflow { _block: &'cx BasicBlock, intrinsic: &'cx Intrinsic, initial_state: Self::State, + operands: SmallVec<[crate::ir::Operand; 4]>, ) -> Self::State { match *intrinsic { - Intrinsic::Authorize => { - debug!("authorize intrinsic found"); - PermissionTest::Yes + Intrinsic::ApiCall | Intrinsic::SafeCall | Intrinsic::Authorize => { + let second = operands.get(1); + if let Some(operand) = second { + match operand { + Operand::Lit(lit) => {} + Operand::Var(var) => { + if let Base::Var(varid) = var.base { + match _interp.curr_body.get().unwrap().vars[varid].clone() { + VarKind::GlobalRef(_def_id) => { + let smthng = self.variables_from_defid.get(&_def_id); + if let Some(value) = smthng { + match value { + Value::Const(const_var) => match const_var { + Const::Literal(_lit) => match _lit { + Operand::Var(var) => { + if let Base::Var(var_id__) = var.base { + let varkind___ = _interp + .curr_body + .get() + .unwrap() + .vars[var_id__] + .clone(); + match varkind___ { + VarKind::LocalDef(def__) => { + let value = &self + .variables_from_defid + .get(&def__.clone()); + let thing = _interp + .env() + .defs + .defs + .get(def__); + if let Some(id) = thing { + if let DefKind::GlobalObj(obj_id) = id { + let class = _interp.env().defs.classes.get(obj_id.clone()); + } + } + } + VarKind::GlobalRef(def__) => { + let value = &self + .variables_from_defid + .get(&def__.clone()); + let thing = _interp + .env() + .defs + .defs + .get(def__); + if let Some(id) = thing { + if let DefKind::GlobalObj(obj_id) = id { + let class = _interp.env().defs.classes.get(obj_id.clone()); + } + } + } + _ => {} + } + } + } + _ => {} + }, + _ => {} + }, + _ => {} + } + } + } + _ => {} + } + } + } + } + } } + _ => {} + } + match *intrinsic { + Intrinsic::Authorize => initial_state, Intrinsic::Fetch => initial_state, Intrinsic::ApiCall => initial_state, Intrinsic::SafeCall => initial_state, @@ -1931,13 +2045,6 @@ impl<'cx> Dataflow<'cx> for PermisisionDataflow { _ => {} } } - - // println!("self.variables {:#?}", self.variables); - - for (stmt, inst) in block.iter().enumerate() { - let loc = Location::new(bb, stmt as u32); - state = self.transfer_inst(interp, def, loc, block, inst, state); - } state } @@ -1950,8 +2057,8 @@ impl<'cx> Dataflow<'cx> for PermisisionDataflow { worklist: &mut WorkList, ) { self.super_join_term(interp, def, block, state, worklist); - for def in self.needs_call.drain(..) { - worklist.push_front_blocks(interp.env(), def); + for (def, arguments) in self.needs_call.drain(..) { + worklist.push_front_blocks(interp.env(), def, arguments); } } } @@ -2012,20 +2119,9 @@ impl<'cx> Checker<'cx> for PermissionChecker { interp: &Interp<'cx, Self>, intrinsic: &'cx Intrinsic, state: &Self::State, + operands: Option>, ) -> ControlFlow<(), Self::State> { - match *intrinsic { - Intrinsic::Authorize => ControlFlow::Continue(*state), - Intrinsic::Fetch | Intrinsic::EnvRead | Intrinsic::StorageRead => { - debug!("authenticated"); - ControlFlow::Continue(PermissionTest::Yes) - } - Intrinsic::ApiCall if *state == PermissionTest::Yes => { - let vuln = PermissionVuln::new(); - ControlFlow::Break(()) - } - Intrinsic::ApiCall => ControlFlow::Continue(*state), - Intrinsic::SafeCall => ControlFlow::Continue(*state), - } + ControlFlow::Continue(*state) } } diff --git a/crates/forge_analyzer/src/ctx.rs b/crates/forge_analyzer/src/ctx.rs index 1c7c3ab8..aba5080f 100644 --- a/crates/forge_analyzer/src/ctx.rs +++ b/crates/forge_analyzer/src/ctx.rs @@ -191,8 +191,8 @@ impl AppCtx { }) }); debug!(?input, "transfer in"); - let next = funcs.blocks[block].stmts.iter_enumerated().fold( - (None, false), + let next = funcs.blocks[block].stmts.iter_enumerated().fold( + (None, false), |(call, curr), (_stmt_id, val)| match val { IrStmt::Call(ref call_id) => { if let Some(meta) = self.func(call_id) { @@ -235,7 +235,6 @@ impl AppCtx { #[inline] pub(crate) fn func_mut(&mut self, func: &ModItem) -> Option<&mut FunctionMeta> { - self.modctx .get_mut(func.mod_id)? .functions diff --git a/crates/forge_analyzer/src/interp.rs b/crates/forge_analyzer/src/interp.rs index d73cc9cc..79934341 100644 --- a/crates/forge_analyzer/src/interp.rs +++ b/crates/forge_analyzer/src/interp.rs @@ -208,7 +208,7 @@ pub trait Dataflow<'cx>: Sized { debug!("{name} {def:?} is called from {calls:?}"); for &(def, loc) in calls { if worklist.visited(&def) { - worklist.push_back_force(def, loc.block); + worklist.push_back_force(def, loc.block, vec![]); } } } @@ -216,15 +216,15 @@ pub trait Dataflow<'cx>: Sized { Successors::One(succ) => { let mut succ_state = interp.block_state_mut(def, succ); if succ_state.join_changed(&state) { - worklist.push_back(def, succ); + worklist.push_back(def, succ, vec![]); } } Successors::Two(succ1, succ2) => { if interp.block_state_mut(def, succ1).join_changed(&state) { - worklist.push_back(def, succ1); + worklist.push_back(def, succ1, vec![]); } if interp.block_state_mut(def, succ2).join_changed(&state) { - worklist.push_back(def, succ2); + worklist.push_back(def, succ2, vec![]); } } } @@ -785,9 +785,11 @@ impl<'cx, C: Runner<'cx>> Interp<'cx, C> { self.dataflow_visited.insert(def); let func = self.env().def_ref(def).expect_body(); self.curr_body.set(Some(func)); + let block = func.block(block_id); let mut before_state = self.block_state(def, block_id); let block = func.block(block_id); for &pred in func.predecessors(block_id) { + let block_ = func.block(pred); before_state = before_state.join(&self.block_state(def, pred)); } let state = diff --git a/crates/forge_analyzer/src/ir.rs b/crates/forge_analyzer/src/ir.rs index b3efe602..3cb44289 100644 --- a/crates/forge_analyzer/src/ir.rs +++ b/crates/forge_analyzer/src/ir.rs @@ -304,7 +304,6 @@ impl Body { class_instantiations: Default::default(), ident_to_local: Default::default(), def_id_to_vars: Default::default(), - var_id_to_value: Default::default(), predecessors: Default::default(), } } @@ -431,6 +430,8 @@ impl Body { env: &'cx Environment, callee: &Operand, ) -> Option<(DefId, &'cx Body)> { + /* callee is the name of the function that is being called */ + match callee { Operand::Var(Variable { base: Base::Var(var), diff --git a/crates/forge_analyzer/src/worklist.rs b/crates/forge_analyzer/src/worklist.rs index 3e946126..3f26cbf0 100644 --- a/crates/forge_analyzer/src/worklist.rs +++ b/crates/forge_analyzer/src/worklist.rs @@ -10,7 +10,7 @@ use crate::{ #[derive(Debug, Clone)] pub struct WorkList { - worklist: VecDeque<(V, W)>, + worklist: VecDeque<(V, W, Vec)>, visited: FxHashSet, } @@ -27,7 +27,7 @@ where } #[inline] - pub fn pop_front(&mut self) -> Option<(V, W)> { + pub fn pop_front(&mut self) -> (Option<(V, W, Vec)>) { self.worklist.pop_front() } @@ -61,15 +61,15 @@ where V: Eq + Hash + Copy, { #[inline] - pub fn push_back(&mut self, v: V, w: W) { + pub fn push_back(&mut self, v: V, w: W, args: Vec) { if self.visited.insert(v) { - self.worklist.push_back((v, w)); + self.worklist.push_back((v, w, args)); } } #[inline] - pub fn push_back_force(&mut self, v: V, w: W) { - self.worklist.push_back((v, w)); + pub fn push_back_force(&mut self, v: V, w: W, args: Vec) { + self.worklist.push_back((v, w, args)); } } @@ -96,12 +96,12 @@ impl WorkList { } } -impl Extend<(V, W)> for WorkList +impl Extend<(V, W, Vec)> for WorkList where V: Eq + Hash, { #[inline] - fn extend>(&mut self, iter: T) { + fn extend)>>(&mut self, iter: T) { self.worklist.extend(iter); } } diff --git a/test-apps/basic/src/index.tsx b/test-apps/basic/src/index.tsx index 31abfe31..4381afa1 100644 --- a/test-apps/basic/src/index.tsx +++ b/test-apps/basic/src/index.tsx @@ -1,27 +1,26 @@ -import ForgeUI, { render, Fragment, Macro, Text } from '@forge/ui'; -import api, { route } from '@forge/api'; -import { testFn } from './test'; +import ForgeUI, { render, Fragment, Macro, Text } from "@forge/ui"; +import api, { route } from "@forge/api"; +import { testFn } from "./test"; const foo = () => { - const res = api.asApp().requestConfluence(route`/rest/api/3/test`); - test_function("hi") - return res; + const res = api.asApp().requestConfluence(route`/rest/api/3/test`); + test_function("hi"); + return res; }; let test_function = (word) => { - console.log(word); - let test_var = "test_var"; -} + console.log(word); + let test_var = "test_var"; +}; const App = () => { - foo(); - test_function("test_word"); - testFn(); - return ( - - Hello world! - - ); + foo(); + test_function("test_word"); + return ( + + Hello world! + + ); }; export const run = render(} />); From 15c3704965acc72badbcbdb9d5dbaf3b81da8a38 Mon Sep 17 00:00:00 2001 From: James Gersbach Date: Fri, 23 Jun 2023 09:19:57 -0700 Subject: [PATCH 415/517] definition analysis --- crates/forge_analyzer/src/checkers.rs | 111 +++++++++++++------------- 1 file changed, 55 insertions(+), 56 deletions(-) diff --git a/crates/forge_analyzer/src/checkers.rs b/crates/forge_analyzer/src/checkers.rs index 60c43b76..5c93e5f7 100644 --- a/crates/forge_analyzer/src/checkers.rs +++ b/crates/forge_analyzer/src/checkers.rs @@ -1890,6 +1890,8 @@ impl PermisisionDataflow { _ => {} } } + + fn add_something() {} } impl<'cx> Dataflow<'cx> for PermisisionDataflow { @@ -1919,66 +1921,18 @@ impl<'cx> Dataflow<'cx> for PermisisionDataflow { let second = operands.get(1); if let Some(operand) = second { match operand { - Operand::Lit(lit) => {} + Operand::Lit(lit) => { + println!("lit {:?}", lit); + } Operand::Var(var) => { if let Base::Var(varid) = var.base { match _interp.curr_body.get().unwrap().vars[varid].clone() { VarKind::GlobalRef(_def_id) => { - let smthng = self.variables_from_defid.get(&_def_id); - if let Some(value) = smthng { - match value { - Value::Const(const_var) => match const_var { - Const::Literal(_lit) => match _lit { - Operand::Var(var) => { - if let Base::Var(var_id__) = var.base { - let varkind___ = _interp - .curr_body - .get() - .unwrap() - .vars[var_id__] - .clone(); - match varkind___ { - VarKind::LocalDef(def__) => { - let value = &self - .variables_from_defid - .get(&def__.clone()); - let thing = _interp - .env() - .defs - .defs - .get(def__); - if let Some(id) = thing { - if let DefKind::GlobalObj(obj_id) = id { - let class = _interp.env().defs.classes.get(obj_id.clone()); - } - } - } - VarKind::GlobalRef(def__) => { - let value = &self - .variables_from_defid - .get(&def__.clone()); - let thing = _interp - .env() - .defs - .defs - .get(def__); - if let Some(id) = thing { - if let DefKind::GlobalObj(obj_id) = id { - let class = _interp.env().defs.classes.get(obj_id.clone()); - } - } - } - _ => {} - } - } - } - _ => {} - }, - _ => {} - }, - _ => {} - } - } + self.read_variable_from_variable(_interp, _def_id); + } + VarKind::LocalDef(_def_id) => { + self.read_variable_from_variable(_interp, _def_id); + println!("parent3 {:?}", _def_id); } _ => {} } @@ -1999,6 +1953,51 @@ impl<'cx> Dataflow<'cx> for PermisisionDataflow { } } + fn read_variable_from_variable>( + &mut self, + _interp: &Interp<'cx, C>, + defid: DefId, + ) { + if let Some(value) = self.variables_from_defid.get(&defid) { + if let Value::Const(const_var) = value { + match const_var { + Const::Literal(_lit) => match _lit { + Operand::Var(var) => { + if let Base::Var(var_id__) = var.base { + let varkind = + _interp.curr_body.get().unwrap().vars[var_id__].clone(); + match varkind { + VarKind::LocalDef(def__) => { + self.read_variable_from_class(_interp, def__); + } + VarKind::GlobalRef(def__) => { + self.read_variable_from_class(_interp, def__); + } + _ => {} + } + } + } + _ => {} + }, + _ => {} + } + } + } + } + + fn read_variable_from_class>( + &mut self, + _interp: &Interp<'cx, C>, + defid: DefId, + ) { + let def_kind = _interp.env().defs.defs.get(defid); + if let Some(id) = def_kind { + if let DefKind::GlobalObj(obj_id) = id { + let class = _interp.env().defs.classes.get(obj_id.clone()); + } + } + } + fn transfer_call>( &mut self, interp: &Interp<'cx, C>, From 07ab24b0d26c4aaa9dcfee8564decd35b67331e9 Mon Sep 17 00:00:00 2001 From: James Gersbach Date: Sun, 25 Jun 2023 12:30:35 -0700 Subject: [PATCH 416/517] wip --- .vscode/launch.json | 219 ++++++++++++-------------- crates/forge_analyzer/src/checkers.rs | 178 ++++++++++++--------- crates/forge_analyzer/src/interp.rs | 17 ++ 3 files changed, 217 insertions(+), 197 deletions(-) diff --git a/.vscode/launch.json b/.vscode/launch.json index 871af283..1b93707a 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -1,120 +1,101 @@ { - // Use IntelliSense to learn about possible attributes. - // Hover to view descriptions of existing attributes. - // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 - "version": "0.2.0", - "configurations": [ - { - "type": "lldb", - "request": "launch", - "name": "Debug unit tests in library 'forge_analyzer'", - "cargo": { - "args": [ - "test", - "--no-run", - "--lib", - "--package=forge_analyzer" - ], - "filter": { - "name": "forge_analyzer", - "kind": "lib" - } - }, - "args": [], - "cwd": "${workspaceFolder}" - }, - { - "type": "lldb", - "request": "launch", - "name": "Debug unit tests in library 'forge_file_resolver'", - "cargo": { - "args": [ - "test", - "--no-run", - "--lib", - "--package=forge_file_resolver" - ], - "filter": { - "name": "forge_file_resolver", - "kind": "lib" - } - }, - "args": [], - "cwd": "${workspaceFolder}" - }, - { - "type": "lldb", - "request": "launch", - "name": "Debug unit tests in library 'forge_utils'", - "cargo": { - "args": [ - "test", - "--no-run", - "--lib", - "--package=forge_utils" - ], - "filter": { - "name": "forge_utils", - "kind": "lib" - } - }, - "args": [], - "cwd": "${workspaceFolder}" - }, - { - "type": "lldb", - "request": "launch", - "name": "Debug unit tests in library 'forge_loader'", - "cargo": { - "args": [ - "test", - "--no-run", - "--lib", - "--package=forge_loader" - ], - "filter": { - "name": "forge_loader", - "kind": "lib" - } - }, - "args": [], - "cwd": "${workspaceFolder}" - }, - { - "type": "lldb", - "request": "launch", - "name": "Debug executable 'fsrt'", - "cargo": { - "args": [ - "build", - "--bin=fsrt", - "--package=fsrt" - ], - "filter": { - "name": "fsrt", - "kind": "bin" - } - }, - "args": ["./test-apps/jira-damn-vulnerable-forge-app"], - "cwd": "${workspaceFolder}" - }, - { - "type": "lldb", - "request": "launch", - "name": "Debug unit tests in executable 'fsrt'", - "cargo": { - "args": [ - "test", - "--no-run", - "--bin=fsrt", - "--package=fsrt" - ], - "filter": { - "name": "fsrt", - "kind": "bin" - } - }, - "cwd": "${workspaceFolder}" - } - ] -} \ No newline at end of file + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "type": "lldb", + "request": "launch", + "name": "Debug unit tests in library 'forge_analyzer'", + "cargo": { + "args": [ + "test", + "--no-run", + "--lib", + "--package=forge_analyzer" + ], + "filter": { + "name": "forge_analyzer", + "kind": "lib" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug unit tests in library 'forge_file_resolver'", + "cargo": { + "args": [ + "test", + "--no-run", + "--lib", + "--package=forge_file_resolver" + ], + "filter": { + "name": "forge_file_resolver", + "kind": "lib" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug unit tests in library 'forge_utils'", + "cargo": { + "args": ["test", "--no-run", "--lib", "--package=forge_utils"], + "filter": { + "name": "forge_utils", + "kind": "lib" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug unit tests in library 'forge_loader'", + "cargo": { + "args": ["test", "--no-run", "--lib", "--package=forge_loader"], + "filter": { + "name": "forge_loader", + "kind": "lib" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug executable 'fsrt'", + "cargo": { + "args": ["build", "--bin=fsrt", "--package=fsrt"], + "filter": { + "name": "fsrt", + "kind": "bin" + } + }, + "args": ["./test-apps/jira-damn-vulnerable-forge-app"], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug unit tests in executable 'fsrt'", + "cargo": { + "args": ["test", "--no-run", "--bin=fsrt", "--package=fsrt"], + "filter": { + "name": "fsrt", + "kind": "bin" + } + }, + "cwd": "${workspaceFolder}" + } + ] +} diff --git a/crates/forge_analyzer/src/checkers.rs b/crates/forge_analyzer/src/checkers.rs index 5c93e5f7..20147f11 100644 --- a/crates/forge_analyzer/src/checkers.rs +++ b/crates/forge_analyzer/src/checkers.rs @@ -1854,46 +1854,6 @@ impl WithCallStack for PermissionVuln { fn add_call_stack(&mut self, _stack: Vec) {} } -impl PermisisionDataflow { - fn add_variables(&mut self, rvalue: &Rvalue, defid: &DefId) { - match rvalue { - Rvalue::Read(operand) => { - if self.variables_from_defid.contains_key(&defid) - && self.variables_from_defid.get(&defid).unwrap() - != &Value::Const(Const::Literal(operand.clone())) - { - // currently assuming prev value is not phi - let prev_vars = &self.variables_from_defid[&defid]; - match prev_vars { - Value::Const(prev_var_const) => { - match operand { - Operand::Lit(_) => {} - Operand::Var(var) => match var.base { - Base::Var(var_id) => {} - _ => {} - }, - } - let var_vec = - vec![prev_var_const.clone(), Const::Literal(operand.clone())]; - - self.variables_from_defid - .insert(*defid, Value::Phi(Vec::from(var_vec))); - } - _ => {} - } - } else { - let value = Value::Const(Const::Literal(operand.clone())); - self.variables_from_defid.insert(*defid, value.clone()); - } - } - Rvalue::Template(template) => {} - _ => {} - } - } - - fn add_something() {} -} - impl<'cx> Dataflow<'cx> for PermisisionDataflow { type State = PermissionTest; @@ -1916,9 +1876,30 @@ impl<'cx> Dataflow<'cx> for PermisisionDataflow { initial_state: Self::State, operands: SmallVec<[crate::ir::Operand; 4]>, ) -> Self::State { + println!("operands {operands:?}"); match *intrinsic { Intrinsic::ApiCall | Intrinsic::SafeCall | Intrinsic::Authorize => { - let second = operands.get(1); + let (first, second) = (operands.get(0), operands.get(1)); + if let Some(operand) = first { + match operand { + Operand::Lit(lit) => { + println!("lit from first operand {:?}", lit); + } + Operand::Var(var) => match var.base { + Base::Var(varid) => { + let varkind = &_interp.curr_body.get().unwrap().vars[varid]; + let defid = get_varid_from_defid(&varkind); + if let Some(defid) = defid { + println!( + "actual value {:?}", + self.variables_from_defid.get(&defid) + ); + } + } + _ => {} + }, + } + } if let Some(operand) = second { match operand { Operand::Lit(lit) => { @@ -1928,11 +1909,32 @@ impl<'cx> Dataflow<'cx> for PermisisionDataflow { if let Base::Var(varid) = var.base { match _interp.curr_body.get().unwrap().vars[varid].clone() { VarKind::GlobalRef(_def_id) => { - self.read_variable_from_variable(_interp, _def_id); + /* case where it is passed in as a variable */ + match &self.variables_from_defid.get(&_def_id).unwrap() { + Value::Const(const_var) => { + if let Const::Object(obj) = const_var { + let defid = find_member_of_obj("method", obj); + if let Some(defid) = defid { + let value = + self.variables_from_defid.get(&defid); + println!("value of method {value:?}"); + } + } + } + Value::Phi(phi_var) => {} + _ => {} + } } VarKind::LocalDef(_def_id) => { - self.read_variable_from_variable(_interp, _def_id); - println!("parent3 {:?}", _def_id); + let class = self.read_class_from_object(_interp, _def_id); + if let Some(obj) = class { + let defid = find_member_of_obj("method", &obj); + if let Some(defid) = defid { + let value = self.variables_from_defid.get(&defid); + println!("value of method {value:?}"); + } + } + // } _ => {} } @@ -1953,49 +1955,21 @@ impl<'cx> Dataflow<'cx> for PermisisionDataflow { } } - fn read_variable_from_variable>( + fn read_class_from_object>( &mut self, _interp: &Interp<'cx, C>, defid: DefId, - ) { - if let Some(value) = self.variables_from_defid.get(&defid) { - if let Value::Const(const_var) = value { - match const_var { - Const::Literal(_lit) => match _lit { - Operand::Var(var) => { - if let Base::Var(var_id__) = var.base { - let varkind = - _interp.curr_body.get().unwrap().vars[var_id__].clone(); - match varkind { - VarKind::LocalDef(def__) => { - self.read_variable_from_class(_interp, def__); - } - VarKind::GlobalRef(def__) => { - self.read_variable_from_class(_interp, def__); - } - _ => {} - } - } - } - _ => {} - }, - _ => {} - } - } - } - } - - fn read_variable_from_class>( - &mut self, - _interp: &Interp<'cx, C>, - defid: DefId, - ) { + ) -> Option { let def_kind = _interp.env().defs.defs.get(defid); if let Some(id) = def_kind { if let DefKind::GlobalObj(obj_id) = id { let class = _interp.env().defs.classes.get(obj_id.clone()); + if let Some(class) = class { + return Some(class.clone()); + } } } + None } fn transfer_call>( @@ -2062,6 +2036,54 @@ impl<'cx> Dataflow<'cx> for PermisisionDataflow { } } +fn resolve_var_from_operand(operand: &Operand) -> Option { + if let Operand::Var(var) = operand { + if let Base::Var(varid) = var.base { + return Some(varid); + } + } + None +} + +fn resolve_literal_from_operand(operand: &Operand) -> Option { + if let Operand::Lit(lit) = operand { + return Some(lit.clone()); + } + None +} + +fn get_varid_from_defid(varkind: &VarKind) -> Option { + match varkind { + VarKind::GlobalRef(defid) => Some(defid.clone()), + VarKind::LocalDef(defid) => Some(defid.clone()), + VarKind::Arg(defid) => Some(defid.clone()), + VarKind::Temp { parent } => parent.clone(), + _ => None, + } +} + +fn convert_operand_to_raw(operand: &Operand) -> Option { + if let Operand::Lit(lit) = operand { + match lit { + Literal::BigInt(bigint) => Some(bigint.to_string()), + Literal::Number(num) => Some(num.to_string()), + Literal::Str(str) => Some(str.to_string()), + _ => None, + } + } else { + None + } +} + +fn find_member_of_obj(member: &str, obj: &Class) -> Option { + for (mem, memdefid) in &obj.pub_members { + if mem == member { + return Some(memdefid.clone()); + } + } + None +} + pub struct PermissionChecker { vulns: Vec, } diff --git a/crates/forge_analyzer/src/interp.rs b/crates/forge_analyzer/src/interp.rs index 79934341..f0ecc1c9 100644 --- a/crates/forge_analyzer/src/interp.rs +++ b/crates/forge_analyzer/src/interp.rs @@ -156,6 +156,23 @@ pub trait Dataflow<'cx>: Sized { state } + fn add_variable>( + &mut self, + interp: &Interp<'cx, C>, + defid: &DefId, + rvalue: &Rvalue, + ) { + } + + fn insert_value>( + &mut self, + operand: &Operand, + defid: &DefId, + interp: &Interp<'cx, C>, + prev_values: Option>, + ) { + } + fn add_variable>( &mut self, interp: &mut Interp<'cx, C>, From 3c0b16827ff7f6b40ed812d829df3ea91ada7033 Mon Sep 17 00:00:00 2001 From: James Gersbach Date: Sun, 25 Jun 2023 14:44:15 -0700 Subject: [PATCH 417/517] updates to parsing worklist arguments --- crates/forge_analyzer/src/checkers.rs | 22 +- crates/forge_analyzer/src/interp.rs | 4 +- .../forge_analyzer/src/permission_checker.rs | 0 .../src/permission_classifier.rs | 281 ++++++++++++++++++ crates/forge_analyzer/src/worklist.rs | 22 +- 5 files changed, 307 insertions(+), 22 deletions(-) delete mode 100644 crates/forge_analyzer/src/permission_checker.rs create mode 100644 crates/forge_analyzer/src/permission_classifier.rs diff --git a/crates/forge_analyzer/src/checkers.rs b/crates/forge_analyzer/src/checkers.rs index 20147f11..47fd453b 100644 --- a/crates/forge_analyzer/src/checkers.rs +++ b/crates/forge_analyzer/src/checkers.rs @@ -272,7 +272,7 @@ impl<'cx> Dataflow<'cx> for AuthorizeDataflow { def: DefId, block: &'cx BasicBlock, state: Self::State, - worklist: &mut WorkList, + worklist: &mut WorkList, ) { self.super_join_term(interp, def, block, state, worklist); for def in self.needs_call.drain(..) { @@ -1719,7 +1719,7 @@ impl<'cx> Dataflow<'cx> for AuthenticateDataflow { def: DefId, block: &'cx BasicBlock, state: Self::State, - worklist: &mut WorkList, + worklist: &mut WorkList, ) { self.super_join_term(interp, def, block, state, worklist); for def in self.needs_call.drain(..) { @@ -2027,7 +2027,7 @@ impl<'cx> Dataflow<'cx> for PermisisionDataflow { def: DefId, block: &'cx BasicBlock, state: Self::State, - worklist: &mut WorkList, + worklist: &mut WorkList, ) { self.super_join_term(interp, def, block, state, worklist); for (def, arguments) in self.needs_call.drain(..) { @@ -2064,17 +2064,21 @@ fn get_varid_from_defid(varkind: &VarKind) -> Option { fn convert_operand_to_raw(operand: &Operand) -> Option { if let Operand::Lit(lit) = operand { - match lit { - Literal::BigInt(bigint) => Some(bigint.to_string()), - Literal::Number(num) => Some(num.to_string()), - Literal::Str(str) => Some(str.to_string()), - _ => None, - } + convert_lit_to_raw(lit) } else { None } } +fn convert_lit_to_raw(lit: &Literal) -> Option { + match lit { + Literal::BigInt(bigint) => Some(bigint.to_string()), + Literal::Number(num) => Some(num.to_string()), + Literal::Str(str) => Some(str.to_string()), + _ => None, + } +} + fn find_member_of_obj(member: &str, obj: &Class) -> Option { for (mem, memdefid) in &obj.pub_members { if mem == member { diff --git a/crates/forge_analyzer/src/interp.rs b/crates/forge_analyzer/src/interp.rs index f0ecc1c9..6e208200 100644 --- a/crates/forge_analyzer/src/interp.rs +++ b/crates/forge_analyzer/src/interp.rs @@ -200,7 +200,7 @@ pub trait Dataflow<'cx>: Sized { def: DefId, block: &'cx BasicBlock, state: Self::State, - worklist: &mut WorkList, + worklist: &mut WorkList, ) { self.super_join_term(interp.borrow_mut(), def, block, state, worklist); } @@ -211,7 +211,7 @@ pub trait Dataflow<'cx>: Sized { def: DefId, block: &'cx BasicBlock, state: Self::State, - worklist: &mut WorkList, + worklist: &mut WorkList, ) { match block.successors() { Successors::Return => { diff --git a/crates/forge_analyzer/src/permission_checker.rs b/crates/forge_analyzer/src/permission_checker.rs deleted file mode 100644 index e69de29b..00000000 diff --git a/crates/forge_analyzer/src/permission_classifier.rs b/crates/forge_analyzer/src/permission_classifier.rs new file mode 100644 index 00000000..ddbb6e57 --- /dev/null +++ b/crates/forge_analyzer/src/permission_classifier.rs @@ -0,0 +1,281 @@ +pub(crate) fn check_permission_used( + function_name: &str, + first_arg: &String, + second_arg: Option<&Expr>, +) -> Vec { + let mut used_permissions: Vec = Vec::new(); + + let joined_args = first_arg; + + let post_call = joined_args.contains("POST"); + let delete_call = joined_args.contains("DELTE"); + let put_call = joined_args.contains("PUT"); + + let contains_audit = joined_args.contains("audit"); + let contains_issue = joined_args.contains("issue"); + let contains_content = joined_args.contains("content"); + let contains_user = joined_args.contains("user"); + let contains_theme = joined_args.contains("theme"); + let contains_template = joined_args.contains("template"); + let contains_space = joined_args.contains("space"); + let contains_analytics = joined_args.contains("analytics"); + let contains_cql = joined_args.contains("cql"); + let contains_attachment = joined_args.contains("attachment"); + let contains_contentbody = joined_args.contains("contentbody"); + let contians_permissions = joined_args.contains("permissions"); + let contains_property = joined_args.contains("property"); + let contains_page_tree = joined_args.contains("pageTree"); + let contains_group = joined_args.contains("group"); + let contains_inlinetasks = joined_args.contains("inlinetasks"); + let contains_relation = joined_args.contains("relation"); + let contains_settings = joined_args.contains("settings"); + let contains_permission = joined_args.contains("permission"); + let contains_download = joined_args.contains("download"); + let contains_descendants = joined_args.contains("descendants"); + let contains_comment = joined_args.contains("comment"); + let contains_label = joined_args.contains("contains_label"); + let contains_search = joined_args.contains("contains_search"); + let contains_longtask = joined_args.contains("contains_longtask"); + let contains_notification = joined_args.contains("notification"); + let contains_watch = joined_args.contains("watch"); + let contains_version = joined_args.contains("version"); + let contains_state = joined_args.contains("contains_state"); + let contains_available = joined_args.contains("available"); + let contains_announcement_banner = joined_args.contains("announcementBanner"); + let contains_avatar = joined_args.contains("avatar"); + let contains_size = joined_args.contains("size"); + let contains_dashboard = joined_args.contains("dashboard"); + let contains_gadget = joined_args.contains("gadget"); + let contains_filter = joined_args.contains("filter"); + let contains_tracking = joined_args.contains("tracking"); + let contains_groupuserpicker = joined_args.contains("groupuserpicker"); + let contains_workflow = joined_args.contains("workflow"); + let contains_status = joined_args.contains("status"); + let contains_task = joined_args.contains("task"); + let contains_screen = joined_args.contains("screen"); + let non_get_call = post_call || delete_call || put_call; + let contains_webhook = joined_args.contains("webhook"); + let contains_project = joined_args.contains("project"); + let contains_actor = joined_args.contains("actor"); + let contains_role = joined_args.contains("contains_role"); + let contains_project_validate = joined_args.contains("projectvalidate"); + let contains_email = joined_args.contains("email"); + let contains_notification_scheme = joined_args.contains("notificationscheme"); + let contains_priority = joined_args.contains("priority"); + let contains_properties = joined_args.contains("properties"); + let contains_remote_link = joined_args.contains("remotelink"); + let contains_resolution = joined_args.contains("resolution"); + let contains_security_level = joined_args.contains("securitylevel"); + let contains_issue_security_schemes = joined_args.contains("issuesecurityschemes"); + let contains_issue_type = joined_args.contains("issuetype"); + let contains_issue_type_schemes = joined_args.contains("issuetypescheme"); + let contains_votes = joined_args.contains("contains_votes"); + let contains_worklog = joined_args.contains("worklog"); + let contains_expression = joined_args.contains("expression"); + let contains_configuration = joined_args.contains("configuration"); + let contains_application_properties = joined_args.contains("application-properties"); + + match function_name { + "requestJira" => { + if (contains_dashboard && non_get_call) + || (contains_user && non_get_call) + || contains_task + { + used_permissions.push(ForgePermissions::WriteJiraWork); + if contains_gadget { + used_permissions.push(ForgePermissions::ReadJiraWork) + } + } else if contains_expression { + used_permissions.push(ForgePermissions::ReadJiraUser); + used_permissions.push(ForgePermissions::ReadJiraUser) + } else if (contains_avatar && contains_size) + || contains_dashboard + || contains_status + || contains_groupuserpicker + { + used_permissions.push(ForgePermissions::ReadJiraWork) + } else if (!non_get_call && contains_user) || contains_configuration { + used_permissions.push(ForgePermissions::ReadJiraUser) + } else if contains_webhook { + used_permissions.push(ForgePermissions::ManageJiraWebhook); + used_permissions.push(ForgePermissions::ReadJiraWork) + } else if (contains_remote_link && non_get_call) + || (contains_issue && contains_votes && non_get_call) + || (contains_worklog && non_get_call) + { + used_permissions.push(ForgePermissions::WriteJiraWork) + } else if (contains_issue_type && non_get_call) + || (contains_issue_type && non_get_call) + || (contains_project && non_get_call) + || (contains_project && contains_actor) + || (contains_project && contains_role) + || (contains_project && contains_email) + || (contains_priority && (non_get_call || contains_search)) + || (contains_properties && contains_issue && non_get_call) + || (contains_resolution && non_get_call) + || contains_audit + || contains_avatar + || contains_workflow + || contains_tracking + || contains_status + || contains_screen + || contains_notification_scheme + || contains_security_level + || contains_issue_security_schemes + || contains_issue_type_schemes + || contains_announcement_banner + || contains_application_properties + { + used_permissions.push(ForgePermissions::ManageJiraConfiguration) + } else if contains_filter { + if non_get_call { + used_permissions.push(ForgePermissions::WriteJiraWork) + } else { + used_permissions.push(ForgePermissions::ReadJiraWork) + } + } else if contains_project + || contains_project_validate + || contains_priority + || contains_search + || contains_issue_type + || (contains_issue && contains_votes) + || (contains_properties && contains_issue) + || (contains_remote_link && !non_get_call) + || (contains_resolution && !non_get_call) + || contains_worklog + { + used_permissions.push(ForgePermissions::ReadJiraWork) + } else if post_call { + if contains_issue { + used_permissions.push(ForgePermissions::WriteJiraWork); + } else { + used_permissions.push(ForgePermissions::Unknown); + } + } else { + if contains_issue { + used_permissions.push(ForgePermissions::ReadJiraWork); + } else { + used_permissions.push(ForgePermissions::Unknown); + } + } + } + + // bit flags + "requestConfluence" => { + if non_get_call { + if contains_content { + used_permissions.push(ForgePermissions::WriteConfluenceContent); + } else if contains_audit { + used_permissions.push(ForgePermissions::WriteAuditLogsConfluence); + if post_call { + used_permissions.push(ForgePermissions::ReadAuditLogsConfluence); + } + } else if contains_content && contains_attachment { + if put_call { + // review this more specifically + // /wiki/rest/api/content/{id}/child/attachment/{attachmentId}`, + used_permissions.push(ForgePermissions::WriteConfluenceFile); + used_permissions.push(ForgePermissions::WriteConfluenceProps) + } else { + used_permissions.push(ForgePermissions::WriteConfluenceFile) + } + } else if contains_contentbody { + used_permissions.push(ForgePermissions::ReadConfluenceContentAll) + } else if contains_content && contians_permissions { + used_permissions.push(ForgePermissions::ReadConfluenceContentPermission) + } else if contains_property { + used_permissions.push(ForgePermissions::WriteConfluenceProps) + } else if contains_content + || contains_page_tree + || contains_relation + || contains_template + { + used_permissions.push(ForgePermissions::WriteConfluenceContent) + } else if contains_group { + used_permissions.push(ForgePermissions::WriteConfluenceGroups) + } else if contains_settings { + used_permissions.push(ForgePermissions::ManageConfluenceConfiguration) + } else if contains_space && contains_permission { + if !delete_call { + used_permissions.push(ForgePermissions::ReadSpacePermissionConfluence); + } + used_permissions.push(ForgePermissions::WriteSpacePermissionsConfluence) + } else if contains_space || contains_theme { + used_permissions.push(ForgePermissions::WriteConfluenceSpace); + } else if contains_inlinetasks { + used_permissions.push(ForgePermissions::WriteInlineTaskConfluence) + } else if contains_user && contains_property { + used_permissions.push(ForgePermissions::WriteUserPropertyConfluence); + } else { + used_permissions.push(ForgePermissions::Unknown); + } + } else { + if contains_issue { + used_permissions.push(ForgePermissions::ReadJiraWork); + } else if contains_audit { + used_permissions.push(ForgePermissions::ReadAuditLogsConfluence) + } else if contains_cql { + if contains_user { + used_permissions.push(ForgePermissions::ReadContentDetailsConfluence); + } else { + used_permissions.push(ForgePermissions::SearchConfluence); + } + } else if contains_attachment && contains_download { + used_permissions.push(ForgePermissions::ReadOnlyContentAttachmentConfluence) + } else if contains_longtask { + used_permissions.push(ForgePermissions::ReadContentMetadataConfluence); + used_permissions.push(ForgePermissions::ReadConfluenceSpaceSummary) + } else if contains_content && contains_property { + used_permissions.push(ForgePermissions::ReadConfluenceProps); + } else if contains_template + || contains_relation + || (contains_content + && (contains_comment || contains_descendants || contains_label)) + { + used_permissions.push(ForgePermissions::ReadConfluenceContentSummary) + } else if contains_space && contains_settings { + used_permissions.push(ForgePermissions::ReadConfluenceSpaceSummary) + } else if contains_space && contains_theme { + used_permissions.push(ForgePermissions::ManageConfluenceConfiguration) + } else if contains_space && contains_content && contains_state { + used_permissions.push(ForgePermissions::ReadConfluenceContentAll) + } else if contains_space && contains_content { + used_permissions.push(ForgePermissions::ReadConfluenceContentSummary) + } else if contains_state && contains_content && contains_available { + used_permissions.push(ForgePermissions::WriteConfluenceContent) + } else if contains_content + && (contains_notification + || contains_watch + || contains_version + || contains_state) + { + used_permissions.push(ForgePermissions::ReadConfluenceContentSummary) + } else if contains_space { + used_permissions.push(ForgePermissions::ReadConfluenceProps) + } else if contains_content || contains_analytics { + used_permissions.push(ForgePermissions::ReadConfluenceContentAll) + } else if contains_user && contains_property { + used_permissions.push(ForgePermissions::WriteUserPropertyConfluence) + } else if contains_settings { + used_permissions.push(ForgePermissions::ManageConfluenceConfiguration) + } else if contains_search { + used_permissions.push(ForgePermissions::ReadContentDetailsConfluence) + } else if contains_space { + used_permissions.push(ForgePermissions::ReadConfluenceSpaceSummary) + } else if contains_user { + used_permissions.push(ForgePermissions::ReadConfluenceUser) + } else if contains_label { + used_permissions.push(ForgePermissions::ReadConfluenceContentSummary) + } else if contains_inlinetasks { + used_permissions.push(ForgePermissions::ReadConfluenceContentAll); + } else { + used_permissions.push(ForgePermissions::Unknown); + } + } + } + _ => { + used_permissions.push(ForgePermissions::Unknown); + } + } + used_permissions +} \ No newline at end of file diff --git a/crates/forge_analyzer/src/worklist.rs b/crates/forge_analyzer/src/worklist.rs index 3f26cbf0..a5ca8e01 100644 --- a/crates/forge_analyzer/src/worklist.rs +++ b/crates/forge_analyzer/src/worklist.rs @@ -5,16 +5,16 @@ use tracing::debug; use crate::{ definitions::{DefId, Environment}, - ir::BasicBlockId, + ir::{BasicBlockId, Operand}, }; #[derive(Debug, Clone)] -pub struct WorkList { - worklist: VecDeque<(V, W, Vec)>, +pub struct WorkList { + worklist: VecDeque<(V, W, Vec
)>, visited: FxHashSet, } -impl WorkList +impl WorkList where V: Eq + Hash, { @@ -27,7 +27,7 @@ where } #[inline] - pub fn pop_front(&mut self) -> (Option<(V, W, Vec)>) { + pub fn pop_front(&mut self) -> (Option<(V, W, Vec)>) { self.worklist.pop_front() } @@ -56,24 +56,24 @@ where } } -impl WorkList +impl WorkList where V: Eq + Hash + Copy, { #[inline] - pub fn push_back(&mut self, v: V, w: W, args: Vec) { + pub fn push_back(&mut self, v: V, w: W, args: Vec) { if self.visited.insert(v) { self.worklist.push_back((v, w, args)); } } #[inline] - pub fn push_back_force(&mut self, v: V, w: W, args: Vec) { + pub fn push_back_force(&mut self, v: V, w: W, args: Vec) { self.worklist.push_back((v, w, args)); } } -impl WorkList { +impl WorkList { #[inline] pub(crate) fn push_front_blocks( &mut self, @@ -96,12 +96,12 @@ impl WorkList { } } -impl Extend<(V, W, Vec)> for WorkList +impl Extend<(V, W, Vec)> for WorkList where V: Eq + Hash, { #[inline] - fn extend)>>(&mut self, iter: T) { + fn extend)>>(&mut self, iter: T) { self.worklist.extend(iter); } } From 32ba8a39938b7c873a3b1d38c090813f953a4421 Mon Sep 17 00:00:00 2001 From: James Gersbach Date: Sun, 25 Jun 2023 22:36:48 -0700 Subject: [PATCH 418/517] correclty resolving permissions --- crates/forge_analyzer/src/checkers.rs | 158 ++++++++++++++---- crates/forge_analyzer/src/engine.rs | 1 + crates/forge_analyzer/src/lib.rs | 1 + ..._classifier.rs => permissionclassifier.rs} | 148 ++++++++-------- crates/forge_analyzer/src/reporter.rs | 4 + crates/fsrt/src/main.rs | 4 - 6 files changed, 205 insertions(+), 111 deletions(-) rename crates/forge_analyzer/src/{permission_classifier.rs => permissionclassifier.rs} (71%) diff --git a/crates/forge_analyzer/src/checkers.rs b/crates/forge_analyzer/src/checkers.rs index 47fd453b..6be1cb87 100644 --- a/crates/forge_analyzer/src/checkers.rs +++ b/crates/forge_analyzer/src/checkers.rs @@ -497,6 +497,7 @@ impl IntoVuln for AuthNVuln { app_key: reporter.app_key().to_owned(), app_name: reporter.app_name().to_owned(), date: reporter.current_date(), + unused_permissions: None, } } } @@ -1664,7 +1665,7 @@ impl<'cx> Dataflow<'cx> for AuthenticateDataflow { fn transfer_intrinsic>( &mut self, - _interp: &Interp<'cx, C>, + _interp: &mut Interp<'cx, C>, _def: DefId, _loc: Location, _block: &'cx BasicBlock, @@ -1673,13 +1674,13 @@ impl<'cx> Dataflow<'cx> for AuthenticateDataflow { operands: SmallVec<[crate::ir::Operand; 4]>, ) -> Self::State { match *intrinsic { - Intrinsic::Authorize => initial_state, + Intrinsic::Authorize(_) => initial_state, Intrinsic::Fetch | Intrinsic::EnvRead | Intrinsic::StorageRead => { debug!("authenticated"); Authenticated::Yes } - Intrinsic::ApiCall => initial_state, - Intrinsic::SafeCall => initial_state, + Intrinsic::ApiCall(_) => initial_state, + Intrinsic::SafeCall(_) => initial_state, } } @@ -1761,19 +1762,19 @@ impl<'cx> Checker<'cx> for AuthenticateChecker { operands: Option>, ) -> ControlFlow<(), Self::State> { match *intrinsic { - Intrinsic::Authorize => ControlFlow::Continue(*state), + Intrinsic::Authorize(_) => ControlFlow::Continue(*state), Intrinsic::Fetch | Intrinsic::EnvRead | Intrinsic::StorageRead => { debug!("authenticated"); ControlFlow::Continue(Authenticated::Yes) } - Intrinsic::ApiCall if *state == Authenticated::No => { + Intrinsic::ApiCall(_) if *state == Authenticated::No => { let vuln = AuthNVuln::new(interp.callstack(), interp.env(), interp.entry()); info!("Found a vuln!"); self.vulns.push(vuln); ControlFlow::Continue(*state) } - Intrinsic::ApiCall => ControlFlow::Continue(*state), - Intrinsic::SafeCall => ControlFlow::Continue(*state), + Intrinsic::ApiCall(_) => ControlFlow::Continue(*state), + Intrinsic::SafeCall(_) => ControlFlow::Continue(*state), } } } @@ -1837,6 +1838,7 @@ impl IntoVuln for AuthNVuln { app_key: reporter.app_key().to_owned(), app_name: reporter.app_name().to_owned(), date: reporter.current_date(), + unused_permissions: None, } } } @@ -1854,7 +1856,20 @@ impl WithCallStack for PermissionVuln { fn add_call_stack(&mut self, _stack: Vec) {} } -impl<'cx> Dataflow<'cx> for PermisisionDataflow { +#[derive(Debug, Default, Clone)] +struct IntrinsicArguments { + name: Option, + first_arg: Option>, + second_arg: Option>, +} + +#[derive(Debug, Clone, Copy)] +pub enum IntrinsicName { + RequestConfluence, + RequestJira, +} + +impl<'cx> Dataflow<'cx> for PermissionDataflow { type State = PermissionTest; fn with_interp>( @@ -1868,7 +1883,7 @@ impl<'cx> Dataflow<'cx> for PermisisionDataflow { fn transfer_intrinsic>( &mut self, - _interp: &Interp<'cx, C>, + _interp: &mut Interp<'cx, C>, _def: DefId, _loc: Location, _block: &'cx BasicBlock, @@ -1876,24 +1891,31 @@ impl<'cx> Dataflow<'cx> for PermisisionDataflow { initial_state: Self::State, operands: SmallVec<[crate::ir::Operand; 4]>, ) -> Self::State { - println!("operands {operands:?}"); - match *intrinsic { - Intrinsic::ApiCall | Intrinsic::SafeCall | Intrinsic::Authorize => { + let mut intrinsic_argument = IntrinsicArguments::default(); + + match &*intrinsic { + Intrinsic::ApiCall(value) + | Intrinsic::SafeCall(value) + | Intrinsic::Authorize(value) => { + intrinsic_argument.name = Some(value.clone()); let (first, second) = (operands.get(0), operands.get(1)); if let Some(operand) = first { match operand { Operand::Lit(lit) => { - println!("lit from first operand {:?}", lit); + intrinsic_argument.first_arg = Some(vec![lit.to_string()]); } Operand::Var(var) => match var.base { Base::Var(varid) => { let varkind = &_interp.curr_body.get().unwrap().vars[varid]; let defid = get_varid_from_defid(&varkind); if let Some(defid) = defid { - println!( - "actual value {:?}", - self.variables_from_defid.get(&defid) - ); + if let Some(value) = self.variables_from_defid.get(&defid) { + intrinsic_argument.first_arg = Some(vec![]); + add_elements_to_intrinsic_struct( + value, + &mut intrinsic_argument.first_arg, + ); + } } } _ => {} @@ -1902,9 +1924,7 @@ impl<'cx> Dataflow<'cx> for PermisisionDataflow { } if let Some(operand) = second { match operand { - Operand::Lit(lit) => { - println!("lit {:?}", lit); - } + Operand::Lit(_) => {} Operand::Var(var) => { if let Base::Var(varid) = var.base { match _interp.curr_body.get().unwrap().vars[varid].clone() { @@ -1915,9 +1935,17 @@ impl<'cx> Dataflow<'cx> for PermisisionDataflow { if let Const::Object(obj) = const_var { let defid = find_member_of_obj("method", obj); if let Some(defid) = defid { - let value = - self.variables_from_defid.get(&defid); - println!("value of method {value:?}"); + if let Some(value) = + self.variables_from_defid.get(&defid) + { + println!("value of method {value:?}"); + intrinsic_argument.second_arg = + Some(vec![]); + add_elements_to_intrinsic_struct( + value, + &mut intrinsic_argument.second_arg, + ); + } } } } @@ -1930,8 +1958,16 @@ impl<'cx> Dataflow<'cx> for PermisisionDataflow { if let Some(obj) = class { let defid = find_member_of_obj("method", &obj); if let Some(defid) = defid { - let value = self.variables_from_defid.get(&defid); - println!("value of method {value:?}"); + if let Some(value) = + self.variables_from_defid.get(&defid) + { + intrinsic_argument.second_arg = Some(vec![]); + add_elements_to_intrinsic_struct( + value, + &mut intrinsic_argument.second_arg, + ); + } + // println!("value of method {value:?}"); } } // @@ -1945,11 +1981,43 @@ impl<'cx> Dataflow<'cx> for PermisisionDataflow { } _ => {} } + + let mut all_permissions_used: Vec = vec![]; + let function_name = if intrinsic_argument.name.unwrap() == String::from("requestJira") { + IntrinsicName::RequestJira + } else { + IntrinsicName::RequestConfluence + }; + + intrinsic_argument + .first_arg + .iter() + .for_each(|first_arg_vec| { + intrinsic_argument + .second_arg + .iter() + .for_each(|second_arg_vec| { + first_arg_vec.iter().for_each(|first_arg| { + second_arg_vec.iter().for_each(|second_arg| { + let permissions = check_permission_used( + function_name, + first_arg, + Some(second_arg), + ); + println!("permissions {:?}", permissions); + all_permissions_used.extend_from_slice(&permissions); + }) + }) + }) + }); + + // check_permission_used() + match *intrinsic { - Intrinsic::Authorize => initial_state, + Intrinsic::Authorize(_) => initial_state, Intrinsic::Fetch => initial_state, - Intrinsic::ApiCall => initial_state, - Intrinsic::SafeCall => initial_state, + Intrinsic::ApiCall(_) => initial_state, + Intrinsic::SafeCall(_) => initial_state, Intrinsic::EnvRead => initial_state, Intrinsic::StorageRead => initial_state, } @@ -2088,13 +2156,35 @@ fn find_member_of_obj(member: &str, obj: &Class) -> Option { None } +fn add_elements_to_intrinsic_struct(value: &Value, args: &mut Option>) { + match value { + Value::Const(const_value) => { + if let Const::Literal(literal) = const_value { + args.as_mut().unwrap().push(literal.clone()); + } + } + Value::Phi(phi_value) => { + for value in phi_value { + if let Const::Literal(literal) = value { + args.as_mut().unwrap().push(literal.clone()); + } + } + } + _ => {} + } +} + pub struct PermissionChecker { vulns: Vec, + declared_permissions: HashSet, } impl PermissionChecker { - pub fn new() -> Self { - Self { vulns: vec![] } + pub fn new(declared_permissions: HashSet) -> Self { + Self { + vulns: vec![], + declared_permissions, + } } pub fn into_vulns(self) -> impl IntoIterator { @@ -2104,7 +2194,7 @@ impl PermissionChecker { impl Default for PermissionChecker { fn default() -> Self { - Self::new() + Self::new(HashSet::new()) } } @@ -2136,7 +2226,7 @@ impl JoinSemiLattice for PermissionTest { impl<'cx> Checker<'cx> for PermissionChecker { type State = PermissionTest; - type Dataflow = PermisisionDataflow; + type Dataflow = PermissionDataflow; type Vuln = PermissionVuln; fn visit_intrinsic( @@ -2146,6 +2236,7 @@ impl<'cx> Checker<'cx> for PermissionChecker { state: &Self::State, operands: Option>, ) -> ControlFlow<(), Self::State> { + println!("visitng intrsinsic"); ControlFlow::Continue(*state) } } @@ -2176,6 +2267,7 @@ impl IntoVuln for PermissionVuln { app_key: reporter.app_key().to_string(), app_name: reporter.app_name().to_string(), date: reporter.current_date(), + unused_permissions: None, } } } diff --git a/crates/forge_analyzer/src/engine.rs b/crates/forge_analyzer/src/engine.rs index 65655ef5..43204fba 100644 --- a/crates/forge_analyzer/src/engine.rs +++ b/crates/forge_analyzer/src/engine.rs @@ -133,6 +133,7 @@ impl<'ctx> Machine<'ctx> { let result = self.app.func_res(&orig_func); info!(?result, "analysis complete"); let fname: &str = &orig_func.ident.0; + println!("Result of analyzing {fname}:"); match result { AuthZVal::Unauthorized => { println!("FAIL: Unauthorized call detected from handler: {fname}") diff --git a/crates/forge_analyzer/src/lib.rs b/crates/forge_analyzer/src/lib.rs index 00bf765f..f9a56e97 100644 --- a/crates/forge_analyzer/src/lib.rs +++ b/crates/forge_analyzer/src/lib.rs @@ -7,6 +7,7 @@ pub mod exports; pub mod interp; pub mod ir; pub mod lattice; +pub mod permissionclassifier; pub mod pretty; pub mod reporter; pub mod resolver; diff --git a/crates/forge_analyzer/src/permission_classifier.rs b/crates/forge_analyzer/src/permissionclassifier.rs similarity index 71% rename from crates/forge_analyzer/src/permission_classifier.rs rename to crates/forge_analyzer/src/permissionclassifier.rs index ddbb6e57..e3934916 100644 --- a/crates/forge_analyzer/src/permission_classifier.rs +++ b/crates/forge_analyzer/src/permissionclassifier.rs @@ -1,82 +1,84 @@ +use crate::checkers::IntrinsicName; +use core::fmt; +use forge_loader::forgepermissions::ForgePermissions; + pub(crate) fn check_permission_used( - function_name: &str, + function_name: IntrinsicName, first_arg: &String, - second_arg: Option<&Expr>, + second_arg: Option<&String>, ) -> Vec { let mut used_permissions: Vec = Vec::new(); - let joined_args = first_arg; - - let post_call = joined_args.contains("POST"); - let delete_call = joined_args.contains("DELTE"); - let put_call = joined_args.contains("PUT"); + let post_call = second_arg.unwrap_or(&String::from("")).contains("POST"); + let delete_call = second_arg.unwrap_or(&String::from("")).contains("DELTE"); + let put_call = second_arg.unwrap_or(&String::from("")).contains("PUT"); - let contains_audit = joined_args.contains("audit"); - let contains_issue = joined_args.contains("issue"); - let contains_content = joined_args.contains("content"); - let contains_user = joined_args.contains("user"); - let contains_theme = joined_args.contains("theme"); - let contains_template = joined_args.contains("template"); - let contains_space = joined_args.contains("space"); - let contains_analytics = joined_args.contains("analytics"); - let contains_cql = joined_args.contains("cql"); - let contains_attachment = joined_args.contains("attachment"); - let contains_contentbody = joined_args.contains("contentbody"); - let contians_permissions = joined_args.contains("permissions"); - let contains_property = joined_args.contains("property"); - let contains_page_tree = joined_args.contains("pageTree"); - let contains_group = joined_args.contains("group"); - let contains_inlinetasks = joined_args.contains("inlinetasks"); - let contains_relation = joined_args.contains("relation"); - let contains_settings = joined_args.contains("settings"); - let contains_permission = joined_args.contains("permission"); - let contains_download = joined_args.contains("download"); - let contains_descendants = joined_args.contains("descendants"); - let contains_comment = joined_args.contains("comment"); - let contains_label = joined_args.contains("contains_label"); - let contains_search = joined_args.contains("contains_search"); - let contains_longtask = joined_args.contains("contains_longtask"); - let contains_notification = joined_args.contains("notification"); - let contains_watch = joined_args.contains("watch"); - let contains_version = joined_args.contains("version"); - let contains_state = joined_args.contains("contains_state"); - let contains_available = joined_args.contains("available"); - let contains_announcement_banner = joined_args.contains("announcementBanner"); - let contains_avatar = joined_args.contains("avatar"); - let contains_size = joined_args.contains("size"); - let contains_dashboard = joined_args.contains("dashboard"); - let contains_gadget = joined_args.contains("gadget"); - let contains_filter = joined_args.contains("filter"); - let contains_tracking = joined_args.contains("tracking"); - let contains_groupuserpicker = joined_args.contains("groupuserpicker"); - let contains_workflow = joined_args.contains("workflow"); - let contains_status = joined_args.contains("status"); - let contains_task = joined_args.contains("task"); - let contains_screen = joined_args.contains("screen"); + let contains_audit = first_arg.contains("audit"); + let contains_issue = first_arg.contains("issue"); + let contains_content = first_arg.contains("content"); + let contains_user = first_arg.contains("user"); + let contains_theme = first_arg.contains("theme"); + let contains_template = first_arg.contains("template"); + let contains_space = first_arg.contains("space"); + let contains_analytics = first_arg.contains("analytics"); + let contains_cql = first_arg.contains("cql"); + let contains_attachment = first_arg.contains("attachment"); + let contains_contentbody = first_arg.contains("contentbody"); + let contians_permissions = first_arg.contains("permissions"); + let contains_property = first_arg.contains("property"); + let contains_page_tree = first_arg.contains("pageTree"); + let contains_group = first_arg.contains("group"); + let contains_inlinetasks = first_arg.contains("inlinetasks"); + let contains_relation = first_arg.contains("relation"); + let contains_settings = first_arg.contains("settings"); + let contains_permission = first_arg.contains("permission"); + let contains_download = first_arg.contains("download"); + let contains_descendants = first_arg.contains("descendants"); + let contains_comment = first_arg.contains("comment"); + let contains_label = first_arg.contains("contains_label"); + let contains_search = first_arg.contains("contains_search"); + let contains_longtask = first_arg.contains("contains_longtask"); + let contains_notification = first_arg.contains("notification"); + let contains_watch = first_arg.contains("watch"); + let contains_version = first_arg.contains("version"); + let contains_state = first_arg.contains("contains_state"); + let contains_available = first_arg.contains("available"); + let contains_announcement_banner = first_arg.contains("announcementBanner"); + let contains_avatar = first_arg.contains("avatar"); + let contains_size = first_arg.contains("size"); + let contains_dashboard = first_arg.contains("dashboard"); + let contains_gadget = first_arg.contains("gadget"); + let contains_filter = first_arg.contains("filter"); + let contains_tracking = first_arg.contains("tracking"); + let contains_groupuserpicker = first_arg.contains("groupuserpicker"); + let contains_workflow = first_arg.contains("workflow"); + let contains_status = first_arg.contains("status"); + let contains_task = first_arg.contains("task"); + let contains_screen = first_arg.contains("screen"); let non_get_call = post_call || delete_call || put_call; - let contains_webhook = joined_args.contains("webhook"); - let contains_project = joined_args.contains("project"); - let contains_actor = joined_args.contains("actor"); - let contains_role = joined_args.contains("contains_role"); - let contains_project_validate = joined_args.contains("projectvalidate"); - let contains_email = joined_args.contains("email"); - let contains_notification_scheme = joined_args.contains("notificationscheme"); - let contains_priority = joined_args.contains("priority"); - let contains_properties = joined_args.contains("properties"); - let contains_remote_link = joined_args.contains("remotelink"); - let contains_resolution = joined_args.contains("resolution"); - let contains_security_level = joined_args.contains("securitylevel"); - let contains_issue_security_schemes = joined_args.contains("issuesecurityschemes"); - let contains_issue_type = joined_args.contains("issuetype"); - let contains_issue_type_schemes = joined_args.contains("issuetypescheme"); - let contains_votes = joined_args.contains("contains_votes"); - let contains_worklog = joined_args.contains("worklog"); - let contains_expression = joined_args.contains("expression"); - let contains_configuration = joined_args.contains("configuration"); - let contains_application_properties = joined_args.contains("application-properties"); + let contains_webhook = first_arg.contains("webhook"); + let contains_project = first_arg.contains("project"); + let contains_actor = first_arg.contains("actor"); + let contains_role = first_arg.contains("contains_role"); + let contains_project_validate = first_arg.contains("projectvalidate"); + let contains_email = first_arg.contains("email"); + let contains_notification_scheme = first_arg.contains("notificationscheme"); + let contains_priority = first_arg.contains("priority"); + let contains_properties = first_arg.contains("properties"); + let contains_remote_link = first_arg.contains("remotelink"); + let contains_resolution = first_arg.contains("resolution"); + let contains_security_level = first_arg.contains("securitylevel"); + let contains_issue_security_schemes = first_arg.contains("issuesecurityschemes"); + let contains_issue_type = first_arg.contains("issuetype"); + let contains_issue_type_schemes = first_arg.contains("issuetypescheme"); + let contains_votes = first_arg.contains("contains_votes"); + let contains_worklog = first_arg.contains("worklog"); + let contains_expression = first_arg.contains("expression"); + let contains_configuration = first_arg.contains("configuration"); + let contains_application_properties = first_arg.contains("application-properties"); match function_name { - "requestJira" => { + IntrinsicName::RequestJira => { if (contains_dashboard && non_get_call) || (contains_user && non_get_call) || contains_task @@ -159,9 +161,7 @@ pub(crate) fn check_permission_used( } } } - - // bit flags - "requestConfluence" => { + IntrinsicName::RequestConfluence => { if non_get_call { if contains_content { used_permissions.push(ForgePermissions::WriteConfluenceContent); @@ -278,4 +278,4 @@ pub(crate) fn check_permission_used( } } used_permissions -} \ No newline at end of file +} diff --git a/crates/forge_analyzer/src/reporter.rs b/crates/forge_analyzer/src/reporter.rs index 0fb853a4..b2fdc1e6 100644 --- a/crates/forge_analyzer/src/reporter.rs +++ b/crates/forge_analyzer/src/reporter.rs @@ -1,4 +1,6 @@ +use forge_loader::forgepermissions::ForgePermissions; use serde::Serialize; +use std::collections::HashSet; use time::{Date, OffsetDateTime}; #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)] @@ -14,6 +16,8 @@ pub enum Severity { pub struct Vulnerability { pub(crate) check_name: String, pub(crate) description: String, + #[serde(skip_serializing_if = "Option::is_none")] + pub(crate) unused_permissions: Option>, pub(crate) recommendation: &'static str, pub(crate) proof: String, pub(crate) severity: Severity, diff --git a/crates/fsrt/src/main.rs b/crates/fsrt/src/main.rs index f1665e45..51073f71 100644 --- a/crates/fsrt/src/main.rs +++ b/crates/fsrt/src/main.rs @@ -197,7 +197,6 @@ fn scan_directory(dir: PathBuf, function: Option<&str>, opts: &Args) -> Result<( let manifest: ForgeManifest = serde_yaml::from_str(&manifest).into_diagnostic()?; let name = manifest.app.name.unwrap_or_default(); -<<<<<<< HEAD let requested_permissions = manifest.permissions; let permission_scopes = requested_permissions.scopes; let permissions_declared: HashSet = @@ -211,9 +210,6 @@ fn scan_directory(dir: PathBuf, function: Option<&str>, opts: &Args) -> Result<( } else { vec![] }; - -======= ->>>>>>> 32556bb (feat: EAS-1592 mistakingly classifies resolvers exposed from consumers as user reachable) let paths = collect_sourcefiles(dir.join("src/")).collect::>(); let transpiled_async = paths.iter().any(|path| { From 03686c328fc120bf855b0ddb54a2fe11234cd3ba Mon Sep 17 00:00:00 2001 From: James Gersbach Date: Tue, 27 Jun 2023 08:41:32 -0700 Subject: [PATCH 419/517] fix bug with adding arguments --- crates/forge_analyzer/src/checkers.rs | 27 ++++++++++--------- crates/forge_analyzer/src/interp.rs | 1 - .../src/permissionclassifier.rs | 5 ++-- crates/forge_analyzer/src/reporter.rs | 2 -- 4 files changed, 16 insertions(+), 19 deletions(-) diff --git a/crates/forge_analyzer/src/checkers.rs b/crates/forge_analyzer/src/checkers.rs index 6be1cb87..fad8380a 100644 --- a/crates/forge_analyzer/src/checkers.rs +++ b/crates/forge_analyzer/src/checkers.rs @@ -497,7 +497,6 @@ impl IntoVuln for AuthNVuln { app_key: reporter.app_key().to_owned(), app_name: reporter.app_name().to_owned(), date: reporter.current_date(), - unused_permissions: None, } } } @@ -1838,7 +1837,6 @@ impl IntoVuln for AuthNVuln { app_key: reporter.app_key().to_owned(), app_name: reporter.app_name().to_owned(), date: reporter.current_date(), - unused_permissions: None, } } } @@ -1982,7 +1980,7 @@ impl<'cx> Dataflow<'cx> for PermissionDataflow { _ => {} } - let mut all_permissions_used: Vec = vec![]; + let mut permissions_within_call: Vec = vec![]; let function_name = if intrinsic_argument.name.unwrap() == String::from("requestJira") { IntrinsicName::RequestJira } else { @@ -2005,13 +2003,15 @@ impl<'cx> Dataflow<'cx> for PermissionDataflow { Some(second_arg), ); println!("permissions {:?}", permissions); - all_permissions_used.extend_from_slice(&permissions); + permissions_within_call.extend_from_slice(&permissions); }) }) }) }); - // check_permission_used() + _interp + .permissions + .extend_from_slice(&permissions_within_call); match *intrinsic { Intrinsic::Authorize(_) => initial_state, @@ -2175,8 +2175,8 @@ fn add_elements_to_intrinsic_struct(value: &Value, args: &mut Option } pub struct PermissionChecker { - vulns: Vec, - declared_permissions: HashSet, + pub vulns: Vec, + pub declared_permissions: HashSet, } impl PermissionChecker { @@ -2187,7 +2187,7 @@ impl PermissionChecker { } } - pub fn into_vulns(self) -> impl IntoIterator { + pub fn into_vulns(self) -> impl IntoIterator { self.vulns.into_iter() } } @@ -2237,18 +2237,21 @@ impl<'cx> Checker<'cx> for PermissionChecker { operands: Option>, ) -> ControlFlow<(), Self::State> { println!("visitng intrsinsic"); + for permission in &interp.permissions { + self.declared_permissions.remove(permission); + } ControlFlow::Continue(*state) } } #[derive(Debug)] pub struct PermissionVuln { - // unused_permissions: HashSet, + unused_permissions: HashSet, } impl PermissionVuln { - pub fn new(/*unused_permissions: HashSet */) -> PermissionVuln { - PermissionVuln { /*unused_permissions*/ } + pub fn new(unused_permissions: HashSet) -> PermissionVuln { + PermissionVuln { unused_permissions } } } @@ -2260,14 +2263,12 @@ impl IntoVuln for PermissionVuln { "Unused permissions listed in manifest file:.", // self.unused_permissions.into_iter().join(", ") ), - // unused_permissions: Some(self.unused_permissions), recommendation: "Remove permissions in manifest file that are not needed.", proof: format!("Unused permissions found in manifest.yml"), severity: Severity::Low, app_key: reporter.app_key().to_string(), app_name: reporter.app_name().to_string(), date: reporter.current_date(), - unused_permissions: None, } } } diff --git a/crates/forge_analyzer/src/interp.rs b/crates/forge_analyzer/src/interp.rs index 6e208200..549124c4 100644 --- a/crates/forge_analyzer/src/interp.rs +++ b/crates/forge_analyzer/src/interp.rs @@ -806,7 +806,6 @@ impl<'cx, C: Runner<'cx>> Interp<'cx, C> { let mut before_state = self.block_state(def, block_id); let block = func.block(block_id); for &pred in func.predecessors(block_id) { - let block_ = func.block(pred); before_state = before_state.join(&self.block_state(def, pred)); } let state = diff --git a/crates/forge_analyzer/src/permissionclassifier.rs b/crates/forge_analyzer/src/permissionclassifier.rs index e3934916..af755b78 100644 --- a/crates/forge_analyzer/src/permissionclassifier.rs +++ b/crates/forge_analyzer/src/permissionclassifier.rs @@ -1,11 +1,10 @@ use crate::checkers::IntrinsicName; -use core::fmt; use forge_loader::forgepermissions::ForgePermissions; pub(crate) fn check_permission_used( function_name: IntrinsicName, - first_arg: &String, - second_arg: Option<&String>, + first_arg: &str, + second_arg: Option<&str>, ) -> Vec { let mut used_permissions: Vec = Vec::new(); diff --git a/crates/forge_analyzer/src/reporter.rs b/crates/forge_analyzer/src/reporter.rs index b2fdc1e6..1b993d7c 100644 --- a/crates/forge_analyzer/src/reporter.rs +++ b/crates/forge_analyzer/src/reporter.rs @@ -16,8 +16,6 @@ pub enum Severity { pub struct Vulnerability { pub(crate) check_name: String, pub(crate) description: String, - #[serde(skip_serializing_if = "Option::is_none")] - pub(crate) unused_permissions: Option>, pub(crate) recommendation: &'static str, pub(crate) proof: String, pub(crate) severity: Severity, From c87b6b1098fe0dd12c8530e9a57584143516846d Mon Sep 17 00:00:00 2001 From: James Gersbach Date: Thu, 6 Jul 2023 11:25:53 -0700 Subject: [PATCH 420/517] various updates --- .gitignore | 4 + crates/forge_analyzer/src/checkers.rs | 343 +++++++++++------- crates/forge_analyzer/src/definitions.rs | 1 + crates/forge_analyzer/src/interp.rs | 20 +- .../src/permissionclassifier.rs | 8 +- crates/forge_analyzer/src/worklist.rs | 24 +- .../src/utils.js | 6 + 7 files changed, 243 insertions(+), 163 deletions(-) diff --git a/.gitignore b/.gitignore index ea8c4bf7..f2575964 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,5 @@ /target +<<<<<<< HEAD +======= +/test-apps +>>>>>>> d92122b (various updates) diff --git a/crates/forge_analyzer/src/checkers.rs b/crates/forge_analyzer/src/checkers.rs index fad8380a..61dbd3f7 100644 --- a/crates/forge_analyzer/src/checkers.rs +++ b/crates/forge_analyzer/src/checkers.rs @@ -272,7 +272,7 @@ impl<'cx> Dataflow<'cx> for AuthorizeDataflow { def: DefId, block: &'cx BasicBlock, state: Self::State, - worklist: &mut WorkList, + worklist: &mut WorkList, ) { self.super_join_term(interp, def, block, state, worklist); for def in self.needs_call.drain(..) { @@ -1715,15 +1715,15 @@ impl<'cx> Dataflow<'cx> for AuthenticateDataflow { fn join_term>( &mut self, - interp: &Interp<'cx, C>, + interp: &mut Interp<'cx, C>, def: DefId, block: &'cx BasicBlock, state: Self::State, - worklist: &mut WorkList, + worklist: &mut WorkList, ) { self.super_join_term(interp, def, block, state, worklist); for def in self.needs_call.drain(..) { - worklist.push_front_blocks(interp.env(), def, vec![]); + worklist.push_front_blocks(interp.env(), def); } } } @@ -1855,16 +1855,41 @@ impl WithCallStack for PermissionVuln { } #[derive(Debug, Default, Clone)] -struct IntrinsicArguments { - name: Option, +pub struct IntrinsicArguments { + name: Option, first_arg: Option>, second_arg: Option>, } -#[derive(Debug, Clone, Copy)] -pub enum IntrinsicName { - RequestConfluence, - RequestJira, +impl PermissionDataflow { + fn handle_first_arg( + &self, + operand: &Operand, + _def: DefId, + intrinsic_argument: &mut IntrinsicArguments, + ) { + match operand { + Operand::Lit(lit) => { + intrinsic_argument.first_arg = Some(vec![lit.to_string()]); + } + Operand::Var(var) => { + if let Base::Var(varid) = var.base { + if let Some(value) = self.get_value(_def, varid) { + intrinsic_argument.first_arg = Some(vec![]); + add_elements_to_intrinsic_struct(value, &mut intrinsic_argument.first_arg); + } + } + } + } + } + + fn add_value(&mut self, defid_block: DefId, varid: VarId, value: Value) { + self.varid_to_value.insert((defid_block, varid), value); + } + + fn get_value(&self, defid_block: DefId, varid: VarId) -> Option<&Value> { + self.varid_to_value.get(&(defid_block, varid)) + } } impl<'cx> Dataflow<'cx> for PermissionDataflow { @@ -1890,137 +1915,54 @@ impl<'cx> Dataflow<'cx> for PermissionDataflow { operands: SmallVec<[crate::ir::Operand; 4]>, ) -> Self::State { let mut intrinsic_argument = IntrinsicArguments::default(); - - match &*intrinsic { - Intrinsic::ApiCall(value) - | Intrinsic::SafeCall(value) - | Intrinsic::Authorize(value) => { - intrinsic_argument.name = Some(value.clone()); - let (first, second) = (operands.get(0), operands.get(1)); - if let Some(operand) = first { - match operand { - Operand::Lit(lit) => { - intrinsic_argument.first_arg = Some(vec![lit.to_string()]); - } - Operand::Var(var) => match var.base { - Base::Var(varid) => { - let varkind = &_interp.curr_body.get().unwrap().vars[varid]; - let defid = get_varid_from_defid(&varkind); - if let Some(defid) = defid { - if let Some(value) = self.variables_from_defid.get(&defid) { - intrinsic_argument.first_arg = Some(vec![]); - add_elements_to_intrinsic_struct( - value, - &mut intrinsic_argument.first_arg, - ); - } - } - } - _ => {} - }, - } - } - if let Some(operand) = second { - match operand { - Operand::Lit(_) => {} - Operand::Var(var) => { - if let Base::Var(varid) = var.base { - match _interp.curr_body.get().unwrap().vars[varid].clone() { - VarKind::GlobalRef(_def_id) => { - /* case where it is passed in as a variable */ - match &self.variables_from_defid.get(&_def_id).unwrap() { - Value::Const(const_var) => { - if let Const::Object(obj) = const_var { - let defid = find_member_of_obj("method", obj); - if let Some(defid) = defid { - if let Some(value) = - self.variables_from_defid.get(&defid) - { - println!("value of method {value:?}"); - intrinsic_argument.second_arg = - Some(vec![]); - add_elements_to_intrinsic_struct( - value, - &mut intrinsic_argument.second_arg, - ); - } - } - } - } - Value::Phi(phi_var) => {} - _ => {} - } - } - VarKind::LocalDef(_def_id) => { - let class = self.read_class_from_object(_interp, _def_id); - if let Some(obj) = class { - let defid = find_member_of_obj("method", &obj); - if let Some(defid) = defid { - if let Some(value) = - self.variables_from_defid.get(&defid) - { - intrinsic_argument.second_arg = Some(vec![]); - add_elements_to_intrinsic_struct( - value, - &mut intrinsic_argument.second_arg, - ); - } - // println!("value of method {value:?}"); - } - } - // - } - _ => {} - } - } - } - } - } + println!("transferring intrinsic"); + if let Intrinsic::ApiCall(name) | Intrinsic::SafeCall(name) | Intrinsic::Authorize(name) = + intrinsic + { + intrinsic_argument.name = Some(name.clone()); + let (first, second) = (operands.get(0), operands.get(1)); + if let Some(operand) = first { + self.handle_first_arg(operand, _def, &mut intrinsic_argument); } - _ => {} - } - - let mut permissions_within_call: Vec = vec![]; - let function_name = if intrinsic_argument.name.unwrap() == String::from("requestJira") { - IntrinsicName::RequestJira - } else { - IntrinsicName::RequestConfluence - }; - - intrinsic_argument - .first_arg - .iter() - .for_each(|first_arg_vec| { - intrinsic_argument - .second_arg - .iter() - .for_each(|second_arg_vec| { + if let Some(operand) = second { + self.handle_second_arg(_interp, operand, _def, &mut intrinsic_argument); + } + let mut permissions_within_call: Vec = vec![]; + let intrinsic_func_type = intrinsic_argument.name.unwrap(); + intrinsic_argument + .first_arg + .iter() + .for_each(|first_arg_vec| { + if let Some(second_arg_vec) = intrinsic_argument.second_arg.clone() { first_arg_vec.iter().for_each(|first_arg| { second_arg_vec.iter().for_each(|second_arg| { let permissions = check_permission_used( - function_name, + intrinsic_func_type, first_arg, Some(second_arg), ); - println!("permissions {:?}", permissions); permissions_within_call.extend_from_slice(&permissions); }) }) - }) - }); + } else { + first_arg_vec.iter().for_each(|first_arg| { + let permissions = + check_permission_used(intrinsic_func_type, first_arg, None); + permissions_within_call.extend_from_slice(&permissions); + }) + } + }); - _interp - .permissions - .extend_from_slice(&permissions_within_call); + println!("intrinisc arg: {:?}", intrinsic_argument); - match *intrinsic { - Intrinsic::Authorize(_) => initial_state, - Intrinsic::Fetch => initial_state, - Intrinsic::ApiCall(_) => initial_state, - Intrinsic::SafeCall(_) => initial_state, - Intrinsic::EnvRead => initial_state, - Intrinsic::StorageRead => initial_state, + println!("all permissions so far {permissions_within_call:?}"); + + _interp + .permissions + .extend_from_slice(&permissions_within_call); } + + initial_state } fn read_class_from_object>( @@ -2040,6 +1982,68 @@ impl<'cx> Dataflow<'cx> for PermissionDataflow { None } + fn try_read_mem_from_object>( + &self, + _interp: &Interp<'cx, C>, + _def: DefId, + const_var: Const, + ) -> Option<&Value> { + if let Const::Object(obj) = const_var { + return self.read_mem_from_object(_interp, _def, obj); + } + None + } + + fn read_mem_from_object>( + &self, + _interp: &Interp<'cx, C>, + _def: DefId, + obj: Class, + ) -> Option<&Value> { + let defid_method = obj + .pub_members + .iter() + .filter(|(mem, _)| mem == "method") + .map(|(_, defid)| defid) + .collect_vec(); + if let Some(_alt_defid) = defid_method.get(0) { + for (varid_new, varkind) in _interp.body().vars.clone().into_iter_enumerated() { + if let Some(defid) = get_defid_from_varkind(&varkind) { + if &&defid == _alt_defid { + return self.get_value(_def, varid_new); + } + } + } + } + None + } + + fn def_to_class_property>( + &self, + _interp: &Interp<'cx, C>, + _def: DefId, + defid: DefId, + ) -> Option<&Value> { + if let Some(DefKind::GlobalObj(objid)) = _interp.env().defs.defs.get(defid) { + if let Some(class) = _interp.env().defs.classes.get(objid.clone()) { + return self.read_mem_from_object(_interp, _def, class.clone()); + } + } + None + } + + fn get_values_from_operand>( + &self, + _interp: &Interp<'cx, C>, + _def: DefId, + operand: &Operand, + ) -> Option<&Value> { + if let Some((_, varid)) = self.get_defid_from_operand(_interp, operand) { + return self.get_value(_def, varid); + } + None + } + fn transfer_call>( &mut self, interp: &Interp<'cx, C>, @@ -2091,20 +2095,21 @@ impl<'cx> Dataflow<'cx> for PermissionDataflow { fn join_term>( &mut self, - interp: &Interp<'cx, C>, + interp: &mut Interp<'cx, C>, def: DefId, block: &'cx BasicBlock, state: Self::State, - worklist: &mut WorkList, + worklist: &mut WorkList, ) { self.super_join_term(interp, def, block, state, worklist); - for (def, arguments) in self.needs_call.drain(..) { - worklist.push_front_blocks(interp.env(), def, arguments); + for (def, arguments, values) in self.needs_call.drain(..) { + worklist.push_front_blocks(interp.env(), def); + interp.callstack_arguments.push(values.clone()); } } } -fn resolve_var_from_operand(operand: &Operand) -> Option { +pub(crate) fn resolve_var_from_operand(operand: &Operand) -> Option { if let Operand::Var(var) = operand { if let Base::Var(varid) = var.base { return Some(varid); @@ -2120,11 +2125,28 @@ fn resolve_literal_from_operand(operand: &Operand) -> Option { None } -fn get_varid_from_defid(varkind: &VarKind) -> Option { +fn add_const_to_val_vec(val: &Value, const_val: &Const, vals: &mut Vec) { + match val { + Value::Const(Const::Literal(lit)) => { + if let Const::Literal(lit2) = const_val { + vals.push(lit.to_owned() + &lit2); + } + } + Value::Phi(phi_val2) => phi_val2.iter().for_each(|val2| { + if let (Const::Literal(lit1), Const::Literal(lit2)) = (&const_val, val2) { + vals.push(lit1.to_owned() + lit2); + } + }), + _ => {} + } +} + +pub(crate) fn get_defid_from_varkind(varkind: &VarKind) -> Option { match varkind { VarKind::GlobalRef(defid) => Some(defid.clone()), VarKind::LocalDef(defid) => Some(defid.clone()), VarKind::Arg(defid) => Some(defid.clone()), + VarKind::AnonClosure(defid) => Some(defid.clone()), VarKind::Temp { parent } => parent.clone(), _ => None, } @@ -2156,6 +2178,15 @@ fn find_member_of_obj(member: &str, obj: &Class) -> Option { None } +fn get_str_from_operand(operand: &Operand) -> Option { + if let Operand::Lit(lit) = operand { + if let Literal::Str(str) = lit { + return Some(str.to_string()); + } + } + None +} + fn add_elements_to_intrinsic_struct(value: &Value, args: &mut Option>) { match value { Value::Const(const_value) => { @@ -2174,9 +2205,35 @@ fn add_elements_to_intrinsic_struct(value: &Value, args: &mut Option } } +fn get_prev_value(value: Option<&Value>) -> Option> { + if let Some(value) = value { + return match value { + Value::Const(const_value) => Some(vec![const_value.clone()]), + Value::Phi(phi_value) => Some(phi_value.clone()), + _ => None, + }; + } + None +} + +fn return_value_from_string(values: Vec) -> Value { + assert!(values.len() > 0); + if values.len() == 1 { + return Value::Const(Const::Literal(values.get(0).unwrap().clone())); + } else { + return Value::Phi( + values + .iter() + .map(|val_string| Const::Literal(val_string.clone())) + .collect_vec(), + ); + } +} + pub struct PermissionChecker { pub vulns: Vec, pub declared_permissions: HashSet, + pub used_permissions: HashSet, } impl PermissionChecker { @@ -2184,10 +2241,17 @@ impl PermissionChecker { Self { vulns: vec![], declared_permissions, + used_permissions: HashSet::default(), } } pub fn into_vulns(self) -> impl IntoIterator { + if self.declared_permissions.len() > 0 { + return Vec::from([PermissionVuln { + unused_permissions: self.declared_permissions.clone(), + }]) + .into_iter(); + } self.vulns.into_iter() } } @@ -2236,9 +2300,9 @@ impl<'cx> Checker<'cx> for PermissionChecker { state: &Self::State, operands: Option>, ) -> ControlFlow<(), Self::State> { - println!("visitng intrsinsic"); for permission in &interp.permissions { self.declared_permissions.remove(permission); + self.used_permissions.insert(permission.clone()); } ControlFlow::Continue(*state) } @@ -2260,11 +2324,14 @@ impl IntoVuln for PermissionVuln { Vulnerability { check_name: format!("Least-Privilege"), description: format!( - "Unused permissions listed in manifest file:.", - // self.unused_permissions.into_iter().join(", ") + "Unused permissions listed in manifest file: {:?}", + self.unused_permissions ), recommendation: "Remove permissions in manifest file that are not needed.", - proof: format!("Unused permissions found in manifest.yml"), + proof: format!( + "Unused permissions found in manifest.yml: {:?}", + self.unused_permissions + ), severity: Severity::Low, app_key: reporter.app_key().to_string(), app_name: reporter.app_name().to_string(), diff --git a/crates/forge_analyzer/src/definitions.rs b/crates/forge_analyzer/src/definitions.rs index fa271b6c..fb3c5046 100644 --- a/crates/forge_analyzer/src/definitions.rs +++ b/crates/forge_analyzer/src/definitions.rs @@ -1278,6 +1278,7 @@ impl<'cx> FunctionAnalyzer<'cx> { Some(int) => Rvalue::Intrinsic(int, lowered_args), None => Rvalue::Call(callee, lowered_args), }; + let res = self.body.push_tmp(self.block, call, None); Operand::with_var(res) } diff --git a/crates/forge_analyzer/src/interp.rs b/crates/forge_analyzer/src/interp.rs index 549124c4..17b770c0 100644 --- a/crates/forge_analyzer/src/interp.rs +++ b/crates/forge_analyzer/src/interp.rs @@ -159,15 +159,17 @@ pub trait Dataflow<'cx>: Sized { fn add_variable>( &mut self, interp: &Interp<'cx, C>, - defid: &DefId, + varid: &VarId, + def: DefId, rvalue: &Rvalue, ) { } - fn insert_value>( + fn insert_value2>( &mut self, operand: &Operand, - defid: &DefId, + varid: &VarId, + def: DefId, interp: &Interp<'cx, C>, prev_values: Option>, ) { @@ -200,7 +202,7 @@ pub trait Dataflow<'cx>: Sized { def: DefId, block: &'cx BasicBlock, state: Self::State, - worklist: &mut WorkList, + worklist: &mut WorkList, ) { self.super_join_term(interp.borrow_mut(), def, block, state, worklist); } @@ -211,7 +213,7 @@ pub trait Dataflow<'cx>: Sized { def: DefId, block: &'cx BasicBlock, state: Self::State, - worklist: &mut WorkList, + worklist: &mut WorkList, ) { match block.successors() { Successors::Return => { @@ -225,7 +227,7 @@ pub trait Dataflow<'cx>: Sized { debug!("{name} {def:?} is called from {calls:?}"); for &(def, loc) in calls { if worklist.visited(&def) { - worklist.push_back_force(def, loc.block, vec![]); + worklist.push_back_force(def, loc.block); } } } @@ -233,15 +235,15 @@ pub trait Dataflow<'cx>: Sized { Successors::One(succ) => { let mut succ_state = interp.block_state_mut(def, succ); if succ_state.join_changed(&state) { - worklist.push_back(def, succ, vec![]); + worklist.push_back(def, succ); } } Successors::Two(succ1, succ2) => { if interp.block_state_mut(def, succ1).join_changed(&state) { - worklist.push_back(def, succ1, vec![]); + worklist.push_back(def, succ1); } if interp.block_state_mut(def, succ2).join_changed(&state) { - worklist.push_back(def, succ2, vec![]); + worklist.push_back(def, succ2); } } } diff --git a/crates/forge_analyzer/src/permissionclassifier.rs b/crates/forge_analyzer/src/permissionclassifier.rs index af755b78..2ab45477 100644 --- a/crates/forge_analyzer/src/permissionclassifier.rs +++ b/crates/forge_analyzer/src/permissionclassifier.rs @@ -1,4 +1,4 @@ -use crate::checkers::IntrinsicName; +use crate::definitions::IntrinsicName; use forge_loader::forgepermissions::ForgePermissions; pub(crate) fn check_permission_used( @@ -8,9 +8,9 @@ pub(crate) fn check_permission_used( ) -> Vec { let mut used_permissions: Vec = Vec::new(); - let post_call = second_arg.unwrap_or(&String::from("")).contains("POST"); - let delete_call = second_arg.unwrap_or(&String::from("")).contains("DELTE"); - let put_call = second_arg.unwrap_or(&String::from("")).contains("PUT"); + let post_call = second_arg.unwrap_or("").contains("POST"); + let delete_call = second_arg.unwrap_or("").contains("DELTE"); + let put_call = second_arg.unwrap_or("").contains("PUT"); let contains_audit = first_arg.contains("audit"); let contains_issue = first_arg.contains("issue"); diff --git a/crates/forge_analyzer/src/worklist.rs b/crates/forge_analyzer/src/worklist.rs index a5ca8e01..afac0d19 100644 --- a/crates/forge_analyzer/src/worklist.rs +++ b/crates/forge_analyzer/src/worklist.rs @@ -9,12 +9,12 @@ use crate::{ }; #[derive(Debug, Clone)] -pub struct WorkList { - worklist: VecDeque<(V, W, Vec)>, +pub struct WorkList { + worklist: VecDeque<(V, W)>, visited: FxHashSet, } -impl WorkList +impl WorkList where V: Eq + Hash, { @@ -27,7 +27,7 @@ where } #[inline] - pub fn pop_front(&mut self) -> (Option<(V, W, Vec)>) { + pub fn pop_front(&mut self) -> Option<(V, W)> { self.worklist.pop_front() } @@ -56,24 +56,24 @@ where } } -impl WorkList +impl WorkList where V: Eq + Hash + Copy, { #[inline] - pub fn push_back(&mut self, v: V, w: W, args: Vec) { + pub fn push_back(&mut self, v: V, w: W) { if self.visited.insert(v) { - self.worklist.push_back((v, w, args)); + self.worklist.push_back((v, w)); } } #[inline] - pub fn push_back_force(&mut self, v: V, w: W, args: Vec) { - self.worklist.push_back((v, w, args)); + pub fn push_back_force(&mut self, v: V, w: W) { + self.worklist.push_back((v, w)); } } -impl WorkList { +impl WorkList { #[inline] pub(crate) fn push_front_blocks( &mut self, @@ -96,12 +96,12 @@ impl WorkList { } } -impl Extend<(V, W, Vec)> for WorkList +impl Extend<(V, W)> for WorkList where V: Eq + Hash, { #[inline] - fn extend)>>(&mut self, iter: T) { + fn extend>(&mut self, iter: T) { self.worklist.extend(iter); } } diff --git a/test-apps/jira-damn-vulnerable-forge-app/src/utils.js b/test-apps/jira-damn-vulnerable-forge-app/src/utils.js index 025fed04..f39207ed 100755 --- a/test-apps/jira-damn-vulnerable-forge-app/src/utils.js +++ b/test-apps/jira-damn-vulnerable-forge-app/src/utils.js @@ -33,6 +33,12 @@ export async function fetchIssueSummary(issueIdOrKey, test_value) { }, }; + let val = 'grapefruit'; + + val = 'peach'; + + let pre_url = '/rest/api/3/issue/' + val; + module_exports_func(); func_from_exports(); another_export(); From 9866c540ad56b4e35151412d580a94d4925feb8f Mon Sep 17 00:00:00 2001 From: James Gersbach Date: Tue, 27 Jun 2023 09:52:15 -0700 Subject: [PATCH 421/517] fix to the worklist --- crates/forge_analyzer/src/worklist.rs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/crates/forge_analyzer/src/worklist.rs b/crates/forge_analyzer/src/worklist.rs index afac0d19..55fb3318 100644 --- a/crates/forge_analyzer/src/worklist.rs +++ b/crates/forge_analyzer/src/worklist.rs @@ -27,7 +27,11 @@ where } #[inline] +<<<<<<< HEAD pub fn pop_front(&mut self) -> Option<(V, W)> { +======= + pub fn pop_front(&mut self) -> (Option<(V, W)>) { +>>>>>>> 19837ff (fix to the worklist) self.worklist.pop_front() } @@ -88,7 +92,12 @@ impl WorkList { self.worklist.reserve(blocks.len()); for work in blocks { debug!(?work, "push_front_blocks"); +<<<<<<< HEAD self.worklist.push_front((work.0, work.1)); +======= + self.worklist + .push_front((work.0, work.1)); +>>>>>>> 19837ff (fix to the worklist) } return true; } From 4810f4a05649f1a4a52ff18ea33d03a324f1ba48 Mon Sep 17 00:00:00 2001 From: James Gersbach Date: Mon, 3 Jul 2023 08:17:47 -0700 Subject: [PATCH 422/517] converting defid to varid --- crates/forge_analyzer/src/checkers.rs | 36 +++++++++++++++++++++++++++ crates/forge_analyzer/src/interp.rs | 2 +- crates/forge_analyzer/src/worklist.rs | 9 ------- 3 files changed, 37 insertions(+), 10 deletions(-) diff --git a/crates/forge_analyzer/src/checkers.rs b/crates/forge_analyzer/src/checkers.rs index 61dbd3f7..ef03c147 100644 --- a/crates/forge_analyzer/src/checkers.rs +++ b/crates/forge_analyzer/src/checkers.rs @@ -1982,6 +1982,42 @@ impl<'cx> Dataflow<'cx> for PermissionDataflow { None } + fn try_read_mem_from_object>( + &mut self, + _interp: &Interp<'cx, C>, + _def: DefId, + const_var: Const, + ) -> Option<&Value> { + if let Const::Object(obj) = const_var { + return self.read_mem_from_object(_interp, _def, obj); + } + None + } + + fn read_mem_from_object>( + &mut self, + _interp: &Interp<'cx, C>, + _def: DefId, + obj: Class, + ) -> Option<&Value> { + let defid_method = obj + .pub_members + .iter() + .filter(|(mem, _)| mem == "method") + .map(|(_, defid)| defid) + .collect_vec(); + if let Some(_alt_defid) = defid_method.get(0) { + for (varid_new, varkind) in _interp.body().vars.clone().into_iter_enumerated() { + if let Some(defid) = get_varid_from_defid(&varkind) { + if &&defid == _alt_defid { + return self.varid_to_value.get(&(_def, varid_new)); + } + } + } + } + None + } + fn try_read_mem_from_object>( &self, _interp: &Interp<'cx, C>, diff --git a/crates/forge_analyzer/src/interp.rs b/crates/forge_analyzer/src/interp.rs index 17b770c0..de5f0e2c 100644 --- a/crates/forge_analyzer/src/interp.rs +++ b/crates/forge_analyzer/src/interp.rs @@ -164,7 +164,6 @@ pub trait Dataflow<'cx>: Sized { rvalue: &Rvalue, ) { } - fn insert_value2>( &mut self, operand: &Operand, @@ -633,6 +632,7 @@ impl<'cx, C: Runner<'cx>> Interp<'cx, C> { dataflow_visited: FxHashSet::default(), checker_visited: RefCell::new(FxHashSet::default()), callstack_arguments: Vec::new(), + expecting_value: VecDeque::default(), callstack: RefCell::new(Vec::new()), // vulns: RefCell::new(Vec::new()), value_manager: ValueManager { diff --git a/crates/forge_analyzer/src/worklist.rs b/crates/forge_analyzer/src/worklist.rs index 55fb3318..afac0d19 100644 --- a/crates/forge_analyzer/src/worklist.rs +++ b/crates/forge_analyzer/src/worklist.rs @@ -27,11 +27,7 @@ where } #[inline] -<<<<<<< HEAD pub fn pop_front(&mut self) -> Option<(V, W)> { -======= - pub fn pop_front(&mut self) -> (Option<(V, W)>) { ->>>>>>> 19837ff (fix to the worklist) self.worklist.pop_front() } @@ -92,12 +88,7 @@ impl WorkList { self.worklist.reserve(blocks.len()); for work in blocks { debug!(?work, "push_front_blocks"); -<<<<<<< HEAD self.worklist.push_front((work.0, work.1)); -======= - self.worklist - .push_front((work.0, work.1)); ->>>>>>> 19837ff (fix to the worklist) } return true; } From cc1ffd99e8c02d8de30d23452b919f7998f89341 Mon Sep 17 00:00:00 2001 From: James Gersbach Date: Sat, 8 Jul 2023 17:52:01 -0700 Subject: [PATCH 423/517] wip --- crates/forge_analyzer/src/checkers.rs | 25 +++++++++++++++++-- crates/forge_analyzer/src/definitions.rs | 3 +++ crates/forge_analyzer/src/interp.rs | 2 ++ .../src/GlobalPageApp.jsx | 2 +- .../src/index.jsx | 10 +++----- 5 files changed, 32 insertions(+), 10 deletions(-) diff --git a/crates/forge_analyzer/src/checkers.rs b/crates/forge_analyzer/src/checkers.rs index ef03c147..9d2fc94d 100644 --- a/crates/forge_analyzer/src/checkers.rs +++ b/crates/forge_analyzer/src/checkers.rs @@ -275,6 +275,7 @@ impl<'cx> Dataflow<'cx> for AuthorizeDataflow { worklist: &mut WorkList, ) { self.super_join_term(interp, def, block, state, worklist); + //println!("worklist - adding function {}", interp.env().def_name(def)); for def in self.needs_call.drain(..) { worklist.push_front_blocks(interp.env(), def, interp.call_all); } @@ -1915,7 +1916,7 @@ impl<'cx> Dataflow<'cx> for PermissionDataflow { operands: SmallVec<[crate::ir::Operand; 4]>, ) -> Self::State { let mut intrinsic_argument = IntrinsicArguments::default(); - println!("transferring intrinsic"); + println!("transferring intrinsic {:?}", _interp.env().def_name(_def)); if let Intrinsic::ApiCall(name) | Intrinsic::SafeCall(name) | Intrinsic::Authorize(name) = intrinsic { @@ -2042,10 +2043,26 @@ impl<'cx> Dataflow<'cx> for PermissionDataflow { .filter(|(mem, _)| mem == "method") .map(|(_, defid)| defid) .collect_vec(); + + // println!("deifd methods {defid_method:?} {:?}", obj.pub_members); + // /* everything here is correct */ + + println!("defid to varid {:?}", _interp.body().def_id_to_vars); + println!(); + println!("deifd to varid {:?}", _interp.body().vars); + println!(); + println!("projections"); + println!(); + if let Some(_alt_defid) = defid_method.get(0) { for (varid_new, varkind) in _interp.body().vars.clone().into_iter_enumerated() { + // println!("varid {varid_new} : varkind {varkind:?}"); if let Some(defid) = get_defid_from_varkind(&varkind) { if &&defid == _alt_defid { + println!( + "incorrect value - misinterpreting here {_alt_defid:?} {defid:?} {:?}", + self.get_value(_def, varid_new) + ); return self.get_value(_def, varid_new); } } @@ -2061,7 +2078,10 @@ impl<'cx> Dataflow<'cx> for PermissionDataflow { defid: DefId, ) -> Option<&Value> { if let Some(DefKind::GlobalObj(objid)) = _interp.env().defs.defs.get(defid) { + println!(""); if let Some(class) = _interp.env().defs.classes.get(objid.clone()) { + println!("class~~ {class:?}"); + return self.read_mem_from_object(_interp, _def, class.clone()); } } @@ -2253,7 +2273,7 @@ fn get_prev_value(value: Option<&Value>) -> Option> { } fn return_value_from_string(values: Vec) -> Value { - assert!(values.len() > 0); + // assert!(values.len() > 0); if values.len() == 1 { return Value::Const(Const::Literal(values.get(0).unwrap().clone())); } else { @@ -2336,6 +2356,7 @@ impl<'cx> Checker<'cx> for PermissionChecker { state: &Self::State, operands: Option>, ) -> ControlFlow<(), Self::State> { + //println!("visiting _intrinsic"); for permission in &interp.permissions { self.declared_permissions.remove(permission); self.used_permissions.insert(permission.clone()); diff --git a/crates/forge_analyzer/src/definitions.rs b/crates/forge_analyzer/src/definitions.rs index fb3c5046..544ba197 100644 --- a/crates/forge_analyzer/src/definitions.rs +++ b/crates/forge_analyzer/src/definitions.rs @@ -904,6 +904,8 @@ impl<'cx> FunctionAnalyzer<'cx> { *prop == *"get" || *prop == *"getSecret" || *prop == *"query" } + //println!("as intrinsic "); + match *callee { [PropPath::Unknown((ref name, ..))] if *name == *"fetch" => Some(Intrinsic::Fetch), [PropPath::Def(def), ref authn @ .., PropPath::Static(ref last)] @@ -916,6 +918,7 @@ impl<'cx> FunctionAnalyzer<'cx> { } else { IntrinsicName::RequestConfluence }; + //println!("here within the intrinsic"); let first_arg = first_arg?; let is_as_app = authn.first() == Some(&PropPath::MemberCall("asApp".into())); match classify_api_call(first_arg) { diff --git a/crates/forge_analyzer/src/interp.rs b/crates/forge_analyzer/src/interp.rs index de5f0e2c..6857d345 100644 --- a/crates/forge_analyzer/src/interp.rs +++ b/crates/forge_analyzer/src/interp.rs @@ -93,6 +93,7 @@ pub trait Dataflow<'cx>: Sized { rvalue: &'cx Rvalue, initial_state: Self::State, ) -> Self::State { + // println!("transfer rvalue {:?}", rvalue); match rvalue { Rvalue::Intrinsic(intrinsic, args) => self.transfer_intrinsic( interp, @@ -150,6 +151,7 @@ pub trait Dataflow<'cx>: Sized { ) -> Self::State { let mut state = initial_state; for (stmt, inst) in block.iter().enumerate() { + // println!("inst -- {inst}"); let loc = Location::new(bb, stmt as u32); state = self.transfer_inst(interp, def, loc, block, inst, state); } diff --git a/test-apps/jira-damn-vulnerable-forge-app/src/GlobalPageApp.jsx b/test-apps/jira-damn-vulnerable-forge-app/src/GlobalPageApp.jsx index 364fb310..9e41172d 100755 --- a/test-apps/jira-damn-vulnerable-forge-app/src/GlobalPageApp.jsx +++ b/test-apps/jira-damn-vulnerable-forge-app/src/GlobalPageApp.jsx @@ -12,4 +12,4 @@ const GlobalPageApp = () => { {issue} ) -}; \ No newline at end of file +}; diff --git a/test-apps/jira-damn-vulnerable-forge-app/src/index.jsx b/test-apps/jira-damn-vulnerable-forge-app/src/index.jsx index 98ac94bb..fbc2e4cb 100755 --- a/test-apps/jira-damn-vulnerable-forge-app/src/index.jsx +++ b/test-apps/jira-damn-vulnerable-forge-app/src/index.jsx @@ -18,7 +18,6 @@ import api, { webTrigger, route, storage, properties } from '@forge/api'; import { createHash } from 'crypto'; import jwt from 'jsonwebtoken'; import { fetchIssueSummary } from './utils'; -import { IssuePanelApp } from './IssuePanelApp'; function SharedSecretForm() { const [hashedSecret, setHashedSecret] = useState(null); @@ -98,12 +97,9 @@ function SecureGlance() { return ''; } const [flagVal] = useState(async () => { - const issueData = await fetchIssueSummary( - platformContext.issueKey, - 'test_value_passed_in_as_argument' - ); - const test = writeComment('test', 'test'); - return JSON.stringify(issueData + test); + const issueData = await fetchIssueSummary(platformContext.issueKey, value); + const test = await writeComment('test', 'test'); + return JSON.stringify(issueData); }); return ( From 24afac9400d06596daf42315848a518cdd7f8a47 Mon Sep 17 00:00:00 2001 From: James Gersbach Date: Mon, 10 Jul 2023 08:59:36 -0700 Subject: [PATCH 424/517] wip --- crates/forge_analyzer/src/checkers.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/forge_analyzer/src/checkers.rs b/crates/forge_analyzer/src/checkers.rs index 9d2fc94d..949ff98e 100644 --- a/crates/forge_analyzer/src/checkers.rs +++ b/crates/forge_analyzer/src/checkers.rs @@ -2228,7 +2228,7 @@ fn convert_lit_to_raw(lit: &Literal) -> Option { fn find_member_of_obj(member: &str, obj: &Class) -> Option { for (mem, memdefid) in &obj.pub_members { if mem == member { - return Some(memdefid.clone()); + return Some(*memdefid); } } None From 242717502164cab52753f11032159e8c3e3ba1d3 Mon Sep 17 00:00:00 2001 From: gersbach <93059258+gersbach@users.noreply.github.com> Date: Mon, 10 Jul 2023 08:58:20 -0700 Subject: [PATCH 425/517] Update crates/forge_analyzer/src/checkers.rs Co-authored-by: Joshua Wong --- crates/forge_analyzer/src/checkers.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/crates/forge_analyzer/src/checkers.rs b/crates/forge_analyzer/src/checkers.rs index 949ff98e..92192fbb 100644 --- a/crates/forge_analyzer/src/checkers.rs +++ b/crates/forge_analyzer/src/checkers.rs @@ -2046,7 +2046,6 @@ impl<'cx> Dataflow<'cx> for PermissionDataflow { // println!("deifd methods {defid_method:?} {:?}", obj.pub_members); // /* everything here is correct */ - println!("defid to varid {:?}", _interp.body().def_id_to_vars); println!(); println!("deifd to varid {:?}", _interp.body().vars); From b6162c0b68d8ffbe6a396582b85abca17325af35 Mon Sep 17 00:00:00 2001 From: James Gersbach Date: Tue, 11 Jul 2023 13:08:21 -0700 Subject: [PATCH 426/517] wip --- crates/forge_analyzer/src/checkers.rs | 1279 ++++++------------------- 1 file changed, 312 insertions(+), 967 deletions(-) diff --git a/crates/forge_analyzer/src/checkers.rs b/crates/forge_analyzer/src/checkers.rs index 92192fbb..e7cac632 100644 --- a/crates/forge_analyzer/src/checkers.rs +++ b/crates/forge_analyzer/src/checkers.rs @@ -4,9 +4,16 @@ use std::{cmp::max, mem, ops::ControlFlow}; use tracing::{debug, info, warn}; use crate::{ - definitions::DefId, - interp::{Checker, Dataflow, Frame, Interp, JoinSemiLattice, WithCallStack}, - ir::{BasicBlock, BasicBlockId, Intrinsic, Location}, + definitions::{Class, Const, DefId, DefKind, Environment, IntrinsicName, Value}, + interp::{ + Checker, Dataflow, EntryKind, EntryPoint, Frame, Interp, JoinSemiLattice, WithCallStack, + }, + ir::{ + Base, BasicBlock, BasicBlockId, BinOp, Inst, Intrinsic, Literal, Location, Operand, Rvalue, + Successors, VarId, VarKind, + }, + permissionclassifier::check_permission_used, + reporter::{IntoVuln, Reporter, Severity, Vulnerability}, worklist::WorkList, }; @@ -697,935 +704,25 @@ impl<'cx> Runner<'cx> for SecretChecker { state: &Self::State, operands: Option>, ) -> ControlFlow<(), Self::State> { - if let Intrinsic::SecretFunction(package_data) = intrinsic { - if let Some(operand) = operands - .unwrap_or_default() - .get((package_data.secret_position - 1) as usize) - { - match operand { - Operand::Lit(lit) => { - let vuln = - SecretVuln::new(interp.callstack(), interp.env(), interp.entry()); - if let Literal::Str(_) = lit { - info!("Found a vuln!"); - self.vulns.push(vuln); - } - } - Operand::Var(var) => { - if let Base::Var(varid) = var.base { - if let Some(value) = - interp.get_value(def, varid, var.projections.get(0).cloned()) - { - match value { - Value::Const(_) | Value::Phi(_) => { - let vuln = SecretVuln::new( - interp.callstack(), - interp.env(), - interp.entry(), - ); - info!("Found a vuln!"); - self.vulns.push(vuln); - } - _ => {} - } - } else if let Some(value) = interp.body().vars.get(varid) { - if let VarKind::GlobalRef(def) = value { - if let Some(value) = - interp.value_manager.defid_to_value.get(def) - { - println!("value [] {value:?}"); - match value { - Value::Const(_) | Value::Phi(_) => { - let vuln = SecretVuln::new( - interp.callstack(), - interp.env(), - interp.entry(), - ); - info!("Found a vuln!"); - self.vulns.push(vuln); - } - _ => {} - } - } - } - } - } - } - } - } - } - ControlFlow::Continue(*state) - } -} - -impl<'cx> Checker<'cx> for SecretChecker { - type Vuln = SecretVuln; -} - -pub struct PermissionDataflow { - needs_call: Vec<(DefId, Vec)>, - pub varid_to_value: FxHashMap<(DefId, VarId, Option), Value>, - pub defid_to_value: FxHashMap, -} - -impl PermissionDataflow { - fn handle_second_arg(&self, value: &Value, intrinsic_argument: &mut IntrinsicArguments) { - match value { - Value::Const(Const::Literal(lit)) => { - intrinsic_argument.second_arg = Some(vec![lit.to_string()]); - } - Value::Phi(phi_val) => { - intrinsic_argument.second_arg = Some( - phi_val - .iter() - .map(|data| { - if let Const::Literal(lit) = data { - return Some(lit.to_string()); - } else { - None - } - }) - .filter(|const_val| const_val != &None) - .map(|f| f.unwrap()) - .collect_vec(), - ) - } - _ => {} - } - } -} - -impl WithCallStack for PermissionVuln { - fn add_call_stack(&mut self, _stack: Vec) {} -} - -#[derive(Debug, Default, Clone)] -pub struct IntrinsicArguments { - name: Option, - first_arg: Option>, - second_arg: Option>, -} - -impl<'cx> Dataflow<'cx> for PermissionDataflow { - type State = PermissionTest; - - fn with_interp>( - _interp: &Interp<'cx, C>, - ) -> Self { - Self { - needs_call: vec![], - varid_to_value: FxHashMap::default(), - defid_to_value: FxHashMap::default(), - } - } - - fn transfer_intrinsic>( - &mut self, - _interp: &mut Interp<'cx, C>, - _def: DefId, - _loc: Location, - _block: &'cx BasicBlock, - intrinsic: &'cx Intrinsic, - initial_state: Self::State, - operands: SmallVec<[crate::ir::Operand; 4]>, - ) -> Self::State { - let mut intrinsic_argument = IntrinsicArguments::default(); - if let Intrinsic::ApiCall(name) | Intrinsic::SafeCall(name) | Intrinsic::Authorize(name) = - intrinsic - { - intrinsic_argument.name = Some(name.clone()); - let (first, second) = (operands.get(0), operands.get(1)); - if let Some(operand) = first { - match operand { - Operand::Lit(lit) => { - if &Literal::Undef != lit { - intrinsic_argument.first_arg = Some(vec![lit.to_string()]); - } - } - Operand::Var(var) => { - if let Base::Var(varid) = var.base { - if let Some(value) = _interp.get_value(_def, varid, None) { - intrinsic_argument.first_arg = Some(vec![]); - add_elements_to_intrinsic_struct( - value, - &mut intrinsic_argument.first_arg, - ); - } else if let Some(value) = _interp.body().vars.get(varid) { - if let VarKind::GlobalRef(def) = value { - if let Some(Value::Const(value)) = - _interp.value_manager.defid_to_value.get(def) - { - intrinsic_argument.first_arg = Some(vec![]); - add_elements_to_intrinsic_struct( - &Value::Const(value.clone()), - &mut intrinsic_argument.first_arg, - ); - } - } - } - } - } - } - } - if let Some(Operand::Var(variable)) = second { - if let Base::Var(varid) = variable.base { - if let Some(value) = - _interp.get_value(_def, varid, Some(Projection::Known("method".into()))) - { - self.handle_second_arg(value, &mut intrinsic_argument); - } - } - } - - let mut permissions_within_call: Vec = vec![]; - let intrinsic_func_type = intrinsic_argument.name.unwrap(); - - if intrinsic_argument.first_arg == None { - _interp.permissions.drain(..); - } else { - intrinsic_argument - .first_arg - .iter() - .for_each(|first_arg_vec| { - if let Some(second_arg_vec) = intrinsic_argument.second_arg.clone() { - first_arg_vec.iter().for_each(|first_arg| { - let first_arg = first_arg.replace(&['\"'][..], ""); - second_arg_vec.iter().for_each(|second_arg| { - if intrinsic_func_type == IntrinsicName::RequestConfluence { - let permissions = check_url_for_permissions( - &_interp.confluence_permission_resolver, - &_interp.confluence_regex_map, - translate_request_type(Some(second_arg)), - &first_arg, - ); - permissions_within_call.extend_from_slice(&permissions) - } else if intrinsic_func_type == IntrinsicName::RequestJira { - let permissions = check_url_for_permissions( - &_interp.jira_permission_resolver, - &_interp.jira_regex_map, - translate_request_type(Some(second_arg)), - &first_arg, - ); - permissions_within_call.extend_from_slice(&permissions) - } - }) - }) - } else { - first_arg_vec.iter().for_each(|first_arg| { - let first_arg = first_arg.replace(&['\"'][..], ""); - if intrinsic_func_type == IntrinsicName::RequestConfluence { - let permissions = check_url_for_permissions( - &_interp.confluence_permission_resolver, - &_interp.confluence_regex_map, - RequestType::Get, - &first_arg, - ); - permissions_within_call.extend_from_slice(&permissions) - } else if intrinsic_func_type == IntrinsicName::RequestJira { - let permissions = check_url_for_permissions( - &_interp.jira_permission_resolver, - &_interp.jira_regex_map, - RequestType::Get, - &first_arg, - ); - permissions_within_call.extend_from_slice(&permissions) - } - }) - } - }); - - //println!("permissions within call {permissions_within_call:?}"); - - _interp.permissions = _interp - .permissions - .iter() - .cloned() - .filter(|permissions| !permissions_within_call.contains(permissions)) - .collect_vec(); - } - - // remvove all permissions that it finds - } - initial_state - } - - fn transfer_call>( - &mut self, - interp: &Interp<'cx, C>, - def: DefId, - loc: Location, - _block: &'cx BasicBlock, - callee: &'cx crate::ir::Operand, - initial_state: Self::State, - operands: SmallVec<[crate::ir::Operand; 4]>, - ) -> Self::State { - let Some((callee_def, _body)) = self.resolve_call(interp, callee) else { - return initial_state; - }; - - let callee_name = interp.env().def_name(callee_def); - let caller_name = interp.env().def_name(def); - debug!("Found call to {callee_name} at {def:?} {caller_name}"); - self.needs_call.push((callee_def, operands.into_vec())); - initial_state - } - - fn transfer_block>( - &mut self, - interp: &mut Interp<'cx, C>, - def: DefId, - bb: BasicBlockId, - block: &'cx BasicBlock, - initial_state: Self::State, - arguments: Option>, - ) -> Self::State { - let mut state = initial_state; - - for (stmt, inst) in block.iter().enumerate() { - let loc = Location::new(bb, stmt as u32); - state = self.transfer_inst(interp, def, loc, block, inst, state); - } - state - } - - fn join_term>( - &mut self, - interp: &mut Interp<'cx, C>, - def: DefId, - block: &'cx BasicBlock, - state: Self::State, - worklist: &mut WorkList, - ) { - self.super_join_term(interp, def, block, state, worklist); - for (def, arguments) in self.needs_call.drain(..) { - worklist.push_front_blocks(interp.env(), def, interp.call_all); - } - } -} - -pub struct PermissionChecker { - pub visit: bool, - pub vulns: Vec, -} - -impl PermissionChecker { - pub fn new() -> Self { - Self { - visit: false, - vulns: vec![], - } - } - - pub fn into_vulns(self, permissions: Vec) -> impl IntoIterator { - if permissions.len() > 0 { - return Vec::from([PermissionVuln { - unused_permissions: permissions.clone(), - }]) - .into_iter(); - } - self.vulns.into_iter() - } -} - -impl Default for PermissionChecker { - fn default() -> Self { - PermissionChecker::new() - } -} - -impl fmt::Display for PermissionVuln { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "Authentication vulnerability") - } -} - -#[derive(Debug, PartialEq, Eq, Clone, Copy, PartialOrd, Ord, Default)] -pub enum PermissionTest { - #[default] - Yes, - No, -} - -impl JoinSemiLattice for PermissionTest { - const BOTTOM: Self = Self::Yes; - - #[inline] - fn join_changed(&mut self, other: &Self) -> bool { - let old = mem::replace(self, self.join(other)); - old != *self - } - - #[inline] - fn join(&self, other: &Self) -> Self { - max(*other, *self) - } -} - -#[derive(Debug)] -pub struct PermissionVuln { - unused_permissions: Vec, -} - -impl PermissionVuln { - pub fn new(unused_permissions: Vec) -> PermissionVuln { - PermissionVuln { unused_permissions } - } -} - -pub struct DefintionAnalysisRunner { - pub needs_call: Vec<(DefId, Vec, Vec)>, -} - -impl<'cx> Runner<'cx> for PermissionChecker { - type State = PermissionTest; - type Dataflow = PermissionDataflow; - - fn visit_intrinsic( - &mut self, - interp: &Interp<'cx, Self>, - intrinsic: &'cx Intrinsic, - def: DefId, - state: &Self::State, - operands: Option>, - ) -> ControlFlow<(), Self::State> { - ControlFlow::Continue(*state) - } -} - -impl<'cx> Checker<'cx> for PermissionChecker { - type Vuln = PermissionVuln; -} - -impl IntoVuln for PermissionVuln { - fn into_vuln(self, reporter: &Reporter) -> Vulnerability { - Vulnerability { - check_name: format!("Least-Privilege"), - description: format!( - "Unused permissions listed in manifest file: {:?}", - self.unused_permissions - ), - recommendation: "Remove permissions in manifest file that are not needed.", - proof: format!( - "Unused permissions found in manifest.yml: {:?}", - self.unused_permissions - ), - severity: Severity::Low, - app_key: reporter.app_key().to_string(), - app_name: reporter.app_name().to_string(), - date: reporter.current_date(), - } - } -} - -impl<'cx> Runner<'cx> for DefintionAnalysisRunner { - type State = PermissionTest; - type Dataflow = DefintionAnalysisRunner; - const NAME: &'static str = "DefinitionAnalysis"; - - fn visit_intrinsic( - &mut self, - interp: &Interp<'cx, Self>, - intrinsic: &'cx Intrinsic, - def: DefId, - state: &Self::State, - operands: Option>, - ) -> ControlFlow<(), Self::State> { - ControlFlow::Break(()) - } -} - -impl DefintionAnalysisRunner { - pub fn new() -> Self { - Self { - needs_call: Vec::default(), - } - } -} - -impl<'cx> Dataflow<'cx> for DefintionAnalysisRunner { - type State = PermissionTest; - - fn with_interp>( - _interp: &Interp<'cx, C>, - ) -> Self { - Self { needs_call: vec![] } - } - - fn transfer_intrinsic>( - &mut self, - _interp: &mut Interp<'cx, C>, - _def: DefId, - _loc: Location, - _block: &'cx BasicBlock, - intrinsic: &'cx Intrinsic, - initial_state: Self::State, - operands: SmallVec<[crate::ir::Operand; 4]>, - ) -> Self::State { - initial_state - } - - fn transfer_call>( - &mut self, - interp: &Interp<'cx, C>, - def: DefId, - loc: Location, - _block: &'cx BasicBlock, - callee: &'cx crate::ir::Operand, - initial_state: Self::State, - operands: SmallVec<[crate::ir::Operand; 4]>, - ) -> Self::State { - let Some((callee_def, _body)) = self.resolve_call(interp, callee) else { - return initial_state; - }; - - let callee_name = interp.env().def_name(callee_def); - let caller_name = interp.env().def_name(def); - debug!("Found call to {callee_name} at {def:?} {caller_name}"); - - let mut all_values_to_be_pushed = vec![]; - - for operand in &operands { - match operand.clone() { - Operand::Lit(_) => { - if let Some(lit_value) = convert_operand_to_raw(&operand.clone()) { - all_values_to_be_pushed.push(Value::Const(Const::Literal(lit_value))); - } else { - all_values_to_be_pushed.push(Value::Unknown) - } - } - Operand::Var(var) => match var.base { - Base::Var(varid) => { - if let Some(value) = - interp.get_value(def, varid, var.projections.get(0).cloned()) - { - all_values_to_be_pushed.push(value.clone()); - } else { - all_values_to_be_pushed.push(Value::Unknown) - } - } - _ => all_values_to_be_pushed.push(Value::Unknown), - }, - } - } - self.needs_call - .push((callee_def, operands.into_vec(), all_values_to_be_pushed)); - initial_state - } - - fn transfer_inst>( - &mut self, - interp: &mut Interp<'cx, C>, - def: DefId, - loc: Location, - block: &'cx BasicBlock, - inst: &'cx Inst, - initial_state: Self::State, - ) -> Self::State { - match inst { - Inst::Expr(rvalue) => { - self.transfer_rvalue(interp, def, loc, block, rvalue, initial_state) - } - Inst::Assign(var, rvalue) => { - match var.base { - Base::Var(varid) => match rvalue { - Rvalue::Call(operand, _) => { - if let Operand::Var(variable) = operand { - if let Base::Var(varid) = variable.base { - if let Some(VarKind::GlobalRef(defid)) = - interp.body().vars.get(varid) - { - if let Base::Var(varid_to_assign) = var.base { - interp - .value_manager - .expected_return_values - .insert(*defid, (def, varid_to_assign)); - } - } - } - } - } - Rvalue::Read(operand) => { - if let Rvalue::Read(read) = rvalue { - match read { - Operand::Lit(lit) => { - if let Literal::Str(str) = lit { - if let Base::Var(varid) = var.base { - if let Some(VarKind::GlobalRef(def)) = - interp.body().vars.get(varid) - { - interp.value_manager.defid_to_value.insert( - *def, - Value::Const(Const::Literal( - str.to_string(), - )), - ); - } else if let Some(VarKind::LocalDef(def)) = - interp.body().vars.get(varid) - { - interp.value_manager.defid_to_value.insert( - *def, - Value::Const(Const::Literal( - str.to_string(), - )), - ); - } else if let Some(VarKind::Temp { parent }) = - interp.body().vars.get(varid) - { - if let Some(defid_parent) = parent { - interp.value_manager.defid_to_value.insert( - *defid_parent, - Value::Const(Const::Literal( - str.to_string(), - )), - ); - } - } - } - } - } - Operand::Var(var) => { - // println!("{var}"); - if let Base::Var(varid) = var.base { - let values = interp - .value_manager - .varid_to_value - .get(&(def, varid, None)); - // println!("values: {values:?}") - } - } - } - } - /* should be expanded to include all of the cases ... */ - - self.add_variable(interp, var, &varid, def, rvalue); - } - Rvalue::Template(_) => { - self.add_variable(interp, var, &varid, def, rvalue); - } - _ => {} - }, - _ => {} - } - self.transfer_rvalue(interp, def, loc, block, rvalue, initial_state) - } - } - } - - fn transfer_block>( - &mut self, - interp: &mut Interp<'cx, C>, - def: DefId, - bb: BasicBlockId, - block: &'cx BasicBlock, - initial_state: Self::State, - arguments: Option>, - ) -> Self::State { - let mut state = initial_state; - let mut function_var = interp.curr_body.get().unwrap().vars.clone(); - function_var.pop(); - if let Some(args) = arguments { - let mut args = args.clone(); - args.reverse(); - for (varid, varkind) in function_var.iter_enumerated() { - if let VarKind::Arg(_) = varkind { - if let Some(operand) = args.pop() { - interp.add_value(def, varid, operand.clone(), None); - interp - .body() - .vars - .iter_enumerated() - .for_each(|(varid_alt, varkind_alt)| { - if let (Some(defid_alt), Some(defid)) = ( - get_defid_from_varkind(varkind_alt), - get_defid_from_varkind(varkind), - ) { - if defid == defid_alt && varid_alt != varid { - interp - .value_manager - .varid_to_value - .insert((def, varid_alt, None), operand.clone()); - } - } - }) - } - } - } - } - - for (stmt, inst) in block.iter().enumerate() { - let loc = Location::new(bb, stmt as u32); - state = self.transfer_inst(interp, def, loc, block, inst, state); - } - - for (varid, varkind) in interp.body().vars.clone().iter_enumerated() { - if &VarKind::Ret == varkind { - if let Some((defid_calling_func, varid_calling_func)) = - interp.value_manager.expected_return_values.get(&def) - { - if let Some(value) = interp.get_value(def, varid, None) { - interp.add_value( - *defid_calling_func, - *varid_calling_func, - value.clone(), - None, - ); - } - } - } - } - - state - } - - fn add_variable>( - &mut self, - interp: &mut Interp<'cx, C>, - lval: &Variable, - varid: &VarId, - def: DefId, - rvalue: &Rvalue, - ) { - match rvalue { - Rvalue::Read(operand) => { - // transfer all of the variables - if let Operand::Var(variable) = operand { - if let Base::Var(varid_rval) = variable.base { - interp.value_manager.varid_to_value.clone().iter().for_each( - |((defid, varid_rval_potential, projection), value)| { - if varid_rval_potential == &varid_rval { - interp.add_value(def, *varid, value.clone(), projection.clone()) - } - }, - ); - } - } else { - if let Some(value) = get_prev_value(interp.get_value( - def, - *varid, - lval.projections.get(0).cloned(), - )) { - self.insert_value(interp, operand, lval, varid, def, Some(value)); - } else { - self.insert_value(interp, operand, lval, varid, def, None); - } - } - } - Rvalue::Template(template) => { - let quasis_joined: String = - template.quasis.iter().map(ToString::to_string).collect(); - let mut all_potential_values = vec![String::from("")]; - if template.exprs.len() == 0 { - all_potential_values.push(quasis_joined.clone()); - } else if template.exprs.len() <= 3 { - let mut all_values = vec![String::from("")]; - - for (i, expr) in template.exprs.iter().enumerate() { - if let Some(quasis) = template.quasis.get(i) { - all_values = all_values - .iter() - .map(|value| format!("{value}{quasis}")) - .collect(); - } - let mut new_values = vec![]; - - let values = self.get_str_from_expr(interp, expr, def); - if values.len() > 0 { - for str_value in values { - for value in &all_values { - if let Some(str) = &str_value { - new_values.push(value.clone() + &**str) - } else { - new_values.push(value.clone()) - } - } - } - all_values = new_values - } - } - - if template.quasis.len() > template.exprs.len() { - let last_elem = template.quasis.last().unwrap(); - all_values = all_values - .iter() - .map(|value| format!("{value}{last_elem}")) - .collect(); - } - - all_potential_values = all_values; - } - if all_potential_values.len() > 1 { - let consts = all_potential_values - .clone() - .into_iter() - .map(|value| Const::Literal(value.clone())) - .collect::>(); - let value = Value::Phi(consts); - interp.add_value(def, *varid, value.clone(), None); - } else if all_potential_values.len() == 1 { - interp.add_value( - def, - *varid, - Value::Const(Const::Literal(all_potential_values.get(0).unwrap().clone())), - None, - ); - } - } - Rvalue::Bin(binop, op1, op2) => { - if binop == &BinOp::Add { - let val1 = if let Some(val) = get_str_from_operand(op1) { - Some(Value::Const(Const::Literal(val))) - } else { - self.get_values_from_operand(interp, def, op2) - }; - let val2 = if let Some(val) = get_str_from_operand(op2) { - Some(Value::Const(Const::Literal(val))) - } else { - self.get_values_from_operand(interp, def, op2) - }; - let mut new_vals = vec![]; - if let (Some(val1), Some(val2)) = (val1.clone(), val2.clone()) { - match val1 { - Value::Const(const_val) => { - add_const_to_val_vec(&val2, &const_val, &mut new_vals) - } - Value::Phi(phi_val) => phi_val - .iter() - .for_each(|val1| add_const_to_val_vec(&val2, &val1, &mut new_vals)), - _ => {} - } - interp - .value_manager - .varid_to_value - .insert((def, *varid, None), return_value_from_string(new_vals)); - } else if let Some(val1) = val1 { - interp.add_value(def, *varid, val1, None); - } else if let Some(val2) = val2 { - interp.add_value(def, *varid, val2, None); - } - } - } - _ => {} - } - } - - fn insert_value>( - &mut self, - interp: &mut Interp<'cx, C>, - operand: &Operand, - lval: &Variable, - varid: &VarId, - def: DefId, - prev_values: Option>, - ) { - match operand { - Operand::Lit(lit) => { - if &Literal::Undef != lit { - if let Some(prev_values) = prev_values { - if let Some(lit_value) = convert_operand_to_raw(operand) { - let const_value = Const::Literal(lit_value); - let mut all_values = prev_values.clone(); - all_values.push(const_value); - let value = Value::Phi(all_values); - interp.add_value(def, *varid, value, lval.projections.get(0).cloned()); - } - } else { - if let Some(lit_value) = convert_operand_to_raw(operand) { - let value = Value::Const(Const::Literal(lit_value)); - interp.add_value(def, *varid, value, lval.projections.get(0).cloned()); - } - } - } - } - Operand::Var(var) => { - if let Base::Var(prev_varid) = var.base { - let potential_varkind = &interp.curr_body.get().unwrap().vars.get(prev_varid); - if let Some(VarKind::LocalDef(local_defid)) = potential_varkind { - if let Some(class) = - self.read_class_from_object(interp, local_defid.clone()) - { - if let Some(prev_values) = prev_values { - let const_value = Const::Object(class.clone()); - let mut all_values = prev_values.clone(); - all_values.push(const_value); - let value = Value::Phi(all_values); - interp.add_value( - def, - *varid, - value, - lval.projections.get(0).cloned(), - ); - } else { - let value = Value::Const(Const::Object(class)); - interp.add_value( - def, - *varid, - value, - lval.projections.get(0).cloned(), - ); - } - } - } else { - if let Some(potential_value) = interp.get_value(def, prev_varid, None) { - interp.value_manager.varid_to_value.insert( - (def, *varid, var.projections.get(0).cloned()), - potential_value.clone(), - ); - } - } - } - } - } - } - - fn join_term>( - &mut self, - interp: &mut Interp<'cx, C>, - def: DefId, - block: &'cx BasicBlock, - state: Self::State, - worklist: &mut WorkList, - ) { - self.super_join_term(interp, def, block, state, worklist); - for (def, _arguments, values) in self.needs_call.drain(..) { - worklist.push_front_blocks(interp.env(), def, interp.call_all); - interp.callstack_arguments.push(values.clone()); - } - } + // println!("visitng intrinsic"); - fn get_str_from_expr>( - &self, - _interp: &Interp<'cx, C>, - expr: &Operand, - def: DefId, - ) -> Vec> { - if let Some(str) = get_str_from_operand(expr) { - return vec![Some(str)]; - } else if let Operand::Var(var) = expr { - if let Base::Var(varid) = var.base { - let value = _interp.get_value(def, varid, None); - if let Some(value) = value { - match value { - Value::Const(Const::Literal(str)) => { - return vec![Some(str.clone())]; - } - Value::Phi(phi_val) => { - return phi_val - .iter() - .map(|const_val| { - if let Const::Literal(str) = const_val { - Some(str.clone()) - } else { - None - } - }) - .collect_vec(); - } - _ => {} - } - } + match *intrinsic { + Intrinsic::Authorize(_) => { + debug!("authorize intrinsic found"); + ControlFlow::Continue(AuthorizeState::Yes) + } + Intrinsic::Fetch => ControlFlow::Continue(*state), + Intrinsic::ApiCall(_) if *state == AuthorizeState::No => { + let vuln = AuthZVuln::new(interp.callstack(), interp.env(), interp.entry()); + info!("Found a vuln!"); + self.vulns.push(vuln); + ControlFlow::Continue(*state) } + Intrinsic::ApiCall(_) => ControlFlow::Continue(*state), + Intrinsic::SafeCall(_) => ControlFlow::Continue(*state), + Intrinsic::EnvRead => ControlFlow::Continue(*state), + Intrinsic::StorageRead => ControlFlow::Continue(*state), } - vec![None] } } @@ -1891,6 +988,39 @@ impl PermissionDataflow { fn get_value(&self, defid_block: DefId, varid: VarId) -> Option<&Value> { self.varid_to_value.get(&(defid_block, varid)) } + + fn get_str_from_expr(&self, expr: &Operand, def: DefId) -> Vec> { + if let Some(str) = get_str_from_operand(expr) { + return vec![Some(str)]; + } else if let Operand::Var(var) = expr { + if let Base::Var(varid) = var.base { + let value = self.get_value(def, varid); + if let Some(value) = value { + match value { + Value::Const(const_val) => { + if let Const::Literal(str) = const_val { + return vec![Some(str.clone())]; + } + } + Value::Phi(phi_val) => { + return phi_val + .iter() + .map(|const_val| { + if let Const::Literal(str) = const_val { + Some(str.clone()) + } else { + None + } + }) + .collect_vec(); + } + _ => {} + } + } + } + } + vec![None] + } } impl<'cx> Dataflow<'cx> for PermissionDataflow { @@ -1901,7 +1031,73 @@ impl<'cx> Dataflow<'cx> for PermissionDataflow { ) -> Self { Self { needs_call: vec![], - variables: FxHashMap::default(), + varid_to_value: FxHashMap::default(), + } + } + + fn try_insert>( + &self, + _interp: &Interp<'cx, C>, + _def: DefId, + const_var: Const, + intrinsic_argument: &mut IntrinsicArguments, + ) { + if let Some(val) = self.try_read_mem_from_object(_interp, _def.clone(), const_var.clone()) { + intrinsic_argument.second_arg = Some(vec![]); + add_elements_to_intrinsic_struct(val, &mut intrinsic_argument.second_arg); + } + } + + fn handle_second_arg>( + &self, + _interp: &Interp<'cx, C>, + operand: &Operand, + _def: DefId, + intrinsic_argument: &mut IntrinsicArguments, + ) { + println!("operand {:?}", operand); + + println!(); + println!("all vars {:?}", self.varid_to_value); + println!(); + + if let Some((defid, varid)) = self.get_defid_from_operand(_interp, operand) { + // getting number 29 here + + println!("defid -varid {defid:?}. {varid:?}"); + + println!("varid ~~~ values {:?}", _interp.body().vars.get(varid)); + + if let Some(val) = self.get_value(_def, varid) { + println!("this is the value {val:?}"); + + match val { + Value::Const(const_var) => { + self.try_insert(_interp, _def, const_var.clone(), &mut *intrinsic_argument); + } + Value::Phi(phi_var) => { + phi_var.iter().for_each(|const_val| { + self.try_insert( + _interp, + _def, + const_val.clone(), + &mut *intrinsic_argument, + ); + }); + } + _ => {} + } + } else if let Some(val) = self.def_to_class_property(_interp, _def, defid) { + /* looks like a misclassification here */ + println!("within class to property {val:?} "); + + println!("this is the value {val:?}"); + intrinsic_argument.second_arg = Some(vec![]); + add_elements_to_intrinsic_struct(val, &mut intrinsic_argument.second_arg); + println!("{:?} intrinsic arg second", intrinsic_argument.second_arg); + } + } else { + // println!("found other arg {var:?}") } } @@ -1953,11 +1149,6 @@ impl<'cx> Dataflow<'cx> for PermissionDataflow { }) } }); - - println!("intrinisc arg: {:?}", intrinsic_argument); - - println!("all permissions so far {permissions_within_call:?}"); - _interp .permissions .extend_from_slice(&permissions_within_call); @@ -2046,6 +1237,7 @@ impl<'cx> Dataflow<'cx> for PermissionDataflow { // println!("deifd methods {defid_method:?} {:?}", obj.pub_members); // /* everything here is correct */ + println!("defid to varid {:?}", _interp.body().def_id_to_vars); println!(); println!("deifd to varid {:?}", _interp.body().vars); @@ -2055,7 +1247,6 @@ impl<'cx> Dataflow<'cx> for PermissionDataflow { if let Some(_alt_defid) = defid_method.get(0) { for (varid_new, varkind) in _interp.body().vars.clone().into_iter_enumerated() { - // println!("varid {varid_new} : varkind {varkind:?}"); if let Some(defid) = get_defid_from_varkind(&varkind) { if &&defid == _alt_defid { println!( @@ -2077,7 +1268,6 @@ impl<'cx> Dataflow<'cx> for PermissionDataflow { defid: DefId, ) -> Option<&Value> { if let Some(DefKind::GlobalObj(objid)) = _interp.env().defs.defs.get(defid) { - println!(""); if let Some(class) = _interp.env().defs.classes.get(objid.clone()) { println!("class~~ {class:?}"); @@ -2113,41 +1303,205 @@ impl<'cx> Dataflow<'cx> for PermissionDataflow { Inst::Assign(variable, rvalue) => match variable.base { Base::Var(varid) => { match rvalue { - Rvalue::Read(operand) => { - // case not phi - if self.variables.contains_key(&varid) { - // currently assuming prev value is not phi - let prev_vars = &self.variables[&varid]; - match prev_vars { - Value::Const(prev_var_const) => { - let var_vec = vec![ - prev_var_const.clone(), - Const::Literal(operand.clone()), - ]; - - self.variables - .insert(varid, Value::Phi(Vec::from(var_vec))); - } - _ => {} + /* this puts any return value back in the thing */ + Rvalue::Call(operand, _) => { + if let Some((defid, varid)) = + self.get_defid_from_operand(interp, operand) + { + interp.expecting_value.push_back((defid, (varid, defid))); + } + if let Some((value, defid)) = interp.return_value.clone() { + if defid != def || true { + self.add_value(def, varid, value); } - } else { - self.variables.insert( - varid, - Value::Const(Const::Literal(operand.clone())), - ); } } + Rvalue::Read(_) => { + self.add_variable(interp, &varid, def, rvalue); + } _ => {} } + + self.add_variable(interp, &varid, def, rvalue); } _ => {} }, + } + } + + for (varid, varkind) in interp.body().vars.iter_enumerated() { + match varkind { + VarKind::Ret => { + for (defid, (varid_value, defid_value)) in &interp.expecting_value { + if def == defid.clone() { + if let Some(value) = self.get_value(def, varid) { + self.add_value(def, varid, value.clone()); + } + } + } + if let Some(value) = self.get_value(def, varid) { + interp.return_value = Some((value.clone(), def)); + } + } _ => {} } } + state } + fn add_variable>( + &mut self, + interp: &Interp<'cx, C>, + varid: &VarId, + def: DefId, + rvalue: &Rvalue, + ) { + match rvalue { + Rvalue::Read(operand) => { + if let Some(value) = get_prev_value(self.get_value(def, *varid)) { + self.insert_value2(operand, varid, def, interp, Some(value)); + } else { + self.insert_value2(operand, varid, def, interp, None); + } + } + Rvalue::Template(template) => { + let quasis_joined = template.quasis.join(""); + let mut all_potential_values = vec![quasis_joined]; + for expr in &template.exprs { + if let Some(value) = self.get_value(def, *varid) { + match value { + // TODO: get values from all of the operands and add them if they do have a value + Value::Const(const_value) => { + let mut new_all_values = vec![]; + if let Const::Literal(literal_string) = const_value { + for values in &all_potential_values { + new_all_values.push(values.clone() + literal_string); + } + } + all_potential_values = new_all_values; + } + Value::Phi(phi_value) => { + let mut new_all_values = vec![]; + for constant in phi_value { + if let Const::Literal(literal_string) = constant { + for values in &all_potential_values { + new_all_values.push(values.clone() + literal_string); + } + } + } + all_potential_values = new_all_values; + } + _ => {} + } + } + } + + if all_potential_values.len() > 1 { + let consts = all_potential_values + .into_iter() + .map(|value| Const::Literal(value.clone())) + .collect::>(); + let value = Value::Phi(consts); + self.add_value(def, *varid, value.clone()); + } else if all_potential_values.len() == 1 { + self.add_value( + def, + *varid, + Value::Const(Const::Literal(all_potential_values.get(0).unwrap().clone())), + ); + } + } + Rvalue::Bin(binop, op1, op2) => { + if binop == &BinOp::Add { + let val1 = if let Some(val) = get_str_from_operand(op1) { + Some(Value::Const(Const::Literal(val))) + } else { + self.get_values_from_operand(interp, def, op1).cloned() + }; + let val2 = if let Some(val) = get_str_from_operand(op2) { + Some(Value::Const(Const::Literal(val))) + } else { + self.get_values_from_operand(interp, def, op2).cloned() + }; + let mut new_vals = vec![]; + if let (Some(val1), Some(val2)) = (val1.clone(), val2.clone()) { + match val1 { + Value::Const(const_val) => { + add_const_to_val_vec(&val2, &const_val, &mut new_vals) + } + Value::Phi(phi_val) => phi_val + .iter() + .for_each(|val1| add_const_to_val_vec(&val2, &val1, &mut new_vals)), + _ => {} + } + self.varid_to_value + .insert((def, *varid), return_value_from_string(new_vals)); + } else if let Some(val1) = val1 { + self.add_value(def, *varid, val1); + } else if let Some(val2) = val2 { + self.add_value(def, *varid, val2); + } + } + } + _ => {} + } + } + + fn insert_value2>( + &mut self, + operand: &Operand, + varid: &VarId, + def: DefId, + interp: &Interp<'cx, C>, + prev_values: Option>, + ) { + match operand { + Operand::Lit(_lit) => { + if let Some(prev_values) = prev_values { + if let Some(lit_value) = convert_operand_to_raw(operand) { + let const_value = Const::Literal(lit_value); + let mut all_values = prev_values.clone(); + all_values.push(const_value); + let value = Value::Phi(all_values); + self.add_value(def, *varid, value); + } + } else { + if let Some(lit_value) = convert_operand_to_raw(operand) { + let value = Value::Const(Const::Literal(lit_value)); + self.add_value(def, *varid, value); + } + } + } + Operand::Var(var) => { + if let Base::Var(prev_varid) = var.base { + let potential_varkind = &interp.curr_body.get().unwrap().vars.get(prev_varid); + if let Some(VarKind::LocalDef(local_defid)) = potential_varkind { + if let Some(class) = + self.read_class_from_object(interp, local_defid.clone()) + { + if let Some(prev_values) = prev_values { + let const_value = Const::Object(class.clone()); + let mut all_values = prev_values.clone(); + all_values.push(const_value); + let value = Value::Phi(all_values); + self.add_value(def, *varid, value); + } else { + let value = Value::Const(Const::Object(class)); + self.add_value(def, *varid, value); + } + } + } else { + if let Some(potential_value) = self.get_value(def, prev_varid) { + self.varid_to_value + .insert((def, *varid), potential_value.clone()); + } + } + } + } + } + } + fn join_term>( &mut self, interp: &mut Interp<'cx, C>, @@ -2224,15 +1578,6 @@ fn convert_lit_to_raw(lit: &Literal) -> Option { } } -fn find_member_of_obj(member: &str, obj: &Class) -> Option { - for (mem, memdefid) in &obj.pub_members { - if mem == member { - return Some(*memdefid); - } - } - None -} - fn get_str_from_operand(operand: &Operand) -> Option { if let Operand::Lit(lit) = operand { if let Literal::Str(str) = lit { From bd599d3260794581184a5926568962c502b9a17e Mon Sep 17 00:00:00 2001 From: James Gersbach Date: Wed, 12 Jul 2023 13:26:17 -0700 Subject: [PATCH 427/517] wip --- .gitignore | 3 --- crates/forge_analyzer/src/checkers.rs | 27 ++++++++++++++------------- 2 files changed, 14 insertions(+), 16 deletions(-) diff --git a/.gitignore b/.gitignore index f2575964..831f1db6 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,2 @@ /target -<<<<<<< HEAD -======= /test-apps ->>>>>>> d92122b (various updates) diff --git a/crates/forge_analyzer/src/checkers.rs b/crates/forge_analyzer/src/checkers.rs index e7cac632..e0964cc0 100644 --- a/crates/forge_analyzer/src/checkers.rs +++ b/crates/forge_analyzer/src/checkers.rs @@ -971,9 +971,11 @@ impl PermissionDataflow { intrinsic_argument.first_arg = Some(vec![lit.to_string()]); } Operand::Var(var) => { + // println!("varid_to_value {:?}", self.varid_to_value); if let Base::Var(varid) = var.base { if let Some(value) = self.get_value(_def, varid) { intrinsic_argument.first_arg = Some(vec![]); + // println!("value {value:?}"); add_elements_to_intrinsic_struct(value, &mut intrinsic_argument.first_arg); } } @@ -1112,16 +1114,19 @@ impl<'cx> Dataflow<'cx> for PermissionDataflow { operands: SmallVec<[crate::ir::Operand; 4]>, ) -> Self::State { let mut intrinsic_argument = IntrinsicArguments::default(); - println!("transferring intrinsic {:?}", _interp.env().def_name(_def)); + // println!("transferring intrinsic {:?}", _interp.env().def_name(_def)); if let Intrinsic::ApiCall(name) | Intrinsic::SafeCall(name) | Intrinsic::Authorize(name) = intrinsic { intrinsic_argument.name = Some(name.clone()); let (first, second) = (operands.get(0), operands.get(1)); if let Some(operand) = first { + // println!("operand fro intrinsic {operand:?}"); + self.handle_first_arg(operand, _def, &mut intrinsic_argument); } if let Some(operand) = second { + println!("operand: {operand:?}"); self.handle_second_arg(_interp, operand, _def, &mut intrinsic_argument); } let mut permissions_within_call: Vec = vec![]; @@ -1149,11 +1154,15 @@ impl<'cx> Dataflow<'cx> for PermissionDataflow { }) } }); + + println!("intrinsic args {:?}", intrinsic_argument); _interp .permissions .extend_from_slice(&permissions_within_call); } + println!("all permissions: {:?}", _interp.permissions); + initial_state } @@ -1234,17 +1243,6 @@ impl<'cx> Dataflow<'cx> for PermissionDataflow { .filter(|(mem, _)| mem == "method") .map(|(_, defid)| defid) .collect_vec(); - - // println!("deifd methods {defid_method:?} {:?}", obj.pub_members); - // /* everything here is correct */ - - println!("defid to varid {:?}", _interp.body().def_id_to_vars); - println!(); - println!("deifd to varid {:?}", _interp.body().vars); - println!(); - println!("projections"); - println!(); - if let Some(_alt_defid) = defid_method.get(0) { for (varid_new, varkind) in _interp.body().vars.clone().into_iter_enumerated() { if let Some(defid) = get_defid_from_varkind(&varkind) { @@ -1316,7 +1314,10 @@ impl<'cx> Dataflow<'cx> for PermissionDataflow { } } } - Rvalue::Read(_) => { + Rvalue::Read(read_value) => { + self.add_variable(interp, &varid, def, rvalue); + } + Rvalue::Template(tpl) => { self.add_variable(interp, &varid, def, rvalue); } _ => {} From cbe7ea9f6f4577c80a7bfe7d67ddc4a09b9646f4 Mon Sep 17 00:00:00 2001 From: James Gersbach Date: Wed, 12 Jul 2023 14:26:16 -0700 Subject: [PATCH 428/517] fix to not reading the second argument --- crates/forge_analyzer/src/definitions.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/forge_analyzer/src/definitions.rs b/crates/forge_analyzer/src/definitions.rs index 544ba197..dc46fc39 100644 --- a/crates/forge_analyzer/src/definitions.rs +++ b/crates/forge_analyzer/src/definitions.rs @@ -1496,7 +1496,7 @@ impl<'cx> FunctionAnalyzer<'cx> { let lowered_var = self.body.coerce_to_lval( self.block, lowered_value.clone(), - None, + Some(next_key), ); let rval = Rvalue::Read(lowered_value); From c2967aef21bf6b2561f5785d180b43145086b530 Mon Sep 17 00:00:00 2001 From: James Gersbach Date: Thu, 13 Jul 2023 19:43:55 -0700 Subject: [PATCH 429/517] wip --- crates/forge_analyzer/src/checkers.rs | 46 +++++++++++++++++++-------- 1 file changed, 33 insertions(+), 13 deletions(-) diff --git a/crates/forge_analyzer/src/checkers.rs b/crates/forge_analyzer/src/checkers.rs index e0964cc0..cadc72cc 100644 --- a/crates/forge_analyzer/src/checkers.rs +++ b/crates/forge_analyzer/src/checkers.rs @@ -282,7 +282,7 @@ impl<'cx> Dataflow<'cx> for AuthorizeDataflow { worklist: &mut WorkList, ) { self.super_join_term(interp, def, block, state, worklist); - //println!("worklist - adding function {}", interp.env().def_name(def)); + println!("worklist - adding function {}", interp.env().def_name(def)); for def in self.needs_call.drain(..) { worklist.push_front_blocks(interp.env(), def, interp.call_all); } @@ -975,7 +975,7 @@ impl PermissionDataflow { if let Base::Var(varid) = var.base { if let Some(value) = self.get_value(_def, varid) { intrinsic_argument.first_arg = Some(vec![]); - // println!("value {value:?}"); + println!("value guava {value:?} {varid:?}"); add_elements_to_intrinsic_struct(value, &mut intrinsic_argument.first_arg); } } @@ -984,11 +984,14 @@ impl PermissionDataflow { } fn add_value(&mut self, defid_block: DefId, varid: VarId, value: Value) { - self.varid_to_value.insert((defid_block, varid), value); + print!("defid_block {:?}", defid_block); + println!("{:?}", self.varid_to_value); + self.varid_to_value.insert(varid, value); + println!("{:?}", self.varid_to_value); } fn get_value(&self, defid_block: DefId, varid: VarId) -> Option<&Value> { - self.varid_to_value.get(&(defid_block, varid)) + self.varid_to_value.get(&varid) } fn get_str_from_expr(&self, expr: &Operand, def: DefId) -> Vec> { @@ -1113,6 +1116,8 @@ impl<'cx> Dataflow<'cx> for PermissionDataflow { initial_state: Self::State, operands: SmallVec<[crate::ir::Operand; 4]>, ) -> Self::State { + println!("varid to value {:?}", self.varid_to_value); + let mut intrinsic_argument = IntrinsicArguments::default(); // println!("transferring intrinsic {:?}", _interp.env().def_name(_def)); if let Intrinsic::ApiCall(name) | Intrinsic::SafeCall(name) | Intrinsic::Authorize(name) = @@ -1303,27 +1308,41 @@ impl<'cx> Dataflow<'cx> for PermissionDataflow { match rvalue { /* this puts any return value back in the thing */ Rvalue::Call(operand, _) => { + println!("Expecting return from call {varid:?}"); /* Issue here where the variable is not beign assigned correctly to the proper variable */ if let Some((defid, varid)) = self.get_defid_from_operand(interp, operand) { interp.expecting_value.push_back((defid, (varid, defid))); } - if let Some((value, defid)) = interp.return_value.clone() { - if defid != def || true { - self.add_value(def, varid, value); + // if let Some((value, defid)) = interp.return_value.clone() { + + // //println!("expecitng val {:?}", interp.expecting_value); + + // if defid != def { + // //println!("return value being returned {def:?}, {varid:?}, {value:?}"); + // //self.add_value(def, varid, value); + // } + // } + if let Some((defid, _)) = + self.get_defid_from_operand(interp, operand) + { + if let Some(return_value) = interp.return_value_alt.get(&defid) + { + println!("kiwi return value {return_value:?} {varid:?}"); + self.add_value(def, varid, return_value.clone()); } } } - Rvalue::Read(read_value) => { + Rvalue::Read(_) => { self.add_variable(interp, &varid, def, rvalue); } - Rvalue::Template(tpl) => { + Rvalue::Template(_) => { self.add_variable(interp, &varid, def, rvalue); } _ => {} } - self.add_variable(interp, &varid, def, rvalue); + //self.add_variable(interp, &varid, def, rvalue); } _ => {} }, @@ -1341,7 +1360,9 @@ impl<'cx> Dataflow<'cx> for PermissionDataflow { } } if let Some(value) = self.get_value(def, varid) { + println!("return value {value:?}"); interp.return_value = Some((value.clone(), def)); + interp.return_value_alt.insert(def, value.clone()); } } _ => {} @@ -1437,7 +1458,7 @@ impl<'cx> Dataflow<'cx> for PermissionDataflow { _ => {} } self.varid_to_value - .insert((def, *varid), return_value_from_string(new_vals)); + .insert(*varid, return_value_from_string(new_vals)); } else if let Some(val1) = val1 { self.add_value(def, *varid, val1); } else if let Some(val2) = val2 { @@ -1494,8 +1515,7 @@ impl<'cx> Dataflow<'cx> for PermissionDataflow { } } else { if let Some(potential_value) = self.get_value(def, prev_varid) { - self.varid_to_value - .insert((def, *varid), potential_value.clone()); + self.varid_to_value.insert(*varid, potential_value.clone()); } } } From 77899bd896d4ed62d1fb618088f8d3c18f8cdc35 Mon Sep 17 00:00:00 2001 From: James Gersbach Date: Fri, 14 Jul 2023 08:52:58 -0700 Subject: [PATCH 430/517] fix to unlimited looping beteween visit blocks --- crates/forge_analyzer/src/checkers.rs | 18 ++++++++++++++---- crates/forge_analyzer/src/interp.rs | 10 +++++----- 2 files changed, 19 insertions(+), 9 deletions(-) diff --git a/crates/forge_analyzer/src/checkers.rs b/crates/forge_analyzer/src/checkers.rs index cadc72cc..b0956fdc 100644 --- a/crates/forge_analyzer/src/checkers.rs +++ b/crates/forge_analyzer/src/checkers.rs @@ -716,7 +716,7 @@ impl<'cx> Runner<'cx> for SecretChecker { let vuln = AuthZVuln::new(interp.callstack(), interp.env(), interp.entry()); info!("Found a vuln!"); self.vulns.push(vuln); - ControlFlow::Continue(*state) + ControlFlow::Break(()) } Intrinsic::ApiCall(_) => ControlFlow::Continue(*state), Intrinsic::SafeCall(_) => ControlFlow::Continue(*state), @@ -827,12 +827,16 @@ impl<'cx> Dataflow<'cx> for AuthenticateDataflow { } pub struct AuthenticateChecker { + visit: bool, vulns: Vec, } impl AuthenticateChecker { pub fn new() -> Self { - Self { vulns: vec![] } + Self { + visit: false, + vulns: vec![], + } } pub fn into_vulns(self) -> impl IntoIterator { @@ -868,7 +872,7 @@ impl<'cx> Checker<'cx> for AuthenticateChecker { let vuln = AuthNVuln::new(interp.callstack(), interp.env(), interp.entry()); info!("Found a vuln!"); self.vulns.push(vuln); - ControlFlow::Continue(*state) + ControlFlow::Break(()) } Intrinsic::ApiCall(_) => ControlFlow::Continue(*state), Intrinsic::SafeCall(_) => ControlFlow::Continue(*state), @@ -1652,6 +1656,7 @@ fn return_value_from_string(values: Vec) -> Value { } pub struct PermissionChecker { + pub visit: bool, pub vulns: Vec, pub declared_permissions: HashSet, pub used_permissions: HashSet, @@ -1660,6 +1665,7 @@ pub struct PermissionChecker { impl PermissionChecker { pub fn new(declared_permissions: HashSet) -> Self { Self { + visit: false, vulns: vec![], declared_permissions, used_permissions: HashSet::default(), @@ -1714,6 +1720,10 @@ impl<'cx> Checker<'cx> for PermissionChecker { type Dataflow = PermissionDataflow; type Vuln = PermissionVuln; + fn visit(&mut self) -> bool { + false + } + fn visit_intrinsic( &mut self, interp: &Interp<'cx, Self>, @@ -1721,7 +1731,7 @@ impl<'cx> Checker<'cx> for PermissionChecker { state: &Self::State, operands: Option>, ) -> ControlFlow<(), Self::State> { - //println!("visiting _intrinsic"); + println!("visiting _intrinsic"); for permission in &interp.permissions { self.declared_permissions.remove(permission); self.used_permissions.insert(permission.clone()); diff --git a/crates/forge_analyzer/src/interp.rs b/crates/forge_analyzer/src/interp.rs index 6857d345..01c01494 100644 --- a/crates/forge_analyzer/src/interp.rs +++ b/crates/forge_analyzer/src/interp.rs @@ -457,10 +457,8 @@ pub trait Runner<'cx>: Sized { let mut curr_state = interp.block_state(def, id).join(curr_state); for stmt in block { match stmt { - Inst::Expr(r) => curr_state = self.visit_rvalue(interp, r, def, id, &curr_state)?, - Inst::Assign(_, r) => { - curr_state = self.visit_rvalue(interp, r, def, id, &curr_state)? - } + Inst::Expr(r) => curr_state = self.visit_rvalue(interp, r, id, &curr_state)?, + Inst::Assign(_, r) => curr_state = self.visit_rvalue(interp, r, id, &curr_state)?, } } match block.successors() { @@ -864,7 +862,9 @@ impl<'cx, C: Runner<'cx>> Interp<'cx, C> { })?; self.set_body(body); self.run(resolved_def); - checker.visit_body(self, resolved_def, body, &C::State::BOTTOM); + if checker.visit() { + checker.visit_body(self, resolved_def, body, &C::State::BOTTOM); + } Ok(()) } From 55549beb3c1218eeac8f40aa4a88ef788c40668e Mon Sep 17 00:00:00 2001 From: James Gersbach Date: Fri, 14 Jul 2023 08:53:23 -0700 Subject: [PATCH 431/517] fix to unlimited looping beteween visit blocks --- crates/forge_analyzer/src/checkers.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/forge_analyzer/src/checkers.rs b/crates/forge_analyzer/src/checkers.rs index b0956fdc..caa58835 100644 --- a/crates/forge_analyzer/src/checkers.rs +++ b/crates/forge_analyzer/src/checkers.rs @@ -1721,7 +1721,7 @@ impl<'cx> Checker<'cx> for PermissionChecker { type Vuln = PermissionVuln; fn visit(&mut self) -> bool { - false + self.visit } fn visit_intrinsic( From b47deca5d6877d175aeff6f76e7b1e41837f85d4 Mon Sep 17 00:00:00 2001 From: James Gersbach Date: Sat, 15 Jul 2023 21:06:05 -0700 Subject: [PATCH 432/517] cleaning up --- crates/forge_analyzer/src/checkers.rs | 22 ---------------------- crates/fsrt/src/main.rs | 1 - 2 files changed, 23 deletions(-) diff --git a/crates/forge_analyzer/src/checkers.rs b/crates/forge_analyzer/src/checkers.rs index caa58835..18ebe844 100644 --- a/crates/forge_analyzer/src/checkers.rs +++ b/crates/forge_analyzer/src/checkers.rs @@ -282,7 +282,6 @@ impl<'cx> Dataflow<'cx> for AuthorizeDataflow { worklist: &mut WorkList, ) { self.super_join_term(interp, def, block, state, worklist); - println!("worklist - adding function {}", interp.env().def_name(def)); for def in self.needs_call.drain(..) { worklist.push_front_blocks(interp.env(), def, interp.call_all); } @@ -704,8 +703,6 @@ impl<'cx> Runner<'cx> for SecretChecker { state: &Self::State, operands: Option>, ) -> ControlFlow<(), Self::State> { - // println!("visitng intrinsic"); - match *intrinsic { Intrinsic::Authorize(_) => { debug!("authorize intrinsic found"); @@ -975,11 +972,9 @@ impl PermissionDataflow { intrinsic_argument.first_arg = Some(vec![lit.to_string()]); } Operand::Var(var) => { - // println!("varid_to_value {:?}", self.varid_to_value); if let Base::Var(varid) = var.base { if let Some(value) = self.get_value(_def, varid) { intrinsic_argument.first_arg = Some(vec![]); - println!("value guava {value:?} {varid:?}"); add_elements_to_intrinsic_struct(value, &mut intrinsic_argument.first_arg); } } @@ -988,10 +983,7 @@ impl PermissionDataflow { } fn add_value(&mut self, defid_block: DefId, varid: VarId, value: Value) { - print!("defid_block {:?}", defid_block); - println!("{:?}", self.varid_to_value); self.varid_to_value.insert(varid, value); - println!("{:?}", self.varid_to_value); } fn get_value(&self, defid_block: DefId, varid: VarId) -> Option<&Value> { @@ -1064,12 +1056,6 @@ impl<'cx> Dataflow<'cx> for PermissionDataflow { _def: DefId, intrinsic_argument: &mut IntrinsicArguments, ) { - println!("operand {:?}", operand); - - println!(); - println!("all vars {:?}", self.varid_to_value); - println!(); - if let Some((defid, varid)) = self.get_defid_from_operand(_interp, operand) { // getting number 29 here @@ -1120,22 +1106,16 @@ impl<'cx> Dataflow<'cx> for PermissionDataflow { initial_state: Self::State, operands: SmallVec<[crate::ir::Operand; 4]>, ) -> Self::State { - println!("varid to value {:?}", self.varid_to_value); - let mut intrinsic_argument = IntrinsicArguments::default(); - // println!("transferring intrinsic {:?}", _interp.env().def_name(_def)); if let Intrinsic::ApiCall(name) | Intrinsic::SafeCall(name) | Intrinsic::Authorize(name) = intrinsic { intrinsic_argument.name = Some(name.clone()); let (first, second) = (operands.get(0), operands.get(1)); if let Some(operand) = first { - // println!("operand fro intrinsic {operand:?}"); - self.handle_first_arg(operand, _def, &mut intrinsic_argument); } if let Some(operand) = second { - println!("operand: {operand:?}"); self.handle_second_arg(_interp, operand, _def, &mut intrinsic_argument); } let mut permissions_within_call: Vec = vec![]; @@ -1364,7 +1344,6 @@ impl<'cx> Dataflow<'cx> for PermissionDataflow { } } if let Some(value) = self.get_value(def, varid) { - println!("return value {value:?}"); interp.return_value = Some((value.clone(), def)); interp.return_value_alt.insert(def, value.clone()); } @@ -1731,7 +1710,6 @@ impl<'cx> Checker<'cx> for PermissionChecker { state: &Self::State, operands: Option>, ) -> ControlFlow<(), Self::State> { - println!("visiting _intrinsic"); for permission in &interp.permissions { self.declared_permissions.remove(permission); self.used_permissions.insert(permission.clone()); diff --git a/crates/fsrt/src/main.rs b/crates/fsrt/src/main.rs index 51073f71..5a57ff5c 100644 --- a/crates/fsrt/src/main.rs +++ b/crates/fsrt/src/main.rs @@ -242,7 +242,6 @@ fn scan_directory(dir: PathBuf, function: Option<&str>, opts: &Args) -> Result<( return Ok(()); } proj.add_funcs(funcrefs); - resolve_calls(&mut proj.ctx); if let Some(func) = opts.dump_ir.as_ref() { let mut lock = std::io::stdout().lock(); From fb56c91cdd71cd2211aa6f6bb044c422b406df24 Mon Sep 17 00:00:00 2001 From: James Gersbach Date: Tue, 18 Jul 2023 08:36:28 -0700 Subject: [PATCH 433/517] wip --- crates/forge_analyzer/src/analyzer.rs | 8 ++++-- crates/forge_analyzer/src/checkers.rs | 3 +-- crates/forge_analyzer/src/definitions.rs | 28 +++++++++++++++++++ crates/forge_analyzer/src/exports.rs | 34 ++++++++++++++++++++++-- 4 files changed, 67 insertions(+), 6 deletions(-) diff --git a/crates/forge_analyzer/src/analyzer.rs b/crates/forge_analyzer/src/analyzer.rs index 3102f6bc..6eb2c60d 100644 --- a/crates/forge_analyzer/src/analyzer.rs +++ b/crates/forge_analyzer/src/analyzer.rs @@ -6,8 +6,8 @@ use std::{fmt, mem}; use crate::ctx::{BasicBlockId, FunctionMeta, IrStmt, ModuleCtx, TerminatorKind, STARTING_BLOCK}; use crate::lattice::MeetSemiLattice; use swc_core::ecma::ast::{ - ArrowExpr, BindingIdent, CallExpr, Callee, Expr, ExprOrSpread, FnDecl, FnExpr, Id, IfStmt, - JSXElementName, JSXOpeningElement, MemberExpr, MemberProp, Pat, Stmt, Str, ThrowStmt, + ArrowExpr, BindingIdent, CallExpr, Callee, Expr, ExprOrSpread, ExprStmt, FnDecl, FnExpr, Id, + IfStmt, JSXElementName, JSXOpeningElement, MemberExpr, MemberProp, Pat, Stmt, Str, ThrowStmt, TplElement, VarDeclarator, }; use swc_core::ecma::visit::{noop_visit_type, Visit, VisitWith}; @@ -285,6 +285,10 @@ impl Visit for FunctionCollector<'_> { n.function.visit_children_with(self); } + fn visit_expr_stmt(&mut self, n: &ExprStmt) { + println!("visitng expr sttm 2"); + } + fn visit_var_declarator(&mut self, n: &VarDeclarator) { if let VarDeclarator { name: Pat::Ident(BindingIdent { id, .. }), diff --git a/crates/forge_analyzer/src/checkers.rs b/crates/forge_analyzer/src/checkers.rs index 18ebe844..dd25d2ca 100644 --- a/crates/forge_analyzer/src/checkers.rs +++ b/crates/forge_analyzer/src/checkers.rs @@ -1149,9 +1149,7 @@ impl<'cx> Dataflow<'cx> for PermissionDataflow { .permissions .extend_from_slice(&permissions_within_call); } - println!("all permissions: {:?}", _interp.permissions); - initial_state } @@ -1653,6 +1651,7 @@ impl PermissionChecker { pub fn into_vulns(self) -> impl IntoIterator { if self.declared_permissions.len() > 0 { + println!("here in wrong case"); return Vec::from([PermissionVuln { unused_permissions: self.declared_permissions.clone(), }]) diff --git a/crates/forge_analyzer/src/definitions.rs b/crates/forge_analyzer/src/definitions.rs index dc46fc39..c665b154 100644 --- a/crates/forge_analyzer/src/definitions.rs +++ b/crates/forge_analyzer/src/definitions.rs @@ -40,6 +40,30 @@ use swc_core::{ use tracing::{debug, field::debug, info, instrument, warn}; use typed_index_collections::{TiSlice, TiVec}; +/** + * ident`, `block`, `stmt`, `expr`, `pat`, `ty`, `lifetime`, `literal`, `path`, `meta`, `tt`, `item` and `vis + */ +macro_rules! unwrap_or { + ($c:vis, $e:expr, $or_do_what:expr) => { + if let c(d) = $e { + d + } else { + $or_do_what + } + }; +} + +macro_rules! add { + // macth like arm for macro + ($a:expr,$b:expr) => { + // macro expand to this code + { + // $a and $b will be templated using the value/variable provided to macro + $a + $b + } + }; +} + use crate::{ ctx::ModId, ir::{ @@ -142,6 +166,8 @@ pub fn run_resolver( exports: vec![], default: None, }; + println!(); + //println!("module ---> {:#?}", module.body); module.visit_children_with(&mut export_collector); let mod_id = environment .exports @@ -3087,6 +3113,7 @@ impl Visit for GlobalCollector<'_> { impl Visit for ExportCollector<'_> { noop_visit_type!(); fn visit_export_decl(&mut self, n: &ExportDecl) { + //println!("visit export decl {n:#?}"); match &n.decl { Decl::Class(ClassDecl { ident, .. }) => { let ident = ident.to_id(); @@ -3094,6 +3121,7 @@ impl Visit for ExportCollector<'_> { } Decl::Fn(FnDecl { ident, .. }) => { let ident = ident.to_id(); + //println!("FNDECL = {ident:?}"); self.add_export(DefRes::Function(()), ident); } Decl::Var(vardecls) => { diff --git a/crates/forge_analyzer/src/exports.rs b/crates/forge_analyzer/src/exports.rs index 826bc46c..5b98a441 100644 --- a/crates/forge_analyzer/src/exports.rs +++ b/crates/forge_analyzer/src/exports.rs @@ -2,8 +2,9 @@ use crate::Exports; use forge_utils::FxHashMap; use swc_core::ecma::{ ast::{ - BindingIdent, Decl, ExportAll, ExportDecl, ExportDefaultDecl, ExportDefaultExpr, FnDecl, - ModuleDecl, ModuleItem, Pat, VarDecl, VarDeclarator, + AssignExpr, BindingIdent, Decl, ExportAll, ExportDecl, ExportDefaultDecl, + ExportDefaultExpr, Expr, FnDecl, MemberProp, ModuleDecl, ModuleExportName, ModuleItem, Pat, + PatOrExpr, VarDecl, VarDeclarator, }, visit::{noop_visit_type, Visit, VisitWith}, }; @@ -24,6 +25,9 @@ impl ExportCollector { impl Visit for ExportCollector { noop_visit_type!(); fn visit_export_decl(&mut self, n: &ExportDecl) { + //println!("export collector {n:?}"); + println!(); + match &n.decl { Decl::Class(_) => {} Decl::Fn(FnDecl { ident, .. }) => { @@ -33,10 +37,12 @@ impl Visit for ExportCollector { let ident = ident.to_id(); debug!(?ident, "function export"); // TODO: redo export layout to avoid clones + println!("exported ident {ident:?}"); export_ids.add_named(ident.clone(), ident); } Decl::Var(vardecls) => { let VarDecl { decls, .. } = &**vardecls; + //println!("exported vardecls {vardecls:?}"); decls.iter().for_each(|var| self.visit_var_declarator(var)); } Decl::Using(_) @@ -47,6 +53,30 @@ impl Visit for ExportCollector { }; } + fn visit_module_export_name(&mut self, n: &ModuleExportName) { + //println!("visiting module_export name"); + //println!("{n:?}"); + } + + fn visit_assign_expr(&mut self, n: &AssignExpr) { + // clean this before pushing + if let PatOrExpr::Expr(expr) = &n.left { + println!("here found moudle.exports"); + if let Expr::Member(mem_expr) = &**expr { + if let Expr::Ident(ident) = &*mem_expr.obj { + println!("here found moudle.exports"); + if ident.sym.to_string() == "module" { + if let MemberProp::Ident(ident_property) = &mem_expr.prop { + if ident_property.sym.to_string() == "export" { + println!("here found moudle.exports") + } + } + } + } + } + } + } + fn visit_var_declarator(&mut self, n: &VarDeclarator) { if let Pat::Ident(BindingIdent { id, .. }) = &n.name { let export_ids = self From 9192cdccf57fa21697743b329f196bf83befbac9 Mon Sep 17 00:00:00 2001 From: James Gersbach Date: Wed, 19 Jul 2023 15:33:35 -0700 Subject: [PATCH 434/517] fix to the issue of not resolving default exports --- crates/forge_analyzer/src/analyzer.rs | 4 +- crates/forge_analyzer/src/checkers.rs | 70 +++++++++++++++++++++++- crates/forge_analyzer/src/definitions.rs | 61 ++++++++++++++++----- crates/forge_analyzer/src/exports.rs | 29 ---------- crates/forge_analyzer/src/interp.rs | 2 - 5 files changed, 116 insertions(+), 50 deletions(-) diff --git a/crates/forge_analyzer/src/analyzer.rs b/crates/forge_analyzer/src/analyzer.rs index 6eb2c60d..0aec70b0 100644 --- a/crates/forge_analyzer/src/analyzer.rs +++ b/crates/forge_analyzer/src/analyzer.rs @@ -285,9 +285,7 @@ impl Visit for FunctionCollector<'_> { n.function.visit_children_with(self); } - fn visit_expr_stmt(&mut self, n: &ExprStmt) { - println!("visitng expr sttm 2"); - } + // fn visit_asign_expr() fn visit_var_declarator(&mut self, n: &VarDeclarator) { if let VarDeclarator { diff --git a/crates/forge_analyzer/src/checkers.rs b/crates/forge_analyzer/src/checkers.rs index dd25d2ca..3d76e739 100644 --- a/crates/forge_analyzer/src/checkers.rs +++ b/crates/forge_analyzer/src/checkers.rs @@ -355,6 +355,73 @@ impl<'cx> Runner<'cx> for PrototypePollutionChecker { } } +pub struct PrototypePollutionChecker; + +#[derive(Debug, Clone, Copy, PartialOrd, Ord, PartialEq, Eq, Default)] +enum PrototypePollutionState { + Yes, + #[default] + No, +} + +impl JoinSemiLattice for PrototypePollutionState { + const BOTTOM: Self = Self::No; + fn join(&self, other: &Self) -> Self { + match (self, other) { + (Self::Yes, _) | (_, Self::Yes) => Self::Yes, + _ => Self::No, + } + } + + fn join_changed(&mut self, other: &Self) -> bool { + let old = mem::replace(self, self.join(other)); + old != *self + } +} + +impl<'cx> Runner<'cx> for PrototypePollutionChecker { + type State = Vec; + + type Dataflow = TaintDataflow; + + const NAME: &'static str = "PrototypePollution"; + + fn visit_intrinsic( + &mut self, + interp: &Interp<'cx, Self>, + intrinsic: &'cx Intrinsic, + def: DefId, + state: &Self::State, + operands: Option>, + ) -> ControlFlow<(), Self::State> { + ControlFlow::Continue(state.clone()) + } + fn visit_block( + &mut self, + interp: &Interp<'cx, Self>, + def: DefId, + id: BasicBlockId, + block: &'cx BasicBlock, + curr_state: &Self::State, + ) -> ControlFlow<(), Self::State> { + for inst in &block.insts { + if let Inst::Assign(l, r) = inst { + if let [Projection::Computed(Base::Var(fst)), Projection::Computed(Base::Var(snd)), ..] = + *l.projections + { + if curr_state.get(fst.0 as usize).copied() == Some(Taint::Yes) + && curr_state.get(snd.0 as usize).copied() == Some(Taint::Yes) + { + info!("Prototype pollution vuln detected"); + return ControlFlow::Break(()); + } + } + } + } + ControlFlow::Continue(curr_state.clone()) + } +} + pub struct AuthZChecker { visit: bool, vulns: Vec, @@ -1144,12 +1211,10 @@ impl<'cx> Dataflow<'cx> for PermissionDataflow { } }); - println!("intrinsic args {:?}", intrinsic_argument); _interp .permissions .extend_from_slice(&permissions_within_call); } - println!("all permissions: {:?}", _interp.permissions); initial_state } @@ -1651,7 +1716,6 @@ impl PermissionChecker { pub fn into_vulns(self) -> impl IntoIterator { if self.declared_permissions.len() > 0 { - println!("here in wrong case"); return Vec::from([PermissionVuln { unused_permissions: self.declared_permissions.clone(), }]) diff --git a/crates/forge_analyzer/src/definitions.rs b/crates/forge_analyzer/src/definitions.rs index c665b154..02dbdf01 100644 --- a/crates/forge_analyzer/src/definitions.rs +++ b/crates/forge_analyzer/src/definitions.rs @@ -166,8 +166,6 @@ pub fn run_resolver( exports: vec![], default: None, }; - println!(); - //println!("module ---> {:#?}", module.body); module.visit_children_with(&mut export_collector); let mod_id = environment .exports @@ -929,9 +927,6 @@ impl<'cx> FunctionAnalyzer<'cx> { fn is_storage_read(prop: &JsWord) -> bool { *prop == *"get" || *prop == *"getSecret" || *prop == *"query" } - - //println!("as intrinsic "); - match *callee { [PropPath::Unknown((ref name, ..))] if *name == *"fetch" => Some(Intrinsic::Fetch), [PropPath::Def(def), ref authn @ .., PropPath::Static(ref last)] @@ -944,7 +939,6 @@ impl<'cx> FunctionAnalyzer<'cx> { } else { IntrinsicName::RequestConfluence }; - //println!("here within the intrinsic"); let first_arg = first_arg?; let is_as_app = authn.first() == Some(&PropPath::MemberCall("asApp".into())); match classify_api_call(first_arg) { @@ -1316,8 +1310,11 @@ impl<'cx> FunctionAnalyzer<'cx> { match n { Pat::Ident(BindingIdent { id, .. }) => { let id = id.to_id(); - let def = self.res.get_or_insert_sym(id, self.module); + let def = self.res.get_or_insert_sym(id.clone(), self.module); let var = self.body.get_or_insert_global(def); + + // issue is here, where it may be getting the wrong def + self.push_curr_inst(Inst::Assign(Variable::new(var), val)); } Pat::Array(ArrayPat { elems, .. }) => { @@ -2300,6 +2297,41 @@ impl Visit for FunctionCollector<'_> { } } } +} + +impl Visit for FunctionCollector<'_> { + fn visit_assign_expr(&mut self, n: &AssignExpr) { + if let PatOrExpr::Pat(pat) = &n.left { + if let Pat::Expr(expr) = &**pat { + if let Expr::Member(mem_expr) = &**expr { + if let Expr::Ident(ident) = &*mem_expr.obj { + if ident.sym.to_string() == "exports" { + if let MemberProp::Ident(ident_property) = &mem_expr.prop { + match &*n.right { + Expr::Fn(FnExpr { ident, function }) => { + if let Some(defid) = + self.res.get_sym(ident_property.to_id(), self.module) + { + self.handle_function(&**function, Some(defid)); + } + } + _ => {} + } + } + } + } + } + } + } + + n.visit_children_with(self) + } + + fn visit_function(&mut self, n: &Function) { + // likley an issue where we are adding anon instead of using the actual value + n.visit_children_with(self); + self.handle_function(n, None); + } fn visit_arrow_expr( &mut self, @@ -2930,6 +2962,7 @@ impl ExportCollector<'_> { fn add_default(&mut self, def: DefRes, id: Option) -> DefId { let defid = match id { + // ehck here, this may be hte issue Some(id) => self.res_table.add_sym(def, id, self.curr_mod), None => { self.res_table.names.push("default".into()); @@ -3113,7 +3146,6 @@ impl Visit for GlobalCollector<'_> { impl Visit for ExportCollector<'_> { noop_visit_type!(); fn visit_export_decl(&mut self, n: &ExportDecl) { - //println!("visit export decl {n:#?}"); match &n.decl { Decl::Class(ClassDecl { ident, .. }) => { let ident = ident.to_id(); @@ -3121,7 +3153,6 @@ impl Visit for ExportCollector<'_> { } Decl::Fn(FnDecl { ident, .. }) => { let ident = ident.to_id(); - //println!("FNDECL = {ident:?}"); self.add_export(DefRes::Function(()), ident); } Decl::Var(vardecls) => { @@ -3279,7 +3310,7 @@ impl Visit for ExportCollector<'_> { } fn ident_from_assign_expr(n: &AssignExpr) -> Option { - if let Some(mem_expr) = mem_expr_from_assign(n) { + if let Some(mem_expr) = mem_expr_from_assign(n.clone()) { if let Expr::Ident(ident) = &*mem_expr.obj { return Some(ident.clone()); } @@ -3287,9 +3318,13 @@ fn ident_from_assign_expr(n: &AssignExpr) -> Option { None } -fn mem_expr_from_assign(n: &AssignExpr) -> Option<&MemberExpr> { - if let Some(Expr::Member(mem_expr)) = n.left.as_expr() { - return Some(mem_expr); +fn mem_expr_from_assign(n: AssignExpr) -> Option { + if let PatOrExpr::Pat(pat) = &n.left { + if let Pat::Expr(expr) = &**pat { + if let Expr::Member(mem_expr) = &**expr { + return Some(mem_expr.clone()); + } + } } None } diff --git a/crates/forge_analyzer/src/exports.rs b/crates/forge_analyzer/src/exports.rs index 5b98a441..4f5488f4 100644 --- a/crates/forge_analyzer/src/exports.rs +++ b/crates/forge_analyzer/src/exports.rs @@ -25,9 +25,6 @@ impl ExportCollector { impl Visit for ExportCollector { noop_visit_type!(); fn visit_export_decl(&mut self, n: &ExportDecl) { - //println!("export collector {n:?}"); - println!(); - match &n.decl { Decl::Class(_) => {} Decl::Fn(FnDecl { ident, .. }) => { @@ -37,12 +34,10 @@ impl Visit for ExportCollector { let ident = ident.to_id(); debug!(?ident, "function export"); // TODO: redo export layout to avoid clones - println!("exported ident {ident:?}"); export_ids.add_named(ident.clone(), ident); } Decl::Var(vardecls) => { let VarDecl { decls, .. } = &**vardecls; - //println!("exported vardecls {vardecls:?}"); decls.iter().for_each(|var| self.visit_var_declarator(var)); } Decl::Using(_) @@ -53,30 +48,6 @@ impl Visit for ExportCollector { }; } - fn visit_module_export_name(&mut self, n: &ModuleExportName) { - //println!("visiting module_export name"); - //println!("{n:?}"); - } - - fn visit_assign_expr(&mut self, n: &AssignExpr) { - // clean this before pushing - if let PatOrExpr::Expr(expr) = &n.left { - println!("here found moudle.exports"); - if let Expr::Member(mem_expr) = &**expr { - if let Expr::Ident(ident) = &*mem_expr.obj { - println!("here found moudle.exports"); - if ident.sym.to_string() == "module" { - if let MemberProp::Ident(ident_property) = &mem_expr.prop { - if ident_property.sym.to_string() == "export" { - println!("here found moudle.exports") - } - } - } - } - } - } - } - fn visit_var_declarator(&mut self, n: &VarDeclarator) { if let Pat::Ident(BindingIdent { id, .. }) = &n.name { let export_ids = self diff --git a/crates/forge_analyzer/src/interp.rs b/crates/forge_analyzer/src/interp.rs index 01c01494..b53b839c 100644 --- a/crates/forge_analyzer/src/interp.rs +++ b/crates/forge_analyzer/src/interp.rs @@ -93,7 +93,6 @@ pub trait Dataflow<'cx>: Sized { rvalue: &'cx Rvalue, initial_state: Self::State, ) -> Self::State { - // println!("transfer rvalue {:?}", rvalue); match rvalue { Rvalue::Intrinsic(intrinsic, args) => self.transfer_intrinsic( interp, @@ -151,7 +150,6 @@ pub trait Dataflow<'cx>: Sized { ) -> Self::State { let mut state = initial_state; for (stmt, inst) in block.iter().enumerate() { - // println!("inst -- {inst}"); let loc = Location::new(bb, stmt as u32); state = self.transfer_inst(interp, def, loc, block, inst, state); } From 3594d4d07655faab5b75fbdecac7078d4789b6f0 Mon Sep 17 00:00:00 2001 From: James Gersbach Date: Wed, 19 Jul 2023 16:09:08 -0700 Subject: [PATCH 435/517] removing unneeded comments --- crates/forge_analyzer/src/definitions.rs | 4 ---- 1 file changed, 4 deletions(-) diff --git a/crates/forge_analyzer/src/definitions.rs b/crates/forge_analyzer/src/definitions.rs index 02dbdf01..b379b9bf 100644 --- a/crates/forge_analyzer/src/definitions.rs +++ b/crates/forge_analyzer/src/definitions.rs @@ -1312,9 +1312,6 @@ impl<'cx> FunctionAnalyzer<'cx> { let id = id.to_id(); let def = self.res.get_or_insert_sym(id.clone(), self.module); let var = self.body.get_or_insert_global(def); - - // issue is here, where it may be getting the wrong def - self.push_curr_inst(Inst::Assign(Variable::new(var), val)); } Pat::Array(ArrayPat { elems, .. }) => { @@ -2962,7 +2959,6 @@ impl ExportCollector<'_> { fn add_default(&mut self, def: DefRes, id: Option) -> DefId { let defid = match id { - // ehck here, this may be hte issue Some(id) => self.res_table.add_sym(def, id, self.curr_mod), None => { self.res_table.names.push("default".into()); From 00094fa0fec72dcea01002a9ccc4f45f76944d9a Mon Sep 17 00:00:00 2001 From: James Gersbach Date: Wed, 2 Aug 2023 12:06:04 -0700 Subject: [PATCH 436/517] small updates --- crates/forge_analyzer/src/analyzer.rs | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/crates/forge_analyzer/src/analyzer.rs b/crates/forge_analyzer/src/analyzer.rs index 0aec70b0..3e9ba2aa 100644 --- a/crates/forge_analyzer/src/analyzer.rs +++ b/crates/forge_analyzer/src/analyzer.rs @@ -6,8 +6,8 @@ use std::{fmt, mem}; use crate::ctx::{BasicBlockId, FunctionMeta, IrStmt, ModuleCtx, TerminatorKind, STARTING_BLOCK}; use crate::lattice::MeetSemiLattice; use swc_core::ecma::ast::{ - ArrowExpr, BindingIdent, CallExpr, Callee, Expr, ExprOrSpread, ExprStmt, FnDecl, FnExpr, Id, - IfStmt, JSXElementName, JSXOpeningElement, MemberExpr, MemberProp, Pat, Stmt, Str, ThrowStmt, + ArrowExpr, BindingIdent, CallExpr, Callee, Expr, ExprOrSpread, FnDecl, FnExpr, Id, IfStmt, + JSXElementName, JSXOpeningElement, MemberExpr, MemberProp, Pat, Stmt, Str, ThrowStmt, TplElement, VarDeclarator, }; use swc_core::ecma::visit::{noop_visit_type, Visit, VisitWith}; @@ -237,7 +237,6 @@ impl Visit for FunctionAnalyzer<'_> { _ => {} } } - // we don't need to add this to the IR, since we know it's useless return; } else { IrStmt::Call(id.into()) @@ -285,8 +284,6 @@ impl Visit for FunctionCollector<'_> { n.function.visit_children_with(self); } - // fn visit_asign_expr() - fn visit_var_declarator(&mut self, n: &VarDeclarator) { if let VarDeclarator { name: Pat::Ident(BindingIdent { id, .. }), From 526dada4b82beb0024241d6c159f234cc802d192 Mon Sep 17 00:00:00 2001 From: James Gersbach Date: Fri, 4 Aug 2023 12:27:39 -0700 Subject: [PATCH 437/517] removing unneeded file --- .vscode/launch.json | 101 -------------------------- crates/forge_analyzer/src/exports.rs | 5 +- crates/forge_analyzer/src/reporter.rs | 1 - crates/forge_analyzer/src/worklist.rs | 2 +- 4 files changed, 3 insertions(+), 106 deletions(-) delete mode 100644 .vscode/launch.json diff --git a/.vscode/launch.json b/.vscode/launch.json deleted file mode 100644 index 1b93707a..00000000 --- a/.vscode/launch.json +++ /dev/null @@ -1,101 +0,0 @@ -{ - // Use IntelliSense to learn about possible attributes. - // Hover to view descriptions of existing attributes. - // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 - "version": "0.2.0", - "configurations": [ - { - "type": "lldb", - "request": "launch", - "name": "Debug unit tests in library 'forge_analyzer'", - "cargo": { - "args": [ - "test", - "--no-run", - "--lib", - "--package=forge_analyzer" - ], - "filter": { - "name": "forge_analyzer", - "kind": "lib" - } - }, - "args": [], - "cwd": "${workspaceFolder}" - }, - { - "type": "lldb", - "request": "launch", - "name": "Debug unit tests in library 'forge_file_resolver'", - "cargo": { - "args": [ - "test", - "--no-run", - "--lib", - "--package=forge_file_resolver" - ], - "filter": { - "name": "forge_file_resolver", - "kind": "lib" - } - }, - "args": [], - "cwd": "${workspaceFolder}" - }, - { - "type": "lldb", - "request": "launch", - "name": "Debug unit tests in library 'forge_utils'", - "cargo": { - "args": ["test", "--no-run", "--lib", "--package=forge_utils"], - "filter": { - "name": "forge_utils", - "kind": "lib" - } - }, - "args": [], - "cwd": "${workspaceFolder}" - }, - { - "type": "lldb", - "request": "launch", - "name": "Debug unit tests in library 'forge_loader'", - "cargo": { - "args": ["test", "--no-run", "--lib", "--package=forge_loader"], - "filter": { - "name": "forge_loader", - "kind": "lib" - } - }, - "args": [], - "cwd": "${workspaceFolder}" - }, - { - "type": "lldb", - "request": "launch", - "name": "Debug executable 'fsrt'", - "cargo": { - "args": ["build", "--bin=fsrt", "--package=fsrt"], - "filter": { - "name": "fsrt", - "kind": "bin" - } - }, - "args": ["./test-apps/jira-damn-vulnerable-forge-app"], - "cwd": "${workspaceFolder}" - }, - { - "type": "lldb", - "request": "launch", - "name": "Debug unit tests in executable 'fsrt'", - "cargo": { - "args": ["test", "--no-run", "--bin=fsrt", "--package=fsrt"], - "filter": { - "name": "fsrt", - "kind": "bin" - } - }, - "cwd": "${workspaceFolder}" - } - ] -} diff --git a/crates/forge_analyzer/src/exports.rs b/crates/forge_analyzer/src/exports.rs index 4f5488f4..826bc46c 100644 --- a/crates/forge_analyzer/src/exports.rs +++ b/crates/forge_analyzer/src/exports.rs @@ -2,9 +2,8 @@ use crate::Exports; use forge_utils::FxHashMap; use swc_core::ecma::{ ast::{ - AssignExpr, BindingIdent, Decl, ExportAll, ExportDecl, ExportDefaultDecl, - ExportDefaultExpr, Expr, FnDecl, MemberProp, ModuleDecl, ModuleExportName, ModuleItem, Pat, - PatOrExpr, VarDecl, VarDeclarator, + BindingIdent, Decl, ExportAll, ExportDecl, ExportDefaultDecl, ExportDefaultExpr, FnDecl, + ModuleDecl, ModuleItem, Pat, VarDecl, VarDeclarator, }, visit::{noop_visit_type, Visit, VisitWith}, }; diff --git a/crates/forge_analyzer/src/reporter.rs b/crates/forge_analyzer/src/reporter.rs index 1b993d7c..f5cd6441 100644 --- a/crates/forge_analyzer/src/reporter.rs +++ b/crates/forge_analyzer/src/reporter.rs @@ -1,6 +1,5 @@ use forge_loader::forgepermissions::ForgePermissions; use serde::Serialize; -use std::collections::HashSet; use time::{Date, OffsetDateTime}; #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)] diff --git a/crates/forge_analyzer/src/worklist.rs b/crates/forge_analyzer/src/worklist.rs index afac0d19..3e946126 100644 --- a/crates/forge_analyzer/src/worklist.rs +++ b/crates/forge_analyzer/src/worklist.rs @@ -5,7 +5,7 @@ use tracing::debug; use crate::{ definitions::{DefId, Environment}, - ir::{BasicBlockId, Operand}, + ir::BasicBlockId, }; #[derive(Debug, Clone)] From d81602e7cd5971010f6ab1845379fa37e6c8c9ae Mon Sep 17 00:00:00 2001 From: James Gersbach Date: Thu, 15 Jun 2023 10:43:23 -0700 Subject: [PATCH 438/517] fixes to projections --- crates/forge_analyzer/src/definitions.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/crates/forge_analyzer/src/definitions.rs b/crates/forge_analyzer/src/definitions.rs index b379b9bf..fa1eab9b 100644 --- a/crates/forge_analyzer/src/definitions.rs +++ b/crates/forge_analyzer/src/definitions.rs @@ -1489,7 +1489,6 @@ impl<'cx> FunctionAnalyzer<'cx> { .res .add_anonymous("__UNKNOWN", AnonType::Obj, self.module); let class_var_id = self.body.add_var(VarKind::LocalDef((def_id))); - let mut var = Variable::new(class_var_id); if let DefKind::GlobalObj(class_id) = self.res.defs.defs[def_id] { props.iter().for_each(|prop_or_spread| { let mut var = Variable::new(class_var_id); From 2e9d5a6213665dbc9fa09ea3a8cce8b28b9d20d0 Mon Sep 17 00:00:00 2001 From: James Gersbach Date: Sun, 9 Jul 2023 21:45:04 -0700 Subject: [PATCH 439/517] lowering functions that are called with handlers --- crates/forge_analyzer/src/interp.rs | 1 + crates/forge_analyzer/src/ir.rs | 7 +++++ .../src/IssuePanelApp.jsx | 27 ++++++++++--------- .../src/index.jsx | 1 + 4 files changed, 24 insertions(+), 12 deletions(-) diff --git a/crates/forge_analyzer/src/interp.rs b/crates/forge_analyzer/src/interp.rs index b53b839c..093b607b 100644 --- a/crates/forge_analyzer/src/interp.rs +++ b/crates/forge_analyzer/src/interp.rs @@ -799,6 +799,7 @@ impl<'cx, C: Runner<'cx>> Interp<'cx, C> { let arguments = self.callstack_arguments.pop(); let name = self.env.def_name(def); debug!("Dataflow: {name} - {block_id}"); + println!("Dataflow: {name} - {block_id}"); self.dataflow_visited.insert(def); let func = self.env().def_ref(def).expect_body(); self.curr_body.set(Some(func)); diff --git a/crates/forge_analyzer/src/ir.rs b/crates/forge_analyzer/src/ir.rs index 3cb44289..73ffc42f 100644 --- a/crates/forge_analyzer/src/ir.rs +++ b/crates/forge_analyzer/src/ir.rs @@ -490,6 +490,13 @@ impl Body { } } + pub(crate) fn create_variable(&self, varid: VarId) -> Variable { + Variable { + base: Base::Var(varid), + projections: Default::default(), + } + } + pub(crate) fn push_tmp( &mut self, bb: BasicBlockId, diff --git a/test-apps/jira-damn-vulnerable-forge-app/src/IssuePanelApp.jsx b/test-apps/jira-damn-vulnerable-forge-app/src/IssuePanelApp.jsx index 553d69c6..5e794162 100755 --- a/test-apps/jira-damn-vulnerable-forge-app/src/IssuePanelApp.jsx +++ b/test-apps/jira-damn-vulnerable-forge-app/src/IssuePanelApp.jsx @@ -12,19 +12,22 @@ export const IssuePanelApp = () => { writeComment(); const writeCommentFunction = () => { - writeComment(issueId, 'Overwrite') - } + writeComment(issueId, 'Overwrite'); + }; return ( - , - ]} - > - Overwrite vuln - + + , + ]} + > + Overwrite vuln + + ); }; + +function Bananas() { + return <>; +} diff --git a/test-apps/jira-damn-vulnerable-forge-app/src/index.jsx b/test-apps/jira-damn-vulnerable-forge-app/src/index.jsx index fbc2e4cb..43a6cca5 100755 --- a/test-apps/jira-damn-vulnerable-forge-app/src/index.jsx +++ b/test-apps/jira-damn-vulnerable-forge-app/src/index.jsx @@ -18,6 +18,7 @@ import api, { webTrigger, route, storage, properties } from '@forge/api'; import { createHash } from 'crypto'; import jwt from 'jsonwebtoken'; import { fetchIssueSummary } from './utils'; +import {IssuePanelApp} from './IssuePanelApp'; function SharedSecretForm() { const [hashedSecret, setHashedSecret] = useState(null); From 6bd5f3a9027594dddc0b2088452ef10ec1ff7c0b Mon Sep 17 00:00:00 2001 From: gersbach <93059258+gersbach@users.noreply.github.com> Date: Tue, 11 Jul 2023 13:02:59 -0700 Subject: [PATCH 440/517] Update crates/forge_analyzer/src/interp.rs Co-authored-by: Joshua Wong --- crates/forge_analyzer/src/interp.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/crates/forge_analyzer/src/interp.rs b/crates/forge_analyzer/src/interp.rs index 093b607b..b53b839c 100644 --- a/crates/forge_analyzer/src/interp.rs +++ b/crates/forge_analyzer/src/interp.rs @@ -799,7 +799,6 @@ impl<'cx, C: Runner<'cx>> Interp<'cx, C> { let arguments = self.callstack_arguments.pop(); let name = self.env.def_name(def); debug!("Dataflow: {name} - {block_id}"); - println!("Dataflow: {name} - {block_id}"); self.dataflow_visited.insert(def); let func = self.env().def_ref(def).expect_body(); self.curr_body.set(Some(func)); From 93a79001b503540a01200cb2149ea7f44bfad4d4 Mon Sep 17 00:00:00 2001 From: James Gersbach Date: Tue, 11 Jul 2023 13:40:46 -0700 Subject: [PATCH 441/517] fixes to comments --- crates/forge_analyzer/src/ir.rs | 7 ------- 1 file changed, 7 deletions(-) diff --git a/crates/forge_analyzer/src/ir.rs b/crates/forge_analyzer/src/ir.rs index 73ffc42f..3cb44289 100644 --- a/crates/forge_analyzer/src/ir.rs +++ b/crates/forge_analyzer/src/ir.rs @@ -490,13 +490,6 @@ impl Body { } } - pub(crate) fn create_variable(&self, varid: VarId) -> Variable { - Variable { - base: Base::Var(varid), - projections: Default::default(), - } - } - pub(crate) fn push_tmp( &mut self, bb: BasicBlockId, From c05947b2d3f4aed884460279de16460b8ceb692c Mon Sep 17 00:00:00 2001 From: James Gersbach Date: Mon, 24 Jul 2023 08:58:09 -0700 Subject: [PATCH 442/517] lowering then and array functions --- crates/forge_analyzer/src/definitions.rs | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/crates/forge_analyzer/src/definitions.rs b/crates/forge_analyzer/src/definitions.rs index fa1eab9b..7f2e1e72 100644 --- a/crates/forge_analyzer/src/definitions.rs +++ b/crates/forge_analyzer/src/definitions.rs @@ -2121,6 +2121,27 @@ impl Visit for LocalDefiner<'_> { fn visit_fn_decl(&mut self, _: &FnDecl) {} } +impl Visit for FunctionAnalyzer<'_> { + fn visit_call_expr(&mut self, n: &CallExpr) { + if call_func_with_name(n, "then") + || call_func_with_name(n, "map") + || call_func_with_name(n, "foreach") + || call_func_with_name(n, "filter") + { + if let Some(lambda_function) = n.args.get(0) { + if let Expr::Arrow(arrow) = &*lambda_function.expr { + if let BlockStmtOrExpr::BlockStmt(block_stmt) = &arrow.body { + block_stmt + .stmts + .iter() + .for_each(|stmt| self.lower_stmt(stmt)); + } + } + } + } + } +} + impl Visit for FunctionCollector<'_> { fn visit_export_default_decl(&mut self, n: &ExportDefaultDecl) { if let Some(defid) = self.res.default_export(self.module) { From 1f4127f15a15e31d20d2dc1e8059ee1924b7bedb Mon Sep 17 00:00:00 2001 From: James Gersbach Date: Thu, 27 Jul 2023 11:01:38 -0700 Subject: [PATCH 443/517] updates to pr --- crates/forge_analyzer/src/definitions.rs | 21 --------------------- 1 file changed, 21 deletions(-) diff --git a/crates/forge_analyzer/src/definitions.rs b/crates/forge_analyzer/src/definitions.rs index 7f2e1e72..fa1eab9b 100644 --- a/crates/forge_analyzer/src/definitions.rs +++ b/crates/forge_analyzer/src/definitions.rs @@ -2121,27 +2121,6 @@ impl Visit for LocalDefiner<'_> { fn visit_fn_decl(&mut self, _: &FnDecl) {} } -impl Visit for FunctionAnalyzer<'_> { - fn visit_call_expr(&mut self, n: &CallExpr) { - if call_func_with_name(n, "then") - || call_func_with_name(n, "map") - || call_func_with_name(n, "foreach") - || call_func_with_name(n, "filter") - { - if let Some(lambda_function) = n.args.get(0) { - if let Expr::Arrow(arrow) = &*lambda_function.expr { - if let BlockStmtOrExpr::BlockStmt(block_stmt) = &arrow.body { - block_stmt - .stmts - .iter() - .for_each(|stmt| self.lower_stmt(stmt)); - } - } - } - } - } -} - impl Visit for FunctionCollector<'_> { fn visit_export_default_decl(&mut self, n: &ExportDefaultDecl) { if let Some(defid) = self.res.default_export(self.module) { From 9e2ebe3975630be6f4128f9f111d76b1e49930bc Mon Sep 17 00:00:00 2001 From: James Gersbach Date: Thu, 20 Jul 2023 16:13:04 -0700 Subject: [PATCH 444/517] handling lowering of useEffect functions --- crates/forge_analyzer/src/definitions.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/crates/forge_analyzer/src/definitions.rs b/crates/forge_analyzer/src/definitions.rs index fa1eab9b..510685d3 100644 --- a/crates/forge_analyzer/src/definitions.rs +++ b/crates/forge_analyzer/src/definitions.rs @@ -2779,6 +2779,8 @@ impl Visit for Lowerer<'_> { class.pub_members.push((fname, new_def)); } } + } else if let Expr::Ident(ident) = &**expr { + //ident.sym } } } @@ -2981,6 +2983,8 @@ impl Visit for ImportCollector<'_> { noop_visit_type!(); fn visit_import_decl(&mut self, n: &ImportDecl) { + println!("visitng import decl {n:?}"); + let Str { value, .. } = &*n.src; let old_import = mem::replace(&mut self.current_import, value.clone()); n.visit_children_with(self); From aef818470405c0872c7b931ea5deb9ddbb5d8c0e Mon Sep 17 00:00:00 2001 From: James Gersbach Date: Thu, 20 Jul 2023 16:14:16 -0700 Subject: [PATCH 445/517] cleaning up --- crates/forge_analyzer/src/definitions.rs | 4 ---- 1 file changed, 4 deletions(-) diff --git a/crates/forge_analyzer/src/definitions.rs b/crates/forge_analyzer/src/definitions.rs index 510685d3..fa1eab9b 100644 --- a/crates/forge_analyzer/src/definitions.rs +++ b/crates/forge_analyzer/src/definitions.rs @@ -2779,8 +2779,6 @@ impl Visit for Lowerer<'_> { class.pub_members.push((fname, new_def)); } } - } else if let Expr::Ident(ident) = &**expr { - //ident.sym } } } @@ -2983,8 +2981,6 @@ impl Visit for ImportCollector<'_> { noop_visit_type!(); fn visit_import_decl(&mut self, n: &ImportDecl) { - println!("visitng import decl {n:?}"); - let Str { value, .. } = &*n.src; let old_import = mem::replace(&mut self.current_import, value.clone()); n.visit_children_with(self); From c9e54daba866da9f4ffa22887d6d3d7a515ab834 Mon Sep 17 00:00:00 2001 From: Joshua Wong Date: Wed, 2 Aug 2023 11:50:49 -0500 Subject: [PATCH 446/517] feat: generate check name based on stacktrace If there was a false positive in the scanner, a similar ticket will be created if the partner pushes an update, since the artifact id is included in the path. We remove the artifact id by starting the hash at the src directory and include the stacktrace, so that the ticket will remain the same. --- crates/forge_analyzer/src/checkers.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/crates/forge_analyzer/src/checkers.rs b/crates/forge_analyzer/src/checkers.rs index 3d76e739..730f8c3f 100644 --- a/crates/forge_analyzer/src/checkers.rs +++ b/crates/forge_analyzer/src/checkers.rs @@ -992,8 +992,12 @@ impl IntoVuln for AuthNVuln { use std::hash::{Hash, Hasher}; let mut hasher = DefaultHasher::new(); - self.file.hash(&mut hasher); + self.file + .iter() + .skip_while(|comp| *comp != "src") + .for_each(|comp| comp.hash(&mut hasher)); self.entry_func.hash(&mut hasher); + self.stack.hash(&mut hasher); Vulnerability { check_name: format!("Custom-Check-Authentication-{}", hasher.finish()), description: format!("Insufficient Authentication through webhook {} in {:?}.", self.entry_func, self.file), From d26f6fdce5241afc61f1835a5e6325533d25e929 Mon Sep 17 00:00:00 2001 From: James Gersbach Date: Thu, 20 Jul 2023 08:51:07 -0700 Subject: [PATCH 447/517] handling default and nodejs exports --- crates/forge_analyzer/src/definitions.rs | 37 +++++++++++++++++++++++- 1 file changed, 36 insertions(+), 1 deletion(-) diff --git a/crates/forge_analyzer/src/definitions.rs b/crates/forge_analyzer/src/definitions.rs index fa1eab9b..beacdbf7 100644 --- a/crates/forge_analyzer/src/definitions.rs +++ b/crates/forge_analyzer/src/definitions.rs @@ -770,7 +770,7 @@ impl ResolverTable { #[inline] fn get_sym(&mut self, id: Id, module: ModId) -> Option { - self.sym_id(id.clone(), module) + self.sym_id(id.clone(), module); } fn reserve_def(&mut self, name: JsWord, module: ModId) -> DefId { @@ -2295,6 +2295,41 @@ impl Visit for FunctionCollector<'_> { } } +impl Visit for FunctionCollector<'_> { + fn visit_assign_expr(&mut self, n: &AssignExpr) { + if let PatOrExpr::Pat(pat) = &n.left { + if let Pat::Expr(expr) = &**pat { + if let Expr::Member(mem_expr) = &**expr { + if let Expr::Ident(ident) = &*mem_expr.obj { + if ident.sym.to_string() == "exports" { + if let MemberProp::Ident(ident_property) = &mem_expr.prop { + match &*n.right { + Expr::Fn(FnExpr { ident, function }) => { + if let Some(defid) = + self.res.get_sym(ident_property.to_id(), self.module) + { + self.handle_function(&**function, Some(defid)); + } + } + _ => {} + } + } + } + } + } + } + } + + n.visit_children_with(self) + } + + fn visit_function(&mut self, n: &Function) { + // likley an issue where we are adding anon instead of using the actual value + n.visit_children_with(self); + self.handle_function(n, None); + } +} + impl Visit for FunctionCollector<'_> { fn visit_assign_expr(&mut self, n: &AssignExpr) { if let PatOrExpr::Pat(pat) = &n.left { From 56da8391967208bb55c533c852bad509bcd5481f Mon Sep 17 00:00:00 2001 From: James Gersbach Date: Thu, 20 Jul 2023 14:24:09 -0700 Subject: [PATCH 448/517] cleaning up --- crates/forge_analyzer/src/definitions.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/crates/forge_analyzer/src/definitions.rs b/crates/forge_analyzer/src/definitions.rs index beacdbf7..0ea93141 100644 --- a/crates/forge_analyzer/src/definitions.rs +++ b/crates/forge_analyzer/src/definitions.rs @@ -2359,7 +2359,6 @@ impl Visit for FunctionCollector<'_> { } fn visit_function(&mut self, n: &Function) { - // likley an issue where we are adding anon instead of using the actual value n.visit_children_with(self); self.handle_function(n, None); } From e44a3eb4c4c7dd2741428cda2a426dba094fdcc5 Mon Sep 17 00:00:00 2001 From: James Gersbach Date: Thu, 20 Jul 2023 14:34:28 -0700 Subject: [PATCH 449/517] more code --- crates/forge_analyzer/src/definitions.rs | 38 ++++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/crates/forge_analyzer/src/definitions.rs b/crates/forge_analyzer/src/definitions.rs index 0ea93141..b019a26c 100644 --- a/crates/forge_analyzer/src/definitions.rs +++ b/crates/forge_analyzer/src/definitions.rs @@ -3254,6 +3254,44 @@ impl Visit for ExportCollector<'_> { n.visit_children_with(self); } + + fn visit_assign_expr(&mut self, n: &AssignExpr) { + if let Some(ident) = ident_from_assign_expr(n) { + if ident.sym.to_string() == "module" { + if let Some(mem_expr) = mem_expr_from_assign(n.clone()) { + if let MemberProp::Ident(ident_property) = &mem_expr.prop { + if ident_property.sym.to_string() == "exports" { + match &*n.right { + Expr::Fn(FnExpr { ident, function }) => self.add_default( + DefRes::Function(()), + ident.as_ref().map(Ident::to_id), + ), + Expr::Class(ClassExpr { ident, class }) => self.add_default( + DefRes::Class(()), + ident.as_ref().map(Ident::to_id), + ), + _ => {} + } + } + } + } + } else if ident.sym.to_string() == "exports" { + if let Some(mem_expr) = mem_expr_from_assign(n.clone()) { + if let MemberProp::Ident(ident_property) = &mem_expr.prop { + //self.add_export(DefRes::Undefined, ident_property.to_id()); + match &*n.right { + Expr::Fn(FnExpr { ident, function }) => { + self.add_export(DefRes::Function(()), ident_property.to_id()); + } + _ => {} + } + } + } + } + } + n.visit_children_with(self); + } + fn visit_var_declarator(&mut self, n: &VarDeclarator) { // TODO: handle other kinds of destructuring patterns if let Pat::Ident(BindingIdent { id, .. }) = &n.name { From c9c65366d24051d148c9d6063f97ed9fa31b4e14 Mon Sep 17 00:00:00 2001 From: James Gersbach Date: Thu, 20 Jul 2023 14:36:46 -0700 Subject: [PATCH 450/517] formatitng --- crates/forge_analyzer/src/definitions.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/crates/forge_analyzer/src/definitions.rs b/crates/forge_analyzer/src/definitions.rs index b019a26c..0fbde3d9 100644 --- a/crates/forge_analyzer/src/definitions.rs +++ b/crates/forge_analyzer/src/definitions.rs @@ -3254,7 +3254,6 @@ impl Visit for ExportCollector<'_> { n.visit_children_with(self); } - fn visit_assign_expr(&mut self, n: &AssignExpr) { if let Some(ident) = ident_from_assign_expr(n) { if ident.sym.to_string() == "module" { From 072fc95d4af8cf9a482720cc364aa09df2a804c6 Mon Sep 17 00:00:00 2001 From: James Gersbach Date: Tue, 25 Jul 2023 10:44:14 -0700 Subject: [PATCH 451/517] fixes to comments on pr --- crates/forge_analyzer/src/definitions.rs | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/crates/forge_analyzer/src/definitions.rs b/crates/forge_analyzer/src/definitions.rs index 0fbde3d9..4473cdf4 100644 --- a/crates/forge_analyzer/src/definitions.rs +++ b/crates/forge_analyzer/src/definitions.rs @@ -2336,7 +2336,7 @@ impl Visit for FunctionCollector<'_> { if let Pat::Expr(expr) = &**pat { if let Expr::Member(mem_expr) = &**expr { if let Expr::Ident(ident) = &*mem_expr.obj { - if ident.sym.to_string() == "exports" { + if &ident.sym == "exports" { if let MemberProp::Ident(ident_property) = &mem_expr.prop { match &*n.right { Expr::Fn(FnExpr { ident, function }) => { @@ -3256,8 +3256,8 @@ impl Visit for ExportCollector<'_> { fn visit_assign_expr(&mut self, n: &AssignExpr) { if let Some(ident) = ident_from_assign_expr(n) { - if ident.sym.to_string() == "module" { - if let Some(mem_expr) = mem_expr_from_assign(n.clone()) { + if &ident.sym == "module" { + if let Some(mem_expr) = mem_expr_from_assign(n) { if let MemberProp::Ident(ident_property) = &mem_expr.prop { if ident_property.sym.to_string() == "exports" { match &*n.right { @@ -3274,10 +3274,11 @@ impl Visit for ExportCollector<'_> { } } } - } else if ident.sym.to_string() == "exports" { - if let Some(mem_expr) = mem_expr_from_assign(n.clone()) { + } else if &ident.sym == "exports" { + if let Some(mem_expr) = mem_expr_from_assign(n) { if let MemberProp::Ident(ident_property) = &mem_expr.prop { //self.add_export(DefRes::Undefined, ident_property.to_id()); + // TODO: handling aliases match &*n.right { Expr::Fn(FnExpr { ident, function }) => { self.add_export(DefRes::Function(()), ident_property.to_id()); @@ -3376,7 +3377,7 @@ impl Visit for ExportCollector<'_> { } fn ident_from_assign_expr(n: &AssignExpr) -> Option { - if let Some(mem_expr) = mem_expr_from_assign(n.clone()) { + if let Some(mem_expr) = mem_expr_from_assign(n) { if let Expr::Ident(ident) = &*mem_expr.obj { return Some(ident.clone()); } @@ -3384,11 +3385,11 @@ fn ident_from_assign_expr(n: &AssignExpr) -> Option { None } -fn mem_expr_from_assign(n: AssignExpr) -> Option { +fn mem_expr_from_assign(n: &AssignExpr) -> Option<&MemberExpr> { if let PatOrExpr::Pat(pat) = &n.left { if let Pat::Expr(expr) = &**pat { if let Expr::Member(mem_expr) = &**expr { - return Some(mem_expr.clone()); + return Some(mem_expr); } } } From 6aecf425372b0f7acb214d4b318ad58349406605 Mon Sep 17 00:00:00 2001 From: James Gersbach Date: Tue, 25 Jul 2023 10:47:58 -0700 Subject: [PATCH 452/517] formatting --- crates/forge_analyzer/src/definitions.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/forge_analyzer/src/definitions.rs b/crates/forge_analyzer/src/definitions.rs index 4473cdf4..ec4dc01e 100644 --- a/crates/forge_analyzer/src/definitions.rs +++ b/crates/forge_analyzer/src/definitions.rs @@ -3259,7 +3259,7 @@ impl Visit for ExportCollector<'_> { if &ident.sym == "module" { if let Some(mem_expr) = mem_expr_from_assign(n) { if let MemberProp::Ident(ident_property) = &mem_expr.prop { - if ident_property.sym.to_string() == "exports" { + if &ident_property.sym == "exports" { match &*n.right { Expr::Fn(FnExpr { ident, function }) => self.add_default( DefRes::Function(()), From 83c0474f3394955f4f863272687d600bdb6b51a4 Mon Sep 17 00:00:00 2001 From: James Gersbach Date: Tue, 25 Jul 2023 10:51:44 -0700 Subject: [PATCH 453/517] fixes to pr --- crates/forge_analyzer/src/definitions.rs | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/crates/forge_analyzer/src/definitions.rs b/crates/forge_analyzer/src/definitions.rs index ec4dc01e..a0a466e7 100644 --- a/crates/forge_analyzer/src/definitions.rs +++ b/crates/forge_analyzer/src/definitions.rs @@ -3386,12 +3386,8 @@ fn ident_from_assign_expr(n: &AssignExpr) -> Option { } fn mem_expr_from_assign(n: &AssignExpr) -> Option<&MemberExpr> { - if let PatOrExpr::Pat(pat) = &n.left { - if let Pat::Expr(expr) = &**pat { - if let Expr::Member(mem_expr) = &**expr { - return Some(mem_expr); - } - } + if let Some(Expr::Member(mem_expr)) = n.left.as_expr() { + return Some(mem_expr); } None } From 1cebba0c447eeb83d40139c9e18b73501df4ffe7 Mon Sep 17 00:00:00 2001 From: James Gersbach Date: Mon, 24 Jul 2023 12:43:37 -0700 Subject: [PATCH 454/517] prevents scan if the code is transplied --- crates/fsrt/src/main.rs | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/crates/fsrt/src/main.rs b/crates/fsrt/src/main.rs index 5a57ff5c..7372e5fe 100644 --- a/crates/fsrt/src/main.rs +++ b/crates/fsrt/src/main.rs @@ -9,6 +9,7 @@ use miette::{IntoDiagnostic, Result}; use std::{ collections::HashSet, fs, + io::{self, BufReader}, os::unix::prelude::OsStrExt, path::{Path, PathBuf}, sync::Arc, @@ -235,12 +236,8 @@ fn scan_directory(dir: PathBuf, function: Option<&str>, opts: &Args) -> Result<( }); let src_root = dir.join("src"); - let mut proj = - ForgeProject::with_files_and_sourceroot(src_root, paths.clone(), secret_packages); - if transpiled_async { - warn!("Unable to scan due to transpiled async"); - return Ok(()); - } + let mut proj = ForgeProject::with_files_and_sourceroot(src_root, paths.clone()); + proj.opts = opts.clone(); proj.add_funcs(funcrefs); resolve_calls(&mut proj.ctx); if let Some(func) = opts.dump_ir.as_ref() { From d7a6da2dd170574851055f88db3cd5e1a4ea1778 Mon Sep 17 00:00:00 2001 From: James Gersbach Date: Wed, 19 Jul 2023 16:04:21 -0700 Subject: [PATCH 455/517] cleaning up code --- crates/forge_analyzer/src/engine.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/crates/forge_analyzer/src/engine.rs b/crates/forge_analyzer/src/engine.rs index 43204fba..319a3d4f 100644 --- a/crates/forge_analyzer/src/engine.rs +++ b/crates/forge_analyzer/src/engine.rs @@ -117,7 +117,6 @@ impl<'ctx> Machine<'ctx> { Inst::Step(next_block, next_stmt) => { debug!(?next_block, ?next_stmt, "stepping into"); self.eip = (next_block, next_stmt); - // we need to return if possible if let Some(val) = self.transfer() { if let Some(ret) = self.callstack.pop() { debug!(?ret, "returning"); From 37cc049929a1338211f69bd01415e32bf6593a77 Mon Sep 17 00:00:00 2001 From: James Gersbach Date: Fri, 4 Aug 2023 14:37:20 -0700 Subject: [PATCH 456/517] wip --- crates/forge_analyzer/src/checkers.rs | 4 ++++ crates/forge_analyzer/src/definitions.rs | 1 + test-apps/jira-damn-vulnerable-forge-app/src/utils.js | 1 + 3 files changed, 6 insertions(+) diff --git a/crates/forge_analyzer/src/checkers.rs b/crates/forge_analyzer/src/checkers.rs index 730f8c3f..2a0eed78 100644 --- a/crates/forge_analyzer/src/checkers.rs +++ b/crates/forge_analyzer/src/checkers.rs @@ -1197,6 +1197,7 @@ impl<'cx> Dataflow<'cx> for PermissionDataflow { .for_each(|first_arg_vec| { if let Some(second_arg_vec) = intrinsic_argument.second_arg.clone() { first_arg_vec.iter().for_each(|first_arg| { + println!("first arg ===> {first_arg:?}"); second_arg_vec.iter().for_each(|second_arg| { let permissions = check_permission_used( intrinsic_func_type, @@ -1208,6 +1209,7 @@ impl<'cx> Dataflow<'cx> for PermissionDataflow { }) } else { first_arg_vec.iter().for_each(|first_arg| { + println!("first arg ===> {first_arg:?}"); let permissions = check_permission_used(intrinsic_func_type, first_arg, None); permissions_within_call.extend_from_slice(&permissions); @@ -1215,6 +1217,8 @@ impl<'cx> Dataflow<'cx> for PermissionDataflow { } }); + println!("permissions within call {permissions_within_call:?}"); + _interp .permissions .extend_from_slice(&permissions_within_call); diff --git a/crates/forge_analyzer/src/definitions.rs b/crates/forge_analyzer/src/definitions.rs index a0a466e7..1310c802 100644 --- a/crates/forge_analyzer/src/definitions.rs +++ b/crates/forge_analyzer/src/definitions.rs @@ -3500,6 +3500,7 @@ impl Environment { fn add_class_method(&mut self, n: PropName, class_def: DefId, owner: DefId) { if let DefKind::Class(class) = self.def_mut(class_def) { if let PropName::Ident(ident) = &n { + println!("adding class method --"); class.pub_members.push((ident.sym.to_owned(), owner)); } } diff --git a/test-apps/jira-damn-vulnerable-forge-app/src/utils.js b/test-apps/jira-damn-vulnerable-forge-app/src/utils.js index f39207ed..0e172968 100755 --- a/test-apps/jira-damn-vulnerable-forge-app/src/utils.js +++ b/test-apps/jira-damn-vulnerable-forge-app/src/utils.js @@ -33,6 +33,7 @@ export async function fetchIssueSummary(issueIdOrKey, test_value) { }, }; + testFunctionFromTestFile(); let val = 'grapefruit'; val = 'peach'; From 284af26b4ba49252cef8f74e9bfffbb30c2d918a Mon Sep 17 00:00:00 2001 From: James Gersbach Date: Thu, 3 Aug 2023 08:59:52 -0700 Subject: [PATCH 457/517] final permission resolver --- Cargo.lock | 177 +++++++++++++++++++ crates/forge_analyzer/src/checkers.rs | 1 - crates/forge_permission_resolver/src/test.rs | 1 - 3 files changed, 177 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index be437e6c..1475839e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -265,6 +265,18 @@ version = "3.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "572f695136211188308f16ad2ca5c851a712c464060ae6974944458eb83880ba" +[[package]] +name = "bytes" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89b2fd2a0dcf38d7971e2194b6b6eebab45ae01067456a7fd93d5547a61b70be" + +[[package]] +name = "castaway" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2698f953def977c68f935bb0dfa959375ad4638570e969e2f1e9f433cbf1af6" + [[package]] name = "cc" version = "1.0.78" @@ -528,6 +540,21 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "event-listener" +version = "2.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0" + +[[package]] +name = "fastrand" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e51093e27b0797c359783294ca4f0a911c270184cb10f85783b118614a1501be" +dependencies = [ + "instant", +] + [[package]] name = "fixedbitset" version = "0.4.2" @@ -650,6 +677,33 @@ dependencies = [ "walkdir", ] +[[package]] +name = "futures-core" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4bca583b7e26f571124fe5b7561d49cb2868d79116cfa0eefce955557c6fee8c" + +[[package]] +name = "futures-io" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fff74096e71ed47f8e023204cfd0aa1289cd54ae5430a9523be060cdb849964" + +[[package]] +name = "futures-lite" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49a9d51ce47660b1e808d3c990b4709f2f415d928835a17dfd16991515c46bce" +dependencies = [ + "fastrand", + "futures-core", + "futures-io", + "memchr", + "parking", + "pin-project-lite", + "waker-fn", +] + [[package]] name = "generic-array" version = "0.14.6" @@ -832,6 +886,33 @@ version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "616cde7c720bb2bb5824a224687d8f77bfd38922027f01d825cd7453be5099fb" +[[package]] +name = "isahc" +version = "1.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "334e04b4d781f436dc315cb1e7515bd96826426345d498149e4bde36b67f8ee9" +dependencies = [ + "async-channel", + "castaway", + "crossbeam-utils", + "curl", + "curl-sys", + "encoding_rs", + "event-listener", + "futures-lite", + "http", + "log", + "mime", + "once_cell", + "polling", + "slab", + "sluice", + "tracing", + "tracing-futures", + "url", + "waker-fn", +] + [[package]] name = "itertools" version = "0.10.5" @@ -1015,6 +1096,12 @@ dependencies = [ "syn 1.0.107", ] +[[package]] +name = "mime" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" + [[package]] name = "minimal-lexical" version = "0.2.1" @@ -1152,6 +1239,12 @@ version = "3.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c1b04fb49957986fdce4d6ee7a65027d55d4b6d2265e5848bbb507b58ccfdb6f" +[[package]] +name = "parking" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14f2252c834a40ed9bb5422029649578e63aa341ac401f74e719dd1afda8394e" + [[package]] name = "parking_lot" version = "0.12.1" @@ -1270,6 +1363,12 @@ version = "0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116" +[[package]] +name = "pkg-config" +version = "0.3.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26072860ba924cbfa98ea39c8c19b4dd6a4a25423dbdf219c1eca91aa0cf6964" + [[package]] name = "pmutil" version = "0.6.1" @@ -1493,6 +1592,21 @@ dependencies = [ "winapi", ] +[[package]] +name = "ring" +version = "0.16.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3053cf52e236a3ed746dfc745aa9cacf1b791d846bdaf412f60a8d7d6e17c8fc" +dependencies = [ + "cc", + "libc", + "once_cell", + "spin", + "untrusted", + "web-sys", + "winapi", +] + [[package]] name = "rustc-demangle" version = "0.1.21" @@ -1586,6 +1700,15 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "schannel" +version = "0.1.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c3733bf4cf7ea0880754e19cb5a462007c4a8c1914bff372ccc95b464f1df88" +dependencies = [ + "windows-sys 0.48.0", +] + [[package]] name = "scoped-tls" version = "1.0.1" @@ -1741,6 +1864,16 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f67ad224767faa3c7d8b6d91985b78e70a1324408abcb1cfcc2be4c06bc06043" +[[package]] +name = "socket2" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64a4a911eed85daf18834cfaa86a79b7d266ff93ff5ba14005426219480ed662" +dependencies = [ + "libc", + "winapi", +] + [[package]] name = "sourcemap" version = "6.2.1" @@ -2975,6 +3108,16 @@ dependencies = [ "valuable", ] +[[package]] +name = "tracing-futures" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97d095ae15e245a057c8e8451bab9b3ee1e1f68e9ba2b4fbc18d0ac5237835f2" +dependencies = [ + "pin-project", + "tracing", +] + [[package]] name = "tracing-log" version = "0.2.0" @@ -3108,6 +3251,28 @@ dependencies = [ "webpki-roots", ] +[[package]] +name = "untrusted" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" + +[[package]] +name = "ureq" +version = "2.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b11c96ac7ee530603dcdf68ed1557050f374ce55a5a07193ebf8cbc9f8927e9" +dependencies = [ + "base64 0.21.2", + "flate2", + "log", + "once_cell", + "rustls", + "rustls-webpki 0.100.1", + "url", + "webpki-roots", +] + [[package]] name = "url" version = "2.5.0" @@ -3131,6 +3296,12 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" +[[package]] +name = "vcpkg" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" + [[package]] name = "vergen" version = "8.2.6" @@ -3147,6 +3318,12 @@ version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" +[[package]] +name = "waker-fn" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d5b2c62b4012a3e1eca5a7e077d13b3bf498c4073e33ccd58626607748ceeca" + [[package]] name = "walkdir" version = "2.3.2" diff --git a/crates/forge_analyzer/src/checkers.rs b/crates/forge_analyzer/src/checkers.rs index 2a0eed78..5cb00a51 100644 --- a/crates/forge_analyzer/src/checkers.rs +++ b/crates/forge_analyzer/src/checkers.rs @@ -918,7 +918,6 @@ impl<'cx> Checker<'cx> for AuthenticateChecker { type State = Authenticated; type Dataflow = AuthenticateDataflow; type Vuln = AuthNVuln; - fn visit_intrinsic( &mut self, interp: &Interp<'cx, Self>, diff --git a/crates/forge_permission_resolver/src/test.rs b/crates/forge_permission_resolver/src/test.rs index a0e6ad08..4532e364 100644 --- a/crates/forge_permission_resolver/src/test.rs +++ b/crates/forge_permission_resolver/src/test.rs @@ -76,7 +76,6 @@ mod tests { assert_eq!(result, expected_permission); } - // TODO: Add a test case using a manifest that has a function exposed through both a non user invocable module and a user invocable module #[test] fn test_catch_indirect_func_invoke() { From acd64cea5771c1d1a738fb1d22eb97edc4d8a3c1 Mon Sep 17 00:00:00 2001 From: James Gersbach Date: Thu, 3 Aug 2023 09:12:40 -0700 Subject: [PATCH 458/517] cleaning up --- .../src/permissions_resolver.rs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/crates/forge_permission_resolver/src/permissions_resolver.rs b/crates/forge_permission_resolver/src/permissions_resolver.rs index 5bd3e8a9..6a17d9db 100644 --- a/crates/forge_permission_resolver/src/permissions_resolver.rs +++ b/crates/forge_permission_resolver/src/permissions_resolver.rs @@ -267,3 +267,13 @@ mod test { assert_eq!(result, expected_permission); } } + +fn get_scopes(endpoint_data: &RequestDetails) -> Vec { + return endpoint_data + .permission + .clone() + .into_iter() + .map(|data| data.scopes) + .flatten() + .collect(); +} From d413d401a5049ba3311986ad60e54cc3723cfd17 Mon Sep 17 00:00:00 2001 From: James Gersbach Date: Fri, 4 Aug 2023 15:30:54 -0700 Subject: [PATCH 459/517] fixed to pr --- Cargo.lock | 104 ++-------------------------- crates/forge_analyzer/src/engine.rs | 1 + 2 files changed, 5 insertions(+), 100 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 1475839e..9816cb91 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -265,18 +265,6 @@ version = "3.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "572f695136211188308f16ad2ca5c851a712c464060ae6974944458eb83880ba" -[[package]] -name = "bytes" -version = "1.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89b2fd2a0dcf38d7971e2194b6b6eebab45ae01067456a7fd93d5547a61b70be" - -[[package]] -name = "castaway" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2698f953def977c68f935bb0dfa959375ad4638570e969e2f1e9f433cbf1af6" - [[package]] name = "cc" version = "1.0.78" @@ -677,33 +665,6 @@ dependencies = [ "walkdir", ] -[[package]] -name = "futures-core" -version = "0.3.28" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4bca583b7e26f571124fe5b7561d49cb2868d79116cfa0eefce955557c6fee8c" - -[[package]] -name = "futures-io" -version = "0.3.28" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fff74096e71ed47f8e023204cfd0aa1289cd54ae5430a9523be060cdb849964" - -[[package]] -name = "futures-lite" -version = "1.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49a9d51ce47660b1e808d3c990b4709f2f415d928835a17dfd16991515c46bce" -dependencies = [ - "fastrand", - "futures-core", - "futures-io", - "memchr", - "parking", - "pin-project-lite", - "waker-fn", -] - [[package]] name = "generic-array" version = "0.14.6" @@ -885,7 +846,6 @@ name = "is_ci" version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "616cde7c720bb2bb5824a224687d8f77bfd38922027f01d825cd7453be5099fb" - [[package]] name = "isahc" version = "1.7.2" @@ -1096,12 +1056,6 @@ dependencies = [ "syn 1.0.107", ] -[[package]] -name = "mime" -version = "0.3.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" - [[package]] name = "minimal-lexical" version = "0.2.1" @@ -1239,12 +1193,6 @@ version = "3.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c1b04fb49957986fdce4d6ee7a65027d55d4b6d2265e5848bbb507b58ccfdb6f" -[[package]] -name = "parking" -version = "2.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14f2252c834a40ed9bb5422029649578e63aa341ac401f74e719dd1afda8394e" - [[package]] name = "parking_lot" version = "0.12.1" @@ -1265,7 +1213,7 @@ dependencies = [ "libc", "redox_syscall", "smallvec", - "windows-sys 0.42.0", + "windows-sys", ] [[package]] @@ -1363,12 +1311,6 @@ version = "0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116" -[[package]] -name = "pkg-config" -version = "0.3.27" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26072860ba924cbfa98ea39c8c19b4dd6a4a25423dbdf219c1eca91aa0cf6964" - [[package]] name = "pmutil" version = "0.6.1" @@ -1700,15 +1642,6 @@ dependencies = [ "winapi-util", ] -[[package]] -name = "schannel" -version = "0.1.22" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c3733bf4cf7ea0880754e19cb5a462007c4a8c1914bff372ccc95b464f1df88" -dependencies = [ - "windows-sys 0.48.0", -] - [[package]] name = "scoped-tls" version = "1.0.1" @@ -1864,16 +1797,6 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f67ad224767faa3c7d8b6d91985b78e70a1324408abcb1cfcc2be4c06bc06043" -[[package]] -name = "socket2" -version = "0.4.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64a4a911eed85daf18834cfaa86a79b7d266ff93ff5ba14005426219480ed662" -dependencies = [ - "libc", - "winapi", -] - [[package]] name = "sourcemap" version = "6.2.1" @@ -3108,16 +3031,6 @@ dependencies = [ "valuable", ] -[[package]] -name = "tracing-futures" -version = "0.2.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97d095ae15e245a057c8e8451bab9b3ee1e1f68e9ba2b4fbc18d0ac5237835f2" -dependencies = [ - "pin-project", - "tracing", -] - [[package]] name = "tracing-log" version = "0.2.0" @@ -3264,11 +3177,14 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b11c96ac7ee530603dcdf68ed1557050f374ce55a5a07193ebf8cbc9f8927e9" dependencies = [ "base64 0.21.2", + "encoding_rs", "flate2", "log", "once_cell", "rustls", "rustls-webpki 0.100.1", + "serde", + "serde_json", "url", "webpki-roots", ] @@ -3296,12 +3212,6 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" -[[package]] -name = "vcpkg" -version = "0.2.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" - [[package]] name = "vergen" version = "8.2.6" @@ -3318,12 +3228,6 @@ version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" -[[package]] -name = "waker-fn" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d5b2c62b4012a3e1eca5a7e077d13b3bf498c4073e33ccd58626607748ceeca" - [[package]] name = "walkdir" version = "2.3.2" diff --git a/crates/forge_analyzer/src/engine.rs b/crates/forge_analyzer/src/engine.rs index 319a3d4f..43204fba 100644 --- a/crates/forge_analyzer/src/engine.rs +++ b/crates/forge_analyzer/src/engine.rs @@ -117,6 +117,7 @@ impl<'ctx> Machine<'ctx> { Inst::Step(next_block, next_stmt) => { debug!(?next_block, ?next_stmt, "stepping into"); self.eip = (next_block, next_stmt); + // we need to return if possible if let Some(val) = self.transfer() { if let Some(ret) = self.callstack.pop() { debug!(?ret, "returning"); From a1cb91348ed7e1fc306579d313b545eff8f38bbe Mon Sep 17 00:00:00 2001 From: James Gersbach Date: Sat, 5 Aug 2023 16:28:55 -0700 Subject: [PATCH 460/517] erros with defintion analysis -- correct intermixing of exprs and quasis --- crates/forge_analyzer/src/checkers.rs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/crates/forge_analyzer/src/checkers.rs b/crates/forge_analyzer/src/checkers.rs index 5cb00a51..00248931 100644 --- a/crates/forge_analyzer/src/checkers.rs +++ b/crates/forge_analyzer/src/checkers.rs @@ -1441,6 +1441,8 @@ impl<'cx> Dataflow<'cx> for PermissionDataflow { } } Rvalue::Template(template) => { + // trying to get the template stirngs in order .... + let quasis_joined = template.quasis.join(""); let mut all_potential_values = vec![quasis_joined]; for expr in &template.exprs { @@ -1470,6 +1472,9 @@ impl<'cx> Dataflow<'cx> for PermissionDataflow { _ => {} } } + + println!("all potential values {all_potential_values:?} {all_values:?}"); + all_potential_values = all_values; } if all_potential_values.len() > 1 { @@ -1477,6 +1482,8 @@ impl<'cx> Dataflow<'cx> for PermissionDataflow { .into_iter() .map(|value| Const::Literal(value.clone())) .collect::>(); + + println!("consts == {consts:?}"); let value = Value::Phi(consts); self.add_value(def, *varid, value.clone()); } else if all_potential_values.len() == 1 { From 0f1d184470b5d8406cb0546495d676e3ad9e5d76 Mon Sep 17 00:00:00 2001 From: James Gersbach Date: Sun, 6 Aug 2023 11:51:51 -0700 Subject: [PATCH 461/517] working module exports --- crates/forge_analyzer/src/checkers.rs | 79 ++++- crates/forge_analyzer/src/definitions.rs | 7 + crates/forge_analyzer/src/interp.rs | 1 + crates/forge_analyzer/src/ir.rs | 1 + crates/forge_analyzer/src/lib.rs | 1 - .../src/permissionclassifier.rs | 280 ------------------ crates/forge_analyzer/src/reporter.rs | 1 - crates/forge_permission_resolver/src/test.rs | 4 + 8 files changed, 76 insertions(+), 298 deletions(-) delete mode 100644 crates/forge_analyzer/src/permissionclassifier.rs diff --git a/crates/forge_analyzer/src/checkers.rs b/crates/forge_analyzer/src/checkers.rs index 00248931..364a2452 100644 --- a/crates/forge_analyzer/src/checkers.rs +++ b/crates/forge_analyzer/src/checkers.rs @@ -12,7 +12,6 @@ use crate::{ Base, BasicBlock, BasicBlockId, BinOp, Inst, Intrinsic, Literal, Location, Operand, Rvalue, Successors, VarId, VarKind, }, - permissionclassifier::check_permission_used, reporter::{IntoVuln, Reporter, Severity, Vulnerability}, worklist::WorkList, }; @@ -1188,7 +1187,10 @@ impl<'cx> Dataflow<'cx> for PermissionDataflow { if let Some(operand) = second { self.handle_second_arg(_interp, operand, _def, &mut intrinsic_argument); } - let mut permissions_within_call: Vec = vec![]; + + // CLEAN UP + + let mut permissions_within_call: Vec = vec![]; let intrinsic_func_type = intrinsic_argument.name.unwrap(); intrinsic_argument .first_arg @@ -1198,20 +1200,49 @@ impl<'cx> Dataflow<'cx> for PermissionDataflow { first_arg_vec.iter().for_each(|first_arg| { println!("first arg ===> {first_arg:?}"); second_arg_vec.iter().for_each(|second_arg| { - let permissions = check_permission_used( - intrinsic_func_type, - first_arg, - Some(second_arg), - ); - permissions_within_call.extend_from_slice(&permissions); + if intrinsic_func_type == IntrinsicName::RequestConfluence { + let permissions = check_url_for_permissions( + &_interp.confluence_permission_resolver, + &_interp.confluence_regex_map, + trnaslate_request_type(Some(second_arg)), + &first_arg, + ); + permissions_within_call.extend_from_slice(&permissions) + // change this to seoiimthinf abdonaofoadinadsjoklj + } else if intrinsic_func_type == IntrinsicName::RequestJira { + let permissions = check_url_for_permissions( + &_interp.jira_permission_resolver, + &_interp.jira_regex_map, + trnaslate_request_type(Some(second_arg)), + &first_arg, + ); + permissions_within_call.extend_from_slice(&permissions) + } }) }) } else { first_arg_vec.iter().for_each(|first_arg| { println!("first arg ===> {first_arg:?}"); - let permissions = - check_permission_used(intrinsic_func_type, first_arg, None); - permissions_within_call.extend_from_slice(&permissions); + if intrinsic_func_type == IntrinsicName::RequestConfluence { + let permissions = check_url_for_permissions( + &_interp.confluence_permission_resolver, + &_interp.confluence_regex_map, + RequestType::Get, + &first_arg, + ); + permissions_within_call.extend_from_slice(&permissions) + // change this to seoiimthinf abdonaofoadinadsjoklj + } else if intrinsic_func_type == IntrinsicName::RequestJira { + let permissions = check_url_for_permissions( + &_interp.jira_permission_resolver, + &_interp.jira_regex_map, + RequestType::Get, + &first_arg, + ); + permissions_within_call.extend_from_slice(&permissions) + } + + //HERE }) } }); @@ -1221,6 +1252,8 @@ impl<'cx> Dataflow<'cx> for PermissionDataflow { _interp .permissions .extend_from_slice(&permissions_within_call); + + println!("_interp permisisions {:?}", _interp.permissions); } initial_state } @@ -1711,15 +1744,29 @@ fn return_value_from_string(values: Vec) -> Value { } } +fn trnaslate_request_type(request_type: Option<&str>) -> RequestType { + if let Some(request_type) = request_type { + match request_type { + "PATCH" => RequestType::Patch, + "PUT" => RequestType::Put, + "DELETE" => RequestType::Delete, + "POST" => RequestType::Post, + _ => RequestType::Get, + } + } else { + return RequestType::Get; + } +} + pub struct PermissionChecker { pub visit: bool, pub vulns: Vec, - pub declared_permissions: HashSet, - pub used_permissions: HashSet, + pub declared_permissions: HashSet, + pub used_permissions: HashSet, } impl PermissionChecker { - pub fn new(declared_permissions: HashSet) -> Self { + pub fn new(declared_permissions: HashSet) -> Self { Self { visit: false, vulns: vec![], @@ -1797,11 +1844,11 @@ impl<'cx> Checker<'cx> for PermissionChecker { #[derive(Debug)] pub struct PermissionVuln { - unused_permissions: HashSet, + unused_permissions: HashSet, } impl PermissionVuln { - pub fn new(unused_permissions: HashSet) -> PermissionVuln { + pub fn new(unused_permissions: HashSet) -> PermissionVuln { PermissionVuln { unused_permissions } } } diff --git a/crates/forge_analyzer/src/definitions.rs b/crates/forge_analyzer/src/definitions.rs index 1310c802..7607adf3 100644 --- a/crates/forge_analyzer/src/definitions.rs +++ b/crates/forge_analyzer/src/definitions.rs @@ -3283,6 +3283,13 @@ impl Visit for ExportCollector<'_> { Expr::Fn(FnExpr { ident, function }) => { self.add_export(DefRes::Function(()), ident_property.to_id()); } + Expr::Ident(ident) => { + let export_defid = + self.add_export(DefRes::Function(()), ident_property.to_id()); + self.res_table + .exported_names + .insert((ident.sym.clone(), self.curr_mod), export_defid); + } _ => {} } } diff --git a/crates/forge_analyzer/src/interp.rs b/crates/forge_analyzer/src/interp.rs index b53b839c..d6978426 100644 --- a/crates/forge_analyzer/src/interp.rs +++ b/crates/forge_analyzer/src/interp.rs @@ -799,6 +799,7 @@ impl<'cx, C: Runner<'cx>> Interp<'cx, C> { let arguments = self.callstack_arguments.pop(); let name = self.env.def_name(def); debug!("Dataflow: {name} - {block_id}"); + println!("Dataflow: {def:?} {name} - {block_id}"); self.dataflow_visited.insert(def); let func = self.env().def_ref(def).expect_body(); self.curr_body.set(Some(func)); diff --git a/crates/forge_analyzer/src/ir.rs b/crates/forge_analyzer/src/ir.rs index 3cb44289..7964c3ac 100644 --- a/crates/forge_analyzer/src/ir.rs +++ b/crates/forge_analyzer/src/ir.rs @@ -412,6 +412,7 @@ impl Body { #[inline] pub(crate) fn push_inst(&mut self, bb: BasicBlockId, inst: Inst) { + println!("{:?} {inst}", self.owner); self.blocks[bb].insts.push(inst); } diff --git a/crates/forge_analyzer/src/lib.rs b/crates/forge_analyzer/src/lib.rs index f9a56e97..00bf765f 100644 --- a/crates/forge_analyzer/src/lib.rs +++ b/crates/forge_analyzer/src/lib.rs @@ -7,7 +7,6 @@ pub mod exports; pub mod interp; pub mod ir; pub mod lattice; -pub mod permissionclassifier; pub mod pretty; pub mod reporter; pub mod resolver; diff --git a/crates/forge_analyzer/src/permissionclassifier.rs b/crates/forge_analyzer/src/permissionclassifier.rs deleted file mode 100644 index 2ab45477..00000000 --- a/crates/forge_analyzer/src/permissionclassifier.rs +++ /dev/null @@ -1,280 +0,0 @@ -use crate::definitions::IntrinsicName; -use forge_loader::forgepermissions::ForgePermissions; - -pub(crate) fn check_permission_used( - function_name: IntrinsicName, - first_arg: &str, - second_arg: Option<&str>, -) -> Vec { - let mut used_permissions: Vec = Vec::new(); - - let post_call = second_arg.unwrap_or("").contains("POST"); - let delete_call = second_arg.unwrap_or("").contains("DELTE"); - let put_call = second_arg.unwrap_or("").contains("PUT"); - - let contains_audit = first_arg.contains("audit"); - let contains_issue = first_arg.contains("issue"); - let contains_content = first_arg.contains("content"); - let contains_user = first_arg.contains("user"); - let contains_theme = first_arg.contains("theme"); - let contains_template = first_arg.contains("template"); - let contains_space = first_arg.contains("space"); - let contains_analytics = first_arg.contains("analytics"); - let contains_cql = first_arg.contains("cql"); - let contains_attachment = first_arg.contains("attachment"); - let contains_contentbody = first_arg.contains("contentbody"); - let contians_permissions = first_arg.contains("permissions"); - let contains_property = first_arg.contains("property"); - let contains_page_tree = first_arg.contains("pageTree"); - let contains_group = first_arg.contains("group"); - let contains_inlinetasks = first_arg.contains("inlinetasks"); - let contains_relation = first_arg.contains("relation"); - let contains_settings = first_arg.contains("settings"); - let contains_permission = first_arg.contains("permission"); - let contains_download = first_arg.contains("download"); - let contains_descendants = first_arg.contains("descendants"); - let contains_comment = first_arg.contains("comment"); - let contains_label = first_arg.contains("contains_label"); - let contains_search = first_arg.contains("contains_search"); - let contains_longtask = first_arg.contains("contains_longtask"); - let contains_notification = first_arg.contains("notification"); - let contains_watch = first_arg.contains("watch"); - let contains_version = first_arg.contains("version"); - let contains_state = first_arg.contains("contains_state"); - let contains_available = first_arg.contains("available"); - let contains_announcement_banner = first_arg.contains("announcementBanner"); - let contains_avatar = first_arg.contains("avatar"); - let contains_size = first_arg.contains("size"); - let contains_dashboard = first_arg.contains("dashboard"); - let contains_gadget = first_arg.contains("gadget"); - let contains_filter = first_arg.contains("filter"); - let contains_tracking = first_arg.contains("tracking"); - let contains_groupuserpicker = first_arg.contains("groupuserpicker"); - let contains_workflow = first_arg.contains("workflow"); - let contains_status = first_arg.contains("status"); - let contains_task = first_arg.contains("task"); - let contains_screen = first_arg.contains("screen"); - let non_get_call = post_call || delete_call || put_call; - let contains_webhook = first_arg.contains("webhook"); - let contains_project = first_arg.contains("project"); - let contains_actor = first_arg.contains("actor"); - let contains_role = first_arg.contains("contains_role"); - let contains_project_validate = first_arg.contains("projectvalidate"); - let contains_email = first_arg.contains("email"); - let contains_notification_scheme = first_arg.contains("notificationscheme"); - let contains_priority = first_arg.contains("priority"); - let contains_properties = first_arg.contains("properties"); - let contains_remote_link = first_arg.contains("remotelink"); - let contains_resolution = first_arg.contains("resolution"); - let contains_security_level = first_arg.contains("securitylevel"); - let contains_issue_security_schemes = first_arg.contains("issuesecurityschemes"); - let contains_issue_type = first_arg.contains("issuetype"); - let contains_issue_type_schemes = first_arg.contains("issuetypescheme"); - let contains_votes = first_arg.contains("contains_votes"); - let contains_worklog = first_arg.contains("worklog"); - let contains_expression = first_arg.contains("expression"); - let contains_configuration = first_arg.contains("configuration"); - let contains_application_properties = first_arg.contains("application-properties"); - - match function_name { - IntrinsicName::RequestJira => { - if (contains_dashboard && non_get_call) - || (contains_user && non_get_call) - || contains_task - { - used_permissions.push(ForgePermissions::WriteJiraWork); - if contains_gadget { - used_permissions.push(ForgePermissions::ReadJiraWork) - } - } else if contains_expression { - used_permissions.push(ForgePermissions::ReadJiraUser); - used_permissions.push(ForgePermissions::ReadJiraUser) - } else if (contains_avatar && contains_size) - || contains_dashboard - || contains_status - || contains_groupuserpicker - { - used_permissions.push(ForgePermissions::ReadJiraWork) - } else if (!non_get_call && contains_user) || contains_configuration { - used_permissions.push(ForgePermissions::ReadJiraUser) - } else if contains_webhook { - used_permissions.push(ForgePermissions::ManageJiraWebhook); - used_permissions.push(ForgePermissions::ReadJiraWork) - } else if (contains_remote_link && non_get_call) - || (contains_issue && contains_votes && non_get_call) - || (contains_worklog && non_get_call) - { - used_permissions.push(ForgePermissions::WriteJiraWork) - } else if (contains_issue_type && non_get_call) - || (contains_issue_type && non_get_call) - || (contains_project && non_get_call) - || (contains_project && contains_actor) - || (contains_project && contains_role) - || (contains_project && contains_email) - || (contains_priority && (non_get_call || contains_search)) - || (contains_properties && contains_issue && non_get_call) - || (contains_resolution && non_get_call) - || contains_audit - || contains_avatar - || contains_workflow - || contains_tracking - || contains_status - || contains_screen - || contains_notification_scheme - || contains_security_level - || contains_issue_security_schemes - || contains_issue_type_schemes - || contains_announcement_banner - || contains_application_properties - { - used_permissions.push(ForgePermissions::ManageJiraConfiguration) - } else if contains_filter { - if non_get_call { - used_permissions.push(ForgePermissions::WriteJiraWork) - } else { - used_permissions.push(ForgePermissions::ReadJiraWork) - } - } else if contains_project - || contains_project_validate - || contains_priority - || contains_search - || contains_issue_type - || (contains_issue && contains_votes) - || (contains_properties && contains_issue) - || (contains_remote_link && !non_get_call) - || (contains_resolution && !non_get_call) - || contains_worklog - { - used_permissions.push(ForgePermissions::ReadJiraWork) - } else if post_call { - if contains_issue { - used_permissions.push(ForgePermissions::WriteJiraWork); - } else { - used_permissions.push(ForgePermissions::Unknown); - } - } else { - if contains_issue { - used_permissions.push(ForgePermissions::ReadJiraWork); - } else { - used_permissions.push(ForgePermissions::Unknown); - } - } - } - IntrinsicName::RequestConfluence => { - if non_get_call { - if contains_content { - used_permissions.push(ForgePermissions::WriteConfluenceContent); - } else if contains_audit { - used_permissions.push(ForgePermissions::WriteAuditLogsConfluence); - if post_call { - used_permissions.push(ForgePermissions::ReadAuditLogsConfluence); - } - } else if contains_content && contains_attachment { - if put_call { - // review this more specifically - // /wiki/rest/api/content/{id}/child/attachment/{attachmentId}`, - used_permissions.push(ForgePermissions::WriteConfluenceFile); - used_permissions.push(ForgePermissions::WriteConfluenceProps) - } else { - used_permissions.push(ForgePermissions::WriteConfluenceFile) - } - } else if contains_contentbody { - used_permissions.push(ForgePermissions::ReadConfluenceContentAll) - } else if contains_content && contians_permissions { - used_permissions.push(ForgePermissions::ReadConfluenceContentPermission) - } else if contains_property { - used_permissions.push(ForgePermissions::WriteConfluenceProps) - } else if contains_content - || contains_page_tree - || contains_relation - || contains_template - { - used_permissions.push(ForgePermissions::WriteConfluenceContent) - } else if contains_group { - used_permissions.push(ForgePermissions::WriteConfluenceGroups) - } else if contains_settings { - used_permissions.push(ForgePermissions::ManageConfluenceConfiguration) - } else if contains_space && contains_permission { - if !delete_call { - used_permissions.push(ForgePermissions::ReadSpacePermissionConfluence); - } - used_permissions.push(ForgePermissions::WriteSpacePermissionsConfluence) - } else if contains_space || contains_theme { - used_permissions.push(ForgePermissions::WriteConfluenceSpace); - } else if contains_inlinetasks { - used_permissions.push(ForgePermissions::WriteInlineTaskConfluence) - } else if contains_user && contains_property { - used_permissions.push(ForgePermissions::WriteUserPropertyConfluence); - } else { - used_permissions.push(ForgePermissions::Unknown); - } - } else { - if contains_issue { - used_permissions.push(ForgePermissions::ReadJiraWork); - } else if contains_audit { - used_permissions.push(ForgePermissions::ReadAuditLogsConfluence) - } else if contains_cql { - if contains_user { - used_permissions.push(ForgePermissions::ReadContentDetailsConfluence); - } else { - used_permissions.push(ForgePermissions::SearchConfluence); - } - } else if contains_attachment && contains_download { - used_permissions.push(ForgePermissions::ReadOnlyContentAttachmentConfluence) - } else if contains_longtask { - used_permissions.push(ForgePermissions::ReadContentMetadataConfluence); - used_permissions.push(ForgePermissions::ReadConfluenceSpaceSummary) - } else if contains_content && contains_property { - used_permissions.push(ForgePermissions::ReadConfluenceProps); - } else if contains_template - || contains_relation - || (contains_content - && (contains_comment || contains_descendants || contains_label)) - { - used_permissions.push(ForgePermissions::ReadConfluenceContentSummary) - } else if contains_space && contains_settings { - used_permissions.push(ForgePermissions::ReadConfluenceSpaceSummary) - } else if contains_space && contains_theme { - used_permissions.push(ForgePermissions::ManageConfluenceConfiguration) - } else if contains_space && contains_content && contains_state { - used_permissions.push(ForgePermissions::ReadConfluenceContentAll) - } else if contains_space && contains_content { - used_permissions.push(ForgePermissions::ReadConfluenceContentSummary) - } else if contains_state && contains_content && contains_available { - used_permissions.push(ForgePermissions::WriteConfluenceContent) - } else if contains_content - && (contains_notification - || contains_watch - || contains_version - || contains_state) - { - used_permissions.push(ForgePermissions::ReadConfluenceContentSummary) - } else if contains_space { - used_permissions.push(ForgePermissions::ReadConfluenceProps) - } else if contains_content || contains_analytics { - used_permissions.push(ForgePermissions::ReadConfluenceContentAll) - } else if contains_user && contains_property { - used_permissions.push(ForgePermissions::WriteUserPropertyConfluence) - } else if contains_settings { - used_permissions.push(ForgePermissions::ManageConfluenceConfiguration) - } else if contains_search { - used_permissions.push(ForgePermissions::ReadContentDetailsConfluence) - } else if contains_space { - used_permissions.push(ForgePermissions::ReadConfluenceSpaceSummary) - } else if contains_user { - used_permissions.push(ForgePermissions::ReadConfluenceUser) - } else if contains_label { - used_permissions.push(ForgePermissions::ReadConfluenceContentSummary) - } else if contains_inlinetasks { - used_permissions.push(ForgePermissions::ReadConfluenceContentAll); - } else { - used_permissions.push(ForgePermissions::Unknown); - } - } - } - _ => { - used_permissions.push(ForgePermissions::Unknown); - } - } - used_permissions -} diff --git a/crates/forge_analyzer/src/reporter.rs b/crates/forge_analyzer/src/reporter.rs index f5cd6441..0fb853a4 100644 --- a/crates/forge_analyzer/src/reporter.rs +++ b/crates/forge_analyzer/src/reporter.rs @@ -1,4 +1,3 @@ -use forge_loader::forgepermissions::ForgePermissions; use serde::Serialize; use time::{Date, OffsetDateTime}; diff --git a/crates/forge_permission_resolver/src/test.rs b/crates/forge_permission_resolver/src/test.rs index 4532e364..c8d95f4d 100644 --- a/crates/forge_permission_resolver/src/test.rs +++ b/crates/forge_permission_resolver/src/test.rs @@ -3,6 +3,10 @@ use crate::permissions_resolver::{ }; mod tests { + use crate::permissions_resolver::{ + get_permission_resolver_confluence, get_permission_resolver_jira, + }; + use super::*; use crate::permissions_resolver::{ get_permission_resolver_confluence, get_permission_resolver_jira, From c492533ad7e18eeda423f3d411fa7e1baf7dc696 Mon Sep 17 00:00:00 2001 From: James Gersbach Date: Sun, 6 Aug 2023 14:06:08 -0700 Subject: [PATCH 462/517] formatting --- crates/forge_analyzer/src/checkers.rs | 4 ---- crates/forge_analyzer/src/ir.rs | 1 - 2 files changed, 5 deletions(-) diff --git a/crates/forge_analyzer/src/checkers.rs b/crates/forge_analyzer/src/checkers.rs index 364a2452..a827d255 100644 --- a/crates/forge_analyzer/src/checkers.rs +++ b/crates/forge_analyzer/src/checkers.rs @@ -1208,7 +1208,6 @@ impl<'cx> Dataflow<'cx> for PermissionDataflow { &first_arg, ); permissions_within_call.extend_from_slice(&permissions) - // change this to seoiimthinf abdonaofoadinadsjoklj } else if intrinsic_func_type == IntrinsicName::RequestJira { let permissions = check_url_for_permissions( &_interp.jira_permission_resolver, @@ -1231,7 +1230,6 @@ impl<'cx> Dataflow<'cx> for PermissionDataflow { &first_arg, ); permissions_within_call.extend_from_slice(&permissions) - // change this to seoiimthinf abdonaofoadinadsjoklj } else if intrinsic_func_type == IntrinsicName::RequestJira { let permissions = check_url_for_permissions( &_interp.jira_permission_resolver, @@ -1474,8 +1472,6 @@ impl<'cx> Dataflow<'cx> for PermissionDataflow { } } Rvalue::Template(template) => { - // trying to get the template stirngs in order .... - let quasis_joined = template.quasis.join(""); let mut all_potential_values = vec![quasis_joined]; for expr in &template.exprs { diff --git a/crates/forge_analyzer/src/ir.rs b/crates/forge_analyzer/src/ir.rs index 7964c3ac..3cb44289 100644 --- a/crates/forge_analyzer/src/ir.rs +++ b/crates/forge_analyzer/src/ir.rs @@ -412,7 +412,6 @@ impl Body { #[inline] pub(crate) fn push_inst(&mut self, bb: BasicBlockId, inst: Inst) { - println!("{:?} {inst}", self.owner); self.blocks[bb].insts.push(inst); } From d05751de96f9178340e5c44cb45ec1361a48cef4 Mon Sep 17 00:00:00 2001 From: James Gersbach Date: Sun, 6 Aug 2023 19:46:05 -0700 Subject: [PATCH 463/517] working body definition analysis --- crates/forge_analyzer/src/checkers.rs | 137 ++++++++++-------- crates/forge_analyzer/src/definitions.rs | 3 +- crates/forge_analyzer/src/interp.rs | 2 + .../src/utils.js | 2 +- 4 files changed, 77 insertions(+), 67 deletions(-) diff --git a/crates/forge_analyzer/src/checkers.rs b/crates/forge_analyzer/src/checkers.rs index a827d255..0c7edb79 100644 --- a/crates/forge_analyzer/src/checkers.rs +++ b/crates/forge_analyzer/src/checkers.rs @@ -1042,7 +1042,7 @@ impl PermissionDataflow { } Operand::Var(var) => { if let Base::Var(varid) = var.base { - if let Some(value) = self.get_value(_def, varid) { + if let Some(value) = self.get_value(_def, varid, None) { intrinsic_argument.first_arg = Some(vec![]); add_elements_to_intrinsic_struct(value, &mut intrinsic_argument.first_arg); } @@ -1051,12 +1051,25 @@ impl PermissionDataflow { } } - fn add_value(&mut self, defid_block: DefId, varid: VarId, value: Value) { - self.varid_to_value.insert(varid, value); + fn add_value( + &mut self, + defid_block: DefId, + varid: VarId, + value: Value, + projection: Option, + ) { + println!("varid from {varid:?} -- {projection:?} -- {value:?}"); + self.varid_to_value + .insert((defid_block, varid, projection), value); } - fn get_value(&self, defid_block: DefId, varid: VarId) -> Option<&Value> { - self.varid_to_value.get(&varid) + fn get_value( + &self, + defid_block: DefId, + varid: VarId, + projection: Option, + ) -> Option<&Value> { + self.varid_to_value.get(&(defid_block, varid, projection)) } fn get_str_from_expr(&self, expr: &Operand, def: DefId) -> Vec> { @@ -1064,7 +1077,7 @@ impl PermissionDataflow { return vec![Some(str)]; } else if let Operand::Var(var) = expr { if let Base::Var(varid) = var.base { - let value = self.get_value(def, varid); + let value = self.get_value(def, varid, None); if let Some(value) = value { match value { Value::Const(const_val) => { @@ -1105,19 +1118,6 @@ impl<'cx> Dataflow<'cx> for PermissionDataflow { } } - fn try_insert>( - &self, - _interp: &Interp<'cx, C>, - _def: DefId, - const_var: Const, - intrinsic_argument: &mut IntrinsicArguments, - ) { - if let Some(val) = self.try_read_mem_from_object(_interp, _def.clone(), const_var.clone()) { - intrinsic_argument.second_arg = Some(vec![]); - add_elements_to_intrinsic_struct(val, &mut intrinsic_argument.second_arg); - } - } - fn handle_second_arg>( &self, _interp: &Interp<'cx, C>, @@ -1188,8 +1188,6 @@ impl<'cx> Dataflow<'cx> for PermissionDataflow { self.handle_second_arg(_interp, operand, _def, &mut intrinsic_argument); } - // CLEAN UP - let mut permissions_within_call: Vec = vec![]; let intrinsic_func_type = intrinsic_argument.name.unwrap(); intrinsic_argument @@ -1198,7 +1196,6 @@ impl<'cx> Dataflow<'cx> for PermissionDataflow { .for_each(|first_arg_vec| { if let Some(second_arg_vec) = intrinsic_argument.second_arg.clone() { first_arg_vec.iter().for_each(|first_arg| { - println!("first arg ===> {first_arg:?}"); second_arg_vec.iter().for_each(|second_arg| { if intrinsic_func_type == IntrinsicName::RequestConfluence { let permissions = check_url_for_permissions( @@ -1221,7 +1218,6 @@ impl<'cx> Dataflow<'cx> for PermissionDataflow { }) } else { first_arg_vec.iter().for_each(|first_arg| { - println!("first arg ===> {first_arg:?}"); if intrinsic_func_type == IntrinsicName::RequestConfluence { let permissions = check_url_for_permissions( &_interp.confluence_permission_resolver, @@ -1239,19 +1235,13 @@ impl<'cx> Dataflow<'cx> for PermissionDataflow { ); permissions_within_call.extend_from_slice(&permissions) } - - //HERE }) } }); - println!("permissions within call {permissions_within_call:?}"); - _interp .permissions .extend_from_slice(&permissions_within_call); - - println!("_interp permisisions {:?}", _interp.permissions); } initial_state } @@ -1371,8 +1361,8 @@ impl<'cx> Dataflow<'cx> for PermissionDataflow { _def: DefId, operand: &Operand, ) -> Option<&Value> { - if let Some((_, varid)) = self.get_defid_from_operand(_interp, operand) { - return self.get_value(_def, varid); + if let Some((var, varid)) = resolve_var_from_operand(operand) { + return self.get_value(_def, varid, var.projections.get(0).cloned()); } None } @@ -1439,12 +1429,12 @@ impl<'cx> Dataflow<'cx> for PermissionDataflow { VarKind::Ret => { for (defid, (varid_value, defid_value)) in &interp.expecting_value { if def == defid.clone() { - if let Some(value) = self.get_value(def, varid) { - self.add_value(def, varid, value.clone()); + if let Some(value) = self.get_value(def, varid, None) { + self.add_value(def, varid, value.clone(), None); } } } - if let Some(value) = self.get_value(def, varid) { + if let Some(value) = self.get_value(def, varid, None) { interp.return_value = Some((value.clone(), def)); interp.return_value_alt.insert(def, value.clone()); } @@ -1459,16 +1449,34 @@ impl<'cx> Dataflow<'cx> for PermissionDataflow { fn add_variable>( &mut self, interp: &Interp<'cx, C>, + lval: &Variable, varid: &VarId, def: DefId, rvalue: &Rvalue, ) { match rvalue { Rvalue::Read(operand) => { - if let Some(value) = get_prev_value(self.get_value(def, *varid)) { - self.insert_value2(operand, varid, def, interp, Some(value)); + // transfer all of the variables + if let Operand::Var(variable) = operand { + if let Base::Var(varid_rval) = variable.base { + self.varid_to_value.clone().iter().for_each( + |((defid, varid_rval_potential, projection), value)| { + if varid_rval_potential == &varid_rval { + self.add_value(def, *varid, value.clone(), projection.clone()) + } + }, + ); + } } else { - self.insert_value2(operand, varid, def, interp, None); + if let Some(value) = get_prev_value(self.get_value( + def, + *varid, + lval.projections.get(0).cloned(), + )) { + self.insert_value(operand, lval, varid, def, interp, Some(value)); + } else { + self.insert_value(operand, lval, varid, def, interp, None); + } } } Rvalue::Template(template) => { @@ -1502,7 +1510,6 @@ impl<'cx> Dataflow<'cx> for PermissionDataflow { } } - println!("all potential values {all_potential_values:?} {all_values:?}"); all_potential_values = all_values; } @@ -1511,15 +1518,14 @@ impl<'cx> Dataflow<'cx> for PermissionDataflow { .into_iter() .map(|value| Const::Literal(value.clone())) .collect::>(); - - println!("consts == {consts:?}"); let value = Value::Phi(consts); - self.add_value(def, *varid, value.clone()); + self.add_value(def, *varid, value.clone(), None); } else if all_potential_values.len() == 1 { self.add_value( def, *varid, Value::Const(Const::Literal(all_potential_values.get(0).unwrap().clone())), + None, ); } } @@ -1547,11 +1553,11 @@ impl<'cx> Dataflow<'cx> for PermissionDataflow { _ => {} } self.varid_to_value - .insert(*varid, return_value_from_string(new_vals)); + .insert((def, *varid, None), return_value_from_string(new_vals)); } else if let Some(val1) = val1 { - self.add_value(def, *varid, val1); + self.add_value(def, *varid, val1, None); } else if let Some(val2) = val2 { - self.add_value(def, *varid, val2); + self.add_value(def, *varid, val2, None); } } } @@ -1559,9 +1565,10 @@ impl<'cx> Dataflow<'cx> for PermissionDataflow { } } - fn insert_value2>( + fn insert_value>( &mut self, operand: &Operand, + lval: &Variable, varid: &VarId, def: DefId, interp: &Interp<'cx, C>, @@ -1575,12 +1582,12 @@ impl<'cx> Dataflow<'cx> for PermissionDataflow { let mut all_values = prev_values.clone(); all_values.push(const_value); let value = Value::Phi(all_values); - self.add_value(def, *varid, value); + self.add_value(def, *varid, value, lval.projections.get(0).cloned()); } } else { if let Some(lit_value) = convert_operand_to_raw(operand) { let value = Value::Const(Const::Literal(lit_value)); - self.add_value(def, *varid, value); + self.add_value(def, *varid, value, lval.projections.get(0).cloned()); } } } @@ -1596,15 +1603,28 @@ impl<'cx> Dataflow<'cx> for PermissionDataflow { let mut all_values = prev_values.clone(); all_values.push(const_value); let value = Value::Phi(all_values); - self.add_value(def, *varid, value); + self.add_value( + def, + *varid, + value, + lval.projections.get(0).cloned(), + ); } else { let value = Value::Const(Const::Object(class)); - self.add_value(def, *varid, value); + self.add_value( + def, + *varid, + value, + lval.projections.get(0).cloned(), + ); } } } else { - if let Some(potential_value) = self.get_value(def, prev_varid) { - self.varid_to_value.insert(*varid, potential_value.clone()); + if let Some(potential_value) = self.get_value(def, prev_varid, None) { + self.varid_to_value.insert( + (def, *varid, var.projections.get(0).cloned()), + potential_value.clone(), + ); } } } @@ -1628,22 +1648,15 @@ impl<'cx> Dataflow<'cx> for PermissionDataflow { } } -pub(crate) fn resolve_var_from_operand(operand: &Operand) -> Option { +pub(crate) fn resolve_var_from_operand(operand: &Operand) -> Option<(Variable, VarId)> { if let Operand::Var(var) = operand { if let Base::Var(varid) = var.base { - return Some(varid); + return Some((var.clone(), varid)); } } None } -fn resolve_literal_from_operand(operand: &Operand) -> Option { - if let Operand::Lit(lit) = operand { - return Some(lit.clone()); - } - None -} - fn add_const_to_val_vec(val: &Value, const_val: &Const, vals: &mut Vec) { match val { Value::Const(Const::Literal(lit)) => { @@ -1830,10 +1843,6 @@ impl<'cx> Checker<'cx> for PermissionChecker { state: &Self::State, operands: Option>, ) -> ControlFlow<(), Self::State> { - for permission in &interp.permissions { - self.declared_permissions.remove(permission); - self.used_permissions.insert(permission.clone()); - } ControlFlow::Continue(*state) } } diff --git a/crates/forge_analyzer/src/definitions.rs b/crates/forge_analyzer/src/definitions.rs index 7607adf3..f60757e4 100644 --- a/crates/forge_analyzer/src/definitions.rs +++ b/crates/forge_analyzer/src/definitions.rs @@ -1515,7 +1515,7 @@ impl<'cx> FunctionAnalyzer<'cx> { let lowered_var = self.body.coerce_to_lval( self.block, lowered_value.clone(), - Some(next_key), + None, ); let rval = Rvalue::Read(lowered_value); @@ -3507,7 +3507,6 @@ impl Environment { fn add_class_method(&mut self, n: PropName, class_def: DefId, owner: DefId) { if let DefKind::Class(class) = self.def_mut(class_def) { if let PropName::Ident(ident) = &n { - println!("adding class method --"); class.pub_members.push((ident.sym.to_owned(), owner)); } } diff --git a/crates/forge_analyzer/src/interp.rs b/crates/forge_analyzer/src/interp.rs index d6978426..302499bb 100644 --- a/crates/forge_analyzer/src/interp.rs +++ b/crates/forge_analyzer/src/interp.rs @@ -159,6 +159,7 @@ pub trait Dataflow<'cx>: Sized { fn add_variable>( &mut self, interp: &Interp<'cx, C>, + lval: &Variable, varid: &VarId, def: DefId, rvalue: &Rvalue, @@ -167,6 +168,7 @@ pub trait Dataflow<'cx>: Sized { fn insert_value2>( &mut self, operand: &Operand, + lval: &Variable, varid: &VarId, def: DefId, interp: &Interp<'cx, C>, diff --git a/test-apps/jira-damn-vulnerable-forge-app/src/utils.js b/test-apps/jira-damn-vulnerable-forge-app/src/utils.js index 0e172968..5363beb6 100755 --- a/test-apps/jira-damn-vulnerable-forge-app/src/utils.js +++ b/test-apps/jira-damn-vulnerable-forge-app/src/utils.js @@ -25,7 +25,7 @@ let global = 'test'; export async function fetchIssueSummary(issueIdOrKey, test_value) { let obj = { - method: 'POST', + method: 'DELETE', bananas: 'apple', headers: { // From e45059e57a5851b67dd43cd21e2eb788e12d54ea Mon Sep 17 00:00:00 2001 From: James Gersbach Date: Mon, 7 Aug 2023 14:02:32 -0700 Subject: [PATCH 464/517] cleaning up codebase --- crates/forge_analyzer/src/checkers.rs | 165 ++++++------------ crates/forge_analyzer/src/interp.rs | 4 +- .../src/permissions_resolver.rs | 3 +- .../src/utils.js | 6 +- 4 files changed, 59 insertions(+), 119 deletions(-) diff --git a/crates/forge_analyzer/src/checkers.rs b/crates/forge_analyzer/src/checkers.rs index 0c7edb79..6ceaee55 100644 --- a/crates/forge_analyzer/src/checkers.rs +++ b/crates/forge_analyzer/src/checkers.rs @@ -13,6 +13,10 @@ use crate::{ Successors, VarId, VarKind, }, reporter::{IntoVuln, Reporter, Severity, Vulnerability}, + utils::{ + add_const_to_val_vec, add_elements_to_intrinsic_struct, convert_operand_to_raw, + get_defid_from_varkind, get_prev_value, get_str_from_operand, resolve_var_from_operand, + }, worklist::WorkList, }; @@ -781,6 +785,7 @@ impl<'cx> Runner<'cx> for SecretChecker { self.vulns.push(vuln); ControlFlow::Break(()) } + Intrinsic::JWTSign(_) => ControlFlow::Continue(*state), Intrinsic::ApiCall(_) => ControlFlow::Continue(*state), Intrinsic::SafeCall(_) => ControlFlow::Continue(*state), Intrinsic::EnvRead => ControlFlow::Continue(*state), @@ -839,6 +844,7 @@ impl<'cx> Dataflow<'cx> for AuthenticateDataflow { debug!("authenticated"); Authenticated::Yes } + Intrinsic::JWTSign(_) => initial_state, Intrinsic::ApiCall(_) => initial_state, Intrinsic::SafeCall(_) => initial_state, } @@ -936,6 +942,7 @@ impl<'cx> Checker<'cx> for AuthenticateChecker { self.vulns.push(vuln); ControlFlow::Break(()) } + Intrinsic::JWTSign(_) => ControlFlow::Continue(*state), Intrinsic::ApiCall(_) => ControlFlow::Continue(*state), Intrinsic::SafeCall(_) => ControlFlow::Continue(*state), } @@ -1051,6 +1058,44 @@ impl PermissionDataflow { } } + fn handle_second_arg( + &self, + operand: &Operand, + _def: DefId, + intrinsic_argument: &mut IntrinsicArguments, + ) { + if let Operand::Var(variable) = operand { + if let Base::Var(varid) = variable.base { + if let Some(value) = + self.get_value(_def, varid, Some(Projection::Known("method".into()))) + { + match value { + Value::Const(Const::Literal(lit)) => { + intrinsic_argument.second_arg = Some(vec![lit.to_string()]); + } + Value::Phi(phi_val) => { + intrinsic_argument.second_arg = Some( + phi_val + .iter() + .map(|data| { + if let Const::Literal(lit) = data { + return Some(lit.to_string()); + } else { + None + } + }) + .filter(|const_val| const_val != &None) + .map(|f| f.unwrap()) + .collect_vec(), + ) + } + _ => {} + } + } + } + } + } + fn add_value( &mut self, defid_block: DefId, @@ -1058,7 +1103,7 @@ impl PermissionDataflow { value: Value, projection: Option, ) { - println!("varid from {varid:?} -- {projection:?} -- {value:?}"); + // println!("varid from {varid:?} -- {projection:?} -- {value:?}"); self.varid_to_value .insert((defid_block, varid, projection), value); } @@ -1185,9 +1230,11 @@ impl<'cx> Dataflow<'cx> for PermissionDataflow { self.handle_first_arg(operand, _def, &mut intrinsic_argument); } if let Some(operand) = second { - self.handle_second_arg(_interp, operand, _def, &mut intrinsic_argument); + self.handle_second_arg(operand, _def, &mut intrinsic_argument); } + println!("intrinsic argument {intrinsic_argument:?}"); + let mut permissions_within_call: Vec = vec![]; let intrinsic_func_type = intrinsic_argument.name.unwrap(); intrinsic_argument @@ -1239,6 +1286,8 @@ impl<'cx> Dataflow<'cx> for PermissionDataflow { } }); + println!("permissions found {permissions_within_call:?}"); + _interp .permissions .extend_from_slice(&permissions_within_call); @@ -1515,6 +1564,7 @@ impl<'cx> Dataflow<'cx> for PermissionDataflow { if all_potential_values.len() > 1 { let consts = all_potential_values + .clone() .into_iter() .map(|value| Const::Literal(value.clone())) .collect::>(); @@ -1648,97 +1698,6 @@ impl<'cx> Dataflow<'cx> for PermissionDataflow { } } -pub(crate) fn resolve_var_from_operand(operand: &Operand) -> Option<(Variable, VarId)> { - if let Operand::Var(var) = operand { - if let Base::Var(varid) = var.base { - return Some((var.clone(), varid)); - } - } - None -} - -fn add_const_to_val_vec(val: &Value, const_val: &Const, vals: &mut Vec) { - match val { - Value::Const(Const::Literal(lit)) => { - if let Const::Literal(lit2) = const_val { - vals.push(lit.to_owned() + &lit2); - } - } - Value::Phi(phi_val2) => phi_val2.iter().for_each(|val2| { - if let (Const::Literal(lit1), Const::Literal(lit2)) = (&const_val, val2) { - vals.push(lit1.to_owned() + lit2); - } - }), - _ => {} - } -} - -pub(crate) fn get_defid_from_varkind(varkind: &VarKind) -> Option { - match varkind { - VarKind::GlobalRef(defid) => Some(defid.clone()), - VarKind::LocalDef(defid) => Some(defid.clone()), - VarKind::Arg(defid) => Some(defid.clone()), - VarKind::AnonClosure(defid) => Some(defid.clone()), - VarKind::Temp { parent } => parent.clone(), - _ => None, - } -} - -fn convert_operand_to_raw(operand: &Operand) -> Option { - if let Operand::Lit(lit) = operand { - convert_lit_to_raw(lit) - } else { - None - } -} - -fn convert_lit_to_raw(lit: &Literal) -> Option { - match lit { - Literal::BigInt(bigint) => Some(bigint.to_string()), - Literal::Number(num) => Some(num.to_string()), - Literal::Str(str) => Some(str.to_string()), - _ => None, - } -} - -fn get_str_from_operand(operand: &Operand) -> Option { - if let Operand::Lit(lit) = operand { - if let Literal::Str(str) = lit { - return Some(str.to_string()); - } - } - None -} - -fn add_elements_to_intrinsic_struct(value: &Value, args: &mut Option>) { - match value { - Value::Const(const_value) => { - if let Const::Literal(literal) = const_value { - args.as_mut().unwrap().push(literal.clone()); - } - } - Value::Phi(phi_value) => { - for value in phi_value { - if let Const::Literal(literal) = value { - args.as_mut().unwrap().push(literal.clone()); - } - } - } - _ => {} - } -} - -fn get_prev_value(value: Option<&Value>) -> Option> { - if let Some(value) = value { - return match value { - Value::Const(const_value) => Some(vec![const_value.clone()]), - Value::Phi(phi_value) => Some(phi_value.clone()), - _ => None, - }; - } - None -} - fn return_value_from_string(values: Vec) -> Value { // assert!(values.len() > 0); if values.len() == 1 { @@ -1827,26 +1786,6 @@ impl JoinSemiLattice for PermissionTest { } } -impl<'cx> Checker<'cx> for PermissionChecker { - type State = PermissionTest; - type Dataflow = PermissionDataflow; - type Vuln = PermissionVuln; - - fn visit(&mut self) -> bool { - self.visit - } - - fn visit_intrinsic( - &mut self, - interp: &Interp<'cx, Self>, - intrinsic: &'cx Intrinsic, - state: &Self::State, - operands: Option>, - ) -> ControlFlow<(), Self::State> { - ControlFlow::Continue(*state) - } -} - #[derive(Debug)] pub struct PermissionVuln { unused_permissions: HashSet, diff --git a/crates/forge_analyzer/src/interp.rs b/crates/forge_analyzer/src/interp.rs index 302499bb..b402f515 100644 --- a/crates/forge_analyzer/src/interp.rs +++ b/crates/forge_analyzer/src/interp.rs @@ -863,9 +863,7 @@ impl<'cx, C: Runner<'cx>> Interp<'cx, C> { })?; self.set_body(body); self.run(resolved_def); - if checker.visit() { - checker.visit_body(self, resolved_def, body, &C::State::BOTTOM); - } + checker.visit_body(self, resolved_def, body, &C::State::BOTTOM); Ok(()) } diff --git a/crates/forge_permission_resolver/src/permissions_resolver.rs b/crates/forge_permission_resolver/src/permissions_resolver.rs index 6a17d9db..e3982bbd 100644 --- a/crates/forge_permission_resolver/src/permissions_resolver.rs +++ b/crates/forge_permission_resolver/src/permissions_resolver.rs @@ -66,7 +66,8 @@ pub fn check_url_for_permissions( request: RequestType, url: &str, ) -> Vec { - // sort by the length of regex + println!("check_url_for_permissions {request:?} {url:?}"); + let mut length_of_regex = endpoint_regex .iter() .map(|(string, regex)| (regex.as_str().len(), &*string)) diff --git a/test-apps/jira-damn-vulnerable-forge-app/src/utils.js b/test-apps/jira-damn-vulnerable-forge-app/src/utils.js index 5363beb6..3fc6de88 100755 --- a/test-apps/jira-damn-vulnerable-forge-app/src/utils.js +++ b/test-apps/jira-damn-vulnerable-forge-app/src/utils.js @@ -25,7 +25,7 @@ let global = 'test'; export async function fetchIssueSummary(issueIdOrKey, test_value) { let obj = { - method: 'DELETE', + method: 'POST', bananas: 'apple', headers: { // @@ -33,6 +33,8 @@ export async function fetchIssueSummary(issueIdOrKey, test_value) { }, }; + var token = jwt.sign({ foo: 'bar' }, 'secret_token'); + testFunctionFromTestFile(); let val = 'grapefruit'; @@ -111,7 +113,7 @@ export async function writeComment(issueIdOrKey, comment) { const resp = await api .asApp() .requestJira(route`/rest/api/3/issue/${issueIdOrKey}/comment`, { - method: 'POST', + method: 'DELETE', headers: { Accept: 'application/json', 'Content-Type': 'application/json', From 9bcc6e3b4cfaeb0b32e2148482329382df9c2bb2 Mon Sep 17 00:00:00 2001 From: James Gersbach Date: Mon, 7 Aug 2023 14:52:55 -0700 Subject: [PATCH 465/517] failed attemp to put the definition analysis into the interp --- crates/forge_analyzer/src/checkers.rs | 287 ++++++++++++-------------- crates/forge_analyzer/src/interp.rs | 52 ++--- 2 files changed, 166 insertions(+), 173 deletions(-) diff --git a/crates/forge_analyzer/src/checkers.rs b/crates/forge_analyzer/src/checkers.rs index 6ceaee55..888544da 100644 --- a/crates/forge_analyzer/src/checkers.rs +++ b/crates/forge_analyzer/src/checkers.rs @@ -16,6 +16,7 @@ use crate::{ utils::{ add_const_to_val_vec, add_elements_to_intrinsic_struct, convert_operand_to_raw, get_defid_from_varkind, get_prev_value, get_str_from_operand, resolve_var_from_operand, + return_value_from_string, }, worklist::WorkList, }; @@ -1036,121 +1037,6 @@ pub struct IntrinsicArguments { second_arg: Option>, } -impl PermissionDataflow { - fn handle_first_arg( - &self, - operand: &Operand, - _def: DefId, - intrinsic_argument: &mut IntrinsicArguments, - ) { - match operand { - Operand::Lit(lit) => { - intrinsic_argument.first_arg = Some(vec![lit.to_string()]); - } - Operand::Var(var) => { - if let Base::Var(varid) = var.base { - if let Some(value) = self.get_value(_def, varid, None) { - intrinsic_argument.first_arg = Some(vec![]); - add_elements_to_intrinsic_struct(value, &mut intrinsic_argument.first_arg); - } - } - } - } - } - - fn handle_second_arg( - &self, - operand: &Operand, - _def: DefId, - intrinsic_argument: &mut IntrinsicArguments, - ) { - if let Operand::Var(variable) = operand { - if let Base::Var(varid) = variable.base { - if let Some(value) = - self.get_value(_def, varid, Some(Projection::Known("method".into()))) - { - match value { - Value::Const(Const::Literal(lit)) => { - intrinsic_argument.second_arg = Some(vec![lit.to_string()]); - } - Value::Phi(phi_val) => { - intrinsic_argument.second_arg = Some( - phi_val - .iter() - .map(|data| { - if let Const::Literal(lit) = data { - return Some(lit.to_string()); - } else { - None - } - }) - .filter(|const_val| const_val != &None) - .map(|f| f.unwrap()) - .collect_vec(), - ) - } - _ => {} - } - } - } - } - } - - fn add_value( - &mut self, - defid_block: DefId, - varid: VarId, - value: Value, - projection: Option, - ) { - // println!("varid from {varid:?} -- {projection:?} -- {value:?}"); - self.varid_to_value - .insert((defid_block, varid, projection), value); - } - - fn get_value( - &self, - defid_block: DefId, - varid: VarId, - projection: Option, - ) -> Option<&Value> { - self.varid_to_value.get(&(defid_block, varid, projection)) - } - - fn get_str_from_expr(&self, expr: &Operand, def: DefId) -> Vec> { - if let Some(str) = get_str_from_operand(expr) { - return vec![Some(str)]; - } else if let Operand::Var(var) = expr { - if let Base::Var(varid) = var.base { - let value = self.get_value(def, varid, None); - if let Some(value) = value { - match value { - Value::Const(const_val) => { - if let Const::Literal(str) = const_val { - return vec![Some(str.clone())]; - } - } - Value::Phi(phi_val) => { - return phi_val - .iter() - .map(|const_val| { - if let Const::Literal(str) = const_val { - Some(str.clone()) - } else { - None - } - }) - .collect_vec(); - } - _ => {} - } - } - } - } - vec![None] - } -} - impl<'cx> Dataflow<'cx> for PermissionDataflow { type State = PermissionTest; @@ -1227,10 +1113,10 @@ impl<'cx> Dataflow<'cx> for PermissionDataflow { intrinsic_argument.name = Some(name.clone()); let (first, second) = (operands.get(0), operands.get(1)); if let Some(operand) = first { - self.handle_first_arg(operand, _def, &mut intrinsic_argument); + self.handle_first_arg(_interp, operand, _def, &mut intrinsic_argument); } if let Some(operand) = second { - self.handle_second_arg(operand, _def, &mut intrinsic_argument); + self.handle_second_arg(_interp, operand, _def, &mut intrinsic_argument); } println!("intrinsic argument {intrinsic_argument:?}"); @@ -1473,19 +1359,19 @@ impl<'cx> Dataflow<'cx> for PermissionDataflow { } } - for (varid, varkind) in interp.body().vars.iter_enumerated() { + for (varid, varkind) in interp.body().vars.clone().iter_enumerated() { match varkind { VarKind::Ret => { for (defid, (varid_value, defid_value)) in &interp.expecting_value { if def == defid.clone() { - if let Some(value) = self.get_value(def, varid, None) { - self.add_value(def, varid, value.clone(), None); + if let Some(value) = interp.get_value(def, varid, None).clone() { + interp.add_value(def, varid, value.clone(), None); } } } - if let Some(value) = self.get_value(def, varid, None) { + if let Some(value) = interp.get_value(def, varid, None) { interp.return_value = Some((value.clone(), def)); - interp.return_value_alt.insert(def, value.clone()); + //interp.return_value_alt.insert(def, value.clone()); } } _ => {} @@ -1497,7 +1383,7 @@ impl<'cx> Dataflow<'cx> for PermissionDataflow { fn add_variable>( &mut self, - interp: &Interp<'cx, C>, + interp: &mut Interp<'cx, C>, lval: &Variable, varid: &VarId, def: DefId, @@ -1508,23 +1394,23 @@ impl<'cx> Dataflow<'cx> for PermissionDataflow { // transfer all of the variables if let Operand::Var(variable) = operand { if let Base::Var(varid_rval) = variable.base { - self.varid_to_value.clone().iter().for_each( + interp.varid_to_value.clone().iter().for_each( |((defid, varid_rval_potential, projection), value)| { if varid_rval_potential == &varid_rval { - self.add_value(def, *varid, value.clone(), projection.clone()) + interp.add_value(def, *varid, value.clone(), projection.clone()) } }, ); } } else { - if let Some(value) = get_prev_value(self.get_value( + if let Some(value) = get_prev_value(interp.get_value( def, *varid, lval.projections.get(0).cloned(), )) { - self.insert_value(operand, lval, varid, def, interp, Some(value)); + self.insert_value(interp, operand, lval, varid, def, Some(value)); } else { - self.insert_value(operand, lval, varid, def, interp, None); + self.insert_value(interp, operand, lval, varid, def, None); } } } @@ -1569,9 +1455,9 @@ impl<'cx> Dataflow<'cx> for PermissionDataflow { .map(|value| Const::Literal(value.clone())) .collect::>(); let value = Value::Phi(consts); - self.add_value(def, *varid, value.clone(), None); + interp.add_value(def, *varid, value.clone(), None); } else if all_potential_values.len() == 1 { - self.add_value( + interp.add_value( def, *varid, Value::Const(Const::Literal(all_potential_values.get(0).unwrap().clone())), @@ -1605,9 +1491,9 @@ impl<'cx> Dataflow<'cx> for PermissionDataflow { self.varid_to_value .insert((def, *varid, None), return_value_from_string(new_vals)); } else if let Some(val1) = val1 { - self.add_value(def, *varid, val1, None); + interp.add_value(def, *varid, val1, None); } else if let Some(val2) = val2 { - self.add_value(def, *varid, val2, None); + interp.add_value(def, *varid, val2, None); } } } @@ -1617,11 +1503,11 @@ impl<'cx> Dataflow<'cx> for PermissionDataflow { fn insert_value>( &mut self, + interp: &mut Interp<'cx, C>, operand: &Operand, lval: &Variable, varid: &VarId, def: DefId, - interp: &Interp<'cx, C>, prev_values: Option>, ) { match operand { @@ -1632,12 +1518,12 @@ impl<'cx> Dataflow<'cx> for PermissionDataflow { let mut all_values = prev_values.clone(); all_values.push(const_value); let value = Value::Phi(all_values); - self.add_value(def, *varid, value, lval.projections.get(0).cloned()); + interp.add_value(def, *varid, value, lval.projections.get(0).cloned()); } } else { if let Some(lit_value) = convert_operand_to_raw(operand) { let value = Value::Const(Const::Literal(lit_value)); - self.add_value(def, *varid, value, lval.projections.get(0).cloned()); + interp.add_value(def, *varid, value, lval.projections.get(0).cloned()); } } } @@ -1653,7 +1539,7 @@ impl<'cx> Dataflow<'cx> for PermissionDataflow { let mut all_values = prev_values.clone(); all_values.push(const_value); let value = Value::Phi(all_values); - self.add_value( + interp.add_value( def, *varid, value, @@ -1661,7 +1547,7 @@ impl<'cx> Dataflow<'cx> for PermissionDataflow { ); } else { let value = Value::Const(Const::Object(class)); - self.add_value( + interp.add_value( def, *varid, value, @@ -1670,7 +1556,7 @@ impl<'cx> Dataflow<'cx> for PermissionDataflow { } } } else { - if let Some(potential_value) = self.get_value(def, prev_varid, None) { + if let Some(potential_value) = interp.get_value(def, prev_varid, None) { self.varid_to_value.insert( (def, *varid, var.projections.get(0).cloned()), potential_value.clone(), @@ -1696,19 +1582,104 @@ impl<'cx> Dataflow<'cx> for PermissionDataflow { interp.callstack_arguments.push(values.clone()); } } -} -fn return_value_from_string(values: Vec) -> Value { - // assert!(values.len() > 0); - if values.len() == 1 { - return Value::Const(Const::Literal(values.get(0).unwrap().clone())); - } else { - return Value::Phi( - values - .iter() - .map(|val_string| Const::Literal(val_string.clone())) - .collect_vec(), - ); + fn handle_first_arg>( + &self, + interp: &Interp<'cx, C>, + operand: &Operand, + _def: DefId, + intrinsic_argument: &mut IntrinsicArguments, + ) { + match operand { + Operand::Lit(lit) => { + intrinsic_argument.first_arg = Some(vec![lit.to_string()]); + } + Operand::Var(var) => { + if let Base::Var(varid) = var.base { + if let Some(value) = interp.get_value(_def, varid, None) { + intrinsic_argument.first_arg = Some(vec![]); + add_elements_to_intrinsic_struct(value, &mut intrinsic_argument.first_arg); + } + } + } + } + } + + fn handle_second_arg>( + &self, + interp: &Interp<'cx, C>, + operand: &Operand, + _def: DefId, + intrinsic_argument: &mut IntrinsicArguments, + ) { + if let Operand::Var(variable) = operand { + if let Base::Var(varid) = variable.base { + if let Some(value) = + interp.get_value(_def, varid, Some(Projection::Known("method".into()))) + { + match value { + Value::Const(Const::Literal(lit)) => { + intrinsic_argument.second_arg = Some(vec![lit.to_string()]); + } + Value::Phi(phi_val) => { + intrinsic_argument.second_arg = Some( + phi_val + .iter() + .map(|data| { + if let Const::Literal(lit) = data { + return Some(lit.to_string()); + } else { + None + } + }) + .filter(|const_val| const_val != &None) + .map(|f| f.unwrap()) + .collect_vec(), + ) + } + _ => {} + } + } + } + } + } + + fn get_str_from_expr>( + &self, + _interp: &Interp<'cx, C>, + expr: &Operand, + def: DefId, + ) -> Vec> { + if let Some(str) = get_str_from_operand(expr) { + return vec![Some(str)]; + } else if let Operand::Var(var) = expr { + if let Base::Var(varid) = var.base { + let value = _interp.get_value(def, varid, None); + if let Some(value) = value { + match value { + Value::Const(const_val) => { + if let Const::Literal(str) = const_val { + return vec![Some(str.clone())]; + } + } + Value::Phi(phi_val) => { + return phi_val + .iter() + .map(|const_val| { + if let Const::Literal(str) = const_val { + Some(str.clone()) + } else { + None + } + }) + .collect_vec(); + } + _ => {} + } + } + } + } + vec![None] } } @@ -1797,6 +1768,22 @@ impl PermissionVuln { } } +impl<'cx> Checker<'cx> for PermissionChecker { + type State = PermissionTest; + type Dataflow = PermissionDataflow; + type Vuln = PermissionVuln; + + fn visit_intrinsic( + &mut self, + interp: &Interp<'cx, Self>, + intrinsic: &'cx Intrinsic, + state: &Self::State, + operands: Option>, + ) -> ControlFlow<(), Self::State> { + ControlFlow::Continue(*state) + } +} + impl IntoVuln for PermissionVuln { fn into_vuln(self, reporter: &Reporter) -> Vulnerability { Vulnerability { diff --git a/crates/forge_analyzer/src/interp.rs b/crates/forge_analyzer/src/interp.rs index b402f515..5b1b5add 100644 --- a/crates/forge_analyzer/src/interp.rs +++ b/crates/forge_analyzer/src/interp.rs @@ -157,26 +157,6 @@ pub trait Dataflow<'cx>: Sized { } fn add_variable>( - &mut self, - interp: &Interp<'cx, C>, - lval: &Variable, - varid: &VarId, - def: DefId, - rvalue: &Rvalue, - ) { - } - fn insert_value2>( - &mut self, - operand: &Operand, - lval: &Variable, - varid: &VarId, - def: DefId, - interp: &Interp<'cx, C>, - prev_values: Option>, - ) { - } - - fn add_variable>( &mut self, interp: &mut Interp<'cx, C>, lval: &Variable, @@ -185,8 +165,7 @@ pub trait Dataflow<'cx>: Sized { rvalue: &Rvalue, ) { } - - fn insert_value>( + fn insert_value2>( &mut self, interp: &mut Interp<'cx, C>, operand: &Operand, @@ -197,7 +176,7 @@ pub trait Dataflow<'cx>: Sized { ) { } - fn join_term>( + fn join_term>( &mut self, interp: &mut Interp<'cx, C>, def: DefId, @@ -285,6 +264,15 @@ pub trait Dataflow<'cx>: Sized { ) { } + fn handle_first_arg>( + &self, + _interp: &Interp<'cx, C>, + operand: &Operand, + _def: DefId, + intrinsic_argument: &mut IntrinsicArguments, + ) { + } + fn read_mem_from_object>( &self, _interp: &Interp<'cx, C>, @@ -294,6 +282,24 @@ pub trait Dataflow<'cx>: Sized { None } + fn handle_second_arg>( + &self, + _interp: &Interp<'cx, C>, + operand: &Operand, + _def: DefId, + intrinsic_argument: &mut IntrinsicArguments, + ) { + } + + fn get_str_from_expr>( + &self, + _interp: &Interp<'cx, C>, + expr: &Operand, + def: DefId, + ) -> Vec> { + vec![None] + } + fn get_str_from_expr>( &self, _interp: &Interp<'cx, C>, From 5bcaeecdcf46e7504448f637eb90a614f92cf642 Mon Sep 17 00:00:00 2001 From: James Gersbach Date: Mon, 7 Aug 2023 21:03:09 -0700 Subject: [PATCH 466/517] working definition analysis seperated analysis and permission checker --- Cargo.lock | 95 +++++++++++++++++++++++++++ crates/forge_analyzer/src/checkers.rs | 77 +++++++++++++++------- crates/forge_analyzer/src/interp.rs | 6 +- crates/fsrt/Cargo.toml | 1 + 4 files changed, 154 insertions(+), 25 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 9816cb91..de5f8ca9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -665,6 +665,95 @@ dependencies = [ "walkdir", ] +[[package]] +name = "futures" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23342abe12aba583913b2e62f22225ff9c950774065e4bfb61a19cd9770fec40" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "955518d47e09b25bbebc7a18df10b81f0c766eaf4c4f1cccef2fca5f2a4fb5f2" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4bca583b7e26f571124fe5b7561d49cb2868d79116cfa0eefce955557c6fee8c" + +[[package]] +name = "futures-executor" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccecee823288125bd88b4d7f565c9e58e41858e47ab72e8ea2d64e93624386e0" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-io" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fff74096e71ed47f8e023204cfd0aa1289cd54ae5430a9523be060cdb849964" + +[[package]] +name = "futures-macro" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89ca545a94061b6365f2c7355b4b32bd20df3ff95f02da9329b34ccc3bd6ee72" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.28", +] + +[[package]] +name = "futures-sink" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f43be4fe21a13b9781a69afa4985b0f6ee0e1afab2c6f454a8cf30e2b2237b6e" + +[[package]] +name = "futures-task" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76d3d132be6c0e6aa1534069c705a74a5997a356c0dc2f86a47765e5617c5b65" + +[[package]] +name = "futures-util" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26b01e40b772d54cf6c6d721c1d1abd0647a0106a12ecaa1c186273392a69533" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "pin-utils", + "slab", +] + [[package]] name = "generic-array" version = "0.14.6" @@ -1311,6 +1400,12 @@ version = "0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116" +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + [[package]] name = "pmutil" version = "0.6.1" diff --git a/crates/forge_analyzer/src/checkers.rs b/crates/forge_analyzer/src/checkers.rs index 888544da..67f08fae 100644 --- a/crates/forge_analyzer/src/checkers.rs +++ b/crates/forge_analyzer/src/checkers.rs @@ -6,7 +6,8 @@ use tracing::{debug, info, warn}; use crate::{ definitions::{Class, Const, DefId, DefKind, Environment, IntrinsicName, Value}, interp::{ - Checker, Dataflow, EntryKind, EntryPoint, Frame, Interp, JoinSemiLattice, WithCallStack, + Checker, Dataflow, EntryKind, EntryPoint, Frame, Interp, JoinSemiLattice, Runner, + WithCallStack, }, ir::{ Base, BasicBlock, BasicBlockId, BinOp, Inst, Intrinsic, Literal, Location, Operand, Rvalue, @@ -795,6 +796,10 @@ impl<'cx> Runner<'cx> for SecretChecker { } } +impl<'cx> Checker<'cx> for AuthZChecker { + type Vuln = AuthZVuln; +} + #[derive(Debug, PartialEq, Eq, Clone, Copy, PartialOrd, Ord)] pub enum Authenticated { No, @@ -823,13 +828,13 @@ pub struct AuthenticateDataflow { impl<'cx> Dataflow<'cx> for AuthenticateDataflow { type State = Authenticated; - fn with_interp>( + fn with_interp>( _interp: &Interp<'cx, C>, ) -> Self { Self { needs_call: vec![] } } - fn transfer_intrinsic>( + fn transfer_intrinsic>( &mut self, _interp: &mut Interp<'cx, C>, _def: DefId, @@ -851,7 +856,7 @@ impl<'cx> Dataflow<'cx> for AuthenticateDataflow { } } - fn transfer_call>( + fn transfer_call>( &mut self, interp: &Interp<'cx, C>, def: DefId, @@ -881,7 +886,7 @@ impl<'cx> Dataflow<'cx> for AuthenticateDataflow { } } - fn join_term>( + fn join_term>( &mut self, interp: &mut Interp<'cx, C>, def: DefId, @@ -920,10 +925,10 @@ impl Default for AuthenticateChecker { } } -impl<'cx> Checker<'cx> for AuthenticateChecker { +impl<'cx> Runner<'cx> for AuthenticateChecker { type State = Authenticated; type Dataflow = AuthenticateDataflow; - type Vuln = AuthNVuln; + fn visit_intrinsic( &mut self, interp: &Interp<'cx, Self>, @@ -950,6 +955,10 @@ impl<'cx> Checker<'cx> for AuthenticateChecker { } } +impl<'cx> Checker<'cx> for AuthenticateChecker { + type Vuln = AuthNVuln; +} + #[derive(Debug)] pub struct AuthNVuln { stack: String, @@ -1040,7 +1049,7 @@ pub struct IntrinsicArguments { impl<'cx> Dataflow<'cx> for PermissionDataflow { type State = PermissionTest; - fn with_interp>( + fn with_interp>( _interp: &Interp<'cx, C>, ) -> Self { Self { @@ -1096,7 +1105,7 @@ impl<'cx> Dataflow<'cx> for PermissionDataflow { } } - fn transfer_intrinsic>( + fn transfer_intrinsic>( &mut self, _interp: &mut Interp<'cx, C>, _def: DefId, @@ -1113,10 +1122,33 @@ impl<'cx> Dataflow<'cx> for PermissionDataflow { intrinsic_argument.name = Some(name.clone()); let (first, second) = (operands.get(0), operands.get(1)); if let Some(operand) = first { - self.handle_first_arg(_interp, operand, _def, &mut intrinsic_argument); + match operand { + Operand::Lit(lit) => { + intrinsic_argument.first_arg = Some(vec![lit.to_string()]); + } + Operand::Var(var) => { + if let Base::Var(varid) = var.base { + if let Some(value) = _interp.get_value(_def, varid, None) { + intrinsic_argument.first_arg = Some(vec![]); + add_elements_to_intrinsic_struct( + value, + &mut intrinsic_argument.first_arg, + ); + } + } + } + } } if let Some(operand) = second { - self.handle_second_arg(_interp, operand, _def, &mut intrinsic_argument); + if let Operand::Var(variable) = operand { + if let Base::Var(varid) = variable.base { + if let Some(value) = + _interp.get_value(_def, varid, Some(Projection::Known("method".into()))) + { + self.handle_second_arg(value, &mut intrinsic_argument); + } + } + } } println!("intrinsic argument {intrinsic_argument:?}"); @@ -1134,7 +1166,7 @@ impl<'cx> Dataflow<'cx> for PermissionDataflow { let permissions = check_url_for_permissions( &_interp.confluence_permission_resolver, &_interp.confluence_regex_map, - trnaslate_request_type(Some(second_arg)), + translate_request_type(Some(second_arg)), &first_arg, ); permissions_within_call.extend_from_slice(&permissions) @@ -1142,7 +1174,7 @@ impl<'cx> Dataflow<'cx> for PermissionDataflow { let permissions = check_url_for_permissions( &_interp.jira_permission_resolver, &_interp.jira_regex_map, - trnaslate_request_type(Some(second_arg)), + translate_request_type(Some(second_arg)), &first_arg, ); permissions_within_call.extend_from_slice(&permissions) @@ -1362,7 +1394,7 @@ impl<'cx> Dataflow<'cx> for PermissionDataflow { for (varid, varkind) in interp.body().vars.clone().iter_enumerated() { match varkind { VarKind::Ret => { - for (defid, (varid_value, defid_value)) in &interp.expecting_value { + for (defid, (varid_value, defid_value)) in interp.expecting_value.clone() { if def == defid.clone() { if let Some(value) = interp.get_value(def, varid, None).clone() { interp.add_value(def, varid, value.clone(), None); @@ -1381,7 +1413,7 @@ impl<'cx> Dataflow<'cx> for PermissionDataflow { state } - fn add_variable>( + fn add_variable>( &mut self, interp: &mut Interp<'cx, C>, lval: &Variable, @@ -1470,12 +1502,12 @@ impl<'cx> Dataflow<'cx> for PermissionDataflow { let val1 = if let Some(val) = get_str_from_operand(op1) { Some(Value::Const(Const::Literal(val))) } else { - self.get_values_from_operand(interp, def, op1).cloned() + self.get_values_from_operand(interp, def, op2) }; let val2 = if let Some(val) = get_str_from_operand(op2) { Some(Value::Const(Const::Literal(val))) } else { - self.get_values_from_operand(interp, def, op2).cloned() + self.get_values_from_operand(interp, def, op2) }; let mut new_vals = vec![]; if let (Some(val1), Some(val2)) = (val1.clone(), val2.clone()) { @@ -1488,7 +1520,8 @@ impl<'cx> Dataflow<'cx> for PermissionDataflow { .for_each(|val1| add_const_to_val_vec(&val2, &val1, &mut new_vals)), _ => {} } - self.varid_to_value + interp + .varid_to_value .insert((def, *varid, None), return_value_from_string(new_vals)); } else if let Some(val1) = val1 { interp.add_value(def, *varid, val1, None); @@ -1501,7 +1534,7 @@ impl<'cx> Dataflow<'cx> for PermissionDataflow { } } - fn insert_value>( + fn insert_value>( &mut self, interp: &mut Interp<'cx, C>, operand: &Operand, @@ -1557,7 +1590,7 @@ impl<'cx> Dataflow<'cx> for PermissionDataflow { } } else { if let Some(potential_value) = interp.get_value(def, prev_varid, None) { - self.varid_to_value.insert( + interp.varid_to_value.insert( (def, *varid, var.projections.get(0).cloned()), potential_value.clone(), ); @@ -1568,7 +1601,7 @@ impl<'cx> Dataflow<'cx> for PermissionDataflow { } } - fn join_term>( + fn join_term>( &mut self, interp: &mut Interp<'cx, C>, def: DefId, @@ -1583,7 +1616,7 @@ impl<'cx> Dataflow<'cx> for PermissionDataflow { } } - fn handle_first_arg>( + fn get_str_from_expr>( &self, interp: &Interp<'cx, C>, operand: &Operand, diff --git a/crates/forge_analyzer/src/interp.rs b/crates/forge_analyzer/src/interp.rs index 5b1b5add..4ea1de06 100644 --- a/crates/forge_analyzer/src/interp.rs +++ b/crates/forge_analyzer/src/interp.rs @@ -156,7 +156,7 @@ pub trait Dataflow<'cx>: Sized { state } - fn add_variable>( + fn add_variable>( &mut self, interp: &mut Interp<'cx, C>, lval: &Variable, @@ -176,7 +176,7 @@ pub trait Dataflow<'cx>: Sized { ) { } - fn join_term>( + fn join_term>( &mut self, interp: &mut Interp<'cx, C>, def: DefId, @@ -282,7 +282,7 @@ pub trait Dataflow<'cx>: Sized { None } - fn handle_second_arg>( + fn get_str_from_expr>( &self, _interp: &Interp<'cx, C>, operand: &Operand, diff --git a/crates/fsrt/Cargo.toml b/crates/fsrt/Cargo.toml index 3d1220ba..a4eeee5a 100644 --- a/crates/fsrt/Cargo.toml +++ b/crates/fsrt/Cargo.toml @@ -23,3 +23,4 @@ tracing-subscriber.workspace = true tracing-tree.workspace = true tracing.workspace = true walkdir.workspace = true +futures = "0.3.28" From 8cca6951f45b1c8447475a95bc8488ce37de9463 Mon Sep 17 00:00:00 2001 From: James Gersbach Date: Mon, 7 Aug 2023 23:08:42 -0700 Subject: [PATCH 467/517] finalize secret scanner --- Cargo.lock | 95 ------- crates/forge_analyzer/src/checkers.rs | 241 +++++++++++++++++- crates/forge_analyzer/src/interp.rs | 7 +- .../src/permissions_resolver.rs | 2 - crates/fsrt/Cargo.toml | 1 - .../src/utils.js | 3 + 6 files changed, 243 insertions(+), 106 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index de5f8ca9..9816cb91 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -665,95 +665,6 @@ dependencies = [ "walkdir", ] -[[package]] -name = "futures" -version = "0.3.28" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23342abe12aba583913b2e62f22225ff9c950774065e4bfb61a19cd9770fec40" -dependencies = [ - "futures-channel", - "futures-core", - "futures-executor", - "futures-io", - "futures-sink", - "futures-task", - "futures-util", -] - -[[package]] -name = "futures-channel" -version = "0.3.28" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "955518d47e09b25bbebc7a18df10b81f0c766eaf4c4f1cccef2fca5f2a4fb5f2" -dependencies = [ - "futures-core", - "futures-sink", -] - -[[package]] -name = "futures-core" -version = "0.3.28" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4bca583b7e26f571124fe5b7561d49cb2868d79116cfa0eefce955557c6fee8c" - -[[package]] -name = "futures-executor" -version = "0.3.28" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ccecee823288125bd88b4d7f565c9e58e41858e47ab72e8ea2d64e93624386e0" -dependencies = [ - "futures-core", - "futures-task", - "futures-util", -] - -[[package]] -name = "futures-io" -version = "0.3.28" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fff74096e71ed47f8e023204cfd0aa1289cd54ae5430a9523be060cdb849964" - -[[package]] -name = "futures-macro" -version = "0.3.28" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89ca545a94061b6365f2c7355b4b32bd20df3ff95f02da9329b34ccc3bd6ee72" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.28", -] - -[[package]] -name = "futures-sink" -version = "0.3.28" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f43be4fe21a13b9781a69afa4985b0f6ee0e1afab2c6f454a8cf30e2b2237b6e" - -[[package]] -name = "futures-task" -version = "0.3.28" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76d3d132be6c0e6aa1534069c705a74a5997a356c0dc2f86a47765e5617c5b65" - -[[package]] -name = "futures-util" -version = "0.3.28" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26b01e40b772d54cf6c6d721c1d1abd0647a0106a12ecaa1c186273392a69533" -dependencies = [ - "futures-channel", - "futures-core", - "futures-io", - "futures-macro", - "futures-sink", - "futures-task", - "memchr", - "pin-project-lite", - "pin-utils", - "slab", -] - [[package]] name = "generic-array" version = "0.14.6" @@ -1400,12 +1311,6 @@ version = "0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116" -[[package]] -name = "pin-utils" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" - [[package]] name = "pmutil" version = "0.6.1" diff --git a/crates/forge_analyzer/src/checkers.rs b/crates/forge_analyzer/src/checkers.rs index 67f08fae..7a45d9d1 100644 --- a/crates/forge_analyzer/src/checkers.rs +++ b/crates/forge_analyzer/src/checkers.rs @@ -4,7 +4,7 @@ use std::{cmp::max, mem, ops::ControlFlow}; use tracing::{debug, info, warn}; use crate::{ - definitions::{Class, Const, DefId, DefKind, Environment, IntrinsicName, Value}, + definitions::{Class, Const, DefId, DefKind, Environment, IntrinsicName, PackageData, Value}, interp::{ Checker, Dataflow, EntryKind, EntryPoint, Frame, Interp, JoinSemiLattice, Runner, WithCallStack, @@ -933,6 +933,7 @@ impl<'cx> Runner<'cx> for AuthenticateChecker { &mut self, interp: &Interp<'cx, Self>, intrinsic: &'cx Intrinsic, + def: DefId, state: &Self::State, operands: Option>, ) -> ControlFlow<(), Self::State> { @@ -1030,6 +1031,240 @@ impl WithCallStack for AuthNVuln { fn add_call_stack(&mut self, _stack: Vec) {} } +pub struct SecretDataflow { + needs_call: Vec, +} + +#[derive(Debug, PartialEq, Eq, Clone, Copy, PartialOrd, Ord)] +pub enum SecretState { + ALL, +} + +impl JoinSemiLattice for SecretState { + const BOTTOM: Self = Self::ALL; + + #[inline] + fn join_changed(&mut self, other: &Self) -> bool { + let old = mem::replace(self, max(*other, *self)); + old == *self + } + + #[inline] + fn join(&self, other: &Self) -> Self { + max(*other, *self) + } +} + +impl<'cx> Dataflow<'cx> for SecretDataflow { + type State = SecretState; + + fn with_interp>( + _interp: &Interp<'cx, C>, + ) -> Self { + Self { needs_call: vec![] } + } + + fn transfer_intrinsic>( + &mut self, + _interp: &mut Interp<'cx, C>, + _def: DefId, + _loc: Location, + _block: &'cx BasicBlock, + intrinsic: &'cx Intrinsic, + initial_state: Self::State, + operands: SmallVec<[crate::ir::Operand; 4]>, + ) -> Self::State { + initial_state + } + + fn transfer_call>( + &mut self, + interp: &Interp<'cx, C>, + def: DefId, + loc: Location, + _block: &'cx BasicBlock, + callee: &'cx crate::ir::Operand, + initial_state: Self::State, + operands: SmallVec<[crate::ir::Operand; 4]>, + ) -> Self::State { + let Some((callee_def, _body)) = self.resolve_call(interp, callee) else { + return initial_state; + }; + self.needs_call.push(callee_def); + SecretState::ALL + } + + fn join_term>( + &mut self, + interp: &mut Interp<'cx, C>, + def: DefId, + block: &'cx BasicBlock, + state: Self::State, + worklist: &mut WorkList, + ) { + self.super_join_term(interp, def, block, state, worklist); + for def in self.needs_call.drain(..) { + worklist.push_front_blocks(interp.env(), def); + } + } +} + +pub struct SecretChecker { + visit: bool, + vulns: Vec, +} + +impl SecretChecker { + pub fn new() -> Self { + Self { + visit: true, + vulns: vec![], + } + } + + pub fn into_vulns(self) -> impl IntoIterator { + // TODO: make this an associated function on the Checker trait. + self.vulns.into_iter() + } +} + +impl Default for SecretChecker { + fn default() -> Self { + Self::new() + } +} + +#[derive(Debug)] +pub struct SecretVuln { + stack: String, + entry_func: String, + file: PathBuf, +} + +impl SecretVuln { + fn new(callstack: Vec, env: &Environment, entry: &EntryPoint) -> Self { + let entry_func = match &entry.kind { + EntryKind::Function(func) => func.clone(), + EntryKind::Resolver(res, prop) => format!("{res}.{prop}"), + EntryKind::Empty => { + warn!("empty function"); + String::new() + } + }; + let file = entry.file.clone(); + let stack = Itertools::intersperse( + iter::once(&*entry_func).chain( + callstack + .into_iter() + .rev() + .map(|frame| env.def_name(frame.calling_function)), + ), + " -> ", + ) + .collect(); + Self { + stack, + entry_func, + file, + } + } +} + +impl fmt::Display for SecretVuln { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "Hardcoded secret vulnerability") + } +} + +impl IntoVuln for SecretVuln { + fn into_vuln(self, reporter: &Reporter) -> Vulnerability { + use std::collections::hash_map::DefaultHasher; + use std::hash::{Hash, Hasher}; + + let mut hasher = DefaultHasher::new(); + self.file + .iter() + .skip_while(|comp| *comp != "src") + .for_each(|comp| comp.hash(&mut hasher)); + self.entry_func.hash(&mut hasher); + self.stack.hash(&mut hasher); + Vulnerability { + check_name: format!("Secret-{}", hasher.finish()), + description: format!( + "Hardcoded secret found within codebase {} in {:?}.", + self.entry_func, self.file + ), + recommendation: "Use secrets as enviornment variables instead of hardcoding them.", + proof: format!("Hardcoded secret found in found via {}", self.stack), + severity: Severity::High, + app_key: reporter.app_key().to_owned(), + app_name: reporter.app_name().to_owned(), + date: reporter.current_date(), + } + } +} + +impl WithCallStack for SecretVuln { + fn add_call_stack(&mut self, _stack: Vec) {} +} + +impl<'cx> Runner<'cx> for SecretChecker { + type State = SecretState; + type Dataflow = SecretDataflow; + + fn visit_intrinsic( + &mut self, + interp: &Interp<'cx, Self>, + intrinsic: &'cx Intrinsic, + def: DefId, + state: &Self::State, + operands: Option>, + ) -> ControlFlow<(), Self::State> { + if let Intrinsic::JWTSign(package_data) = intrinsic { + if let Some(operand) = operands + .unwrap_or_default() + .get((package_data.secret_position - 1) as usize) + { + match operand { + Operand::Lit(lit) => { + let vuln = + SecretVuln::new(interp.callstack(), interp.env(), interp.entry()); + if let Literal::Str(_) = lit { + info!("Found a vuln!"); + self.vulns.push(vuln); + } + } + Operand::Var(var) => { + if let Base::Var(varid) = var.base { + if let Some(value) = + interp.get_value(def, varid, var.projections.get(0).cloned()) + { + match value { + Value::Const(_) | Value::Phi(_) => { + let vuln = SecretVunew( + interp.callstack(), + interp.env(), + interp.entry(), + ); + info!("Found a vuln!"); + self.vulns.push(vuln); + } + _ => {} + } + } + } + } + } + } + } + ControlFlow::Continue(*state) + } +} + +impl<'cx> Checker<'cx> for SecretChecker { + type Vuln = SecretVuln; +} + pub struct PermisisionDataflow { needs_call: Vec, variables: FxHashMap, @@ -1151,8 +1386,6 @@ impl<'cx> Dataflow<'cx> for PermissionDataflow { } } - println!("intrinsic argument {intrinsic_argument:?}"); - let mut permissions_within_call: Vec = vec![]; let intrinsic_func_type = intrinsic_argument.name.unwrap(); intrinsic_argument @@ -1204,8 +1437,6 @@ impl<'cx> Dataflow<'cx> for PermissionDataflow { } }); - println!("permissions found {permissions_within_call:?}"); - _interp .permissions .extend_from_slice(&permissions_within_call); diff --git a/crates/forge_analyzer/src/interp.rs b/crates/forge_analyzer/src/interp.rs index 4ea1de06..43fe5b89 100644 --- a/crates/forge_analyzer/src/interp.rs +++ b/crates/forge_analyzer/src/interp.rs @@ -463,8 +463,10 @@ pub trait Runner<'cx>: Sized { let mut curr_state = interp.block_state(def, id).join(curr_state); for stmt in block { match stmt { - Inst::Expr(r) => curr_state = self.visit_rvalue(interp, r, id, &curr_state)?, - Inst::Assign(_, r) => curr_state = self.visit_rvalue(interp, r, id, &curr_state)?, + Inst::Expr(r) => curr_state = self.visit_rvalue(interp, r, def, id, &curr_state)?, + Inst::Assign(_, r) => { + curr_state = self.visit_rvalue(interp, r, def, id, &curr_state)? + } } } match block.successors() { @@ -807,7 +809,6 @@ impl<'cx, C: Runner<'cx>> Interp<'cx, C> { let arguments = self.callstack_arguments.pop(); let name = self.env.def_name(def); debug!("Dataflow: {name} - {block_id}"); - println!("Dataflow: {def:?} {name} - {block_id}"); self.dataflow_visited.insert(def); let func = self.env().def_ref(def).expect_body(); self.curr_body.set(Some(func)); diff --git a/crates/forge_permission_resolver/src/permissions_resolver.rs b/crates/forge_permission_resolver/src/permissions_resolver.rs index e3982bbd..1cd57eae 100644 --- a/crates/forge_permission_resolver/src/permissions_resolver.rs +++ b/crates/forge_permission_resolver/src/permissions_resolver.rs @@ -66,8 +66,6 @@ pub fn check_url_for_permissions( request: RequestType, url: &str, ) -> Vec { - println!("check_url_for_permissions {request:?} {url:?}"); - let mut length_of_regex = endpoint_regex .iter() .map(|(string, regex)| (regex.as_str().len(), &*string)) diff --git a/crates/fsrt/Cargo.toml b/crates/fsrt/Cargo.toml index a4eeee5a..3d1220ba 100644 --- a/crates/fsrt/Cargo.toml +++ b/crates/fsrt/Cargo.toml @@ -23,4 +23,3 @@ tracing-subscriber.workspace = true tracing-tree.workspace = true tracing.workspace = true walkdir.workspace = true -futures = "0.3.28" diff --git a/test-apps/jira-damn-vulnerable-forge-app/src/utils.js b/test-apps/jira-damn-vulnerable-forge-app/src/utils.js index 3fc6de88..61e822f1 100755 --- a/test-apps/jira-damn-vulnerable-forge-app/src/utils.js +++ b/test-apps/jira-damn-vulnerable-forge-app/src/utils.js @@ -35,6 +35,9 @@ export async function fetchIssueSummary(issueIdOrKey, test_value) { var token = jwt.sign({ foo: 'bar' }, 'secret_token'); + // trick case + var token = jwt.sign({ foo: 'bar' }, 'process.env.SECRET'); + testFunctionFromTestFile(); let val = 'grapefruit'; From e14ffcfcef61cb18dddda9b25b2fc3abc5b7c013 Mon Sep 17 00:00:00 2001 From: James Gersbach Date: Tue, 8 Aug 2023 10:13:43 -0700 Subject: [PATCH 468/517] patch to definition analysis --- crates/forge_analyzer/src/checkers.rs | 44 +++++++++---------- crates/forge_analyzer/src/interp.rs | 2 +- .../src/index.jsx | 2 +- 3 files changed, 22 insertions(+), 26 deletions(-) diff --git a/crates/forge_analyzer/src/checkers.rs b/crates/forge_analyzer/src/checkers.rs index 7a45d9d1..23537294 100644 --- a/crates/forge_analyzer/src/checkers.rs +++ b/crates/forge_analyzer/src/checkers.rs @@ -1374,14 +1374,12 @@ impl<'cx> Dataflow<'cx> for PermissionDataflow { } } } - if let Some(operand) = second { - if let Operand::Var(variable) = operand { - if let Base::Var(varid) = variable.base { - if let Some(value) = - _interp.get_value(_def, varid, Some(Projection::Known("method".into()))) - { - self.handle_second_arg(value, &mut intrinsic_argument); - } + if let Some(Operand::Var(variable)) = second { + if let Base::Var(varid) = variable.base { + if let Some(value) = + _interp.get_value(_def, varid, Some(Projection::Known("method".into()))) + { + self.handle_second_arg(value, &mut intrinsic_argument); } } } @@ -1437,6 +1435,8 @@ impl<'cx> Dataflow<'cx> for PermissionDataflow { } }); + println!("intrinsic argument {intrinsic_argument:?}"); + _interp .permissions .extend_from_slice(&permissions_within_call); @@ -1623,21 +1623,19 @@ impl<'cx> Dataflow<'cx> for PermissionDataflow { } for (varid, varkind) in interp.body().vars.clone().iter_enumerated() { - match varkind { - VarKind::Ret => { - for (defid, (varid_value, defid_value)) in interp.expecting_value.clone() { - if def == defid.clone() { - if let Some(value) = interp.get_value(def, varid, None).clone() { - interp.add_value(def, varid, value.clone(), None); - } - } - } + if &VarKind::Ret == varkind { + if let Some((defid_calling_func, varid_calling_func)) = + interp.expected_return_values.get(&def) + { if let Some(value) = interp.get_value(def, varid, None) { - interp.return_value = Some((value.clone(), def)); - //interp.return_value_alt.insert(def, value.clone()); + interp.add_value( + *defid_calling_func, + *varid_calling_func, + value.clone(), + None, + ); } } - _ => {} } } @@ -1921,10 +1919,8 @@ impl<'cx> Dataflow<'cx> for PermissionDataflow { let value = _interp.get_value(def, varid, None); if let Some(value) = value { match value { - Value::Const(const_val) => { - if let Const::Literal(str) = const_val { - return vec![Some(str.clone())]; - } + Value::Const(Const::Literal(str)) => { + return vec![Some(str.clone())]; } Value::Phi(phi_val) => { return phi_val diff --git a/crates/forge_analyzer/src/interp.rs b/crates/forge_analyzer/src/interp.rs index 43fe5b89..d44b6494 100644 --- a/crates/forge_analyzer/src/interp.rs +++ b/crates/forge_analyzer/src/interp.rs @@ -811,7 +811,7 @@ impl<'cx, C: Runner<'cx>> Interp<'cx, C> { debug!("Dataflow: {name} - {block_id}"); self.dataflow_visited.insert(def); let func = self.env().def_ref(def).expect_body(); - self.curr_body.set(Some(func)); + self.curr_body.set(Some(func)) let block = func.block(block_id); let mut before_state = self.block_state(def, block_id); let block = func.block(block_id); diff --git a/test-apps/jira-damn-vulnerable-forge-app/src/index.jsx b/test-apps/jira-damn-vulnerable-forge-app/src/index.jsx index 43a6cca5..a9ae5641 100755 --- a/test-apps/jira-damn-vulnerable-forge-app/src/index.jsx +++ b/test-apps/jira-damn-vulnerable-forge-app/src/index.jsx @@ -18,7 +18,7 @@ import api, { webTrigger, route, storage, properties } from '@forge/api'; import { createHash } from 'crypto'; import jwt from 'jsonwebtoken'; import { fetchIssueSummary } from './utils'; -import {IssuePanelApp} from './IssuePanelApp'; +import { IssuePanelApp } from './IssuePanelApp'; function SharedSecretForm() { const [hashedSecret, setHashedSecret] = useState(null); From b939c1aadc2da2adc2e3c5591a5f0e53b595216c Mon Sep 17 00:00:00 2001 From: James Gersbach Date: Tue, 8 Aug 2023 10:30:25 -0700 Subject: [PATCH 469/517] formatting --- crates/forge_permission_resolver/src/test.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/crates/forge_permission_resolver/src/test.rs b/crates/forge_permission_resolver/src/test.rs index c8d95f4d..51cc8efd 100644 --- a/crates/forge_permission_resolver/src/test.rs +++ b/crates/forge_permission_resolver/src/test.rs @@ -3,6 +3,7 @@ use crate::permissions_resolver::{ }; mod tests { + use super::*; use crate::permissions_resolver::{ get_permission_resolver_confluence, get_permission_resolver_jira, }; From d6f1bd2de52cf822dda1ccd6e3c319906a2d541d Mon Sep 17 00:00:00 2001 From: James Gersbach Date: Tue, 8 Aug 2023 17:01:56 -0700 Subject: [PATCH 470/517] cleaning up --- crates/forge_analyzer/src/analyzer.rs | 1 + crates/forge_analyzer/src/definitions.rs | 4 ++++ crates/forge_analyzer/src/ir.rs | 1 - 3 files changed, 5 insertions(+), 1 deletion(-) diff --git a/crates/forge_analyzer/src/analyzer.rs b/crates/forge_analyzer/src/analyzer.rs index 3e9ba2aa..3102f6bc 100644 --- a/crates/forge_analyzer/src/analyzer.rs +++ b/crates/forge_analyzer/src/analyzer.rs @@ -237,6 +237,7 @@ impl Visit for FunctionAnalyzer<'_> { _ => {} } } + // we don't need to add this to the IR, since we know it's useless return; } else { IrStmt::Call(id.into()) diff --git a/crates/forge_analyzer/src/definitions.rs b/crates/forge_analyzer/src/definitions.rs index f60757e4..d65dd70c 100644 --- a/crates/forge_analyzer/src/definitions.rs +++ b/crates/forge_analyzer/src/definitions.rs @@ -3084,6 +3084,8 @@ impl Visit for ImportCollector<'_> { } None => warn!("unable to find default import for {}", &self.current_import), } + + // POI } Err(_) => { let foreign_id = self.foreign_defs.push_and_get_key(ForeignItem { @@ -3355,6 +3357,8 @@ impl Visit for ExportCollector<'_> { n.decl.visit_children_with(self); } + // POI + fn visit_export_named_specifier(&mut self, n: &ExportNamedSpecifier) { if let ModuleExportName::Ident(ident) = &n.orig { let export_defid = self.add_export(DefRes::Undefined, ident.to_id()); diff --git a/crates/forge_analyzer/src/ir.rs b/crates/forge_analyzer/src/ir.rs index 3cb44289..a789e807 100644 --- a/crates/forge_analyzer/src/ir.rs +++ b/crates/forge_analyzer/src/ir.rs @@ -430,7 +430,6 @@ impl Body { env: &'cx Environment, callee: &Operand, ) -> Option<(DefId, &'cx Body)> { - /* callee is the name of the function that is being called */ match callee { Operand::Var(Variable { From 7b5d2890a6b05569639d09070379a04d0283ef8d Mon Sep 17 00:00:00 2001 From: James Gersbach Date: Wed, 9 Aug 2023 20:10:52 -0700 Subject: [PATCH 471/517] functioning definition export --- crates/forge_analyzer/src/definitions.rs | 29 ++++++++++++++++++- crates/forge_analyzer/src/interp.rs | 3 ++ crates/forge_analyzer/src/ir.rs | 1 - .../src/permissions_resolver.rs | 3 ++ 4 files changed, 34 insertions(+), 2 deletions(-) diff --git a/crates/forge_analyzer/src/definitions.rs b/crates/forge_analyzer/src/definitions.rs index d65dd70c..8c15fe89 100644 --- a/crates/forge_analyzer/src/definitions.rs +++ b/crates/forge_analyzer/src/definitions.rs @@ -1211,6 +1211,8 @@ impl<'cx> FunctionAnalyzer<'cx> { } fn get_operand_for_call(&mut self, expr: &Expr) -> Operand { + println!("expr {expr:?}"); + if let Expr::Ident(ident) = expr { if let Some(DefKind::Class(class)) = self.res.sym_to_def(ident.to_id(), self.module) { let id = ident.to_id(); @@ -2173,6 +2175,15 @@ impl Visit for FunctionCollector<'_> { self.curr_class = Some(defid); } } + Expr::Class(class) => { + println!("class ~~~ {ident_property:?}"); + if let Some(defid) = + self.res.get_sym(ident_property.to_id(), self.module) + { + println!("found def ... {defid:?}"); + self.curr_class = Some(defid); + } + } _ => {} } } @@ -2196,6 +2207,9 @@ impl Visit for FunctionCollector<'_> { { self.curr_class = Some(class.def); // sets the current class so that mehtods are assigned to the proper class } + + // need a way to get the curr class without using the sym ... + n.visit_children_with(self); } @@ -2248,8 +2262,11 @@ impl Visit for FunctionCollector<'_> { fn visit_class_method(&mut self, n: &ClassMethod) { //n.visit_children_with(self); + println!("visitng class method {:?}", self.curr_class); if let Some(class_def) = self.curr_class { + println!("1 ~ {n:?}"); if let DefKind::Class(class) = self.res.clone().def_ref(class_def) { + println!("2 ~ {n:?}"); if let Some((_, owner)) = &class.pub_members.iter().find(|(name, defid)| { if let PropName::Ident(ident) = &n.key { return name == &ident.sym; @@ -2787,9 +2804,12 @@ impl Visit for Lowerer<'_> { fn visit_class_method(&mut self, n: &ClassMethod) { n.visit_children_with(self); + println!("adding class mehtod .... "); if let Some(class_def) = self.curr_class { if let DefKind::Class(class) = self.res.def_mut(class_def) { if let PropName::Ident(ident) = &n.key { + println!("adding class mehtod .... "); + let owner = self.res .add_anonymous("__CLASSMETHOD", AnonType::Closure, self.curr_mod); @@ -3000,6 +3020,7 @@ impl ExportCollector<'_> { } }; self.default = Some(defid); + defid } @@ -3074,6 +3095,8 @@ impl Visit for ImportCollector<'_> { debug_assert_ne!(self.curr_mod, mod_id); match self.resolver.default_export(mod_id) { Some(def) => { + println!("def -- {def:?} {:?} {:?}", mod_id, self.curr_mod); + self.resolver.resolver.symbol_to_id.insert( Symbol { module: self.curr_mod, @@ -3082,7 +3105,7 @@ impl Visit for ImportCollector<'_> { def, ); } - None => warn!("unable to find default import for {}", &self.current_import), + None => println!("unable to find default import for {}", &self.current_import), } // POI @@ -3285,6 +3308,9 @@ impl Visit for ExportCollector<'_> { Expr::Fn(FnExpr { ident, function }) => { self.add_export(DefRes::Function(()), ident_property.to_id()); } + Expr::Class(_) => { + self.add_export(DefRes::Class(()), ident_property.to_id()); + } Expr::Ident(ident) => { let export_defid = self.add_export(DefRes::Function(()), ident_property.to_id()); @@ -3794,6 +3820,7 @@ impl Definitions { } DefKind::Class(_) => { let objid = classes.push_and_get_key(Class::new(id)); + println!("pusing class 1"); DefKind::Class(objid) } DefKind::Foreign(d) => DefKind::Foreign(d), diff --git a/crates/forge_analyzer/src/interp.rs b/crates/forge_analyzer/src/interp.rs index d44b6494..010198a1 100644 --- a/crates/forge_analyzer/src/interp.rs +++ b/crates/forge_analyzer/src/interp.rs @@ -806,10 +806,13 @@ impl<'cx, C: Runner<'cx>> Interp<'cx, C> { worklist.push_front_blocks(self.env, func_def, self.call_all); let old_body = self.curr_body.get(); while let Some((def, block_id)) = worklist.pop_front() { + println!("def from popping {def:?} block id from popping {block_id:?}"); + let arguments = self.callstack_arguments.pop(); let name = self.env.def_name(def); debug!("Dataflow: {name} - {block_id}"); self.dataflow_visited.insert(def); + println!("expect body 8"); let func = self.env().def_ref(def).expect_body(); self.curr_body.set(Some(func)) let block = func.block(block_id); diff --git a/crates/forge_analyzer/src/ir.rs b/crates/forge_analyzer/src/ir.rs index a789e807..ddcb6dde 100644 --- a/crates/forge_analyzer/src/ir.rs +++ b/crates/forge_analyzer/src/ir.rs @@ -430,7 +430,6 @@ impl Body { env: &'cx Environment, callee: &Operand, ) -> Option<(DefId, &'cx Body)> { - match callee { Operand::Var(Variable { base: Base::Var(var), diff --git a/crates/forge_permission_resolver/src/permissions_resolver.rs b/crates/forge_permission_resolver/src/permissions_resolver.rs index 1cd57eae..a25e09f1 100644 --- a/crates/forge_permission_resolver/src/permissions_resolver.rs +++ b/crates/forge_permission_resolver/src/permissions_resolver.rs @@ -108,8 +108,11 @@ pub fn get_permisions_for( endpoint_map_classic: &mut PermissionHashMap, endpoint_regex: &mut HashMap, ) { + println!("prev to call"); if let Result::Ok(response) = ureq::get(url).call() { + println!("prev to parsing"); let data: SwaggerReponse = response.into_json().unwrap(); + println!("finish parsing"); for (key, endpoint_data) in &data.paths { let endpoint_data = get_request_type(endpoint_data, key); endpoint_data From b0dd29ba4fe1b1dfbfadc7e0a713fed569a66bd1 Mon Sep 17 00:00:00 2001 From: James Gersbach Date: Thu, 10 Aug 2023 10:51:13 -0700 Subject: [PATCH 472/517] fix bug for exporting default classes --- crates/forge_analyzer/src/checkers.rs | 2 -- crates/forge_analyzer/src/definitions.rs | 34 +++++++------------ crates/forge_analyzer/src/interp.rs | 3 -- .../src/permissions_resolver.rs | 3 -- 4 files changed, 13 insertions(+), 29 deletions(-) diff --git a/crates/forge_analyzer/src/checkers.rs b/crates/forge_analyzer/src/checkers.rs index 23537294..8b783fec 100644 --- a/crates/forge_analyzer/src/checkers.rs +++ b/crates/forge_analyzer/src/checkers.rs @@ -1435,8 +1435,6 @@ impl<'cx> Dataflow<'cx> for PermissionDataflow { } }); - println!("intrinsic argument {intrinsic_argument:?}"); - _interp .permissions .extend_from_slice(&permissions_within_call); diff --git a/crates/forge_analyzer/src/definitions.rs b/crates/forge_analyzer/src/definitions.rs index 8c15fe89..542fc16e 100644 --- a/crates/forge_analyzer/src/definitions.rs +++ b/crates/forge_analyzer/src/definitions.rs @@ -1211,8 +1211,6 @@ impl<'cx> FunctionAnalyzer<'cx> { } fn get_operand_for_call(&mut self, expr: &Expr) -> Operand { - println!("expr {expr:?}"); - if let Expr::Ident(ident) = expr { if let Some(DefKind::Class(class)) = self.res.sym_to_def(ident.to_id(), self.module) { let id = ident.to_id(); @@ -2176,11 +2174,9 @@ impl Visit for FunctionCollector<'_> { } } Expr::Class(class) => { - println!("class ~~~ {ident_property:?}"); if let Some(defid) = self.res.get_sym(ident_property.to_id(), self.module) { - println!("found def ... {defid:?}"); self.curr_class = Some(defid); } } @@ -2207,9 +2203,6 @@ impl Visit for FunctionCollector<'_> { { self.curr_class = Some(class.def); // sets the current class so that mehtods are assigned to the proper class } - - // need a way to get the curr class without using the sym ... - n.visit_children_with(self); } @@ -2262,11 +2255,8 @@ impl Visit for FunctionCollector<'_> { fn visit_class_method(&mut self, n: &ClassMethod) { //n.visit_children_with(self); - println!("visitng class method {:?}", self.curr_class); if let Some(class_def) = self.curr_class { - println!("1 ~ {n:?}"); if let DefKind::Class(class) = self.res.clone().def_ref(class_def) { - println!("2 ~ {n:?}"); if let Some((_, owner)) = &class.pub_members.iter().find(|(name, defid)| { if let PropName::Ident(ident) = &n.key { return name == &ident.sym; @@ -2804,12 +2794,9 @@ impl Visit for Lowerer<'_> { fn visit_class_method(&mut self, n: &ClassMethod) { n.visit_children_with(self); - println!("adding class mehtod .... "); if let Some(class_def) = self.curr_class { if let DefKind::Class(class) = self.res.def_mut(class_def) { if let PropName::Ident(ident) = &n.key { - println!("adding class mehtod .... "); - let owner = self.res .add_anonymous("__CLASSMETHOD", AnonType::Closure, self.curr_mod); @@ -3095,7 +3082,10 @@ impl Visit for ImportCollector<'_> { debug_assert_ne!(self.curr_mod, mod_id); match self.resolver.default_export(mod_id) { Some(def) => { - println!("def -- {def:?} {:?} {:?}", mod_id, self.curr_mod); + println!( + "def -- {:?} {def:?} {:?} {:?}", + n.local, mod_id, self.curr_mod + ); self.resolver.resolver.symbol_to_id.insert( Symbol { @@ -3105,10 +3095,8 @@ impl Visit for ImportCollector<'_> { def, ); } - None => println!("unable to find default import for {}", &self.current_import), + None => warn!("unable to find default import for {}", &self.current_import), } - - // POI } Err(_) => { let foreign_id = self.foreign_defs.push_and_get_key(ForeignItem { @@ -3294,6 +3282,13 @@ impl Visit for ExportCollector<'_> { DefRes::Class(()), ident.as_ref().map(Ident::to_id), ), + Expr::Ident(ident) => { + self.add_default(DefRes::Undefined, None); + self.res_table.default_export_names.insert( + (ident.sym.clone(), self.curr_mod), + self.default.unwrap(), + ); + } _ => {} } } @@ -3313,7 +3308,7 @@ impl Visit for ExportCollector<'_> { } Expr::Ident(ident) => { let export_defid = - self.add_export(DefRes::Function(()), ident_property.to_id()); + self.add_export(DefRes::Undefined, ident_property.to_id()); self.res_table .exported_names .insert((ident.sym.clone(), self.curr_mod), export_defid); @@ -3383,8 +3378,6 @@ impl Visit for ExportCollector<'_> { n.decl.visit_children_with(self); } - // POI - fn visit_export_named_specifier(&mut self, n: &ExportNamedSpecifier) { if let ModuleExportName::Ident(ident) = &n.orig { let export_defid = self.add_export(DefRes::Undefined, ident.to_id()); @@ -3820,7 +3813,6 @@ impl Definitions { } DefKind::Class(_) => { let objid = classes.push_and_get_key(Class::new(id)); - println!("pusing class 1"); DefKind::Class(objid) } DefKind::Foreign(d) => DefKind::Foreign(d), diff --git a/crates/forge_analyzer/src/interp.rs b/crates/forge_analyzer/src/interp.rs index 010198a1..d44b6494 100644 --- a/crates/forge_analyzer/src/interp.rs +++ b/crates/forge_analyzer/src/interp.rs @@ -806,13 +806,10 @@ impl<'cx, C: Runner<'cx>> Interp<'cx, C> { worklist.push_front_blocks(self.env, func_def, self.call_all); let old_body = self.curr_body.get(); while let Some((def, block_id)) = worklist.pop_front() { - println!("def from popping {def:?} block id from popping {block_id:?}"); - let arguments = self.callstack_arguments.pop(); let name = self.env.def_name(def); debug!("Dataflow: {name} - {block_id}"); self.dataflow_visited.insert(def); - println!("expect body 8"); let func = self.env().def_ref(def).expect_body(); self.curr_body.set(Some(func)) let block = func.block(block_id); diff --git a/crates/forge_permission_resolver/src/permissions_resolver.rs b/crates/forge_permission_resolver/src/permissions_resolver.rs index a25e09f1..1cd57eae 100644 --- a/crates/forge_permission_resolver/src/permissions_resolver.rs +++ b/crates/forge_permission_resolver/src/permissions_resolver.rs @@ -108,11 +108,8 @@ pub fn get_permisions_for( endpoint_map_classic: &mut PermissionHashMap, endpoint_regex: &mut HashMap, ) { - println!("prev to call"); if let Result::Ok(response) = ureq::get(url).call() { - println!("prev to parsing"); let data: SwaggerReponse = response.into_json().unwrap(); - println!("finish parsing"); for (key, endpoint_data) in &data.paths { let endpoint_data = get_request_type(endpoint_data, key); endpoint_data From 2331c0e05f956954f58d2dc70b9ce3f1fe8b73be Mon Sep 17 00:00:00 2001 From: James Gersbach Date: Thu, 10 Aug 2023 13:55:43 -0700 Subject: [PATCH 473/517] improvments to performance --- crates/forge_analyzer/src/checkers.rs | 6 +++--- crates/forge_analyzer/src/definitions.rs | 15 ++++++--------- .../src/permissions_resolver.rs | 3 +++ .../jira-damn-vulnerable-forge-app/src/utils.js | 2 +- 4 files changed, 13 insertions(+), 13 deletions(-) diff --git a/crates/forge_analyzer/src/checkers.rs b/crates/forge_analyzer/src/checkers.rs index 8b783fec..b2ef8db7 100644 --- a/crates/forge_analyzer/src/checkers.rs +++ b/crates/forge_analyzer/src/checkers.rs @@ -896,7 +896,7 @@ impl<'cx> Dataflow<'cx> for AuthenticateDataflow { ) { self.super_join_term(interp, def, block, state, worklist); for def in self.needs_call.drain(..) { - worklist.push_front_blocks(interp.env(), def); + worklist.push_front_blocks(interp.env(), def, interp.call_all); } } } @@ -1104,7 +1104,7 @@ impl<'cx> Dataflow<'cx> for SecretDataflow { ) { self.super_join_term(interp, def, block, state, worklist); for def in self.needs_call.drain(..) { - worklist.push_front_blocks(interp.env(), def); + worklist.push_front_blocks(interp.env(), def, interp.call_all); } } } @@ -1838,7 +1838,7 @@ impl<'cx> Dataflow<'cx> for PermissionDataflow { ) { self.super_join_term(interp, def, block, state, worklist); for (def, arguments, values) in self.needs_call.drain(..) { - worklist.push_front_blocks(interp.env(), def); + worklist.push_front_blocks(interp.env(), def, interp.call_all); interp.callstack_arguments.push(values.clone()); } } diff --git a/crates/forge_analyzer/src/definitions.rs b/crates/forge_analyzer/src/definitions.rs index 542fc16e..0601e758 100644 --- a/crates/forge_analyzer/src/definitions.rs +++ b/crates/forge_analyzer/src/definitions.rs @@ -3082,11 +3082,6 @@ impl Visit for ImportCollector<'_> { debug_assert_ne!(self.curr_mod, mod_id); match self.resolver.default_export(mod_id) { Some(def) => { - println!( - "def -- {:?} {def:?} {:?} {:?}", - n.local, mod_id, self.curr_mod - ); - self.resolver.resolver.symbol_to_id.insert( Symbol { module: self.curr_mod, @@ -3284,10 +3279,11 @@ impl Visit for ExportCollector<'_> { ), Expr::Ident(ident) => { self.add_default(DefRes::Undefined, None); - self.res_table.default_export_names.insert( - (ident.sym.clone(), self.curr_mod), - self.default.unwrap(), - ); + // adding the default export, so we can resolve it during the lowering + self.res_table + .exported_names + .insert((ident.sym.clone(), self.curr_mod), self.default.unwrap()); + } _ => {} } @@ -3394,6 +3390,7 @@ impl Visit for ExportCollector<'_> { n.visit_children_with(self) } } + } fn visit_export_default_expr(&mut self, n: &ExportDefaultExpr) { if let Expr::Ident(ident) = &*n.expr { diff --git a/crates/forge_permission_resolver/src/permissions_resolver.rs b/crates/forge_permission_resolver/src/permissions_resolver.rs index 1cd57eae..e3e8cee8 100644 --- a/crates/forge_permission_resolver/src/permissions_resolver.rs +++ b/crates/forge_permission_resolver/src/permissions_resolver.rs @@ -108,8 +108,11 @@ pub fn get_permisions_for( endpoint_map_classic: &mut PermissionHashMap, endpoint_regex: &mut HashMap, ) { + println!("pre api call"); if let Result::Ok(response) = ureq::get(url).call() { + println!("post api call"); let data: SwaggerReponse = response.into_json().unwrap(); + println!("post swagger parse"); for (key, endpoint_data) in &data.paths { let endpoint_data = get_request_type(endpoint_data, key); endpoint_data diff --git a/test-apps/jira-damn-vulnerable-forge-app/src/utils.js b/test-apps/jira-damn-vulnerable-forge-app/src/utils.js index 61e822f1..69fe3b6f 100755 --- a/test-apps/jira-damn-vulnerable-forge-app/src/utils.js +++ b/test-apps/jira-damn-vulnerable-forge-app/src/utils.js @@ -74,7 +74,7 @@ export async function fetchIssueSummary(issueIdOrKey, test_value) { // diffunc(); // different_function(); - let a_class = new ANewClass(); + let a_class = new classone(); a_class.function_a_new_class(); let val = 'grapefruit'; From e2912664ad07cf83680109bde53f65f47a68c442 Mon Sep 17 00:00:00 2001 From: James Gersbach Date: Fri, 11 Aug 2023 09:39:17 -0700 Subject: [PATCH 474/517] working global lowerer --- crates/forge_analyzer/src/checkers.rs | 127 +++++++++++------- crates/forge_analyzer/src/definitions.rs | 18 ++- .../src/utils.js | 4 +- 3 files changed, 94 insertions(+), 55 deletions(-) diff --git a/crates/forge_analyzer/src/checkers.rs b/crates/forge_analyzer/src/checkers.rs index b2ef8db7..0c6fb3a2 100644 --- a/crates/forge_analyzer/src/checkers.rs +++ b/crates/forge_analyzer/src/checkers.rs @@ -1359,7 +1359,9 @@ impl<'cx> Dataflow<'cx> for PermissionDataflow { if let Some(operand) = first { match operand { Operand::Lit(lit) => { - intrinsic_argument.first_arg = Some(vec![lit.to_string()]); + if &Literal::Undef != lit { + intrinsic_argument.first_arg = Some(vec![lit.to_string()]); + } } Operand::Var(var) => { if let Base::Var(varid) = var.base { @@ -1386,18 +1388,58 @@ impl<'cx> Dataflow<'cx> for PermissionDataflow { let mut permissions_within_call: Vec = vec![]; let intrinsic_func_type = intrinsic_argument.name.unwrap(); - intrinsic_argument - .first_arg - .iter() - .for_each(|first_arg_vec| { - if let Some(second_arg_vec) = intrinsic_argument.second_arg.clone() { - first_arg_vec.iter().for_each(|first_arg| { - second_arg_vec.iter().for_each(|second_arg| { + + //println!("intrinsic_argument {:?}", intrinsic_argument); + + if intrinsic_argument.first_arg == None { + println!("interp permissions {:?}", _interp.permissions); + println!("found none ...."); + _interp.permissions.drain(..); + println!("interp permissions {:?}", _interp.permissions); + } else { + intrinsic_argument + .first_arg + .iter() + .for_each(|first_arg_vec| { + if let Some(second_arg_vec) = intrinsic_argument.second_arg.clone() { + first_arg_vec.iter().for_each(|first_arg| { + println!("first arg before {first_arg:?}"); + let first_arg = first_arg.replace(&['\"'][..], ""); + + println!("first arg after {first_arg:?}"); + + second_arg_vec.iter().for_each(|second_arg| { + if intrinsic_func_type == IntrinsicName::RequestConfluence { + let permissions = check_url_for_permissions( + &_interp.confluence_permission_resolver, + &_interp.confluence_regex_map, + translate_request_type(Some(second_arg)), + &first_arg, + ); + permissions_within_call.extend_from_slice(&permissions) + } else if intrinsic_func_type == IntrinsicName::RequestJira { + let permissions = check_url_for_permissions( + &_interp.jira_permission_resolver, + &_interp.jira_regex_map, + translate_request_type(Some(second_arg)), + &first_arg, + ); + permissions_within_call.extend_from_slice(&permissions) + } + }) + }) + } else { + first_arg_vec.iter().for_each(|first_arg| { + println!("first arg before {first_arg:?}"); + let first_arg = first_arg.replace(&['\"'][..], ""); + + println!("first arg after {first_arg:?}"); + if intrinsic_func_type == IntrinsicName::RequestConfluence { let permissions = check_url_for_permissions( &_interp.confluence_permission_resolver, &_interp.confluence_regex_map, - translate_request_type(Some(second_arg)), + RequestType::Get, &first_arg, ); permissions_within_call.extend_from_slice(&permissions) @@ -1405,39 +1447,26 @@ impl<'cx> Dataflow<'cx> for PermissionDataflow { let permissions = check_url_for_permissions( &_interp.jira_permission_resolver, &_interp.jira_regex_map, - translate_request_type(Some(second_arg)), + RequestType::Get, &first_arg, ); permissions_within_call.extend_from_slice(&permissions) } }) - }) - } else { - first_arg_vec.iter().for_each(|first_arg| { - if intrinsic_func_type == IntrinsicName::RequestConfluence { - let permissions = check_url_for_permissions( - &_interp.confluence_permission_resolver, - &_interp.confluence_regex_map, - RequestType::Get, - &first_arg, - ); - permissions_within_call.extend_from_slice(&permissions) - } else if intrinsic_func_type == IntrinsicName::RequestJira { - let permissions = check_url_for_permissions( - &_interp.jira_permission_resolver, - &_interp.jira_regex_map, - RequestType::Get, - &first_arg, - ); - permissions_within_call.extend_from_slice(&permissions) - } - }) - } - }); + } + }); + + println!("permissions within call {permissions_within_call:?}"); + + _interp.permissions = _interp + .permissions + .iter() + .cloned() + .filter(|permissions| !permissions_within_call.contains(permissions)) + .collect_vec(); + } - _interp - .permissions - .extend_from_slice(&permissions_within_call); + // remvove all permissions that it finds } initial_state } @@ -1772,18 +1801,20 @@ impl<'cx> Dataflow<'cx> for PermissionDataflow { ) { match operand { Operand::Lit(_lit) => { - if let Some(prev_values) = prev_values { - if let Some(lit_value) = convert_operand_to_raw(operand) { - let const_value = Const::Literal(lit_value); - let mut all_values = prev_values.clone(); - all_values.push(const_value); - let value = Value::Phi(all_values); - interp.add_value(def, *varid, value, lval.projections.get(0).cloned()); - } - } else { - if let Some(lit_value) = convert_operand_to_raw(operand) { - let value = Value::Const(Const::Literal(lit_value)); - interp.add_value(def, *varid, value, lval.projections.get(0).cloned()); + if &Literal::Undef != _lit { + if let Some(prev_values) = prev_values { + if let Some(lit_value) = convert_operand_to_raw(operand) { + let const_value = Const::Literal(lit_value); + let mut all_values = prev_values.clone(); + all_values.push(const_value); + let value = Value::Phi(all_values); + interp.add_value(def, *varid, value, lval.projections.get(0).cloned()); + } + } else { + if let Some(lit_value) = convert_operand_to_raw(operand) { + let value = Value::Const(Const::Literal(lit_value)); + interp.add_value(def, *varid, value, lval.projections.get(0).cloned()); + } } } } diff --git a/crates/forge_analyzer/src/definitions.rs b/crates/forge_analyzer/src/definitions.rs index 0601e758..2c7c222e 100644 --- a/crates/forge_analyzer/src/definitions.rs +++ b/crates/forge_analyzer/src/definitions.rs @@ -190,6 +190,7 @@ pub fn run_resolver( module.visit_with(&mut import_collector); } + let mut foreign = TiVec::default(); for (curr_mod, module) in modules.iter_enumerated() { let mut import_collector = ImportCollector { @@ -224,6 +225,7 @@ pub fn run_resolver( module.visit_with(&mut lowerer); } + for (curr_mod, module) in modules.iter_enumerated() { let global_id = environment.get_or_reserve_global_scope(curr_mod); let mut global_collector = GlobalCollector { @@ -538,6 +540,8 @@ pub struct Environment { pub resolver: ResolverTable, } +// POI + struct ImportCollector<'cx> { resolver: &'cx mut Environment, file_resolver: &'cx ForgeResolver, @@ -1686,7 +1690,11 @@ impl<'cx> FunctionAnalyzer<'cx> { let var = self.body.get_or_insert_global(def); Operand::with_var(var) } - Expr::Lit(lit) => lit.clone().into(), + Expr::Lit(lit) => { + println!("lowering literal {lit:?}"); + + lit.clone().into() + } Expr::Tpl(tpl) => { let tpl = self.lower_tpl(tpl); Operand::with_var( @@ -3280,10 +3288,10 @@ impl Visit for ExportCollector<'_> { Expr::Ident(ident) => { self.add_default(DefRes::Undefined, None); // adding the default export, so we can resolve it during the lowering - self.res_table - .exported_names - .insert((ident.sym.clone(), self.curr_mod), self.default.unwrap()); - + self.res_table.exported_names.insert( + (ident.sym.clone(), self.curr_mod), + self.default.unwrap(), + ); } _ => {} } diff --git a/test-apps/jira-damn-vulnerable-forge-app/src/utils.js b/test-apps/jira-damn-vulnerable-forge-app/src/utils.js index 69fe3b6f..5d8f3413 100755 --- a/test-apps/jira-damn-vulnerable-forge-app/src/utils.js +++ b/test-apps/jira-damn-vulnerable-forge-app/src/utils.js @@ -74,7 +74,7 @@ export async function fetchIssueSummary(issueIdOrKey, test_value) { // diffunc(); // different_function(); - let a_class = new classone(); + let a_class = new ANewClass(); a_class.function_a_new_class(); let val = 'grapefruit'; @@ -116,7 +116,7 @@ export async function writeComment(issueIdOrKey, comment) { const resp = await api .asApp() .requestJira(route`/rest/api/3/issue/${issueIdOrKey}/comment`, { - method: 'DELETE', + method: 'POST', headers: { Accept: 'application/json', 'Content-Type': 'application/json', From 3161af1397fce5b6a9d97552824cd24a4a8e3314 Mon Sep 17 00:00:00 2001 From: James Gersbach Date: Fri, 11 Aug 2023 10:53:56 -0700 Subject: [PATCH 475/517] fix to permission checker --- crates/forge_analyzer/src/checkers.rs | 34 ++++++++++++++++++++++-- crates/forge_analyzer/src/definitions.rs | 2 -- 2 files changed, 32 insertions(+), 4 deletions(-) diff --git a/crates/forge_analyzer/src/checkers.rs b/crates/forge_analyzer/src/checkers.rs index 0c6fb3a2..1554340c 100644 --- a/crates/forge_analyzer/src/checkers.rs +++ b/crates/forge_analyzer/src/checkers.rs @@ -1241,7 +1241,7 @@ impl<'cx> Runner<'cx> for SecretChecker { { match value { Value::Const(_) | Value::Phi(_) => { - let vuln = SecretVunew( + let vuln = SecretVuln::new( interp.callstack(), interp.env(), interp.entry(), @@ -1251,6 +1251,23 @@ impl<'cx> Runner<'cx> for SecretChecker { } _ => {} } + } else if let Some(value) = interp.body().vars.get(varid) { + if let VarKind::GlobalRef(def) = value { + if let Some(value) = interp.defid_to_value.get(def) { + match value { + Value::Const(_) | Value::Phi(_) => { + let vuln = SecretVuln::new( + interp.callstack(), + interp.env(), + interp.entry(), + ); + info!("Found a vuln!"); + self.vulns.push(vuln); + } + _ => {} + } + } + } } } } @@ -1290,6 +1307,7 @@ impl<'cx> Dataflow<'cx> for PermissionDataflow { Self { needs_call: vec![], varid_to_value: FxHashMap::default(), + defid_to_value: FxHashMap::default(), } } @@ -1371,6 +1389,18 @@ impl<'cx> Dataflow<'cx> for PermissionDataflow { value, &mut intrinsic_argument.first_arg, ); + } else if let Some(value) = _interp.body().vars.get(varid) { + if let VarKind::GlobalRef(def) = value { + if let Some(Value::Const(value)) = + _interp.defid_to_value.get(def) + { + intrinsic_argument.first_arg = Some(vec![]); + add_elements_to_intrinsic_struct( + &Value::Const(value.clone()), + &mut intrinsic_argument.first_arg, + ); + } + } } } } @@ -1389,7 +1419,7 @@ impl<'cx> Dataflow<'cx> for PermissionDataflow { let mut permissions_within_call: Vec = vec![]; let intrinsic_func_type = intrinsic_argument.name.unwrap(); - //println!("intrinsic_argument {:?}", intrinsic_argument); + println!("intrinsic_argument {:?}", intrinsic_argument); if intrinsic_argument.first_arg == None { println!("interp permissions {:?}", _interp.permissions); diff --git a/crates/forge_analyzer/src/definitions.rs b/crates/forge_analyzer/src/definitions.rs index 2c7c222e..e952e797 100644 --- a/crates/forge_analyzer/src/definitions.rs +++ b/crates/forge_analyzer/src/definitions.rs @@ -1691,8 +1691,6 @@ impl<'cx> FunctionAnalyzer<'cx> { Operand::with_var(var) } Expr::Lit(lit) => { - println!("lowering literal {lit:?}"); - lit.clone().into() } Expr::Tpl(tpl) => { From da3b616462ee18783e4a16f92e5c618ff1caa663 Mon Sep 17 00:00:00 2001 From: James Gersbach Date: Fri, 11 Aug 2023 10:54:32 -0700 Subject: [PATCH 476/517] formatting --- crates/forge_analyzer/src/definitions.rs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/crates/forge_analyzer/src/definitions.rs b/crates/forge_analyzer/src/definitions.rs index e952e797..5a1eed75 100644 --- a/crates/forge_analyzer/src/definitions.rs +++ b/crates/forge_analyzer/src/definitions.rs @@ -1690,9 +1690,7 @@ impl<'cx> FunctionAnalyzer<'cx> { let var = self.body.get_or_insert_global(def); Operand::with_var(var) } - Expr::Lit(lit) => { - lit.clone().into() - } + Expr::Lit(lit) => lit.clone().into(), Expr::Tpl(tpl) => { let tpl = self.lower_tpl(tpl); Operand::with_var( From b9ddcc5681607438cac08fd4faed44ae38796ef1 Mon Sep 17 00:00:00 2001 From: James Gersbach Date: Fri, 11 Aug 2023 15:18:47 -0700 Subject: [PATCH 477/517] currnet --- crates/forge_analyzer/src/checkers.rs | 12 ++---------- .../src/permissions_resolver.rs | 3 --- 2 files changed, 2 insertions(+), 13 deletions(-) diff --git a/crates/forge_analyzer/src/checkers.rs b/crates/forge_analyzer/src/checkers.rs index 1554340c..ab194fda 100644 --- a/crates/forge_analyzer/src/checkers.rs +++ b/crates/forge_analyzer/src/checkers.rs @@ -1419,7 +1419,7 @@ impl<'cx> Dataflow<'cx> for PermissionDataflow { let mut permissions_within_call: Vec = vec![]; let intrinsic_func_type = intrinsic_argument.name.unwrap(); - println!("intrinsic_argument {:?}", intrinsic_argument); + //println!("intrinsic_argument {:?}", intrinsic_argument); if intrinsic_argument.first_arg == None { println!("interp permissions {:?}", _interp.permissions); @@ -1433,11 +1433,7 @@ impl<'cx> Dataflow<'cx> for PermissionDataflow { .for_each(|first_arg_vec| { if let Some(second_arg_vec) = intrinsic_argument.second_arg.clone() { first_arg_vec.iter().for_each(|first_arg| { - println!("first arg before {first_arg:?}"); let first_arg = first_arg.replace(&['\"'][..], ""); - - println!("first arg after {first_arg:?}"); - second_arg_vec.iter().for_each(|second_arg| { if intrinsic_func_type == IntrinsicName::RequestConfluence { let permissions = check_url_for_permissions( @@ -1460,11 +1456,7 @@ impl<'cx> Dataflow<'cx> for PermissionDataflow { }) } else { first_arg_vec.iter().for_each(|first_arg| { - println!("first arg before {first_arg:?}"); let first_arg = first_arg.replace(&['\"'][..], ""); - - println!("first arg after {first_arg:?}"); - if intrinsic_func_type == IntrinsicName::RequestConfluence { let permissions = check_url_for_permissions( &_interp.confluence_permission_resolver, @@ -1486,7 +1478,7 @@ impl<'cx> Dataflow<'cx> for PermissionDataflow { } }); - println!("permissions within call {permissions_within_call:?}"); + //println!("permissions within call {permissions_within_call:?}"); _interp.permissions = _interp .permissions diff --git a/crates/forge_permission_resolver/src/permissions_resolver.rs b/crates/forge_permission_resolver/src/permissions_resolver.rs index e3e8cee8..1cd57eae 100644 --- a/crates/forge_permission_resolver/src/permissions_resolver.rs +++ b/crates/forge_permission_resolver/src/permissions_resolver.rs @@ -108,11 +108,8 @@ pub fn get_permisions_for( endpoint_map_classic: &mut PermissionHashMap, endpoint_regex: &mut HashMap, ) { - println!("pre api call"); if let Result::Ok(response) = ureq::get(url).call() { - println!("post api call"); let data: SwaggerReponse = response.into_json().unwrap(); - println!("post swagger parse"); for (key, endpoint_data) in &data.paths { let endpoint_data = get_request_type(endpoint_data, key); endpoint_data From 8b984069e1d7c55b12d4798c5d152bef28cbbfc2 Mon Sep 17 00:00:00 2001 From: Joshua Wong Date: Thu, 7 Sep 2023 15:31:47 -0500 Subject: [PATCH 478/517] chore: move test module directly below the permission_resolver module and only build during testing. --- crates/forge_permission_resolver/src/test.rs | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/crates/forge_permission_resolver/src/test.rs b/crates/forge_permission_resolver/src/test.rs index 51cc8efd..e9b8a01e 100644 --- a/crates/forge_permission_resolver/src/test.rs +++ b/crates/forge_permission_resolver/src/test.rs @@ -8,11 +8,6 @@ mod tests { get_permission_resolver_confluence, get_permission_resolver_jira, }; - use super::*; - use crate::permissions_resolver::{ - get_permission_resolver_confluence, get_permission_resolver_jira, - }; - #[test] fn test_simple_url_with_end_var() { let result = find_regex_for_endpoint("/rest/api/3/version/{id}/mergeto/{moveIssuesTo}"); @@ -80,10 +75,4 @@ mod tests { assert_eq!(result, expected_permission); } - - // TODO: Add a test case using a manifest that has a function exposed through both a non user invocable module and a user invocable module - #[test] - fn test_catch_indirect_func_invoke() { - assert_eq!(0, 0); - } } From 67b79beeb56939ede06943360a1f1fa68417b6f7 Mon Sep 17 00:00:00 2001 From: Gersbach Date: Mon, 16 Oct 2023 16:11:26 -0400 Subject: [PATCH 479/517] cleaning up codebase --- crates/forge_analyzer/src/checkers.rs | 9 +++------ crates/forge_analyzer/src/utils.rs | 2 +- 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/crates/forge_analyzer/src/checkers.rs b/crates/forge_analyzer/src/checkers.rs index ab194fda..a36443c3 100644 --- a/crates/forge_analyzer/src/checkers.rs +++ b/crates/forge_analyzer/src/checkers.rs @@ -1422,10 +1422,7 @@ impl<'cx> Dataflow<'cx> for PermissionDataflow { //println!("intrinsic_argument {:?}", intrinsic_argument); if intrinsic_argument.first_arg == None { - println!("interp permissions {:?}", _interp.permissions); - println!("found none ...."); _interp.permissions.drain(..); - println!("interp permissions {:?}", _interp.permissions); } else { intrinsic_argument .first_arg @@ -1822,8 +1819,8 @@ impl<'cx> Dataflow<'cx> for PermissionDataflow { prev_values: Option>, ) { match operand { - Operand::Lit(_lit) => { - if &Literal::Undef != _lit { + Operand::Lit(lit) => { + if &Literal::Undef != lit { if let Some(prev_values) = prev_values { if let Some(lit_value) = convert_operand_to_raw(operand) { let const_value = Const::Literal(lit_value); @@ -1890,7 +1887,7 @@ impl<'cx> Dataflow<'cx> for PermissionDataflow { worklist: &mut WorkList, ) { self.super_join_term(interp, def, block, state, worklist); - for (def, arguments, values) in self.needs_call.drain(..) { + for (def, _arguments, values) in self.needs_call.drain(..) { worklist.push_front_blocks(interp.env(), def, interp.call_all); interp.callstack_arguments.push(values.clone()); } diff --git a/crates/forge_analyzer/src/utils.rs b/crates/forge_analyzer/src/utils.rs index 49c0859e..c902b83c 100644 --- a/crates/forge_analyzer/src/utils.rs +++ b/crates/forge_analyzer/src/utils.rs @@ -157,4 +157,4 @@ pub fn eq_prop_name(n: &MemberProp, name: &str) -> bool { }, _ => false, } -} +} \ No newline at end of file From 2a700d4466677b4ad0fc57b5c5028cc4a2f05e15 Mon Sep 17 00:00:00 2001 From: Gersbach Date: Tue, 17 Oct 2023 15:05:53 -0400 Subject: [PATCH 480/517] refractoring to a value manager --- crates/forge_analyzer/src/checkers.rs | 11 +++++------ crates/forge_analyzer/src/interp.rs | 1 - crates/forge_analyzer/src/utils.rs | 2 +- 3 files changed, 6 insertions(+), 8 deletions(-) diff --git a/crates/forge_analyzer/src/checkers.rs b/crates/forge_analyzer/src/checkers.rs index a36443c3..9304fe9d 100644 --- a/crates/forge_analyzer/src/checkers.rs +++ b/crates/forge_analyzer/src/checkers.rs @@ -1392,7 +1392,7 @@ impl<'cx> Dataflow<'cx> for PermissionDataflow { } else if let Some(value) = _interp.body().vars.get(varid) { if let VarKind::GlobalRef(def) = value { if let Some(Value::Const(value)) = - _interp.defid_to_value.get(def) + _interp.value_manager.defid_to_value.get(def) { intrinsic_argument.first_arg = Some(vec![]); add_elements_to_intrinsic_struct( @@ -1419,8 +1419,6 @@ impl<'cx> Dataflow<'cx> for PermissionDataflow { let mut permissions_within_call: Vec = vec![]; let intrinsic_func_type = intrinsic_argument.name.unwrap(); - //println!("intrinsic_argument {:?}", intrinsic_argument); - if intrinsic_argument.first_arg == None { _interp.permissions.drain(..); } else { @@ -1671,7 +1669,7 @@ impl<'cx> Dataflow<'cx> for PermissionDataflow { for (varid, varkind) in interp.body().vars.clone().iter_enumerated() { if &VarKind::Ret == varkind { if let Some((defid_calling_func, varid_calling_func)) = - interp.expected_return_values.get(&def) + interp.value_manager.expected_return_values.get(&def) { if let Some(value) = interp.get_value(def, varid, None) { interp.add_value( @@ -1701,7 +1699,7 @@ impl<'cx> Dataflow<'cx> for PermissionDataflow { // transfer all of the variables if let Operand::Var(variable) = operand { if let Base::Var(varid_rval) = variable.base { - interp.varid_to_value.clone().iter().for_each( + interp.value_manager.varid_to_value.clone().iter().for_each( |((defid, varid_rval_potential, projection), value)| { if varid_rval_potential == &varid_rval { interp.add_value(def, *varid, value.clone(), projection.clone()) @@ -1796,6 +1794,7 @@ impl<'cx> Dataflow<'cx> for PermissionDataflow { _ => {} } interp + .value_manager .varid_to_value .insert((def, *varid, None), return_value_from_string(new_vals)); } else if let Some(val1) = val1 { @@ -1867,7 +1866,7 @@ impl<'cx> Dataflow<'cx> for PermissionDataflow { } } else { if let Some(potential_value) = interp.get_value(def, prev_varid, None) { - interp.varid_to_value.insert( + interp.value_manager.varid_to_value.insert( (def, *varid, var.projections.get(0).cloned()), potential_value.clone(), ); diff --git a/crates/forge_analyzer/src/interp.rs b/crates/forge_analyzer/src/interp.rs index d44b6494..d34a236b 100644 --- a/crates/forge_analyzer/src/interp.rs +++ b/crates/forge_analyzer/src/interp.rs @@ -640,7 +640,6 @@ impl<'cx, C: Runner<'cx>> Interp<'cx, C> { dataflow_visited: FxHashSet::default(), checker_visited: RefCell::new(FxHashSet::default()), callstack_arguments: Vec::new(), - expecting_value: VecDeque::default(), callstack: RefCell::new(Vec::new()), // vulns: RefCell::new(Vec::new()), value_manager: ValueManager { diff --git a/crates/forge_analyzer/src/utils.rs b/crates/forge_analyzer/src/utils.rs index c902b83c..49c0859e 100644 --- a/crates/forge_analyzer/src/utils.rs +++ b/crates/forge_analyzer/src/utils.rs @@ -157,4 +157,4 @@ pub fn eq_prop_name(n: &MemberProp, name: &str) -> bool { }, _ => false, } -} \ No newline at end of file +} From 5ef7c9087da31ade2f5073519e502ff535a98e67 Mon Sep 17 00:00:00 2001 From: Joshua Wong Date: Tue, 26 Sep 2023 11:56:05 -0500 Subject: [PATCH 481/517] feat: recognize manual authorization for custom fields --- Cargo.lock | 14 ++++++++++++++ crates/forge_analyzer/Cargo.toml | 1 + crates/forge_analyzer/src/definitions.rs | 2 +- crates/forge_analyzer/src/utils.rs | 9 +++++++++ crates/fsrt/src/main.rs | 1 - 5 files changed, 25 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 9816cb91..a57ee199 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -578,6 +578,7 @@ dependencies = [ "serde", "serde_json", "smallvec", + "stacker", "swc_core", "time 0.3.30", "tracing", @@ -1841,6 +1842,19 @@ dependencies = [ "winapi", ] +[[package]] +name = "stacker" +version = "0.1.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c886bd4480155fd3ef527d45e9ac8dd7118a898a46530b7b94c3e21866259fce" +dependencies = [ + "cc", + "cfg-if", + "libc", + "psm", + "winapi", +] + [[package]] name = "static-map-macro" version = "0.3.0" diff --git a/crates/forge_analyzer/Cargo.toml b/crates/forge_analyzer/Cargo.toml index 050e88a1..1fbbfe68 100644 --- a/crates/forge_analyzer/Cargo.toml +++ b/crates/forge_analyzer/Cargo.toml @@ -21,6 +21,7 @@ petgraph.workspace = true regex.workspace = true serde_json.workspace = true serde.workspace = true +stacker.workspace = true smallvec.workspace = true swc_core.workspace = true time.workspace = true diff --git a/crates/forge_analyzer/src/definitions.rs b/crates/forge_analyzer/src/definitions.rs index 5a1eed75..cfb79b75 100644 --- a/crates/forge_analyzer/src/definitions.rs +++ b/crates/forge_analyzer/src/definitions.rs @@ -1847,7 +1847,7 @@ impl<'cx> FunctionAnalyzer<'cx> { alt: cont, }); let check = mem::replace(&mut self.block, body_id); - self.lower_stmt(body); + self.set_curr_terminator(Terminator::Goto(check)); self.block = cont; } diff --git a/crates/forge_analyzer/src/utils.rs b/crates/forge_analyzer/src/utils.rs index 49c0859e..7b6fd117 100644 --- a/crates/forge_analyzer/src/utils.rs +++ b/crates/forge_analyzer/src/utils.rs @@ -158,3 +158,12 @@ pub fn eq_prop_name(n: &MemberProp, name: &str) -> bool { _ => false, } } + +const RED_ZONE: usize = 100 * 1024; + +const STACK_SIZE: usize = 1024 * 1024; + +#[inline] +pub(crate) fn ensure_sufficient_stack(f: impl FnOnce() -> T) -> T { + stacker::maybe_grow(RED_ZONE, STACK_SIZE, || f()) +} diff --git a/crates/fsrt/src/main.rs b/crates/fsrt/src/main.rs index 7372e5fe..2996750a 100644 --- a/crates/fsrt/src/main.rs +++ b/crates/fsrt/src/main.rs @@ -9,7 +9,6 @@ use miette::{IntoDiagnostic, Result}; use std::{ collections::HashSet, fs, - io::{self, BufReader}, os::unix::prelude::OsStrExt, path::{Path, PathBuf}, sync::Arc, From 91f7bb99598cb045381cf628fb295857ee86ff0c Mon Sep 17 00:00:00 2001 From: Joshua Wong Date: Tue, 26 Sep 2023 12:13:30 -0500 Subject: [PATCH 482/517] fix: lower while loop body --- crates/forge_analyzer/src/definitions.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/forge_analyzer/src/definitions.rs b/crates/forge_analyzer/src/definitions.rs index cfb79b75..5a1eed75 100644 --- a/crates/forge_analyzer/src/definitions.rs +++ b/crates/forge_analyzer/src/definitions.rs @@ -1847,7 +1847,7 @@ impl<'cx> FunctionAnalyzer<'cx> { alt: cont, }); let check = mem::replace(&mut self.block, body_id); - + self.lower_stmt(body); self.set_curr_terminator(Terminator::Goto(check)); self.block = cont; } From 8917e3d16cccff12e4d852e74e7aa2db4fcf9075 Mon Sep 17 00:00:00 2001 From: Joshua Wong Date: Tue, 26 Sep 2023 12:15:57 -0500 Subject: [PATCH 483/517] fix: remove stacker usage in interp --- crates/forge_analyzer/src/utils.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/forge_analyzer/src/utils.rs b/crates/forge_analyzer/src/utils.rs index 7b6fd117..f873dbb8 100644 --- a/crates/forge_analyzer/src/utils.rs +++ b/crates/forge_analyzer/src/utils.rs @@ -165,5 +165,5 @@ const STACK_SIZE: usize = 1024 * 1024; #[inline] pub(crate) fn ensure_sufficient_stack(f: impl FnOnce() -> T) -> T { - stacker::maybe_grow(RED_ZONE, STACK_SIZE, || f()) + stacker::maybe_grow(RED_ZONE, STACK_SIZE, f) } From 5df5e38f3d9722f16b0710787f88cb0be8d84fc5 Mon Sep 17 00:00:00 2001 From: awang7 Date: Mon, 30 Oct 2023 10:35:17 -0400 Subject: [PATCH 484/517] chore: added additional known common package functions in forge apps for secret scanner --- secretdata.yaml | 28 +++++++++++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/secretdata.yaml b/secretdata.yaml index 7d4c77b8..3dbccddf 100644 --- a/secretdata.yaml +++ b/secretdata.yaml @@ -90,5 +90,31 @@ secret_position: 2 - package_name: jwt-simple identifier: encode - method: + secret_position: 2 +- package_name: alassian-jwt + function_name: encodeSymmetric + secret_position: 2 +- package_name: alassian-jwt + function_name: encodeAsymmetric + secret_position: 2 +- package_name: crypto-js + function_name: HmacMD5 + secret_position: 2 +- package_name: crypto-js + function_name: HmacSHA1 + secret_position: 2 +- package_name: crypto-js + function_name: HmacSHA256 + secret_position: 2 +- package_name: crypto-js + function_name: HmacSHA512 + secret_position: 2 +- package_name: crypto-js + function_name: PBKDF2 + secret_position: 1 +- package_name: crypto-js + function_name: encrypt + secret_position: 2 +- package_name: crypto-js + function_name: decrypt secret_position: 2 From 91d6f46c0198af8a45fc29ebbd5a6f067e4f57fc Mon Sep 17 00:00:00 2001 From: awang7 Date: Mon, 6 Nov 2023 11:46:14 -0500 Subject: [PATCH 485/517] feat: added scanning in secret scanner for packages imported using star method --- crates/forge_analyzer/src/definitions.rs | 10 ++++- secretdata.yaml | 49 +++++++++++++----------- 2 files changed, 36 insertions(+), 23 deletions(-) diff --git a/crates/forge_analyzer/src/definitions.rs b/crates/forge_analyzer/src/definitions.rs index 5a1eed75..e89df13a 100644 --- a/crates/forge_analyzer/src/definitions.rs +++ b/crates/forge_analyzer/src/definitions.rs @@ -933,6 +933,7 @@ impl<'cx> FunctionAnalyzer<'cx> { } match *callee { [PropPath::Unknown((ref name, ..))] if *name == *"fetch" => Some(Intrinsic::Fetch), + // [PropPath::Def(def)] => {}] [PropPath::Def(def), ref authn @ .., PropPath::Static(ref last)] if (*last == *"requestJira" || *last == *"requestConfluence") && Some(&ImportKind::Default) @@ -3734,7 +3735,14 @@ impl Environment { /// Check if def is exported from the foreign module specified in `module_name`. pub fn is_imported_from(&self, def: DefId, module_name: &str) -> Option<&ImportKind> { match self.def_ref(def) { - DefKind::Foreign(f) if f.module_name == *module_name => Some(&f.kind), + DefKind::Foreign(f) if f.module_name == *module_name => { + let return_value = Some(&f.kind); + debug!( + "Entered match statement. Does evaluate true! return_value: {return_value:?}" + ); + + Some(&f.kind) + } _ => None, } } diff --git a/secretdata.yaml b/secretdata.yaml index 3dbccddf..3b2f827d 100644 --- a/secretdata.yaml +++ b/secretdata.yaml @@ -94,27 +94,32 @@ - package_name: alassian-jwt function_name: encodeSymmetric secret_position: 2 -- package_name: alassian-jwt +- package_name: atlassian-jwt + identifier: star function_name: encodeAsymmetric secret_position: 2 -- package_name: crypto-js - function_name: HmacMD5 - secret_position: 2 -- package_name: crypto-js - function_name: HmacSHA1 - secret_position: 2 -- package_name: crypto-js - function_name: HmacSHA256 - secret_position: 2 -- package_name: crypto-js - function_name: HmacSHA512 - secret_position: 2 -- package_name: crypto-js - function_name: PBKDF2 - secret_position: 1 -- package_name: crypto-js - function_name: encrypt - secret_position: 2 -- package_name: crypto-js - function_name: decrypt - secret_position: 2 +# - package_name: crypto-js +# function_name: HmacMD5 +# secret_position: 2 +# - package_name: crypto-js +# function_name: HmacSHA1 +# secret_position: 2 +# - package_name: crypto-js +# function_name: HmacSHA256 +# secret_position: 2 +# - package_name: crypto-js +# function_name: HmacSHA512 +# secret_position: 2 +# - package_name: crypto-js +# function_name: PBKDF2 +# secret_position: 1 +# - package_name: crypto-js +# function_name: encrypt +# secret_position: 2 +# - package_name: crypto-js +# function_name: decrypt +# secret_position: 2 +# - package_name: jwt-simple +# function_name: encode +# secret_position: 2 + From a2b6c0d81557137d96a739bf4d6ef820d1de8515 Mon Sep 17 00:00:00 2001 From: awang7 Date: Mon, 6 Nov 2023 16:25:47 -0500 Subject: [PATCH 486/517] feat: added crypto-js to secretdata.yaml scanning list. And updated DVFA with test cases --- secretdata.yaml | 57 +++++++++++++++++++++++++++---------------------- 1 file changed, 32 insertions(+), 25 deletions(-) diff --git a/secretdata.yaml b/secretdata.yaml index 3b2f827d..6bde5e00 100644 --- a/secretdata.yaml +++ b/secretdata.yaml @@ -98,28 +98,35 @@ identifier: star function_name: encodeAsymmetric secret_position: 2 -# - package_name: crypto-js -# function_name: HmacMD5 -# secret_position: 2 -# - package_name: crypto-js -# function_name: HmacSHA1 -# secret_position: 2 -# - package_name: crypto-js -# function_name: HmacSHA256 -# secret_position: 2 -# - package_name: crypto-js -# function_name: HmacSHA512 -# secret_position: 2 -# - package_name: crypto-js -# function_name: PBKDF2 -# secret_position: 1 -# - package_name: crypto-js -# function_name: encrypt -# secret_position: 2 -# - package_name: crypto-js -# function_name: decrypt -# secret_position: 2 -# - package_name: jwt-simple -# function_name: encode -# secret_position: 2 - +- package_name: crypto-js + identifier: star + function_name: HmacMD5 + secret_position: 2 +- package_name: crypto-js + identifier: star + function_name: HmacSHA1 + secret_position: 2 +- package_name: crypto-js + identifier: star + function_name: HmacSHA256 + secret_position: 2 +- package_name: crypto-js + identifier: star + function_name: HmacSHA512 + secret_position: 2 +- package_name: crypto-js + identifier: star + function_name: PBKDF2 + secret_position: 1 +- package_name: crypto-js + identifier: star + function_name: encrypt + secret_position: 2 +- package_name: crypto-js + identifier: star + function_name: decrypt + secret_position: 2 +- package_name: jwt-simple + identifier: star + function_name: encode + secret_position: 2 From c557851dc2b80b1a91b93a16cece8e39d99fd895 Mon Sep 17 00:00:00 2001 From: awang7 Date: Tue, 7 Nov 2023 22:42:41 -0500 Subject: [PATCH 487/517] feat: updated secretdata.yaml with new flag identifier to catch object exports. added new scanning pathway for star imports where prop path is identifier or method with children object imports. --- secretdata.yaml | 76 +++++++++++++++++++++++++++++++++++++------------ 1 file changed, 58 insertions(+), 18 deletions(-) diff --git a/secretdata.yaml b/secretdata.yaml index 6bde5e00..61b45fb5 100644 --- a/secretdata.yaml +++ b/secretdata.yaml @@ -95,38 +95,78 @@ function_name: encodeSymmetric secret_position: 2 - package_name: atlassian-jwt - identifier: star - function_name: encodeAsymmetric + identifier: encodeAsymmetric + method: secret_position: 2 - package_name: crypto-js - identifier: star - function_name: HmacMD5 + identifier: HmacMD5 + method: secret_position: 2 - package_name: crypto-js - identifier: star - function_name: HmacSHA1 + identifier: HmacSHA1 + method: secret_position: 2 - package_name: crypto-js - identifier: star - function_name: HmacSHA256 + identifier: HmacSHA256 + method: secret_position: 2 - package_name: crypto-js - identifier: star - function_name: HmacSHA512 + identifier: HmacSHA512 + method: secret_position: 2 - package_name: crypto-js - identifier: star - function_name: PBKDF2 + identifier: PBKDF2 + method: secret_position: 1 - package_name: crypto-js - identifier: star - function_name: encrypt + identifier: AES + method: encrypt + secret_position: 2 +- package_name: crypto-js + identifier: AES + method: decrypt + secret_position: 2 +- package_name: crypto-js + identifier: DES + method: encrypt + secret_position: 2 +- package_name: crypto-js + identifier: DES + method: decrypt + secret_position: 2 +- package_name: crypto-js + identifier: TripleDES + method: encrypt + secret_position: 2 +- package_name: crypto-js + identifier: TripleDES + method: decrypt secret_position: 2 - package_name: crypto-js - identifier: star - function_name: decrypt + identifier: Rabbit + method: encrypt + secret_position: 2 +- package_name: crypto-js + identifier: Rabbit + method: decrypt + secret_position: 2 +- package_name: crypto-js + identifier: RC4 + method: encrypt + secret_position: 2 +- package_name: crypto-js + identifier: RC4 + method: decrypt + secret_position: 2 +- package_name: crypto-js + identifier: RC4DROP + method: encrypt + secret_position: 2 +- package_name: crypto-js + identifier: RC4Drop + method: decrypt secret_position: 2 - package_name: jwt-simple - identifier: star - function_name: encode + identifier: encode + method: secret_position: 2 From 9cc4fbdda4cf347af37291e964becae171841bb4 Mon Sep 17 00:00:00 2001 From: awang7 Date: Mon, 20 Nov 2023 17:04:23 -0500 Subject: [PATCH 488/517] fixed: revised fix_import to check for default and named functions. Added more test cases for named imports. todo: add more edge cases and review is_import --- crates/forge_analyzer/src/definitions.rs | 6 +++--- secretdata.yaml | 4 ++++ 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/crates/forge_analyzer/src/definitions.rs b/crates/forge_analyzer/src/definitions.rs index e89df13a..bc7ab24a 100644 --- a/crates/forge_analyzer/src/definitions.rs +++ b/crates/forge_analyzer/src/definitions.rs @@ -3737,9 +3737,9 @@ impl Environment { match self.def_ref(def) { DefKind::Foreign(f) if f.module_name == *module_name => { let return_value = Some(&f.kind); - debug!( - "Entered match statement. Does evaluate true! return_value: {return_value:?}" - ); + // debug!( + // "Entered match statement. Does evaluate true! return_value: {return_value:?}" + // ); Some(&f.kind) } diff --git a/secretdata.yaml b/secretdata.yaml index 61b45fb5..2be38084 100644 --- a/secretdata.yaml +++ b/secretdata.yaml @@ -12,6 +12,10 @@ identifier: verify method: secret_position: 2 +# - package_name: jsonwebtoken +# identifier: verify +# method: +# secret_position: 2 - package_name: atlassian-jwt identifier: encodeSymmetric method: From 6afd028249f67e7f2428317b7b2bdbd21cf23cce Mon Sep 17 00:00:00 2001 From: awang7 Date: Mon, 27 Nov 2023 15:55:14 -0500 Subject: [PATCH 489/517] chore: cleaned up test cases in util.js. Added verify to secretdata.yaml. Todo: resolve edge case where same secret scanner vuln got reported twice - after adding jsonwebtoken funtion verify --- crates/forge_analyzer/src/definitions.rs | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/crates/forge_analyzer/src/definitions.rs b/crates/forge_analyzer/src/definitions.rs index bc7ab24a..ac4a0d28 100644 --- a/crates/forge_analyzer/src/definitions.rs +++ b/crates/forge_analyzer/src/definitions.rs @@ -3735,14 +3735,7 @@ impl Environment { /// Check if def is exported from the foreign module specified in `module_name`. pub fn is_imported_from(&self, def: DefId, module_name: &str) -> Option<&ImportKind> { match self.def_ref(def) { - DefKind::Foreign(f) if f.module_name == *module_name => { - let return_value = Some(&f.kind); - // debug!( - // "Entered match statement. Does evaluate true! return_value: {return_value:?}" - // ); - - Some(&f.kind) - } + DefKind::Foreign(f) if f.module_name == *module_name => Some(&f.kind), _ => None, } } From d1cc73d6f98e22c111181781548aef7a273e74eb Mon Sep 17 00:00:00 2001 From: Joshua Wong Date: Tue, 28 Nov 2023 00:38:04 -0600 Subject: [PATCH 490/517] chore: bump deps --- Cargo.lock | 25 +------------------------ crates/forge_analyzer/Cargo.toml | 1 - crates/forge_analyzer/src/utils.rs | 9 --------- 3 files changed, 1 insertion(+), 34 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index a57ee199..2e15e3ee 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -578,7 +578,6 @@ dependencies = [ "serde", "serde_json", "smallvec", - "stacker", "swc_core", "time 0.3.30", "tracing", @@ -1214,7 +1213,7 @@ dependencies = [ "libc", "redox_syscall", "smallvec", - "windows-sys", + "windows-sys 0.42.0", ] [[package]] @@ -1402,15 +1401,6 @@ dependencies = [ "unicode-ident", ] -[[package]] -name = "psm" -version = "0.1.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5787f7cda34e3033a72192c018bc5883100330f362ef279a8cbccfce8bb4e874" -dependencies = [ - "cc", -] - [[package]] name = "quote" version = "1.0.32" @@ -1842,19 +1832,6 @@ dependencies = [ "winapi", ] -[[package]] -name = "stacker" -version = "0.1.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c886bd4480155fd3ef527d45e9ac8dd7118a898a46530b7b94c3e21866259fce" -dependencies = [ - "cc", - "cfg-if", - "libc", - "psm", - "winapi", -] - [[package]] name = "static-map-macro" version = "0.3.0" diff --git a/crates/forge_analyzer/Cargo.toml b/crates/forge_analyzer/Cargo.toml index 1fbbfe68..050e88a1 100644 --- a/crates/forge_analyzer/Cargo.toml +++ b/crates/forge_analyzer/Cargo.toml @@ -21,7 +21,6 @@ petgraph.workspace = true regex.workspace = true serde_json.workspace = true serde.workspace = true -stacker.workspace = true smallvec.workspace = true swc_core.workspace = true time.workspace = true diff --git a/crates/forge_analyzer/src/utils.rs b/crates/forge_analyzer/src/utils.rs index f873dbb8..49c0859e 100644 --- a/crates/forge_analyzer/src/utils.rs +++ b/crates/forge_analyzer/src/utils.rs @@ -158,12 +158,3 @@ pub fn eq_prop_name(n: &MemberProp, name: &str) -> bool { _ => false, } } - -const RED_ZONE: usize = 100 * 1024; - -const STACK_SIZE: usize = 1024 * 1024; - -#[inline] -pub(crate) fn ensure_sufficient_stack(f: impl FnOnce() -> T) -> T { - stacker::maybe_grow(RED_ZONE, STACK_SIZE, f) -} From e09ed8bc548488ce4eb6e94d794c9c506b78717a Mon Sep 17 00:00:00 2001 From: Joshua Wong Date: Tue, 28 Nov 2023 01:13:27 -0600 Subject: [PATCH 491/517] chore: bump swc version --- Cargo.lock | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/Cargo.lock b/Cargo.lock index 2e15e3ee..2d5d043b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1401,6 +1401,15 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "psm" +version = "0.1.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5787f7cda34e3033a72192c018bc5883100330f362ef279a8cbccfce8bb4e874" +dependencies = [ + "cc", +] + [[package]] name = "quote" version = "1.0.32" From 6836375f1b191ed5fb8a44314fb220fe8be6a028 Mon Sep 17 00:00:00 2001 From: Joshua Wong Date: Tue, 28 Nov 2023 12:51:20 -0600 Subject: [PATCH 492/517] fix: swc compile errors --- crates/forge_analyzer/src/checkers.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/forge_analyzer/src/checkers.rs b/crates/forge_analyzer/src/checkers.rs index 9304fe9d..0f30ac22 100644 --- a/crates/forge_analyzer/src/checkers.rs +++ b/crates/forge_analyzer/src/checkers.rs @@ -4,7 +4,7 @@ use std::{cmp::max, mem, ops::ControlFlow}; use tracing::{debug, info, warn}; use crate::{ - definitions::{Class, Const, DefId, DefKind, Environment, IntrinsicName, PackageData, Value}, + definitions::{Const, DefId, Environment, IntrinsicName, Value}, interp::{ Checker, Dataflow, EntryKind, EntryPoint, Frame, Interp, JoinSemiLattice, Runner, WithCallStack, From 9c5a14c32f128eb80200ed9535aa34ddc67c4e0b Mon Sep 17 00:00:00 2001 From: awang7 Date: Tue, 28 Nov 2023 23:58:30 -0500 Subject: [PATCH 493/517] refactor: rebuilt as_intrinsic to be sleaker and more organized. Tests passes in utils.js. --- crates/forge_analyzer/src/definitions.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/crates/forge_analyzer/src/definitions.rs b/crates/forge_analyzer/src/definitions.rs index ac4a0d28..5a1eed75 100644 --- a/crates/forge_analyzer/src/definitions.rs +++ b/crates/forge_analyzer/src/definitions.rs @@ -933,7 +933,6 @@ impl<'cx> FunctionAnalyzer<'cx> { } match *callee { [PropPath::Unknown((ref name, ..))] if *name == *"fetch" => Some(Intrinsic::Fetch), - // [PropPath::Def(def)] => {}] [PropPath::Def(def), ref authn @ .., PropPath::Static(ref last)] if (*last == *"requestJira" || *last == *"requestConfluence") && Some(&ImportKind::Default) From 54e317e19a00e3bd1ca75f4f329ff756638a76cb Mon Sep 17 00:00:00 2001 From: awang7 Date: Wed, 29 Nov 2023 15:28:04 -0500 Subject: [PATCH 494/517] fix: resolving PR comments. Pushing fixes to see which comments are left --- .gitignore | 1 - crates/forge_analyzer/src/definitions.rs | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/.gitignore b/.gitignore index 831f1db6..ea8c4bf7 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1 @@ /target -/test-apps diff --git a/crates/forge_analyzer/src/definitions.rs b/crates/forge_analyzer/src/definitions.rs index 5a1eed75..415379e2 100644 --- a/crates/forge_analyzer/src/definitions.rs +++ b/crates/forge_analyzer/src/definitions.rs @@ -1314,7 +1314,7 @@ impl<'cx> FunctionAnalyzer<'cx> { match n { Pat::Ident(BindingIdent { id, .. }) => { let id = id.to_id(); - let def = self.res.get_or_insert_sym(id.clone(), self.module); + let def = self.res.get_or_insert_sym(id, self.module); let var = self.body.get_or_insert_global(def); self.push_curr_inst(Inst::Assign(Variable::new(var), val)); } From 0b002fbef8d2e28474bf7f1341c6cfed11ce6aed Mon Sep 17 00:00:00 2001 From: awang7 Date: Thu, 30 Nov 2023 10:39:39 -0500 Subject: [PATCH 495/517] fix: removed the unwrap() on self.default and made add_defult return a DefId --- crates/forge_analyzer/src/definitions.rs | 87 ++++++++++++++++++++---- 1 file changed, 75 insertions(+), 12 deletions(-) diff --git a/crates/forge_analyzer/src/definitions.rs b/crates/forge_analyzer/src/definitions.rs index 415379e2..db2284aa 100644 --- a/crates/forge_analyzer/src/definitions.rs +++ b/crates/forge_analyzer/src/definitions.rs @@ -3020,6 +3020,8 @@ impl ExportCollector<'_> { self.res_table.owning_module.push(self.curr_mod); self.default = Some(defid); } + + // fn add_default(&mut self, def: DefRes, id: Option) -> DefId {} } // Import collector for run_resolver @@ -3273,22 +3275,83 @@ impl Visit for ExportCollector<'_> { if let MemberProp::Ident(ident_property) = &mem_expr.prop { if &ident_property.sym == "exports" { match &*n.right { - Expr::Fn(FnExpr { ident, function }) => self.add_default( - DefRes::Function(()), - ident.as_ref().map(Ident::to_id), - ), - Expr::Class(ClassExpr { ident, class }) => self.add_default( - DefRes::Class(()), - ident.as_ref().map(Ident::to_id), - ), + Expr::Fn(FnExpr { ident, function }) => { + self.add_default( + DefRes::Function(()), + ident.as_ref().map(Ident::to_id), + ); + } + Expr::Class(ClassExpr { ident, class }) => { + self.add_default( + DefRes::Class(()), + ident.as_ref().map(Ident::to_id), + ); + } Expr::Ident(ident) => { - self.add_default(DefRes::Undefined, None); + let default_id = self.add_default(DefRes::Undefined, None); // adding the default export, so we can resolve it during the lowering - self.res_table.exported_names.insert( - (ident.sym.clone(), self.curr_mod), - self.default.unwrap(), + self.res_table + .exported_names + .insert((ident.sym.clone(), self.curr_mod), default_id); + } + _ => {} + } + } + } + } + } else if &ident.sym == "exports" { + if let Some(mem_expr) = mem_expr_from_assign(n) { + if let MemberProp::Ident(ident_property) = &mem_expr.prop { + // TODO: handling aliases + match &*n.right { + Expr::Fn(FnExpr { ident, function }) => { + self.add_export(DefRes::Function(()), ident_property.to_id()); + } + Expr::Class(_) => { + self.add_export(DefRes::Class(()), ident_property.to_id()); + } + Expr::Ident(ident) => { + let export_defid = + self.add_export(DefRes::Undefined, ident_property.to_id()); + self.res_table + .exported_names + .insert((ident.sym.clone(), self.curr_mod), export_defid); + } + _ => {} + } + } + } + } + } + n.visit_children_with(self); + } + + fn visit_assign_expr(&mut self, n: &AssignExpr) { + if let Some(ident) = ident_from_assign_expr(n) { + if &ident.sym == "module" { + if let Some(mem_expr) = mem_expr_from_assign(n) { + if let MemberProp::Ident(ident_property) = &mem_expr.prop { + if &ident_property.sym == "exports" { + match &*n.right { + Expr::Fn(FnExpr { ident, function }) => { + self.add_default( + DefRes::Function(()), + ident.as_ref().map(Ident::to_id), ); } + Expr::Class(ClassExpr { ident, class }) => { + self.add_default( + DefRes::Class(()), + ident.as_ref().map(Ident::to_id), + ); + } + Expr::Ident(ident) => { + let default_id = self.add_default(DefRes::Undefined, None); + // adding the default export, so we can resolve it during the lowering + self.res_table + .exported_names + .insert((ident.sym.clone(), self.curr_mod), default_id); + } _ => {} } } From f2f4069b17820317f44c7bac03242b7ca7467158 Mon Sep 17 00:00:00 2001 From: Joshua Wong Date: Thu, 30 Nov 2023 13:21:55 -0600 Subject: [PATCH 496/517] fix: clean up some code --- crates/forge_analyzer/src/checkers.rs | 2 +- crates/forge_analyzer/src/definitions.rs | 26 ------------------------ 2 files changed, 1 insertion(+), 27 deletions(-) diff --git a/crates/forge_analyzer/src/checkers.rs b/crates/forge_analyzer/src/checkers.rs index 0f30ac22..ccb197b7 100644 --- a/crates/forge_analyzer/src/checkers.rs +++ b/crates/forge_analyzer/src/checkers.rs @@ -1220,7 +1220,7 @@ impl<'cx> Runner<'cx> for SecretChecker { state: &Self::State, operands: Option>, ) -> ControlFlow<(), Self::State> { - if let Intrinsic::JWTSign(package_data) = intrinsic { + if let Intrinsic::SecretFunction(package_data) = intrinsic { if let Some(operand) = operands .unwrap_or_default() .get((package_data.secret_position - 1) as usize) diff --git a/crates/forge_analyzer/src/definitions.rs b/crates/forge_analyzer/src/definitions.rs index db2284aa..2af8abd2 100644 --- a/crates/forge_analyzer/src/definitions.rs +++ b/crates/forge_analyzer/src/definitions.rs @@ -40,30 +40,6 @@ use swc_core::{ use tracing::{debug, field::debug, info, instrument, warn}; use typed_index_collections::{TiSlice, TiVec}; -/** - * ident`, `block`, `stmt`, `expr`, `pat`, `ty`, `lifetime`, `literal`, `path`, `meta`, `tt`, `item` and `vis - */ -macro_rules! unwrap_or { - ($c:vis, $e:expr, $or_do_what:expr) => { - if let c(d) = $e { - d - } else { - $or_do_what - } - }; -} - -macro_rules! add { - // macth like arm for macro - ($a:expr,$b:expr) => { - // macro expand to this code - { - // $a and $b will be templated using the value/variable provided to macro - $a + $b - } - }; -} - use crate::{ ctx::ModId, ir::{ @@ -540,8 +516,6 @@ pub struct Environment { pub resolver: ResolverTable, } -// POI - struct ImportCollector<'cx> { resolver: &'cx mut Environment, file_resolver: &'cx ForgeResolver, From cc62b91fccbd8e4038b4c204febc4577f8133a7c Mon Sep 17 00:00:00 2001 From: Joshua Wong Date: Wed, 6 Dec 2023 10:35:22 -0600 Subject: [PATCH 497/517] feat: add initial implementation of the prototype pollution scanner --- crates/fsrt/src/main.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/crates/fsrt/src/main.rs b/crates/fsrt/src/main.rs index 2996750a..f5bc2b99 100644 --- a/crates/fsrt/src/main.rs +++ b/crates/fsrt/src/main.rs @@ -76,6 +76,10 @@ struct Args { #[arg(long)] check_permissions: bool, + // Run the prototype pollution scanner + #[arg(long)] + check_prototype_pollution: bool, + /// The directory to scan. Assumes there is a `manifest.ya?ml` file in the top level /// directory, and that the source code is located in `src/` #[arg(name = "DIRS", value_hint = ValueHint::DirPath)] From 58f7cd48242fea0e1a5de4c0ff4f8ce1a1199418 Mon Sep 17 00:00:00 2001 From: Joshua Wong Date: Fri, 22 Dec 2023 13:50:40 -0600 Subject: [PATCH 498/517] feat: enable new scanners by default --- crates/fsrt/src/main.rs | 4 ---- 1 file changed, 4 deletions(-) diff --git a/crates/fsrt/src/main.rs b/crates/fsrt/src/main.rs index f5bc2b99..2996750a 100644 --- a/crates/fsrt/src/main.rs +++ b/crates/fsrt/src/main.rs @@ -76,10 +76,6 @@ struct Args { #[arg(long)] check_permissions: bool, - // Run the prototype pollution scanner - #[arg(long)] - check_prototype_pollution: bool, - /// The directory to scan. Assumes there is a `manifest.ya?ml` file in the top level /// directory, and that the source code is located in `src/` #[arg(name = "DIRS", value_hint = ValueHint::DirPath)] From abc03d93198ebb8f8b9cb92f2b581912f26baae3 Mon Sep 17 00:00:00 2001 From: Joshua Wong Date: Wed, 3 Jan 2024 10:35:12 -0600 Subject: [PATCH 499/517] fix: use correct check name in secret scanner vulner report --- crates/forge_analyzer/src/checkers.rs | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/crates/forge_analyzer/src/checkers.rs b/crates/forge_analyzer/src/checkers.rs index ccb197b7..237c9a8b 100644 --- a/crates/forge_analyzer/src/checkers.rs +++ b/crates/forge_analyzer/src/checkers.rs @@ -776,10 +776,7 @@ impl<'cx> Runner<'cx> for SecretChecker { operands: Option>, ) -> ControlFlow<(), Self::State> { match *intrinsic { - Intrinsic::Authorize(_) => { - debug!("authorize intrinsic found"); - ControlFlow::Continue(AuthorizeState::Yes) - } + Intrinsic::Authorize(_) => ControlFlow::Continue(AuthorizeState::Yes), Intrinsic::Fetch => ControlFlow::Continue(*state), Intrinsic::ApiCall(_) if *state == AuthorizeState::No => { let vuln = AuthZVuln::new(interp.callstack(), interp.env(), interp.entry()); @@ -929,6 +926,8 @@ impl<'cx> Runner<'cx> for AuthenticateChecker { type State = Authenticated; type Dataflow = AuthenticateDataflow; + const NAME: &'static str = "Authentication"; + fn visit_intrinsic( &mut self, interp: &Interp<'cx, Self>, @@ -1189,7 +1188,7 @@ impl IntoVuln for SecretVuln { self.entry_func.hash(&mut hasher); self.stack.hash(&mut hasher); Vulnerability { - check_name: format!("Secret-{}", hasher.finish()), + check_name: format!("Hardcoded-Secret-{}", hasher.finish()), description: format!( "Hardcoded secret found within codebase {} in {:?}.", self.entry_func, self.file @@ -1212,6 +1211,8 @@ impl<'cx> Runner<'cx> for SecretChecker { type State = SecretState; type Dataflow = SecretDataflow; + const NAME: &'static str = "Secret"; + fn visit_intrinsic( &mut self, interp: &Interp<'cx, Self>, From 99eb53a43b1d0526718a522c02c0b4709a2446bd Mon Sep 17 00:00:00 2001 From: awang7 Date: Wed, 3 Jan 2024 11:34:53 -0800 Subject: [PATCH 500/517] NEW: Fresh branch branching off of EAS-1893 to whitelist admin module functions --- crates/forge_loader/src/manifest.rs | 4 +++- crates/fsrt/src/main.rs | 1 + 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/crates/forge_loader/src/manifest.rs b/crates/forge_loader/src/manifest.rs index ad02a875..6d313ba7 100644 --- a/crates/forge_loader/src/manifest.rs +++ b/crates/forge_loader/src/manifest.rs @@ -255,7 +255,7 @@ pub enum FunctionTy { WebTrigger(T), } -// Struct used for tracking what scan a funtion requires. +// Struct used for tracking what scan a funtcion requires. #[derive(Debug, Clone, PartialEq, Eq)] pub struct Entrypoint<'a> { function: &'a str, @@ -341,10 +341,12 @@ impl<'a> ForgeModules<'a> { .binary_search_by_key(&func.key, |trigger| &trigger.function) .is_ok(); let invokable = invokable_functions.contains(func.key); + let admin = false; Ok::<_, Error>(Entrypoint { function: FunctionRef::try_from(func)?, invokable, web_trigger, + admin, }) }); } diff --git a/crates/fsrt/src/main.rs b/crates/fsrt/src/main.rs index 2996750a..7795e0bb 100644 --- a/crates/fsrt/src/main.rs +++ b/crates/fsrt/src/main.rs @@ -231,6 +231,7 @@ fn scan_directory(dir: PathBuf, function: Option<&str>, opts: &Args) -> Result<( function: entrypoint.function.try_resolve(&paths, &dir)?, invokable: entrypoint.invokable, web_trigger: entrypoint.web_trigger, + admin: entrypoint.admin, }) }); From eec1d4b255c28269d383d0bac2d15a864a847431 Mon Sep 17 00:00:00 2001 From: awang7 Date: Wed, 3 Jan 2024 14:51:50 -0800 Subject: [PATCH 501/517] feat: tracking functions exposed in jira:adminPage module added. TODO: test whether they are being tagged. --- crates/forge_loader/src/manifest.rs | 23 ++++++++++++++++++++--- crates/fsrt/src/main.rs | 2 ++ 2 files changed, 22 insertions(+), 3 deletions(-) diff --git a/crates/forge_loader/src/manifest.rs b/crates/forge_loader/src/manifest.rs index 6d313ba7..8b679b2f 100644 --- a/crates/forge_loader/src/manifest.rs +++ b/crates/forge_loader/src/manifest.rs @@ -138,6 +138,7 @@ pub struct WorkflowPostFunction<'a> { // Add more structs here for deserializing forge modules #[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] pub struct ForgeModules<'a> { + // deserializing non user-invocable modules #[serde(rename = "macro", default, borrow)] macros: Vec>, #[serde(rename = "function", default, borrow)] @@ -148,7 +149,7 @@ pub struct ForgeModules<'a> { issue_glance: Vec>, #[serde(rename = "jira:accessImportType", default, borrow)] access_import_type: Vec>, - // deserializing non user-invocable modules + // deserializing user invokable module functions #[serde(rename = "webtrigger", default, borrow)] webtriggers: Vec>, #[serde(rename = "trigger", default, borrow)] @@ -167,10 +168,20 @@ pub struct ForgeModules<'a> { pub workflow_validator: Vec>, #[serde(rename = "jira:workflowPostFunction", default, borrow)] pub workflow_post_function: Vec>, - // deserializing user invokable module functions + // deserializing admin pages + #[serde(rename = "jira:adminPage", default, borrow)] + pub jira_admin: Vec>, #[serde(flatten)] extra: FxHashMap>>, } +#[derive(Debug, Clone, PartialEq, Eq, Deserialize)] +pub struct JiraAdminPage<'a> { + key: &'a str, + function: &'a str, + resource: &'a str, + render: &'a str, + title: &'a str, +} #[derive(Debug, Clone, PartialEq, Eq, Deserialize)] pub struct Consumer<'a> { @@ -335,13 +346,19 @@ impl<'a> ForgeModules<'a> { invokable_functions.extend(access.import_status); }); + // TODO: create admin list and check whether function is in admin list then set admin bool to true. If not, set to false. self.functions.into_iter().flat_map(move |func| { let web_trigger = self .webtriggers .binary_search_by_key(&func.key, |trigger| &trigger.function) .is_ok(); let invokable = invokable_functions.contains(func.key); - let admin = false; + // this checks whether the funton being scanned is being used in an admin module. Rn it only checks for jira_admin page module. + // optionally: compass:adminPage could also be considered. + let admin = self + .jira_admin + .iter() + .any(|admin_function| admin_function.function == func.key); Ok::<_, Error>(Entrypoint { function: FunctionRef::try_from(func)?, invokable, diff --git a/crates/fsrt/src/main.rs b/crates/fsrt/src/main.rs index 7795e0bb..a453772a 100644 --- a/crates/fsrt/src/main.rs +++ b/crates/fsrt/src/main.rs @@ -90,6 +90,7 @@ struct ResolvedEntryPoint<'a> { def_id: DefId, webtrigger: bool, invokable: bool, + admin: bool, } struct ForgeProject<'a> { @@ -161,6 +162,7 @@ impl<'a> ForgeProject<'a> { def_id, invokable: entrypoint.invokable, webtrigger: entrypoint.web_trigger, + admin: entrypoint.admin, }) })); } From 112bd9a0d1019d1259dcfd3a02805bfccfd9b7f3 Mon Sep 17 00:00:00 2001 From: awang7 Date: Mon, 8 Jan 2024 13:40:16 -0800 Subject: [PATCH 502/517] rebasing EAS-1673 to be head of EAS-1893 --- crates/forge_loader/src/manifest.rs | 158 ++++++++++++++++++++++------ 1 file changed, 127 insertions(+), 31 deletions(-) diff --git a/crates/forge_loader/src/manifest.rs b/crates/forge_loader/src/manifest.rs index 8b679b2f..98a387c0 100644 --- a/crates/forge_loader/src/manifest.rs +++ b/crates/forge_loader/src/manifest.rs @@ -178,8 +178,6 @@ pub struct ForgeModules<'a> { pub struct JiraAdminPage<'a> { key: &'a str, function: &'a str, - resource: &'a str, - render: &'a str, title: &'a str, } @@ -283,67 +281,67 @@ impl<'a> ForgeModules<'a> { let mut invokable_functions = BTreeSet::new(); self.data_provider.iter().for_each(|dataprovider| { - invokable_functions.extend(dataprovider.callback); + non_user_invokable_mod_functions.extend(dataprovider.callback); }); self.custom_field.iter().for_each(|customfield| { - invokable_functions.extend(customfield.value); - invokable_functions.extend(customfield.search_suggestions); - invokable_functions.extend(customfield.edit); + non_user_invokable_mod_functions.extend(customfield.value); + non_user_invokable_mod_functions.extend(customfield.search_suggestions); + non_user_invokable_mod_functions.extend(customfield.edit); - invokable_functions.insert(customfield.common_keys.function); - invokable_functions.extend(customfield.common_keys.resolver); + non_user_invokable_mod_functions.insert(customfield.common_keys.function); + non_user_invokable_mod_functions.extend(customfield.common_keys.resolver); }); self.ui_modifications.iter().for_each(|ui| { - invokable_functions.insert(ui.common_keys.function); - invokable_functions.extend(ui.common_keys.resolver); + non_user_invokable_mod_functions.insert(ui.common_keys.function); + non_user_invokable_mod_functions.extend(ui.common_keys.resolver); }); self.workflow_validator.iter().for_each(|validator| { - invokable_functions.insert(validator.common_keys.key); + non_user_invokable_mod_functions.insert(validator.common_keys.key); - invokable_functions.insert(validator.common_keys.function); + non_user_invokable_mod_functions.insert(validator.common_keys.function); - invokable_functions.extend(validator.common_keys.resolver); + non_user_invokable_mod_functions.extend(validator.common_keys.resolver); }); self.workflow_post_function .iter() .for_each(|post_function| { - invokable_functions.insert(post_function.common_keys.key); + non_user_invokable_mod_functions.insert(post_function.common_keys.key); - invokable_functions.insert(post_function.common_keys.function); + non_user_invokable_mod_functions.insert(post_function.common_keys.function); - invokable_functions.extend(post_function.common_keys.resolver); + non_user_invokable_mod_functions.extend(post_function.common_keys.resolver); }); // get user invokable modules that have additional exposure endpoints. // ie macros has config and export fields on top of resolver fields that are functions self.macros.iter().for_each(|macros| { - invokable_functions.insert(macros.common_keys.key); + non_user_invokable_mod_functions.insert(macros.common_keys.key); - invokable_functions.insert(macros.common_keys.function); - invokable_functions.extend(macros.common_keys.resolver); + non_user_invokable_mod_functions.insert(macros.common_keys.function); + non_user_invokable_mod_functions.extend(macros.common_keys.resolver); - invokable_functions.extend(macros.config); - invokable_functions.extend(macros.export); + non_user_invokable_mod_functions.extend(macros.config); + non_user_invokable_mod_functions.extend(macros.export); }); self.issue_glance.iter().for_each(|issue| { - invokable_functions.insert(issue.common_keys.function); - invokable_functions.extend(issue.common_keys.resolver); - invokable_functions.extend(issue.dynamic_properties); + non_user_invokable_mod_functions.insert(issue.common_keys.function); + non_user_invokable_mod_functions.extend(issue.common_keys.resolver); + non_user_invokable_mod_functions.extend(issue.dynamic_properties); }); self.access_import_type.iter().for_each(|access| { - invokable_functions.insert(access.common_keys.function); - invokable_functions.extend(access.common_keys.resolver); + non_user_invokable_mod_functions.insert(access.common_keys.function); + non_user_invokable_mod_functions.extend(access.common_keys.resolver); - invokable_functions.extend(access.one_delete_import); - invokable_functions.extend(access.stop_import); - invokable_functions.extend(access.start_import); - invokable_functions.extend(access.import_status); + non_user_invokable_mod_functions.extend(access.one_delete_import); + non_user_invokable_mod_functions.extend(access.stop_import); + non_user_invokable_mod_functions.extend(access.start_import); + non_user_invokable_mod_functions.extend(access.import_status); }); // TODO: create admin list and check whether function is in admin list then set admin bool to true. If not, set to false. @@ -352,7 +350,7 @@ impl<'a> ForgeModules<'a> { .webtriggers .binary_search_by_key(&func.key, |trigger| &trigger.function) .is_ok(); - let invokable = invokable_functions.contains(func.key); + let invokable = non_user_invokable_mod_functions.contains(func.key); // this checks whether the funton being scanned is being used in an admin module. Rn it only checks for jira_admin page module. // optionally: compass:adminPage could also be considered. let admin = self @@ -591,4 +589,102 @@ mod tests { assert_eq!(func, "Catch-me-if-you-can3"); } } + + // Test checking whether jira:adminPage gets flagged. + #[test] + fn test_deserialize_admin_check() { + let json = r#"{ + "app": { + "name": "My App", + "id": "my-app" + }, + "modules": { + "jira:adminPage": [ + { + "key": "testing-admin-tag", + "function": "main1", + "title": "writing-a-test-for-admin-flag" + } + ], + "macro": [ + { + "key": "my-macro", + "function": "main2" + } + ], + "function": [ + { + "key": "main1", + "handler": "index.run" + }, + { + "key": "main2", + "handler": "src.run" + } + ] + }, + "permissions": { + "scopes": [ + "my-scope" + ], + "content": { + "scripts": [ + "my-script.js" + ], + "styles": [ + "my-style.css" + ] + } + } + }"#; + let manifest: ForgeManifest = serde_json::from_str(json).unwrap(); + let mut admin_func = manifest.modules.into_analyzable_functions(); + + assert_eq!( + admin_func.next(), + Some(Entrypoint { + function: FunctionRef::try_from(FunctionMod { + key: "main1", + handler: "index.run", + providers: None, + }) + .unwrap(), + invokable: false, + web_trigger: false, + admin: true + }) + ); + + assert_eq!( + admin_func.next(), + Some(Entrypoint { + function: FunctionRef::try_from(FunctionMod { + key: "main2", + handler: "src.run", + providers: None, + }) + .unwrap(), + invokable: true, + web_trigger: false, + admin: false + }) + ); + + // assert_eq!(manifest.app.name, Some("My App")); + // assert_eq!(manifest.app.id, "my-app"); + // assert_eq!(manifest.modules.macros.len(), 1); + // assert_eq!(manifest.modules.macros[0].common_keys.key, "My Macro"); + // // assert_eq!(manifest.modules.macros[0].function, "my-macro"); + // assert_eq!(manifest.modules.functions.len(), 1); + // assert_eq!( + // manifest.modules.functions[0], + // FunctionMod { + // key: "my-function", + // handler: "my-function-handler", + // providers: Some(AuthProviders { + // auth: vec!["my-auth-provider"] + // }), + // } + // ); + } } From c98a468492cb1c406f81d704a9a54830074f0d09 Mon Sep 17 00:00:00 2001 From: awang7 Date: Thu, 4 Jan 2024 17:35:14 -0800 Subject: [PATCH 503/517] chore: cleaning up and reverting iterator to og name. --- crates/forge_loader/src/manifest.rs | 58 ++++++++++++++--------------- 1 file changed, 29 insertions(+), 29 deletions(-) diff --git a/crates/forge_loader/src/manifest.rs b/crates/forge_loader/src/manifest.rs index 98a387c0..41c05a6e 100644 --- a/crates/forge_loader/src/manifest.rs +++ b/crates/forge_loader/src/manifest.rs @@ -281,67 +281,67 @@ impl<'a> ForgeModules<'a> { let mut invokable_functions = BTreeSet::new(); self.data_provider.iter().for_each(|dataprovider| { - non_user_invokable_mod_functions.extend(dataprovider.callback); + invokable_functions.extend(dataprovider.callback); }); self.custom_field.iter().for_each(|customfield| { - non_user_invokable_mod_functions.extend(customfield.value); - non_user_invokable_mod_functions.extend(customfield.search_suggestions); - non_user_invokable_mod_functions.extend(customfield.edit); + invokable_functions.extend(customfield.value); + invokable_functions.extend(customfield.search_suggestions); + invokable_functions.extend(customfield.edit); - non_user_invokable_mod_functions.insert(customfield.common_keys.function); - non_user_invokable_mod_functions.extend(customfield.common_keys.resolver); + invokable_functions.insert(customfield.common_keys.function); + invokable_functions.extend(customfield.common_keys.resolver); }); self.ui_modifications.iter().for_each(|ui| { - non_user_invokable_mod_functions.insert(ui.common_keys.function); - non_user_invokable_mod_functions.extend(ui.common_keys.resolver); + invokable_functions.insert(ui.common_keys.function); + invokable_functions.extend(ui.common_keys.resolver); }); self.workflow_validator.iter().for_each(|validator| { - non_user_invokable_mod_functions.insert(validator.common_keys.key); + invokable_functions.insert(validator.common_keys.key); - non_user_invokable_mod_functions.insert(validator.common_keys.function); + invokable_functions.insert(validator.common_keys.function); - non_user_invokable_mod_functions.extend(validator.common_keys.resolver); + invokable_functions.extend(validator.common_keys.resolver); }); self.workflow_post_function .iter() .for_each(|post_function| { - non_user_invokable_mod_functions.insert(post_function.common_keys.key); + invokable_functions.insert(post_function.common_keys.key); - non_user_invokable_mod_functions.insert(post_function.common_keys.function); + invokable_functions.insert(post_function.common_keys.function); - non_user_invokable_mod_functions.extend(post_function.common_keys.resolver); + invokable_functions.extend(post_function.common_keys.resolver); }); // get user invokable modules that have additional exposure endpoints. // ie macros has config and export fields on top of resolver fields that are functions self.macros.iter().for_each(|macros| { - non_user_invokable_mod_functions.insert(macros.common_keys.key); + invokable_functions.insert(macros.common_keys.key); - non_user_invokable_mod_functions.insert(macros.common_keys.function); - non_user_invokable_mod_functions.extend(macros.common_keys.resolver); + invokable_functions.insert(macros.common_keys.function); + invokable_functions.extend(macros.common_keys.resolver); - non_user_invokable_mod_functions.extend(macros.config); - non_user_invokable_mod_functions.extend(macros.export); + invokable_functions.extend(macros.config); + invokable_functions.extend(macros.export); }); self.issue_glance.iter().for_each(|issue| { - non_user_invokable_mod_functions.insert(issue.common_keys.function); - non_user_invokable_mod_functions.extend(issue.common_keys.resolver); - non_user_invokable_mod_functions.extend(issue.dynamic_properties); + invokable_functions.insert(issue.common_keys.function); + invokable_functions.extend(issue.common_keys.resolver); + invokable_functions.extend(issue.dynamic_properties); }); self.access_import_type.iter().for_each(|access| { - non_user_invokable_mod_functions.insert(access.common_keys.function); - non_user_invokable_mod_functions.extend(access.common_keys.resolver); + invokable_functions.insert(access.common_keys.function); + invokable_functions.extend(access.common_keys.resolver); - non_user_invokable_mod_functions.extend(access.one_delete_import); - non_user_invokable_mod_functions.extend(access.stop_import); - non_user_invokable_mod_functions.extend(access.start_import); - non_user_invokable_mod_functions.extend(access.import_status); + invokable_functions.extend(access.one_delete_import); + invokable_functions.extend(access.stop_import); + invokable_functions.extend(access.start_import); + invokable_functions.extend(access.import_status); }); // TODO: create admin list and check whether function is in admin list then set admin bool to true. If not, set to false. @@ -350,7 +350,7 @@ impl<'a> ForgeModules<'a> { .webtriggers .binary_search_by_key(&func.key, |trigger| &trigger.function) .is_ok(); - let invokable = non_user_invokable_mod_functions.contains(func.key); + let invokable = invokable_functions.contains(func.key); // this checks whether the funton being scanned is being used in an admin module. Rn it only checks for jira_admin page module. // optionally: compass:adminPage could also be considered. let admin = self From 010388d83c9d141927502d44077ec96f7b28fdf6 Mon Sep 17 00:00:00 2001 From: awang7 Date: Thu, 4 Jan 2024 17:31:45 -0800 Subject: [PATCH 504/517] feat: wrote smol test to check for admin flag based on current behavior --- crates/forge_loader/src/manifest.rs | 58 ++++++++++++++--------------- 1 file changed, 29 insertions(+), 29 deletions(-) diff --git a/crates/forge_loader/src/manifest.rs b/crates/forge_loader/src/manifest.rs index 41c05a6e..98a387c0 100644 --- a/crates/forge_loader/src/manifest.rs +++ b/crates/forge_loader/src/manifest.rs @@ -281,67 +281,67 @@ impl<'a> ForgeModules<'a> { let mut invokable_functions = BTreeSet::new(); self.data_provider.iter().for_each(|dataprovider| { - invokable_functions.extend(dataprovider.callback); + non_user_invokable_mod_functions.extend(dataprovider.callback); }); self.custom_field.iter().for_each(|customfield| { - invokable_functions.extend(customfield.value); - invokable_functions.extend(customfield.search_suggestions); - invokable_functions.extend(customfield.edit); + non_user_invokable_mod_functions.extend(customfield.value); + non_user_invokable_mod_functions.extend(customfield.search_suggestions); + non_user_invokable_mod_functions.extend(customfield.edit); - invokable_functions.insert(customfield.common_keys.function); - invokable_functions.extend(customfield.common_keys.resolver); + non_user_invokable_mod_functions.insert(customfield.common_keys.function); + non_user_invokable_mod_functions.extend(customfield.common_keys.resolver); }); self.ui_modifications.iter().for_each(|ui| { - invokable_functions.insert(ui.common_keys.function); - invokable_functions.extend(ui.common_keys.resolver); + non_user_invokable_mod_functions.insert(ui.common_keys.function); + non_user_invokable_mod_functions.extend(ui.common_keys.resolver); }); self.workflow_validator.iter().for_each(|validator| { - invokable_functions.insert(validator.common_keys.key); + non_user_invokable_mod_functions.insert(validator.common_keys.key); - invokable_functions.insert(validator.common_keys.function); + non_user_invokable_mod_functions.insert(validator.common_keys.function); - invokable_functions.extend(validator.common_keys.resolver); + non_user_invokable_mod_functions.extend(validator.common_keys.resolver); }); self.workflow_post_function .iter() .for_each(|post_function| { - invokable_functions.insert(post_function.common_keys.key); + non_user_invokable_mod_functions.insert(post_function.common_keys.key); - invokable_functions.insert(post_function.common_keys.function); + non_user_invokable_mod_functions.insert(post_function.common_keys.function); - invokable_functions.extend(post_function.common_keys.resolver); + non_user_invokable_mod_functions.extend(post_function.common_keys.resolver); }); // get user invokable modules that have additional exposure endpoints. // ie macros has config and export fields on top of resolver fields that are functions self.macros.iter().for_each(|macros| { - invokable_functions.insert(macros.common_keys.key); + non_user_invokable_mod_functions.insert(macros.common_keys.key); - invokable_functions.insert(macros.common_keys.function); - invokable_functions.extend(macros.common_keys.resolver); + non_user_invokable_mod_functions.insert(macros.common_keys.function); + non_user_invokable_mod_functions.extend(macros.common_keys.resolver); - invokable_functions.extend(macros.config); - invokable_functions.extend(macros.export); + non_user_invokable_mod_functions.extend(macros.config); + non_user_invokable_mod_functions.extend(macros.export); }); self.issue_glance.iter().for_each(|issue| { - invokable_functions.insert(issue.common_keys.function); - invokable_functions.extend(issue.common_keys.resolver); - invokable_functions.extend(issue.dynamic_properties); + non_user_invokable_mod_functions.insert(issue.common_keys.function); + non_user_invokable_mod_functions.extend(issue.common_keys.resolver); + non_user_invokable_mod_functions.extend(issue.dynamic_properties); }); self.access_import_type.iter().for_each(|access| { - invokable_functions.insert(access.common_keys.function); - invokable_functions.extend(access.common_keys.resolver); + non_user_invokable_mod_functions.insert(access.common_keys.function); + non_user_invokable_mod_functions.extend(access.common_keys.resolver); - invokable_functions.extend(access.one_delete_import); - invokable_functions.extend(access.stop_import); - invokable_functions.extend(access.start_import); - invokable_functions.extend(access.import_status); + non_user_invokable_mod_functions.extend(access.one_delete_import); + non_user_invokable_mod_functions.extend(access.stop_import); + non_user_invokable_mod_functions.extend(access.start_import); + non_user_invokable_mod_functions.extend(access.import_status); }); // TODO: create admin list and check whether function is in admin list then set admin bool to true. If not, set to false. @@ -350,7 +350,7 @@ impl<'a> ForgeModules<'a> { .webtriggers .binary_search_by_key(&func.key, |trigger| &trigger.function) .is_ok(); - let invokable = invokable_functions.contains(func.key); + let invokable = non_user_invokable_mod_functions.contains(func.key); // this checks whether the funton being scanned is being used in an admin module. Rn it only checks for jira_admin page module. // optionally: compass:adminPage could also be considered. let admin = self From d4693d28fff4fb3168207188fc929dd40de5861e Mon Sep 17 00:00:00 2001 From: awang7 Date: Thu, 4 Jan 2024 17:35:14 -0800 Subject: [PATCH 505/517] chore: cleaning up and reverting iterator to og name. --- crates/forge_loader/src/manifest.rs | 58 ++++++++++++++--------------- 1 file changed, 29 insertions(+), 29 deletions(-) diff --git a/crates/forge_loader/src/manifest.rs b/crates/forge_loader/src/manifest.rs index 98a387c0..41c05a6e 100644 --- a/crates/forge_loader/src/manifest.rs +++ b/crates/forge_loader/src/manifest.rs @@ -281,67 +281,67 @@ impl<'a> ForgeModules<'a> { let mut invokable_functions = BTreeSet::new(); self.data_provider.iter().for_each(|dataprovider| { - non_user_invokable_mod_functions.extend(dataprovider.callback); + invokable_functions.extend(dataprovider.callback); }); self.custom_field.iter().for_each(|customfield| { - non_user_invokable_mod_functions.extend(customfield.value); - non_user_invokable_mod_functions.extend(customfield.search_suggestions); - non_user_invokable_mod_functions.extend(customfield.edit); + invokable_functions.extend(customfield.value); + invokable_functions.extend(customfield.search_suggestions); + invokable_functions.extend(customfield.edit); - non_user_invokable_mod_functions.insert(customfield.common_keys.function); - non_user_invokable_mod_functions.extend(customfield.common_keys.resolver); + invokable_functions.insert(customfield.common_keys.function); + invokable_functions.extend(customfield.common_keys.resolver); }); self.ui_modifications.iter().for_each(|ui| { - non_user_invokable_mod_functions.insert(ui.common_keys.function); - non_user_invokable_mod_functions.extend(ui.common_keys.resolver); + invokable_functions.insert(ui.common_keys.function); + invokable_functions.extend(ui.common_keys.resolver); }); self.workflow_validator.iter().for_each(|validator| { - non_user_invokable_mod_functions.insert(validator.common_keys.key); + invokable_functions.insert(validator.common_keys.key); - non_user_invokable_mod_functions.insert(validator.common_keys.function); + invokable_functions.insert(validator.common_keys.function); - non_user_invokable_mod_functions.extend(validator.common_keys.resolver); + invokable_functions.extend(validator.common_keys.resolver); }); self.workflow_post_function .iter() .for_each(|post_function| { - non_user_invokable_mod_functions.insert(post_function.common_keys.key); + invokable_functions.insert(post_function.common_keys.key); - non_user_invokable_mod_functions.insert(post_function.common_keys.function); + invokable_functions.insert(post_function.common_keys.function); - non_user_invokable_mod_functions.extend(post_function.common_keys.resolver); + invokable_functions.extend(post_function.common_keys.resolver); }); // get user invokable modules that have additional exposure endpoints. // ie macros has config and export fields on top of resolver fields that are functions self.macros.iter().for_each(|macros| { - non_user_invokable_mod_functions.insert(macros.common_keys.key); + invokable_functions.insert(macros.common_keys.key); - non_user_invokable_mod_functions.insert(macros.common_keys.function); - non_user_invokable_mod_functions.extend(macros.common_keys.resolver); + invokable_functions.insert(macros.common_keys.function); + invokable_functions.extend(macros.common_keys.resolver); - non_user_invokable_mod_functions.extend(macros.config); - non_user_invokable_mod_functions.extend(macros.export); + invokable_functions.extend(macros.config); + invokable_functions.extend(macros.export); }); self.issue_glance.iter().for_each(|issue| { - non_user_invokable_mod_functions.insert(issue.common_keys.function); - non_user_invokable_mod_functions.extend(issue.common_keys.resolver); - non_user_invokable_mod_functions.extend(issue.dynamic_properties); + invokable_functions.insert(issue.common_keys.function); + invokable_functions.extend(issue.common_keys.resolver); + invokable_functions.extend(issue.dynamic_properties); }); self.access_import_type.iter().for_each(|access| { - non_user_invokable_mod_functions.insert(access.common_keys.function); - non_user_invokable_mod_functions.extend(access.common_keys.resolver); + invokable_functions.insert(access.common_keys.function); + invokable_functions.extend(access.common_keys.resolver); - non_user_invokable_mod_functions.extend(access.one_delete_import); - non_user_invokable_mod_functions.extend(access.stop_import); - non_user_invokable_mod_functions.extend(access.start_import); - non_user_invokable_mod_functions.extend(access.import_status); + invokable_functions.extend(access.one_delete_import); + invokable_functions.extend(access.stop_import); + invokable_functions.extend(access.start_import); + invokable_functions.extend(access.import_status); }); // TODO: create admin list and check whether function is in admin list then set admin bool to true. If not, set to false. @@ -350,7 +350,7 @@ impl<'a> ForgeModules<'a> { .webtriggers .binary_search_by_key(&func.key, |trigger| &trigger.function) .is_ok(); - let invokable = non_user_invokable_mod_functions.contains(func.key); + let invokable = invokable_functions.contains(func.key); // this checks whether the funton being scanned is being used in an admin module. Rn it only checks for jira_admin page module. // optionally: compass:adminPage could also be considered. let admin = self From f99c1589022f04854c1a770216f9fb8ad1532cbb Mon Sep 17 00:00:00 2001 From: awang7 Date: Mon, 8 Jan 2024 14:14:46 -0800 Subject: [PATCH 506/517] chore: resolving more conflicts --- crates/forge_loader/src/manifest.rs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/crates/forge_loader/src/manifest.rs b/crates/forge_loader/src/manifest.rs index 41c05a6e..e7f1abb6 100644 --- a/crates/forge_loader/src/manifest.rs +++ b/crates/forge_loader/src/manifest.rs @@ -343,8 +343,6 @@ impl<'a> ForgeModules<'a> { invokable_functions.extend(access.start_import); invokable_functions.extend(access.import_status); }); - - // TODO: create admin list and check whether function is in admin list then set admin bool to true. If not, set to false. self.functions.into_iter().flat_map(move |func| { let web_trigger = self .webtriggers @@ -363,7 +361,7 @@ impl<'a> ForgeModules<'a> { web_trigger, admin, }) - }); + }) } } From 6b17156b03293bd282af983cef77bb809538cafd Mon Sep 17 00:00:00 2001 From: Joshua Wong Date: Mon, 8 Jan 2024 18:02:37 -0600 Subject: [PATCH 507/517] fix: update checker spawning to use new entrypoint flags --- crates/fsrt/src/main.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/crates/fsrt/src/main.rs b/crates/fsrt/src/main.rs index a453772a..d497d914 100644 --- a/crates/fsrt/src/main.rs +++ b/crates/fsrt/src/main.rs @@ -407,7 +407,6 @@ fn scan_directory(dir: PathBuf, function: Option<&str>, opts: &Args) -> Result<( reporter .add_vulnerabilities(vec![PermissionVuln::new(perm_interp.permissions)].into_iter()); } - let report = serde_json::to_string(&reporter.into_report()).into_diagnostic()?; debug!("On the debug layer: Writing Report"); match &opts.out { From a8263a3005c420f937554133c3c0a86ffeff0441 Mon Sep 17 00:00:00 2001 From: Joshua Wong Date: Mon, 8 Jan 2024 18:03:31 -0600 Subject: [PATCH 508/517] chore: remove unused imports in forge_loader --- crates/forge_loader/src/manifest.rs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/crates/forge_loader/src/manifest.rs b/crates/forge_loader/src/manifest.rs index e7f1abb6..90cc4e65 100644 --- a/crates/forge_loader/src/manifest.rs +++ b/crates/forge_loader/src/manifest.rs @@ -3,14 +3,12 @@ use std::{ collections::HashSet, hash::Hash, path::{Path, PathBuf}, - sync::Arc, }; -use crate::{forgepermissions::ForgePermissions, Error}; +use crate::Error; use forge_utils::FxHashMap; -use itertools::{Either, Itertools}; +use itertools::Itertools; use serde::Deserialize; -use serde_json::map::Entry; use tracing::trace; #[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] From 13efe7ec42f57536a742b10754b34749e20d2633 Mon Sep 17 00:00:00 2001 From: Alina Wang Date: Thu, 11 Jan 2024 16:20:45 -0800 Subject: [PATCH 509/517] chore: resolved error. Fixed local tests that were failing --- crates/forge_analyzer/src/definitions.rs | 2 -- crates/forge_loader/src/manifest.rs | 37 +++++++----------------- 2 files changed, 10 insertions(+), 29 deletions(-) diff --git a/crates/forge_analyzer/src/definitions.rs b/crates/forge_analyzer/src/definitions.rs index 2af8abd2..d98595f2 100644 --- a/crates/forge_analyzer/src/definitions.rs +++ b/crates/forge_analyzer/src/definitions.rs @@ -2994,8 +2994,6 @@ impl ExportCollector<'_> { self.res_table.owning_module.push(self.curr_mod); self.default = Some(defid); } - - // fn add_default(&mut self, def: DefRes, id: Option) -> DefId {} } // Import collector for run_resolver diff --git a/crates/forge_loader/src/manifest.rs b/crates/forge_loader/src/manifest.rs index 90cc4e65..a5a57d68 100644 --- a/crates/forge_loader/src/manifest.rs +++ b/crates/forge_loader/src/manifest.rs @@ -35,6 +35,7 @@ struct CommonKey<'a> { #[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] struct MacroMod<'a> { + #[serde(flatten, borrow)] common_keys: CommonKey<'a>, config: Option<&'a str>, export: Option<&'a str>, @@ -475,7 +476,7 @@ mod tests { assert_eq!(manifest.app.name, Some("My App")); assert_eq!(manifest.app.id, "my-app"); assert_eq!(manifest.modules.macros.len(), 1); - assert_eq!(manifest.modules.macros[0].common_keys.key, "My Macro"); + assert_eq!(manifest.modules.macros[0].common_keys.key, "my-macro"); // assert_eq!(manifest.modules.macros[0].function, "my-macro"); assert_eq!(manifest.modules.functions.len(), 1); assert_eq!( @@ -525,15 +526,9 @@ mod tests { "key": "my-macro", "title": "My Macro", "function": "Catch-me-if-you-can0", - "resolver": { - "function": "Catch-me-if-you-can1" - }, - "config": { - "function": "Catch-me-if-you-can2" - }, - "export": { - "function": "Catch-me-if-you-can3" - } + "resolver": "Catch-me-if-you-can1", + "config": "Catch-me-if-you-can2", + "export": "Catch-me-if-you-can3" } ], "function": [ @@ -573,7 +568,12 @@ mod tests { "Catch-me-if-you-can0" ); + let Some(func_test) = manifest.modules.macros[0].common_keys.resolver else { + panic!("No dice!") + }; + println!("what is func? {}", func_test); if let Some(func) = manifest.modules.macros[0].common_keys.resolver { + println!("what is func? {}", func); assert_eq!(func, "Catch-me-if-you-can1"); } @@ -665,22 +665,5 @@ mod tests { admin: false }) ); - - // assert_eq!(manifest.app.name, Some("My App")); - // assert_eq!(manifest.app.id, "my-app"); - // assert_eq!(manifest.modules.macros.len(), 1); - // assert_eq!(manifest.modules.macros[0].common_keys.key, "My Macro"); - // // assert_eq!(manifest.modules.macros[0].function, "my-macro"); - // assert_eq!(manifest.modules.functions.len(), 1); - // assert_eq!( - // manifest.modules.functions[0], - // FunctionMod { - // key: "my-function", - // handler: "my-function-handler", - // providers: Some(AuthProviders { - // auth: vec!["my-auth-provider"] - // }), - // } - // ); } } From 52424361bfe7fcf0b45378b600f26b7cc00c40dc Mon Sep 17 00:00:00 2001 From: Alina Wang Date: Fri, 26 Jan 2024 12:50:05 -0500 Subject: [PATCH 510/517] feat: resolving PR comments in progress --- crates/forge_loader/src/manifest.rs | 206 ++++++++++++++++------------ 1 file changed, 118 insertions(+), 88 deletions(-) diff --git a/crates/forge_loader/src/manifest.rs b/crates/forge_loader/src/manifest.rs index a5a57d68..49c3449a 100644 --- a/crates/forge_loader/src/manifest.rs +++ b/crates/forge_loader/src/manifest.rs @@ -16,63 +16,77 @@ struct AuthProviders<'a> { #[serde(borrow)] auth: Vec<&'a str>, } -// Maps the Functions Module in common Modules -#[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] -pub struct FunctionMod<'a> { - key: &'a str, - handler: &'a str, - #[serde(borrow)] - providers: Option>, -} // Abstracting away key, function, and resolver into a single struct for reuse whoo! +// And helper functions for ease of use #[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] struct CommonKey<'a> { key: &'a str, - function: &'a str, - resolver: Option<&'a str>, + resolver: Option>, +} + +impl<'a> CommonKey<'a> { + fn append_functions + Extend>( + &self, + funcs: &mut I, + ) { + funcs.extend(self.key); + if let Some(Resolver { + function, + method, + endpoint, + }) = self.resolver + { + funcs.extend(function); + } + } +} +#[derive(Debug, Clone, PartialEq, Eq, Deserialize)] +pub struct Resolver<'a> { + pub function: Option<&'a str>, + pub method: Option<&'a str>, + pub endpoint: Option<&'a str>, } +// Common Modules #[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] -struct MacroMod<'a> { - #[serde(flatten, borrow)] - common_keys: CommonKey<'a>, - config: Option<&'a str>, - export: Option<&'a str>, +pub struct FunctionMod<'a> { + key: &'a str, + handler: &'a str, + #[serde(borrow)] + providers: Option>, } -#[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] -struct ContentByLineItem<'a> { - #[serde(flatten, borrow)] - common_keys: CommonKey<'a>, +#[derive(Debug, Clone, PartialEq, Eq, Deserialize)] +pub struct Consumer<'a> { + key: &'a str, + queue: &'a str, #[serde(borrow)] - dynamic_properties: Option<&'a str>, + pub resolver: Resolver<'a>, } -#[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] -struct IssueGlance<'a> { - #[serde(flatten, borrow)] - common_keys: CommonKey<'a>, - dynamic_properties: Option<&'a str>, +// Trigger Modules +#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize)] +#[serde(rename_all = "lowercase")] +enum Interval { + Hour, + Day, + Week, } -#[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] -struct AccessImportType<'a> { + +#[derive(Debug, Clone, PartialEq, Eq, Deserialize)] +struct ScheduledTrigger<'a> { #[serde(flatten, borrow)] - common_keys: CommonKey<'a>, - one_delete_import: Option<&'a str>, - start_import: Option<&'a str>, - stop_import: Option<&'a str>, - import_status: Option<&'a str>, + raw: RawTrigger<'a>, + interval: Interval, } -// WebTrigger => RawTrigger; WHY IS THIS NAMED DIFFERENTLY !? WHO CHANGED NAMES #[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] struct RawTrigger<'a> { key: &'a str, function: &'a str, } -// Trigger => EventTriger; WHY IS THIS NAMED DIFFERENTLY !? WHO CHANGED NAMES #[derive(Debug, Clone, PartialEq, Eq, Deserialize)] struct EventTrigger<'a> { #[serde(flatten, borrow)] @@ -81,34 +95,40 @@ struct EventTrigger<'a> { events: Vec<&'a str>, } -#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize)] -#[serde(rename_all = "lowercase")] -enum Interval { - Hour, - Day, - Week, +// Confluence Modules +#[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] +struct MacroMod<'a> { + #[serde(flatten, borrow)] + common_keys: CommonKey<'a>, + config: Option<&'a str>, + export: Option<&'a str>, } -// Thank you to whomeever kept this one the same. T.T -#[derive(Debug, Clone, PartialEq, Eq, Deserialize)] -struct ScheduledTrigger<'a> { +#[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] +struct ContentByLineItem<'a> { #[serde(flatten, borrow)] - raw: RawTrigger<'a>, - interval: Interval, + common_keys: CommonKey<'a>, + #[serde(borrow)] + dynamic_properties: Option<&'a str>, } -// compass DataProvider module +// Jira Modules #[derive(Debug, Clone, PartialEq, Eq, Deserialize)] -pub struct DataProvider<'a> { - #[serde(flatten, borrow)] +pub struct JiraAdminPage<'a> { key: &'a str, - callback: Option<&'a str>, + function: &'a str, + title: &'a str, +} + +#[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] +struct IssueGlance<'a> { + #[serde(flatten, borrow)] + common_keys: CommonKey<'a>, + dynamic_properties: Option<&'a str>, } -// Struct for Custom field Module. Check that search suggestion gets read in correctly. #[derive(Debug, Clone, PartialEq, Eq, Deserialize)] pub struct CustomField<'a> { - // all attributes below involve function calls #[serde(flatten, borrow)] common_keys: CommonKey<'a>, value: Option<&'a str>, @@ -133,13 +153,32 @@ pub struct WorkflowPostFunction<'a> { #[serde(flatten, borrow)] common_keys: CommonKey<'a>, } +// Jira Service Management Modules +#[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] +struct AccessImportType<'a> { + #[serde(flatten, borrow)] + common_keys: CommonKey<'a>, + one_delete_import: Option<&'a str>, + start_import: Option<&'a str>, + stop_import: Option<&'a str>, + import_status: Option<&'a str>, +} + +// Compass Modules +#[derive(Debug, Clone, PartialEq, Eq, Deserialize)] +pub struct DataProvider<'a> { + #[serde(flatten, borrow)] + key: &'a str, + callback: Option<&'a str>, +} // Add more structs here for deserializing forge modules #[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] pub struct ForgeModules<'a> { // deserializing non user-invocable modules - #[serde(rename = "macro", default, borrow)] - macros: Vec>, + // Common Modules including triggers + #[serde(rename = "consumer", default, borrow)] + pub consumers: Vec>, #[serde(rename = "function", default, borrow)] pub functions: Vec>, #[serde(rename = "contentByLineItem", default, borrow)] @@ -155,44 +194,33 @@ pub struct ForgeModules<'a> { event_triggers: Vec>, #[serde(rename = "scheduledTrigger", default, borrow)] scheduled_triggers: Vec>, - #[serde(rename = "consumer", default, borrow)] - pub consumers: Vec>, - #[serde(rename = "compass:dataProvider", default, borrow)] - pub data_provider: Vec>, + // confluence Modules + #[serde(rename = "contentByLineItem", default, borrow)] + content_by_line_item: Vec>, + #[serde(rename = "macro", default, borrow)] + macros: Vec>, + // jira modules #[serde(rename = "jira:customField", default, borrow)] pub custom_field: Vec>, + #[serde(rename = "jira:issueGlance", default, borrow)] + issue_glance: Vec>, + #[serde(rename = "jira:accessImportType", default, borrow)] + access_import_type: Vec>, #[serde(rename = "jira:uiModificatons", default, borrow)] pub ui_modifications: Vec>, #[serde(rename = "jira:workflowValidator", default, borrow)] pub workflow_validator: Vec>, #[serde(rename = "jira:workflowPostFunction", default, borrow)] pub workflow_post_function: Vec>, + // Compass Modules + #[serde(rename = "compass:dataProvider", default, borrow)] + pub data_provider: Vec>, // deserializing admin pages #[serde(rename = "jira:adminPage", default, borrow)] pub jira_admin: Vec>, #[serde(flatten)] extra: FxHashMap>>, } -#[derive(Debug, Clone, PartialEq, Eq, Deserialize)] -pub struct JiraAdminPage<'a> { - key: &'a str, - function: &'a str, - title: &'a str, -} - -#[derive(Debug, Clone, PartialEq, Eq, Deserialize)] -pub struct Consumer<'a> { - key: &'a str, - queue: &'a str, - #[serde(borrow)] - pub resolver: Resolver<'a>, -} - -#[derive(Debug, Clone, PartialEq, Eq, Deserialize)] -pub struct Resolver<'a> { - pub function: &'a str, - method: Option<&'a str>, -} #[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] struct Content<'a> { @@ -288,21 +316,21 @@ impl<'a> ForgeModules<'a> { invokable_functions.extend(customfield.search_suggestions); invokable_functions.extend(customfield.edit); - invokable_functions.insert(customfield.common_keys.function); - invokable_functions.extend(customfield.common_keys.resolver); + invokable_functions.extend(customfield.common_keys.function); + invokable_functions.extend(customfield.common_keys.function); }); self.ui_modifications.iter().for_each(|ui| { - invokable_functions.insert(ui.common_keys.function); + invokable_functions.extend(ui.common_keys.function); invokable_functions.extend(ui.common_keys.resolver); }); self.workflow_validator.iter().for_each(|validator| { invokable_functions.insert(validator.common_keys.key); - invokable_functions.insert(validator.common_keys.function); + invokable_functions.extend(validator.common_keys.function); - invokable_functions.extend(validator.common_keys.resolver); + invokable_functions.extend(validator.common_keys.endpoint); }); self.workflow_post_function @@ -310,9 +338,9 @@ impl<'a> ForgeModules<'a> { .for_each(|post_function| { invokable_functions.insert(post_function.common_keys.key); - invokable_functions.insert(post_function.common_keys.function); + invokable_functions.extend(post_function.common_keys.resolver.function); - invokable_functions.extend(post_function.common_keys.resolver); + // invokable_functions.extend(post_function.common_keys.function); }); // get user invokable modules that have additional exposure endpoints. @@ -320,7 +348,7 @@ impl<'a> ForgeModules<'a> { self.macros.iter().for_each(|macros| { invokable_functions.insert(macros.common_keys.key); - invokable_functions.insert(macros.common_keys.function); + invokable_functions.extend(macros.common_keys.function); invokable_functions.extend(macros.common_keys.resolver); invokable_functions.extend(macros.config); @@ -328,13 +356,13 @@ impl<'a> ForgeModules<'a> { }); self.issue_glance.iter().for_each(|issue| { - invokable_functions.insert(issue.common_keys.function); + invokable_functions.extend(issue.common_keys.function); invokable_functions.extend(issue.common_keys.resolver); invokable_functions.extend(issue.dynamic_properties); }); self.access_import_type.iter().for_each(|access| { - invokable_functions.insert(access.common_keys.function); + invokable_functions.extend(access.common_keys.function); invokable_functions.extend(access.common_keys.resolver); invokable_functions.extend(access.one_delete_import); @@ -526,7 +554,9 @@ mod tests { "key": "my-macro", "title": "My Macro", "function": "Catch-me-if-you-can0", - "resolver": "Catch-me-if-you-can1", + "resolver": [ + "function": "Catch-me-if-you-can1", + ] "config": "Catch-me-if-you-can2", "export": "Catch-me-if-you-can3" } From 1387aba024740b752ac2758caa69aa9dc1f102ff Mon Sep 17 00:00:00 2001 From: Alina Wang Date: Mon, 5 Feb 2024 15:22:21 -0500 Subject: [PATCH 511/517] feat: implmented helper function append_function. And updated existing modules to add to Btreeset with helper function --- crates/forge_loader/src/manifest.rs | 69 +++++++++++++---------------- 1 file changed, 30 insertions(+), 39 deletions(-) diff --git a/crates/forge_loader/src/manifest.rs b/crates/forge_loader/src/manifest.rs index 49c3449a..ab6c51ba 100644 --- a/crates/forge_loader/src/manifest.rs +++ b/crates/forge_loader/src/manifest.rs @@ -9,7 +9,8 @@ use crate::Error; use forge_utils::FxHashMap; use itertools::Itertools; use serde::Deserialize; -use tracing::trace; +use serde_json::map::Iter; +use tracing::{info, trace}; #[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] struct AuthProviders<'a> { @@ -22,15 +23,14 @@ struct AuthProviders<'a> { #[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] struct CommonKey<'a> { key: &'a str, + function: Option<&'a str>, resolver: Option>, } impl<'a> CommonKey<'a> { - fn append_functions + Extend>( - &self, - funcs: &mut I, - ) { - funcs.extend(self.key); + fn append_functions>(&self, funcs: &mut I) { + funcs.extend(self.function); + if let Some(Resolver { function, method, @@ -315,56 +315,50 @@ impl<'a> ForgeModules<'a> { invokable_functions.extend(customfield.value); invokable_functions.extend(customfield.search_suggestions); invokable_functions.extend(customfield.edit); - - invokable_functions.extend(customfield.common_keys.function); - invokable_functions.extend(customfield.common_keys.function); + customfield + .common_keys + .append_functions(&mut invokable_functions); }); self.ui_modifications.iter().for_each(|ui| { - invokable_functions.extend(ui.common_keys.function); - invokable_functions.extend(ui.common_keys.resolver); + ui.common_keys.append_functions(&mut invokable_functions); }); self.workflow_validator.iter().for_each(|validator| { - invokable_functions.insert(validator.common_keys.key); - - invokable_functions.extend(validator.common_keys.function); - - invokable_functions.extend(validator.common_keys.endpoint); + validator + .common_keys + .append_functions(&mut invokable_functions) }); self.workflow_post_function .iter() .for_each(|post_function| { - invokable_functions.insert(post_function.common_keys.key); - - invokable_functions.extend(post_function.common_keys.resolver.function); - - // invokable_functions.extend(post_function.common_keys.function); + post_function + .common_keys + .append_functions(&mut invokable_functions); }); // get user invokable modules that have additional exposure endpoints. // ie macros has config and export fields on top of resolver fields that are functions self.macros.iter().for_each(|macros| { invokable_functions.insert(macros.common_keys.key); - - invokable_functions.extend(macros.common_keys.function); - invokable_functions.extend(macros.common_keys.resolver); + macros + .common_keys + .append_functions(&mut invokable_functions); invokable_functions.extend(macros.config); invokable_functions.extend(macros.export); }); self.issue_glance.iter().for_each(|issue| { - invokable_functions.extend(issue.common_keys.function); - invokable_functions.extend(issue.common_keys.resolver); + issue.common_keys.append_functions(&mut invokable_functions); invokable_functions.extend(issue.dynamic_properties); }); self.access_import_type.iter().for_each(|access| { - invokable_functions.extend(access.common_keys.function); - invokable_functions.extend(access.common_keys.resolver); - + access + .common_keys + .append_functions(&mut invokable_functions); invokable_functions.extend(access.one_delete_import); invokable_functions.extend(access.stop_import); invokable_functions.extend(access.start_import); @@ -593,20 +587,17 @@ mod tests { }"#; let manifest: ForgeManifest = serde_json::from_str(json).unwrap(); assert_eq!(manifest.modules.macros.len(), 1); - assert_eq!( - manifest.modules.macros[0].common_keys.function, - "Catch-me-if-you-can0" - ); + if let Some(string) = manifest.modules.macros[0].common_keys.function { + assert_eq!(string, "Catch-me-if-you-can0"); + } - let Some(func_test) = manifest.modules.macros[0].common_keys.resolver else { + let Some(ref resolver) = manifest.modules.macros[0].common_keys.resolver else { panic!("No dice!") }; - println!("what is func? {}", func_test); - if let Some(func) = manifest.modules.macros[0].common_keys.resolver { - println!("what is func? {}", func); - assert_eq!(func, "Catch-me-if-you-can1"); - } + if let Some(string) = resolver.function { + assert_eq!(string, "Catch-me-if-you-can1"); + } if let Some(func) = manifest.modules.macros[0].config { assert_eq!(func, "Catch-me-if-you-can2"); } From e35c34d5efe4a24e12aaa584ebc6691da2d94bbe Mon Sep 17 00:00:00 2001 From: Alina Wang Date: Mon, 5 Feb 2024 17:27:18 -0500 Subject: [PATCH 512/517] fix: added compass and confluence modules to get deserialized. Todo: finish updating into_analyzable_functions to add functions from new modules. Then add jira modules and functions associated --- crates/forge_loader/src/manifest.rs | 211 ++++++++++++++++++++-------- 1 file changed, 150 insertions(+), 61 deletions(-) diff --git a/crates/forge_loader/src/manifest.rs b/crates/forge_loader/src/manifest.rs index ab6c51ba..022be437 100644 --- a/crates/forge_loader/src/manifest.rs +++ b/crates/forge_loader/src/manifest.rs @@ -41,13 +41,20 @@ impl<'a> CommonKey<'a> { } } } -#[derive(Debug, Clone, PartialEq, Eq, Deserialize)] +#[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] pub struct Resolver<'a> { pub function: Option<&'a str>, pub method: Option<&'a str>, pub endpoint: Option<&'a str>, } +// Implementing a struct for structs with 1 value (function) + +#[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] +pub struct JustFunc<'a> { + pub function: Option<&'a str>, +} + // Common Modules #[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] pub struct FunctionMod<'a> { @@ -74,6 +81,7 @@ enum Interval { Week, } +// Maps to Scheduled Trigger under Common Modules #[derive(Debug, Clone, PartialEq, Eq, Deserialize)] struct ScheduledTrigger<'a> { #[serde(flatten, borrow)] @@ -81,12 +89,14 @@ struct ScheduledTrigger<'a> { interval: Interval, } +// Maps to Web Trigger under Common Modules #[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] struct RawTrigger<'a> { key: &'a str, function: &'a str, } +// maps to Trigger under Common Modules #[derive(Debug, Clone, PartialEq, Eq, Deserialize)] struct EventTrigger<'a> { #[serde(flatten, borrow)] @@ -95,21 +105,57 @@ struct EventTrigger<'a> { events: Vec<&'a str>, } -// Confluence Modules +// Compass Modules #[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] -struct MacroMod<'a> { +pub struct CompassAdminPage<'a> { + #[serde(flatten, borrow)] + common_keys: CommonKey<'a>, +} + +#[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] +pub struct ComponentPage<'a> { + #[serde(flatten, borrow)] + common_keys: CommonKey<'a>, +} +#[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] +pub struct DataProvider<'a> { + #[serde(flatten, borrow)] + common_keys: CommonKey<'a>, + callback: JustFunc<'a>, +} + +#[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] +pub struct CompassGlobalPage<'a> { + #[serde(flatten, borrow)] + common_keys: CommonKey<'a>, +} + +#[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] +pub struct TeamPage<'a> { #[serde(flatten, borrow)] common_keys: CommonKey<'a>, - config: Option<&'a str>, - export: Option<&'a str>, } +// Confluence Modules +#[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] +struct ContentAction<'a> { + #[serde(flatten, borrow)] + common_keys: CommonKey<'a>, +} #[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] struct ContentByLineItem<'a> { #[serde(flatten, borrow)] common_keys: CommonKey<'a>, #[serde(borrow)] - dynamic_properties: Option<&'a str>, + dynamic_properties: JustFunc<'a>, +} + +#[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] +struct MacroMod<'a> { + #[serde(flatten, borrow)] + common_keys: CommonKey<'a>, + config: JustFunc<'a>, + export: JustFunc<'a>, } // Jira Modules @@ -118,13 +164,7 @@ pub struct JiraAdminPage<'a> { key: &'a str, function: &'a str, title: &'a str, -} - -#[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] -struct IssueGlance<'a> { - #[serde(flatten, borrow)] - common_keys: CommonKey<'a>, - dynamic_properties: Option<&'a str>, + resolver: Resolver<'a>, } #[derive(Debug, Clone, PartialEq, Eq, Deserialize)] @@ -136,6 +176,13 @@ pub struct CustomField<'a> { edit: Option<&'a str>, } +#[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] +struct IssueGlance<'a> { + #[serde(flatten, borrow)] + common_keys: CommonKey<'a>, + dynamic_properties: JustFunc<'a>, +} + #[derive(Debug, Clone, PartialEq, Eq, Deserialize)] pub struct UiModificatons<'a> { #[serde(flatten, borrow)] @@ -155,21 +202,13 @@ pub struct WorkflowPostFunction<'a> { } // Jira Service Management Modules #[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] -struct AccessImportType<'a> { +struct AssetsImportType<'a> { #[serde(flatten, borrow)] common_keys: CommonKey<'a>, - one_delete_import: Option<&'a str>, - start_import: Option<&'a str>, - stop_import: Option<&'a str>, - import_status: Option<&'a str>, -} - -// Compass Modules -#[derive(Debug, Clone, PartialEq, Eq, Deserialize)] -pub struct DataProvider<'a> { - #[serde(flatten, borrow)] - key: &'a str, - callback: Option<&'a str>, + one_delete_import: JustFunc<'a>, + start_import: JustFunc<'a>, + stop_import: JustFunc<'a>, + import_status: JustFunc<'a>, } // Add more structs here for deserializing forge modules @@ -194,9 +233,32 @@ pub struct ForgeModules<'a> { event_triggers: Vec>, #[serde(rename = "scheduledTrigger", default, borrow)] scheduled_triggers: Vec>, + // Compass Modules + #[serde(rename = "compass:adminPage", default, borrow)] + compass_admin_page: Vec>, + #[serde(rename = "compass:componentPage", default, borrow)] + component_page: Vec>, + #[serde(rename = "compass:dataProvider", default, borrow)] + pub data_provider: Vec>, + #[serde(rename = "compass:globalPage", default, borrow)] + compass_global_page: Vec>, + #[serde(rename = "compass:teamPage", default, borrow)] + team_page: Vec>, // confluence Modules - #[serde(rename = "contentByLineItem", default, borrow)] + #[serde(rename = "confluence:contentAction", default, borrow)] + content_action: Vec>, + #[serde(rename = "confluence:contentByLineItem", default, borrow)] content_by_line_item: Vec>, + #[serde(rename = "confluence:contextMenu", default, borrow)] + context_action: Vec>, + #[serde(rename = "confluence:globalPage", default, borrow)] + confluence_global_page: Vec>, + #[serde(rename = "confluence:homepageFeed", default, borrow)] + homepage_feed: Vec>, + #[serde(rename = "confluence:spacePage", default, borrow)] + space_page: Vec>, + #[serde(rename = "confluence:spaceSettings", default, borrow)] + space_settings: Vec>, #[serde(rename = "macro", default, borrow)] macros: Vec>, // jira modules @@ -204,20 +266,18 @@ pub struct ForgeModules<'a> { pub custom_field: Vec>, #[serde(rename = "jira:issueGlance", default, borrow)] issue_glance: Vec>, - #[serde(rename = "jira:accessImportType", default, borrow)] - access_import_type: Vec>, #[serde(rename = "jira:uiModificatons", default, borrow)] pub ui_modifications: Vec>, #[serde(rename = "jira:workflowValidator", default, borrow)] pub workflow_validator: Vec>, #[serde(rename = "jira:workflowPostFunction", default, borrow)] pub workflow_post_function: Vec>, - // Compass Modules - #[serde(rename = "compass:dataProvider", default, borrow)] - pub data_provider: Vec>, + // Jira Service Management Modules + #[serde(rename = "jiraServiceManagement:assetsImportType", default, borrow)] + access_import_type: Vec>, // deserializing admin pages #[serde(rename = "jira:adminPage", default, borrow)] - pub jira_admin: Vec>, + pub jira_admin_page: Vec>, #[serde(flatten)] extra: FxHashMap>>, } @@ -307,10 +367,61 @@ impl<'a> ForgeModules<'a> { .sort_unstable_by_key(|trigger| trigger.function); let mut invokable_functions = BTreeSet::new(); + // Compass Module Functions + self.compass_admin_page + .iter() + .for_each(|compass_admin| compass_admin.append_functions(&mut invokable_functions)); + + self.component_page + .iter() + .for_each(|component_page| component_page.append_functions(&mut invokable_functions)); + self.data_provider.iter().for_each(|dataprovider| { - invokable_functions.extend(dataprovider.callback); + invokable_functions.extend(dataprovider.callback.function); + }); + + self.compass_global_page + .iter() + .for_each(|global_page| global_page.append_functions(&mut invokable_functions)); + + self.team_page + .iter() + .for_each(|team_page| team_page.append_functions(&mut invokable_functions)); + + // Confluence Module Functions + // get user invokable modules that have additional exposure endpoints. + // ie macros has config and export fields on top of resolver fields that are functions + self.content_action + .iter() + .for_each(|content_action| content_action.append_functions(&mut invokable_functions)); + + self.content_by_line_item.iter().for_each(|by_line_item| { + by_line_item + .common_keys + .append_functions(&mut invokable_functions); + invokable_functions.extend(by_line_item.dynamic_properties.function) }); + self.macros.iter().for_each(|macros| { + macros + .common_keys + .append_functions(&mut invokable_functions); + + invokable_functions.extend(macros.config.function); + invokable_functions.extend(macros.export.function); + }); + + self.access_import_type.iter().for_each(|access| { + access + .common_keys + .append_functions(&mut invokable_functions); + invokable_functions.extend(access.one_delete_import.function); + invokable_functions.extend(access.stop_import.function); + invokable_functions.extend(access.start_import.function); + invokable_functions.extend(access.import_status.function); + }); + + // Jira module functons self.custom_field.iter().for_each(|customfield| { invokable_functions.extend(customfield.value); invokable_functions.extend(customfield.search_suggestions); @@ -319,6 +430,10 @@ impl<'a> ForgeModules<'a> { .common_keys .append_functions(&mut invokable_functions); }); + self.issue_glance.iter().for_each(|issue| { + issue.common_keys.append_functions(&mut invokable_functions); + invokable_functions.extend(issue.dynamic_properties.function); + }); self.ui_modifications.iter().for_each(|ui| { ui.common_keys.append_functions(&mut invokable_functions); @@ -338,32 +453,6 @@ impl<'a> ForgeModules<'a> { .append_functions(&mut invokable_functions); }); - // get user invokable modules that have additional exposure endpoints. - // ie macros has config and export fields on top of resolver fields that are functions - self.macros.iter().for_each(|macros| { - invokable_functions.insert(macros.common_keys.key); - macros - .common_keys - .append_functions(&mut invokable_functions); - - invokable_functions.extend(macros.config); - invokable_functions.extend(macros.export); - }); - - self.issue_glance.iter().for_each(|issue| { - issue.common_keys.append_functions(&mut invokable_functions); - invokable_functions.extend(issue.dynamic_properties); - }); - - self.access_import_type.iter().for_each(|access| { - access - .common_keys - .append_functions(&mut invokable_functions); - invokable_functions.extend(access.one_delete_import); - invokable_functions.extend(access.stop_import); - invokable_functions.extend(access.start_import); - invokable_functions.extend(access.import_status); - }); self.functions.into_iter().flat_map(move |func| { let web_trigger = self .webtriggers @@ -373,7 +462,7 @@ impl<'a> ForgeModules<'a> { // this checks whether the funton being scanned is being used in an admin module. Rn it only checks for jira_admin page module. // optionally: compass:adminPage could also be considered. let admin = self - .jira_admin + .jira_admin_page .iter() .any(|admin_function| admin_function.function == func.key); Ok::<_, Error>(Entrypoint { From 182229479ac2449cd21cdce9737612e91a55beca Mon Sep 17 00:00:00 2001 From: Alina Wang Date: Mon, 5 Feb 2024 22:02:10 -0500 Subject: [PATCH 513/517] feat: added rest of jira modules. Updated into_analyzable_functions to accomodate functions from new modules. --- crates/forge_loader/src/manifest.rs | 150 ++++++++++++++++++++++++---- 1 file changed, 132 insertions(+), 18 deletions(-) diff --git a/crates/forge_loader/src/manifest.rs b/crates/forge_loader/src/manifest.rs index 022be437..3b1bd2f7 100644 --- a/crates/forge_loader/src/manifest.rs +++ b/crates/forge_loader/src/manifest.rs @@ -176,8 +176,24 @@ pub struct CustomField<'a> { edit: Option<&'a str>, } +#[derive(Debug, Clone, PartialEq, Eq, Deserialize)] +pub struct CustomFieldType<'a> { + #[serde(flatten, borrow)] + common_keys: CommonKey<'a>, + value: Option<&'a str>, + edit: Option<&'a str>, + context_config: Option<&'a str>, +} + +#[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] +pub struct DashboardGadget<'a> { + #[serde(flatten, borrow)] + common_keys: CommonKey<'a>, + edit: Option<&'a str>, +} + #[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] -struct IssueGlance<'a> { +struct IssueClass<'a> { #[serde(flatten, borrow)] common_keys: CommonKey<'a>, dynamic_properties: JustFunc<'a>, @@ -250,7 +266,7 @@ pub struct ForgeModules<'a> { #[serde(rename = "confluence:contentByLineItem", default, borrow)] content_by_line_item: Vec>, #[serde(rename = "confluence:contextMenu", default, borrow)] - context_action: Vec>, + context_menu: Vec>, #[serde(rename = "confluence:globalPage", default, borrow)] confluence_global_page: Vec>, #[serde(rename = "confluence:homepageFeed", default, borrow)] @@ -262,10 +278,34 @@ pub struct ForgeModules<'a> { #[serde(rename = "macro", default, borrow)] macros: Vec>, // jira modules + #[serde(rename = "jira:adminPage", default, borrow)] + pub jira_admin_page: Vec>, #[serde(rename = "jira:customField", default, borrow)] pub custom_field: Vec>, + #[serde(rename = "jira:customFieldType", default, borrow)] + custom_field_type: Vec>, + #[serde(rename = "jira:dashboardBackgroundScript", default, borrow)] + dashboard_background_script: Vec>, + #[serde(rename = "jira:dashboardGadget", default, borrow)] + dashboard_gadget: Vec>, + #[serde(rename = "jira:globalPage", default, borrow)] + jira_global_page: Vec>, + #[serde(rename = "jira:issueAction", default, borrow)] + issue_action: Vec>, + #[serde(rename = "jira:issueContext", default, borrow)] + issue_context: Vec>, #[serde(rename = "jira:issueGlance", default, borrow)] - issue_glance: Vec>, + issue_glance: Vec>, + #[serde(rename = "jira:issuePanel", default, borrow)] + issue_panel: Vec>, + #[serde(rename = "jira:issueViewBackgroundScript", default, borrow)] + issue_view_background_script: Vec>, + #[serde(rename = "jira:jqlFunction", default, borrow)] + jql_function: Vec>, + #[serde(rename = "jira:projectPage", default, borrow)] + project_page: Vec>, + #[serde(rename = "jira:projectSettingsPage", default, borrow)] + project_settings_page: Vec>, #[serde(rename = "jira:uiModificatons", default, borrow)] pub ui_modifications: Vec>, #[serde(rename = "jira:workflowValidator", default, borrow)] @@ -274,10 +314,8 @@ pub struct ForgeModules<'a> { pub workflow_post_function: Vec>, // Jira Service Management Modules #[serde(rename = "jiraServiceManagement:assetsImportType", default, borrow)] - access_import_type: Vec>, + assets_import_type: Vec>, // deserializing admin pages - #[serde(rename = "jira:adminPage", default, borrow)] - pub jira_admin_page: Vec>, #[serde(flatten)] extra: FxHashMap>>, } @@ -402,6 +440,26 @@ impl<'a> ForgeModules<'a> { invokable_functions.extend(by_line_item.dynamic_properties.function) }); + self.context_menu + .iter() + .for_each(|context_menu| context_menu.append_functions(&mut invokable_functions)); + + self.confluence_global_page + .iter() + .for_each(|global_page| global_page.append_functions(&mut invokable_functions)); + + self.homepage_feed + .iter() + .for_each(|homepage_feed| homepage_feed.append_functions(&mut invokable_functions)); + + self.space_page + .iter() + .for_each(|space_page| space_page.append_functions(&mut invokable_functions)); + + self.space_settings + .iter() + .for_each(|space_settings| space_settings.append_functions(&mut invokable_functions)); + self.macros.iter().for_each(|macros| { macros .common_keys @@ -411,16 +469,6 @@ impl<'a> ForgeModules<'a> { invokable_functions.extend(macros.export.function); }); - self.access_import_type.iter().for_each(|access| { - access - .common_keys - .append_functions(&mut invokable_functions); - invokable_functions.extend(access.one_delete_import.function); - invokable_functions.extend(access.stop_import.function); - invokable_functions.extend(access.start_import.function); - invokable_functions.extend(access.import_status.function); - }); - // Jira module functons self.custom_field.iter().for_each(|customfield| { invokable_functions.extend(customfield.value); @@ -430,11 +478,66 @@ impl<'a> ForgeModules<'a> { .common_keys .append_functions(&mut invokable_functions); }); + + self.custom_field_type.iter().for_each(|custom_field_type| { + invokable_functions.extend(custom_field_type.value); + invokable_functions.extend(custom_field_type.edit); + invokable_functions.extend(custom_field_type.context_config); + + custom_field_type + .common_keys + .append_functions(&mut invokable_functions); + }); + + self.dashboard_background_script + .iter() + .for_each(|dbs| dbs.append_functions(&mut invokable_functions)); + + self.dashboard_gadget.iter().for_each(|gadget| { + invokable_functions.extend(gadget.edit); + gadget + .common_keys + .append_functions(&mut invokable_functions) + }); + + self.jira_global_page + .iter() + .for_each(|global_page| global_page.append_functions(&mut invokable_functions)); + + self.issue_action + .iter() + .for_each(|issue| issue.append_functions(&mut invokable_functions)); + + self.issue_context.iter().for_each(|issue| { + invokable_functions.extend(issue.dynamic_properties.function); + issue.common_keys.append_functions(&mut invokable_functions) + }); + self.issue_glance.iter().for_each(|issue| { issue.common_keys.append_functions(&mut invokable_functions); invokable_functions.extend(issue.dynamic_properties.function); }); + self.issue_panel + .iter() + .for_each(|issue| issue.append_functions(&mut invokable_functions)); + + self.issue_view_background_script + .iter() + .for_each(|issue| issue.append_functions(&mut invokable_functions)); + + self.jql_function + .iter() + .for_each(|item| item.append_functions(&mut invokable_functions)); + + self.project_page + .iter() + .for_each(|item| item.append_functions(&mut invokable_functions)); + + self.project_settings_page + .iter() + .for_each(|item| item.append_functions(&mut invokable_functions)); + self.ui_modifications.iter().for_each(|ui| { ui.common_keys.append_functions(&mut invokable_functions); }); @@ -453,6 +556,17 @@ impl<'a> ForgeModules<'a> { .append_functions(&mut invokable_functions); }); + // JSM modules + self.assets_import_type.iter().for_each(|access| { + access + .common_keys + .append_functions(&mut invokable_functions); + invokable_functions.extend(access.one_delete_import.function); + invokable_functions.extend(access.stop_import.function); + invokable_functions.extend(access.start_import.function); + invokable_functions.extend(access.import_status.function); + }); + self.functions.into_iter().flat_map(move |func| { let web_trigger = self .webtriggers @@ -687,11 +801,11 @@ mod tests { if let Some(string) = resolver.function { assert_eq!(string, "Catch-me-if-you-can1"); } - if let Some(func) = manifest.modules.macros[0].config { + if let Some(func) = manifest.modules.macros[0].config.function { assert_eq!(func, "Catch-me-if-you-can2"); } - if let Some(func) = manifest.modules.macros[0].export { + if let Some(func) = manifest.modules.macros[0].export.function { assert_eq!(func, "Catch-me-if-you-can3"); } } From a6cf055eea7c11d06b7eb7b52cb7ca9b7df498d1 Mon Sep 17 00:00:00 2001 From: Alina Wang Date: Tue, 6 Feb 2024 11:50:06 -0500 Subject: [PATCH 514/517] resolve: destructured ForgeModule struct to address comment on tracking all and new modules. Added compass_admin_page check to module function check --- crates/forge_loader/src/manifest.rs | 130 +++++++++++++++++++--------- 1 file changed, 87 insertions(+), 43 deletions(-) diff --git a/crates/forge_loader/src/manifest.rs b/crates/forge_loader/src/manifest.rs index 3b1bd2f7..1ad6465a 100644 --- a/crates/forge_loader/src/manifest.rs +++ b/crates/forge_loader/src/manifest.rs @@ -399,68 +399,109 @@ pub struct Entrypoint<'a> { impl<'a> ForgeModules<'a> { // TODO: function returns iterator where each item is some specified type. - pub fn into_analyzable_functions(mut self) -> impl Iterator> { + pub fn into_analyzable_functions(self) -> impl Iterator> { + // destructuring ForgeModules to remember to add new modules to this + let Self { + mut webtriggers, + custom_field, + consumers: _, + functions, + event_triggers: _, + scheduled_triggers: _, + compass_admin_page, + component_page, + data_provider, + compass_global_page, + team_page, + content_action, + content_by_line_item, + context_menu, + confluence_global_page, + homepage_feed, + space_page, + space_settings, + macros, + jira_admin_page, + custom_field_type, + dashboard_background_script, + dashboard_gadget, + jira_global_page, + issue_action, + issue_context, + issue_glance, + issue_panel, + issue_view_background_script, + jql_function, + project_page, + project_settings_page, + ui_modifications, + workflow_validator, + workflow_post_function, + assets_import_type, + extra: _, + } = self; + // number of webtriggers are usually low, so it's better to just sort them and reuse self.webtriggers .sort_unstable_by_key(|trigger| trigger.function); let mut invokable_functions = BTreeSet::new(); // Compass Module Functions - self.compass_admin_page + compass_admin_page .iter() .for_each(|compass_admin| compass_admin.append_functions(&mut invokable_functions)); - self.component_page + component_page .iter() .for_each(|component_page| component_page.append_functions(&mut invokable_functions)); - self.data_provider.iter().for_each(|dataprovider| { + data_provider.iter().for_each(|dataprovider| { invokable_functions.extend(dataprovider.callback.function); }); - self.compass_global_page + compass_global_page .iter() .for_each(|global_page| global_page.append_functions(&mut invokable_functions)); - self.team_page + team_page .iter() .for_each(|team_page| team_page.append_functions(&mut invokable_functions)); // Confluence Module Functions // get user invokable modules that have additional exposure endpoints. // ie macros has config and export fields on top of resolver fields that are functions - self.content_action + content_action .iter() .for_each(|content_action| content_action.append_functions(&mut invokable_functions)); - self.content_by_line_item.iter().for_each(|by_line_item| { + content_by_line_item.iter().for_each(|by_line_item| { by_line_item .common_keys .append_functions(&mut invokable_functions); invokable_functions.extend(by_line_item.dynamic_properties.function) }); - self.context_menu + context_menu .iter() .for_each(|context_menu| context_menu.append_functions(&mut invokable_functions)); - self.confluence_global_page + confluence_global_page .iter() .for_each(|global_page| global_page.append_functions(&mut invokable_functions)); - self.homepage_feed + homepage_feed .iter() .for_each(|homepage_feed| homepage_feed.append_functions(&mut invokable_functions)); - self.space_page + space_page .iter() .for_each(|space_page| space_page.append_functions(&mut invokable_functions)); - self.space_settings + space_settings .iter() .for_each(|space_settings| space_settings.append_functions(&mut invokable_functions)); - self.macros.iter().for_each(|macros| { + macros.iter().for_each(|macros| { macros .common_keys .append_functions(&mut invokable_functions); @@ -470,7 +511,7 @@ impl<'a> ForgeModules<'a> { }); // Jira module functons - self.custom_field.iter().for_each(|customfield| { + custom_field.iter().for_each(|customfield| { invokable_functions.extend(customfield.value); invokable_functions.extend(customfield.search_suggestions); invokable_functions.extend(customfield.edit); @@ -479,7 +520,7 @@ impl<'a> ForgeModules<'a> { .append_functions(&mut invokable_functions); }); - self.custom_field_type.iter().for_each(|custom_field_type| { + custom_field_type.iter().for_each(|custom_field_type| { invokable_functions.extend(custom_field_type.value); invokable_functions.extend(custom_field_type.edit); invokable_functions.extend(custom_field_type.context_config); @@ -489,75 +530,73 @@ impl<'a> ForgeModules<'a> { .append_functions(&mut invokable_functions); }); - self.dashboard_background_script + dashboard_background_script .iter() .for_each(|dbs| dbs.append_functions(&mut invokable_functions)); - self.dashboard_gadget.iter().for_each(|gadget| { + dashboard_gadget.iter().for_each(|gadget| { invokable_functions.extend(gadget.edit); gadget .common_keys .append_functions(&mut invokable_functions) }); - self.jira_global_page + jira_global_page .iter() .for_each(|global_page| global_page.append_functions(&mut invokable_functions)); - self.issue_action + issue_action .iter() .for_each(|issue| issue.append_functions(&mut invokable_functions)); - self.issue_context.iter().for_each(|issue| { + issue_context.iter().for_each(|issue| { invokable_functions.extend(issue.dynamic_properties.function); issue.common_keys.append_functions(&mut invokable_functions) }); - self.issue_glance.iter().for_each(|issue| { + issue_glance.iter().for_each(|issue| { issue.common_keys.append_functions(&mut invokable_functions); invokable_functions.extend(issue.dynamic_properties.function); }); - self.issue_panel + issue_panel .iter() .for_each(|issue| issue.append_functions(&mut invokable_functions)); - self.issue_view_background_script + issue_view_background_script .iter() .for_each(|issue| issue.append_functions(&mut invokable_functions)); - self.jql_function + jql_function .iter() .for_each(|item| item.append_functions(&mut invokable_functions)); - self.project_page + project_page .iter() .for_each(|item| item.append_functions(&mut invokable_functions)); - self.project_settings_page + project_settings_page .iter() .for_each(|item| item.append_functions(&mut invokable_functions)); - self.ui_modifications.iter().for_each(|ui| { + ui_modifications.iter().for_each(|ui| { ui.common_keys.append_functions(&mut invokable_functions); }); - self.workflow_validator.iter().for_each(|validator| { + workflow_validator.iter().for_each(|validator| { validator .common_keys .append_functions(&mut invokable_functions) }); - self.workflow_post_function - .iter() - .for_each(|post_function| { - post_function - .common_keys - .append_functions(&mut invokable_functions); - }); + workflow_post_function.iter().for_each(|post_function| { + post_function + .common_keys + .append_functions(&mut invokable_functions); + }); // JSM modules - self.assets_import_type.iter().for_each(|access| { + assets_import_type.iter().for_each(|access| { access .common_keys .append_functions(&mut invokable_functions); @@ -567,18 +606,23 @@ impl<'a> ForgeModules<'a> { invokable_functions.extend(access.import_status.function); }); - self.functions.into_iter().flat_map(move |func| { - let web_trigger = self - .webtriggers + functions.into_iter().flat_map(move |func| { + let web_trigger = webtriggers .binary_search_by_key(&func.key, |trigger| &trigger.function) .is_ok(); let invokable = invokable_functions.contains(func.key); // this checks whether the funton being scanned is being used in an admin module. Rn it only checks for jira_admin page module. // optionally: compass:adminPage could also be considered. - let admin = self - .jira_admin_page + let admin = jira_admin_page .iter() - .any(|admin_function| admin_function.function == func.key); + .any(|admin_function| admin_function.function == func.key) + || compass_admin_page.iter().any(|admin_function| { + let Some(string) = admin_function.function else { + return false; + }; + return string == func.key; + }); + Ok::<_, Error>(Entrypoint { function: FunctionRef::try_from(func)?, invokable, From aec5b6e358e6d95a9aed5a2bf1912a58d1fcebf2 Mon Sep 17 00:00:00 2001 From: Alina Wang Date: Tue, 6 Feb 2024 15:05:11 -0500 Subject: [PATCH 515/517] fixx addressed most comments. Updated fields to be optional or a struct. removed data_provider. Todo: resolve copy issue and add JSM modules --- crates/forge_loader/src/manifest.rs | 150 +++++++++++++++++----------- 1 file changed, 90 insertions(+), 60 deletions(-) diff --git a/crates/forge_loader/src/manifest.rs b/crates/forge_loader/src/manifest.rs index 1ad6465a..b0ef8773 100644 --- a/crates/forge_loader/src/manifest.rs +++ b/crates/forge_loader/src/manifest.rs @@ -20,7 +20,7 @@ struct AuthProviders<'a> { // Abstracting away key, function, and resolver into a single struct for reuse whoo! // And helper functions for ease of use -#[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] +#[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize, Copy)] struct CommonKey<'a> { key: &'a str, function: Option<&'a str>, @@ -41,7 +41,7 @@ impl<'a> CommonKey<'a> { } } } -#[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] +#[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize, Copy)] pub struct Resolver<'a> { pub function: Option<&'a str>, pub method: Option<&'a str>, @@ -50,7 +50,7 @@ pub struct Resolver<'a> { // Implementing a struct for structs with 1 value (function) -#[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] +#[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize, Copy)] pub struct JustFunc<'a> { pub function: Option<&'a str>, } @@ -117,12 +117,12 @@ pub struct ComponentPage<'a> { #[serde(flatten, borrow)] common_keys: CommonKey<'a>, } -#[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] -pub struct DataProvider<'a> { - #[serde(flatten, borrow)] - common_keys: CommonKey<'a>, - callback: JustFunc<'a>, -} +// #[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] +// pub struct DataProvider<'a> { +// #[serde(flatten, borrow)] +// common_keys: CommonKey<'a>, +// callback: Option>, +// } #[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] pub struct CompassGlobalPage<'a> { @@ -146,7 +146,7 @@ struct ContentAction<'a> { struct ContentByLineItem<'a> { #[serde(flatten, borrow)] common_keys: CommonKey<'a>, - #[serde(borrow)] + #[serde(borrow, rename = "dynamicProperties")] dynamic_properties: JustFunc<'a>, } @@ -154,49 +154,48 @@ struct ContentByLineItem<'a> { struct MacroMod<'a> { #[serde(flatten, borrow)] common_keys: CommonKey<'a>, - config: JustFunc<'a>, - export: JustFunc<'a>, + config: Option>, + export: Option>, } // Jira Modules #[derive(Debug, Clone, PartialEq, Eq, Deserialize)] pub struct JiraAdminPage<'a> { - key: &'a str, - function: &'a str, title: &'a str, - resolver: Resolver<'a>, + #[serde(flatten, borrow)] + common_keys: CommonKey<'a>, } #[derive(Debug, Clone, PartialEq, Eq, Deserialize)] pub struct CustomField<'a> { #[serde(flatten, borrow)] common_keys: CommonKey<'a>, - value: Option<&'a str>, - search_suggestions: Option<&'a str>, - edit: Option<&'a str>, + value: Option>, + edit: Option>, } #[derive(Debug, Clone, PartialEq, Eq, Deserialize)] pub struct CustomFieldType<'a> { #[serde(flatten, borrow)] common_keys: CommonKey<'a>, - value: Option<&'a str>, - edit: Option<&'a str>, - context_config: Option<&'a str>, + value: Option>, + edit: Option>, + context_config: Option>, } #[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] pub struct DashboardGadget<'a> { #[serde(flatten, borrow)] common_keys: CommonKey<'a>, - edit: Option<&'a str>, + edit: Option>, } #[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] +#[serde(rename_all = "camelCase")] struct IssueClass<'a> { #[serde(flatten, borrow)] common_keys: CommonKey<'a>, - dynamic_properties: JustFunc<'a>, + dynamic_properties: Option>, } #[derive(Debug, Clone, PartialEq, Eq, Deserialize)] @@ -217,11 +216,12 @@ pub struct WorkflowPostFunction<'a> { common_keys: CommonKey<'a>, } // Jira Service Management Modules -#[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] +#[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize, Copy)] +#[serde(rename_all = "camelCase")] struct AssetsImportType<'a> { #[serde(flatten, borrow)] common_keys: CommonKey<'a>, - one_delete_import: JustFunc<'a>, + on_delete_import: Option>, start_import: JustFunc<'a>, stop_import: JustFunc<'a>, import_status: JustFunc<'a>, @@ -254,8 +254,6 @@ pub struct ForgeModules<'a> { compass_admin_page: Vec>, #[serde(rename = "compass:componentPage", default, borrow)] component_page: Vec>, - #[serde(rename = "compass:dataProvider", default, borrow)] - pub data_provider: Vec>, #[serde(rename = "compass:globalPage", default, borrow)] compass_global_page: Vec>, #[serde(rename = "compass:teamPage", default, borrow)] @@ -410,7 +408,6 @@ impl<'a> ForgeModules<'a> { scheduled_triggers: _, compass_admin_page, component_page, - data_provider, compass_global_page, team_page, content_action, @@ -455,10 +452,6 @@ impl<'a> ForgeModules<'a> { .iter() .for_each(|component_page| component_page.append_functions(&mut invokable_functions)); - data_provider.iter().for_each(|dataprovider| { - invokable_functions.extend(dataprovider.callback.function); - }); - compass_global_page .iter() .for_each(|global_page| global_page.append_functions(&mut invokable_functions)); @@ -501,29 +494,43 @@ impl<'a> ForgeModules<'a> { .iter() .for_each(|space_settings| space_settings.append_functions(&mut invokable_functions)); - macros.iter().for_each(|macros| { - macros - .common_keys - .append_functions(&mut invokable_functions); - - invokable_functions.extend(macros.config.function); - invokable_functions.extend(macros.export.function); + macros.iter().for_each(|mac| { + mac.common_keys.append_functions(&mut invokable_functions); + self.clone() + .add_optional(mac.config, &mut invokable_functions); + self.clone() + .add_optional(mac.export, &mut invokable_functions); }); // Jira module functons custom_field.iter().for_each(|customfield| { - invokable_functions.extend(customfield.value); - invokable_functions.extend(customfield.search_suggestions); - invokable_functions.extend(customfield.edit); + // let Some(func) = customfield.value; + // invokable_functions.extend(func.function); + self.clone() + .add_optional(customfield.value, &mut invokable_functions); + + // let Some(func) = customfield.edit; + // invokable_functions.extend(func.function); + self.clone() + .add_optional(customfield.edit, &mut invokable_functions); + customfield .common_keys .append_functions(&mut invokable_functions); }); custom_field_type.iter().for_each(|custom_field_type| { - invokable_functions.extend(custom_field_type.value); - invokable_functions.extend(custom_field_type.edit); - invokable_functions.extend(custom_field_type.context_config); + // invokable_functions.extend(custom_field_type.value); + self.clone() + .add_optional(custom_field_type.value, &mut invokable_functions); + + // invokable_functions.extend(custom_field_type.edit); + self.clone() + .add_optional(custom_field_type.edit, &mut invokable_functions); + + // invokable_functions.extend(custom_field_type.context_config); + self.clone() + .add_optional(custom_field_type.context_config, &mut invokable_functions); custom_field_type .common_keys @@ -535,7 +542,10 @@ impl<'a> ForgeModules<'a> { .for_each(|dbs| dbs.append_functions(&mut invokable_functions)); dashboard_gadget.iter().for_each(|gadget| { - invokable_functions.extend(gadget.edit); + // invokable_functions.extend(gadget.edit); + self.clone() + .add_optional(gadget.edit, &mut invokable_functions); + gadget .common_keys .append_functions(&mut invokable_functions) @@ -550,13 +560,20 @@ impl<'a> ForgeModules<'a> { .for_each(|issue| issue.append_functions(&mut invokable_functions)); issue_context.iter().for_each(|issue| { - invokable_functions.extend(issue.dynamic_properties.function); + // let Some(func) = issue.dynamic_properties; + // invokable_functions.extend(func.function); + self.clone() + .add_optional(issue.dynamic_properties, &mut invokable_functions); + issue.common_keys.append_functions(&mut invokable_functions) }); issue_glance.iter().for_each(|issue| { issue.common_keys.append_functions(&mut invokable_functions); - invokable_functions.extend(issue.dynamic_properties.function); + // let Some(func) = issue.dynamic_properties; + // invokable_functions.extend(func.function); + self.clone() + .add_optional(issue.dynamic_properties, &mut invokable_functions); }); issue_panel @@ -600,7 +617,11 @@ impl<'a> ForgeModules<'a> { access .common_keys .append_functions(&mut invokable_functions); - invokable_functions.extend(access.one_delete_import.function); + // let Some(func) = access.on_delete_import; + // invokable_functions.extend(func.function); + self.clone() + .add_optional(access.on_delete_import, &mut invokable_functions); + invokable_functions.extend(access.stop_import.function); invokable_functions.extend(access.start_import.function); invokable_functions.extend(access.import_status.function); @@ -613,15 +634,17 @@ impl<'a> ForgeModules<'a> { let invokable = invokable_functions.contains(func.key); // this checks whether the funton being scanned is being used in an admin module. Rn it only checks for jira_admin page module. // optionally: compass:adminPage could also be considered. - let admin = jira_admin_page - .iter() - .any(|admin_function| admin_function.function == func.key) - || compass_admin_page.iter().any(|admin_function| { - let Some(string) = admin_function.function else { - return false; - }; - return string == func.key; - }); + let admin = jira_admin_page.iter().any(|admin_function| { + let Some(string) = admin_function.common_keys.function else { + return false; + }; + return string == func.key; + }) || compass_admin_page.iter().any(|admin_function| { + let Some(string) = admin_function.function else { + return false; + }; + return string == func.key; + }); Ok::<_, Error>(Entrypoint { function: FunctionRef::try_from(func)?, @@ -631,6 +654,11 @@ impl<'a> ForgeModules<'a> { }) }) } + + pub fn add_optional(self, optional: Option>, iter: &mut BTreeSet<&str>) { + let Some(func) = optional; + iter.extend(func.function); + } } impl FunctionRef<'_, S> { @@ -845,11 +873,13 @@ mod tests { if let Some(string) = resolver.function { assert_eq!(string, "Catch-me-if-you-can1"); } - if let Some(func) = manifest.modules.macros[0].config.function { + if let Some(justfunc) = manifest.modules.macros[0].config { + let Some(func) = justfunc.function; assert_eq!(func, "Catch-me-if-you-can2"); } - if let Some(func) = manifest.modules.macros[0].export.function { + if let Some(justfunc) = manifest.modules.macros[0].export { + let Some(func) = justfunc.function; assert_eq!(func, "Catch-me-if-you-can3"); } } From 3012f13b127ea0c49397fc5416cc155621cccef1 Mon Sep 17 00:00:00 2001 From: Alina Wang Date: Tue, 6 Feb 2024 18:07:11 -0500 Subject: [PATCH 516/517] resolve: uadded JSM modules with functions. And updated into_analyzable_functions to destructure self as refs and handle optional properties in modules that could hold functions. TODO: resolve trait requirement error. --- crates/forge_loader/src/manifest.rs | 174 ++++++++++++++++++++-------- 1 file changed, 127 insertions(+), 47 deletions(-) diff --git a/crates/forge_loader/src/manifest.rs b/crates/forge_loader/src/manifest.rs index b0ef8773..cf694d0a 100644 --- a/crates/forge_loader/src/manifest.rs +++ b/crates/forge_loader/src/manifest.rs @@ -20,7 +20,7 @@ struct AuthProviders<'a> { // Abstracting away key, function, and resolver into a single struct for reuse whoo! // And helper functions for ease of use -#[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize, Copy)] +#[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] struct CommonKey<'a> { key: &'a str, function: Option<&'a str>, @@ -41,7 +41,7 @@ impl<'a> CommonKey<'a> { } } } -#[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize, Copy)] +#[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] pub struct Resolver<'a> { pub function: Option<&'a str>, pub method: Option<&'a str>, @@ -50,7 +50,7 @@ pub struct Resolver<'a> { // Implementing a struct for structs with 1 value (function) -#[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize, Copy)] +#[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] pub struct JustFunc<'a> { pub function: Option<&'a str>, } @@ -151,7 +151,7 @@ struct ContentByLineItem<'a> { } #[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] -struct MacroMod<'a> { +pub struct MacroMod<'a> { #[serde(flatten, borrow)] common_keys: CommonKey<'a>, config: Option>, @@ -216,7 +216,7 @@ pub struct WorkflowPostFunction<'a> { common_keys: CommonKey<'a>, } // Jira Service Management Modules -#[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize, Copy)] +#[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] #[serde(rename_all = "camelCase")] struct AssetsImportType<'a> { #[serde(flatten, borrow)] @@ -313,6 +313,41 @@ pub struct ForgeModules<'a> { // Jira Service Management Modules #[serde(rename = "jiraServiceManagement:assetsImportType", default, borrow)] assets_import_type: Vec>, + #[serde(rename = "jiraServiceManagement:organizationPanel", default, borrow)] + org_panel: Vec>, + #[serde(rename = "jiraServiceManagement:portalFooter", default, borrow)] + portal_footer: Vec>, + #[serde(rename = "jiraServiceManagement:portalHeader", default, borrow)] + portal_header: Vec>, + #[serde(rename = "jiraServiceManagement:portalProfilePanel", default, borrow)] + portal_profile_panel: Vec>, + #[serde( + rename = "jiraServiceManagement:portalRequestCreatePropertyPanel", + default, + borrow + )] + portal_req: Vec>, + #[serde(rename = "jiraServiceManagement:portalRequestDetail", default, borrow)] + portal_request_detail: Vec>, + #[serde( + rename = "jiraServiceManagement:portalRequestDetailPanel", + default, + borrow + )] + portal_request_detail_panel: Vec>, + #[serde( + rename = "jiraServiceManagement:portalRequestViewAction", + default, + borrow + )] + portal_request_view_action: Vec>, + #[serde(rename = "jiraServiceManagement:portalSubheader", default, borrow)] + portal_subheader: Vec>, + #[serde(rename = "jiraServiceManagement:portalUserMenuAction", default, borrow)] + portal_header_menu_action: Vec>, + #[serde(rename = "jiraServiceManagement:queuePage", default, borrow)] + queue_page: Vec>, + // deserializing admin pages #[serde(flatten)] extra: FxHashMap>>, @@ -401,41 +436,52 @@ impl<'a> ForgeModules<'a> { // destructuring ForgeModules to remember to add new modules to this let Self { mut webtriggers, - custom_field, + ref custom_field, consumers: _, - functions, + ref functions, event_triggers: _, scheduled_triggers: _, - compass_admin_page, - component_page, - compass_global_page, - team_page, - content_action, - content_by_line_item, - context_menu, - confluence_global_page, - homepage_feed, - space_page, - space_settings, - macros, - jira_admin_page, - custom_field_type, - dashboard_background_script, - dashboard_gadget, - jira_global_page, - issue_action, - issue_context, - issue_glance, - issue_panel, - issue_view_background_script, - jql_function, - project_page, - project_settings_page, - ui_modifications, - workflow_validator, - workflow_post_function, - assets_import_type, + ref compass_admin_page, + ref component_page, + ref compass_global_page, + ref team_page, + ref content_action, + ref content_by_line_item, + ref context_menu, + ref confluence_global_page, + ref homepage_feed, + ref space_page, + ref space_settings, + ref macros, + ref jira_admin_page, + ref custom_field_type, + ref dashboard_background_script, + ref dashboard_gadget, + ref jira_global_page, + ref issue_action, + ref issue_context, + ref issue_glance, + ref issue_panel, + ref issue_view_background_script, + ref jql_function, + ref project_page, + ref project_settings_page, + ref ui_modifications, + ref workflow_validator, + ref workflow_post_function, + ref assets_import_type, extra: _, + ref org_panel, + ref portal_footer, + ref portal_header, + ref portal_profile_panel, + ref portal_req, + ref portal_request_detail, + ref portal_request_detail_panel, + ref portal_request_view_action, + ref portal_subheader, + ref queue_page, + ref portal_header_menu_action, } = self; // number of webtriggers are usually low, so it's better to just sort them and reuse @@ -494,23 +540,19 @@ impl<'a> ForgeModules<'a> { .iter() .for_each(|space_settings| space_settings.append_functions(&mut invokable_functions)); - macros.iter().for_each(|mac| { - mac.common_keys.append_functions(&mut invokable_functions); + macros.clone().iter().for_each(|mac| { self.clone() .add_optional(mac.config, &mut invokable_functions); self.clone() .add_optional(mac.export, &mut invokable_functions); + mac.common_keys.append_functions(&mut invokable_functions); }); // Jira module functons custom_field.iter().for_each(|customfield| { - // let Some(func) = customfield.value; - // invokable_functions.extend(func.function); self.clone() .add_optional(customfield.value, &mut invokable_functions); - // let Some(func) = customfield.edit; - // invokable_functions.extend(func.function); self.clone() .add_optional(customfield.edit, &mut invokable_functions); @@ -560,8 +602,6 @@ impl<'a> ForgeModules<'a> { .for_each(|issue| issue.append_functions(&mut invokable_functions)); issue_context.iter().for_each(|issue| { - // let Some(func) = issue.dynamic_properties; - // invokable_functions.extend(func.function); self.clone() .add_optional(issue.dynamic_properties, &mut invokable_functions); @@ -570,8 +610,6 @@ impl<'a> ForgeModules<'a> { issue_glance.iter().for_each(|issue| { issue.common_keys.append_functions(&mut invokable_functions); - // let Some(func) = issue.dynamic_properties; - // invokable_functions.extend(func.function); self.clone() .add_optional(issue.dynamic_properties, &mut invokable_functions); }); @@ -626,6 +664,48 @@ impl<'a> ForgeModules<'a> { invokable_functions.extend(access.start_import.function); invokable_functions.extend(access.import_status.function); }); + org_panel + .iter() + .for_each(|panel| panel.append_functions(&mut invokable_functions)); + + portal_footer + .iter() + .for_each(|footer| footer.append_functions(&mut invokable_functions)); + + portal_header + .iter() + .for_each(|header| header.append_functions(&mut invokable_functions)); + + portal_profile_panel + .iter() + .for_each(|profile| profile.append_functions(&mut invokable_functions)); + + portal_req + .iter() + .for_each(|req| req.append_functions(&mut invokable_functions)); + + portal_request_detail + .iter() + .for_each(|req| req.append_functions(&mut invokable_functions)); + + portal_request_detail_panel + .iter() + .for_each(|req| req.append_functions(&mut invokable_functions)); + + portal_request_view_action + .iter() + .for_each(|req| req.append_functions(&mut invokable_functions)); + + portal_subheader + .iter() + .for_each(|subheader| subheader.append_functions(&mut invokable_functions)); + portal_header_menu_action + .iter() + .for_each(|action| action.append_functions(&mut invokable_functions)); + + queue_page + .iter() + .for_each(|page| page.append_functions(&mut invokable_functions)); functions.into_iter().flat_map(move |func| { let web_trigger = webtriggers @@ -700,7 +780,7 @@ impl<'a, Resolved> FunctionRef<'a, Resolved> { } } -impl<'a> TryFrom> for FunctionRef<'a> { +impl<'a> TryFrom> for &'a FunctionRef<'a> { type Error = Error; fn try_from(func_handler: FunctionMod<'a>) -> Result { From 85ac8903a1d5fe03ce2943ebce61cd579bbc60a0 Mon Sep 17 00:00:00 2001 From: Alina Wang Date: Wed, 7 Feb 2024 15:55:13 -0500 Subject: [PATCH 517/517] feat: new trait implemented to append functions. Updated into_analyzable_functions to use trait. --- crates/forge_loader/src/manifest.rs | 391 +++++++++++++--------------- 1 file changed, 177 insertions(+), 214 deletions(-) diff --git a/crates/forge_loader/src/manifest.rs b/crates/forge_loader/src/manifest.rs index cf694d0a..b5e08e98 100644 --- a/crates/forge_loader/src/manifest.rs +++ b/crates/forge_loader/src/manifest.rs @@ -20,14 +20,18 @@ struct AuthProviders<'a> { // Abstracting away key, function, and resolver into a single struct for reuse whoo! // And helper functions for ease of use -#[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] +#[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize, Copy)] struct CommonKey<'a> { key: &'a str, function: Option<&'a str>, resolver: Option>, } -impl<'a> CommonKey<'a> { +trait HasFunctions<'a> { + fn append_functions>(&self, funcs: &mut I); +} + +impl<'a> HasFunctions<'a> for CommonKey<'a> { fn append_functions>(&self, funcs: &mut I) { funcs.extend(self.function); @@ -41,7 +45,25 @@ impl<'a> CommonKey<'a> { } } } -#[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] + +impl<'a> HasFunctions<'a> for JustFunc<'a> { + fn append_functions>(&self, funcs: &mut I) { + funcs.extend(self.function); + } +} + +impl<'a, I, E: HasFunctions<'a>> HasFunctions<'a> for I +where + for<'c> &'c I: IntoIterator, +{ + fn append_functions>(&self, funcs: &mut B) { + // iterating over &I + for e in self { + e.append_functions(funcs); + } + } +} +#[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize, Copy)] pub struct Resolver<'a> { pub function: Option<&'a str>, pub method: Option<&'a str>, @@ -50,7 +72,7 @@ pub struct Resolver<'a> { // Implementing a struct for structs with 1 value (function) -#[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] +#[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize, Copy)] pub struct JustFunc<'a> { pub function: Option<&'a str>, } @@ -90,7 +112,7 @@ struct ScheduledTrigger<'a> { } // Maps to Web Trigger under Common Modules -#[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] +#[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize, Copy)] struct RawTrigger<'a> { key: &'a str, function: &'a str, @@ -150,7 +172,7 @@ struct ContentByLineItem<'a> { dynamic_properties: JustFunc<'a>, } -#[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize)] +#[derive(Default, Debug, Clone, PartialEq, Eq, Deserialize, Copy)] pub struct MacroMod<'a> { #[serde(flatten, borrow)] common_keys: CommonKey<'a>, @@ -159,7 +181,7 @@ pub struct MacroMod<'a> { } // Jira Modules -#[derive(Debug, Clone, PartialEq, Eq, Deserialize)] +#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Copy)] pub struct JiraAdminPage<'a> { title: &'a str, #[serde(flatten, borrow)] @@ -436,52 +458,52 @@ impl<'a> ForgeModules<'a> { // destructuring ForgeModules to remember to add new modules to this let Self { mut webtriggers, - ref custom_field, + custom_field, consumers: _, - ref functions, + functions, event_triggers: _, scheduled_triggers: _, - ref compass_admin_page, - ref component_page, - ref compass_global_page, - ref team_page, - ref content_action, - ref content_by_line_item, - ref context_menu, - ref confluence_global_page, - ref homepage_feed, - ref space_page, - ref space_settings, - ref macros, - ref jira_admin_page, - ref custom_field_type, - ref dashboard_background_script, - ref dashboard_gadget, - ref jira_global_page, - ref issue_action, - ref issue_context, - ref issue_glance, - ref issue_panel, - ref issue_view_background_script, - ref jql_function, - ref project_page, - ref project_settings_page, - ref ui_modifications, - ref workflow_validator, - ref workflow_post_function, - ref assets_import_type, + compass_admin_page, + component_page, + compass_global_page, + team_page, + content_action, + content_by_line_item, + context_menu, + confluence_global_page, + homepage_feed, + space_page, + space_settings, + macros, + jira_admin_page, + custom_field_type, + dashboard_background_script, + dashboard_gadget, + jira_global_page, + issue_action, + issue_context, + issue_glance, + issue_panel, + issue_view_background_script, + jql_function, + project_page, + project_settings_page, + ui_modifications, + workflow_validator, + workflow_post_function, + assets_import_type, extra: _, - ref org_panel, - ref portal_footer, - ref portal_header, - ref portal_profile_panel, - ref portal_req, - ref portal_request_detail, - ref portal_request_detail_panel, - ref portal_request_view_action, - ref portal_subheader, - ref queue_page, - ref portal_header_menu_action, + org_panel, + portal_footer, + portal_header, + portal_profile_panel, + portal_req, + portal_request_detail, + portal_request_detail_panel, + portal_request_view_action, + portal_subheader, + queue_page, + portal_header_menu_action, } = self; // number of webtriggers are usually low, so it's better to just sort them and reuse @@ -490,21 +512,12 @@ impl<'a> ForgeModules<'a> { let mut invokable_functions = BTreeSet::new(); // Compass Module Functions - compass_admin_page - .iter() - .for_each(|compass_admin| compass_admin.append_functions(&mut invokable_functions)); + compass_admin_page.append_functions(&mut invokable_functions); - component_page - .iter() - .for_each(|component_page| component_page.append_functions(&mut invokable_functions)); - - compass_global_page - .iter() - .for_each(|global_page| global_page.append_functions(&mut invokable_functions)); + component_page.append_functions(&mut invokable_functions); - team_page - .iter() - .for_each(|team_page| team_page.append_functions(&mut invokable_functions)); + compass_global_page.append_functions(&mut invokable_functions); + team_page.append_functions(&mut invokable_functions); // Confluence Module Functions // get user invokable modules that have additional exposure endpoints. @@ -520,206 +533,157 @@ impl<'a> ForgeModules<'a> { invokable_functions.extend(by_line_item.dynamic_properties.function) }); - context_menu - .iter() - .for_each(|context_menu| context_menu.append_functions(&mut invokable_functions)); + context_menu.append_functions(&mut invokable_functions); - confluence_global_page - .iter() - .for_each(|global_page| global_page.append_functions(&mut invokable_functions)); + confluence_global_page.append_functions(&mut invokable_functions); - homepage_feed - .iter() - .for_each(|homepage_feed| homepage_feed.append_functions(&mut invokable_functions)); + homepage_feed.append_functions(&mut invokable_functions); - space_page - .iter() - .for_each(|space_page| space_page.append_functions(&mut invokable_functions)); + space_page.append_functions(&mut invokable_functions); - space_settings - .iter() - .for_each(|space_settings| space_settings.append_functions(&mut invokable_functions)); - - macros.clone().iter().for_each(|mac| { - self.clone() - .add_optional(mac.config, &mut invokable_functions); - self.clone() - .add_optional(mac.export, &mut invokable_functions); - mac.common_keys.append_functions(&mut invokable_functions); - }); + space_settings.append_functions(&mut invokable_functions); - // Jira module functons - custom_field.iter().for_each(|customfield| { - self.clone() - .add_optional(customfield.value, &mut invokable_functions); + for m in macros { + m.common_keys.append_functions(&mut invokable_functions); + m.config.append_functions(&mut invokable_functions); + m.export.append_functions(&mut invokable_functions); + } - self.clone() - .add_optional(customfield.edit, &mut invokable_functions); + // Jira module functons + custom_field.into_iter().for_each(|customfield| { + customfield.value.append_functions(&mut invokable_functions); + customfield.value.append_functions(&mut invokable_functions); customfield .common_keys .append_functions(&mut invokable_functions); }); - custom_field_type.iter().for_each(|custom_field_type| { - // invokable_functions.extend(custom_field_type.value); - self.clone() - .add_optional(custom_field_type.value, &mut invokable_functions); - - // invokable_functions.extend(custom_field_type.edit); - self.clone() - .add_optional(custom_field_type.edit, &mut invokable_functions); + custom_field_type.into_iter().for_each(|custom_field_type| { + custom_field_type + .common_keys + .append_functions(&mut invokable_functions); - // invokable_functions.extend(custom_field_type.context_config); - self.clone() - .add_optional(custom_field_type.context_config, &mut invokable_functions); + custom_field_type + .edit + .append_functions(&mut invokable_functions); + custom_field_type + .context_config + .append_functions(&mut invokable_functions); custom_field_type - .common_keys + .value .append_functions(&mut invokable_functions); }); - dashboard_background_script - .iter() - .for_each(|dbs| dbs.append_functions(&mut invokable_functions)); - - dashboard_gadget.iter().for_each(|gadget| { - // invokable_functions.extend(gadget.edit); - self.clone() - .add_optional(gadget.edit, &mut invokable_functions); + dashboard_background_script.append_functions(&mut invokable_functions); + for gadget in dashboard_gadget { gadget .common_keys - .append_functions(&mut invokable_functions) - }); + .append_functions(&mut invokable_functions); + gadget.edit.append_functions(&mut invokable_functions); + } - jira_global_page - .iter() - .for_each(|global_page| global_page.append_functions(&mut invokable_functions)); + jira_global_page.append_functions(&mut invokable_functions); - issue_action - .iter() - .for_each(|issue| issue.append_functions(&mut invokable_functions)); + issue_action.append_functions(&mut invokable_functions); - issue_context.iter().for_each(|issue| { - self.clone() - .add_optional(issue.dynamic_properties, &mut invokable_functions); - - issue.common_keys.append_functions(&mut invokable_functions) - }); + for issue in issue_context { + issue.common_keys.append_functions(&mut invokable_functions); + issue + .dynamic_properties + .append_functions(&mut invokable_functions); + } - issue_glance.iter().for_each(|issue| { + for issue in issue_glance { issue.common_keys.append_functions(&mut invokable_functions); - self.clone() - .add_optional(issue.dynamic_properties, &mut invokable_functions); - }); + issue + .dynamic_properties + .append_functions(&mut invokable_functions); + } - issue_panel - .iter() - .for_each(|issue| issue.append_functions(&mut invokable_functions)); + issue_panel.append_functions(&mut invokable_functions); - issue_view_background_script - .iter() - .for_each(|issue| issue.append_functions(&mut invokable_functions)); + issue_view_background_script.append_functions(&mut invokable_functions); - jql_function - .iter() - .for_each(|item| item.append_functions(&mut invokable_functions)); + jql_function.append_functions(&mut invokable_functions); - project_page - .iter() - .for_each(|item| item.append_functions(&mut invokable_functions)); + project_page.append_functions(&mut invokable_functions); - project_settings_page - .iter() - .for_each(|item| item.append_functions(&mut invokable_functions)); + project_settings_page.append_functions(&mut invokable_functions); - ui_modifications.iter().for_each(|ui| { + for ui in ui_modifications { ui.common_keys.append_functions(&mut invokable_functions); - }); + } - workflow_validator.iter().for_each(|validator| { - validator - .common_keys - .append_functions(&mut invokable_functions) - }); + for valid in workflow_validator { + valid.common_keys.append_functions(&mut invokable_functions); + } - workflow_post_function.iter().for_each(|post_function| { - post_function - .common_keys - .append_functions(&mut invokable_functions); - }); + for post in workflow_post_function { + post.common_keys.append_functions(&mut invokable_functions); + } // JSM modules - assets_import_type.iter().for_each(|access| { - access + for assets in assets_import_type { + assets .common_keys .append_functions(&mut invokable_functions); - // let Some(func) = access.on_delete_import; - // invokable_functions.extend(func.function); - self.clone() - .add_optional(access.on_delete_import, &mut invokable_functions); - - invokable_functions.extend(access.stop_import.function); - invokable_functions.extend(access.start_import.function); - invokable_functions.extend(access.import_status.function); - }); + + assets + .on_delete_import + .append_functions(&mut invokable_functions); + + assets + .stop_import + .append_functions(&mut invokable_functions); + + assets + .start_import + .append_functions(&mut invokable_functions); + + assets + .import_status + .append_functions(&mut invokable_functions); + } org_panel .iter() .for_each(|panel| panel.append_functions(&mut invokable_functions)); - portal_footer - .iter() - .for_each(|footer| footer.append_functions(&mut invokable_functions)); + org_panel.append_functions(&mut invokable_functions); - portal_header - .iter() - .for_each(|header| header.append_functions(&mut invokable_functions)); + portal_footer.append_functions(&mut invokable_functions); + portal_header.append_functions(&mut invokable_functions); + portal_profile_panel.append_functions(&mut invokable_functions); - portal_profile_panel - .iter() - .for_each(|profile| profile.append_functions(&mut invokable_functions)); + portal_req.append_functions(&mut invokable_functions); - portal_req - .iter() - .for_each(|req| req.append_functions(&mut invokable_functions)); + portal_request_detail.append_functions(&mut invokable_functions); - portal_request_detail - .iter() - .for_each(|req| req.append_functions(&mut invokable_functions)); + portal_request_detail_panel.append_functions(&mut invokable_functions); - portal_request_detail_panel - .iter() - .for_each(|req| req.append_functions(&mut invokable_functions)); + portal_request_view_action.append_functions(&mut invokable_functions); - portal_request_view_action - .iter() - .for_each(|req| req.append_functions(&mut invokable_functions)); + portal_subheader.append_functions(&mut invokable_functions); - portal_subheader - .iter() - .for_each(|subheader| subheader.append_functions(&mut invokable_functions)); - portal_header_menu_action - .iter() - .for_each(|action| action.append_functions(&mut invokable_functions)); + portal_header_menu_action.append_functions(&mut invokable_functions); - queue_page - .iter() - .for_each(|page| page.append_functions(&mut invokable_functions)); + queue_page.append_functions(&mut invokable_functions); - functions.into_iter().flat_map(move |func| { + functions.clone().into_iter().flat_map(move |func| { let web_trigger = webtriggers .binary_search_by_key(&func.key, |trigger| &trigger.function) .is_ok(); let invokable = invokable_functions.contains(func.key); // this checks whether the funton being scanned is being used in an admin module. Rn it only checks for jira_admin page module. // optionally: compass:adminPage could also be considered. - let admin = jira_admin_page.iter().any(|admin_function| { + let admin = jira_admin_page.clone().iter().any(|admin_function| { let Some(string) = admin_function.common_keys.function else { return false; }; return string == func.key; - }) || compass_admin_page.iter().any(|admin_function| { + }) || compass_admin_page.clone().iter().any(|admin_function| { let Some(string) = admin_function.function else { return false; }; @@ -734,11 +698,6 @@ impl<'a> ForgeModules<'a> { }) }) } - - pub fn add_optional(self, optional: Option>, iter: &mut BTreeSet<&str>) { - let Some(func) = optional; - iter.extend(func.function); - } } impl FunctionRef<'_, S> { @@ -780,7 +739,7 @@ impl<'a, Resolved> FunctionRef<'a, Resolved> { } } -impl<'a> TryFrom> for &'a FunctionRef<'a> { +impl<'a> TryFrom> for FunctionRef<'a> { type Error = Error; fn try_from(func_handler: FunctionMod<'a>) -> Result { @@ -877,7 +836,7 @@ mod tests { auth: vec!["my-auth-provider"], }), }; - let func_ref: FunctionRef = func_handler.try_into().unwrap(); + let func_ref: FunctionRef = FunctionRef::try_from(func_handler).unwrap(); assert_eq!( func_ref, FunctionRef { @@ -903,11 +862,15 @@ mod tests { "key": "my-macro", "title": "My Macro", "function": "Catch-me-if-you-can0", - "resolver": [ - "function": "Catch-me-if-you-can1", - ] - "config": "Catch-me-if-you-can2", - "export": "Catch-me-if-you-can3" + "resolver": { + "function": "Catch-me-if-you-can1" + }, + "config": { + "function": "Catch-me-if-you-can2" + }, + "export": { + "function": "Catch-me-if-you-can3" + } } ], "function": [ @@ -954,12 +917,12 @@ mod tests { assert_eq!(string, "Catch-me-if-you-can1"); } if let Some(justfunc) = manifest.modules.macros[0].config { - let Some(func) = justfunc.function; + let func = justfunc.function.unwrap(); assert_eq!(func, "Catch-me-if-you-can2"); } if let Some(justfunc) = manifest.modules.macros[0].export { - let Some(func) = justfunc.function; + let func = justfunc.function.unwrap(); assert_eq!(func, "Catch-me-if-you-can3"); } }