diff --git a/detectors/soroban-version/Cargo.toml b/detectors/soroban-version/Cargo.toml index 6acc66e4..60b26923 100644 --- a/detectors/soroban-version/Cargo.toml +++ b/detectors/soroban-version/Cargo.toml @@ -8,10 +8,8 @@ crate-type = ["cdylib"] [dependencies] dylint_linting = { workspace = true } -if_chain = { workspace = true } semver = "1.0.4" serde_json = "1.0" -toml = "0.8.8" ureq = { version = "2.7.1", features = ["json"] } scout-audit-internal = { workspace = true } diff --git a/detectors/soroban-version/src/lib.rs b/detectors/soroban-version/src/lib.rs index acc10d23..f4ba013e 100644 --- a/detectors/soroban-version/src/lib.rs +++ b/detectors/soroban-version/src/lib.rs @@ -4,12 +4,13 @@ extern crate rustc_ast; extern crate rustc_hir; extern crate rustc_span; -use std::{env, fs, io::Error, path::Path}; +use std::{io::Error, process::Command}; use rustc_ast::Crate; use rustc_lint::{EarlyContext, EarlyLintPass, LintContext}; use scout_audit_internal::Detector; -use semver::*; +use semver::Version; +use serde_json::Value; dylint_linting::declare_early_lint! { /// ### What it does @@ -24,7 +25,7 @@ dylint_linting::declare_early_lint! { impl EarlyLintPass for CheckSorobanVersion { fn check_crate(&mut self, cx: &EarlyContext<'_>, _: &Crate) { - let latest_version = match get_version() { + let latest_soroban_version = match get_latest_soroban_version() { Ok(version) => version, Err(e) => { cx.sess() @@ -34,54 +35,70 @@ impl EarlyLintPass for CheckSorobanVersion { } }; - let manifest_dir = match env::var("CARGO_MANIFEST_DIR") { - Ok(dir) => dir, + let cargo_metadata = match get_cargo_metadata() { + Ok(metadata) => metadata, Err(e) => { cx.sess() - .struct_warn(format!( - "Environment variable CARGO_MANIFEST_DIR not found: {}", - e - )) + .struct_warn(format!("Failed to get cargo metadata: {}", e)) .emit(); return; } }; - let cargo_toml_path = Path::new(&manifest_dir).join("Cargo.toml"); - let cargo_toml = match fs::read_to_string(cargo_toml_path) { - Ok(content) => content, - Err(e) => { + let cargo_toml_package_opt = match cargo_metadata["packages"].as_array() { + Some(packages) => packages.first(), + None => { cx.sess() - .struct_warn(format!("Unable to read Cargo.toml: {}", e)) + .struct_warn("Error parsing cargo metadata: packages not found") .emit(); return; } }; - let toml: toml::Value = match toml::from_str(&cargo_toml) { - Ok(value) => value, - Err(e) => { + let cargo_toml_package = match cargo_toml_package_opt { + Some(package) => package, + None => { + cx.sess() + .struct_warn("Error parsing cargo metadata: first package not found") + .emit(); + return; + } + }; + + let dependencies = match cargo_toml_package["dependencies"].as_array() { + Some(dependencies) => dependencies, + None => { cx.sess() - .struct_warn(format!("Error parsing Cargo.toml: {}", e)) + .struct_warn("Error parsing cargo metadata: dependencies not found") .emit(); return; } }; - let soroban_version = match toml - .get("dependencies") - .and_then(|d| d.get("soroban-sdk").and_then(|i| i.get("version"))) + let current_dependency = match dependencies + .iter() + .find(|&dep| dep["name"].as_str().unwrap_or("") == "soroban-sdk") { - Some(version) => version.to_string(), + Some(current_dependency) => current_dependency, None => { cx.sess() - .struct_warn("Soroban dependency not found in Cargo.toml") + .struct_warn("Soroban dependency not found in dependencies") .emit(); return; } }; - let req = match Version::parse(&latest_version.replace('\"', "")) { + let current_dependency_version = match current_dependency["req"].as_str() { + Some(version) => version, + None => { + cx.sess() + .struct_warn("Error parsing current Soroban version") + .emit(); + return; + } + }; + + let req = match Version::parse(&latest_soroban_version.replace('\"', "")) { Ok(version) => version, Err(e) => { cx.sess() @@ -91,7 +108,9 @@ impl EarlyLintPass for CheckSorobanVersion { } }; - let soroban_version = match VersionReq::parse(&soroban_version.replace('\"', "")) { + let cleaned_version = current_dependency_version.replace(&['=', '^'][..], ""); + + let soroban_version = match Version::parse(&cleaned_version) { Ok(version) => version, Err(e) => { cx.sess() @@ -101,18 +120,20 @@ impl EarlyLintPass for CheckSorobanVersion { } }; - if !soroban_version.matches(&req) { + if !soroban_version.eq(&req) { Detector::SorobanVersion.span_lint_and_help( cx, CHECK_SOROBAN_VERSION, rustc_span::DUMMY_SP, - &format!(r#"The latest Soroban version is {latest_version}, and your version is "{soroban_version}""#), + &format!( + r#"The latest Soroban version is {latest_soroban_version}, and your version is "{soroban_version}""# + ), ); } } } -fn get_version() -> Result { +fn get_latest_soroban_version() -> Result { let response = ureq::get("https://crates.io/api/v1/crates/soroban-sdk") .set("User-Agent", "Scout/1.0") .call(); @@ -132,3 +153,16 @@ fn get_version() -> Result { Err(_) => Err("Failed to get Soroban version from crates.io".to_string()), } } + +fn get_cargo_metadata() -> Result { + let output = Command::new("cargo") + .args(["metadata", "--format-version=1", "--no-deps"]) + .output(); + + match output { + Ok(output) if output.status.success() => serde_json::from_slice(&output.stdout) + .map_err(|e| format!("Error parsing cargo metadata: {}", e)), + Ok(output) => Err(String::from_utf8_lossy(&output.stderr).into_owned()), + Err(e) => Err(e.to_string()), + } +} diff --git a/scripts/check-ci-detectors-to-test.py b/scripts/check-ci-detectors-to-test.py index 53abe9d2..b1d4ad59 100644 --- a/scripts/check-ci-detectors-to-test.py +++ b/scripts/check-ci-detectors-to-test.py @@ -13,12 +13,13 @@ def is_special_directory(directory): with open(args.gh_workflow_path, "r") as f: workflow = yaml.safe_load(f) - detectors_to_test = workflow["jobs"]["test"]["strategy"]["matrix"]["test"] + detectors_to_test = set(workflow["jobs"]["test"]["strategy"]["matrix"]["test"]) - detectors = [ f.name for f in os.scandir(args.detectors_path) if f.is_dir() and not is_special_directory(f.name) ] - detectors.sort() + detectors = set(f.name for f in os.scandir(args.detectors_path) if f.is_dir() and not is_special_directory(f.name)) + # detectors.sort() - if (detectors != detectors_to_test): + sym_diff = detectors ^ detectors_to_test + if len(sym_diff)!=0 and sym_diff!=set(['soroban-version']): print("Detectors to test in the workflow are not the same as the detectors in the detectors folder.") print("Detectors to test: ", detectors_to_test) print("Detectors in the folder: ", detectors)