Skip to content

Commit 3496266

Browse files
authored
Merge pull request #215 from rust-secure-code/proc-macros
Record proc macros as build dependencies
2 parents 7f9ff85 + b93469c commit 3496266

File tree

9 files changed

+162
-16
lines changed

9 files changed

+162
-16
lines changed

auditable-cyclonedx/src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -124,7 +124,7 @@ fn purl(pkg: &auditable_serde::Package) -> String {
124124
Source::Local => "&download_url=redacted",
125125
Source::Registry => "&repository_url=redacted",
126126
Source::Other(_) => "&download_url=redacted",
127-
unknown => panic!("Unknown source: {:?}", unknown),
127+
unknown => panic!("Unknown source: {unknown:?}"),
128128
});
129129
purl
130130
}

cargo-auditable/src/auditable_from_metadata.rs

Lines changed: 35 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,12 @@
11
//! Converts from `cargo_metadata` crate structs to `auditable-serde` structs,
22
//! which map to our own serialialized representation.
33
4-
use std::{cmp::min, cmp::Ordering::*, collections::HashMap, error::Error, fmt::Display};
4+
use std::{
5+
cmp::{min, Ordering::*},
6+
collections::{HashMap, HashSet},
7+
error::Error,
8+
fmt::Display,
9+
};
510

611
use auditable_serde::{DependencyKind, Package, Source, VersionInfo};
712

@@ -84,6 +89,8 @@ pub fn encode_audit_data(
8489
.repr
8590
.as_str();
8691

92+
let proc_macros = proc_macro_packages(metadata);
93+
8794
// Walk the dependency tree and resolve dependency kinds for each package.
8895
// We need this because there may be several different paths to the same package
8996
// and we need to aggregate dependency types across all of them.
@@ -103,8 +110,14 @@ pub fn encode_audit_data(
103110
let parent_dep_kind = id_to_dep_kind[parent.id.repr.as_str()];
104111
for child in &parent.deps {
105112
let child_id = child.pkg.repr.as_str();
106-
let dep_kind = strongest_dep_kind(child.dep_kinds.as_slice());
107-
let dep_kind = min(dep_kind, parent_dep_kind);
113+
let mut dep_kind = strongest_dep_kind(child.dep_kinds.as_slice());
114+
// If the parent is a build dependency that has a runtime dependency, overall dependency should be 'build'.
115+
// This propagates the dependency kinds that way from parent to child.
116+
dep_kind = min(dep_kind, parent_dep_kind);
117+
// proc macros require special handling since cargo_metadata reports them as normal deps
118+
if proc_macros.contains(child_id) {
119+
dep_kind = min(dep_kind, PrivateDepKind::Build);
120+
}
108121
let dep_kind_on_previous_visit = id_to_dep_kind.get(child_id);
109122
if dep_kind_on_previous_visit.is_none()
110123
|| &dep_kind > dep_kind_on_previous_visit.unwrap()
@@ -215,6 +228,25 @@ fn strongest_dep_kind(deps: &[cargo_metadata::DepKindInfo]) -> PrivateDepKind {
215228
.unwrap_or(PrivateDepKind::Runtime) // for compatibility with Rust earlier than 1.41
216229
}
217230

231+
fn proc_macro_packages(metadata: &cargo_metadata::Metadata) -> HashSet<&str> {
232+
metadata
233+
.packages
234+
.iter()
235+
.filter_map(|pkg| {
236+
// As of Rust 1.88 a single crate cannot be both a proc macro and something else.
237+
// Checking that length is 1 is purely to hedge against support for it being added in the future.
238+
if pkg.targets.len() == 1
239+
&& pkg.targets[0].kind.len() == 1
240+
&& pkg.targets[0].kind[0] == "proc-macro"
241+
{
242+
Some(pkg.id.repr.as_str())
243+
} else {
244+
None
245+
}
246+
})
247+
.collect()
248+
}
249+
218250
#[cfg(test)]
219251
mod tests {
220252
#![allow(unused_imports)] // otherwise conditional compilation emits warnings

cargo-auditable/src/rustc_wrapper.rs

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -93,9 +93,8 @@ pub fn main(rustc_path: &OsStr) {
9393
command_with_args.extend(command.get_args());
9494
eprintln!(
9595
"Failed to invoke rustc! Make sure it's in your $PATH\n\
96-
The error was: {}\n\
97-
The attempted call was: {:?}",
98-
err, command_with_args,
96+
The error was: {err}\n\
97+
The attempted call was: {command_with_args:?}",
9998
);
10099
std::process::exit(1);
101100
});

cargo-auditable/tests/fixtures/proc-macro-dependency/Cargo.lock

Lines changed: 65 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
[package]
2+
name = "proc-macro-dependency"
3+
version = "0.1.0"
4+
edition = "2024"
5+
6+
[dependencies]
7+
serde = { version = "1.0.219", features = ["derive"] }
8+
9+
[workspace]
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
use serde::{Deserialize, Serialize};
2+
3+
fn main() {
4+
println!("{:?}", Hello("Hello, world!"));
5+
}
6+
7+
#[derive(Serialize, Deserialize, Debug)]
8+
struct Hello (&'static str);

cargo-auditable/tests/it.rs

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -552,3 +552,36 @@ fn test_path_not_equal_name_inner(sbom: bool) {
552552
.iter()
553553
.any(|p| p.name == "baz" && p.kind == DependencyKind::Runtime));
554554
}
555+
556+
#[test]
557+
fn test_proc_macro() {
558+
test_proc_macro_inner(false);
559+
test_proc_macro_inner(true);
560+
}
561+
fn test_proc_macro_inner(sbom: bool) {
562+
// Path to workspace fixture Cargo.toml. See that file for overview of workspace members and their dependencies.
563+
let workspace_cargo_toml = PathBuf::from(env!("CARGO_MANIFEST_DIR"))
564+
.join("tests/fixtures/proc-macro-dependency/Cargo.toml");
565+
// Run in workspace root with default features
566+
let bins = run_cargo_auditable(workspace_cargo_toml, &[], &[], sbom);
567+
eprintln!("Proc macro binary map: {bins:?}");
568+
569+
// proc-macro-dependency should depend on
570+
let binary = &bins.get("proc-macro-dependency").unwrap()[0];
571+
let dep_info = get_dependency_info(binary);
572+
eprintln!("{binary} dependency info: {dep_info:?}");
573+
// locate the serde_derive proc macro package
574+
let serde_derive_info = dep_info
575+
.packages
576+
.iter()
577+
.find(|p| p.name == "serde_derive")
578+
.expect("Could not find 'serde_derive' in the embedded dependency list!");
579+
assert_eq!(serde_derive_info.kind, DependencyKind::Build);
580+
// locate the syn package which is norm a dependency of serde-derive
581+
let syn_info = dep_info
582+
.packages
583+
.iter()
584+
.find(|p| p.name == "syn")
585+
.expect("Could not find 'syn' in the embedded dependency list!");
586+
assert_eq!(syn_info.kind, DependencyKind::Build);
587+
}

resolverver/src/error.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,10 @@ impl std::fmt::Display for UnrecognizedValue {
1010
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1111
match self {
1212
UnrecognizedValue::UnknownResolver(resolver) => {
13-
write!(f, "Unrecognized resolver version: {}", resolver)
13+
write!(f, "Unrecognized resolver version: {resolver}")
1414
}
1515
UnrecognizedValue::UnknownEdition(edition) => {
16-
write!(f, "Unrecognized Rust edition: {}", edition)
16+
write!(f, "Unrecognized Rust edition: {edition}")
1717
}
1818
}
1919
}
@@ -30,8 +30,8 @@ pub enum Error {
3030
impl fmt::Display for Error {
3131
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
3232
match self {
33-
Error::UnrecognizedValue(msg) => write!(f, "{}", msg),
34-
Error::TomlParseError(err) => write!(f, "Failed to parse Cargo.toml: {}", err),
33+
Error::UnrecognizedValue(msg) => write!(f, "{msg}"),
34+
Error::TomlParseError(err) => write!(f, "Failed to parse Cargo.toml: {err}"),
3535
}
3636
}
3737
}

resolverver/src/raw_fields.rs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,7 @@ package.edition = \"2021\"
9595
}),
9696
};
9797

