Skip to content

Commit 0fef32a

Browse files
a-orensoul2zimate
andauthored
fix: read ignore patterns from workspace member Cargo.toml files (#375)
## Description Previously, ignore patterns (exhortignore / trustify-da-ignore) were only read from the root Cargo.toml. In a virtual workspace, member crates have their own Cargo.toml with [dependencies], so ignore comments there were never detected. This caused the Java client to include dependencies that should have been ignored at the member level. Now, when processing each workspace member for stack analysis, its own Cargo.toml is parsed for ignore patterns and merged with workspace-level ignores before walking the dependency tree. **Related issue (if any):** fixes #374 ## Checklist - [x] I have followed this repository's contributing guidelines. - [x] I will adhere to the project's code of conduct. ## Additional information > Anything else? --------- Co-authored-by: Chao Wang <chaowan@redhat.com>
1 parent 203bfb5 commit 0fef32a

3 files changed

Lines changed: 119 additions & 28 deletions

File tree

src/main/java/io/github/guacsec/trustifyda/providers/CargoProvider.java

Lines changed: 49 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -202,13 +202,60 @@ private void handleVirtualWorkspace(
202202
+ metadata.workspaceMembers());
203203
}
204204
for (String memberId : metadata.workspaceMembers()) {
205+
Set<String> memberIgnoredDeps = getMemberIgnoredDeps(memberId, packageMap, ignoredDeps);
205206
processWorkspaceMember(
206-
sbom, root, memberId, nodeMap, packageMap, ignoredDeps, analysisType);
207+
sbom, root, memberId, nodeMap, packageMap, memberIgnoredDeps, analysisType);
207208
}
208209
}
209210
}
210211
}
211212

