@cdxgen/cdxgen 13.3.0 → 13.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (255) hide show
  1. package/README.md +9 -6
  2. package/bin/cdxgen.js +90 -26
  3. package/bin/evinse.js +18 -1
  4. package/bin/repl.js +86 -0
  5. package/bin/sign.js +45 -15
  6. package/bin/tracebom.js +5 -1
  7. package/bin/validate.js +84 -18
  8. package/bin/verify.js +155 -86
  9. package/data/helpers/init.gradle +46 -21
  10. package/data/remediations.json +61 -0
  11. package/data/rules/dependency-sources.yaml +67 -0
  12. package/data/rules/scala-evidence.yaml +115 -0
  13. package/lib/audit/index.js +38 -6
  14. package/lib/cli/index.js +113 -54
  15. package/lib/cli/jsBom.js +50 -29
  16. package/lib/cli/jvmBom.js +665 -138
  17. package/lib/cli/managedBom.js +320 -200
  18. package/lib/cli/nativeBom.js +139 -30
  19. package/lib/core/activity.js +12 -0
  20. package/lib/core/env.js +17 -9
  21. package/lib/core/fs.js +3 -2
  22. package/lib/core/httpClient.js +159 -24
  23. package/lib/core/pluginRun.js +184 -0
  24. package/lib/core/processTree.js +100 -0
  25. package/lib/core/runState.js +39 -0
  26. package/lib/core/toml.js +72 -0
  27. package/lib/ecosystems/asarutils.js +4 -2
  28. package/lib/ecosystems/bunutils.js +5 -7
  29. package/lib/ecosystems/cargoCache.js +231 -8
  30. package/lib/ecosystems/cmakeResolver.js +10 -21
  31. package/lib/ecosystems/cppBuildContext.js +631 -0
  32. package/lib/ecosystems/cppEvidence.js +337 -22
  33. package/lib/ecosystems/cppIncludeResolver.js +294 -0
  34. package/lib/ecosystems/denoutils.js +93 -66
  35. package/lib/ecosystems/ecosystems.js +1967 -788
  36. package/lib/ecosystems/golem.js +28 -12
  37. package/lib/ecosystems/gradleutils.js +314 -30
  38. package/lib/ecosystems/jsEvidence.js +85 -24
  39. package/lib/ecosystems/kosi.js +21 -4
  40. package/lib/ecosystems/npmutils.js +64 -0
  41. package/lib/ecosystems/parsers-dotnet.js +567 -151
  42. package/lib/ecosystems/parsers-elm.js +46 -28
  43. package/lib/ecosystems/parsers-gleam.js +3 -4
  44. package/lib/ecosystems/parsers-go.js +324 -61
  45. package/lib/ecosystems/parsers-js.js +92 -26
  46. package/lib/ecosystems/parsers-julia.js +2 -3
  47. package/lib/ecosystems/parsers-jvm.js +204 -12
  48. package/lib/ecosystems/parsers-misc.js +182 -55
  49. package/lib/ecosystems/parsers-python.js +141 -10
  50. package/lib/ecosystems/parsers-rust.js +4 -4
  51. package/lib/ecosystems/parsers-terraform.js +1573 -19
  52. package/lib/ecosystems/pythonutils.js +102 -6
  53. package/lib/ecosystems/remote/huggingface.js +3 -0
  54. package/lib/ecosystems/rubyutils.js +67 -17
  55. package/lib/ecosystems/rusi.js +22 -9
  56. package/lib/ecosystems/sbtutils.js +243 -187
  57. package/lib/ecosystems/scalacliutils.js +342 -0
  58. package/lib/ecosystems/scalasem.js +2618 -0
  59. package/lib/ecosystems/terraformRegistry.js +288 -0
  60. package/lib/ecosystems/utils.js +2 -0
  61. package/lib/ecosystems/vendoredCode.js +156 -0
  62. package/lib/evinser/evinser.js +578 -69
  63. package/lib/helpers/agenticToolutils.js +2 -2
  64. package/lib/helpers/bomSigner.js +576 -128
  65. package/lib/inventory/aiCollector.js +4 -1
  66. package/lib/inventory/analyzer.js +110 -127
  67. package/lib/inventory/atomUtils.js +171 -48
  68. package/lib/inventory/auditCategories.js +7 -0
  69. package/lib/inventory/buildToolRateLimit.js +144 -0
  70. package/lib/inventory/cacheCommands.js +2 -1
  71. package/lib/inventory/cmakeBuildDirs.js +254 -0
  72. package/lib/inventory/deps.js +501 -124
  73. package/lib/inventory/dosai.js +248 -44
  74. package/lib/inventory/dosaiParsers.js +310 -15
  75. package/lib/inventory/dynamic.js +52 -6
  76. package/lib/inventory/evidenceUtils.js +154 -109
  77. package/lib/inventory/fetchBatch.js +574 -38
  78. package/lib/inventory/fetchDiskCache.js +597 -0
  79. package/lib/inventory/forgeEnricher.js +14 -5
  80. package/lib/inventory/jvmLocalRepos.js +917 -0
  81. package/lib/inventory/mcpConfigParser.js +2 -3
  82. package/lib/inventory/parentSelection.js +146 -0
  83. package/lib/inventory/rustFormulationParser.js +3 -4
  84. package/lib/inventory/scalaCoords.js +153 -0
  85. package/lib/inventory/source.js +8 -8
  86. package/lib/inventory/spdx.js +101 -0
  87. package/lib/inventory/traceRunner.js +88 -7
  88. package/lib/inventory/tracedCrypto.js +294 -0
  89. package/lib/managers/docker.js +33 -32
  90. package/lib/managers/oci.js +24 -2
  91. package/lib/parsers/cmakeCache.js +69 -0
  92. package/lib/parsers/cmakeLists.js +16 -2
  93. package/lib/parsers/cmakePresets.js +400 -0
  94. package/lib/parsers/compileCommands.js +557 -0
  95. package/lib/parsers/strictJson.js +322 -0
  96. package/lib/parsers/terraformConfig.js +1073 -0
  97. package/lib/parsers/terraformSource.js +652 -0
  98. package/lib/server/server.js +23 -0
  99. package/lib/stages/postgen/introspection/markers.js +1 -1
  100. package/lib/stages/postgen/postgen.js +15 -1
  101. package/lib/stages/postgen/specVersionCompat.js +32 -0
  102. package/lib/validator/index.js +25 -10
  103. package/lib/validator/reporters/json.js +1 -0
  104. package/package.json +21 -21
  105. package/types/lib/audit/index.d.ts.map +1 -1
  106. package/types/lib/cli/index.d.ts.map +1 -1
  107. package/types/lib/cli/jsBom.d.ts.map +1 -1
  108. package/types/lib/cli/jvmBom.d.ts +24 -1
  109. package/types/lib/cli/jvmBom.d.ts.map +1 -1
  110. package/types/lib/cli/managedBom.d.ts +7 -4
  111. package/types/lib/cli/managedBom.d.ts.map +1 -1
  112. package/types/lib/cli/nativeBom.d.ts +3 -1
  113. package/types/lib/cli/nativeBom.d.ts.map +1 -1
  114. package/types/lib/core/activity.d.ts.map +1 -1
  115. package/types/lib/core/env.d.ts +9 -0
  116. package/types/lib/core/env.d.ts.map +1 -1
  117. package/types/lib/core/fs.d.ts.map +1 -1
  118. package/types/lib/core/httpClient.d.ts +13 -8
  119. package/types/lib/core/httpClient.d.ts.map +1 -1
  120. package/types/lib/core/pluginRun.d.ts +97 -0
  121. package/types/lib/core/pluginRun.d.ts.map +1 -0
  122. package/types/lib/core/processTree.d.ts +36 -0
  123. package/types/lib/core/processTree.d.ts.map +1 -0
  124. package/types/lib/core/runState.d.ts +29 -0
  125. package/types/lib/core/runState.d.ts.map +1 -0
  126. package/types/lib/core/toml.d.ts +20 -0
  127. package/types/lib/core/toml.d.ts.map +1 -0
  128. package/types/lib/ecosystems/asarutils.d.ts.map +1 -1
  129. package/types/lib/ecosystems/bunutils.d.ts.map +1 -1
  130. package/types/lib/ecosystems/cargoCache.d.ts +12 -0
  131. package/types/lib/ecosystems/cargoCache.d.ts.map +1 -1
  132. package/types/lib/ecosystems/cmakeResolver.d.ts +5 -3
  133. package/types/lib/ecosystems/cmakeResolver.d.ts.map +1 -1
  134. package/types/lib/ecosystems/cppBuildContext.d.ts +55 -0
  135. package/types/lib/ecosystems/cppBuildContext.d.ts.map +1 -0
  136. package/types/lib/ecosystems/cppEvidence.d.ts +18 -2
  137. package/types/lib/ecosystems/cppEvidence.d.ts.map +1 -1
  138. package/types/lib/ecosystems/cppIncludeResolver.d.ts +92 -0
  139. package/types/lib/ecosystems/cppIncludeResolver.d.ts.map +1 -0
  140. package/types/lib/ecosystems/denoutils.d.ts +9 -3
  141. package/types/lib/ecosystems/denoutils.d.ts.map +1 -1
  142. package/types/lib/ecosystems/ecosystems.d.ts +62 -13
  143. package/types/lib/ecosystems/ecosystems.d.ts.map +1 -1
  144. package/types/lib/ecosystems/golem.d.ts.map +1 -1
  145. package/types/lib/ecosystems/gradleutils.d.ts +50 -0
  146. package/types/lib/ecosystems/gradleutils.d.ts.map +1 -1
  147. package/types/lib/ecosystems/jsEvidence.d.ts.map +1 -1
  148. package/types/lib/ecosystems/kosi.d.ts.map +1 -1
  149. package/types/lib/ecosystems/npmutils.d.ts +15 -0
  150. package/types/lib/ecosystems/npmutils.d.ts.map +1 -1
  151. package/types/lib/ecosystems/parsers-dotnet.d.ts +118 -4
  152. package/types/lib/ecosystems/parsers-dotnet.d.ts.map +1 -1
  153. package/types/lib/ecosystems/parsers-elm.d.ts.map +1 -1
  154. package/types/lib/ecosystems/parsers-gleam.d.ts.map +1 -1
  155. package/types/lib/ecosystems/parsers-go.d.ts +44 -2
  156. package/types/lib/ecosystems/parsers-go.d.ts.map +1 -1
  157. package/types/lib/ecosystems/parsers-js.d.ts +6 -1
  158. package/types/lib/ecosystems/parsers-js.d.ts.map +1 -1
  159. package/types/lib/ecosystems/parsers-julia.d.ts.map +1 -1
  160. package/types/lib/ecosystems/parsers-jvm.d.ts +46 -1
  161. package/types/lib/ecosystems/parsers-jvm.d.ts.map +1 -1
  162. package/types/lib/ecosystems/parsers-misc.d.ts +3 -1
  163. package/types/lib/ecosystems/parsers-misc.d.ts.map +1 -1
  164. package/types/lib/ecosystems/parsers-python.d.ts +53 -3
  165. package/types/lib/ecosystems/parsers-python.d.ts.map +1 -1
  166. package/types/lib/ecosystems/parsers-terraform.d.ts +38 -21
  167. package/types/lib/ecosystems/parsers-terraform.d.ts.map +1 -1
  168. package/types/lib/ecosystems/pythonutils.d.ts +27 -2
  169. package/types/lib/ecosystems/pythonutils.d.ts.map +1 -1
  170. package/types/lib/ecosystems/remote/huggingface.d.ts.map +1 -1
  171. package/types/lib/ecosystems/rubyutils.d.ts +21 -1
  172. package/types/lib/ecosystems/rubyutils.d.ts.map +1 -1
  173. package/types/lib/ecosystems/rusi.d.ts.map +1 -1
  174. package/types/lib/ecosystems/sbtutils.d.ts +88 -2
  175. package/types/lib/ecosystems/sbtutils.d.ts.map +1 -1
  176. package/types/lib/ecosystems/scalacliutils.d.ts +56 -0
  177. package/types/lib/ecosystems/scalacliutils.d.ts.map +1 -0
  178. package/types/lib/ecosystems/scalasem.d.ts +311 -0
  179. package/types/lib/ecosystems/scalasem.d.ts.map +1 -0
  180. package/types/lib/ecosystems/terraformRegistry.d.ts +9 -0
  181. package/types/lib/ecosystems/terraformRegistry.d.ts.map +1 -0
  182. package/types/lib/ecosystems/utils.d.ts +2 -2
  183. package/types/lib/ecosystems/utils.d.ts.map +1 -1
  184. package/types/lib/ecosystems/vendoredCode.d.ts +32 -0
  185. package/types/lib/ecosystems/vendoredCode.d.ts.map +1 -0
  186. package/types/lib/evinser/evinser.d.ts +75 -8
  187. package/types/lib/evinser/evinser.d.ts.map +1 -1
  188. package/types/lib/helpers/bomSigner.d.ts +80 -4
  189. package/types/lib/helpers/bomSigner.d.ts.map +1 -1
  190. package/types/lib/inventory/aiCollector.d.ts.map +1 -1
  191. package/types/lib/inventory/analyzer.d.ts.map +1 -1
  192. package/types/lib/inventory/atomUtils.d.ts +82 -10
  193. package/types/lib/inventory/atomUtils.d.ts.map +1 -1
  194. package/types/lib/inventory/auditCategories.d.ts +3 -0
  195. package/types/lib/inventory/auditCategories.d.ts.map +1 -1
  196. package/types/lib/inventory/buildToolRateLimit.d.ts +38 -0
  197. package/types/lib/inventory/buildToolRateLimit.d.ts.map +1 -0
  198. package/types/lib/inventory/cacheCommands.d.ts +2 -1
  199. package/types/lib/inventory/cacheCommands.d.ts.map +1 -1
  200. package/types/lib/inventory/cmakeBuildDirs.d.ts +58 -0
  201. package/types/lib/inventory/cmakeBuildDirs.d.ts.map +1 -0
  202. package/types/lib/inventory/deps.d.ts +59 -1
  203. package/types/lib/inventory/deps.d.ts.map +1 -1
  204. package/types/lib/inventory/dosai.d.ts +57 -8
  205. package/types/lib/inventory/dosai.d.ts.map +1 -1
  206. package/types/lib/inventory/dosaiParsers.d.ts +53 -7
  207. package/types/lib/inventory/dosaiParsers.d.ts.map +1 -1
  208. package/types/lib/inventory/dynamic.d.ts +10 -0
  209. package/types/lib/inventory/dynamic.d.ts.map +1 -1
  210. package/types/lib/inventory/evidenceUtils.d.ts +19 -4
  211. package/types/lib/inventory/evidenceUtils.d.ts.map +1 -1
  212. package/types/lib/inventory/fetchBatch.d.ts +83 -14
  213. package/types/lib/inventory/fetchBatch.d.ts.map +1 -1
  214. package/types/lib/inventory/fetchDiskCache.d.ts +169 -0
  215. package/types/lib/inventory/fetchDiskCache.d.ts.map +1 -0
  216. package/types/lib/inventory/forgeEnricher.d.ts.map +1 -1
  217. package/types/lib/inventory/jvmLocalRepos.d.ts +178 -0
  218. package/types/lib/inventory/jvmLocalRepos.d.ts.map +1 -0
  219. package/types/lib/inventory/mcpConfigParser.d.ts.map +1 -1
  220. package/types/lib/inventory/parentSelection.d.ts +34 -0
  221. package/types/lib/inventory/parentSelection.d.ts.map +1 -0
  222. package/types/lib/inventory/rustFormulationParser.d.ts.map +1 -1
  223. package/types/lib/inventory/scalaCoords.d.ts +80 -0
  224. package/types/lib/inventory/scalaCoords.d.ts.map +1 -0
  225. package/types/lib/inventory/source.d.ts.map +1 -1
  226. package/types/lib/inventory/spdx.d.ts +9 -0
  227. package/types/lib/inventory/spdx.d.ts.map +1 -1
  228. package/types/lib/inventory/traceRunner.d.ts +12 -0
  229. package/types/lib/inventory/traceRunner.d.ts.map +1 -1
  230. package/types/lib/inventory/tracedCrypto.d.ts +42 -0
  231. package/types/lib/inventory/tracedCrypto.d.ts.map +1 -0
  232. package/types/lib/managers/docker.d.ts.map +1 -1
  233. package/types/lib/managers/oci.d.ts +3 -1
  234. package/types/lib/managers/oci.d.ts.map +1 -1
  235. package/types/lib/parsers/cmakeCache.d.ts +16 -0
  236. package/types/lib/parsers/cmakeCache.d.ts.map +1 -1
  237. package/types/lib/parsers/cmakeLists.d.ts +2 -1
  238. package/types/lib/parsers/cmakeLists.d.ts.map +1 -1
  239. package/types/lib/parsers/cmakePresets.d.ts +78 -0
  240. package/types/lib/parsers/cmakePresets.d.ts.map +1 -0
  241. package/types/lib/parsers/compileCommands.d.ts +115 -0
  242. package/types/lib/parsers/compileCommands.d.ts.map +1 -0
  243. package/types/lib/parsers/strictJson.d.ts +46 -0
  244. package/types/lib/parsers/strictJson.d.ts.map +1 -0
  245. package/types/lib/parsers/terraformConfig.d.ts +70 -0
  246. package/types/lib/parsers/terraformConfig.d.ts.map +1 -0
  247. package/types/lib/parsers/terraformSource.d.ts +66 -0
  248. package/types/lib/parsers/terraformSource.d.ts.map +1 -0
  249. package/types/lib/server/server.d.ts.map +1 -1
  250. package/types/lib/stages/postgen/postgen.d.ts.map +1 -1
  251. package/types/lib/stages/postgen/specVersionCompat.d.ts.map +1 -1
  252. package/types/lib/validator/index.d.ts +8 -0
  253. package/types/lib/validator/index.d.ts.map +1 -1
  254. package/types/lib/validator/reporters/json.d.ts.map +1 -1
  255. package/lib/third-party/arborist/snapshots/calc-dep-flags.test.cjs +0 -462
