Skip to content

Commit 7af1021

Browse files
authored
Merge pull request #173 from rust-secure-code/private-cargo-metadata
Make `cargo_metadata` private
2 parents 88a2b2d + 782e673 commit 7af1021

File tree

19 files changed

+252
-574
lines changed

19 files changed

+252
-574
lines changed

Cargo.lock

-1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

auditable-serde/Cargo.toml

-6
Original file line numberDiff line numberDiff line change
@@ -13,17 +13,11 @@ all-features = true
1313

1414
[features]
1515
default = []
16-
from_metadata = ["cargo_metadata"]
1716
schema = ["schemars"]
1817

1918
[dependencies]
2019
serde = { version = "1", features = ["serde_derive"] }
2120
serde_json = "1.0.57"
2221
semver = { version = "1.0", features = ["serde"] }
23-
cargo_metadata = { version = "0.18", optional = true }
2422
topological-sort = "0.2.2"
2523
schemars = {version = "0.8.10", optional = true }
26-
27-
[[example]]
28-
name = "from-metadata"
29-
required-features = ["from_metadata"]

auditable-serde/README.md

-2
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,6 @@ Parses and serializes the JSON dependency tree embedded in executables by the
33

44
This crate defines the data structures that a serialized to/from JSON
55
and implements the serialization/deserialization routines via `serde`.
6-
It also provides optional conversions from [`cargo metadata`](https://docs.rs/cargo_metadata/)
7-
and to [`Cargo.lock`](https://docs.rs/cargo-lock) formats.
86

97
The [`VersionInfo`] struct is where all the magic happens, see the docs on it for more info.
108

auditable-serde/examples/from-metadata.rs

-29
This file was deleted.

auditable-serde/src/lib.rs

-257
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,7 @@ use validation::RawVersionInfo;
88

99
use serde::{Deserialize, Serialize};
1010

11-
#[cfg(feature = "from_metadata")]
12-
use std::convert::TryFrom;
1311
use std::str::FromStr;
14-
#[cfg(feature = "from_metadata")]
15-
use std::{cmp::min, cmp::Ordering::*, collections::HashMap, error::Error, fmt::Display};
1612

1713
/// Dependency tree embedded in the binary.
1814
///
@@ -35,19 +31,6 @@ use std::{cmp::min, cmp::Ordering::*, collections::HashMap, error::Error, fmt::D
3531
///
3632
/// If deserialization succeeds, it is guaranteed that there is only one root package,
3733
/// and that are no cyclic dependencies.
38-
///
39-
/// ## Optional features
40-
///
41-
/// If the `from_metadata` feature is enabled, a conversion from
42-
/// [`cargo_metadata::Metadata`](https://docs.rs/cargo_metadata/0.11.1/cargo_metadata/struct.Metadata.html)
43-
/// is possible via the `TryFrom` trait. This is the preferred way to construct this structure.
44-
/// An example demonstrating that can be found
45-
/// [here](https://github.com/rust-secure-code/cargo-auditable/blob/master/auditable-serde/examples/from-metadata.rs).
46-
///
47-
/// If the `toml` feature is enabled, a conversion into the [`cargo_lock::Lockfile`](https://docs.rs/cargo-lock/)
48-
/// struct is possible via the `TryFrom` trait. This can be useful if you need to interoperate with tooling
49-
/// that consumes the `Cargo.lock` file format. An example demonstrating it can be found
50-
/// [here](https://github.com/rust-secure-code/cargo-auditable/blob/master/auditable-serde/examples/json-to-toml.rs).
5134
#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, PartialOrd, Ord, Clone)]
5235
#[serde(try_from = "RawVersionInfo")]
5336
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
@@ -123,21 +106,6 @@ impl From<Source> for String {
123106
}
124107
}
125108

126-
#[cfg(feature = "from_metadata")]
127-
impl From<&cargo_metadata::Source> for Source {
128-
fn from(meta_source: &cargo_metadata::Source) -> Self {
129-
match meta_source.repr.as_str() {
130-
"registry+https://github.com/rust-lang/crates.io-index" => Source::CratesIo,
131-
source => Source::from(
132-
source
133-
.split('+')
134-
.next()
135-
.expect("Encoding of source strings in `cargo metadata` has changed!"),
136-
),
137-
}
138-
}
139-
}
140-
141109
#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, PartialOrd, Ord, Copy, Clone, Default)]
142110
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
143111
pub enum DependencyKind {
@@ -149,28 +117,6 @@ pub enum DependencyKind {
149117
Runtime,
150118
}
151119

152-
/// The values are ordered from weakest to strongest so that casting to integer would make sense
153-
#[cfg(feature = "from_metadata")]
154-
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Copy, Clone)]
155-
enum PrivateDepKind {
156-
Development,
157-
Build,
158-
Runtime,
159-
}
160-
161-
#[cfg(feature = "from_metadata")]
162-
impl From<PrivateDepKind> for DependencyKind {
163-
fn from(priv_kind: PrivateDepKind) -> Self {
164-
match priv_kind {
165-
PrivateDepKind::Development => {
166-
panic!("Cannot convert development dependency to serializable format")
167-
}
168-
PrivateDepKind::Build => DependencyKind::Build,
169-
PrivateDepKind::Runtime => DependencyKind::Runtime,
170-
}
171-
}
172-
}
173-
174120
fn is_default<T: Default + PartialEq>(value: &T) -> bool {
175121
let default_value = T::default();
176122
value == &default_value
@@ -183,191 +129,6 @@ impl FromStr for VersionInfo {
183129
}
184130
}
185131

186-
#[cfg(feature = "from_metadata")]
187-
impl From<&cargo_metadata::DependencyKind> for PrivateDepKind {
188-
fn from(kind: &cargo_metadata::DependencyKind) -> Self {
189-
match kind {
190-
cargo_metadata::DependencyKind::Normal => PrivateDepKind::Runtime,
191-
cargo_metadata::DependencyKind::Development => PrivateDepKind::Development,
192-
cargo_metadata::DependencyKind::Build => PrivateDepKind::Build,
193-
_ => panic!("Unknown dependency kind"),
194-
}
195-
}
196-
}
197-
198-
/// Error returned by the conversion from
199-
/// [`cargo_metadata::Metadata`](https://docs.rs/cargo_metadata/0.11.1/cargo_metadata/struct.Metadata.html)
200-
#[cfg(feature = "from_metadata")]
201-
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
202-
pub enum InsufficientMetadata {
203-
NoDeps,
204-
VirtualWorkspace,
205-
}
206-
207-
#[cfg(feature = "from_metadata")]
208-
impl Display for InsufficientMetadata {
209-
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
210-
match self {
211-
InsufficientMetadata::NoDeps => {
212-
write!(f, "Missing dependency information! Please call 'cargo metadata' without '--no-deps' flag.")
213-
}
214-
InsufficientMetadata::VirtualWorkspace => {
215-
write!(f, "Missing root crate! Please call this from a package directory, not workspace root.")
216-
}
217-
}
218-
}
219-
}
220-
221-
#[cfg(feature = "from_metadata")]
222-
impl Error for InsufficientMetadata {}
223-
224-
#[cfg(feature = "from_metadata")]
225-
impl TryFrom<&cargo_metadata::Metadata> for VersionInfo {
226-
type Error = InsufficientMetadata;
227-
fn try_from(metadata: &cargo_metadata::Metadata) -> Result<Self, Self::Error> {
228-
let toplevel_crate_id = metadata
229-
.resolve
230-
.as_ref()
231-
.ok_or(InsufficientMetadata::NoDeps)?
232-
.root
233-
.as_ref()
234-
.ok_or(InsufficientMetadata::VirtualWorkspace)?
235-
.repr
236-
.as_str();
237-
238-
// Walk the dependency tree and resolve dependency kinds for each package.
239-
// We need this because there may be several different paths to the same package
240-
// and we need to aggregate dependency types across all of them.
241-
// Moreover, `cargo metadata` doesn't propagate dependency information:
242-
// A runtime dependency of a build dependency of your package should be recorded
243-
// as *build* dependency, but Cargo flags it as a runtime dependency.
244-
// Hoo boy, here I go hand-rolling BFS again!
245-
let nodes = &metadata.resolve.as_ref().unwrap().nodes;
246-
let id_to_node: HashMap<&str, &cargo_metadata::Node> =
247-
nodes.iter().map(|n| (n.id.repr.as_str(), n)).collect();
248-
let mut id_to_dep_kind: HashMap<&str, PrivateDepKind> = HashMap::new();
249-
id_to_dep_kind.insert(toplevel_crate_id, PrivateDepKind::Runtime);
250-
let mut current_queue: Vec<&cargo_metadata::Node> = vec![id_to_node[toplevel_crate_id]];
251-
let mut next_step_queue: Vec<&cargo_metadata::Node> = Vec::new();
252-
while !current_queue.is_empty() {
253-
for parent in current_queue.drain(..) {
254-
let parent_dep_kind = id_to_dep_kind[parent.id.repr.as_str()];
255-
for child in &parent.deps {
256-
let child_id = child.pkg.repr.as_str();
257-
let dep_kind = strongest_dep_kind(child.dep_kinds.as_slice());
258-
let dep_kind = min(dep_kind, parent_dep_kind);
259-
let dep_kind_on_previous_visit = id_to_dep_kind.get(child_id);
260-
if dep_kind_on_previous_visit.is_none()
261-
|| &dep_kind > dep_kind_on_previous_visit.unwrap()
262-
{
263-
// if we haven't visited this node in dependency graph yet
264-
// or if we've visited it with a weaker dependency type,
265-
// records its new dependency type and add it to the queue to visit its dependencies
266-
id_to_dep_kind.insert(child_id, dep_kind);
267-
next_step_queue.push(id_to_node[child_id]);
268-
}
269-
}
270-
}
271-
std::mem::swap(&mut next_step_queue, &mut current_queue);
272-
}
273-
274-
let metadata_package_dep_kind = |p: &cargo_metadata::Package| {
275-
let package_id = p.id.repr.as_str();
276-
id_to_dep_kind.get(package_id)
277-
};
278-
279-
// Remove dev-only dependencies from the package list and collect them to Vec
280-
let mut packages: Vec<&cargo_metadata::Package> = metadata
281-
.packages
282-
.iter()
283-
.filter(|p| {
284-
let dep_kind = metadata_package_dep_kind(p);
285-
// Dependencies that are present in the workspace but not used by the current root crate
286-
// will not be in the map we've built by traversing the root crate's dependencies.
287-
// In this case they will not be in the map at all. We skip them, along with dev-dependencies.
288-
dep_kind.is_some() && dep_kind.unwrap() != &PrivateDepKind::Development
289-
})
290-
.collect();
291-
292-
// This function is the simplest place to introduce sorting, since
293-
// it contains enough data to distinguish between equal-looking packages
294-
// and provide a stable sorting that might not be possible
295-
// using the data from VersionInfo struct alone.
296-
//
297-
// We use sort_unstable here because there is no point in
298-
// not reordering equal elements, since they're supplied by
299-
// in arbitrary order by cargo-metadata anyway
300-
// and the order even varies between executions.
301-
packages.sort_unstable_by(|a, b| {
302-
// This is a workaround for Package not implementing Ord.
303-
// Deriving it in cargo_metadata might be more reliable?
304-
let names_order = a.name.cmp(&b.name);
305-
if names_order != Equal {
306-
return names_order;
307-
}
308-
let versions_order = a.name.cmp(&b.name);
309-
if versions_order != Equal {
310-
return versions_order;
311-
}
312-
// IDs are unique so comparing them should be sufficient
313-
a.id.repr.cmp(&b.id.repr)
314-
});
315-
316-
// Build a mapping from package ID to the index of that package in the Vec
317-
// because serializable representation doesn't store IDs
318-
let mut id_to_index = HashMap::new();
319-
for (index, package) in packages.iter().enumerate() {
320-
id_to_index.insert(package.id.repr.as_str(), index);
321-
}
322-
323-
// Convert packages from cargo-metadata representation to our representation
324-
let mut packages: Vec<Package> = packages
325-
.into_iter()
326-
.map(|p| Package {
327-
name: p.name.to_owned(),
328-
version: p.version.clone(),
329-
source: p.source.as_ref().map_or(Source::Local, Source::from),
330-
kind: (*metadata_package_dep_kind(p).unwrap()).into(),
331-
dependencies: Vec::new(),
332-
root: p.id.repr == toplevel_crate_id,
333-
})
334-
.collect();
335-
336-
// Fill in dependency info from resolved dependency graph
337-
for node in metadata.resolve.as_ref().unwrap().nodes.iter() {
338-
let package_id = node.id.repr.as_str();
339-
if id_to_index.contains_key(package_id) {
340-
// dev-dependencies are not included
341-
let package: &mut Package = &mut packages[id_to_index[package_id]];
342-
// Dependencies
343-
for dep in node.deps.iter() {
344-
// Omit the graph edge if this is a development dependency
345-
// to fix https://github.com/rustsec/rustsec/issues/1043
346-
// It is possible that something that we depend on normally
347-
// is also a dev-dependency for something,
348-
// and dev-dependencies are allowed to have cycles,
349-
// so we may end up encoding cyclic graph if we don't handle that.
350-
let dep_id = dep.pkg.repr.as_str();
351-
if strongest_dep_kind(&dep.dep_kinds) != PrivateDepKind::Development {
352-
package.dependencies.push(id_to_index[dep_id]);
353-
}
354-
}
355-
// .sort_unstable() is fine because they're all integers
356-
package.dependencies.sort_unstable();
357-
}
358-
}
359-
Ok(VersionInfo { packages })
360-
}
361-
}
362-
363-
#[cfg(feature = "from_metadata")]
364-
fn strongest_dep_kind(deps: &[cargo_metadata::DepKindInfo]) -> PrivateDepKind {
365-
deps.iter()
366-
.map(|d| PrivateDepKind::from(&d.kind))
367-
.max()
368-
.unwrap_or(PrivateDepKind::Runtime) // for compatibility with Rust earlier than 1.41
369-
}
370-
371132
#[cfg(test)]
372133
mod tests {
373134
#![allow(unused_imports)] // otherwise conditional compilation emits warnings
@@ -378,24 +139,6 @@ mod tests {
378139
path::{Path, PathBuf},
379140
};
380141

381-
#[cfg(feature = "from_metadata")]
382-
fn load_metadata(cargo_toml_path: &Path) -> cargo_metadata::Metadata {
383-
let mut cmd = cargo_metadata::MetadataCommand::new();
384-
cmd.manifest_path(cargo_toml_path);
385-
cmd.exec().unwrap()
386-
}
387-
388-
#[test]
389-
#[cfg(feature = "from_metadata")]
390-
fn dependency_cycle() {
391-
let cargo_toml_path = PathBuf::from(std::env::var("CARGO_MANIFEST_DIR").unwrap())
392-
.join("tests/fixtures/cargo-audit-dep-cycle/Cargo.toml");
393-
let metadata = load_metadata(&cargo_toml_path);
394-
let version_info_struct: VersionInfo = (&metadata).try_into().unwrap();
395-
let json = serde_json::to_string(&version_info_struct).unwrap();
396-
VersionInfo::from_str(&json).unwrap(); // <- the part we care about succeeding
397-
}
398-
399142
#[cfg(feature = "schema")]
400143
/// Generate a JsonSchema for VersionInfo
401144
fn generate_schema() -> schemars::schema::RootSchema {

auditable-serde/tests/fixtures/cargo-audit-dep-cycle/Cargo.lock

-24
This file was deleted.

cargo-auditable/Cargo.toml

+1-1
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ readme = "../README.md"
1414

1515
[dependencies]
1616
object = {version = "0.30", default-features = false, features = ["write"]}
17-
auditable-serde = {version = "0.7.0", path = "../auditable-serde", features = ["from_metadata"]}
17+
auditable-serde = {version = "0.7.0", path = "../auditable-serde"}
1818
miniz_oxide = {version = "0.8.0"}
1919
serde_json = "1.0.57"
2020
cargo_metadata = "0.18"

0 commit comments

Comments
 (0)