Skip to content

Commit c7f761d

Browse files
authored
fix(go): merge children on MVS version key collision instead of overwriting (#421)
## Summary - Fix `HashMap.put()` collision in `getFinalPackagesVersionsForModule()` that dropped transitive dependencies when multiple parent module versions remapped to the same MVS-selected version - Replace `put()` with `merge()` using `LinkedHashSet` to combine children lists while deduplicating - Add reproducer test confirming 138 components (matching JS client), up from 134 Implements [TC-4127](https://redhat.atlassian.net/browse/TC-4127) ## Test plan - [x] Reproducer test `Test_Golang_MvS_Enabled_Preserves_All_Transitive_Dependencies` passes (138 components) - [x] Existing `Test_Golang_MvS_Logic_Disabled` passes (5 opencensus versions disabled, 1 enabled) - [x] All 6 parameterized `test_the_provideStack` + `test_the_provideComponent` tests pass - [x] All Go module tests pass (12/12 in surefire) - [x] Spotless formatting clean 🤖 Generated with [Claude Code](https://claude.com/claude-code) [TC-4127]: https://redhat.atlassian.net/browse/TC-4127?atlOrigin=eyJpIjoiNWRkNTljNzYxNjVmNDY3MDlhMDU5Y2ZhYzA5YTRkZjUiLCJwIjoiZ2l0aHViLWNvbS1KU1cifQ
1 parent 3c526e7 commit c7f761d

8 files changed

Lines changed: 1487 additions & 1073 deletions

File tree

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

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -354,7 +354,14 @@ private Map<String, List<String>> getFinalPackagesVersionsForModule(
354354
}
355355
List<String> packagesWithFinalVersions =
356356
getListOfPackagesWithFinalVersions(finalModulesVersions, value);
357-
listWithModifiedVersions.put(packageWithSelectedVersion, packagesWithFinalVersions);
357+
listWithModifiedVersions.merge(
358+
packageWithSelectedVersion,
359+
packagesWithFinalVersions,
360+
(existing, incoming) -> {
361+
var combined = new java.util.LinkedHashSet<>(existing);
362+
combined.addAll(incoming);
363+
return new ArrayList<>(combined);
364+
});
358365
});
359366

360367
return listWithModifiedVersions;

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

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -198,6 +198,37 @@ void Test_Golang_MvS_Logic_Disabled() throws IOException {
198198
== 1);
199199
}
200200