@@ -20,7 +20,9 @@ import {
20
20
  safeSpawnSync,
21
21
  safeWriteChunksSync,
22
22
  safeWriteSync,
23
+ TIMEOUT_MS,
23
24
  } from "../core/fs.js";
25
+ import { formatDuration, reapProcessTree } from "../core/processTree.js";
24
26
  import {
25
27
  indexJsonObject,
26
28
  MAX_JSON_TEXT_BYTES,
@@ -675,6 +677,69 @@ export function readDosaiDataFlowReport(reportFile, options = {}) {
675
677
  });
676
678
  }
677
679
 
680
+ /**
681
+ * Whether a dosai run ended because it ran out of time.
682
+ *
683
+ * dosai has no limit of its own: cdxgen's spawn timeout
684
+ * (CDXGEN_TIMEOUT_MS) stops the process, which reaches the caller only as an
685
+ * ETIMEDOUT error looking like any other failure (issue 4438).
686
+ *
687
+ * @param {Object} result spawnSync result
688
+ * @returns {boolean} true when the run was stopped by the spawn timeout
689
+ */
690
+ export function dosaiRunTimedOut(result) {
691
+ return result?.error?.code === "ETIMEDOUT";
692
+ }
693
+
694
+ /**
695
+ * How a dosai run was stopped before it could finish, if it was.
696
+ *
697
+ * Besides the spawn timeout, a run is stopped when its console output outgrows
698
+ * CDXGEN_MAX_BUFFER (ENOBUFS) or a signal ends it, such as the kernel's
699
+ * out-of-memory killer. A stopped run can leave its slice half written.
700
+ *
701
+ * @param {Object} result spawnSync result
702
+ * @returns {"timeout"|"buffer"|"signal"|undefined} Why the run was stopped, or undefined when it ended by itself
703
+ */
704
+ export function dosaiRunStopReason(result) {
705
+ if (dosaiRunTimedOut(result)) {
706
+ return "timeout";
707
+ }
708
+ if (result?.error?.code === "ENOBUFS") {
709
+ return "buffer";
710
+ }
711
+ if (result?.signal) {
712
+ return "signal";
713
+ }
714
+ return undefined;
715
+ }
716
+
717
+ // The output files of the dosai runs of this process that were stopped, so a
718
+ // caller does not follow the stop warning with advice for another failure.
719
+ const stoppedDosaiOutputs = new Set();
720
+
721
+ /**
722
+ * Whether the dosai run that should have written a file was stopped (by the
723
+ * time limit, the output buffer limit, or a signal) and already reported so.
724
+ *
725
+ * @param {string} outputFile Output file passed to the dosai run
726
+ * @returns {boolean} true when the run was stopped
727
+ */
728
+ export function dosaiRunWasStopped(outputFile) {
729
+ const outputPath = safeDosaiPath(outputFile);
730
+ return Boolean(outputPath && stoppedDosaiOutputs.has(outputPath));
731
+ }
732
+
733
+ function dosaiStopMessage(reason, signal) {
734
+ if (reason === "timeout") {
735
+ return `WARN: dosai did not finish within ${formatDuration(TIMEOUT_MS)} and was stopped, so its slices are missing or incomplete. Allow more time with CDXGEN_TIMEOUT_MS (milliseconds, every external command).`;
736
+ }
737
+ if (reason === "buffer") {
738
+ return "WARN: dosai wrote more console output than CDXGEN_MAX_BUFFER allows and was stopped, so its slices are missing or incomplete. Raise CDXGEN_MAX_BUFFER (bytes).";
739
+ }
740
+ return `WARN: dosai was stopped by ${signal || "a signal"} before it finished, so its slices are missing or incomplete. A SIGKILL usually means the system ran out of memory.`;
741
+ }
742
+
678
743
  /**
679
744
  * Run a dosai subcommand ("methods", "dataflows", or "crypto") against a source
680
745
  * tree and write its JSON output to the given file.
@@ -720,6 +785,8 @@ export function runDosaiCommand(command, src, outputFile, options = {}) {
720
785
  if (DEBUG_MODE) {
721
786
  console.log("Executing", executable, [...args, ...excludeArgs].join(" "));
722
787
  }
788
+ stoppedDosaiOutputs.delete(outputPath);
789
+ let startedAt = Date.now();
723
790
  let result = safeSpawnSync(executable, [...args, ...excludeArgs], {
724
791
  cwd: srcPath,
725
792
  shell: false,
@@ -735,6 +802,7 @@ export function runDosaiCommand(command, src, outputFile, options = {}) {
735
802
  warnDosaiOnce(
736
803
  "This dosai build does not support --exclude, so it runs without the exclude patterns. Upgrade dosai or set DOSAI_CMD to a newer build to honour them.",
737
804
  );
805
+ startedAt = Date.now();
738
806
  result = safeSpawnSync(executable, args, {
739
807
  cwd: srcPath,
740
808
  shell: false,
@@ -756,6 +824,22 @@ export function runDosaiCommand(command, src, outputFile, options = {}) {
756
824
  "Alternatively, download the dosai self-contained binary (-full suffix) from https://github.com/owasp-dep-scan/dosai/releases and set DOSAI_CMD to its location.",
757
825
  );
758
826
  }
827
+ const stopReason = dosaiRunStopReason(result);
828
+ if (stopReason) {
829
+ // Visible without debug output: otherwise a stopped dosai reads like any
830
+ // other failure while the scan silently loses its .NET evidence.
831
+ console.warn(dosaiStopMessage(stopReason, result.signal));
832
+ // Windows kills only the dosai process itself, and the build hosts it
833
+ // started would keep running.
834
+ reapProcessTree(result.pid, startedAt, Date.now(), "dosai");
835
+ // A stopped dosai can leave its output half written, and the caller would
836
+ // read the truncated file back as a slice.
837
+ if (safeExistsSync(outputPath)) {
838
+ safeRmSync(outputPath, { force: true });
839
+ }
840
+ stoppedDosaiOutputs.add(outputPath);
841
+ return false;
842
+ }
759
843
  if (result?.status !== 0 || result?.error || !safeExistsSync(outputPath)) {
760
844
  if (DEBUG_MODE) {
761
845
  if (result?.stderr || result?.stdout) {
@@ -958,21 +1042,63 @@ export function persistDosaiSemanticsReport(
958
1042
  * Build a purl alias map for a list of components.
959
1043
  *
960
1044
  * @param {Object[]} [components] Component objects with purl fields
961
- * @returns {Map<string, string>} Map of exact purls and normalized type/namespace/name keys to canonical purls
1045
+ * @param {Object} [options] Options passed to {@link buildDosaiPurlAliasMap} (`srcPath`)
1046
+ * @returns {Map<string, string>} Map of component purls, carrying the version-free identity index
962
1047
  */
