From cf5aa336e660e4c98481ebf8d15dd4e54c38581e Mon Sep 17 00:00:00 2001 From: DmitriyLewen <91113035+DmitriyLewen@users.noreply.github.com> Date: Mon, 10 Jun 2024 12:30:27 +0600 Subject: [PATCH] fix(nodejs): fix infinite loop when package link from `package-lock.json` file is broken (#6858) --- pkg/dependency/parser/nodejs/npm/parse.go | 16 ++++++++++--- .../parser/nodejs/npm/parse_test.go | 6 +++++ .../testdata/package-lock_v3_broken_link.json | 24 +++++++++++++++++++ 3 files changed, 43 insertions(+), 3 deletions(-) create mode 100644 pkg/dependency/parser/nodejs/npm/testdata/package-lock_v3_broken_link.json diff --git a/pkg/dependency/parser/nodejs/npm/parse.go b/pkg/dependency/parser/nodejs/npm/parse.go index ec5d654e7469..6e99cdd1bfcb 100644 --- a/pkg/dependency/parser/nodejs/npm/parse.go +++ b/pkg/dependency/parser/nodejs/npm/parse.go @@ -194,8 +194,16 @@ func (p *Parser) parseV2(packages map[string]Package) ([]ftypes.Package, []ftype // node_modules/func1 -> link to target // see `package-lock_v3_with_workspace.json` to better understanding func (p *Parser) resolveLinks(packages map[string]Package) { - links := lo.PickBy(packages, func(_ string, pkg Package) bool { - return pkg.Link + links := lo.PickBy(packages, func(pkgPath string, pkg Package) bool { + if !pkg.Link { + return false + } + if pkg.Resolved == "" { + p.logger.Warn("`package-lock.json` contains broken link with empty `resolved` field. This package will be skipped to avoid receiving an empty package", log.String("pkg", pkgPath)) + delete(packages, pkgPath) + return false + } + return true }) // Early return if len(links) == 0 { @@ -208,7 +216,9 @@ func (p *Parser) resolveLinks(packages map[string]Package) { } workspaces := rootPkg.Workspaces - for pkgPath, pkg := range packages { + // Changing the map during the map iteration causes unexpected behavior, + // so we need to iterate over the cloned `packages` map, but change the original `packages` map. + for pkgPath, pkg := range maps.Clone(packages) { for linkPath, link := range links { if !strings.HasPrefix(pkgPath, link.Resolved) { continue diff --git a/pkg/dependency/parser/nodejs/npm/parse_test.go b/pkg/dependency/parser/nodejs/npm/parse_test.go index 68f5d1e8491f..7b0b5042c831 100644 --- a/pkg/dependency/parser/nodejs/npm/parse_test.go +++ b/pkg/dependency/parser/nodejs/npm/parse_test.go @@ -53,6 +53,12 @@ func TestParse(t *testing.T) { want: npmV3WithoutRootDepsField, wantDeps: npmV3WithoutRootDepsFieldDeps, }, + { + name: "lock version v3 with broken link", + file: "testdata/package-lock_v3_broken_link.json", + want: nil, + wantDeps: nil, + }, } for _, tt := range tests { diff --git a/pkg/dependency/parser/nodejs/npm/testdata/package-lock_v3_broken_link.json b/pkg/dependency/parser/nodejs/npm/testdata/package-lock_v3_broken_link.json new file mode 100644 index 000000000000..d01c0fe3e996 --- /dev/null +++ b/pkg/dependency/parser/nodejs/npm/testdata/package-lock_v3_broken_link.json @@ -0,0 +1,24 @@ +{ + "name": "node_v3_without_direct_deps", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "node_v3_without_direct_deps", + "version": "1.0.0", + "license": "ISC" + }, + "functions/func1": { + "version": "1.0.0", + "license": "ISC", + "dependencies": { + "debug": "^2.6.9" + } + }, + "node_modules/func1": { + "resolved": "", + "link": true + } + } +} \ No newline at end of file