201+
/**
202+
* Verifies that MVS-enabled mode preserves all transitive dependencies (TC-3818).
203+
*
204+
* <p>When MVS is enabled (the default), {@code getFinalPackagesVersionsForModule()} uses {@code
205+
* HashMap.put()} which overwrites children when two original parent versions remap to the same
206+
* MVS-selected version. This causes the Java client to produce fewer components than the JS
207+
* client.
208+
*/
209+
@Test
210+
void Test_Golang_MvS_Enabled_Preserves_All_Transitive_Dependencies() throws IOException {
211+
// Given the MVS test fixture with MVS enabled (the default — no property override)
212+
String goModPath = getFileFromResource("go.mod", "msc/golang/mvs_logic/go.mod");
213+
Path manifest = Path.of(goModPath);
214+
GoModulesProvider goModulesProvider = new GoModulesProvider(manifest);
215+
216+
// When generating the SBOM with stack analysis
217+
String resultSbom =
218+
dropIgnoredKeepFormat(
219+
goModulesProvider.getDependenciesSbom(manifest, true).getAsJsonString());
220+
221+
// Then the SBOM should contain exactly 138 components (matching JS client output)
222+
JsonNode sbomTree = JSON_MAPPER.readTree(resultSbom);
223+
int componentCount = sbomTree.path("components").size();
224+
assertEquals(
225+
138,
226+
componentCount,
227+
"MVS-enabled SBOM should contain 138 components (matching JS client). "
228+
+ "A lower count indicates the HashMap.put() collision bug in "
229+
+ "getFinalPackagesVersionsForModule() is losing transitive dependencies.");
230+
}
231+
201232
@Test
202233
void test_isGoToolchainEntry_filters_go_and_toolchain() {
203234
// go@* entries should be filtered

src/test/resources/tst_manifests/golang/go_mod_light_no_ignore/expected_sbom_stack_analysis.json

Lines changed: 62 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -189,6 +189,22 @@
189189
"version": "v0.0.0-20201021035429-f5854403a974",
190190
"purl": "pkg:golang/golang.org/x/net@v0.0.0-20201021035429-f5854403a974"
191191
},
192+
{
193+
"type": "library",
194+
"bom-ref": "pkg:golang/github.com/davecgh/go-spew@v1.1.1",
195+
"group": "github.com/davecgh",
196+
"name": "go-spew",
197+
"version": "v1.1.1",
198+
"purl": "pkg:golang/github.com/davecgh/go-spew@v1.1.1"
199+
},
200+
{
201+
"type": "library",
202+
"bom-ref": "pkg:golang/github.com/pmezard/go-difflib@v1.0.0",
203+
"group": "github.com/pmezard",
204+
"name": "go-difflib",
205+
"version": "v1.0.0",
206+
"purl": "pkg:golang/github.com/pmezard/go-difflib@v1.0.0"
207+
},
192208
{
193209
"type": "library",
194210
"bom-ref": "pkg:golang/github.com/spf13/cobra@v0.0.5",
@@ -229,22 +245,6 @@
229245
"version": "v1.1.0",
230246
"purl": "pkg:golang/github.com/mitchellh/go-homedir@v1.1.0"
231247
},
232-
{
233-
"type": "library",
234-
"bom-ref": "pkg:golang/github.com/davecgh/go-spew@v1.1.1",
235-
"group": "github.com/davecgh",
236-
"name": "go-spew",
237-
"version": "v1.1.1",
238-
"purl": "pkg:golang/github.com/davecgh/go-spew@v1.1.1"
239-
},
240-
{
241-
"type": "library",
242-
"bom-ref": "pkg:golang/github.com/pmezard/go-difflib@v1.0.0",
243-
"group": "github.com/pmezard",
244-
"name": "go-difflib",
245-
"version": "v1.0.0",
246-
"purl": "pkg:golang/github.com/pmezard/go-difflib@v1.0.0"
247-
},
248248
{
249249
"type": "library",
250250
"bom-ref": "pkg:golang/golang.org/x/tools@v0.0.0-20210112183307-1e6ecd4bf1b0",
@@ -253,14 +253,6 @@
253253
"version": "v0.0.0-20210112183307-1e6ecd4bf1b0",
254254
"purl": "pkg:golang/golang.org/x/tools@v0.0.0-20210112183307-1e6ecd4bf1b0"
255255
},
256-
{
257-
"type": "library",
258-
"bom-ref": "pkg:golang/gopkg.in/yaml.v3@v3.0.1",
259-
"group": "gopkg.in",
260-
"name": "yaml.v3",
261-
"version": "v3.0.1",
262-
"purl": "pkg:golang/gopkg.in/yaml.v3@v3.0.1"
263-
},
264256
{
265257
"type": "library",
266258
"bom-ref": "pkg:golang/gopkg.in/check.v1@v0.0.0-20161208181325-20d25e280405",
@@ -271,19 +263,11 @@
271263
},
272264
{
273265
"type": "library",
274-
"bom-ref": "pkg:golang/github.com/yuin/goldmark@v1.2.1",
275-
"group": "github.com/yuin",
276-
"name": "goldmark",
277-
"version": "v1.2.1",
278-
"purl": "pkg:golang/github.com/yuin/goldmark@v1.2.1"
279-
},
280-
{
281-
"type": "library",
282-
"bom-ref": "pkg:golang/golang.org/x/mod@v0.3.0",
283-
"group": "golang.org/x",
284-
"name": "mod",
285-
"version": "v0.3.0",
286-
"purl": "pkg:golang/golang.org/x/mod@v0.3.0"
266+
"bom-ref": "pkg:golang/gopkg.in/yaml.v3@v3.0.1",
267+
"group": "gopkg.in",
268+
"name": "yaml.v3",
269+
"version": "v3.0.1",
270+
"purl": "pkg:golang/gopkg.in/yaml.v3@v3.0.1"
287271
},
288272
{
289273
"type": "library",
@@ -301,6 +285,22 @@
301285
"version": "v0.0.0-20200804184101-5ec99f83aff1",
302286
"purl": "pkg:golang/golang.org/x/xerrors@v0.0.0-20200804184101-5ec99f83aff1"
303287
},
288+
{
289+
"type": "library",
290+
"bom-ref": "pkg:golang/github.com/yuin/goldmark@v1.2.1",
291+
"group": "github.com/yuin",
292+
"name": "goldmark",
293+
"version": "v1.2.1",
294+
"purl": "pkg:golang/github.com/yuin/goldmark@v1.2.1"
295+
},
296+
{
297+
"type": "library",
298+
"bom-ref": "pkg:golang/golang.org/x/mod@v0.3.0",
299+
"group": "golang.org/x",
300+
"name": "mod",
301+
"version": "v0.3.0",
302+
"purl": "pkg:golang/golang.org/x/mod@v0.3.0"
303+
},
304304
{
305305
"type": "library",
306306
"bom-ref": "pkg:golang/github.com/russross/blackfriday@v1.5.2",
@@ -417,8 +417,8 @@
417417
{
418418
"ref": "pkg:golang/golang.org/x/crypto@v0.0.0-20200622213623-75b288015ac9",
419419
"dependsOn": [
420-
"pkg:golang/golang.org/x/net@v0.0.0-20201021035429-f5854403a974",
421-
"pkg:golang/golang.org/x/sys@v0.0.0-20200930185726-fdedc70b468f"
420+
"pkg:golang/golang.org/x/sys@v0.0.0-20200930185726-fdedc70b468f",
421+
"pkg:golang/golang.org/x/net@v0.0.0-20201021035429-f5854403a974"
422422
]
423423
},
424424
{
@@ -441,10 +441,18 @@
441441
"ref": "pkg:golang/golang.org/x/net@v0.0.0-20201021035429-f5854403a974",
442442
"dependsOn": [
443443
"pkg:golang/golang.org/x/crypto@v0.0.0-20200622213623-75b288015ac9",
444-
"pkg:golang/golang.org/x/sys@v0.0.0-20200930185726-fdedc70b468f",
445-
"pkg:golang/golang.org/x/text@v0.3.3"
444+
"pkg:golang/golang.org/x/text@v0.3.3",
445+
"pkg:golang/golang.org/x/sys@v0.0.0-20200930185726-fdedc70b468f"
446446
]
447447
},
448+
{
449+
"ref": "pkg:golang/github.com/davecgh/go-spew@v1.1.1",
450+
"dependsOn": []
451+
},
452+
{
453+
"ref": "pkg:golang/github.com/pmezard/go-difflib@v1.0.0",
454+
"dependsOn": []
455+
},
448456
{
449457
"ref": "pkg:golang/github.com/spf13/cobra@v0.0.5",
450458
"dependsOn": [
@@ -475,32 +483,32 @@
475483
"ref": "pkg:golang/github.com/mitchellh/go-homedir@v1.1.0",
476484
"dependsOn": []
477485
},
478-
{
479-
"ref": "pkg:golang/github.com/davecgh/go-spew@v1.1.1",
480-
"dependsOn": []
481-
},
482-
{
483-
"ref": "pkg:golang/github.com/pmezard/go-difflib@v1.0.0",
484-
"dependsOn": []
485-
},
486486
{
487487
"ref": "pkg:golang/golang.org/x/tools@v0.0.0-20210112183307-1e6ecd4bf1b0",
488488
"dependsOn": [
489-
"pkg:golang/github.com/yuin/goldmark@v1.2.1",
490-
"pkg:golang/golang.org/x/mod@v0.3.0",
491489
"pkg:golang/golang.org/x/net@v0.0.0-20201021035429-f5854403a974",
492490
"pkg:golang/golang.org/x/sync@v0.0.0-20201020160332-67f06af15bc9",
493-
"pkg:golang/golang.org/x/xerrors@v0.0.0-20200804184101-5ec99f83aff1"
491+
"pkg:golang/golang.org/x/xerrors@v0.0.0-20200804184101-5ec99f83aff1",
492+
"pkg:golang/github.com/yuin/goldmark@v1.2.1",
493+
"pkg:golang/golang.org/x/mod@v0.3.0"
494494
]
495495
},
496+
{
497+
"ref": "pkg:golang/gopkg.in/check.v1@v0.0.0-20161208181325-20d25e280405",
498+
"dependsOn": []
499+
},
496500
{
497501
"ref": "pkg:golang/gopkg.in/yaml.v3@v3.0.1",
498502
"dependsOn": [
499503
"pkg:golang/gopkg.in/check.v1@v0.0.0-20161208181325-20d25e280405"
500504
]
501505
},
502506
{
503-
"ref": "pkg:golang/gopkg.in/check.v1@v0.0.0-20161208181325-20d25e280405",
507+
"ref": "pkg:golang/golang.org/x/sync@v0.0.0-20201020160332-67f06af15bc9",
508+
"dependsOn": []
509+
},
510+
{
511+
"ref": "pkg:golang/golang.org/x/xerrors@v0.0.0-20200804184101-5ec99f83aff1",
504512
"dependsOn": []
505513
},
506514
{
@@ -515,17 +523,9 @@
515523
"pkg:golang/golang.org/x/xerrors@v0.0.0-20200804184101-5ec99f83aff1"
516524
]
517525
},
518-
{
519-
"ref": "pkg:golang/golang.org/x/sync@v0.0.0-20201020160332-67f06af15bc9",
520-
"dependsOn": []
521-
},
522-
{
523-
"ref": "pkg:golang/golang.org/x/xerrors@v0.0.0-20200804184101-5ec99f83aff1",
524-
"dependsOn": []
525-
},
526526
{
527527
"ref": "pkg:golang/github.com/russross/blackfriday@v1.5.2",
528528
"dependsOn": []
529529
}
530530
]
531-
}
531+
}

0 commit comments

Comments
 (0)