@cdxgen/cdxgen 13.2.0 → 13.3.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 +28 -12
- package/bin/cdxgen.js +227 -119
- package/bin/evinse.js +5 -0
- package/bin/sign.js +88 -14
- package/bin/tracebom.js +64 -4
- package/bin/validate.js +29 -7
- package/bin/verify.js +79 -39
- package/data/remediations.json +24 -0
- package/lib/cli/bomAssembly.js +105 -15
- package/lib/cli/cliOptions.js +93 -1
- package/lib/cli/index.js +43 -4
- package/lib/cli/jsBom.js +49 -24
- package/lib/cli/jvmBom.js +46 -12
- package/lib/cli/managedBom.js +112 -41
- package/lib/cli/nativeBom.js +125 -14
- package/lib/core/env.js +5 -6
- package/lib/core/fs.js +174 -6
- package/lib/core/metadataProperties.js +97 -0
- package/lib/core/projectConfig.js +31 -4
- package/lib/ecosystems/bunutils.js +269 -101
- package/lib/ecosystems/caxa.js +434 -2
- package/lib/ecosystems/cmakeResolver.js +66 -0
- package/lib/ecosystems/cppEvidence.js +64 -31
- package/lib/ecosystems/ecosystems.js +19 -8
- package/lib/ecosystems/gradleutils.js +42 -7
- package/lib/ecosystems/jsEvidence.js +19 -20
- package/lib/ecosystems/kosi.js +10 -3
- package/lib/ecosystems/npmutils.js +110 -9
- package/lib/ecosystems/parsers-dotnet.js +51 -10
- package/lib/ecosystems/parsers-go.js +47 -9
- package/lib/ecosystems/parsers-js.js +171 -50
- package/lib/ecosystems/parsers-misc.js +747 -161
- package/lib/ecosystems/parsers-python.js +26 -14
- package/lib/ecosystems/parsers-rust.js +24 -18
- package/lib/ecosystems/rubyutils.js +1426 -166
- package/lib/ecosystems/rusi.js +32 -1
- package/lib/evinser/evinser.js +436 -128
- package/lib/evinser/swiftsem.js +1914 -302
- package/lib/helpers/bomSigner.js +652 -221
- package/lib/inventory/atomUtils.js +568 -31
- package/lib/inventory/cdxrs.js +103 -12
- package/lib/inventory/depsUtils.js +183 -11
- package/lib/inventory/display.js +4 -5
- package/lib/inventory/dosai.js +550 -13
- package/lib/inventory/dynamic.js +39 -1
- package/lib/inventory/evidenceUtils.js +14 -6
- package/lib/inventory/fetchBatch.js +116 -17
- package/lib/inventory/protobom.js +8 -1
- package/lib/inventory/purl.js +152 -3
- package/lib/inventory/sbomDocument.js +2 -70
- package/lib/inventory/source.js +37 -19
- package/lib/inventory/traceRunner.js +210 -21
- package/lib/managers/binary.js +115 -24
- package/lib/managers/docker.js +440 -20
- package/lib/managers/oci.js +39 -1
- package/lib/parsers/cmakeLists.js +452 -0
- package/lib/parsers/largeJson.js +454 -0
- package/lib/parsers/npmrc.js +19 -1
- package/lib/stages/postgen/postgen.js +68 -15
- package/lib/validator/bomValidator.js +6 -16
- package/lib/validator/index.js +48 -10
- package/lib/validator/reporters/console.js +3 -0
- package/lib/validator/reporters/json.js +1 -0
- package/lib/validator/reporters/sarif.js +7 -0
- package/package.json +20 -20
- package/types/lib/cli/bomAssembly.d.ts +2 -1
- package/types/lib/cli/bomAssembly.d.ts.map +1 -1
- package/types/lib/cli/cliOptions.d.ts +33 -1
- package/types/lib/cli/cliOptions.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.map +1 -1
- package/types/lib/cli/managedBom.d.ts.map +1 -1
- package/types/lib/cli/nativeBom.d.ts.map +1 -1
- package/types/lib/core/env.d.ts.map +1 -1
- package/types/lib/core/fs.d.ts +55 -0
- package/types/lib/core/fs.d.ts.map +1 -1
- package/types/lib/core/metadataProperties.d.ts +39 -0
- package/types/lib/core/metadataProperties.d.ts.map +1 -0
- package/types/lib/core/projectConfig.d.ts +6 -2
- package/types/lib/core/projectConfig.d.ts.map +1 -1
- package/types/lib/ecosystems/bunutils.d.ts +12 -5
- package/types/lib/ecosystems/bunutils.d.ts.map +1 -1
- package/types/lib/ecosystems/caxa.d.ts +14 -0
- package/types/lib/ecosystems/caxa.d.ts.map +1 -1
- package/types/lib/ecosystems/cmakeResolver.d.ts +23 -0
- package/types/lib/ecosystems/cmakeResolver.d.ts.map +1 -1
- package/types/lib/ecosystems/cppEvidence.d.ts +1 -1
- package/types/lib/ecosystems/cppEvidence.d.ts.map +1 -1
- 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 +20 -4
- package/types/lib/ecosystems/gradleutils.d.ts.map +1 -1
- package/types/lib/ecosystems/jsEvidence.d.ts +5 -2
- package/types/lib/ecosystems/jsEvidence.d.ts.map +1 -1
- package/types/lib/ecosystems/kosi.d.ts +13 -0
- package/types/lib/ecosystems/kosi.d.ts.map +1 -1
- package/types/lib/ecosystems/npmutils.d.ts +42 -0
- package/types/lib/ecosystems/npmutils.d.ts.map +1 -1
- package/types/lib/ecosystems/parsers-dotnet.d.ts.map +1 -1
- package/types/lib/ecosystems/parsers-go.d.ts +10 -0
- package/types/lib/ecosystems/parsers-go.d.ts.map +1 -1
- package/types/lib/ecosystems/parsers-js.d.ts +10 -4
- package/types/lib/ecosystems/parsers-js.d.ts.map +1 -1
- package/types/lib/ecosystems/parsers-misc.d.ts +67 -3
- package/types/lib/ecosystems/parsers-misc.d.ts.map +1 -1
- package/types/lib/ecosystems/parsers-python.d.ts.map +1 -1
- package/types/lib/ecosystems/parsers-rust.d.ts.map +1 -1
- package/types/lib/ecosystems/pythonutils.d.ts +1 -1
- package/types/lib/ecosystems/rubyutils.d.ts +143 -2
- package/types/lib/ecosystems/rubyutils.d.ts.map +1 -1
- package/types/lib/ecosystems/rusi.d.ts +13 -0
- package/types/lib/ecosystems/rusi.d.ts.map +1 -1
- package/types/lib/evinser/evinser.d.ts +59 -9
- package/types/lib/evinser/evinser.d.ts.map +1 -1
- package/types/lib/evinser/swiftsem.d.ts +280 -15
- package/types/lib/evinser/swiftsem.d.ts.map +1 -1
- package/types/lib/helpers/bomSigner.d.ts +106 -13
- package/types/lib/helpers/bomSigner.d.ts.map +1 -1
- package/types/lib/inventory/atomUtils.d.ts +145 -3
- package/types/lib/inventory/atomUtils.d.ts.map +1 -1
- package/types/lib/inventory/cdxrs.d.ts +8 -3
- package/types/lib/inventory/cdxrs.d.ts.map +1 -1
- package/types/lib/inventory/depsUtils.d.ts +15 -0
- package/types/lib/inventory/depsUtils.d.ts.map +1 -1
- package/types/lib/inventory/display.d.ts.map +1 -1
- package/types/lib/inventory/dosai.d.ts +64 -1
- package/types/lib/inventory/dosai.d.ts.map +1 -1
- package/types/lib/inventory/dynamic.d.ts.map +1 -1
- package/types/lib/inventory/evidenceUtils.d.ts.map +1 -1
- package/types/lib/inventory/fetchBatch.d.ts.map +1 -1
- package/types/lib/inventory/plugins.d.ts.map +1 -1
- package/types/lib/inventory/protobom.d.ts.map +1 -1
- package/types/lib/inventory/purl.d.ts +71 -2
- package/types/lib/inventory/purl.d.ts.map +1 -1
- package/types/lib/inventory/sbomDocument.d.ts.map +1 -1
- package/types/lib/inventory/source.d.ts.map +1 -1
- package/types/lib/inventory/traceRunner.d.ts +47 -1
- package/types/lib/inventory/traceRunner.d.ts.map +1 -1
- package/types/lib/managers/binary.d.ts +30 -1
- package/types/lib/managers/binary.d.ts.map +1 -1
- package/types/lib/managers/docker.d.ts +29 -0
- package/types/lib/managers/docker.d.ts.map +1 -1
- package/types/lib/managers/oci.d.ts +14 -0
- package/types/lib/managers/oci.d.ts.map +1 -1
- package/types/lib/parsers/cmakeLists.d.ts +76 -0
- package/types/lib/parsers/cmakeLists.d.ts.map +1 -0
- package/types/lib/parsers/largeJson.d.ts +52 -0
- package/types/lib/parsers/largeJson.d.ts.map +1 -0
- package/types/lib/parsers/npmrc.d.ts +5 -0
- package/types/lib/parsers/npmrc.d.ts.map +1 -1
- package/types/lib/stages/postgen/postgen.d.ts.map +1 -1
- package/types/lib/validator/bomValidator.d.ts.map +1 -1
- package/types/lib/validator/index.d.ts +10 -1
- package/types/lib/validator/index.d.ts.map +1 -1
- package/types/lib/validator/reporters/console.d.ts.map +1 -1
- package/types/lib/validator/reporters/json.d.ts.map +1 -1
- package/types/lib/validator/reporters/sarif.d.ts.map +1 -1
package/lib/evinser/evinser.js
CHANGED
|
@@ -40,10 +40,15 @@ import {
|
|
|
40
40
|
isRusiRustLanguage,
|
|
41
41
|
} from "../ecosystems/rusi.js";
|
|
42
42
|
import {
|
|
43
|
+
atomCompileCommandsArgs,
|
|
43
44
|
buildAtomCommandEnv,
|
|
44
45
|
executeAtom,
|
|
45
46
|
filterAtomSlicesByExcludePatterns,
|
|
47
|
+
readReachablesSlices,
|
|
48
|
+
readSlicesFile,
|
|
49
|
+
removeReachablesChunkFiles,
|
|
46
50
|
} from "../inventory/atomUtils.js";
|
|
51
|
+
import { isCycloneDxComponentTypeEnabled } from "../inventory/bomUtils.js";
|
|
47
52
|
import {
|
|
48
53
|
collectDosaiCryptoComponents,
|
|
49
54
|
findCryptoAlgos,
|
|
@@ -62,9 +67,12 @@ import {
|
|
|
62
67
|
isDosaiDotnetLanguage,
|
|
63
68
|
normalizeDosaiServiceMap,
|
|
64
69
|
persistDosaiSemanticsReport,
|
|
70
|
+
readDosaiDataFlowReport,
|
|
71
|
+
readDosaiMethodsReport,
|
|
65
72
|
} from "../inventory/dosai.js";
|
|
66
73
|
import { parseOccurrenceEvidenceLocation } from "../inventory/evidenceUtils.js";
|
|
67
74
|
import { executeOsQuery } from "../managers/binary.js";
|
|
75
|
+
import { MAX_JSON_TEXT_BYTES, readJsonFile } from "../parsers/largeJson.js";
|
|
68
76
|
import { postProcess } from "../stages/postgen/postgen.js";
|
|
69
77
|
import { createOrLoad } from "./db.js";
|
|
70
78
|
import { findPurlLocations } from "./scalasem.js";
|
|
@@ -96,10 +104,88 @@ function appendComponentProperties(component, properties = []) {
|
|
|
96
104
|
}
|
|
97
105
|
}
|
|
98
106
|
|
|
107
|
+
/**
|
|
108
|
+
* Merges one analyzer run's metadata properties into the BOM's metadata
|
|
109
|
+
* component. Analyzer metadata describes a single run, so when the input BOM
|
|
110
|
+
* already carries facts from an earlier run (cdxgen --profile research, then
|
|
111
|
+
* evinse), everything under the analyzer's own `cdx:<analyzer>:` prefix is
|
|
112
|
+
* replaced. Replacing by exact name would keep facts the new run no longer
|
|
113
|
+
* emits, such as a cdx:rusi:requestedBackend left from a fallback run.
|
|
114
|
+
*
|
|
115
|
+
* @param {Object} component Metadata component to update in place
|
|
116
|
+
* @param {Object[]} metadataProperties Properties from the analyzer run
|
|
117
|
+
*/
|
|
118
|
+
export function mergeAnalyzerMetadataProperties(
|
|
119
|
+
component,
|
|
120
|
+
metadataProperties = [],
|
|
121
|
+
) {
|
|
122
|
+
if (!component || !metadataProperties.length) {
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
if (component.properties?.length) {
|
|
126
|
+
const analyzerPrefixes = [
|
|
127
|
+
...new Set(
|
|
128
|
+
metadataProperties
|
|
129
|
+
.map((p) => /^cdx:[^:]+:/.exec(p.name)?.[0])
|
|
130
|
+
.filter(Boolean),
|
|
131
|
+
),
|
|
132
|
+
];
|
|
133
|
+
component.properties = component.properties.filter(
|
|
134
|
+
(p) => !analyzerPrefixes.some((prefix) => p.name?.startsWith(prefix)),
|
|
135
|
+
);
|
|
136
|
+
}
|
|
137
|
+
appendComponentProperties(component, metadataProperties);
|
|
138
|
+
}
|
|
139
|
+
|
|
99
140
|
function filterAtomSliceData(sliceData, options = {}) {
|
|
100
141
|
return filterAtomSlicesByExcludePatterns(sliceData, options.exclude);
|
|
101
142
|
}
|
|
102
143
|
|
|
144
|
+
/**
|
|
145
|
+
* Build the options for the evinse phase (prepareDB, analyzeProject, and
|
|
146
|
+
* createEvinseFile) from the cdxgen CLI options.
|
|
147
|
+
*
|
|
148
|
+
* This object used to be assembled inline in bin/cdxgen.js, which dropped
|
|
149
|
+
* fields the analyzers read - most notably `exclude`, so every dosai and atom
|
|
150
|
+
* run triggered by `--evidence` ignored the user's `--exclude` patterns. Keep
|
|
151
|
+
* the wiring in one place so the forwarded fields stay testable.
|
|
152
|
+
*
|
|
153
|
+
* @param {Object} options CLI options
|
|
154
|
+
* @param {Object} args Parsed CLI arguments; `args._[0]` is the source directory
|
|
155
|
+
* @param {string} input Path of the BOM file the evinse phase enriches
|
|
156
|
+
* @returns {Object} Options for prepareDB, analyzeProject, and createEvinseFile
|
|
157
|
+
*/
|
|
158
|
+
export function buildEvinseOptions(options, args, input) {
|
|
159
|
+
const sourceDir = args._?.[0] || ".";
|
|
160
|
+
return {
|
|
161
|
+
_: args._,
|
|
162
|
+
input,
|
|
163
|
+
output: options.evinseOutput,
|
|
164
|
+
language: options.projectType,
|
|
165
|
+
skipMavenCollector: false,
|
|
166
|
+
force: false,
|
|
167
|
+
withReachables: options.deep,
|
|
168
|
+
usagesSlicesFile: options.usagesSlicesFile,
|
|
169
|
+
dataFlowSlicesFile: options.dataFlowSlicesFile,
|
|
170
|
+
reachablesSlicesFile: options.reachablesSlicesFile,
|
|
171
|
+
semanticsSlicesFile: options.semanticsSlicesFile,
|
|
172
|
+
openapiSpecFile: options.openapiSpecFile,
|
|
173
|
+
componentType: options.componentType,
|
|
174
|
+
includeCrypto:
|
|
175
|
+
options.includeCrypto &&
|
|
176
|
+
isCycloneDxComponentTypeEnabled("cryptographic-asset", options),
|
|
177
|
+
specVersion: options.specVersion,
|
|
178
|
+
profile: options.profile,
|
|
179
|
+
jsonPretty: options.jsonPretty,
|
|
180
|
+
exclude: options.exclude,
|
|
181
|
+
// Absolute path of the deps slice createCsharpBom writes for --deep runs,
|
|
182
|
+
// so analyzeProject can reuse it instead of rerunning dosai methods.
|
|
183
|
+
depsSlicesFile: options.depsSlicesFile
|
|
184
|
+
? resolve(sourceDir, options.depsSlicesFile)
|
|
185
|
+
: undefined,
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
|
|
103
189
|
/**
|
|
104
190
|
* Function to create the db for the libraries referred in the sbom.
|
|
105
191
|
*
|
|
@@ -444,6 +530,11 @@ export async function createSlice(
|
|
|
444
530
|
`Creating ${sliceType} slice for ${resolve(filePath)}. Please wait ...`,
|
|
445
531
|
);
|
|
446
532
|
const atomFile = join(sliceOutputDir, `${language}-app.atom`);
|
|
533
|
+
if (sliceType === "reachables") {
|
|
534
|
+
// atom writes reachables in numbered chunks next to the slices file; one
|
|
535
|
+
// left by an earlier run must not be read back as part of this one.
|
|
536
|
+
removeReachablesChunkFiles(slicesFile);
|
|
537
|
+
}
|
|
447
538
|
let args = [sliceType];
|
|
448
539
|
// Support for crypto slices aka CBOM
|
|
449
540
|
if (sliceType === "reachables" && options.includeCrypto) {
|
|
@@ -488,6 +579,7 @@ export async function createSlice(
|
|
|
488
579
|
args.push("--slice-depth");
|
|
489
580
|
args.push(readEnvironmentVariable("ATOM_SLICE_DEPTH"));
|
|
490
581
|
}
|
|
582
|
+
args.push(...atomCompileCommandsArgs(filePath, language, options));
|
|
491
583
|
args.push(resolve(filePath));
|
|
492
584
|
const atomExcludeEnv = buildAtomCommandEnv(options, language);
|
|
493
585
|
// Execute atom
|
|
@@ -598,8 +690,9 @@ export function purlToLanguage(purl, filePath) {
|
|
|
598
690
|
* Seed purl-location and import maps from SBOM component evidence.
|
|
599
691
|
*
|
|
600
692
|
* Walks the supplied components reading `internal:ImportedModules` (or
|
|
601
|
-
* `internal:Namespaces` for php/ruby
|
|
602
|
-
*
|
|
693
|
+
* `internal:Namespaces` for php/ruby, plus the `internal:ImportedSymbols` the
|
|
694
|
+
* C/C++ collector records) and `evidence.occurrences` properties to construct
|
|
695
|
+
* lookup maps used during slice analysis.
|
|
603
696
|
*
|
|
604
697
|
* @param {Object[]} components CycloneDX components from the input SBOM
|
|
605
698
|
* @param {string} language Application language used to select the import property name
|
|
@@ -624,6 +717,18 @@ export function initFromSbom(components, language) {
|
|
|
624
717
|
.forEach((v) => {
|
|
625
718
|
purlImportsMap[comp.purl] = (v.value || "").split(",");
|
|
626
719
|
});
|
|
720
|
+
// A C/C++ component records the symbols the project uses from it, which
|
|
721
|
+
// are what C usages are matched against. The list is pipe-separated:
|
|
722
|
+
// C++ symbols may contain commas.
|
|
723
|
+
if (["c", "cpp", "c++"].includes(language)) {
|
|
724
|
+
(comp.properties || [])
|
|
725
|
+
.filter((v) => v.name === "internal:ImportedSymbols")
|
|
726
|
+
.forEach((v) => {
|
|
727
|
+
purlImportsMap[comp.purl] = (
|
|
728
|
+
purlImportsMap[comp.purl] || []
|
|
729
|
+
).concat((v.value || "").split("|").filter(Boolean));
|
|
730
|
+
});
|
|
731
|
+
}
|
|
627
732
|
}
|
|
628
733
|
if (comp.evidence.occurrences) {
|
|
629
734
|
// Keep the `location#line` shape so a re-run over a cdxgen BOM does not
|
|
@@ -654,6 +759,65 @@ function usableSlicesFile(slicesFile) {
|
|
|
654
759
|
return fileSizeInBytes > 1024;
|
|
655
760
|
}
|
|
656
761
|
|
|
762
|
+
/**
|
|
763
|
+
* Load a semantics slices file for reuse when it belongs to this project.
|
|
764
|
+
*
|
|
765
|
+
* `--semantics-slices-file` defaults to `semantics.slices.json` in the working
|
|
766
|
+
* directory, which is shared by every project scanned from there and also
|
|
767
|
+
* receives the reports of other analyzers (dosai, rusi, golem). A file is
|
|
768
|
+
* only reused when its shape matches the language. Swift slices must also
|
|
769
|
+
* describe the same project directory and must not predate the input SBOM:
|
|
770
|
+
* `cdxgen --evidence` writes a fresh SBOM before evinse runs, so an older
|
|
771
|
+
* slice was made from an earlier state of the sources and is regenerated.
|
|
772
|
+
* Scala slices cannot be regenerated by evinse and are reused whenever their
|
|
773
|
+
* shape matches.
|
|
774
|
+
*
|
|
775
|
+
* @param {string} language Project language
|
|
776
|
+
* @param {string} slicesFile Candidate semantics slices file
|
|
777
|
+
* @param {string} bomFile Input SBOM
|
|
778
|
+
* @param {string} dirPath Project directory
|
|
779
|
+
* @returns {undefined|Object} Parsed slice when it can be reused
|
|
780
|
+
*/
|
|
781
|
+
export function loadReusableSemanticsSlice(
|
|
782
|
+
language,
|
|
783
|
+
slicesFile,
|
|
784
|
+
bomFile,
|
|
785
|
+
dirPath,
|
|
786
|
+
) {
|
|
787
|
+
if (!usableSlicesFile(slicesFile)) {
|
|
788
|
+
return undefined;
|
|
789
|
+
}
|
|
790
|
+
let parsed;
|
|
791
|
+
try {
|
|
792
|
+
parsed = readJsonFile(slicesFile);
|
|
793
|
+
} catch (_e) {
|
|
794
|
+
return undefined;
|
|
795
|
+
}
|
|
796
|
+
if (!parsed || typeof parsed !== "object") {
|
|
797
|
+
return undefined;
|
|
798
|
+
}
|
|
799
|
+
if (!PROJECT_TYPE_ALIASES.swift.includes(language)) {
|
|
800
|
+
// Scala semantics slices are keyed by .scala source file names
|
|
801
|
+
return Object.keys(parsed).some((key) => key.endsWith(".scala"))
|
|
802
|
+
? parsed
|
|
803
|
+
: undefined;
|
|
804
|
+
}
|
|
805
|
+
if (!(parsed.fileIndexes || parsed.buildSymbols || parsed.moduleInfos)) {
|
|
806
|
+
return undefined;
|
|
807
|
+
}
|
|
808
|
+
if (parsed.projectPath && resolve(parsed.projectPath) !== resolve(dirPath)) {
|
|
809
|
+
return undefined;
|
|
810
|
+
}
|
|
811
|
+
try {
|
|
812
|
+
if (fs.statSync(slicesFile).mtimeMs < fs.statSync(bomFile).mtimeMs) {
|
|
813
|
+
return undefined;
|
|
814
|
+
}
|
|
815
|
+
} catch (_e) {
|
|
816
|
+
// Without timestamps the shape and project checks decide
|
|
817
|
+
}
|
|
818
|
+
return parsed;
|
|
819
|
+
}
|
|
820
|
+
|
|
657
821
|
/**
|
|
658
822
|
* Function to analyze the project
|
|
659
823
|
*
|
|
@@ -826,14 +990,23 @@ export async function analyzeProject(dbObjMap, options) {
|
|
|
826
990
|
options.usagesSlicesFile &&
|
|
827
991
|
usableSlicesFile(options.usagesSlicesFile)
|
|
828
992
|
) {
|
|
829
|
-
usageSlice =
|
|
830
|
-
fs.readFileSync(options.usagesSlicesFile, "utf-8"),
|
|
831
|
-
);
|
|
993
|
+
usageSlice = readDosaiMethodsReport(options.usagesSlicesFile);
|
|
832
994
|
usagesSlicesFile = options.usagesSlicesFile;
|
|
995
|
+
} else if (usableSlicesFile(options.depsSlicesFile)) {
|
|
996
|
+
// createCsharpBom already ran dosai methods on this tree for --deep runs
|
|
997
|
+
// and wrote the deps slice. For dosai, that output IS the usages slice,
|
|
998
|
+
// so reuse it instead of invoking dosai a second time.
|
|
999
|
+
if (DEBUG_MODE) {
|
|
1000
|
+
console.log(
|
|
1001
|
+
`Reusing the dosai deps slices file "${options.depsSlicesFile}" for the usages analysis.`,
|
|
1002
|
+
);
|
|
1003
|
+
}
|
|
1004
|
+
usageSlice = readDosaiMethodsReport(options.depsSlicesFile);
|
|
1005
|
+
usagesSlicesFile = options.depsSlicesFile;
|
|
833
1006
|
} else {
|
|
834
1007
|
retMap = await createSlice(language, dirPath, "usages", options);
|
|
835
1008
|
if (retMap?.slicesFile && safeExistsSync(retMap.slicesFile)) {
|
|
836
|
-
usageSlice =
|
|
1009
|
+
usageSlice = readDosaiMethodsReport(retMap.slicesFile);
|
|
837
1010
|
usagesSlicesFile = retMap.slicesFile;
|
|
838
1011
|
}
|
|
839
1012
|
}
|
|
@@ -867,16 +1040,12 @@ export async function analyzeProject(dbObjMap, options) {
|
|
|
867
1040
|
safeExistsSync(options.dataFlowSlicesFile)
|
|
868
1041
|
) {
|
|
869
1042
|
dataFlowSlicesFile = options.dataFlowSlicesFile;
|
|
870
|
-
dataFlowSlice =
|
|
871
|
-
fs.readFileSync(options.dataFlowSlicesFile, "utf-8"),
|
|
872
|
-
);
|
|
1043
|
+
dataFlowSlice = readDosaiDataFlowReport(options.dataFlowSlicesFile);
|
|
873
1044
|
} else {
|
|
874
1045
|
retMap = await createSlice(language, dirPath, "data-flow", options);
|
|
875
1046
|
if (retMap?.slicesFile && safeExistsSync(retMap.slicesFile)) {
|
|
876
1047
|
dataFlowSlicesFile = retMap.slicesFile;
|
|
877
|
-
dataFlowSlice =
|
|
878
|
-
fs.readFileSync(retMap.slicesFile, "utf-8"),
|
|
879
|
-
);
|
|
1048
|
+
dataFlowSlice = readDosaiDataFlowReport(retMap.slicesFile);
|
|
880
1049
|
}
|
|
881
1050
|
}
|
|
882
1051
|
if (dataFlowSlice && Object.keys(dataFlowSlice).length) {
|
|
@@ -923,7 +1092,7 @@ export async function analyzeProject(dbObjMap, options) {
|
|
|
923
1092
|
) {
|
|
924
1093
|
reachablesSlicesFile = options.reachablesSlicesFile;
|
|
925
1094
|
reachablesSlice = filterAtomSliceData(
|
|
926
|
-
|
|
1095
|
+
readReachablesSlices(options.reachablesSlicesFile),
|
|
927
1096
|
options,
|
|
928
1097
|
);
|
|
929
1098
|
} else {
|
|
@@ -931,7 +1100,7 @@ export async function analyzeProject(dbObjMap, options) {
|
|
|
931
1100
|
if (retMap?.slicesFile && safeExistsSync(retMap.slicesFile)) {
|
|
932
1101
|
reachablesSlicesFile = retMap.slicesFile;
|
|
933
1102
|
reachablesSlice = filterAtomSliceData(
|
|
934
|
-
|
|
1103
|
+
readReachablesSlices(retMap.slicesFile),
|
|
935
1104
|
options,
|
|
936
1105
|
);
|
|
937
1106
|
}
|
|
@@ -947,7 +1116,7 @@ export async function analyzeProject(dbObjMap, options) {
|
|
|
947
1116
|
// Reuse existing usages slices
|
|
948
1117
|
if (options.usagesSlicesFile && usableSlicesFile(options.usagesSlicesFile)) {
|
|
949
1118
|
usageSlice = filterAtomSliceData(
|
|
950
|
-
|
|
1119
|
+
readSlicesFile(options.usagesSlicesFile, "usages"),
|
|
951
1120
|
options,
|
|
952
1121
|
);
|
|
953
1122
|
usagesSlicesFile = options.usagesSlicesFile;
|
|
@@ -956,7 +1125,7 @@ export async function analyzeProject(dbObjMap, options) {
|
|
|
956
1125
|
retMap = await createSlice(language, dirPath, "usages", options);
|
|
957
1126
|
if (retMap?.slicesFile && safeExistsSync(retMap.slicesFile)) {
|
|
958
1127
|
usageSlice = filterAtomSliceData(
|
|
959
|
-
|
|
1128
|
+
readSlicesFile(retMap.slicesFile, "usages"),
|
|
960
1129
|
options,
|
|
961
1130
|
);
|
|
962
1131
|
usagesSlicesFile = retMap.slicesFile;
|
|
@@ -982,21 +1151,20 @@ export async function analyzeProject(dbObjMap, options) {
|
|
|
982
1151
|
components.length
|
|
983
1152
|
) {
|
|
984
1153
|
// Reuse existing semantics slices for swift and scala
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
1154
|
+
const reusableSlice = loadReusableSemanticsSlice(
|
|
1155
|
+
language,
|
|
1156
|
+
options.semanticsSlicesFile,
|
|
1157
|
+
bomFile,
|
|
1158
|
+
dirPath,
|
|
1159
|
+
);
|
|
1160
|
+
if (reusableSlice) {
|
|
1161
|
+
semanticsSlice = reusableSlice;
|
|
992
1162
|
semanticsSlicesFile = options.semanticsSlicesFile;
|
|
993
1163
|
} else if (PROJECT_TYPE_ALIASES.swift.includes(language)) {
|
|
994
1164
|
// Generate our own slices for swift
|
|
995
1165
|
retMap = await createSlice(language, dirPath, "semantics", options);
|
|
996
1166
|
if (retMap?.slicesFile && safeExistsSync(retMap.slicesFile)) {
|
|
997
|
-
semanticsSlice =
|
|
998
|
-
fs.readFileSync(retMap.slicesFile, "utf-8"),
|
|
999
|
-
);
|
|
1167
|
+
semanticsSlice = readSlicesFile(retMap.slicesFile, "semantics");
|
|
1000
1168
|
semanticsSlicesFile = retMap.slicesFile;
|
|
1001
1169
|
}
|
|
1002
1170
|
}
|
|
@@ -1033,7 +1201,7 @@ export async function analyzeProject(dbObjMap, options) {
|
|
|
1033
1201
|
) {
|
|
1034
1202
|
dataFlowSlicesFile = options.dataFlowSlicesFile;
|
|
1035
1203
|
dataFlowSlice = filterAtomSliceData(
|
|
1036
|
-
|
|
1204
|
+
readSlicesFile(options.dataFlowSlicesFile, "data-flow"),
|
|
1037
1205
|
options,
|
|
1038
1206
|
);
|
|
1039
1207
|
} else if (!PROJECT_TYPE_ALIASES.scala.includes(language)) {
|
|
@@ -1041,7 +1209,7 @@ export async function analyzeProject(dbObjMap, options) {
|
|
|
1041
1209
|
if (retMap?.slicesFile && safeExistsSync(retMap.slicesFile)) {
|
|
1042
1210
|
dataFlowSlicesFile = retMap.slicesFile;
|
|
1043
1211
|
dataFlowSlice = filterAtomSliceData(
|
|
1044
|
-
|
|
1212
|
+
readSlicesFile(retMap.slicesFile, "data-flow"),
|
|
1045
1213
|
options,
|
|
1046
1214
|
);
|
|
1047
1215
|
}
|
|
@@ -1400,6 +1568,14 @@ export async function parseSliceUsages(
|
|
|
1400
1568
|
/**
|
|
1401
1569
|
* Method to parse semantic slice data. Currently supported for swift and scala languages.
|
|
1402
1570
|
*
|
|
1571
|
+
* Swift slices created by cdxgen record, for every indexed source file, the
|
|
1572
|
+
* lines that reference each dependency module (`moduleReferences`); the module
|
|
1573
|
+
* of a reference is the declaring module recovered from the compiler-resolved
|
|
1574
|
+
* USR. Components are mapped to their package's modules through the
|
|
1575
|
+
* `packages` slice, so every occurrence is backed by a resolved reference.
|
|
1576
|
+
* Slices without `moduleReferences` (created by older cdxgen versions) fall
|
|
1577
|
+
* back to matching symbol names.
|
|
1578
|
+
*
|
|
1403
1579
|
* @param {String} language Project language.
|
|
1404
1580
|
* @param {Array} components Components from the input SBOM
|
|
1405
1581
|
* @param {Object} semanticsSlice Semantic slice data
|
|
@@ -1413,86 +1589,47 @@ export function parseSemanticSlices(language, components, semanticsSlice) {
|
|
|
1413
1589
|
if (!components) {
|
|
1414
1590
|
return undefined;
|
|
1415
1591
|
}
|
|
1416
|
-
const
|
|
1417
|
-
const
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
// - A granular and precise moduleInfos, which has the exact classes, protocols, enums etc belonging to each module
|
|
1422
|
-
// The objective then is to build the purlLocationMap, which is the list of locations where a given purl is used
|
|
1423
|
-
// For this, we have two attributes:
|
|
1424
|
-
// - A simpler fileStructures attribute that has the mapping between a swift file and the list of referenced types
|
|
1425
|
-
// - A granular fileIndexes attribute that contains information about the clang modules and line number information
|
|
1426
|
-
|
|
1427
|
-
// We first need to map out the component names to their purls
|
|
1428
|
-
// This is because the semantics slice use the module names everywhere
|
|
1592
|
+
const fileIndexes = semanticsSlice?.fileIndexes || {};
|
|
1593
|
+
const hasModuleReferences = Object.values(fileIndexes).some(
|
|
1594
|
+
(afileIndex) => afileIndex?.moduleReferences,
|
|
1595
|
+
);
|
|
1596
|
+
const purlLocationsSet = {};
|
|
1429
1597
|
for (const comp of components) {
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
if (semanticsSlice?.buildSymbols[comp.name]) {
|
|
1435
|
-
for (const asym of semanticsSlice.buildSymbols[comp.name]) {
|
|
1436
|
-
componentSymbolsMap[comp.name].add(asym);
|
|
1437
|
-
}
|
|
1438
|
-
}
|
|
1439
|
-
const moduleInfo = semanticsSlice?.moduleInfos[comp.name] || {};
|
|
1440
|
-
if (moduleInfo.classes) {
|
|
1441
|
-
for (const asym of moduleInfo.classes) {
|
|
1442
|
-
componentSymbolsMap[comp.name].add(asym);
|
|
1443
|
-
}
|
|
1444
|
-
}
|
|
1445
|
-
if (moduleInfo.protocols) {
|
|
1446
|
-
for (const asym of moduleInfo.protocols) {
|
|
1447
|
-
componentSymbolsMap[comp.name].add(asym);
|
|
1448
|
-
}
|
|
1449
|
-
}
|
|
1450
|
-
if (moduleInfo.enums) {
|
|
1451
|
-
for (const asym of moduleInfo.enums) {
|
|
1452
|
-
componentSymbolsMap[comp.name].add(asym);
|
|
1453
|
-
}
|
|
1598
|
+
// Local packages and the root application have no purl, so there is
|
|
1599
|
+
// nothing the evidence could be attached to downstream
|
|
1600
|
+
if (!comp?.purl) {
|
|
1601
|
+
continue;
|
|
1454
1602
|
}
|
|
1455
|
-
|
|
1456
|
-
if (
|
|
1457
|
-
|
|
1458
|
-
for (const asym of moduleInfo.classMethods[aclassName]) {
|
|
1459
|
-
componentSymbolsMap[comp.name].add(asym);
|
|
1460
|
-
}
|
|
1461
|
-
}
|
|
1603
|
+
const moduleNames = resolveSwiftComponentModules(comp, semanticsSlice);
|
|
1604
|
+
if (!moduleNames.size) {
|
|
1605
|
+
continue;
|
|
1462
1606
|
}
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1607
|
+
const locations = purlLocationsSet[comp.purl] || new Set();
|
|
1608
|
+
if (hasModuleReferences) {
|
|
1609
|
+
for (const [aswiftFile, afileIndex] of Object.entries(fileIndexes)) {
|
|
1610
|
+
for (const [amodule, lines] of Object.entries(
|
|
1611
|
+
afileIndex?.moduleReferences || {},
|
|
1612
|
+
)) {
|
|
1613
|
+
if (!moduleNames.has(amodule)) {
|
|
1614
|
+
continue;
|
|
1615
|
+
}
|
|
1616
|
+
for (const aline of lines) {
|
|
1617
|
+
locations.add(`${aswiftFile}#${aline}`);
|
|
1618
|
+
}
|
|
1467
1619
|
}
|
|
1468
1620
|
}
|
|
1469
|
-
}
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
}
|
|
1475
|
-
}
|
|
1476
|
-
}
|
|
1477
|
-
const purlLocationsSet = {};
|
|
1478
|
-
// We now have a good set of data in componentSymbolsMap and allObfuscationsMap
|
|
1479
|
-
// We can iterate these symbols and check if they exist under fileIndexes.symbolLocations
|
|
1480
|
-
for (const compName of Object.keys(componentSymbolsMap)) {
|
|
1481
|
-
const compSymbols = Array.from(componentSymbolsMap[compName]);
|
|
1482
|
-
const searchHits = searchSymbolLocations(
|
|
1483
|
-
compSymbols,
|
|
1484
|
-
semanticsSlice?.fileIndexes,
|
|
1485
|
-
);
|
|
1486
|
-
// We have an occurrence hit. Let's populate the purlLocationMap
|
|
1487
|
-
if (searchHits?.length) {
|
|
1488
|
-
const locations =
|
|
1489
|
-
purlLocationsSet[componentNamePurlMap[compName]] || new Set();
|
|
1490
|
-
for (const ahit of searchHits) {
|
|
1621
|
+
} else {
|
|
1622
|
+
for (const ahit of searchSymbolLocations(
|
|
1623
|
+
collectLegacySwiftSymbols(moduleNames, semanticsSlice),
|
|
1624
|
+
fileIndexes,
|
|
1625
|
+
)) {
|
|
1491
1626
|
for (const aline of ahit.lineNumbers) {
|
|
1492
1627
|
locations.add(`${ahit.file}#${aline}`);
|
|
1493
1628
|
}
|
|
1494
1629
|
}
|
|
1495
|
-
|
|
1630
|
+
}
|
|
1631
|
+
if (locations.size) {
|
|
1632
|
+
purlLocationsSet[comp.purl] = locations;
|
|
1496
1633
|
}
|
|
1497
1634
|
}
|
|
1498
1635
|
const purlLocationMap = {};
|
|
@@ -1502,22 +1639,184 @@ export function parseSemanticSlices(language, components, semanticsSlice) {
|
|
|
1502
1639
|
return { purlLocationMap };
|
|
1503
1640
|
}
|
|
1504
1641
|
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1642
|
+
/**
|
|
1643
|
+
* Normalise a repository location or Swift purl into `host/owner/name`.
|
|
1644
|
+
*
|
|
1645
|
+
* @param {String} value Repository URL, scp-style git location, or swift purl
|
|
1646
|
+
* @returns {undefined|String} Lowercase `host/path` without scheme and `.git`
|
|
1647
|
+
*/
|
|
1648
|
+
function normalizeSwiftPackageLocation(value) {
|
|
1649
|
+
if (!value || typeof value !== "string") {
|
|
1508
1650
|
return undefined;
|
|
1509
1651
|
}
|
|
1510
|
-
|
|
1511
|
-
|
|
1652
|
+
let text = value.trim();
|
|
1653
|
+
if (text.startsWith("pkg:swift/")) {
|
|
1654
|
+
text = text.slice("pkg:swift/".length).split("?")[0].split("#")[0];
|
|
1655
|
+
const at = text.lastIndexOf("@");
|
|
1656
|
+
if (at > 0) {
|
|
1657
|
+
text = text.slice(0, at);
|
|
1658
|
+
}
|
|
1659
|
+
try {
|
|
1660
|
+
text = decodeURIComponent(text);
|
|
1661
|
+
} catch (_e) {
|
|
1662
|
+
// Keep the raw value
|
|
1663
|
+
}
|
|
1664
|
+
} else {
|
|
1665
|
+
text = text.split("?")[0].split("#")[0];
|
|
1666
|
+
const scheme = text.indexOf("://");
|
|
1667
|
+
if (scheme >= 0) {
|
|
1668
|
+
text = text.slice(scheme + 3);
|
|
1669
|
+
// Drop userinfo such as git@
|
|
1670
|
+
const slash = text.indexOf("/");
|
|
1671
|
+
const at = text.indexOf("@");
|
|
1672
|
+
if (at >= 0 && (slash < 0 || at < slash)) {
|
|
1673
|
+
text = text.slice(at + 1);
|
|
1674
|
+
}
|
|
1675
|
+
} else if (text.includes("@") && text.includes(":")) {
|
|
1676
|
+
// scp-style git@github.com:owner/name.git
|
|
1677
|
+
text = text.slice(text.indexOf("@") + 1).replace(":", "/");
|
|
1678
|
+
} else {
|
|
1679
|
+
return undefined;
|
|
1680
|
+
}
|
|
1681
|
+
}
|
|
1682
|
+
text = text.replace(/\/+$/, "");
|
|
1683
|
+
if (text.endsWith(".git")) {
|
|
1684
|
+
text = text.slice(0, -4);
|
|
1685
|
+
}
|
|
1686
|
+
if (text.startsWith("www.")) {
|
|
1687
|
+
text = text.slice(4);
|
|
1688
|
+
}
|
|
1689
|
+
return text.toLowerCase() || undefined;
|
|
1690
|
+
}
|
|
1691
|
+
|
|
1692
|
+
/**
|
|
1693
|
+
* Resolve the Swift module names that belong to a SBOM component.
|
|
1694
|
+
*
|
|
1695
|
+
* A component is named after its package repository or identity while the
|
|
1696
|
+
* semantic slices are keyed by module names. With the `packages` slice, a
|
|
1697
|
+
* package cloned from a repository matches the component whose purl names
|
|
1698
|
+
* the same repository, so a fork with the same name never inherits another
|
|
1699
|
+
* package's evidence; registry and local packages, which have no repository
|
|
1700
|
+
* URL, match by identity or name. Older slices only carry the
|
|
1701
|
+
* `packageModules` aliases (identity, package name, repository name), which
|
|
1702
|
+
* are matched against the component name and the `cdx:swift:packageName`
|
|
1703
|
+
* property, so that `swift-argument-parser` resolves to its `ArgumentParser`
|
|
1704
|
+
* module.
|
|
1705
|
+
*
|
|
1706
|
+
* @param {Object} comp SBOM component
|
|
1707
|
+
* @param {Object} semanticsSlice Semantic slice data
|
|
1708
|
+
* @returns {Set<string>} Module names associated with the component
|
|
1709
|
+
*/
|
|
1710
|
+
function resolveSwiftComponentModules(comp, semanticsSlice) {
|
|
1711
|
+
const moduleNames = new Set();
|
|
1712
|
+
if (!comp?.name) {
|
|
1713
|
+
return moduleNames;
|
|
1714
|
+
}
|
|
1715
|
+
const aliases = new Set(
|
|
1716
|
+
[comp.name, getPropertyValue(comp, "cdx:swift:packageName")]
|
|
1717
|
+
.filter(Boolean)
|
|
1718
|
+
.map((alias) => String(alias).toLowerCase()),
|
|
1719
|
+
);
|
|
1720
|
+
if (Array.isArray(semanticsSlice?.packages)) {
|
|
1721
|
+
const componentLocation = normalizeSwiftPackageLocation(comp.purl);
|
|
1722
|
+
for (const apkg of semanticsSlice.packages) {
|
|
1723
|
+
const packageLocation = normalizeSwiftPackageLocation(apkg?.location);
|
|
1724
|
+
const matches =
|
|
1725
|
+
packageLocation && componentLocation
|
|
1726
|
+
? packageLocation === componentLocation
|
|
1727
|
+
: [apkg?.identity, apkg?.name].some(
|
|
1728
|
+
(alias) => alias && aliases.has(String(alias).toLowerCase()),
|
|
1729
|
+
);
|
|
1730
|
+
if (matches) {
|
|
1731
|
+
for (const amodule of apkg.modules || []) {
|
|
1732
|
+
moduleNames.add(amodule);
|
|
1733
|
+
}
|
|
1734
|
+
}
|
|
1735
|
+
}
|
|
1736
|
+
return moduleNames;
|
|
1737
|
+
}
|
|
1738
|
+
const packageModules = semanticsSlice?.packageModules || {};
|
|
1739
|
+
for (const alias of new Set([comp.name, ...aliases])) {
|
|
1740
|
+
if (Array.isArray(packageModules[alias])) {
|
|
1741
|
+
for (const amodule of packageModules[alias]) {
|
|
1742
|
+
moduleNames.add(amodule);
|
|
1743
|
+
}
|
|
1744
|
+
}
|
|
1745
|
+
}
|
|
1746
|
+
// Modules named after the package (Yams, SWXMLHash) still match
|
|
1747
|
+
if (!moduleNames.size) {
|
|
1748
|
+
moduleNames.add(comp.name);
|
|
1749
|
+
}
|
|
1750
|
+
return moduleNames;
|
|
1751
|
+
}
|
|
1752
|
+
|
|
1753
|
+
function getPropertyValue(comp, propertyName) {
|
|
1754
|
+
for (const aproperty of comp?.properties || []) {
|
|
1755
|
+
if (aproperty?.name === propertyName) {
|
|
1756
|
+
return aproperty.value;
|
|
1757
|
+
}
|
|
1758
|
+
}
|
|
1759
|
+
return undefined;
|
|
1760
|
+
}
|
|
1761
|
+
|
|
1762
|
+
/**
|
|
1763
|
+
* Collect the symbol names of a component's modules from slices that predate
|
|
1764
|
+
* `moduleReferences`, using the moduleInfos data and the coarser buildSymbols
|
|
1765
|
+
* derived from the build's output file maps.
|
|
1766
|
+
*
|
|
1767
|
+
* @param {Set<String>} moduleNames Module names of the component
|
|
1768
|
+
* @param {Object} semanticsSlice Semantic slice data
|
|
1769
|
+
* @returns {Object} `{ symbols, freeFunctions }` name sets
|
|
1770
|
+
*/
|
|
1771
|
+
function collectLegacySwiftSymbols(moduleNames, semanticsSlice) {
|
|
1772
|
+
const symbols = new Set();
|
|
1773
|
+
const freeFunctions = new Set();
|
|
1774
|
+
for (const moduleName of moduleNames) {
|
|
1775
|
+
for (const asym of semanticsSlice?.buildSymbols?.[moduleName] || []) {
|
|
1776
|
+
symbols.add(asym);
|
|
1777
|
+
}
|
|
1778
|
+
const moduleInfo = semanticsSlice?.moduleInfos?.[moduleName] || {};
|
|
1779
|
+
for (const asym of [
|
|
1780
|
+
...(moduleInfo.classes || []),
|
|
1781
|
+
...(moduleInfo.protocols || []),
|
|
1782
|
+
...(moduleInfo.enums || []),
|
|
1783
|
+
]) {
|
|
1784
|
+
symbols.add(asym);
|
|
1785
|
+
}
|
|
1786
|
+
for (const methods of [
|
|
1787
|
+
...Object.values(moduleInfo.classMethods || {}),
|
|
1788
|
+
...Object.values(moduleInfo.protocolMethods || {}),
|
|
1789
|
+
]) {
|
|
1790
|
+
for (const asym of methods || []) {
|
|
1791
|
+
symbols.add(asym);
|
|
1792
|
+
}
|
|
1793
|
+
}
|
|
1794
|
+
for (const asym of moduleInfo.functions || []) {
|
|
1795
|
+
freeFunctions.add(asym);
|
|
1796
|
+
}
|
|
1797
|
+
}
|
|
1798
|
+
return { symbols, freeFunctions };
|
|
1799
|
+
}
|
|
1800
|
+
|
|
1801
|
+
function searchSymbolLocations(componentSymbols, fileIndexes) {
|
|
1802
|
+
const searchHits = [];
|
|
1803
|
+
const { symbols, freeFunctions } = componentSymbols;
|
|
1804
|
+
for (const aswiftFile of Object.keys(fileIndexes || {})) {
|
|
1805
|
+
const symbolLocations = fileIndexes[aswiftFile]?.symbolLocations;
|
|
1806
|
+
if (!symbolLocations) {
|
|
1512
1807
|
continue;
|
|
1513
1808
|
}
|
|
1514
|
-
for (const asym of Object.keys(
|
|
1515
|
-
|
|
1516
|
-
|
|
1809
|
+
for (const asym of Object.keys(symbolLocations)) {
|
|
1810
|
+
// The index records free functions by their full selector
|
|
1811
|
+
// (`dump(object:canonical:...)`) while module interfaces give the base
|
|
1812
|
+
// name. Only top-level functions are matched by base name: members such
|
|
1813
|
+
// as `encode(to:)` exist in nearly every module
|
|
1814
|
+
const baseName = asym.includes("(") ? asym.split("(")[0] : undefined;
|
|
1815
|
+
if (symbols.has(asym) || (baseName && freeFunctions.has(baseName))) {
|
|
1517
1816
|
searchHits.push({
|
|
1518
1817
|
file: aswiftFile,
|
|
1519
1818
|
symbol: asym,
|
|
1520
|
-
lineNumbers,
|
|
1819
|
+
lineNumbers: symbolLocations[asym],
|
|
1521
1820
|
});
|
|
1522
1821
|
}
|
|
1523
1822
|
}
|
|
@@ -1950,7 +2249,10 @@ export async function createEvinseFile(sliceArtefacts, options) {
|
|
|
1950
2249
|
bomJson.metadata.component = workspaceComponent;
|
|
1951
2250
|
}
|
|
1952
2251
|
}
|
|
1953
|
-
|
|
2252
|
+
mergeAnalyzerMetadataProperties(
|
|
2253
|
+
bomJson.metadata?.component,
|
|
2254
|
+
metadataProperties,
|
|
2255
|
+
);
|
|
1954
2256
|
let occEvidencePresent = false;
|
|
1955
2257
|
let csEvidencePresent = false;
|
|
1956
2258
|
let servicesPresent = false;
|
|
@@ -2009,8 +2311,15 @@ export async function createEvinseFile(sliceArtefacts, options) {
|
|
|
2009
2311
|
servicesPresent = true;
|
|
2010
2312
|
}
|
|
2011
2313
|
// Add the crypto components to the components list
|
|
2314
|
+
// Multi-type scans may have collected the same crypto assets during BOM
|
|
2315
|
+
// generation, so skip any bom-ref already present to keep the BOM valid.
|
|
2012
2316
|
if (cryptoComponents?.length) {
|
|
2013
|
-
|
|
2317
|
+
const existingRefs = new Set(
|
|
2318
|
+
bomJson.components.map((comp) => comp["bom-ref"]).filter(Boolean),
|
|
2319
|
+
);
|
|
2320
|
+
bomJson.components = bomJson.components.concat(
|
|
2321
|
+
cryptoComponents.filter((comp) => !existingRefs.has(comp["bom-ref"])),
|
|
2322
|
+
);
|
|
2014
2323
|
}
|
|
2015
2324
|
// Add the AI components (machine-learning-model, dataset) discovered by dosai
|
|
2016
2325
|
if (aiComponents?.length) {
|
|
@@ -2035,28 +2344,27 @@ export async function createEvinseFile(sliceArtefacts, options) {
|
|
|
2035
2344
|
bomJson.dependencies = newDependencies;
|
|
2036
2345
|
}
|
|
2037
2346
|
if (options.annotate) {
|
|
2038
|
-
|
|
2039
|
-
|
|
2040
|
-
|
|
2041
|
-
|
|
2042
|
-
|
|
2043
|
-
|
|
2044
|
-
|
|
2045
|
-
|
|
2046
|
-
|
|
2047
|
-
|
|
2048
|
-
|
|
2049
|
-
|
|
2050
|
-
|
|
2051
|
-
|
|
2052
|
-
|
|
2053
|
-
|
|
2054
|
-
if (reachablesSlicesFile && safeExistsSync(reachablesSlicesFile)) {
|
|
2347
|
+
for (const slicesFile of [
|
|
2348
|
+
usagesSlicesFile,
|
|
2349
|
+
dataFlowSlicesFile,
|
|
2350
|
+
reachablesSlicesFile,
|
|
2351
|
+
]) {
|
|
2352
|
+
if (!slicesFile || !safeExistsSync(slicesFile)) {
|
|
2353
|
+
continue;
|
|
2354
|
+
}
|
|
2355
|
+
// The annotation embeds the slice as one string, which a file this
|
|
2356
|
+
// large cannot become.
|
|
2357
|
+
if (fs.statSync(slicesFile).size > MAX_JSON_TEXT_BYTES) {
|
|
2358
|
+
console.warn(
|
|
2359
|
+
`Not annotating the BOM with the slices file "${slicesFile}", which is too large to embed as text.`,
|
|
2360
|
+
);
|
|
2361
|
+
continue;
|
|
2362
|
+
}
|
|
2055
2363
|
bomJson.annotations.push({
|
|
2056
2364
|
subjects: [bomJson.serialNumber],
|
|
2057
2365
|
annotator: { component: bomJson.metadata.tools.components[0] },
|
|
2058
2366
|
timestamp: getTimestamp(),
|
|
2059
|
-
text: fs.readFileSync(
|
|
2367
|
+
text: fs.readFileSync(slicesFile, "utf8"),
|
|
2060
2368
|
});
|
|
2061
2369
|
}
|
|
2062
2370
|
}
|