From 682878a3c78a69d8e1e65fe158363a14d50c3771 Mon Sep 17 00:00:00 2001 From: Adva Oren Date: Thu, 27 Aug 2026 15:35:45 +0300 Subject: [PATCH 1/2] refactor(cargo): improve lock parse error context and remove dead null-guards Add lock file path and error message to the parseCargoLockHashes catch block for descriptive diagnostics, and remove the dead hashMap null-guards in addTransitiveDeps and addDirectDeps (parseCargoLockHashes always returns a Map). Add a test covering the malformed Cargo.lock warning. Implements TC-5729 Assisted-by: Claude Code --- src/providers/rust_cargo.js | 6 +- test/providers/rust_cargo.test.js | 21 +++- .../cargo_malformed_lock_test/Cargo.lock | 4 + .../cargo_malformed_lock_test/Cargo.toml | 11 ++ .../cargo_metadata.json | 110 ++++++++++++++++++ 5 files changed, 148 insertions(+), 4 deletions(-) create mode 100644 test/providers/tst_manifests/cargo/cargo_malformed_lock_test/Cargo.lock create mode 100644 test/providers/tst_manifests/cargo/cargo_malformed_lock_test/Cargo.toml create mode 100644 test/providers/tst_manifests/cargo/cargo_malformed_lock_test/cargo_metadata.json diff --git a/src/providers/rust_cargo.js b/src/providers/rust_cargo.js index 58310ed2..b340e35a 100644 --- a/src/providers/rust_cargo.js +++ b/src/providers/rust_cargo.js @@ -299,7 +299,7 @@ function parseCargoLockHashes(manifestDir, opts = {}) { } } } catch (error) { - console.warn('Failed to parse Cargo.lock for hashes, SBOM will be generated without hashes') + console.warn(`Failed to parse Cargo.lock at ${lockPath} for hashes: ${error.message}. SBOM will be generated without hashes.`) } return hashMap } @@ -470,7 +470,7 @@ function addTransitiveDeps(sbom, metadata, packageId, ignoredDeps, visited, hash ? toPathDepPurl(depPackage.name, depPackage.version) : toPurl(depPackage.name, depPackage.version) - let hashes = hashMap ? hashMap.get(`${depPackage.name}@${depPackage.version}`) : undefined + let hashes = hashMap.get(`${depPackage.name}@${depPackage.version}`) sbom.addDependency(sourcePurl, depPurl, undefined, hashes) addTransitiveDeps(sbom, metadata, depId, ignoredDeps, visited, hashMap) } @@ -501,7 +501,7 @@ function addDirectDeps(sbom, metadata, packageId, parentPurl, ignoredDeps, hashM ? toPathDepPurl(depPackage.name, depPackage.version) : toPurl(depPackage.name, depPackage.version) - let hashes = hashMap ? hashMap.get(`${depPackage.name}@${depPackage.version}`) : undefined + let hashes = hashMap.get(`${depPackage.name}@${depPackage.version}`) sbom.addDependency(parentPurl, depPurl, undefined, hashes) } } diff --git a/test/providers/rust_cargo.test.js b/test/providers/rust_cargo.test.js index ffabcad1..94d49b02 100644 --- a/test/providers/rust_cargo.test.js +++ b/test/providers/rust_cargo.test.js @@ -3,7 +3,7 @@ import path from 'path' import { expect } from 'chai' import esmock from 'esmock' -import { useFakeTimers } from 'sinon' +import { useFakeTimers, spy } from 'sinon' import { availableProviders, match } from '../../src/provider.js' import rustCargo from '../../src/providers/rust_cargo.js' @@ -625,4 +625,23 @@ suite('testing rust-cargo workspace license inheritance', () => { let sbom = await getParsedSbom(workspaceLicenseMissingDir, 'stack') expect(sbom.metadata.component.licenses).to.be.undefined }).timeout(10000) + + const malformedLockDir = 'test/providers/tst_manifests/cargo/cargo_malformed_lock_test' + + /** Verifies error message includes lock file path and error detail when Cargo.lock is malformed. */ + test('verify error message includes lock file path and error detail for malformed Cargo.lock', async () => { + let warnSpy = spy(console, 'warn') + try { + await getParsedSbom(malformedLockDir, 'stack') + expect(warnSpy.called).to.be.true + let warnCall = warnSpy.getCall(0) + let message = warnCall.args[0] + expect(message).to.include('Failed to parse Cargo.lock at') + expect(message).to.include('malformed_lock_test/Cargo.lock') + expect(message).to.include('for hashes:') + expect(message).to.include('SBOM will be generated without hashes.') + } finally { + warnSpy.restore() + } + }).timeout(10000) }); diff --git a/test/providers/tst_manifests/cargo/cargo_malformed_lock_test/Cargo.lock b/test/providers/tst_manifests/cargo/cargo_malformed_lock_test/Cargo.lock new file mode 100644 index 00000000..93c73a58 --- /dev/null +++ b/test/providers/tst_manifests/cargo/cargo_malformed_lock_test/Cargo.lock @@ -0,0 +1,4 @@ +# This is a malformed Cargo.lock file for testing error handling +[package +name = "broken" +this is not valid TOML syntax diff --git a/test/providers/tst_manifests/cargo/cargo_malformed_lock_test/Cargo.toml b/test/providers/tst_manifests/cargo/cargo_malformed_lock_test/Cargo.toml new file mode 100644 index 00000000..c3206cfc --- /dev/null +++ b/test/providers/tst_manifests/cargo/cargo_malformed_lock_test/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "my-test-crate" +version = "0.1.0" +edition = "2021" + +[dependencies] +serde = "1.0.193" +tokio = { version = "1.35.0", features = ["full"] } + +[dev-dependencies] +tempfile = "3.8.0" diff --git a/test/providers/tst_manifests/cargo/cargo_malformed_lock_test/cargo_metadata.json b/test/providers/tst_manifests/cargo/cargo_malformed_lock_test/cargo_metadata.json new file mode 100644 index 00000000..1239801d --- /dev/null +++ b/test/providers/tst_manifests/cargo/cargo_malformed_lock_test/cargo_metadata.json @@ -0,0 +1,110 @@ +{ + "packages": [ + { + "name": "my-test-crate", + "version": "0.1.0", + "id": "path+file:///fake/path/my-test-crate#0.1.0", + "source": null, + "dependencies": [ + { "name": "serde", "kind": null }, + { "name": "tokio", "kind": null }, + { "name": "tempfile", "kind": "dev" } + ] + }, + { + "name": "serde", + "version": "1.0.193", + "id": "registry+https://github.com/rust-lang/crates.io-index#serde@1.0.193", + "source": "registry+https://github.com/rust-lang/crates.io-index", + "dependencies": [ + { "name": "serde_derive", "kind": null } + ] + }, + { + "name": "serde_derive", + "version": "1.0.193", + "id": "registry+https://github.com/rust-lang/crates.io-index#serde_derive@1.0.193", + "source": "registry+https://github.com/rust-lang/crates.io-index", + "dependencies": [] + }, + { + "name": "tokio", + "version": "1.35.0", + "id": "registry+https://github.com/rust-lang/crates.io-index#tokio@1.35.0", + "source": "registry+https://github.com/rust-lang/crates.io-index", + "dependencies": [ + { "name": "pin-project-lite", "kind": null } + ] + }, + { + "name": "pin-project-lite", + "version": "0.2.13", + "id": "registry+https://github.com/rust-lang/crates.io-index#pin-project-lite@0.2.13", + "source": "registry+https://github.com/rust-lang/crates.io-index", + "dependencies": [] + }, + { + "name": "tempfile", + "version": "3.8.0", + "id": "registry+https://github.com/rust-lang/crates.io-index#tempfile@3.8.0", + "source": "registry+https://github.com/rust-lang/crates.io-index", + "dependencies": [] + } + ], + "workspace_members": [ + "path+file:///fake/path/my-test-crate#0.1.0" + ], + "workspace_root": "/fake/path/my-test-crate", + "resolve": { + "root": "path+file:///fake/path/my-test-crate#0.1.0", + "nodes": [ + { + "id": "path+file:///fake/path/my-test-crate#0.1.0", + "deps": [ + { + "pkg": "registry+https://github.com/rust-lang/crates.io-index#serde@1.0.193", + "dep_kinds": [{ "kind": null, "target": null }] + }, + { + "pkg": "registry+https://github.com/rust-lang/crates.io-index#tokio@1.35.0", + "dep_kinds": [{ "kind": null, "target": null }] + }, + { + "pkg": "registry+https://github.com/rust-lang/crates.io-index#tempfile@3.8.0", + "dep_kinds": [{ "kind": "dev", "target": null }] + } + ] + }, + { + "id": "registry+https://github.com/rust-lang/crates.io-index#serde@1.0.193", + "deps": [ + { + "pkg": "registry+https://github.com/rust-lang/crates.io-index#serde_derive@1.0.193", + "dep_kinds": [{ "kind": null, "target": null }] + } + ] + }, + { + "id": "registry+https://github.com/rust-lang/crates.io-index#serde_derive@1.0.193", + "deps": [] + }, + { + "id": "registry+https://github.com/rust-lang/crates.io-index#tokio@1.35.0", + "deps": [ + { + "pkg": "registry+https://github.com/rust-lang/crates.io-index#pin-project-lite@0.2.13", + "dep_kinds": [{ "kind": null, "target": null }] + } + ] + }, + { + "id": "registry+https://github.com/rust-lang/crates.io-index#pin-project-lite@0.2.13", + "deps": [] + }, + { + "id": "registry+https://github.com/rust-lang/crates.io-index#tempfile@3.8.0", + "deps": [] + } + ] + } +} From 7c42c0ca1599cb07b3e0313a3e7985885cf8e1b8 Mon Sep 17 00:00:00 2001 From: Adva Oren Date: Mon, 31 Aug 2026 17:30:05 +0300 Subject: [PATCH 2/2] test(cargo): remove malformed Cargo.lock parse test --- test/providers/rust_cargo.test.js | 21 +--- .../cargo_malformed_lock_test/Cargo.lock | 4 - .../cargo_malformed_lock_test/Cargo.toml | 11 -- .../cargo_metadata.json | 110 ------------------ 4 files changed, 1 insertion(+), 145 deletions(-) delete mode 100644 test/providers/tst_manifests/cargo/cargo_malformed_lock_test/Cargo.lock delete mode 100644 test/providers/tst_manifests/cargo/cargo_malformed_lock_test/Cargo.toml delete mode 100644 test/providers/tst_manifests/cargo/cargo_malformed_lock_test/cargo_metadata.json diff --git a/test/providers/rust_cargo.test.js b/test/providers/rust_cargo.test.js index 94d49b02..ffabcad1 100644 --- a/test/providers/rust_cargo.test.js +++ b/test/providers/rust_cargo.test.js @@ -3,7 +3,7 @@ import path from 'path' import { expect } from 'chai' import esmock from 'esmock' -import { useFakeTimers, spy } from 'sinon' +import { useFakeTimers } from 'sinon' import { availableProviders, match } from '../../src/provider.js' import rustCargo from '../../src/providers/rust_cargo.js' @@ -625,23 +625,4 @@ suite('testing rust-cargo workspace license inheritance', () => { let sbom = await getParsedSbom(workspaceLicenseMissingDir, 'stack') expect(sbom.metadata.component.licenses).to.be.undefined }).timeout(10000) - - const malformedLockDir = 'test/providers/tst_manifests/cargo/cargo_malformed_lock_test' - - /** Verifies error message includes lock file path and error detail when Cargo.lock is malformed. */ - test('verify error message includes lock file path and error detail for malformed Cargo.lock', async () => { - let warnSpy = spy(console, 'warn') - try { - await getParsedSbom(malformedLockDir, 'stack') - expect(warnSpy.called).to.be.true - let warnCall = warnSpy.getCall(0) - let message = warnCall.args[0] - expect(message).to.include('Failed to parse Cargo.lock at') - expect(message).to.include('malformed_lock_test/Cargo.lock') - expect(message).to.include('for hashes:') - expect(message).to.include('SBOM will be generated without hashes.') - } finally { - warnSpy.restore() - } - }).timeout(10000) }); diff --git a/test/providers/tst_manifests/cargo/cargo_malformed_lock_test/Cargo.lock b/test/providers/tst_manifests/cargo/cargo_malformed_lock_test/Cargo.lock deleted file mode 100644 index 93c73a58..00000000 --- a/test/providers/tst_manifests/cargo/cargo_malformed_lock_test/Cargo.lock +++ /dev/null @@ -1,4 +0,0 @@ -# This is a malformed Cargo.lock file for testing error handling -[package -name = "broken" -this is not valid TOML syntax diff --git a/test/providers/tst_manifests/cargo/cargo_malformed_lock_test/Cargo.toml b/test/providers/tst_manifests/cargo/cargo_malformed_lock_test/Cargo.toml deleted file mode 100644 index c3206cfc..00000000 --- a/test/providers/tst_manifests/cargo/cargo_malformed_lock_test/Cargo.toml +++ /dev/null @@ -1,11 +0,0 @@ -[package] -name = "my-test-crate" -version = "0.1.0" -edition = "2021" - -[dependencies] -serde = "1.0.193" -tokio = { version = "1.35.0", features = ["full"] } - -[dev-dependencies] -tempfile = "3.8.0" diff --git a/test/providers/tst_manifests/cargo/cargo_malformed_lock_test/cargo_metadata.json b/test/providers/tst_manifests/cargo/cargo_malformed_lock_test/cargo_metadata.json deleted file mode 100644 index 1239801d..00000000 --- a/test/providers/tst_manifests/cargo/cargo_malformed_lock_test/cargo_metadata.json +++ /dev/null @@ -1,110 +0,0 @@ -{ - "packages": [ - { - "name": "my-test-crate", - "version": "0.1.0", - "id": "path+file:///fake/path/my-test-crate#0.1.0", - "source": null, - "dependencies": [ - { "name": "serde", "kind": null }, - { "name": "tokio", "kind": null }, - { "name": "tempfile", "kind": "dev" } - ] - }, - { - "name": "serde", - "version": "1.0.193", - "id": "registry+https://github.com/rust-lang/crates.io-index#serde@1.0.193", - "source": "registry+https://github.com/rust-lang/crates.io-index", - "dependencies": [ - { "name": "serde_derive", "kind": null } - ] - }, - { - "name": "serde_derive", - "version": "1.0.193", - "id": "registry+https://github.com/rust-lang/crates.io-index#serde_derive@1.0.193", - "source": "registry+https://github.com/rust-lang/crates.io-index", - "dependencies": [] - }, - { - "name": "tokio", - "version": "1.35.0", - "id": "registry+https://github.com/rust-lang/crates.io-index#tokio@1.35.0", - "source": "registry+https://github.com/rust-lang/crates.io-index", - "dependencies": [ - { "name": "pin-project-lite", "kind": null } - ] - }, - { - "name": "pin-project-lite", - "version": "0.2.13", - "id": "registry+https://github.com/rust-lang/crates.io-index#pin-project-lite@0.2.13", - "source": "registry+https://github.com/rust-lang/crates.io-index", - "dependencies": [] - }, - { - "name": "tempfile", - "version": "3.8.0", - "id": "registry+https://github.com/rust-lang/crates.io-index#tempfile@3.8.0", - "source": "registry+https://github.com/rust-lang/crates.io-index", - "dependencies": [] - } - ], - "workspace_members": [ - "path+file:///fake/path/my-test-crate#0.1.0" - ], - "workspace_root": "/fake/path/my-test-crate", - "resolve": { - "root": "path+file:///fake/path/my-test-crate#0.1.0", - "nodes": [ - { - "id": "path+file:///fake/path/my-test-crate#0.1.0", - "deps": [ - { - "pkg": "registry+https://github.com/rust-lang/crates.io-index#serde@1.0.193", - "dep_kinds": [{ "kind": null, "target": null }] - }, - { - "pkg": "registry+https://github.com/rust-lang/crates.io-index#tokio@1.35.0", - "dep_kinds": [{ "kind": null, "target": null }] - }, - { - "pkg": "registry+https://github.com/rust-lang/crates.io-index#tempfile@3.8.0", - "dep_kinds": [{ "kind": "dev", "target": null }] - } - ] - }, - { - "id": "registry+https://github.com/rust-lang/crates.io-index#serde@1.0.193", - "deps": [ - { - "pkg": "registry+https://github.com/rust-lang/crates.io-index#serde_derive@1.0.193", - "dep_kinds": [{ "kind": null, "target": null }] - } - ] - }, - { - "id": "registry+https://github.com/rust-lang/crates.io-index#serde_derive@1.0.193", - "deps": [] - }, - { - "id": "registry+https://github.com/rust-lang/crates.io-index#tokio@1.35.0", - "deps": [ - { - "pkg": "registry+https://github.com/rust-lang/crates.io-index#pin-project-lite@0.2.13", - "dep_kinds": [{ "kind": null, "target": null }] - } - ] - }, - { - "id": "registry+https://github.com/rust-lang/crates.io-index#pin-project-lite@0.2.13", - "deps": [] - }, - { - "id": "registry+https://github.com/rust-lang/crates.io-index#tempfile@3.8.0", - "deps": [] - } - ] - } -}