Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -202,13 +202,60 @@ private void handleVirtualWorkspace(
+ metadata.workspaceMembers());
}
for (String memberId : metadata.workspaceMembers()) {
Set<String> memberIgnoredDeps = getMemberIgnoredDeps(memberId, packageMap, ignoredDeps);
processWorkspaceMember(
sbom, root, memberId, nodeMap, packageMap, ignoredDeps, analysisType);
sbom, root, memberId, nodeMap, packageMap, memberIgnoredDeps, analysisType);
}
}
}
}

/**
* Builds the full set of ignored dependencies for a workspace member by reading the member's own
* Cargo.toml for ignore patterns and merging them with the workspace-level ignored deps.
*/
private Set<String> getMemberIgnoredDeps(
String memberId, Map<String, CargoPackage> packageMap, Set<String> workspaceIgnoredDeps) {
CargoPackage memberPkg = packageMap.get(memberId);
if (memberPkg == null || memberPkg.manifestPath() == null) {
return workspaceIgnoredDeps;
}
Path memberManifest = Path.of(memberPkg.manifestPath());
if (!Files.isRegularFile(memberManifest)) {
return workspaceIgnoredDeps;
}
try {
TomlParseResult memberToml = Toml.parse(memberManifest);
if (memberToml.hasErrors()) {
return workspaceIgnoredDeps;
}
String memberContent = Files.readString(memberManifest, StandardCharsets.UTF_8);
Set<String> memberIgnored = getIgnoredDependencies(memberToml, memberContent);
if (memberIgnored.isEmpty()) {
return workspaceIgnoredDeps;
}
if (debugLoggingIsNeeded()) {
log.info(
"Found "
+ memberIgnored.size()
+ " ignored dependencies in member "
+ memberPkg.name()
+ ": "
+ memberIgnored);
}
Set<String> merged = new HashSet<>(workspaceIgnoredDeps);
merged.addAll(memberIgnored);
return merged;
} catch (IOException e) {
log.warning(
"Failed to read member Cargo.toml for ignore patterns: "
+ memberManifest
+ ": "
+ e.getMessage());
return workspaceIgnoredDeps;
}
}

