diff --git a/Cargo.lock b/Cargo.lock index bd90b2a..27e33e4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -98,6 +98,16 @@ version = "1.0.81" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0952808a6c2afd1aa8947271f3a60f1a6763c7b912d210184c5149b5cf147247" +[[package]] +name = "ariadne" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "367fd0ad87307588d087544707bc5fbf4805ded96c7db922b70d368fa1cb5702" +dependencies = [ + "unicode-width", + "yansi", +] + [[package]] name = "async-trait" version = "0.1.78" @@ -173,9 +183,9 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "clap" -version = "4.5.3" +version = "4.5.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "949626d00e063efc93b6dca932419ceb5432f99769911c0b995f7e884c778813" +checksum = "0fbb260a053428790f3de475e304ff84cdbc4face759ea7a3e64c1edd938a7fc" dependencies = [ "clap_builder", "clap_derive", @@ -183,21 +193,22 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.2" +version = "4.5.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae129e2e766ae0ec03484e609954119f123cc1fe650337e155d03b022f24f7b4" +checksum = "64b17d7ea74e9f833c7dbf2cbe4fb12ff26783eda4782a8975b72f895c9b4d99" dependencies = [ "anstream", "anstyle", "clap_lex", "strsim", + "terminal_size", ] [[package]] name = "clap_derive" -version = "4.5.3" +version = "4.5.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90239a040c80f5e14809ca132ddc4176ab33d5e17e49691793296e3fcb34d72f" +checksum = "501d359d5f3dcaf6ecdeee48833ae73ec6e42723a1e52419c79abf9507eec0a0" dependencies = [ "heck", "proc-macro2", @@ -550,6 +561,23 @@ version = "2.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "523dc4f511e55ab87b694dc30d0f820d60906ef06413f93d4d7a1385599cc149" +[[package]] +name = "metaslang_cst" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c36707c2ce9d5aaed8704762e8fdb08bcafaa5c69c9a9c891a9b1c19d1953b2" +dependencies = [ + "nom", + "serde", + "thiserror", +] + +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + [[package]] name = "miniz_oxide" version = "0.7.2" @@ -570,6 +598,16 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "nom" +version = "7.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +dependencies = [ + "memchr", + "minimal-lexical", +] + [[package]] name = "num_cpus" version = "1.16.0" @@ -620,7 +658,10 @@ dependencies = [ "osmium-libs-solidity-foundry-wrapper", "proc-macro2", "quote", + "regex", + "semver", "serde_json", + "slang_solidity", "solc-ast-rs-types", "syn", "syn-solidity", @@ -762,9 +803,9 @@ dependencies = [ [[package]] name = "regex" -version = "1.10.4" +version = "1.10.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c117dbdfde9c8308975b6a18d71f3f385c89461f7b3fb054288ecf2a2058ba4c" +checksum = "4219d74c6b67a3654a9fbebc4b419e22126d13d2f3c4a07ee0cb61ff79a79619" dependencies = [ "aho-corasick", "memchr", @@ -818,6 +859,12 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "rustversion" +version = "1.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "955d28af4278de8121b7ebeb796b6a45735dc01436d898801014aced2773a3d6" + [[package]] name = "ryu" version = "1.0.17" @@ -830,20 +877,29 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" +[[package]] +name = "semver" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b" +dependencies = [ + "serde", +] + [[package]] name = "serde" -version = "1.0.197" +version = "1.0.204" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fb1c873e1b9b056a4dc4c0c198b24c3ffa059243875552b2bd0933b1aee4ce2" +checksum = "bc76f558e0cbb2a839d37354c575f1dc3fdc6546b5be373ba43d95f231bf7c12" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.197" +version = "1.0.204" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7eb0b34b42edc17f6b7cac84a52a1c5f0e1bb2227e997ca9011ea3dd34e8610b" +checksum = "e0cd7e117be63d3c3678776753929474f3b04a43a080c744d6b0ae2a8c28e222" dependencies = [ "proc-macro2", "quote", @@ -852,11 +908,13 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.114" +version = "1.0.122" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c5f09b1bd632ef549eaa9f60a1f8de742bdbc698e6cee2095fc84dde5f549ae0" +checksum = "784b6203951c57ff748476b126ccb5e8e2959a5c19e5c617ab1956be3dbc68da" dependencies = [ + "indexmap", "itoa", + "memchr", "ryu", "serde", ] @@ -899,6 +957,23 @@ dependencies = [ "autocfg", ] +[[package]] +name = "slang_solidity" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d318186df1097c99b22daa6592b6abe75c48beabe7721a7d65b9b331bd8c058" +dependencies = [ + "ariadne", + "clap", + "metaslang_cst", + "semver", + "serde", + "serde_json", + "strum", + "strum_macros", + "thiserror", +] + [[package]] name = "slither-server" version = "0.1.0" @@ -966,6 +1041,25 @@ version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5ee073c9e4cd00e28217186dbe12796d692868f432bf2e97ee73bed0c56dfa01" +[[package]] +name = "strum" +version = "0.26.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fec0f0aef304996cf250b31b5a10dee7980c85da9d759361292b8bca5a18f06" + +[[package]] +name = "strum_macros" +version = "0.26.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c6bee85a5a24955dc440386795aa378cd9cf82acd5f764469152d2270e581be" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "rustversion", + "syn", +] + [[package]] name = "syn" version = "2.0.53" @@ -989,6 +1083,16 @@ dependencies = [ "syn", ] +[[package]] +name = "terminal_size" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21bebf2b7c9e0a515f6e0f8c51dc0f8e4696391e6f1ff30379559f8365fb0df7" +dependencies = [ + "rustix", + "windows-sys 0.48.0", +] + [[package]] name = "tests-positions-server" version = "0.1.0" @@ -1005,18 +1109,18 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.58" +version = "1.0.63" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03468839009160513471e86a034bb2c5c0e4baae3b43f79ffc55c4a5427b3297" +checksum = "c0342370b38b6a11b6cc11d6a805569958d54cfa061a29969c3b5ce2ea405724" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.58" +version = "1.0.63" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c61f3ba182994efc43764a46c018c347bc492c79f024e705f46567b418f6d4f7" +checksum = "a4558b58466b9ad7ca0f102865eccc95938dca1a74a856f2b57b6629050da261" dependencies = [ "proc-macro2", "quote", @@ -1228,6 +1332,12 @@ dependencies = [ "tinyvec", ] +[[package]] +name = "unicode-width" +version = "0.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0336d538f7abc86d282a4189614dfaa90810dfc2c6f6427eaf88e16311dd225d" + [[package]] name = "url" version = "2.5.0" @@ -1412,6 +1522,12 @@ dependencies = [ "memchr", ] +[[package]] +name = "yansi" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09041cd90cf85f7f8b2df60c646f853b7f535ce68f85244eb6731cf89fa498ec" + [[package]] name = "zerocopy" version = "0.7.32" diff --git a/libs/ast-extractor/Cargo.toml b/libs/ast-extractor/Cargo.toml index 188cb65..4ce24b1 100644 --- a/libs/ast-extractor/Cargo.toml +++ b/libs/ast-extractor/Cargo.toml @@ -18,3 +18,6 @@ osmium-libs-solidity-foundry-wrapper = { path = "../foundry-wrapper"} solc-ast-rs-types = { version = "0.1.6" } serde_json = "1.0.113" log = "0.4.21" +slang_solidity = "0.16.0" +semver = "1.0.23" +regex = "1.10.6" diff --git a/libs/ast-extractor/src/errors.rs b/libs/ast-extractor/src/errors.rs index 7bf8cc0..1441278 100644 --- a/libs/ast-extractor/src/errors.rs +++ b/libs/ast-extractor/src/errors.rs @@ -4,6 +4,8 @@ use thiserror::Error; pub enum ExtractError { #[error("Alloy extraction error: {0}")] Alloy(String), + #[error("Cannot read file version")] + ReadVersion, #[error("Compiler error: {0}")] Compiler(#[from] osmium_libs_solidity_foundry_wrapper::Error), #[error("Cannot read source file")] diff --git a/libs/ast-extractor/src/extract.rs b/libs/ast-extractor/src/extract.rs index 6fa45c2..07f6989 100644 --- a/libs/ast-extractor/src/extract.rs +++ b/libs/ast-extractor/src/extract.rs @@ -3,98 +3,8 @@ * Extract AST from solidity source code * author: 0xMemoryGrinder */ -use crate::errors::ExtractError; -use crate::types::*; -use log::error; -use osmium_libs_solidity_foundry_wrapper::{Compiler, FoundryJsonFile}; -use proc_macro2::TokenStream; -use solc_ast_rs_types::types::SourceUnit; -use std::str::FromStr; - -pub fn extract_ast_from_content(content: &str) -> Result { - let tokens = TokenStream::from_str(content).map_err(|e| ExtractError::Alloy(e.to_string()))?; - let ast = syn_solidity::parse2(tokens).map_err(|e| ExtractError::Alloy(e.to_string()))?; - Ok(ast) -} - -pub fn extract_ast_from_foundry(base_path: &str) -> Result, ExtractError> { - let mut compiler = Compiler::new_with_executable_check()?; - compiler.load_workspace(base_path.to_string())?; - let (path, files) = compiler.compile_ast(base_path)?; - - get_ast_from_foundry_output(&path, files) -} - -fn get_ast_from_foundry_output( - base_path: &str, - files: Vec, -) -> Result, ExtractError> { - let mut ast_files = Vec::new(); - for file in files { - if file.file.contains("safeconsole.sol") { - continue; - } - let ast: Result = serde_json::from_value(file.clone().json); - if let Err(e) = &ast { - error!( - "Error while parsing json ast in file '{}': {:?}", - file.file, e - ); - continue; - } - let ast = ast.unwrap(); - let out_path = &file.file; - ast_files.push(SolidityAstFile { - file: SolidityFile { - path: out_path.clone(), - content: std::fs::read_to_string(std::path::Path::new(&base_path).join(out_path)) - .map_err(|e| { - error!("Error reading compiled file : {}", e); - ExtractError::ReadSourceFile(e) - })?, - }, - ast, - }); - } - Ok(ast_files) -} - -#[cfg(test)] -mod tests { - use super::*; - use std::fs; - use std::path::PathBuf; - - #[test] - fn test_extract_ast_from_content_good() { - let mut path = PathBuf::from(env!("CARGO_MANIFEST_DIR")); - path.push("tests"); - path.push("files"); - path.push("good.sol"); - let source = fs::read_to_string(path).unwrap(); - let res = extract_ast_from_content(&source); - assert!(res.is_ok()); - } - - #[test] - fn test_extract_ast_from_content_invalid_token() { - let source = String::from("contract test { function test() public | uint a = 1 } }"); - let result = extract_ast_from_content(&source); - assert!(result.is_err()); - assert_eq!( - result.unwrap_err().to_string(), - "Alloy extraction error: cannot parse string into token stream" - ); - } - - #[test] - fn test_extract_ast_from_content_missing_semicolumn() { - let source = String::from("contract test { function test() public { uint a = 1 } }"); - let result = extract_ast_from_content(&source); - assert!(result.is_err()); - assert_eq!( - result.unwrap_err().to_string(), - "Alloy extraction error: expected `;`" - ); - } -} +mod foundry; +pub use foundry::extract_ast_from_foundry; +mod slang; +pub use slang::extract_ast_from_content; +pub use slang::extract_ast; diff --git a/libs/ast-extractor/src/extract/foundry.rs b/libs/ast-extractor/src/extract/foundry.rs new file mode 100644 index 0000000..20a703d --- /dev/null +++ b/libs/ast-extractor/src/extract/foundry.rs @@ -0,0 +1,48 @@ +use log::error; +use osmium_libs_solidity_foundry_wrapper::{Compiler, FoundryJsonFile}; +use solc_ast_rs_types::types::SourceUnit; +use crate::errors::ExtractError; +use crate::types::*; + + +pub fn extract_ast_from_foundry(base_path: &str) -> Result, ExtractError> { + let mut compiler = Compiler::new_with_executable_check()?; + compiler.load_workspace(base_path.to_string())?; + let (path, files) = compiler.compile_ast(base_path)?; + + get_ast_from_foundry_output(&path, files) +} + +fn get_ast_from_foundry_output( + base_path: &str, + files: Vec, +) -> Result, ExtractError> { + let mut ast_files = Vec::new(); + for file in files { + if file.file.contains("safeconsole.sol") { + continue; + } + let ast: Result = serde_json::from_value(file.clone().json); + if let Err(e) = &ast { + error!( + "Error while parsing json ast in file '{}': {:?}", + file.file, e + ); + continue; + } + let ast = ast.unwrap(); + let out_path = &file.file; + ast_files.push(SolidityAstFile { + file: SolidityFile { + path: out_path.clone(), + content: std::fs::read_to_string(std::path::Path::new(&base_path).join(out_path)) + .map_err(|e| { + error!("Error reading compiled file : {}", e); + ExtractError::ReadSourceFile(e) + })?, + }, + ast, + }); + } + Ok(ast_files) +} \ No newline at end of file diff --git a/libs/ast-extractor/src/extract/slang.rs b/libs/ast-extractor/src/extract/slang.rs new file mode 100644 index 0000000..eca131a --- /dev/null +++ b/libs/ast-extractor/src/extract/slang.rs @@ -0,0 +1,60 @@ +use crate::errors::ExtractError; +use semver::Version; +use slang_solidity::parse_output::ParseOutput; +use slang_solidity::kinds::NonterminalKind; +use slang_solidity::language::Language; +use std::fs; +use std::path::PathBuf; + +pub fn extract_ast_from_content(content: &str) -> Result { + let pragma_version_regex = regex::Regex::new(r"pragma solidity (\^)?(\d+\.\d+\.\d+);").map_err(|_| ExtractError::ReadVersion)?; + let version: String = pragma_version_regex + .captures(content) + .map(|c| c.get(2).unwrap().as_str().to_string()) + .unwrap_or_else(|| "0.8.0".to_string()); + let language = Language::new(Version::parse(&version).map_err(|_| ExtractError::ReadVersion)?).map_err(|_| ExtractError::ReadVersion)?; + let parse_output = language.parse(NonterminalKind::SourceUnit, content); + + Ok(parse_output) +} + +pub fn extract_ast(filepath: impl Into) -> Result { + let path = filepath.into(); + let content = fs::read_to_string(&path).map_err(ExtractError::ReadSourceFile)?; + extract_ast_from_content(&content) +} + +#[cfg(test)] +mod tests { + use super::*; + use std::fs; + use std::path::PathBuf; + + #[test] + fn test_extract_ast_from_content_good() { + let mut path = PathBuf::from(env!("CARGO_MANIFEST_DIR")); + path.push("tests"); + path.push("files"); + path.push("good.sol"); + let source = fs::read_to_string(path).unwrap(); + let res = extract_ast_from_content(&source); + assert!(res.is_ok()); + } + + #[test] + fn test_extract_ast_from_content_invalid_token() { + let source = String::from("pragma solidity 0.8.0; contract test { function test() public | uint a = 1 } }"); + let result = extract_ast_from_content(&source); + assert!(result.is_ok()); + assert!(result.unwrap().errors().len() > 0); + } + + #[test] + fn test_extract_ast_from_content_missing_semicolumn() { + let source = String::from("pragma solidity 0.8.0; contract test { function test() public { uint a = 1 } }"); + let result = extract_ast_from_content(&source); + eprintln!("{:?}", result); + assert!(result.is_ok()); + assert!(result.unwrap().errors().len() > 0); + } +} \ No newline at end of file diff --git a/libs/ast-extractor/src/lib.rs b/libs/ast-extractor/src/lib.rs index 426f3e2..6bfa929 100644 --- a/libs/ast-extractor/src/lib.rs +++ b/libs/ast-extractor/src/lib.rs @@ -3,8 +3,9 @@ pub mod extract; pub mod retriever; pub mod types; -// Expose syn_solidity crate -pub use syn_solidity::*; +// Expose slang_solidity crate +pub use slang_solidity::*; +pub use slang_solidity::parse_output::ParseOutput as Output; // Publish span location type pub use proc_macro2::LineColumn; diff --git a/libs/ast-extractor/src/retriever/contract.rs b/libs/ast-extractor/src/retriever/contract.rs index c24575f..27fbd67 100644 --- a/libs/ast-extractor/src/retriever/contract.rs +++ b/libs/ast-extractor/src/retriever/contract.rs @@ -3,10 +3,10 @@ * Function to retrieve contract nodes from AST * author: 0xMemoryGrinder */ -use syn_solidity::{ItemContract, Visit}; +use slang_solidity::cst::Node; struct ContractVisitor { - contracts: Vec, + contracts: Vec<>, } impl ContractVisitor { @@ -17,12 +17,6 @@ impl ContractVisitor { } } -impl<'ast> Visit<'ast> for ContractVisitor { - fn visit_item_contract(&mut self, i: &ItemContract) { - self.contracts.push(i.clone()); - syn_solidity::visit::visit_item_contract(self, i); - } -} pub fn retrieve_contract_nodes(ast: &syn_solidity::File) -> Vec { let mut visitor = ContractVisitor::new(); diff --git a/libs/solidhunter/src/linter.rs b/libs/solidhunter/src/linter.rs index 5d3eac9..72a3af6 100644 --- a/libs/solidhunter/src/linter.rs +++ b/libs/solidhunter/src/linter.rs @@ -13,7 +13,7 @@ use std::path::Path; #[derive(Debug, Clone)] pub struct SolidFile { - pub data: osmium_libs_solidity_ast_extractor::File, + pub data: osmium_libs_solidity_ast_extractor::Output, pub path: String, pub content: String, } @@ -108,7 +108,7 @@ impl SolidLinter { fn _add_file( &mut self, path: &str, - ast: osmium_libs_solidity_ast_extractor::File, + ast: osmium_libs_solidity_ast_extractor::Output, content: &str, ) { if self._file_exists(path) { diff --git a/remove-me-5f237fe550774478a9c1.txt b/remove-me-5f237fe550774478a9c1.txt new file mode 100644 index 0000000..74d12c8 --- /dev/null +++ b/remove-me-5f237fe550774478a9c1.txt @@ -0,0 +1 @@ +5f237fe550774478a9c1