diff --git a/src/main/java/io/github/guacsec/trustifyda/providers/CargoProvider.java b/src/main/java/io/github/guacsec/trustifyda/providers/CargoProvider.java index 52ca76d4..19cfc636 100644 --- a/src/main/java/io/github/guacsec/trustifyda/providers/CargoProvider.java +++ b/src/main/java/io/github/guacsec/trustifyda/providers/CargoProvider.java @@ -208,7 +208,7 @@ private void handleVirtualWorkspace( } } - private void processWorkspaceDependencies( + void processWorkspaceDependencies( Sbom sbom, PackageURL root, Map packageMap, @@ -216,6 +216,12 @@ private void processWorkspaceDependencies( TomlParseResult tomlResult) { var workspaceDepsTable = tomlResult.getTable("workspace.dependencies"); + if (workspaceDepsTable == null) { + if (debugLoggingIsNeeded()) { + log.info("No [workspace.dependencies] section found, skipping workspace dependencies"); + } + return; + } if (debugLoggingIsNeeded()) { log.info("Processing " + workspaceDepsTable.keySet().size() + " workspace dependencies"); } diff --git a/src/test/java/io/github/guacsec/trustifyda/providers/CargoProviderCargoParsingTest.java b/src/test/java/io/github/guacsec/trustifyda/providers/CargoProviderCargoParsingTest.java index 72b36545..1d3c922c 100644 --- a/src/test/java/io/github/guacsec/trustifyda/providers/CargoProviderCargoParsingTest.java +++ b/src/test/java/io/github/guacsec/trustifyda/providers/CargoProviderCargoParsingTest.java @@ -16,19 +16,29 @@ */ package io.github.guacsec.trustifyda.providers; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; +import com.github.packageurl.PackageURL; +import io.github.guacsec.trustifyda.sbom.Sbom; +import io.github.guacsec.trustifyda.sbom.SbomFactory; +import io.github.guacsec.trustifyda.tools.Ecosystem.Type; import java.io.IOException; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; +import java.util.HashMap; +import java.util.HashSet; import java.util.Set; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.io.TempDir; +import org.tomlj.Toml; +import org.tomlj.TomlParseResult; public class CargoProviderCargoParsingTest { @@ -720,4 +730,40 @@ public void testVirtualRootWithoutVersionUsesDefault(@TempDir Path tempDir) thro assertTrue(stackSbom.contains("1.0.0"), "SBOM should contain default version: 1.0.0"); } + + @Test + public void testVirtualWorkspaceWithoutWorkspaceDepsDoesNotThrowNPE(@TempDir Path tempDir) + throws Exception { + // Create a Cargo.toml with [workspace] members but NO [workspace.dependencies] + Path cargoToml = tempDir.resolve("Cargo.toml"); + String content = + """ + [workspace] + members = ["crate-a", "crate-b"] + + [workspace.package] + version = "1.0.0" + edition = "2021" + """; + Files.writeString(cargoToml, content); + + TomlParseResult tomlResult = Toml.parse(cargoToml); + // Verify precondition: workspace.dependencies table is null + assertNull( + tomlResult.getTable("workspace.dependencies"), + "Precondition: workspace.dependencies table should be null"); + + CargoProvider provider = new CargoProvider(cargoToml); + Sbom sbom = SbomFactory.newInstance(); + PackageURL root = + new PackageURL(Type.CARGO.getType(), null, "test-workspace", "1.0.0", null, null); + sbom.addRoot(root); + + // This should NOT throw NPE when [workspace.dependencies] is absent + assertDoesNotThrow( + () -> + provider.processWorkspaceDependencies( + sbom, root, new HashMap<>(), new HashSet<>(), tomlResult), + "processWorkspaceDependencies should handle missing [workspace.dependencies] gracefully"); + } }