void processWorkspaceDependencies(
Sbom sbom,
PackageURL root,
Expand Down Expand Up @@ -714,7 +761,7 @@ private ProjectInfo parseCargoToml(TomlParseResult result) throws IOException {
throw new IOException("Invalid Cargo.toml: no [package] or [workspace] section found");
}

private Set<String> getIgnoredDependencies(TomlParseResult result, String content) {
Set<String> getIgnoredDependencies(TomlParseResult result, String content) {
Set<String> normalDependencies = collectNormalDependencies(result);
if (debugLoggingIsNeeded()) {
log.info("Found " + normalDependencies.size() + " normal dependencies in Cargo.toml");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,4 +24,5 @@ public record CargoPackage(
@JsonProperty("name") String name,
@JsonProperty("version") String version,
@JsonProperty("id") String id,
@JsonProperty("manifest_path") String manifestPath,
@JsonProperty("dependencies") List<CargoDependency> dependencies) {}
Original file line number Diff line number Diff line change
Expand Up @@ -405,23 +405,11 @@ public void testComplexDependencySyntaxWithIgnorePatterns(@TempDir Path tempDir)
""";
Files.writeString(cargoToml, content);

// Create RustProvider and test ignore detection
CargoProvider provider = new CargoProvider(cargoToml);

// Read the file content for the updated method signature
String cargoContent = Files.readString(cargoToml, StandardCharsets.UTF_8);
TomlParseResult tomlResult = Toml.parse(cargoToml);

// Parse TOML using TOMLJ (matching the optimized implementation)
org.tomlj.TomlParseResult tomlResult = org.tomlj.Toml.parse(cargoToml);

// Use reflection to test the private getIgnoredDependencies method with new signature
java.lang.reflect.Method method =
CargoProvider.class.getDeclaredMethod(
"getIgnoredDependencies", org.tomlj.TomlParseResult.class, String.class);
method.setAccessible(true);

@SuppressWarnings("unchecked")
Set<String> ignoredDeps = (Set<String>) method.invoke(provider, tomlResult, cargoContent);
Set<String> ignoredDeps = provider.getIgnoredDependencies(tomlResult, cargoContent);

System.out.println("Complex syntax test - Ignored dependencies found:");
for (String dep : ignoredDeps) {
Expand Down Expand Up @@ -627,20 +615,10 @@ public void testEdgeCaseCargoTomlFormats(@TempDir Path tempDir) throws Exception
assertTrue(sbomContent.contains("edge-case-project"), "Should contain project name");

// Test ignore detection with edge case formatting
// Read the file content for the updated method signature
String edgeCargoContent = Files.readString(edgeCaseCargoToml, StandardCharsets.UTF_8);
TomlParseResult edgeTomlResult = Toml.parse(edgeCaseCargoToml);

// Parse TOML using TOMLJ (matching the optimized implementation)
org.tomlj.TomlParseResult edgeTomlResult = org.tomlj.Toml.parse(edgeCaseCargoToml);

java.lang.reflect.Method method =
CargoProvider.class.getDeclaredMethod(
"getIgnoredDependencies", org.tomlj.TomlParseResult.class, String.class);
method.setAccessible(true);

@SuppressWarnings("unchecked")
Set<String> ignoredDeps =
(Set<String>) method.invoke(provider, edgeTomlResult, edgeCargoContent);
Set<String> ignoredDeps = provider.getIgnoredDependencies(edgeTomlResult, edgeCargoContent);

// Should detect ignore patterns despite varying spacing and formatting
assertTrue(ignoredDeps.contains("dep1"), "Should ignore dep1 (extra spaces)");
Expand Down Expand Up @@ -766,4 +744,69 @@ public void testVirtualWorkspaceWithoutWorkspaceDepsDoesNotThrowNPE(@TempDir Pat
sbom, root, new HashMap<>(), new HashSet<>(), tomlResult),
"processWorkspaceDependencies should handle missing [workspace.dependencies] gracefully");
}

@Test
public void testMemberCargoTomlIgnorePatternsDetected(@TempDir Path tempDir) throws Exception {
// Simulate a member's Cargo.toml with exhortignore on a dependency
Path memberDir = tempDir.resolve("crate-a");
Files.createDirectories(memberDir);
Path memberCargoToml = memberDir.resolve("Cargo.toml");
String memberContent =
"""
[package]
name = "crate-a"
version = "0.1.0"
edition = "2021"

[dependencies]
serde = "1.0" # exhortignore
tokio = "1.0"
reqwest = "0.11" # trustify-da-ignore
""";
Files.writeString(memberCargoToml, memberContent);

CargoProvider provider = new CargoProvider(memberCargoToml);
TomlParseResult tomlResult = Toml.parse(memberCargoToml);
String content = Files.readString(memberCargoToml, StandardCharsets.UTF_8);

Set<String> ignoredDeps = provider.getIgnoredDependencies(tomlResult, content);

assertTrue(ignoredDeps.contains("serde"), "serde should be ignored (exhortignore)");
assertFalse(ignoredDeps.contains("tokio"), "tokio should NOT be ignored");
assertTrue(ignoredDeps.contains("reqwest"), "reqwest should be ignored (trustify-da-ignore)");
assertEquals(2, ignoredDeps.size(), "Should find exactly 2 ignored dependencies in member");
}

@Test
public void testMemberIgnorePatternsWithTableFormat(@TempDir Path tempDir) throws Exception {
Path memberDir = tempDir.resolve("crate-b");
Files.createDirectories(memberDir);
Path memberCargoToml = memberDir.resolve("Cargo.toml");
String memberContent =
"""
[package]
name = "crate-b"
version = "0.1.0"
edition = "2021"

[dependencies]
serde-json-wasm = "1.0"

[dependencies.aho-corasick] # trustify-da-ignore
version = "1.0.0"
""";
Files.writeString(memberCargoToml, memberContent);

CargoProvider provider = new CargoProvider(memberCargoToml);
TomlParseResult tomlResult = Toml.parse(memberCargoToml);
String content = Files.readString(memberCargoToml, StandardCharsets.UTF_8);

Set<String> ignoredDeps = provider.getIgnoredDependencies(tomlResult, content);

assertFalse(ignoredDeps.contains("serde-json-wasm"), "serde-json-wasm should NOT be ignored");
assertTrue(
ignoredDeps.contains("aho-corasick"),
"aho-corasick should be ignored (table format with trustify-da-ignore)");
assertEquals(1, ignoredDeps.size(), "Should find exactly 1 ignored dependency in member");
}
}
Loading