963
- export function buildPurlAliasMap(components = []) {
964
- return buildDosaiPurlAliasMap(components);
1048
+ export function buildPurlAliasMap(components = [], options = {}) {
1049
+ return buildDosaiPurlAliasMap(components, options);
965
1050
  }
966
1051
 
967
1052
  /**
968
- * Resolve a possibly-aliased purl to the canonical component purl.
1053
+ * Resolve a dosai purl to the canonical component purl, by version, and for a
1054
+ * package the BOM holds in several versions by the record's location.
969
1055
  *
970
1056
  * @param {string} purl Purl from a dosai report
971
1057
  * @param {Map<string, string>} purlAliasMap Alias map built by buildPurlAliasMap
972
- * @returns {string|undefined} Canonical component purl, the input purl when unaliased, or undefined when empty
1058
+ * @param {string} [location] Source location of the dosai record
1059
+ * @returns {string|undefined} Canonical component purl, the input purl when the BOM has no such package,
1060
+ * or undefined when empty or ambiguous
973
1061
  */
974
- export function resolveComponentPurl(purl, purlAliasMap) {
975
- return resolveDosaiComponentPurl(purl, purlAliasMap);
1062
+ export function resolveComponentPurl(purl, purlAliasMap, location) {
1063
+ return resolveDosaiComponentPurl(purl, purlAliasMap, location);
1064
+ }
1065
+
1066
+ /**
1067
+ * The component purls one PackageReachability fact belongs to: each of its
1068
+ * locations resolved in its own project (a versionless purl from unrestored
1069
+ * projects spans several), else the purl alone.
1070
+ *
1071
+ * @param {Object} reachability dosai PackageReachability entry
1072
+ * @param {Map<string, string>} purlAliasMap Alias map built by buildPurlAliasMap
1073
+ * @param {Object} [graph] Call graph lookups `{ edgesById, nodesById }`
1074
+ * @returns {string[]} Component purls
1075
+ */
1076
+ function reachabilityComponentPurls(reachability, purlAliasMap, graph = {}) {
1077
+ const locations = [
1078
+ ...(reachability.SourceLocations || []).map((location) =>
1079
+ dosaiSourceLocation(location),
1080
+ ),
1081
+ ...(reachability.EdgeIds || []).map((edgeId) =>
1082
+ dosaiSourceLocation(graph.edgesById?.get(edgeId)),
1083
+ ),
1084
+ ...(reachability.NodeIds || []).map((nodeId) =>
1085
+ dosaiSourceLocationFromNode(graph.nodesById?.get(nodeId)),
1086
+ ),
1087
+ ].filter(Boolean);
1088
+ const purls = new Set(
1089
+ locations
1090
+ .map((location) =>
1091
+ resolveComponentPurl(reachability.Purl, purlAliasMap, location),
1092
+ )
1093
+ .filter(Boolean),
1094
+ );
1095
+ if (!purls.size) {
1096
+ const purl = resolveComponentPurl(reachability.Purl, purlAliasMap);
1097
+ if (purl) {
1098
+ purls.add(purl);
1099
+ }
1100
+ }
1101
+ return [...purls];
976
1102
  }
977
1103
 
978
1104
  /**
@@ -1021,24 +1147,51 @@ export function addDosaiReachabilityProperties(component, reachability) {
1021
1147
  *
1022
1148
  * @param {Object} methodsSlice Parsed dosai methods slice JSON
1023
1149
  * @param {Object[]} [components] BOM components used to resolve purl aliases and mutate
1150
+ * @param {Object} [options] Options
1151
+ * @param {string} [options.srcPath] Directory dosai analyzed; a package the BOM holds in several
1152
+ * versions is matched by the project of the record's file
1024
1153
  * @returns {number} Number of distinct components enriched
1025
1154
  */
1026
- export function applyDosaiReachabilityEvidence(methodsSlice, components = []) {
1027
- const purlAliasMap = buildPurlAliasMap(components);
1155
+ export function applyDosaiReachabilityEvidence(
1156
+ methodsSlice,
1157
+ components = [],
1158
+ options = {},
1159
+ ) {
1160
+ const purlAliasMap = buildPurlAliasMap(components, options);
1028
1161
  const componentsByPurl = new Map(
1029
1162
  components
1030
1163
  .filter((component) => component?.purl)
1031
1164
  .map((component) => [component.purl, component]),
1032
1165
  );
1166
+ const graph = {
1167
+ edgesById: new Map(
1168
+ (methodsSlice?.CallGraph?.Edges || []).map((edge) => [edge.Id, edge]),
1169
+ ),
1170
+ nodesById: new Map(
1171
+ (methodsSlice?.CallGraph?.Nodes || []).map((node) => [node.Id, node]),
1172
+ ),
1173
+ };
1033
1174
  const enriched = new Set();
1034
- for (const reachability of methodsSlice?.PackageReachability || []) {
1035
- const purl = resolveComponentPurl(reachability.Purl, purlAliasMap);
1036
- const component = purl && componentsByPurl.get(purl);
1037
- if (!component) {
1038
- continue;
1175
+ // The first fact applied to a component wins, so a fact that names the
1176
+ // component's exact purl goes before one that reached it by name.
1177
+ const facts = [...(methodsSlice?.PackageReachability || [])].sort(
1178
+ (a, b) =>
1179
+ Number(!componentsByPurl.has(a.Purl)) -
1180
+ Number(!componentsByPurl.has(b.Purl)),
1181
+ );
1182
+ for (const reachability of facts) {
1183
+ for (const purl of reachabilityComponentPurls(
1184
+ reachability,
1185
+ purlAliasMap,
1186
+ graph,
1187
+ )) {
1188
+ const component = componentsByPurl.get(purl);
1189
+ if (!component) {
1190
+ continue;
1191
+ }
1192
+ addDosaiReachabilityProperties(component, reachability);
1193
+ enriched.add(component);
1039
1194
  }
1040
- addDosaiReachabilityProperties(component, reachability);
1041
- enriched.add(component);
1042
1195
  }
1043
1196
  return enriched.size;
1044
1197
  }
@@ -1051,10 +1204,17 @@ export function applyDosaiReachabilityEvidence(methodsSlice, components = []) {
1051
1204
  *
1052
1205
  * @param {Object} methodsSlice Parsed dosai methods slice JSON
1053
1206
  * @param {Object[]} [components] BOM components used to resolve purl aliases
1207
+ * @param {Object} [options] Options
1208
+ * @param {string} [options.srcPath] Directory dosai analyzed; a package the BOM holds in several
1209
+ * versions is matched by the project of the record's file
1054
1210
  * @returns {Object} Object with purlLocationMap, purlModulesMap, and purlMethodsMap keyed by purl
1055
1211
  */
1056
- export function collectDosaiPurlEvidence(methodsSlice, components = []) {
1057
- const purlAliasMap = buildPurlAliasMap(components);
1212
+ export function collectDosaiPurlEvidence(
1213
+ methodsSlice,
1214
+ components = [],
1215
+ options = {},
1216
+ ) {
1217
+ const purlAliasMap = buildPurlAliasMap(components, options);
1058
1218
  const purlLocationMap = {};
1059
1219
  const purlModulesMap = {};
1060
1220
  const purlMethodsMap = {};
@@ -1066,7 +1226,11 @@ export function collectDosaiPurlEvidence(methodsSlice, components = []) {
1066
1226
  );
1067
1227
 
1068
1228
  for (const dependency of methodsSlice?.Dependencies || []) {
1069
- const purl = resolveComponentPurl(dependency.Purl, purlAliasMap);
1229
+ const purl = resolveComponentPurl(
1230
+ dependency.Purl,
1231
+ purlAliasMap,
1232
+ dependency.Path,
1233
+ );
1070
1234
  if (!purl) {
1071
1235
  continue;
1072
1236
  }
@@ -1079,42 +1243,61 @@ export function collectDosaiPurlEvidence(methodsSlice, components = []) {
1079
1243
  }
1080
1244
 
1081
1245
  for (const reachability of methodsSlice?.PackageReachability || []) {
1082
- const purl = resolveComponentPurl(reachability.Purl, purlAliasMap);
1083
- if (!purl) {
1084
- continue;
1085
- }
1246
+ // Every location resolves in its own project: a versionless purl from
1247
+ // unrestored projects spans projects restored at different versions.
1248
+ const resolveAt = (location) =>
1249
+ resolveComponentPurl(reachability.Purl, purlAliasMap, location);
1250
+ const factPurls = new Set();
1251
+ // Modules and methods of call-graph items that name no source file
1252
+ // (frames inside the package), held for the fact's own version.
1253
+ const unplaced = [];
1086
1254
  let hasExplicitSourceLocations = false;
1087
1255
  for (const sourceLocation of reachability.SourceLocations || []) {
1088
1256
  const location = dosaiSourceLocation(sourceLocation);
1257
+ const purl = resolveAt(location);
1089
1258
  addDosaiSetValue(purlLocationMap, purl, location);
1259
+ if (purl) {
1260
+ factPurls.add(purl);
1261
+ }
1090
1262
  hasExplicitSourceLocations ||= Boolean(location);
1091
1263
  }
1092
1264
  for (const edgeId of reachability.EdgeIds || []) {
1093
1265
  const edge = edgesById.get(edgeId);
1266
+ const location = dosaiSourceLocation(edge);
1267
+ const purl = resolveAt(location ?? edge?.Path);
1268
+ const method = edge?.CalledMethodName || edge?.TargetName;
1269
+ if (!purl) {
1270
+ unplaced.push([purlMethodsMap, method]);
1271
+ continue;
1272
+ }
1273
+ factPurls.add(purl);
1094
1274
  if (!hasExplicitSourceLocations) {
1095
- addDosaiSetValue(purlLocationMap, purl, dosaiSourceLocation(edge));
1275
+ addDosaiSetValue(purlLocationMap, purl, location);
1096
1276
  }
1097
- addDosaiSetValue(
1098
- purlMethodsMap,
1099
- purl,
1100
- edge?.CalledMethodName || edge?.TargetName,
1101
- );
1277
+ addDosaiSetValue(purlMethodsMap, purl, method);
1102
1278
  }
1103
1279
  for (const nodeId of reachability.NodeIds || []) {
1104
1280
  const node = nodesById.get(nodeId);
1281
+ const location = dosaiSourceLocationFromNode(node);
1282
+ const purl = resolveAt(location);
1283
+ const module = node?.ClassName || node?.Module;
1284
+ const method = node?.Name || node?.Identity?.MethodName;
1285
+ if (!purl) {
1286
+ unplaced.push([purlModulesMap, module], [purlMethodsMap, method]);
1287
+ continue;
1288
+ }
1289
+ factPurls.add(purl);
1105
1290
  if (!hasExplicitSourceLocations) {
1106
- addDosaiSetValue(
1107
- purlLocationMap,
1108
- purl,
1109
- dosaiSourceLocationFromNode(node),
1110
- );
1291
+ addDosaiSetValue(purlLocationMap, purl, location);
1292
+ }
1293
+ addDosaiSetValue(purlModulesMap, purl, module);
1294
+ addDosaiSetValue(purlMethodsMap, purl, method);
1295
+ }
1296
+ if (factPurls.size === 1) {
1297
+ const [purl] = factPurls;
1298
+ for (const [map, value] of unplaced) {
1299
+ addDosaiSetValue(map, purl, value);
1111
1300
  }
1112
- addDosaiSetValue(purlModulesMap, purl, node?.ClassName || node?.Module);
1113
- addDosaiSetValue(
1114
- purlMethodsMap,
1115
- purl,
1116
- node?.Name || node?.Identity?.MethodName,
1117
- );
1118
1301
  }
1119
1302
  }
1120
1303
  return { purlLocationMap, purlModulesMap, purlMethodsMap };
@@ -1128,17 +1311,38 @@ export function collectDosaiPurlEvidence(methodsSlice, components = []) {
1128
1311
  *
1129
1312
  * @param {Object} dataFlowResult Parsed dosai data-flow slice JSON
1130
1313
  * @param {Object[]} [components] BOM components used to resolve purl aliases
1314
+ * @param {Object} [options] Options
1315
+ * @param {string} [options.srcPath] Directory dosai analyzed; a package the BOM holds in several
1316
+ * versions is matched by the project of the record's file
1131
1317
  * @returns {Object} Map of canonical purl to arrays of call-stack frame objects
1132
1318
  */
1133
- export function collectDosaiDataFlowFrames(dataFlowResult, components = []) {
1134
- const purlAliasMap = buildPurlAliasMap(components);
1319
+ export function collectDosaiDataFlowFrames(
1320
+ dataFlowResult,
1321
+ components = [],
1322
+ options = {},
1323
+ ) {
1324
+ const purlAliasMap = buildPurlAliasMap(components, options);
1135
1325
  const nodesById = new Map(
1136
1326
  (dataFlowResult?.Nodes || []).map((node) => [node.Id, node]),
1137
1327
  );
1138
1328
  const dataFlowFrames = {};
1139
1329
  const addFramesForPurl = (purl, frames) => {
1140
- const componentPurl = resolveComponentPurl(purl, purlAliasMap);
1141
- if (!componentPurl || !frames.length) {
1330
+ if (!frames.length) {
1331
+ return;
1332
+ }
1333
+ // The flow runs in the project of its first frame that settles one.
1334
+ let componentPurl;
1335
+ for (const frame of frames) {
1336
+ componentPurl = resolveComponentPurl(
1337
+ purl,
1338
+ purlAliasMap,
1339
+ frame.fullFilename,
1340
+ );
1341
+ if (componentPurl) {
1342
+ break;
1343
+ }
1344
+ }
1345
+ if (!componentPurl) {
1142
1346
  return;
1143
1347
  }
1144
1348
  dataFlowFrames[componentPurl] ??= [];