@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
@@ -12,8 +12,10 @@ import {
12
12
  import {
13
13
  addDosaiSetValue,
14
14
  buildDosaiPurlAliasMap,
15
+ dosaiCallSiteLocation,
15
16
  dosaiSourceLocation,
16
17
  dosaiSourceLocationFromNode,
18
+ pickDosaiComponentByLocation,
17
19
  resolveDosaiComponentPurl,
18
20
  } from "./dosaiParsers.js";
19
21
  import { createGtfoBinsPropertiesFromRow } from "./gtfobins.js";
@@ -240,27 +242,47 @@ function addDotnetIdentityMethod(apkg, value) {
240
242
  * Enrich .NET package components with occurrence evidence and imported module/method
241
243
  * information from a dosai dependency slices file.
242
244
  *
243
- * Builds a mapping of DLL filenames to purls using the `internal:PackageFiles` property of each
244
- * package, then reads the slices file to add occurrence locations, imported modules,
245
- * called methods, and assembly version information where available.
245
+ * Each dosai record names its package by purl, resolved in the project of the
246
+ * record's file, and is attached to the component of that version (see
247
+ * {@link resolveDosaiComponentPurl}). The DLL files of each package
248
+ * (`internal:PackageFiles`) only fill in for a record whose purl names no
249
+ * component, and only when the DLL belongs to one component or the record's
250
+ * location says which: two versions of a package ship the same DLL names.
251
+ *
252
+ * The fallback is needed because dosai names a `System.*` assembly it cannot
253
+ * map to a package with a framework purl (`pkg:nuget/System.Runtime`), as it
254
+ * does whenever the tree has lock files but no restore output: the DLL of
255
+ * System.Data.SQLite.Core is still the package's. A DLL that only a package's
256
+ * build-time folders carry is not listed in `internal:PackageFiles`, so it
257
+ * credits no package with the project's framework calls (issue 4441).
246
258
  *
247
259
  * @param {Object[]} pkgList Array of .NET package component objects to enrich
248
260
  * @param {string} slicesFile Path to the dosai dependency slices JSON file
261
+ * @param {Object} [options] Options
262
+ * @param {string} [options.srcPath] Directory dosai analyzed, which its relative locations
263
+ * and the components' relative manifest paths are relative to
249
264
  * @returns {Object[]} The enriched package list (same array, mutated in place)
250
265
  */
251
- export function addEvidenceForDotnet(pkgList, slicesFile) {
266
+ export function addEvidenceForDotnet(pkgList, slicesFile, options = {}) {
252
267
  // We need two datastructures.
253
268
  // dll to purl mapping from the pkgList
254
269
  // purl to occurrences list using the slicesFile
255
270
  if (!slicesFile || !safeExistsSync(slicesFile)) {
256
271
  return pkgList;
257
272
  }
258
- const pkgFilePurlMap = {};
273
+ const pkgFilePurls = {};
259
274
  const purlLocationMap = {};
260
275
  const purlModulesMap = {};
261
276
  const purlMethodsMap = {};
262
277
  const purlReachabilityMap = {};
263
- const purlAliasMap = buildDosaiPurlAliasMap(pkgList);
278
+ const purlAliasMap = buildDosaiPurlAliasMap(pkgList, options);
279
+ const componentPurls = new Set(pkgList.map((apkg) => apkg.purl));
280
+ // A dosai purl that names a component of this list, resolved in the
281
+ // record's project.
282
+ const componentPurlFor = (purl, location) => {
283
+ const resolved = resolveDosaiComponentPurl(purl, purlAliasMap, location);
284
+ return componentPurls.has(resolved) ? resolved : undefined;
285
+ };
264
286
  for (const apkg of pkgList) {
265
287
  if (apkg.properties && Array.isArray(apkg.properties)) {
266
288
  apkg.properties
@@ -270,20 +292,32 @@ export function addEvidenceForDotnet(pkgList, slicesFile) {
270
292
  const tmpA = aprop.value.split(", ");
271
293
  if (tmpA?.length) {
272
294
  tmpA.forEach((dllFile) => {
273
- pkgFilePurlMap[dllFile] = apkg.purl;
295
+ pkgFilePurls[dllFile] ??= new Set();
296
+ pkgFilePurls[dllFile].add(apkg.purl);
274
297
  });
275
298
  }
276
299
  }
277
300
  });
278
301
  }
279
302
  }
303
+ // The component a DLL belongs to: its only package, else the candidate of the
304
+ // record's project.
305
+ const purlForDll = (dllFile, location) =>
306
+ pkgFilePurls[dllFile]
307
+ ? pickDosaiComponentByLocation(
308
+ pkgFilePurls[dllFile],
309
+ purlAliasMap,
310
+ location,
311
+ )
312
+ : undefined;
280
313
  // Only MethodCalls into a package .dll produce evidence below, so a report
281
314
  // too large to parse whole keeps just those.
282
315
  const slicesData = readDosaiMethodsReport(slicesFile, {
283
316
  keepMethodCall: (methodCall) =>
284
317
  Boolean(
285
- methodCall?.Module?.endsWith(".dll") &&
286
- pkgFilePurlMap[methodCall.Module],
318
+ methodCall?.Purl ||
319
+ (methodCall?.Module?.endsWith(".dll") &&
320
+ pkgFilePurls[methodCall.Module]),
287
321
  ),
288
322
  });
289
323
  if (!slicesData) {
@@ -295,43 +329,36 @@ export function addEvidenceForDotnet(pkgList, slicesFile) {
295
329
  );
296
330
  if (slicesData.Dependencies) {
297
331
  for (const adep of slicesData.Dependencies) {
298
- if (adep.Purl) {
299
- const modPurl = resolveDosaiComponentPurl(adep.Purl, purlAliasMap);
300
- if (modPurl) {
301
- addDosaiSetValue(
302
- purlLocationMap,
303
- modPurl,
304
- dosaiSourceLocation(adep),
305
- );
306
- addDosaiSetValue(
307
- purlModulesMap,
308
- modPurl,
309
- adep.Name || adep.Namespace,
310
- );
311
- }
332
+ const location = dosaiSourceLocation(adep);
333
+ const resolvedPurl = adep.Purl
334
+ ? componentPurlFor(adep.Purl, adep.Path)
335
+ : undefined;
336
+ if (resolvedPurl) {
337
+ addDosaiSetValue(purlLocationMap, resolvedPurl, location);
338
+ addDosaiSetValue(
339
+ purlModulesMap,
340
+ resolvedPurl,
341
+ adep.Name || adep.Namespace,
342
+ );
343
+ continue;
312
344
  }
313
345
  // Case 1: Dependencies slice has the .dll file
314
- if (adep.Module?.endsWith(".dll") && pkgFilePurlMap[adep.Module]) {
315
- const modPurl = pkgFilePurlMap[adep.Module];
316
- if (!purlLocationMap[modPurl]) {
317
- purlLocationMap[modPurl] = new Set();
318
- }
319
- purlLocationMap[modPurl].add(`${adep.Path}#${adep.LineNumber}`);
320
- } else if (
321
- adep?.Name &&
322
- (adep?.Namespace?.startsWith("System") ||
323
- adep?.Namespace?.startsWith("Microsoft"))
324
- ) {
325
- // Case 2: System packages where the .dll information is missing
326
- // In this case, the dll file name is the name followed by dll.
327
- const moduleDll = `${adep.Name}.dll`;
328
- if (pkgFilePurlMap[moduleDll]) {
329
- const modPurl = pkgFilePurlMap[moduleDll];
330
- if (!purlLocationMap[modPurl]) {
331
- purlLocationMap[modPurl] = new Set();
332
- }
333
- purlLocationMap[modPurl].add(`${adep.Path}#${adep.LineNumber}`);
334
- }
346
+ // Case 2: System packages where the .dll information is missing. In
347
+ // this case, the dll file name is the name followed by dll.
348
+ const moduleDll = adep.Module?.endsWith(".dll")
349
+ ? adep.Module
350
+ : adep?.Name &&
351
+ (adep?.Namespace?.startsWith("System") ||
352
+ adep?.Namespace?.startsWith("Microsoft"))
353
+ ? `${adep.Name}.dll`
354
+ : undefined;
355
+ const modPurl = moduleDll && purlForDll(moduleDll, adep.Path);
356
+ if (modPurl) {
357
+ addDosaiSetValue(
358
+ purlLocationMap,
359
+ modPurl,
360
+ `${adep.Path}#${adep.LineNumber}`,
361
+ );
335
362
  }
336
363
  }
337
364
  }
@@ -342,95 +369,113 @@ export function addEvidenceForDotnet(pkgList, slicesFile) {
342
369
  const graphNodes = Object.fromEntries(
343
370
  (slicesData.CallGraph?.Nodes || []).map((node) => [node.Id, node]),
344
371
  );
345
- for (const reachability of slicesData.PackageReachability) {
346
- const modPurl = resolveDosaiComponentPurl(
347
- reachability.Purl,
348
- purlAliasMap,
349
- );
350
- if (!modPurl) {
351
- continue;
352
- }
353
- // Confidence/evidence facts for this purl, applied to the component below so an
354
- // unbuilt tree stays distinguishable from an only-imported package.
355
- purlReachabilityMap[modPurl] ??= reachability;
372
+ // The first fact for a component wins, so a fact that names the
373
+ // component's exact purl goes before one that reached it by name.
374
+ const facts = [...slicesData.PackageReachability].sort(
375
+ (a, b) =>
376
+ Number(!componentPurls.has(a.Purl)) -
377
+ Number(!componentPurls.has(b.Purl)),
378
+ );
379
+ for (const reachability of facts) {
380
+ // A fact can span projects (a versionless purl from unrestored
381
+ // projects), so each location is resolved in its own project, and the
382
+ // fact applies to every component one of its locations reached.
383
+ const factPurls = new Set();
384
+ // Modules and methods of call-graph items that name no source file
385
+ // (frames inside the package), held for the fact's own version.
386
+ const unplaced = [];
387
+ const resolveAt = (location) =>
388
+ resolveDosaiComponentPurl(reachability.Purl, purlAliasMap, location);
356
389
  let hasExplicitSourceLocations = false;
357
390
  for (const sourceLocation of reachability.SourceLocations || []) {
358
391
  const location = dosaiSourceLocation(sourceLocation);
392
+ const modPurl = resolveAt(location);
359
393
  addDosaiSetValue(purlLocationMap, modPurl, location);
394
+ if (modPurl) {
395
+ factPurls.add(modPurl);
396
+ }
360
397
  hasExplicitSourceLocations ||= Boolean(location);
361
398
  }
362
399
  for (const edgeId of reachability.EdgeIds || []) {
363
400
  const edge = graphEdges[edgeId];
401
+ const location = dosaiSourceLocation(edge);
402
+ const modPurl = resolveAt(location ?? edge?.Path);
403
+ const method = edge?.CalledMethodName || edge?.TargetName;
404
+ if (!modPurl) {
405
+ unplaced.push([purlMethodsMap, method]);
406
+ continue;
407
+ }
408
+ factPurls.add(modPurl);
364
409
  if (!hasExplicitSourceLocations) {
365
- addDosaiSetValue(
366
- purlLocationMap,
367
- modPurl,
368
- dosaiSourceLocation(edge),
369
- );
410
+ addDosaiSetValue(purlLocationMap, modPurl, location);
370
411
  }
371
- addDosaiSetValue(
372
- purlMethodsMap,
373
- modPurl,
374
- edge?.CalledMethodName || edge?.TargetName,
375
- );
412
+ addDosaiSetValue(purlMethodsMap, modPurl, method);
376
413
  }
377
414
  for (const nodeId of reachability.NodeIds || []) {
378
415
  const node = graphNodes[nodeId];
416
+ const location = dosaiSourceLocationFromNode(node);
417
+ const modPurl = resolveAt(location);
418
+ const module = node?.ClassName || node?.Module;
419
+ const method = node?.Name || node?.Identity?.MethodName;
420
+ if (!modPurl) {
421
+ unplaced.push([purlModulesMap, module], [purlMethodsMap, method]);
422
+ continue;
423
+ }
424
+ factPurls.add(modPurl);
379
425
  if (!hasExplicitSourceLocations) {
380
- addDosaiSetValue(
381
- purlLocationMap,
382
- modPurl,
383
- dosaiSourceLocationFromNode(node),
384
- );
426
+ addDosaiSetValue(purlLocationMap, modPurl, location);
385
427
  }
386
- addDosaiSetValue(
387
- purlModulesMap,
388
- modPurl,
389
- node?.ClassName || node?.Module,
390
- );
391
- addDosaiSetValue(
392
- purlMethodsMap,
393
- modPurl,
394
- node?.Name || node?.Identity?.MethodName,
395
- );
428
+ addDosaiSetValue(purlModulesMap, modPurl, module);
429
+ addDosaiSetValue(purlMethodsMap, modPurl, method);
396
430
  }
397
- }
398
- }
399
- if (slicesData.MethodCalls) {
400
- for (const amethodCall of slicesData.MethodCalls) {
401
- if (
402
- amethodCall.Module?.endsWith(".dll") &&
403
- pkgFilePurlMap[amethodCall.Module]
404
- ) {
405
- const modPurl = pkgFilePurlMap[amethodCall.Module];
406
- if (!purlLocationMap[modPurl]) {
407
- purlLocationMap[modPurl] = new Set();
408
- }
409
- if (!purlModulesMap[modPurl]) {
410
- purlModulesMap[modPurl] = new Set();
431
+ if (!factPurls.size) {
432
+ const modPurl = resolveAt(undefined);
433
+ if (modPurl) {
434
+ factPurls.add(modPurl);
411
435
  }
412
- if (!purlMethodsMap[modPurl]) {
413
- purlMethodsMap[modPurl] = new Set();
436
+ }
437
+ if (factPurls.size === 1) {
438
+ const [modPurl] = factPurls;
439
+ for (const [map, value] of unplaced) {
440
+ addDosaiSetValue(map, modPurl, value);
414
441
  }
415
- purlLocationMap[modPurl].add(
416
- `${amethodCall.Path}#${amethodCall.LineNumber}`,
417
- );
418
- purlModulesMap[modPurl].add(amethodCall.ClassName);
419
- purlMethodsMap[modPurl].add(amethodCall.CalledMethod);
442
+ }
443
+ // Confidence/evidence facts for this purl, applied to the component below so an
444
+ // unbuilt tree stays distinguishable from an only-imported package.
445
+ for (const modPurl of factPurls) {
446
+ purlReachabilityMap[modPurl] ??= reachability;
420
447
  }
421
448
  }
422
449
  }
423
- if (slicesData.AssemblyInformation) {
424
- for (const apkg of pkgList) {
425
- if (!apkg.version) {
426
- for (const assemblyInfo of slicesData.AssemblyInformation) {
427
- if (apkg.name === assemblyInfo.Name) {
428
- apkg.version = assemblyInfo.Version;
429
- }
430
- }
450
+ if (slicesData.MethodCalls) {
451
+ for (const amethodCall of slicesData.MethodCalls) {
452
+ // dosai's purl is resolved in the caller's project; the DLL name is the
453
+ // same for every version of the package, and fills in for a purl that
454
+ // names no component, such as the framework purl dosai gives a
455
+ // System.* assembly it cannot map to a package.
456
+ const modPurl =
457
+ (amethodCall.Purl
458
+ ? componentPurlFor(amethodCall.Purl, amethodCall.Path)
459
+ : undefined) ||
460
+ (amethodCall.Module?.endsWith(".dll")
461
+ ? purlForDll(amethodCall.Module, amethodCall.Path)
462
+ : undefined);
463
+ if (!modPurl) {
464
+ continue;
431
465
  }
466
+ addDosaiSetValue(
467
+ purlLocationMap,
468
+ modPurl,
469
+ dosaiCallSiteLocation(amethodCall, options.srcPath),
470
+ );
471
+ addDosaiSetValue(purlModulesMap, modPurl, amethodCall.ClassName);
472
+ addDosaiSetValue(purlMethodsMap, modPurl, amethodCall.CalledMethod);
432
473
  }
433
474
  }
475
+ // AssemblyInformation used to fill in a missing version by assembly name.
476
+ // An assembly version is not the package version (Newtonsoft.Json 13.0.3
477
+ // ships assembly 13.0.0.0), the purl and bom-ref kept no version, and the
478
+ // last same-named assembly won, so the component described two packages.
434
479
  }
435
480
  if (Object.keys(purlLocationMap).length) {
436
481
  for (const apkg of pkgList) {