diff --git a/Cargo.lock b/Cargo.lock index efbed76b..176ddce1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -192,6 +192,7 @@ dependencies = [ "assert_cmd", "better-panic", "cargo", + "cargo-geiger-serde", "cargo-platform", "colored", "console 0.11.3", @@ -213,6 +214,11 @@ dependencies = [ [[package]] name = "cargo-geiger-serde" version = "0.1.0" +dependencies = [ + "semver 0.10.0", + "serde", + "url", +] [[package]] name = "cargo-platform" @@ -523,8 +529,8 @@ dependencies = [ name = "geiger" version = "0.4.5" dependencies = [ + "cargo-geiger-serde", "proc-macro2", - "serde", "syn", ] @@ -1448,6 +1454,7 @@ dependencies = [ "idna", "matches", "percent-encoding", + "serde", ] [[package]] diff --git a/cargo-geiger-serde/Cargo.toml b/cargo-geiger-serde/Cargo.toml index 95c5fe4e..d5329c38 100644 --- a/cargo-geiger-serde/Cargo.toml +++ b/cargo-geiger-serde/Cargo.toml @@ -6,6 +6,7 @@ license = "Apache-2.0/MIT" name = "cargo-geiger-serde" version = "0.1.0" -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - [dependencies] +semver = "0.10.0" +serde = { version = "1.0.116", features = ["derive"] } +url = { version = "2.1.1", features = ["serde"] } diff --git a/cargo-geiger-serde/src/lib.rs b/cargo-geiger-serde/src/lib.rs index 31e1bb20..c9d29867 100644 --- a/cargo-geiger-serde/src/lib.rs +++ b/cargo-geiger-serde/src/lib.rs @@ -1,7 +1,19 @@ -#[cfg(test)] -mod tests { - #[test] - fn it_works() { - assert_eq!(2 + 2, 4); - } -} +//! cargo-geiger-serde ☢ +//! ======== +//! +//! This crate provides definitions to serialize the unsafety report. + +#![forbid(unsafe_code)] +#![forbid(warnings)] + +mod package_id; +mod report; +mod source; + +pub use package_id::PackageId; +pub use report::{ + Count, CounterBlock, DependencyKind, PackageInfo, QuickReportEntry, QuickSafetyReport, + ReportEntry, SafetyReport, UnsafeInfo, +}; +pub use source::Source; + diff --git a/cargo-geiger-serde/src/package_id.rs b/cargo-geiger-serde/src/package_id.rs new file mode 100644 index 00000000..9a4ef0c3 --- /dev/null +++ b/cargo-geiger-serde/src/package_id.rs @@ -0,0 +1,14 @@ +use crate::Source; +use semver::Version; +use serde::{Deserialize, Serialize}; + +/// Identifies a package in the dependency tree +#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)] +pub struct PackageId { + /// Package name + pub name: String, + /// Package version + pub version: Version, + /// Package source (e.g. repository, crate registry) + pub source: Source, +} diff --git a/cargo-geiger-serde/src/report.rs b/cargo-geiger-serde/src/report.rs new file mode 100644 index 00000000..5c4c84ad --- /dev/null +++ b/cargo-geiger-serde/src/report.rs @@ -0,0 +1,166 @@ +use crate::PackageId; +use serde::{Deserialize, Serialize}; +use std::{ + ops::{Add, AddAssign}, + path::PathBuf, +}; + +/// Package dependency information +#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)] +pub struct PackageInfo { + pub id: PackageId, + pub dependencies: Vec, + pub dev_dependencies: Vec, + pub build_dependencies: Vec, +} + +impl PackageInfo { + pub fn new(id: PackageId) -> Self { + PackageInfo { + id, + dependencies: Vec::new(), + dev_dependencies: Vec::new(), + build_dependencies: Vec::new(), + } + } + + pub fn push_dependency(&mut self, dep: PackageId, kind: DependencyKind) { + match kind { + DependencyKind::Normal => self.dependencies.push(dep), + DependencyKind::Development => self.dev_dependencies.push(dep), + DependencyKind::Build => self.build_dependencies.push(dep), + } + } +} + +/// Entry of the report generated from scanning for packages that forbid the use of `unsafe` +#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)] +pub struct QuickReportEntry { + pub package: PackageInfo, + /// Whether this package forbids the use of `unsafe` + pub forbids_unsafe: bool, +} + +/// Report generated from scanning for packages that forbid the use of `unsafe` +#[derive(Clone, Debug, Default, Deserialize, PartialEq, Serialize)] +pub struct QuickSafetyReport { + /// Packages that were scanned successfully + pub packages: Vec, + /// Packages that were not scanned successfully + pub packages_without_metrics: Vec, +} + +/// Entry of the report generated from scanning for the use of `unsafe` +#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)] +pub struct ReportEntry { + pub package: PackageInfo, + /// Unsafety scan results + pub unsafety: UnsafeInfo, +} + +/// Report generated from scanning for the use of `unsafe` +#[derive(Clone, Debug, Default, Deserialize, PartialEq, Serialize)] +pub struct SafetyReport { + pub packages: Vec, + pub packages_without_metrics: Vec, + pub used_but_not_scanned_files: Vec, +} + +/// Unsafety usage in a package +#[derive(Clone, Debug, Default, Deserialize, PartialEq, Serialize)] +pub struct UnsafeInfo { + /// Unsafe usage statistics for code used by the project + pub used: CounterBlock, + /// Unsafe usage statistics for code not used by the project + pub unused: CounterBlock, + /// Whether this package forbids the use of `unsafe` + pub forbids_unsafe: bool, +} + +/// Kind of dependency for a package +#[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)] +pub enum DependencyKind { + /// Dependency in the `[dependencies]` section of `Cargo.toml` + Normal, + /// Dependency in the `[dev-dependencies]` section of `Cargo.toml` + Development, + /// Dependency in the `[build-dependencies]` section of `Cargo.toml` + Build, +} + +/// Statistics about the use of `unsafe` +#[derive(Clone, Debug, Default, Deserialize, PartialEq, Serialize)] +pub struct Count { + /// Number of safe items + pub safe: u64, + /// Number of unsafe items + pub unsafe_: u64, +} + +impl Count { + /// Increments the safe or unsafe counter by 1 + pub fn count(&mut self, is_unsafe: bool) { + if is_unsafe { + self.unsafe_ += 1; + } else { + self.safe += 1; + } + } +} + +impl Add for Count { + type Output = Count; + + fn add(self, other: Count) -> Count { + Count { + safe: self.safe + other.safe, + unsafe_: self.unsafe_ + other.unsafe_, + } + } +} + +impl AddAssign for Count { + fn add_assign(&mut self, rhs: Count) { + *self = self.clone() + rhs; + } +} + +/// Unsafe usage metrics collection. +#[derive(Clone, Debug, Default, Deserialize, PartialEq, Serialize)] +pub struct CounterBlock { + pub functions: Count, + pub exprs: Count, + pub item_impls: Count, + pub item_traits: Count, + pub methods: Count, +} + +impl CounterBlock { + pub fn has_unsafe(&self) -> bool { + self.functions.unsafe_ > 0 + || self.exprs.unsafe_ > 0 + || self.item_impls.unsafe_ > 0 + || self.item_traits.unsafe_ > 0 + || self.methods.unsafe_ > 0 + } +} + +impl Add for CounterBlock { + type Output = CounterBlock; + + fn add(self, other: CounterBlock) -> CounterBlock { + CounterBlock { + functions: self.functions + other.functions, + exprs: self.exprs + other.exprs, + item_impls: self.item_impls + other.item_impls, + item_traits: self.item_traits + other.item_traits, + methods: self.methods + other.methods, + } + } +} + +impl AddAssign for CounterBlock { + fn add_assign(&mut self, rhs: Self) { + *self = self.clone() + rhs; + } +} diff --git a/cargo-geiger-serde/src/source.rs b/cargo-geiger-serde/src/source.rs new file mode 100644 index 00000000..ccbf8381 --- /dev/null +++ b/cargo-geiger-serde/src/source.rs @@ -0,0 +1,18 @@ +use serde::{Deserialize, Serialize}; +use url::Url; + +/// Source of a package (where it is fetched from) +#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)] +pub enum Source { + Git { + url: Url, + rev: String, + }, + Registry { + name: String, + url: Url, + }, + Path(Url), + LocalRegistry(Url), + Directory(Url), +} diff --git a/cargo-geiger/Cargo.toml b/cargo-geiger/Cargo.toml index 68f9a171..b39e3bf1 100644 --- a/cargo-geiger/Cargo.toml +++ b/cargo-geiger/Cargo.toml @@ -15,6 +15,7 @@ maintenance = { status = "experimental" } [dependencies] cargo = "0.47.0" +cargo-geiger-serde = { path = "../cargo-geiger-serde", version = "0.1.0" } cargo-platform = "0.1.1" colored = "2.0.0" console = "0.11.3" diff --git a/cargo-geiger/src/format/table.rs b/cargo-geiger/src/format/table.rs index b9109fb5..0c43518a 100644 --- a/cargo-geiger/src/format/table.rs +++ b/cargo-geiger/src/format/table.rs @@ -5,7 +5,7 @@ use crate::scan::{unsafe_stats, GeigerContext}; use crate::tree::TextTreeLine; use cargo::core::package::PackageSet; -use geiger::{Count, CounterBlock}; +use cargo_geiger_serde::{Count, CounterBlock}; use std::collections::HashSet; use std::path::PathBuf; diff --git a/cargo-geiger/src/scan.rs b/cargo-geiger/src/scan.rs index 99432e13..8a65a5d7 100644 --- a/cargo-geiger/src/scan.rs +++ b/cargo-geiger/src/scan.rs @@ -1,7 +1,6 @@ mod default; mod find; mod forbid; -mod report; use crate::args::Args; use crate::format::print::PrintConfig; @@ -10,11 +9,11 @@ use crate::rs_file::RsFileMetricsWrapper; use default::scan_unsafe; use forbid::scan_forbid_unsafe; -use report::{PackageInfo, UnsafeInfo}; use cargo::core::{PackageId, PackageSet, Workspace}; +use cargo::core::dependency::DepKind; use cargo::{CliResult, Config}; -use geiger::CounterBlock; +use cargo_geiger_serde::{CounterBlock, DependencyKind, PackageInfo, UnsafeInfo}; use petgraph::visit::EdgeRef; use std::collections::{HashMap, HashSet}; use std::path::PathBuf; @@ -165,14 +164,14 @@ fn package_metrics<'a>( std::iter::from_fn(move || { let i = indices.pop()?; let id = graph.graph[i].id; - let mut package = PackageInfo::new(id); + let mut package = PackageInfo::new(from_cargo_package_id(id)); for edge in graph.graph.edges(i) { let dep_index = edge.target(); if visited.insert(dep_index) { indices.push(dep_index); } - let dep = graph.graph[dep_index].id; - package.push_dependency(dep, *edge.weight()); + let dep = from_cargo_package_id(graph.graph[dep_index].id); + package.push_dependency(dep, from_cargo_dependency_kind(*edge.weight())); } match geiger_context.pack_id_to_metrics.get(&id) { Some(m) => Some((package, Some(m))), @@ -184,16 +183,49 @@ fn package_metrics<'a>( }) } +fn from_cargo_package_id(id: PackageId) -> cargo_geiger_serde::PackageId { + let source = id.source_id(); + let source_url = source.url().clone(); + let source = if source.is_git() { + cargo_geiger_serde::Source::Git { + url: source_url, + rev: source.precise().expect("Git revision should be known").to_string(), + } + } else if source.is_path() { + cargo_geiger_serde::Source::Path(source_url) + } else if source.is_registry() { + cargo_geiger_serde::Source::Registry { + name: source.display_registry_name(), + url: source_url, + } + } else { + panic!("Unsupported source type: {:?}", source) + }; + cargo_geiger_serde::PackageId { + name: id.name().to_string(), + version: id.version().clone(), + source, + } +} + +fn from_cargo_dependency_kind(kind: DepKind) -> DependencyKind { + match kind { + DepKind::Normal => DependencyKind::Normal, + DepKind::Development => DependencyKind::Development, + DepKind::Build => DependencyKind::Build, + } +} + #[cfg(test)] mod scan_tests { use super::*; use crate::{ rs_file::RsFileMetricsWrapper, - scan::{report::UnsafeInfo, PackageMetrics}, + scan::PackageMetrics, }; - use geiger::Count; + use cargo_geiger_serde::{Count, UnsafeInfo}; use std::{collections::HashSet, path::PathBuf}; #[test] diff --git a/cargo-geiger/src/scan/default.rs b/cargo-geiger/src/scan/default.rs index 2c05ae77..9455e4ba 100644 --- a/cargo-geiger/src/scan/default.rs +++ b/cargo-geiger/src/scan/default.rs @@ -6,7 +6,6 @@ use crate::graph::Graph; use crate::rs_file::resolve_rs_file_deps; use super::find::find_unsafe; -use super::report::{ReportEntry, SafetyReport}; use super::{ list_files_used_but_not_scanned, package_metrics, unsafe_stats, ScanDetails, ScanMode, ScanParameters, @@ -18,6 +17,7 @@ use cargo::core::compiler::CompileMode; use cargo::core::{PackageId, PackageSet, Workspace}; use cargo::ops::CompileOptions; use cargo::{CliError, CliResult, Config}; +use cargo_geiger_serde::{ReportEntry, SafetyReport}; pub fn scan_unsafe( workspace: &Workspace, diff --git a/cargo-geiger/src/scan/forbid.rs b/cargo-geiger/src/scan/forbid.rs index 35eca467..cedde296 100644 --- a/cargo-geiger/src/scan/forbid.rs +++ b/cargo-geiger/src/scan/forbid.rs @@ -4,13 +4,13 @@ use crate::format::print::{OutputFormat, PrintConfig}; use crate::graph::Graph; use super::find::find_unsafe; -use super::report::{QuickReportEntry, QuickSafetyReport}; use super::{package_metrics, ScanMode, ScanParameters}; use table::scan_forbid_to_table; use cargo::core::{PackageId, PackageSet}; use cargo::{CliResult, Config}; +use cargo_geiger_serde::{QuickReportEntry, QuickSafetyReport}; pub fn scan_forbid_unsafe( package_set: &PackageSet, diff --git a/cargo-geiger/src/scan/report.rs b/cargo-geiger/src/scan/report.rs deleted file mode 100644 index be0ed5bb..00000000 --- a/cargo-geiger/src/scan/report.rs +++ /dev/null @@ -1,63 +0,0 @@ -use cargo::core::{dependency::DepKind, PackageId}; -use geiger::CounterBlock; -use serde::{Deserialize, Serialize}; -use std::path::PathBuf; - -#[derive(Clone, Debug, Deserialize, Serialize)] -pub struct PackageInfo { - pub id: PackageId, - pub dependencies: Vec, - pub dev_dependencies: Vec, - pub build_dependencies: Vec, -} - -impl PackageInfo { - pub fn new(id: PackageId) -> Self { - PackageInfo { - id, - dependencies: Vec::new(), - dev_dependencies: Vec::new(), - build_dependencies: Vec::new(), - } - } - - pub fn push_dependency(&mut self, dep: PackageId, kind: DepKind) { - match kind { - DepKind::Normal => self.dependencies.push(dep), - DepKind::Development => self.dev_dependencies.push(dep), - DepKind::Build => self.build_dependencies.push(dep), - } - } -} - -#[derive(Clone, Debug, Deserialize, Serialize)] -pub struct QuickReportEntry { - pub package: PackageInfo, - pub forbids_unsafe: bool, -} - -#[derive(Clone, Debug, Default, Deserialize, Serialize)] -pub struct QuickSafetyReport { - pub packages: Vec, - pub packages_without_metrics: Vec, -} - -#[derive(Clone, Debug, Deserialize, Serialize)] -pub struct ReportEntry { - pub package: PackageInfo, - pub unsafety: UnsafeInfo, -} - -#[derive(Clone, Debug, Default, Deserialize, Serialize)] -pub struct SafetyReport { - pub packages: Vec, - pub packages_without_metrics: Vec, - pub used_but_not_scanned_files: Vec, -} - -#[derive(Clone, Debug, Default, Deserialize, PartialEq, Serialize)] -pub struct UnsafeInfo { - pub used: CounterBlock, - pub unused: CounterBlock, - pub forbids_unsafe: bool, -} diff --git a/geiger/Cargo.toml b/geiger/Cargo.toml index 0bccb401..5bf5f8fd 100644 --- a/geiger/Cargo.toml +++ b/geiger/Cargo.toml @@ -14,6 +14,6 @@ license = "Apache-2.0/MIT" maintenance = { status = "experimental" } [dependencies] -serde = { version = "1.0.116", features = ["derive"] } +cargo-geiger-serde = { path = "../cargo-geiger-serde", version = "0.1.0" } syn = { version = "1.0.34", features = ["parsing", "printing", "clone-impls", "full", "extra-traits", "visit"] } proc-macro2 = "1.0.18" diff --git a/geiger/src/lib.rs b/geiger/src/lib.rs index 5cf6e7a4..46ec9c86 100644 --- a/geiger/src/lib.rs +++ b/geiger/src/lib.rs @@ -7,13 +7,12 @@ #![forbid(unsafe_code)] #![forbid(warnings)] -use serde::{Deserialize, Serialize}; +use cargo_geiger_serde::CounterBlock; use std::error::Error; use std::fmt; use std::fs::File; use std::io; use std::io::Read; -use std::ops::{Add, AddAssign}; use std::path::Path; use std::path::PathBuf; use std::string::FromUtf8Error; @@ -36,76 +35,6 @@ impl fmt::Display for ScanFileError { } } -#[derive(Clone, Debug, Default, Deserialize, PartialEq, Serialize)] -pub struct Count { - /// Number of safe items - pub safe: u64, - - /// Number of unsafe items - pub unsafe_: u64, -} - -impl Count { - fn count(&mut self, is_unsafe: bool) { - if is_unsafe { - self.unsafe_ += 1; - } else { - self.safe += 1; - } - } -} - -impl Add for Count { - type Output = Count; - - fn add(self, other: Count) -> Count { - Count { - safe: self.safe + other.safe, - unsafe_: self.unsafe_ + other.unsafe_, - } - } -} - -/// Unsafe usage metrics collection. -#[derive(Clone, Debug, Default, Deserialize, PartialEq, Serialize)] -pub struct CounterBlock { - pub functions: Count, - pub exprs: Count, - pub item_impls: Count, - pub item_traits: Count, - pub methods: Count, -} - -impl CounterBlock { - pub fn has_unsafe(&self) -> bool { - self.functions.unsafe_ > 0 - || self.exprs.unsafe_ > 0 - || self.item_impls.unsafe_ > 0 - || self.item_traits.unsafe_ > 0 - || self.methods.unsafe_ > 0 - } -} - -impl Add for CounterBlock { - type Output = CounterBlock; - - fn add(self, other: CounterBlock) -> CounterBlock { - CounterBlock { - functions: self.functions + other.functions, - exprs: self.exprs + other.exprs, - item_impls: self.item_impls + other.item_impls, - item_traits: self.item_traits + other.item_traits, - methods: self.methods + other.methods, - } - } -} - -impl AddAssign for CounterBlock { - fn add_assign(&mut self, rhs: Self) { - *self = self.clone() + rhs; - } -} - /// Scan result for a single `.rs` file. #[derive(Debug, Default)] pub struct RsFileMetrics {