From 3992b5aa3fb5ad5c4c4f9b02ca934945cb37b590 Mon Sep 17 00:00:00 2001 From: 0xSwapFeeder Date: Mon, 5 Feb 2024 11:41:38 -0500 Subject: [PATCH] chore(solidity/core/slither): cleaned slither impl --- .../core/crates/slither-server/src/error.rs | 2 +- .../core/crates/slither-server/src/main.rs | 152 +++++---------- .../core/crates/slither-server/src/slither.rs | 177 ++++-------------- .../core/crates/slither-server/src/types.rs | 47 ++++- .../core/crates/slither-server/src/utils.rs | 98 ++++++++++ 5 files changed, 222 insertions(+), 254 deletions(-) create mode 100644 toolchains/solidity/core/crates/slither-server/src/utils.rs diff --git a/toolchains/solidity/core/crates/slither-server/src/error.rs b/toolchains/solidity/core/crates/slither-server/src/error.rs index 7887e3c0..dfadabd3 100644 --- a/toolchains/solidity/core/crates/slither-server/src/error.rs +++ b/toolchains/solidity/core/crates/slither-server/src/error.rs @@ -3,7 +3,7 @@ use thiserror::Error; #[derive(Error, Debug)] pub enum SlitherError { #[error("Error while runing slither")] - Unknown, + Unknown(String), #[error("Error while runing the slither command: {0}")] IoCommandError(#[from] std::io::Error), #[error("Error while parsing slither output: {0}")] diff --git a/toolchains/solidity/core/crates/slither-server/src/main.rs b/toolchains/solidity/core/crates/slither-server/src/main.rs index a5895909..83d0ae9d 100644 --- a/toolchains/solidity/core/crates/slither-server/src/main.rs +++ b/toolchains/solidity/core/crates/slither-server/src/main.rs @@ -1,42 +1,22 @@ mod error; mod slither; mod types; +mod utils; + +use crate::{error::SlitherError, slither::parse_slither_out, types::*}; + use std::sync::Arc; -use std::thread::sleep; -use std::time::Duration; use std::vec; - -use crate::error::SlitherError; -use crate::slither::*; -use tokio::process::{Child, Command}; -use tokio::sync::mpsc::{Receiver, Sender}; use tokio::sync::Mutex; use tokio_util::sync::CancellationToken; use tower_lsp::jsonrpc::Result; use tower_lsp::lsp_types::*; use tower_lsp::{Client, LanguageServer, LspService, Server}; - -#[derive(Debug)] -struct SlitherDiag { - diagnostics: Vec, - uri: Url, -} - -impl SlitherDiag { - fn new(uri: Url, diagnostics: Vec) -> Self { - Self { uri, diagnostics } - } -} - -#[derive(Debug)] -struct SlitherData { - slither_processes: Vec, - receiver: Option>, - sender: Sender, - libs_paths: Vec, - src_paths: Vec, - tests_paths: Vec, -} +use utils::find_foundry_toml_config; +use utils::is_slither_installed; +use utils::is_solc_installed; +use utils::normalize_slither_path; +use utils::parse_foundry_toml; #[derive(Debug)] struct Backend { @@ -45,20 +25,6 @@ struct Backend { join_handle: Arc>>>, } -impl SlitherData { - fn new() -> Self { - let (sender, receiver) = tokio::sync::mpsc::channel::(100); - Self { - libs_paths: vec![], - src_paths: vec![], - tests_paths: vec![], - slither_processes: vec![], - receiver: Some(receiver), - sender, - } - } -} - #[tower_lsp::async_trait] impl LanguageServer for Backend { async fn initialize(&self, params: InitializeParams) -> Result { @@ -88,11 +54,11 @@ impl LanguageServer for Backend { } self.client - .log_message(MessageType::INFO, "diag recv initializing ...") + .log_message(MessageType::INFO, "Initializing diagnostic receiver ...") .await; + let mut receiver = self.data.lock().await.receiver.take().unwrap(); let client = self.client.clone(); - self.join_handle .lock() .await @@ -104,7 +70,7 @@ impl LanguageServer for Backend { } })); self.client - .log_message(MessageType::INFO, "diag recv initialized!") + .log_message(MessageType::INFO, "Finished initializing diagnostic receiver!") .await; let folders = params.workspace_folders; @@ -165,22 +131,21 @@ impl LanguageServer for Backend { ), ) .await; - if self.is_in_libs(file.text_document.uri.path()).await - // || self.is_in_tests(file.text_document.uri.path()).await - // || !self.is_in_src(file.text_document.uri.path()).await - { - self.client - .log_message( - MessageType::INFO, - format!( - "File '{}' is not a source solidity code file, skipping analysis.", - file.text_document.uri.path() - ), - ) - .await; - return; - } - self.check_slither_result(file.text_document.uri).await + self.analyze_file(file.text_document.uri).await + + } + + async fn did_open(&self, file: DidOpenTextDocumentParams) { + self.client + .log_message( + MessageType::INFO, + format!( + "Opened file '{}' for analyzing.", + file.text_document.uri.path() + ), + ) + .await; + self.analyze_file(file.text_document.uri).await } } @@ -193,6 +158,25 @@ impl Backend { } } + async fn analyze_file(&self, file: Url) { + if self.is_in_libs(file.path()).await + || self.is_in_tests(file.path()).await + || !self.is_in_src(file.path()).await + { + self.client + .log_message( + MessageType::INFO, + format!( + "File '{}' is not a source solidity code file, skipping analysis.", + file.path() + ), + ) + .await; + return; + } + self.launch_slither(file).await + } + async fn is_in_libs(&self, path: &str) -> bool { let state = self.data.lock().await; for lib in state.libs_paths.iter() { @@ -243,46 +227,7 @@ impl Backend { let foundry = std::fs::read_to_string(path.clone()); match foundry { Ok(foundry) => { - let foundry: toml::Value = foundry.parse().unwrap(); - let libs = foundry["profile"]["default"]["libs"].as_array(); - match libs { - Some(libs) => { - for lib in libs { - state.libs_paths.push(lib.to_string()); - } - } - None => { - state - .libs_paths - .push(foundry["profile"]["default"]["libs"].to_string()); - } - } - let src = foundry["profile"]["default"]["src"].as_array(); - match src { - Some(src) => { - for src in src { - state.src_paths.push(src.to_string()); - } - } - None => { - state - .src_paths - .push(foundry["profile"]["default"]["src"].to_string()); - } - } - let tests = foundry["profile"]["default"]["test"].as_array(); - match tests { - Some(tests) => { - for test in tests { - state.tests_paths.push(test.to_string()); - } - } - None => { - state - .tests_paths - .push(foundry["profile"]["default"]["test"].to_string()); - } - } + parse_foundry_toml(foundry, &mut state); } Err(e) => { eprintln!( @@ -292,14 +237,13 @@ impl Backend { } } } - Err(_) => {} } } Ok(()) } - async fn check_slither_result(&self, uri: Url) { + async fn launch_slither(&self, uri: Url) { let token = CancellationToken::new(); let clone = token.clone(); self.data.lock().await.slither_processes.push(token); diff --git a/toolchains/solidity/core/crates/slither-server/src/slither.rs b/toolchains/solidity/core/crates/slither-server/src/slither.rs index e6c0f2da..384a9cbe 100644 --- a/toolchains/solidity/core/crates/slither-server/src/slither.rs +++ b/toolchains/solidity/core/crates/slither-server/src/slither.rs @@ -1,167 +1,54 @@ -use crate::error::SlitherError; -use crate::types::SlitherResult; -use std::{error::Error, future::Future, process::Stdio}; -use tokio::{io::{AsyncBufReadExt, AsyncReadExt}, process::{Command, Child}}; -use std::process::Command as StdCommand; +use crate::{error::SlitherError, utils::normalize_slither_path, types::SlitherResult}; +use std::process::Stdio; +use tokio::{io::AsyncReadExt, process::Command}; use tower_lsp::lsp_types::Diagnostic; -use glob::glob; -pub fn is_slither_installed() -> bool { - let output = StdCommand::new("slither").arg("--version").output(); - output.is_ok() -} - -pub fn is_solc_installed() -> bool { - let output = StdCommand::new("solc").arg("--version").output(); - output.is_ok() -} - -#[cfg(target_family = "windows")] -pub fn normalize_slither_path(path: &str) -> String { - let mut path = path.replace("%3A/", "://"); - path.remove(0); - path.to_string() -} - -#[cfg(not(target_family = "windows"))] -pub fn normalize_slither_path(path: &str) -> String { - path.to_string() -} - -struct SlitherProcess { - child: std::process::Child, -} - -impl SlitherProcess { - pub fn create(uri: &str) -> Result { - Ok(Self { - child: - StdCommand::new("slither") - .arg(normalize_slither_path(uri)) - .arg("--exclude") - .arg("naming-convention") - .arg("--json") - .arg("-").spawn()? - }) - } -} - -impl Future for SlitherProcess { - type Output = Result; - - fn poll(mut self: std::pin::Pin<&mut Self>, cx: &mut std::task::Context<'_>) -> std::task::Poll { - match self.child.try_wait() { - Ok(Some(status)) => { - if status.success() { - std::task::Poll::Ready(Ok("".to_string())) - } else { - std::task::Poll::Ready(Err(SlitherError::Unknown)) - } - } - Ok(None) => std::task::Poll::Pending, - Err(e) => std::task::Poll::Ready(Err(SlitherError::IoCommandError(e))), - } - } -} pub async fn parse_slither_out(uri: &str) -> Result, SlitherError> { let mut results: Vec = Vec::new(); - /*let proc = Box::pin(SlitherProcess::create(uri)); - match proc.await { - Ok(out) => { - let json: SlitherResult = - serde_json::from_str(&out.replace("\\\"", "\""))?; - for detector in json.results.detectors { - results.append(&mut crate::types::diag_from_json(detector.clone())); - } - Ok(results) - } - Err(e) => Err(e), - }*/ - /* eprintln!("SLITHER STARTING"); - let output = StdCommand::new("slither") - .arg(normalize_slither_path(uri)) - .arg("--exclude") - .arg("naming-convention") - .arg("--json") - .arg("-").output(); + let mut output = exec_slither(uri)?; + let out = match output.stdout.take() { + Some(out) => out, + None => return Err(SlitherError::Unknown("Failed to get slither output pipe".to_string())), + }; + + let mut buffer = tokio::io::BufReader::new(out); + let mut dst = String::new(); + + output.wait().await?; eprintln!("SLITHER FINISHED"); - match output { - Ok(output) => { - let out_str = String::from_utf8_lossy(&output.stdout).to_string(); - if out_str.is_empty() { - eprintln!("SLITHER EMPTY OUT: {}", String::from_utf8_lossy(&output.stderr).to_string()); - return Ok(results); - } - let json: SlitherResult = - serde_json::from_str(&out_str)?; + + buffer.read_to_string(&mut dst).await?; + let json: Result = + serde_json::from_str(&dst); + + match json { + Ok(json) => { for detector in json.results.detectors { results.append(&mut crate::types::diag_from_json(detector.clone())); } } Err(e) => { - eprintln!("SLITHER ERROR: {:?}", e); + eprintln!("Error parsing slither output: {}", e); + return Err(SlitherError::ParsingFailed(e)); } - }*/ + } + + Ok(results) +} - let mut output = Command::new("slither") + +fn exec_slither(uri: &str) -> Result { + Command::new("slither") .arg(normalize_slither_path(uri)) .arg("--exclude") .arg("naming-convention") .arg("--json") .arg("-") .stdout(Stdio::piped()) - .stderr(Stdio::piped()) // Redirect stderr to stdout - .stdin(Stdio::null()) // Provide an empty input - .spawn().unwrap(); - - let out = output.stdout.take().unwrap(); - eprintln!("SLITHER STARTING"); - - let mut buffer = tokio::io::BufReader::new(out); - let mut dst = String::new(); - eprintln!("SLITHER WAITING"); - output.wait().await?; - eprintln!("SLITHER FINISHED"); - buffer.read_to_string(&mut dst).await.unwrap(); - - let json: SlitherResult = - serde_json::from_str(&dst).expect("Failed to parse slither output"); - for detector in json.results.detectors { - results.append(&mut crate::types::diag_from_json(detector.clone())); - } - eprintln!("SLITHER out: {:?}", results); - - Ok(results) -} - -pub fn exec_slither(filepath: &str) -> Result { - Command::new("slither") - .arg(normalize_slither_path(filepath)) - .arg("--exclude") - .arg("naming-convention") - .arg("--json") - .arg("-").spawn() -} - -/** - * Find the foundry.toml config file in the given workspace using glob. - */ -pub fn find_foundry_toml_config(workspace: &str) -> Result> { - let mut foundry_toml_path = String::new(); - for entry in glob(&format!("{}/**/foundry.toml", workspace))? { - match entry { - Ok(path) => { - foundry_toml_path = path.display().to_string(); - break; - } - Err(e) => eprintln!("{:?}", e), - } - } - if foundry_toml_path.is_empty() { - return Err(Box::new(SlitherError::FoundryTomlNotFound)); - } - Ok(foundry_toml_path) + .stderr(Stdio::piped()) + .stdin(Stdio::null()) + .spawn() } diff --git a/toolchains/solidity/core/crates/slither-server/src/types.rs b/toolchains/solidity/core/crates/slither-server/src/types.rs index 0486f555..9f5ad778 100644 --- a/toolchains/solidity/core/crates/slither-server/src/types.rs +++ b/toolchains/solidity/core/crates/slither-server/src/types.rs @@ -1,6 +1,49 @@ use serde::{Deserialize, Serialize}; use tower_lsp::lsp_types::{Diagnostic, DiagnosticSeverity as Severity, Position, Range}; +use std::vec; +use tokio::sync::mpsc::{Receiver, Sender}; +use tokio_util::sync::CancellationToken; +use tower_lsp::lsp_types::*; +#[derive(Debug)] +pub struct SlitherDiag { + pub diagnostics: Vec, + pub uri: Url, +} + +impl SlitherDiag { + pub fn new(uri: Url, diagnostics: Vec) -> Self { + Self { uri, diagnostics } + } +} + +#[derive(Debug)] +pub struct SlitherData { + pub slither_processes: Vec, + pub receiver: Option>, + pub sender: Sender, + pub libs_paths: Vec, + pub src_paths: Vec, + pub tests_paths: Vec, +} + +impl SlitherData { + pub fn new() -> Self { + let (sender, receiver) = tokio::sync::mpsc::channel::(100); + Self { + libs_paths: vec![], + src_paths: vec![], + tests_paths: vec![], + slither_processes: vec![], + receiver: Some(receiver), + sender, + } + } +} + +///////////////////////// +// SLITHER JSON OUTPUT // +///////////////////////// #[derive(Clone, Serialize, Deserialize, Debug)] pub struct SlitherResult { pub results: SlitherResults, @@ -119,7 +162,3 @@ pub fn diag_from_json(json: SlitherDetector) -> Vec { results } - -//////////////////////////////////////////////////////////// -/////////////////// RELATED TYPES: ///////////////////////// -//////////////////////////////////////////////////////////// diff --git a/toolchains/solidity/core/crates/slither-server/src/utils.rs b/toolchains/solidity/core/crates/slither-server/src/utils.rs new file mode 100644 index 00000000..0a187531 --- /dev/null +++ b/toolchains/solidity/core/crates/slither-server/src/utils.rs @@ -0,0 +1,98 @@ +use crate::error::SlitherError; +use crate::SlitherData; +use std::error::Error; +use std::process::Command as StdCommand; +use glob::glob; + + +pub fn is_slither_installed() -> bool { + let output = StdCommand::new("slither").arg("--version").output(); + output.is_ok() +} + +pub fn is_solc_installed() -> bool { + let output = StdCommand::new("solc").arg("--version").output(); + output.is_ok() +} + +#[cfg(target_family = "windows")] +pub fn normalize_slither_path(path: &str) -> String { + let mut path = path.replace("%3A/", "://"); + path.remove(0); + path.to_string() +} + +#[cfg(not(target_family = "windows"))] +pub fn normalize_slither_path(path: &str) -> String { + path.to_string() +} + +pub fn parse_foundry_toml(foundry: String, state: &mut SlitherData) { + let foundry: toml::Value = match foundry.parse() { + Ok(foundry) => foundry, + Err(e) => { + eprintln!("Error parsing foundry.toml: {}", e); + return; + } + }; + + let libs = foundry["profile"]["default"]["libs"].as_array(); + match libs { + Some(libs) => { + for lib in libs { + state.libs_paths.push(lib.to_string()); + } + } + None => { + state + .libs_paths + .push(foundry["profile"]["default"]["libs"].to_string()); + } + } + let src = foundry["profile"]["default"]["src"].as_array(); + match src { + Some(src) => { + for src in src { + state.src_paths.push(src.to_string()); + } + } + None => { + state + .src_paths + .push(foundry["profile"]["default"]["src"].to_string()); + } + } + let tests = foundry["profile"]["default"]["test"].as_array(); + match tests { + Some(tests) => { + for test in tests { + state.tests_paths.push(test.to_string()); + } + } + None => { + state + .tests_paths + .push(foundry["profile"]["default"]["test"].to_string()); + } + } +} + +/** + * Find the foundry.toml config file in the given workspace using glob. + */ +pub fn find_foundry_toml_config(workspace: &str) -> Result> { + let mut foundry_toml_path = String::new(); + for entry in glob(&format!("{}/**/foundry.toml", workspace))? { + match entry { + Ok(path) => { + foundry_toml_path = path.display().to_string(); + break; + } + Err(e) => eprintln!("{:?}", e), + } + } + if foundry_toml_path.is_empty() { + return Err(Box::new(SlitherError::FoundryTomlNotFound)); + } + Ok(foundry_toml_path) +}