@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.
- package/README.md +9 -6
- package/bin/cdxgen.js +90 -26
- package/bin/evinse.js +18 -1
- package/bin/repl.js +86 -0
- package/bin/sign.js +45 -15
- package/bin/tracebom.js +5 -1
- package/bin/validate.js +84 -18
- package/bin/verify.js +155 -86
- package/data/helpers/init.gradle +46 -21
- package/data/remediations.json +61 -0
- package/data/rules/dependency-sources.yaml +67 -0
- package/data/rules/scala-evidence.yaml +115 -0
- package/lib/audit/index.js +38 -6
- package/lib/cli/index.js +113 -54
- package/lib/cli/jsBom.js +50 -29
- package/lib/cli/jvmBom.js +665 -138
- package/lib/cli/managedBom.js +320 -200
- package/lib/cli/nativeBom.js +139 -30
- package/lib/core/activity.js +12 -0
- package/lib/core/env.js +17 -9
- package/lib/core/fs.js +3 -2
- package/lib/core/httpClient.js +159 -24
- package/lib/core/pluginRun.js +184 -0
- package/lib/core/processTree.js +100 -0
- package/lib/core/runState.js +39 -0
- package/lib/core/toml.js +72 -0
- package/lib/ecosystems/asarutils.js +4 -2
- package/lib/ecosystems/bunutils.js +5 -7
- package/lib/ecosystems/cargoCache.js +231 -8
- package/lib/ecosystems/cmakeResolver.js +10 -21
- package/lib/ecosystems/cppBuildContext.js +631 -0
- package/lib/ecosystems/cppEvidence.js +337 -22
- package/lib/ecosystems/cppIncludeResolver.js +294 -0
- package/lib/ecosystems/denoutils.js +93 -66
- package/lib/ecosystems/ecosystems.js +1967 -788
- package/lib/ecosystems/golem.js +28 -12
- package/lib/ecosystems/gradleutils.js +314 -30
- package/lib/ecosystems/jsEvidence.js +85 -24
- package/lib/ecosystems/kosi.js +21 -4
- package/lib/ecosystems/npmutils.js +64 -0
- package/lib/ecosystems/parsers-dotnet.js +567 -151
- package/lib/ecosystems/parsers-elm.js +46 -28
- package/lib/ecosystems/parsers-gleam.js +3 -4
- package/lib/ecosystems/parsers-go.js +324 -61
- package/lib/ecosystems/parsers-js.js +92 -26
- package/lib/ecosystems/parsers-julia.js +2 -3
- package/lib/ecosystems/parsers-jvm.js +204 -12
- package/lib/ecosystems/parsers-misc.js +182 -55
- package/lib/ecosystems/parsers-python.js +141 -10
- package/lib/ecosystems/parsers-rust.js +4 -4
- package/lib/ecosystems/parsers-terraform.js +1573 -19
- package/lib/ecosystems/pythonutils.js +102 -6
- package/lib/ecosystems/remote/huggingface.js +3 -0
- package/lib/ecosystems/rubyutils.js +67 -17
- package/lib/ecosystems/rusi.js +22 -9
- package/lib/ecosystems/sbtutils.js +243 -187
- package/lib/ecosystems/scalacliutils.js +342 -0
- package/lib/ecosystems/scalasem.js +2618 -0
- package/lib/ecosystems/terraformRegistry.js +288 -0
- package/lib/ecosystems/utils.js +2 -0
- package/lib/ecosystems/vendoredCode.js +156 -0
- package/lib/evinser/evinser.js +578 -69
- package/lib/helpers/agenticToolutils.js +2 -2
- package/lib/helpers/bomSigner.js +576 -128
- package/lib/inventory/aiCollector.js +4 -1
- package/lib/inventory/analyzer.js +110 -127
- package/lib/inventory/atomUtils.js +171 -48
- package/lib/inventory/auditCategories.js +7 -0
- package/lib/inventory/buildToolRateLimit.js +144 -0
- package/lib/inventory/cacheCommands.js +2 -1
- package/lib/inventory/cmakeBuildDirs.js +254 -0
- package/lib/inventory/deps.js +501 -124
- package/lib/inventory/dosai.js +248 -44
- package/lib/inventory/dosaiParsers.js +310 -15
- package/lib/inventory/dynamic.js +52 -6
- package/lib/inventory/evidenceUtils.js +154 -109
- package/lib/inventory/fetchBatch.js +574 -38
- package/lib/inventory/fetchDiskCache.js +597 -0
- package/lib/inventory/forgeEnricher.js +14 -5
- package/lib/inventory/jvmLocalRepos.js +917 -0
- package/lib/inventory/mcpConfigParser.js +2 -3
- package/lib/inventory/parentSelection.js +146 -0
- package/lib/inventory/rustFormulationParser.js +3 -4
- package/lib/inventory/scalaCoords.js +153 -0
- package/lib/inventory/source.js +8 -8
- package/lib/inventory/spdx.js +101 -0
- package/lib/inventory/traceRunner.js +88 -7
- package/lib/inventory/tracedCrypto.js +294 -0
- package/lib/managers/docker.js +33 -32
- package/lib/managers/oci.js +24 -2
- package/lib/parsers/cmakeCache.js +69 -0
- package/lib/parsers/cmakeLists.js +16 -2
- package/lib/parsers/cmakePresets.js +400 -0
- package/lib/parsers/compileCommands.js +557 -0
- package/lib/parsers/strictJson.js +322 -0
- package/lib/parsers/terraformConfig.js +1073 -0
- package/lib/parsers/terraformSource.js +652 -0
- package/lib/server/server.js +23 -0
- package/lib/stages/postgen/introspection/markers.js +1 -1
- package/lib/stages/postgen/postgen.js +15 -1
- package/lib/stages/postgen/specVersionCompat.js +32 -0
- package/lib/validator/index.js +25 -10
- package/lib/validator/reporters/json.js +1 -0
- package/package.json +21 -21
- package/types/lib/audit/index.d.ts.map +1 -1
- package/types/lib/cli/index.d.ts.map +1 -1
- package/types/lib/cli/jsBom.d.ts.map +1 -1
- package/types/lib/cli/jvmBom.d.ts +24 -1
- package/types/lib/cli/jvmBom.d.ts.map +1 -1
- package/types/lib/cli/managedBom.d.ts +7 -4
- package/types/lib/cli/managedBom.d.ts.map +1 -1
- package/types/lib/cli/nativeBom.d.ts +3 -1
- package/types/lib/cli/nativeBom.d.ts.map +1 -1
- package/types/lib/core/activity.d.ts.map +1 -1
- package/types/lib/core/env.d.ts +9 -0
- package/types/lib/core/env.d.ts.map +1 -1
- package/types/lib/core/fs.d.ts.map +1 -1
- package/types/lib/core/httpClient.d.ts +13 -8
- package/types/lib/core/httpClient.d.ts.map +1 -1
- package/types/lib/core/pluginRun.d.ts +97 -0
- package/types/lib/core/pluginRun.d.ts.map +1 -0
- package/types/lib/core/processTree.d.ts +36 -0
- package/types/lib/core/processTree.d.ts.map +1 -0
- package/types/lib/core/runState.d.ts +29 -0
- package/types/lib/core/runState.d.ts.map +1 -0
- package/types/lib/core/toml.d.ts +20 -0
- package/types/lib/core/toml.d.ts.map +1 -0
- package/types/lib/ecosystems/asarutils.d.ts.map +1 -1
- package/types/lib/ecosystems/bunutils.d.ts.map +1 -1
- package/types/lib/ecosystems/cargoCache.d.ts +12 -0
- package/types/lib/ecosystems/cargoCache.d.ts.map +1 -1
- package/types/lib/ecosystems/cmakeResolver.d.ts +5 -3
- package/types/lib/ecosystems/cmakeResolver.d.ts.map +1 -1
- package/types/lib/ecosystems/cppBuildContext.d.ts +55 -0
- package/types/lib/ecosystems/cppBuildContext.d.ts.map +1 -0
- package/types/lib/ecosystems/cppEvidence.d.ts +18 -2
- package/types/lib/ecosystems/cppEvidence.d.ts.map +1 -1
- package/types/lib/ecosystems/cppIncludeResolver.d.ts +92 -0
- package/types/lib/ecosystems/cppIncludeResolver.d.ts.map +1 -0
- package/types/lib/ecosystems/denoutils.d.ts +9 -3
- package/types/lib/ecosystems/denoutils.d.ts.map +1 -1
- package/types/lib/ecosystems/ecosystems.d.ts +62 -13
- package/types/lib/ecosystems/ecosystems.d.ts.map +1 -1
- package/types/lib/ecosystems/golem.d.ts.map +1 -1
- package/types/lib/ecosystems/gradleutils.d.ts +50 -0
- package/types/lib/ecosystems/gradleutils.d.ts.map +1 -1
- package/types/lib/ecosystems/jsEvidence.d.ts.map +1 -1
- package/types/lib/ecosystems/kosi.d.ts.map +1 -1
- package/types/lib/ecosystems/npmutils.d.ts +15 -0
- package/types/lib/ecosystems/npmutils.d.ts.map +1 -1
- package/types/lib/ecosystems/parsers-dotnet.d.ts +118 -4
- package/types/lib/ecosystems/parsers-dotnet.d.ts.map +1 -1
- package/types/lib/ecosystems/parsers-elm.d.ts.map +1 -1
- package/types/lib/ecosystems/parsers-gleam.d.ts.map +1 -1
- package/types/lib/ecosystems/parsers-go.d.ts +44 -2
- package/types/lib/ecosystems/parsers-go.d.ts.map +1 -1
- package/types/lib/ecosystems/parsers-js.d.ts +6 -1
- package/types/lib/ecosystems/parsers-js.d.ts.map +1 -1
- package/types/lib/ecosystems/parsers-julia.d.ts.map +1 -1
- package/types/lib/ecosystems/parsers-jvm.d.ts +46 -1
- package/types/lib/ecosystems/parsers-jvm.d.ts.map +1 -1
- package/types/lib/ecosystems/parsers-misc.d.ts +3 -1
- package/types/lib/ecosystems/parsers-misc.d.ts.map +1 -1
- package/types/lib/ecosystems/parsers-python.d.ts +53 -3
- package/types/lib/ecosystems/parsers-python.d.ts.map +1 -1
- package/types/lib/ecosystems/parsers-terraform.d.ts +38 -21
- package/types/lib/ecosystems/parsers-terraform.d.ts.map +1 -1
- package/types/lib/ecosystems/pythonutils.d.ts +27 -2
- package/types/lib/ecosystems/pythonutils.d.ts.map +1 -1
- package/types/lib/ecosystems/remote/huggingface.d.ts.map +1 -1
- package/types/lib/ecosystems/rubyutils.d.ts +21 -1
- package/types/lib/ecosystems/rubyutils.d.ts.map +1 -1
- package/types/lib/ecosystems/rusi.d.ts.map +1 -1
- package/types/lib/ecosystems/sbtutils.d.ts +88 -2
- package/types/lib/ecosystems/sbtutils.d.ts.map +1 -1
- package/types/lib/ecosystems/scalacliutils.d.ts +56 -0
- package/types/lib/ecosystems/scalacliutils.d.ts.map +1 -0
- package/types/lib/ecosystems/scalasem.d.ts +311 -0
- package/types/lib/ecosystems/scalasem.d.ts.map +1 -0
- package/types/lib/ecosystems/terraformRegistry.d.ts +9 -0
- package/types/lib/ecosystems/terraformRegistry.d.ts.map +1 -0
- package/types/lib/ecosystems/utils.d.ts +2 -2
- package/types/lib/ecosystems/utils.d.ts.map +1 -1
- package/types/lib/ecosystems/vendoredCode.d.ts +32 -0
- package/types/lib/ecosystems/vendoredCode.d.ts.map +1 -0
- package/types/lib/evinser/evinser.d.ts +75 -8
- package/types/lib/evinser/evinser.d.ts.map +1 -1
- package/types/lib/helpers/bomSigner.d.ts +80 -4
- package/types/lib/helpers/bomSigner.d.ts.map +1 -1
- package/types/lib/inventory/aiCollector.d.ts.map +1 -1
- package/types/lib/inventory/analyzer.d.ts.map +1 -1
- package/types/lib/inventory/atomUtils.d.ts +82 -10
- package/types/lib/inventory/atomUtils.d.ts.map +1 -1
- package/types/lib/inventory/auditCategories.d.ts +3 -0
- package/types/lib/inventory/auditCategories.d.ts.map +1 -1
- package/types/lib/inventory/buildToolRateLimit.d.ts +38 -0
- package/types/lib/inventory/buildToolRateLimit.d.ts.map +1 -0
- package/types/lib/inventory/cacheCommands.d.ts +2 -1
- package/types/lib/inventory/cacheCommands.d.ts.map +1 -1
- package/types/lib/inventory/cmakeBuildDirs.d.ts +58 -0
- package/types/lib/inventory/cmakeBuildDirs.d.ts.map +1 -0
- package/types/lib/inventory/deps.d.ts +59 -1
- package/types/lib/inventory/deps.d.ts.map +1 -1
- package/types/lib/inventory/dosai.d.ts +57 -8
- package/types/lib/inventory/dosai.d.ts.map +1 -1
- package/types/lib/inventory/dosaiParsers.d.ts +53 -7
- package/types/lib/inventory/dosaiParsers.d.ts.map +1 -1
- package/types/lib/inventory/dynamic.d.ts +10 -0
- package/types/lib/inventory/dynamic.d.ts.map +1 -1
- package/types/lib/inventory/evidenceUtils.d.ts +19 -4
- package/types/lib/inventory/evidenceUtils.d.ts.map +1 -1
- package/types/lib/inventory/fetchBatch.d.ts +83 -14
- package/types/lib/inventory/fetchBatch.d.ts.map +1 -1
- package/types/lib/inventory/fetchDiskCache.d.ts +169 -0
- package/types/lib/inventory/fetchDiskCache.d.ts.map +1 -0
- package/types/lib/inventory/forgeEnricher.d.ts.map +1 -1
- package/types/lib/inventory/jvmLocalRepos.d.ts +178 -0
- package/types/lib/inventory/jvmLocalRepos.d.ts.map +1 -0
- package/types/lib/inventory/mcpConfigParser.d.ts.map +1 -1
- package/types/lib/inventory/parentSelection.d.ts +34 -0
- package/types/lib/inventory/parentSelection.d.ts.map +1 -0
- package/types/lib/inventory/rustFormulationParser.d.ts.map +1 -1
- package/types/lib/inventory/scalaCoords.d.ts +80 -0
- package/types/lib/inventory/scalaCoords.d.ts.map +1 -0
- package/types/lib/inventory/source.d.ts.map +1 -1
- package/types/lib/inventory/spdx.d.ts +9 -0
- package/types/lib/inventory/spdx.d.ts.map +1 -1
- package/types/lib/inventory/traceRunner.d.ts +12 -0
- package/types/lib/inventory/traceRunner.d.ts.map +1 -1
- package/types/lib/inventory/tracedCrypto.d.ts +42 -0
- package/types/lib/inventory/tracedCrypto.d.ts.map +1 -0
- package/types/lib/managers/docker.d.ts.map +1 -1
- package/types/lib/managers/oci.d.ts +3 -1
- package/types/lib/managers/oci.d.ts.map +1 -1
- package/types/lib/parsers/cmakeCache.d.ts +16 -0
- package/types/lib/parsers/cmakeCache.d.ts.map +1 -1
- package/types/lib/parsers/cmakeLists.d.ts +2 -1
- package/types/lib/parsers/cmakeLists.d.ts.map +1 -1
- package/types/lib/parsers/cmakePresets.d.ts +78 -0
- package/types/lib/parsers/cmakePresets.d.ts.map +1 -0
- package/types/lib/parsers/compileCommands.d.ts +115 -0
- package/types/lib/parsers/compileCommands.d.ts.map +1 -0
- package/types/lib/parsers/strictJson.d.ts +46 -0
- package/types/lib/parsers/strictJson.d.ts.map +1 -0
- package/types/lib/parsers/terraformConfig.d.ts +70 -0
- package/types/lib/parsers/terraformConfig.d.ts.map +1 -0
- package/types/lib/parsers/terraformSource.d.ts +66 -0
- package/types/lib/parsers/terraformSource.d.ts.map +1 -0
- package/types/lib/server/server.d.ts.map +1 -1
- package/types/lib/stages/postgen/postgen.d.ts.map +1 -1
- package/types/lib/stages/postgen/specVersionCompat.d.ts.map +1 -1
- package/types/lib/validator/index.d.ts +8 -0
- package/types/lib/validator/index.d.ts.map +1 -1
- package/types/lib/validator/reporters/json.d.ts.map +1 -1
- package/lib/third-party/arborist/snapshots/calc-dep-flags.test.cjs +0 -462
package/lib/evinser/evinser.js
CHANGED
|
@@ -15,6 +15,11 @@ import {
|
|
|
15
15
|
safeRmSync,
|
|
16
16
|
safeWriteSync,
|
|
17
17
|
} from "../core/fs.js";
|
|
18
|
+
import { hasCppSources } from "../ecosystems/cppEvidence.js";
|
|
19
|
+
import {
|
|
20
|
+
createIncludeAttributor,
|
|
21
|
+
parseCIncludeSlices,
|
|
22
|
+
} from "../ecosystems/cppIncludeResolver.js";
|
|
18
23
|
import {
|
|
19
24
|
analyzeGolemProject,
|
|
20
25
|
collectGolemEvidence,
|
|
@@ -39,6 +44,13 @@ import {
|
|
|
39
44
|
collectRusiEvidence,
|
|
40
45
|
isRusiRustLanguage,
|
|
41
46
|
} from "../ecosystems/rusi.js";
|
|
47
|
+
import {
|
|
48
|
+
analyzeScalaProject,
|
|
49
|
+
collectScalasemApiEndpoints,
|
|
50
|
+
collectScalasemEvidence,
|
|
51
|
+
collectScalasemServices,
|
|
52
|
+
isScalasemLanguage,
|
|
53
|
+
} from "../ecosystems/scalasem.js";
|
|
42
54
|
import {
|
|
43
55
|
atomCompileCommandsArgs,
|
|
44
56
|
buildAtomCommandEnv,
|
|
@@ -53,6 +65,7 @@ import {
|
|
|
53
65
|
collectDosaiCryptoComponents,
|
|
54
66
|
findCryptoAlgos,
|
|
55
67
|
} from "../inventory/cbomutils.js";
|
|
68
|
+
import { cmakeBuildDirCandidates } from "../inventory/cmakeBuildDirs.js";
|
|
56
69
|
import { collectMvnDependencies } from "../inventory/deps.js";
|
|
57
70
|
import { mergeServices } from "../inventory/depsUtils.js";
|
|
58
71
|
import {
|
|
@@ -64,6 +77,7 @@ import {
|
|
|
64
77
|
collectDosaiServicesFromMethods,
|
|
65
78
|
createDosaiDataFlowSlice,
|
|
66
79
|
createDosaiMethodsSlice,
|
|
80
|
+
dosaiRunWasStopped,
|
|
67
81
|
isDosaiDotnetLanguage,
|
|
68
82
|
normalizeDosaiServiceMap,
|
|
69
83
|
persistDosaiSemanticsReport,
|
|
@@ -141,6 +155,81 @@ function filterAtomSliceData(sliceData, options = {}) {
|
|
|
141
155
|
return filterAtomSlicesByExcludePatterns(sliceData, options.exclude);
|
|
142
156
|
}
|
|
143
157
|
|
|
158
|
+
/**
|
|
159
|
+
* The project type whose evidence a package URL type belongs to. Generic
|
|
160
|
+
* purls are left out because many collectors use that type; they count as C
|
|
161
|
+
* only when the tree holds C or C++ sources.
|
|
162
|
+
*/
|
|
163
|
+
const EVIDENCE_LANGUAGE_BY_PURL_TYPE = {
|
|
164
|
+
cargo: "rust",
|
|
165
|
+
cocoapods: "swift",
|
|
166
|
+
composer: "php",
|
|
167
|
+
conan: "c",
|
|
168
|
+
gem: "ruby",
|
|
169
|
+
golang: "go",
|
|
170
|
+
maven: "java",
|
|
171
|
+
npm: "js",
|
|
172
|
+
nuget: "csharp",
|
|
173
|
+
pypi: "python",
|
|
174
|
+
swift: "swift",
|
|
175
|
+
};
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* The evidence language of a `cdxgen --evidence` run that names no project
|
|
179
|
+
* type.
|
|
180
|
+
*
|
|
181
|
+
* A directory with a Scala build takes the scalasem path. Otherwise the
|
|
182
|
+
* generated BOM decides: Java when it lists Maven packages, as before, and
|
|
183
|
+
* else the language most of its packages belong to. A BOM with no package in
|
|
184
|
+
* a language the evidence step analyses gives no language, so no analyzer
|
|
185
|
+
* runs over sources it cannot read.
|
|
186
|
+
*
|
|
187
|
+
* @param {string} dirPath Project directory
|
|
188
|
+
* @param {Object} [bomJson] The BOM generated for the directory
|
|
189
|
+
* @param {Object} [options] CLI options, for the exclude patterns
|
|
190
|
+
* @returns {string|undefined} Project type for the evidence phase
|
|
191
|
+
*/
|
|
192
|
+
export function defaultEvidenceLanguage(dirPath, bomJson, options = {}) {
|
|
193
|
+
const scalaBuildFiles = [
|
|
194
|
+
"build.sbt",
|
|
195
|
+
"build.sc",
|
|
196
|
+
"build.mill",
|
|
197
|
+
"project.scala",
|
|
198
|
+
];
|
|
199
|
+
if (
|
|
200
|
+
scalaBuildFiles.some((file) => safeExistsSync(join(dirPath || ".", file)))
|
|
201
|
+
) {
|
|
202
|
+
return "scala";
|
|
203
|
+
}
|
|
204
|
+
const counts = new Map();
|
|
205
|
+
let cppSources;
|
|
206
|
+
for (const component of bomJson?.components || []) {
|
|
207
|
+
const purl = `${component?.purl || ""}`;
|
|
208
|
+
if (!purl.startsWith("pkg:") || !purl.includes("/")) {
|
|
209
|
+
continue;
|
|
210
|
+
}
|
|
211
|
+
const purlType = purl.slice(4, purl.indexOf("/")).toLowerCase();
|
|
212
|
+
let language = EVIDENCE_LANGUAGE_BY_PURL_TYPE[purlType];
|
|
213
|
+
if (!language && purlType === "generic") {
|
|
214
|
+
cppSources ??= hasCppSources(dirPath || ".", options);
|
|
215
|
+
language = cppSources ? "c" : undefined;
|
|
216
|
+
}
|
|
217
|
+
if (language) {
|
|
218
|
+
counts.set(language, (counts.get(language) || 0) + 1);
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
if (counts.has("java")) {
|
|
222
|
+
return "java";
|
|
223
|
+
}
|
|
224
|
+
let chosen;
|
|
225
|
+
for (const [language, count] of counts) {
|
|
226
|
+
if (!chosen || count > counts.get(chosen)) {
|
|
227
|
+
chosen = language;
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
return chosen;
|
|
231
|
+
}
|
|
232
|
+
|
|
144
233
|
/**
|
|
145
234
|
* Build the options for the evinse phase (prepareDB, analyzeProject, and
|
|
146
235
|
* createEvinseFile) from the cdxgen CLI options.
|
|
@@ -178,6 +267,18 @@ export function buildEvinseOptions(options, args, input) {
|
|
|
178
267
|
profile: options.profile,
|
|
179
268
|
jsonPretty: options.jsonPretty,
|
|
180
269
|
exclude: options.exclude,
|
|
270
|
+
// Forwarded so a failed rusi/golem run can claim the --fail-on-error exit
|
|
271
|
+
// instead of degrading the reachability evidence in silence.
|
|
272
|
+
failOnError: options.failOnError,
|
|
273
|
+
// The scala analyzer reads these: a command override, the test-source
|
|
274
|
+
// switch and the install policy that decides whether builds may run.
|
|
275
|
+
scalasemCommand: options.scalasemCommand,
|
|
276
|
+
scalasemIncludeTests: options.scalasemIncludeTests,
|
|
277
|
+
scalasem: options.scalasem,
|
|
278
|
+
installDeps: options.installDeps,
|
|
279
|
+
requiredOnly: options.requiredOnly,
|
|
280
|
+
// --deep publishes the namespaces the Scala classpath join reads.
|
|
281
|
+
scalasemNamespaces: options.deep,
|
|
181
282
|
// Absolute path of the deps slice createCsharpBom writes for --deep runs,
|
|
182
283
|
// so analyzeProject can reuse it instead of rerunning dosai methods.
|
|
183
284
|
depsSlicesFile: options.depsSlicesFile
|
|
@@ -239,7 +340,7 @@ export async function prepareDB(options) {
|
|
|
239
340
|
await catalogMavenDeps(dirPath, purlsJars, Namespaces, options);
|
|
240
341
|
}
|
|
241
342
|
if (gradleFiles?.length) {
|
|
242
|
-
await catalogGradleDeps(dirPath, purlsJars, Namespaces);
|
|
343
|
+
await catalogGradleDeps(dirPath, purlsJars, Namespaces, options);
|
|
243
344
|
}
|
|
244
345
|
}
|
|
245
346
|
for (const purl of Object.keys(purlsToSlice)) {
|
|
@@ -248,11 +349,42 @@ export async function prepareDB(options) {
|
|
|
248
349
|
return { sequelize, Namespaces, Usages, DataFlows };
|
|
249
350
|
}
|
|
250
351
|
|
|
352
|
+
/**
|
|
353
|
+
* Read the jar namespace map cdxgen writes next to the BOM it generates
|
|
354
|
+
* (`<bom>.map`). The map for the BOM passed as `--input` is preferred; a
|
|
355
|
+
* `bom.json.map` in the project directory is the fallback.
|
|
356
|
+
*
|
|
357
|
+
* @param {string} dirPath Project directory scanned by evinse
|
|
358
|
+
* @param {Object} [options] CLI options, for `input`
|
|
359
|
+
* @returns {Object|undefined} The mapping, when a non-empty one exists.
|
|
360
|
+
*/
|
|
361
|
+
export function readNamespaceMap(dirPath, options = {}) {
|
|
362
|
+
const candidates = [
|
|
363
|
+
options?.input ? `${options.input}.map` : undefined,
|
|
364
|
+
join(dirPath, "bom.json.map"),
|
|
365
|
+
];
|
|
366
|
+
for (const mapFile of candidates) {
|
|
367
|
+
if (!mapFile || !safeExistsSync(mapFile)) {
|
|
368
|
+
continue;
|
|
369
|
+
}
|
|
370
|
+
try {
|
|
371
|
+
const mapData = JSON.parse(fs.readFileSync(mapFile, "utf-8"));
|
|
372
|
+
if (mapData && Object.keys(mapData).length) {
|
|
373
|
+
return mapData;
|
|
374
|
+
}
|
|
375
|
+
} catch (_err) {
|
|
376
|
+
// An unreadable map is the same as no map.
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
return undefined;
|
|
380
|
+
}
|
|
381
|
+
|
|
251
382
|
/**
|
|
252
383
|
* Collect Maven jar namespace mappings for the purls found in the SBOM.
|
|
253
384
|
*
|
|
254
|
-
* If a
|
|
255
|
-
* otherwise the maven command is invoked to collect the jar
|
|
385
|
+
* If cdxgen left a namespace map next to the BOM (see {@link readNamespaceMap})
|
|
386
|
+
* it is reused; otherwise the maven command is invoked to collect the jar
|
|
387
|
+
* dependencies. Each
|
|
256
388
|
* resolved purl is recorded in the `purlsJars` map and persisted in the
|
|
257
389
|
* `Namespaces` model.
|
|
258
390
|
*
|
|
@@ -268,19 +400,7 @@ export async function catalogMavenDeps(
|
|
|
268
400
|
Namespaces,
|
|
269
401
|
options = {},
|
|
270
402
|
) {
|
|
271
|
-
let jarNSMapping;
|
|
272
|
-
if (safeExistsSync(join(dirPath, "bom.json.map"))) {
|
|
273
|
-
try {
|
|
274
|
-
const mapData = JSON.parse(
|
|
275
|
-
fs.readFileSync(join(dirPath, "bom.json.map"), "utf-8"),
|
|
276
|
-
);
|
|
277
|
-
if (mapData && Object.keys(mapData).length) {
|
|
278
|
-
jarNSMapping = mapData;
|
|
279
|
-
}
|
|
280
|
-
} catch (_err) {
|
|
281
|
-
// ignore
|
|
282
|
-
}
|
|
283
|
-
}
|
|
403
|
+
let jarNSMapping = readNamespaceMap(dirPath, options);
|
|
284
404
|
if (!jarNSMapping) {
|
|
285
405
|
console.log("About to collect jar dependencies for the path", dirPath);
|
|
286
406
|
const mavenCmd = getMavenCommand(dirPath, dirPath);
|
|
@@ -324,18 +444,28 @@ export async function catalogMavenDeps(
|
|
|
324
444
|
* @param {Object} Namespaces Sequelize-like Namespaces model used to persist pom and namespaces
|
|
325
445
|
* @returns {Promise<void>}
|
|
326
446
|
*/
|
|
327
|
-
export async function catalogGradleDeps(
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
447
|
+
export async function catalogGradleDeps(
|
|
448
|
+
dirPath,
|
|
449
|
+
purlsJars,
|
|
450
|
+
Namespaces,
|
|
451
|
+
options = {},
|
|
452
|
+
) {
|
|
453
|
+
// The map cdxgen wrote next to the BOM already holds the namespaces; the
|
|
454
|
+
// Gradle cache is walked only without it.
|
|
455
|
+
let jarNSMapping = readNamespaceMap(dirPath, options);
|
|
456
|
+
if (!jarNSMapping) {
|
|
457
|
+
console.log(
|
|
458
|
+
"About to collect jar dependencies from the gradle cache. This would take a while ...",
|
|
459
|
+
);
|
|
460
|
+
const gradleCmd = getGradleCommand(dirPath, dirPath);
|
|
461
|
+
// collect all jars including from the cache if data-flow mode is enabled
|
|
462
|
+
jarNSMapping = await collectGradleDependencies(
|
|
463
|
+
gradleCmd,
|
|
464
|
+
dirPath,
|
|
465
|
+
false,
|
|
466
|
+
true,
|
|
467
|
+
);
|
|
468
|
+
}
|
|
339
469
|
if (jarNSMapping) {
|
|
340
470
|
for (const purl of Object.keys(jarNSMapping)) {
|
|
341
471
|
purlsJars[purl] = jarNSMapping[purl].jarFile;
|
|
@@ -515,7 +645,9 @@ export async function createSlice(
|
|
|
515
645
|
resolve(slicesFile),
|
|
516
646
|
options,
|
|
517
647
|
);
|
|
518
|
-
|
|
648
|
+
// A stopped run has already said why; it says nothing about whether the
|
|
649
|
+
// project is supported (issue 4438).
|
|
650
|
+
if (!sliceResult && !dosaiRunWasStopped(resolve(slicesFile))) {
|
|
519
651
|
console.warn(
|
|
520
652
|
`Unable to generate ${sliceType} slice using dosai. Check if this is a supported .NET project.`,
|
|
521
653
|
);
|
|
@@ -552,15 +684,9 @@ export async function createSlice(
|
|
|
552
684
|
"Use an official cdxgen container image to improve the precision of endpoints detection (for SaaSBOM).",
|
|
553
685
|
);
|
|
554
686
|
}
|
|
555
|
-
if (
|
|
687
|
+
if (language === "ruby") {
|
|
556
688
|
args.push("--remove-atom");
|
|
557
689
|
}
|
|
558
|
-
if (["scala"].includes(language)) {
|
|
559
|
-
semanticsSlicesFile = join(
|
|
560
|
-
sliceOutputDir,
|
|
561
|
-
basename(options.semanticsSlicesFile || "semantics.slices.json"),
|
|
562
|
-
);
|
|
563
|
-
}
|
|
564
690
|
}
|
|
565
691
|
args = args.concat([
|
|
566
692
|
"-l",
|
|
@@ -632,15 +758,6 @@ export async function createSlice(
|
|
|
632
758
|
`openapi spec file "${join(filePath, openapiSpecFile)}" was not generated successfully. Check if atom-tools pypi package is installed and available in PATH.`,
|
|
633
759
|
);
|
|
634
760
|
}
|
|
635
|
-
if (
|
|
636
|
-
["scala"].includes(language) &&
|
|
637
|
-
sliceType === "usages" &&
|
|
638
|
-
!safeExistsSync(semanticsSlicesFile)
|
|
639
|
-
) {
|
|
640
|
-
console.log(
|
|
641
|
-
`Semantics slices file "${semanticsSlicesFile}" was not generated successfully. Try running atom cli in Java mode.`,
|
|
642
|
-
);
|
|
643
|
-
}
|
|
644
761
|
return {
|
|
645
762
|
tempDir: sliceOutputDir,
|
|
646
763
|
tempDirOwned,
|
|
@@ -769,8 +886,8 @@ function usableSlicesFile(slicesFile) {
|
|
|
769
886
|
* describe the same project directory and must not predate the input SBOM:
|
|
770
887
|
* `cdxgen --evidence` writes a fresh SBOM before evinse runs, so an older
|
|
771
888
|
* slice was made from an earlier state of the sources and is regenerated.
|
|
772
|
-
* Scala
|
|
773
|
-
*
|
|
889
|
+
* Scala reports are handled by the scalasem analyzer, which applies its own
|
|
890
|
+
* rules.
|
|
774
891
|
*
|
|
775
892
|
* @param {string} language Project language
|
|
776
893
|
* @param {string} slicesFile Candidate semantics slices file
|
|
@@ -797,10 +914,7 @@ export function loadReusableSemanticsSlice(
|
|
|
797
914
|
return undefined;
|
|
798
915
|
}
|
|
799
916
|
if (!PROJECT_TYPE_ALIASES.swift.includes(language)) {
|
|
800
|
-
|
|
801
|
-
return Object.keys(parsed).some((key) => key.endsWith(".scala"))
|
|
802
|
-
? parsed
|
|
803
|
-
: undefined;
|
|
917
|
+
return undefined;
|
|
804
918
|
}
|
|
805
919
|
if (!(parsed.fileIndexes || parsed.buildSymbols || parsed.moduleInfos)) {
|
|
806
920
|
return undefined;
|
|
@@ -818,6 +932,217 @@ export function loadReusableSemanticsSlice(
|
|
|
818
932
|
return parsed;
|
|
819
933
|
}
|
|
820
934
|
|
|
935
|
+
/**
|
|
936
|
+
* The Scala evidence path: scalasem only, never atom. The branch returns
|
|
937
|
+
* early for every entry point (evinse, --evidence, cbom, saasbom) and every
|
|
938
|
+
* Scala project type, so no usages, reachables, data-flow or atom-run
|
|
939
|
+
* scalasem happens here. Slices the user passes are still read: a version 1
|
|
940
|
+
* semantics slice keeps its usedTypes join, usages and reachables slices
|
|
941
|
+
* keep their own evidence, and an OpenAPI spec keeps its services.
|
|
942
|
+
*
|
|
943
|
+
* @param {string} language Project language, normalized to `scala`.
|
|
944
|
+
* @param {string} dirPath Project directory.
|
|
945
|
+
* @param {Object} options Command line options.
|
|
946
|
+
* @param {Object} bomJson Parsed input SBOM.
|
|
947
|
+
* @param {Object[]} components Components of the input SBOM.
|
|
948
|
+
* @param {Object} purlLocationMap Occurrences seeded from the input SBOM.
|
|
949
|
+
* @param {Object} purlImportsMap Imports seeded from the input SBOM.
|
|
950
|
+
* @param {Object} dbObjMap The evinse database objects.
|
|
951
|
+
* @returns {Promise<Object>} Slice artefacts for `createEvinseFile`.
|
|
952
|
+
*/
|
|
953
|
+
async function analyzeScalaEvidenceProject(
|
|
954
|
+
language,
|
|
955
|
+
dirPath,
|
|
956
|
+
options,
|
|
957
|
+
bomJson,
|
|
958
|
+
components,
|
|
959
|
+
purlLocationMap,
|
|
960
|
+
purlImportsMap,
|
|
961
|
+
dbObjMap,
|
|
962
|
+
) {
|
|
963
|
+
let servicesMap = {};
|
|
964
|
+
const dataFlowFrames = {};
|
|
965
|
+
let componentPropertiesMap = {};
|
|
966
|
+
let metadataProperties = [];
|
|
967
|
+
let cryptoComponents = [];
|
|
968
|
+
const cryptoGeneratePurls = {};
|
|
969
|
+
const mergeLocations = (locationMap) => {
|
|
970
|
+
for (const [purl, locations] of Object.entries(locationMap || {})) {
|
|
971
|
+
purlLocationMap[purl] ??= new Set();
|
|
972
|
+
for (const location of locations) {
|
|
973
|
+
purlLocationMap[purl].add(location);
|
|
974
|
+
}
|
|
975
|
+
}
|
|
976
|
+
};
|
|
977
|
+
const mergeFrames = (framesMap) => {
|
|
978
|
+
for (const [purl, stacks] of Object.entries(framesMap || {})) {
|
|
979
|
+
dataFlowFrames[purl] = [...(dataFlowFrames[purl] || []), ...stacks];
|
|
980
|
+
}
|
|
981
|
+
};
|
|
982
|
+
const mergeProvides = (providesMap) => {
|
|
983
|
+
for (const [purl, refs] of Object.entries(providesMap || {})) {
|
|
984
|
+
cryptoGeneratePurls[purl] ??= new Set();
|
|
985
|
+
for (const ref of refs || []) {
|
|
986
|
+
cryptoGeneratePurls[purl].add(ref);
|
|
987
|
+
}
|
|
988
|
+
}
|
|
989
|
+
};
|
|
990
|
+
const analysis = analyzeScalaProject(dirPath, options);
|
|
991
|
+
if (analysis?.report) {
|
|
992
|
+
const evidence = await collectScalasemEvidence(
|
|
993
|
+
analysis.report,
|
|
994
|
+
[bomJson.metadata?.component, ...components].filter(Boolean),
|
|
995
|
+
{ ...options, scalaNamespaceMap: readNamespaceMap(dirPath, options) },
|
|
996
|
+
);
|
|
997
|
+
mergeLocations(evidence.purlLocationMap);
|
|
998
|
+
mergeFrames(evidence.dataFlowFrames);
|
|
999
|
+
mergeProvides(evidence.cryptoGeneratePurls);
|
|
1000
|
+
componentPropertiesMap = evidence.componentPropertiesMap;
|
|
1001
|
+
cryptoComponents = evidence.cryptoComponents;
|
|
1002
|
+
metadataProperties = evidence.metadataProperties;
|
|
1003
|
+
servicesMap = collectScalasemServices(analysis.report, servicesMap);
|
|
1004
|
+
servicesMap = collectScalasemApiEndpoints(analysis.report, servicesMap);
|
|
1005
|
+
}
|
|
1006
|
+
if (analysis?.metadataProperties?.length) {
|
|
1007
|
+
for (const property of analysis.metadataProperties) {
|
|
1008
|
+
if (
|
|
1009
|
+
!metadataProperties.some(
|
|
1010
|
+
(known) =>
|
|
1011
|
+
known.name === property.name && known.value === property.value,
|
|
1012
|
+
)
|
|
1013
|
+
) {
|
|
1014
|
+
metadataProperties.push(property);
|
|
1015
|
+
}
|
|
1016
|
+
}
|
|
1017
|
+
}
|
|
1018
|
+
// A version 1 semantics slice still goes through the usedTypes join, merged
|
|
1019
|
+
// with the report evidence rather than replacing it.
|
|
1020
|
+
if (analysis?.v1Slice) {
|
|
1021
|
+
mergeLocations(
|
|
1022
|
+
findPurlLocations(components, analysis.v1Slice).purlLocationMap,
|
|
1023
|
+
);
|
|
1024
|
+
}
|
|
1025
|
+
// Usages and reachables slices the user made, with atom or otherwise, are
|
|
1026
|
+
// read as they are for any other language; none is ever generated here.
|
|
1027
|
+
let usagesSlicesFile;
|
|
1028
|
+
let reachablesSlicesFile;
|
|
1029
|
+
if (
|
|
1030
|
+
(options.withReachables || options.includeCrypto) &&
|
|
1031
|
+
usableSlicesFile(options.reachablesSlicesFile)
|
|
1032
|
+
) {
|
|
1033
|
+
reachablesSlicesFile = options.reachablesSlicesFile;
|
|
1034
|
+
const reachables = collectReachableFrames(
|
|
1035
|
+
language,
|
|
1036
|
+
filterAtomSliceData(readReachablesSlices(reachablesSlicesFile), options),
|
|
1037
|
+
);
|
|
1038
|
+
mergeFrames(reachables.dataFlowFrames);
|
|
1039
|
+
mergeProvides(reachables.cryptoGeneratePurls);
|
|
1040
|
+
const knownRefs = new Set(
|
|
1041
|
+
cryptoComponents.map((asset) => asset["bom-ref"]),
|
|
1042
|
+
);
|
|
1043
|
+
cryptoComponents = cryptoComponents.concat(
|
|
1044
|
+
(reachables.cryptoComponents || []).filter(
|
|
1045
|
+
(asset) => !knownRefs.has(asset["bom-ref"]),
|
|
1046
|
+
),
|
|
1047
|
+
);
|
|
1048
|
+
}
|
|
1049
|
+
let userDefinedTypesMap = {};
|
|
1050
|
+
if (usableSlicesFile(options.usagesSlicesFile)) {
|
|
1051
|
+
usagesSlicesFile = options.usagesSlicesFile;
|
|
1052
|
+
const usageSlice = filterAtomSliceData(
|
|
1053
|
+
readSlicesFile(usagesSlicesFile, "usages"),
|
|
1054
|
+
options,
|
|
1055
|
+
);
|
|
1056
|
+
if (usageSlice && Object.keys(usageSlice).length) {
|
|
1057
|
+
const retMap = await parseObjectSlices(
|
|
1058
|
+
language,
|
|
1059
|
+
usageSlice,
|
|
1060
|
+
dbObjMap,
|
|
1061
|
+
servicesMap,
|
|
1062
|
+
purlLocationMap,
|
|
1063
|
+
purlImportsMap,
|
|
1064
|
+
);
|
|
1065
|
+
servicesMap = retMap.servicesMap || servicesMap;
|
|
1066
|
+
userDefinedTypesMap = retMap.userDefinedTypesMap || {};
|
|
1067
|
+
}
|
|
1068
|
+
}
|
|
1069
|
+
// An OpenAPI spec the user has (atom-tools' conversion of this report, or
|
|
1070
|
+
// the project's own) adds its routes to the report's: a route both name is
|
|
1071
|
+
// one service.
|
|
1072
|
+
const openapiSpecFile = options.openapiSpecFile
|
|
1073
|
+
? resolve(dirPath, options.openapiSpecFile)
|
|
1074
|
+
: undefined;
|
|
1075
|
+
if (openapiSpecFile && safeExistsSync(openapiSpecFile)) {
|
|
1076
|
+
const openapiServices = {};
|
|
1077
|
+
detectServicesFromOpenAPI(language, openapiSpecFile, openapiServices);
|
|
1078
|
+
mergeServiceDefinitions(servicesMap, openapiServices);
|
|
1079
|
+
}
|
|
1080
|
+
// A BOM without a project component still needs somewhere for the run's
|
|
1081
|
+
// metadata properties to live; a nameless entry would explain nothing.
|
|
1082
|
+
let workspaceComponent;
|
|
1083
|
+
if (!bomJson.metadata?.component) {
|
|
1084
|
+
workspaceComponent = {
|
|
1085
|
+
type: "application",
|
|
1086
|
+
name: basename(resolve(dirPath)),
|
|
1087
|
+
};
|
|
1088
|
+
}
|
|
1089
|
+
return {
|
|
1090
|
+
purlLocationMap,
|
|
1091
|
+
servicesMap,
|
|
1092
|
+
dataFlowFrames,
|
|
1093
|
+
componentPropertiesMap,
|
|
1094
|
+
metadataProperties,
|
|
1095
|
+
cryptoComponents,
|
|
1096
|
+
cryptoGeneratePurls,
|
|
1097
|
+
usagesSlicesFile,
|
|
1098
|
+
reachablesSlicesFile,
|
|
1099
|
+
semanticsSlicesFile: analysis?.reportFile,
|
|
1100
|
+
userDefinedTypesMap,
|
|
1101
|
+
workspaceComponent,
|
|
1102
|
+
};
|
|
1103
|
+
}
|
|
1104
|
+
|
|
1105
|
+
/**
|
|
1106
|
+
* Merge service definitions into a services map: endpoints join, and a
|
|
1107
|
+
* property another source already carries is not added again, whatever the
|
|
1108
|
+
* case of an HTTP method.
|
|
1109
|
+
*
|
|
1110
|
+
* @param {Object} target Services map that receives the definitions.
|
|
1111
|
+
* @param {Object} source Services map to merge in.
|
|
1112
|
+
* @returns {Object} The target map.
|
|
1113
|
+
*/
|
|
1114
|
+
export function mergeServiceDefinitions(target, source) {
|
|
1115
|
+
const sameProperty = (left, right) =>
|
|
1116
|
+
left.name === right.name &&
|
|
1117
|
+
(left.name === "cdx:service:httpMethod"
|
|
1118
|
+
? String(left.value).toUpperCase() === String(right.value).toUpperCase()
|
|
1119
|
+
: left.value === right.value);
|
|
1120
|
+
for (const [name, definition] of Object.entries(source || {})) {
|
|
1121
|
+
const existing = target[name];
|
|
1122
|
+
if (!existing) {
|
|
1123
|
+
target[name] = definition;
|
|
1124
|
+
continue;
|
|
1125
|
+
}
|
|
1126
|
+
existing.endpoints ??= new Set();
|
|
1127
|
+
for (const endpoint of definition.endpoints || []) {
|
|
1128
|
+
existing.endpoints.add(endpoint);
|
|
1129
|
+
}
|
|
1130
|
+
existing.properties ??= [];
|
|
1131
|
+
for (const property of definition.properties || []) {
|
|
1132
|
+
if (!existing.properties.some((known) => sameProperty(known, property))) {
|
|
1133
|
+
existing.properties.push(property);
|
|
1134
|
+
}
|
|
1135
|
+
}
|
|
1136
|
+
if (definition.authenticated) {
|
|
1137
|
+
existing.authenticated = true;
|
|
1138
|
+
}
|
|
1139
|
+
if (definition.xTrustBoundary) {
|
|
1140
|
+
existing.xTrustBoundary = true;
|
|
1141
|
+
}
|
|
1142
|
+
}
|
|
1143
|
+
return target;
|
|
1144
|
+
}
|
|
1145
|
+
|
|
821
1146
|
/**
|
|
822
1147
|
* Function to analyze the project
|
|
823
1148
|
*
|
|
@@ -827,7 +1152,12 @@ export function loadReusableSemanticsSlice(
|
|
|
827
1152
|
export async function analyzeProject(dbObjMap, options) {
|
|
828
1153
|
const dirPath = options._[0] || ".";
|
|
829
1154
|
const languages = options.language;
|
|
830
|
-
|
|
1155
|
+
let language = Array.isArray(languages) ? languages[0] : languages;
|
|
1156
|
+
// cdxgen --evidence forwards the raw project type, so -t sbt, -t mill and
|
|
1157
|
+
// -t scala3 must be normalized once to behave like -l scala everywhere.
|
|
1158
|
+
if (PROJECT_TYPE_ALIASES.scala.includes(language)) {
|
|
1159
|
+
language = "scala";
|
|
1160
|
+
}
|
|
831
1161
|
let usageSlice;
|
|
832
1162
|
let dataFlowSlice;
|
|
833
1163
|
let reachablesSlice;
|
|
@@ -981,6 +1311,19 @@ export async function analyzeProject(dbObjMap, options) {
|
|
|
981
1311
|
};
|
|
982
1312
|
}
|
|
983
1313
|
|
|
1314
|
+
if (isScalasemLanguage(language)) {
|
|
1315
|
+
return analyzeScalaEvidenceProject(
|
|
1316
|
+
language,
|
|
1317
|
+
dirPath,
|
|
1318
|
+
options,
|
|
1319
|
+
bomJson,
|
|
1320
|
+
components,
|
|
1321
|
+
purlLocationMap,
|
|
1322
|
+
purlImportsMap,
|
|
1323
|
+
dbObjMap,
|
|
1324
|
+
);
|
|
1325
|
+
}
|
|
1326
|
+
|
|
984
1327
|
if (isDosaiDotnetLanguage(language)) {
|
|
985
1328
|
if (options.profile === "research") {
|
|
986
1329
|
options.withDataFlow = true;
|
|
@@ -1011,11 +1354,17 @@ export async function analyzeProject(dbObjMap, options) {
|
|
|
1011
1354
|
}
|
|
1012
1355
|
}
|
|
1013
1356
|
if (usageSlice && Object.keys(usageSlice).length) {
|
|
1014
|
-
|
|
1357
|
+
// dosai locations are relative to the analyzed directory; a package the
|
|
1358
|
+
// BOM holds in several versions is matched by the project of the file.
|
|
1359
|
+
const dosaiEvidence = collectDosaiPurlEvidence(usageSlice, components, {
|
|
1360
|
+
srcPath: resolve(dirPath),
|
|
1361
|
+
});
|
|
1015
1362
|
// Confidence/evidence facts ride along as properties so an unbuilt tree (Low
|
|
1016
1363
|
// confidence, unresolved evidence) stays distinguishable from an only-imported
|
|
1017
1364
|
// package in the emitted BOM.
|
|
1018
|
-
applyDosaiReachabilityEvidence(usageSlice, components
|
|
1365
|
+
applyDosaiReachabilityEvidence(usageSlice, components, {
|
|
1366
|
+
srcPath: resolve(dirPath),
|
|
1367
|
+
});
|
|
1019
1368
|
for (const [purl, locations] of Object.entries(
|
|
1020
1369
|
dosaiEvidence.purlLocationMap,
|
|
1021
1370
|
)) {
|
|
@@ -1049,7 +1398,9 @@ export async function analyzeProject(dbObjMap, options) {
|
|
|
1049
1398
|
}
|
|
1050
1399
|
}
|
|
1051
1400
|
if (dataFlowSlice && Object.keys(dataFlowSlice).length) {
|
|
1052
|
-
dataFlowFrames = collectDosaiDataFlowFrames(dataFlowSlice, components
|
|
1401
|
+
dataFlowFrames = collectDosaiDataFlowFrames(dataFlowSlice, components, {
|
|
1402
|
+
srcPath: resolve(dirPath),
|
|
1403
|
+
});
|
|
1053
1404
|
}
|
|
1054
1405
|
}
|
|
1055
1406
|
if (options.includeCrypto) {
|
|
@@ -1145,12 +1496,8 @@ export async function analyzeProject(dbObjMap, options) {
|
|
|
1145
1496
|
}
|
|
1146
1497
|
}
|
|
1147
1498
|
// Support for semantics slicing
|
|
1148
|
-
if (
|
|
1149
|
-
|
|
1150
|
-
PROJECT_TYPE_ALIASES.scala.includes(language)) &&
|
|
1151
|
-
components.length
|
|
1152
|
-
) {
|
|
1153
|
-
// Reuse existing semantics slices for swift and scala
|
|
1499
|
+
if (PROJECT_TYPE_ALIASES.swift.includes(language) && components.length) {
|
|
1500
|
+
// Reuse existing semantics slices for swift
|
|
1154
1501
|
const reusableSlice = loadReusableSemanticsSlice(
|
|
1155
1502
|
language,
|
|
1156
1503
|
options.semanticsSlicesFile,
|
|
@@ -1160,7 +1507,7 @@ export async function analyzeProject(dbObjMap, options) {
|
|
|
1160
1507
|
if (reusableSlice) {
|
|
1161
1508
|
semanticsSlice = reusableSlice;
|
|
1162
1509
|
semanticsSlicesFile = options.semanticsSlicesFile;
|
|
1163
|
-
} else
|
|
1510
|
+
} else {
|
|
1164
1511
|
// Generate our own slices for swift
|
|
1165
1512
|
retMap = await createSlice(language, dirPath, "semantics", options);
|
|
1166
1513
|
if (retMap?.slicesFile && safeExistsSync(retMap.slicesFile)) {
|
|
@@ -1169,6 +1516,28 @@ export async function analyzeProject(dbObjMap, options) {
|
|
|
1169
1516
|
}
|
|
1170
1517
|
}
|
|
1171
1518
|
}
|
|
1519
|
+
let cIncludeLocations = new Map();
|
|
1520
|
+
// A C/C++ include names the file it resolved to and the functions the
|
|
1521
|
+
// including file calls from it (atom 4 and later): those functions are the
|
|
1522
|
+
// usages of the component that provides the file
|
|
1523
|
+
if (
|
|
1524
|
+
["c", "cpp", "c++"].includes(language) &&
|
|
1525
|
+
usageSlice &&
|
|
1526
|
+
Object.keys(usageSlice).length
|
|
1527
|
+
) {
|
|
1528
|
+
const attributed = attributeCIncludes(
|
|
1529
|
+
usageSlice,
|
|
1530
|
+
components,
|
|
1531
|
+
dirPath,
|
|
1532
|
+
options,
|
|
1533
|
+
);
|
|
1534
|
+
for (const [purl, names] of attributed.symbols) {
|
|
1535
|
+
purlImportsMap[purl] = Array.from(
|
|
1536
|
+
new Set([...(purlImportsMap[purl] || []), ...names]),
|
|
1537
|
+
);
|
|
1538
|
+
}
|
|
1539
|
+
cIncludeLocations = attributed.locations;
|
|
1540
|
+
}
|
|
1172
1541
|
// Parse usage slices
|
|
1173
1542
|
if (usageSlice && Object.keys(usageSlice).length) {
|
|
1174
1543
|
const retMap = await parseObjectSlices(
|
|
@@ -1184,7 +1553,14 @@ export async function analyzeProject(dbObjMap, options) {
|
|
|
1184
1553
|
servicesMap = retMap.servicesMap;
|
|
1185
1554
|
userDefinedTypesMap = retMap.userDefinedTypesMap;
|
|
1186
1555
|
}
|
|
1187
|
-
//
|
|
1556
|
+
// A component whose calls the slices do not show (code inside the project,
|
|
1557
|
+
// such as a vendored library) is still used where its header is included
|
|
1558
|
+
for (const [purl, includeLocations] of cIncludeLocations) {
|
|
1559
|
+
if (!purlLocationMap[purl]?.size) {
|
|
1560
|
+
purlLocationMap[purl] = new Set(includeLocations);
|
|
1561
|
+
}
|
|
1562
|
+
}
|
|
1563
|
+
// Parse the semantics slices for swift
|
|
1188
1564
|
if (
|
|
1189
1565
|
semanticsSlice &&
|
|
1190
1566
|
Object.keys(semanticsSlice).length &&
|
|
@@ -1192,7 +1568,25 @@ export async function analyzeProject(dbObjMap, options) {
|
|
|
1192
1568
|
) {
|
|
1193
1569
|
// Identify the purl locations
|
|
1194
1570
|
const retMap = parseSemanticSlices(language, components, semanticsSlice);
|
|
1195
|
-
|
|
1571
|
+
// The usages step may already have locations for a purl; the semantics
|
|
1572
|
+
// pass adds its own rather than replacing them.
|
|
1573
|
+
for (const [apurl, locations] of Object.entries(
|
|
1574
|
+
retMap.purlLocationMap || {},
|
|
1575
|
+
)) {
|
|
1576
|
+
purlLocationMap[apurl] ??= new Set();
|
|
1577
|
+
for (const alocation of locations) {
|
|
1578
|
+
purlLocationMap[apurl].add(alocation);
|
|
1579
|
+
}
|
|
1580
|
+
}
|
|
1581
|
+
}
|
|
1582
|
+
// A user-supplied OpenAPI spec must yield services even when the usages
|
|
1583
|
+
// slice is empty.
|
|
1584
|
+
if (
|
|
1585
|
+
openapiSpecFile &&
|
|
1586
|
+
safeExistsSync(openapiSpecFile) &&
|
|
1587
|
+
!Object.keys(servicesMap).length
|
|
1588
|
+
) {
|
|
1589
|
+
detectServicesFromOpenAPI(language, openapiSpecFile, servicesMap);
|
|
1196
1590
|
}
|
|
1197
1591
|
if (options.withDataFlow) {
|
|
1198
1592
|
if (
|
|
@@ -1261,6 +1655,125 @@ export async function analyzeProject(dbObjMap, options) {
|
|
|
1261
1655
|
* @param {string} [openapiSpecFile=undefined] Optional path to an OpenAPI spec used for service detection
|
|
1262
1656
|
* @returns {Promise<Object>} Map with `purlLocationMap`, `servicesMap`, and `userDefinedTypesMap`
|
|
1263
1657
|
*/
|
|
1658
|
+
/**
|
|
1659
|
+
* Attribute the include slices of a C/C++ usages report to the BOM
|
|
1660
|
+
* components that provide the files they resolved to: each header's imported
|
|
1661
|
+
* symbols (the functions the including file calls that it declares), and the
|
|
1662
|
+
* include directive's location. Reports from atom releases that do not name
|
|
1663
|
+
* the resolved file attribute nothing.
|
|
1664
|
+
*
|
|
1665
|
+
* @param {Object} usageSlice Parsed usages report
|
|
1666
|
+
* @param {Object[]} components Components of the input BOM
|
|
1667
|
+
* @param {string} dirPath Source directory
|
|
1668
|
+
* @param {Object} options CLI options
|
|
1669
|
+
* @returns {{symbols: Map<string, Set<string>>, locations: Map<string, Set<string>>}}
|
|
1670
|
+
* Imported symbols and include locations (`file#line`) by purl
|
|
1671
|
+
*/
|
|
1672
|
+
export function attributeCIncludes(
|
|
1673
|
+
usageSlice,
|
|
1674
|
+
components,
|
|
1675
|
+
dirPath,
|
|
1676
|
+
options = {},
|
|
1677
|
+
) {
|
|
1678
|
+
const symbols = new Map();
|
|
1679
|
+
const locations = new Map();
|
|
1680
|
+
const includes = [...parseCIncludeSlices(usageSlice)].filter(
|
|
1681
|
+
([, e]) => e.paths.size && e.symbols.size,
|
|
1682
|
+
);
|
|
1683
|
+
if (!includes.length || !components?.length) {
|
|
1684
|
+
return { symbols, locations };
|
|
1685
|
+
}
|
|
1686
|
+
const attribute = createIncludeAttributor({
|
|
1687
|
+
src: dirPath,
|
|
1688
|
+
components,
|
|
1689
|
+
buildDirs: cmakeBuildDirCandidates(dirPath, options),
|
|
1690
|
+
});
|
|
1691
|
+
const withPurl = components.filter((c) => c?.purl);
|
|
1692
|
+
const byName = (name, purlType) =>
|
|
1693
|
+
withPurl.find(
|
|
1694
|
+
(c) =>
|
|
1695
|
+
c.name?.toLowerCase() === `${name}`.toLowerCase() &&
|
|
1696
|
+
(!purlType || c.purl.startsWith(`pkg:${purlType}/`)),
|
|
1697
|
+
);
|
|
1698
|
+
const purlByHeader = new Map();
|
|
1699
|
+
for (const [header, entry] of includes) {
|
|
1700
|
+
const target = attribute(entry.paths);
|
|
1701
|
+
let component;
|
|
1702
|
+
if (target?.kind === "component") {
|
|
1703
|
+
component = target.component;
|
|
1704
|
+
} else if (target?.kind === "os") {
|
|
1705
|
+
component =
|
|
1706
|
+
withPurl.find((c) => c.purl === target.pkgInfo.purl) ||
|
|
1707
|
+
byName(target.pkgInfo.name);
|
|
1708
|
+
} else if (target?.kind === "vcpkg") {
|
|
1709
|
+
component = byName(target.port);
|
|
1710
|
+
} else if (target?.kind === "conan") {
|
|
1711
|
+
component = byName(target.name, "conan") || byName(target.name);
|
|
1712
|
+
}
|
|
1713
|
+
if (!component?.purl) {
|
|
1714
|
+
continue;
|
|
1715
|
+
}
|
|
1716
|
+
purlByHeader.set(header, component.purl);
|
|
1717
|
+
const known = symbols.get(component.purl) || new Set();
|
|
1718
|
+
for (const symbol of entry.symbols) {
|
|
1719
|
+
known.add(symbol);
|
|
1720
|
+
}
|
|
1721
|
+
symbols.set(component.purl, known);
|
|
1722
|
+
}
|
|
1723
|
+
// the directives that include an attributed header and use it
|
|
1724
|
+
for (const slice of usageSlice?.objectSlices || []) {
|
|
1725
|
+
const purl = purlByHeader.get(slice?.fullName);
|
|
1726
|
+
if (
|
|
1727
|
+
!purl ||
|
|
1728
|
+
!`${slice.code || ""}`.startsWith("#include") ||
|
|
1729
|
+
!slice.fileName ||
|
|
1730
|
+
!slice.importedSymbols?.length
|
|
1731
|
+
) {
|
|
1732
|
+
continue;
|
|
1733
|
+
}
|
|
1734
|
+
const known = locations.get(purl) || new Set();
|
|
1735
|
+
known.add(
|
|
1736
|
+
slice.lineNumber
|
|
1737
|
+
? `${slice.fileName}#${slice.lineNumber}`
|
|
1738
|
+
: slice.fileName,
|
|
1739
|
+
);
|
|
1740
|
+
locations.set(purl, known);
|
|
1741
|
+
}
|
|
1742
|
+
return { symbols, locations };
|
|
1743
|
+
}
|
|
1744
|
+
|
|
1745
|
+
/**
|
|
1746
|
+
* Seed the C/C++ imports from the include slices of a usages report (see
|
|
1747
|
+
* `attributeCIncludes`).
|
|
1748
|
+
*
|
|
1749
|
+
* @param {Object} usageSlice Parsed usages report
|
|
1750
|
+
* @param {Object[]} components Components of the input BOM
|
|
1751
|
+
* @param {string} dirPath Source directory
|
|
1752
|
+
* @param {Object} purlImportsMap Imports by purl, extended in place
|
|
1753
|
+
* @param {Object} options CLI options
|
|
1754
|
+
* @returns {Object} purlImportsMap
|
|
1755
|
+
*/
|
|
1756
|
+
export function seedCImportsFromIncludes(
|
|
1757
|
+
usageSlice,
|
|
1758
|
+
components,
|
|
1759
|
+
dirPath,
|
|
1760
|
+
purlImportsMap = {},
|
|
1761
|
+
options = {},
|
|
1762
|
+
) {
|
|
1763
|
+
const { symbols } = attributeCIncludes(
|
|
1764
|
+
usageSlice,
|
|
1765
|
+
components,
|
|
1766
|
+
dirPath,
|
|
1767
|
+
options,
|
|
1768
|
+
);
|
|
1769
|
+
for (const [purl, names] of symbols) {
|
|
1770
|
+
purlImportsMap[purl] = Array.from(
|
|
1771
|
+
new Set([...(purlImportsMap[purl] || []), ...names]),
|
|
1772
|
+
);
|
|
1773
|
+
}
|
|
1774
|
+
return purlImportsMap;
|
|
1775
|
+
}
|
|
1776
|
+
|
|
1264
1777
|
export async function parseObjectSlices(
|
|
1265
1778
|
language,
|
|
1266
1779
|
usageSlice,
|
|
@@ -1581,11 +2094,7 @@ export async function parseSliceUsages(
|
|
|
1581
2094
|
* @param {Object} semanticsSlice Semantic slice data
|
|
1582
2095
|
* @returns {Object} Parsed metadata
|
|
1583
2096
|
*/
|
|
1584
|
-
export function parseSemanticSlices(
|
|
1585
|
-
// For scala, use the dedicated scalasem module.
|
|
1586
|
-
if (language === "scala") {
|
|
1587
|
-
return findPurlLocations(components, semanticsSlice);
|
|
1588
|
-
}
|
|
2097
|
+
export function parseSemanticSlices(_language, components, semanticsSlice) {
|
|
1589
2098
|
if (!components) {
|
|
1590
2099
|
return undefined;
|
|
1591
2100
|
}
|