98-
let parsed: RawTomlFields = toml::from_str(&toml).unwrap();
98+
let parsed: RawTomlFields = toml::from_str(toml).unwrap();
9999
assert_eq!(parsed, expected);
100100

101101
let resolved_expected = TomlFields {
@@ -132,7 +132,7 @@ resolver = \"2\"
132132
}),
133133
};
134134

135-
let parsed: RawTomlFields = toml::from_str(&toml).unwrap();
135+
let parsed: RawTomlFields = toml::from_str(toml).unwrap();
136136
assert_eq!(parsed, expected);
137137

138138
let resolved_expected = TomlFields {
@@ -160,7 +160,7 @@ edition = \"2021\"
160160
workspace: None,
161161
};
162162

163-
let parsed: RawTomlFields = toml::from_str(&toml).unwrap();
163+
let parsed: RawTomlFields = toml::from_str(toml).unwrap();
164164
assert_eq!(parsed, expected);
165165
}
166166

@@ -180,7 +180,7 @@ version = \"0.1.0\"
180180
workspace: None,
181181
};
182182

183-
let parsed: RawTomlFields = toml::from_str(&toml).unwrap();
183+
let parsed: RawTomlFields = toml::from_str(toml).unwrap();
184184
assert_eq!(parsed, expected);
185185

186186
let resolved_expected = TomlFields {
@@ -206,7 +206,7 @@ members = [\"some-package\"]
206206
}),
207207
};
208208

209-
let parsed: RawTomlFields = toml::from_str(&toml).unwrap();
209+
let parsed: RawTomlFields = toml::from_str(toml).unwrap();
210210
assert_eq!(parsed, expected);
211211

212212
let resolved_expected = TomlFields {

0 commit comments

Comments
 (0)