213+
/**
214+
* Builds the full set of ignored dependencies for a workspace member by reading the member's own
215+
* Cargo.toml for ignore patterns and merging them with the workspace-level ignored deps.
216+
*/
217+
private Set<String> getMemberIgnoredDeps(
218+
String memberId, Map<String, CargoPackage> packageMap, Set<String> workspaceIgnoredDeps) {
219+
CargoPackage memberPkg = packageMap.get(memberId);
220+
if (memberPkg == null || memberPkg.manifestPath() == null) {
221+
return workspaceIgnoredDeps;
222+
}
223+
Path memberManifest = Path.of(memberPkg.manifestPath());
224+
if (!Files.isRegularFile(memberManifest)) {
225+
return workspaceIgnoredDeps;
226+
}
227+
try {
228+
TomlParseResult memberToml = Toml.parse(memberManifest);
229+
if (memberToml.hasErrors()) {
230+
return workspaceIgnoredDeps;
231+
}
232+
String memberContent = Files.readString(memberManifest, StandardCharsets.UTF_8);
233+
Set<String> memberIgnored = getIgnoredDependencies(memberToml, memberContent);
234+
if (memberIgnored.isEmpty()) {
235+
return workspaceIgnoredDeps;
236+
}
237+
if (debugLoggingIsNeeded()) {
238+
log.info(
239+
"Found "
240+
+ memberIgnored.size()
241+
+ " ignored dependencies in member "
242+
+ memberPkg.name()
243+
+ ": "
244+
+ memberIgnored);
245+
}
246+
Set<String> merged = new HashSet<>(workspaceIgnoredDeps);
247+
merged.addAll(memberIgnored);
248+
return merged;
249+
} catch (IOException e) {
250+
log.warning(
251+
"Failed to read member Cargo.toml for ignore patterns: "
252+
+ memberManifest
253+
+ ": "
254+
+ e.getMessage());
255+
return workspaceIgnoredDeps;
256+
}
257+
}
258+
212259
void processWorkspaceDependencies(
213260
Sbom sbom,
214261
PackageURL root,
@@ -714,7 +761,7 @@ private ProjectInfo parseCargoToml(TomlParseResult result) throws IOException {
714761
throw new IOException("Invalid Cargo.toml: no [package] or [workspace] section found");
715762
}
716763

717-
private Set<String> getIgnoredDependencies(TomlParseResult result, String content) {
764+
Set<String> getIgnoredDependencies(TomlParseResult result, String content) {
718765
Set<String> normalDependencies = collectNormalDependencies(result);
719766
if (debugLoggingIsNeeded()) {
720767
log.info("Found " + normalDependencies.size() + " normal dependencies in Cargo.toml");

src/main/java/io/github/guacsec/trustifyda/providers/rust/model/CargoPackage.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,4 +24,5 @@ public record CargoPackage(
2424
@JsonProperty("name") String name,
2525
@JsonProperty("version") String version,
2626
@JsonProperty("id") String id,
27+
@JsonProperty("manifest_path") String manifestPath,
2728
@JsonProperty("dependencies") List<CargoDependency> dependencies) {}

src/test/java/io/github/guacsec/trustifyda/providers/CargoProviderCargoParsingTest.java

Lines changed: 69 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -405,23 +405,11 @@ public void testComplexDependencySyntaxWithIgnorePatterns(@TempDir Path tempDir)
405405
""";
406406
Files.writeString(cargoToml, content);
407407

408-
// Create RustProvider and test ignore detection
409408
CargoProvider provider = new CargoProvider(cargoToml);
410-
411-
// Read the file content for the updated method signature
412409
String cargoContent = Files.readString(cargoToml, StandardCharsets.UTF_8);
410+
TomlParseResult tomlResult = Toml.parse(cargoToml);
413411

414-
// Parse TOML using TOMLJ (matching the optimized implementation)
415-
org.tomlj.TomlParseResult tomlResult = org.tomlj.Toml.parse(cargoToml);
416-
417-
// Use reflection to test the private getIgnoredDependencies method with new signature
418-
java.lang.reflect.Method method =
419-
CargoProvider.class.getDeclaredMethod(
420-
"getIgnoredDependencies", org.tomlj.TomlParseResult.class, String.class);
421-
method.setAccessible(true);
422-
423-
@SuppressWarnings("unchecked")
424-
Set<String> ignoredDeps = (Set<String>) method.invoke(provider, tomlResult, cargoContent);
412+
Set<String> ignoredDeps = provider.getIgnoredDependencies(tomlResult, cargoContent);
425413

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

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

633-
// Parse TOML using TOMLJ (matching the optimized implementation)
634-
org.tomlj.TomlParseResult edgeTomlResult = org.tomlj.Toml.parse(edgeCaseCargoToml);
635-
636-
java.lang.reflect.Method method =
637-
CargoProvider.class.getDeclaredMethod(
638-
"getIgnoredDependencies", org.tomlj.TomlParseResult.class, String.class);
639-
method.setAccessible(true);
640-
641-
@SuppressWarnings("unchecked")
642-
Set<String> ignoredDeps =
643-
(Set<String>) method.invoke(provider, edgeTomlResult, edgeCargoContent);
621+
Set<String> ignoredDeps = provider.getIgnoredDependencies(edgeTomlResult, edgeCargoContent);
644622

645623
// Should detect ignore patterns despite varying spacing and formatting
646624
assertTrue(ignoredDeps.contains("dep1"), "Should ignore dep1 (extra spaces)");
@@ -766,4 +744,69 @@ public void testVirtualWorkspaceWithoutWorkspaceDepsDoesNotThrowNPE(@TempDir Pat
766744
sbom, root, new HashMap<>(), new HashSet<>(), tomlResult),
767745
"processWorkspaceDependencies should handle missing [workspace.dependencies] gracefully");
768746
}
747+
748+
@Test
749+
public void testMemberCargoTomlIgnorePatternsDetected(@TempDir Path tempDir) throws Exception {
750+
// Simulate a member's Cargo.toml with exhortignore on a dependency
751+
Path memberDir = tempDir.resolve("crate-a");
752+
Files.createDirectories(memberDir);
753+
Path memberCargoToml = memberDir.resolve("Cargo.toml");
754+
String memberContent =
755+
"""
756+
[package]
757+
name = "crate-a"
758+
version = "0.1.0"
759+
edition = "2021"
760+
761+
[dependencies]
762+
serde = "1.0" # exhortignore
763+
tokio = "1.0"
764+
reqwest = "0.11" # trustify-da-ignore
765+
""";
766+
Files.writeString(memberCargoToml, memberContent);
767+
768+
CargoProvider provider = new CargoProvider(memberCargoToml);
769+
TomlParseResult tomlResult = Toml.parse(memberCargoToml);
770+
String content = Files.readString(memberCargoToml, StandardCharsets.UTF_8);
771+
772+
Set<String> ignoredDeps = provider.getIgnoredDependencies(tomlResult, content);
773+
774+
assertTrue(ignoredDeps.contains("serde"), "serde should be ignored (exhortignore)");
775+
assertFalse(ignoredDeps.contains("tokio"), "tokio should NOT be ignored");
776+
assertTrue(ignoredDeps.contains("reqwest"), "reqwest should be ignored (trustify-da-ignore)");
777+
assertEquals(2, ignoredDeps.size(), "Should find exactly 2 ignored dependencies in member");
778+
}
779+
780+
@Test
781+
public void testMemberIgnorePatternsWithTableFormat(@TempDir Path tempDir) throws Exception {
782+
Path memberDir = tempDir.resolve("crate-b");
783+
Files.createDirectories(memberDir);
784+
Path memberCargoToml = memberDir.resolve("Cargo.toml");
785+
String memberContent =
786+
"""
787+
[package]
788+
name = "crate-b"
789+
version = "0.1.0"
790+
edition = "2021"
791+
792+
[dependencies]
793+
serde-json-wasm = "1.0"
794+
795+
[dependencies.aho-corasick] # trustify-da-ignore
796+
version = "1.0.0"
797+
""";
798+
Files.writeString(memberCargoToml, memberContent);
799+
800+
CargoProvider provider = new CargoProvider(memberCargoToml);
801+
TomlParseResult tomlResult = Toml.parse(memberCargoToml);
802+
String content = Files.readString(memberCargoToml, StandardCharsets.UTF_8);
803+
804+
Set<String> ignoredDeps = provider.getIgnoredDependencies(tomlResult, content);
805+
806+
assertFalse(ignoredDeps.contains("serde-json-wasm"), "serde-json-wasm should NOT be ignored");
807+
assertTrue(
808+
ignoredDeps.contains("aho-corasick"),
809+
"aho-corasick should be ignored (table format with trustify-da-ignore)");
810+
assertEquals(1, ignoredDeps.size(), "Should find exactly 1 ignored dependency in member");
811+
}
769812
}

0 commit comments

Comments
 (0)