@cyclonedx/cdxgen 10.10.6 → 10.11.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/bin/cdxgen.js CHANGED
@@ -202,9 +202,14 @@ const args = yargs(hideBin(process.argv))
202
202
  default: "reachables.slices.json",
203
203
  hidden: true,
204
204
  })
205
+ .option("semantics-slices-file", {
206
+ description: "Path for the semantics slices file.",
207
+ default: "semantics.slices.json",
208
+ hidden: true,
209
+ })
205
210
  .option("spec-version", {
206
- description: "CycloneDX Specification version to use. Defaults to 1.5",
207
- default: 1.5,
211
+ description: "CycloneDX Specification version to use. Defaults to 1.6",
212
+ default: 1.6,
208
213
  type: "number",
209
214
  })
210
215
  .option("filter", {
@@ -380,7 +385,7 @@ if (options.includeFormulation) {
380
385
  /**
381
386
  * Method to apply advanced options such as profile and lifecycles
382
387
  *
383
- * @param {object} CLI options
388
+ * @param {object} options CLI options
384
389
  */
385
390
  const applyAdvancedOptions = (options) => {
386
391
  switch (options.profile) {
@@ -731,7 +736,7 @@ const checkPermissions = (filePath) => {
731
736
  printTable(bomNSData.bomJson);
732
737
  // CBOM related print
733
738
  if (options.includeCrypto) {
734
- console.log("*** Cryptography BOM ***");
739
+ console.log("\n*** Cryptography BOM ***");
735
740
  printTable(bomNSData.bomJson, ["cryptographic-asset"]);
736
741
  printDependencyTree(bomNSData.bomJson, "provides");
737
742
  }
package/bin/evinse.js CHANGED
@@ -63,12 +63,15 @@ const args = yargs(hideBin(process.argv))
63
63
  "js",
64
64
  "ts",
65
65
  "javascript",
66
+ "nodejs",
66
67
  "py",
67
68
  "python",
68
69
  "android",
69
70
  "c",
70
71
  "cpp",
71
72
  "php",
73
+ "swift",
74
+ "ios",
72
75
  ],
73
76
  })
74
77
  .option("db-path", {
@@ -120,6 +123,11 @@ const args = yargs(hideBin(process.argv))
120
123
  description: "Use an existing reachables slices file.",
121
124
  default: "reachables.slices.json",
122
125
  })
126
+ .option("semantics-slices-file", {
127
+ description: "Use an existing semantics slices file.",
128
+ default: "semantics.slices.json",
129
+ hidden: true,
130
+ })
123
131
  .option("print", {
124
132
  alias: "p",
125
133
  type: "boolean",
@@ -141,6 +149,7 @@ const args = yargs(hideBin(process.argv))
141
149
  .scriptName("evinse")
142
150
  .version()
143
151
  .help("h")
152
+ .alias("h", "help")
144
153
  .wrap(Math.min(120, yargs().terminalWidth())).argv;
145
154
 
146
155
  const evinseArt = `
package/lib/cli/index.js CHANGED
@@ -19,8 +19,12 @@ import process from "node:process";
19
19
  import { URL } from "node:url";
20
20
  import got from "got";
21
21
  import { PackageURL } from "packageurl-js";
22
+ import { gte, lte } from "semver";
22
23
  import { parse } from "ssri";
24
+ import { table } from "table";
23
25
  import { v4 as uuidv4 } from "uuid";
26
+ import { findJSImportsExports } from "../helpers/analyzer.js";
27
+ import { collectOSCryptoLibs } from "../helpers/cbomutils.js";
24
28
  import {
25
29
  collectEnvInfo,
26
30
  getBranch,
@@ -144,19 +148,6 @@ import {
144
148
  recomputeScope,
145
149
  splitOutputByGradleProjects,
146
150
  } from "../helpers/utils.js";
147
- let url = import.meta.url;
148
- if (!url.startsWith("file://")) {
149
- url = new URL(`file://${import.meta.url}`).toString();
150
- }
151
- const dirName = dirNameStr;
152
-
153
- const selfPJson = JSON.parse(
154
- readFileSync(join(dirName, "package.json"), "utf-8"),
155
- );
156
- const _version = selfPJson.version;
157
- import { gte, lte } from "semver";
158
- import { findJSImportsExports } from "../helpers/analyzer.js";
159
- import { collectOSCryptoLibs } from "../helpers/cbomutils.js";
160
151
  import {
161
152
  executeOsQuery,
162
153
  getBinaryBom,
@@ -171,6 +162,17 @@ import {
171
162
  parseImageName,
172
163
  } from "../managers/docker.js";
173
164
 
165
+ let url = import.meta.url;
166
+ if (!url.startsWith("file://")) {
167
+ url = new URL(`file://${import.meta.url}`).toString();
168
+ }
169
+ const dirName = dirNameStr;
170
+
171
+ const selfPJson = JSON.parse(
172
+ readFileSync(join(dirName, "package.json"), "utf-8"),
173
+ );
174
+ const _version = selfPJson.version;
175
+
174
176
  const isWin = _platform() === "win32";
175
177
 
176
178
  let osQueries = {};
@@ -195,8 +197,6 @@ const cosDbQueries = JSON.parse(
195
197
  readFileSync(join(dirName, "data", "cosdb-queries.json"), "utf-8"),
196
198
  );
197
199
 
198
- import { table } from "table";
199
-
200
200
  // Construct gradle cache directory
201
201
  let GRADLE_CACHE_DIR =
202
202
  process.env.GRADLE_CACHE_DIR ||
@@ -221,8 +221,9 @@ const HASH_PATTERN =
221
221
  /**
222
222
  * Creates a default parent component based on the directory name.
223
223
  *
224
- * @param {string} path Directory or file name
225
- * @param {string} type Package type
224
+ * @param {String} path Directory or file name
225
+ * @param {String} type Package type
226
+ * @param {Object} options CLI options
226
227
  * @returns component object
227
228
  */
228
229
  const createDefaultParentComponent = (
@@ -734,7 +735,7 @@ function addMetadata(parentComponent = {}, options = {}, context = {}) {
734
735
  */
735
736
  function addExternalReferences(opkg) {
736
737
  let externalReferences = [];
737
- let pkgList = [];
738
+ let pkgList;
738
739
  if (Array.isArray(opkg)) {
739
740
  pkgList = opkg;
740
741
  } else {
@@ -843,11 +844,12 @@ function addComponent(
843
844
  pkg.qualifiers,
844
845
  encodeForPurl(pkg.subpath),
845
846
  );
847
+ let purlString = purl.toString();
846
848
  // There is no purl for cryptographic-asset
847
849
  if (ptype === "cryptographic-asset") {
848
850
  purl = undefined;
851
+ purlString = undefined;
849
852
  }
850
- const purlString = purl.toString();
851
853
  const description = pkg.description || undefined;
852
854
  let compScope = pkg.scope;
853
855
  if (allImports) {
@@ -1025,8 +1027,7 @@ function processHashes(pkg, component) {
1025
1027
  addComponentHash(ahash.alg, ahash.content, component);
1026
1028
  }
1027
1029
  } else if (pkg._shasum) {
1028
- let ahash = { "@alg": "SHA-1", "#text": pkg._shasum };
1029
- ahash = { alg: "SHA-1", content: pkg._shasum };
1030
+ const ahash = { alg: "SHA-1", content: pkg._shasum };
1030
1031
  component.hashes.push(ahash);
1031
1032
  } else if (pkg._integrity) {
1032
1033
  const integrity = parse(pkg._integrity) || {};
@@ -1054,7 +1055,7 @@ function processHashes(pkg, component) {
1054
1055
  * Adds a hash to component.
1055
1056
  */
1056
1057
  function addComponentHash(alg, digest, component) {
1057
- let hash = "";
1058
+ let hash;
1058
1059
  // If it is a valid hash simply use it
1059
1060
  if (new RegExp(HASH_PATTERN).test(digest)) {
1060
1061
  hash = digest;
@@ -1135,7 +1136,7 @@ const buildBomNSData = (options, pkgInfo, ptype, context) => {
1135
1136
  */
1136
1137
  export async function createJarBom(path, options) {
1137
1138
  let pkgList = [];
1138
- let jarFiles = [];
1139
+ let jarFiles;
1139
1140
  let nsMapping = {};
1140
1141
  const parentComponent = createDefaultParentComponent(path, "maven", options);
1141
1142
  if (options.useGradleCache) {
@@ -2267,14 +2268,21 @@ export async function createNodejsBom(path, options) {
2267
2268
  const pkgData = JSON.parse(readFileSync(`${path}/package.json`, "utf8"));
2268
2269
  const mgrData = pkgData.packageManager;
2269
2270
  let mgr = "";
2271
+ let installArgs = ["install"];
2270
2272
  if (mgrData) {
2271
2273
  mgr = mgrData.split("@")[0];
2272
2274
  }
2273
2275
  if (supPkgMgrs.includes(mgr)) {
2274
2276
  pkgMgr = mgr;
2275
2277
  }
2276
- console.log(`Executing '${pkgMgr} install' in`, path);
2277
- const result = spawnSync(pkgMgr, ["install"], {
2278
+ // Support for passing additional args to the install command
2279
+ if (process.env[`${pkgMgr.toUpperCase()}_INSTALL_ARGS`]) {
2280
+ const addArgs =
2281
+ process.env[`${pkgMgr.toUpperCase()}_INSTALL_ARGS`].split(" ");
2282
+ installArgs = installArgs.concat(addArgs);
2283
+ }
2284
+ console.log(`Executing '${pkgMgr} ${installArgs.join(" ")}' in`, path);
2285
+ const result = spawnSync(pkgMgr, installArgs, {
2278
2286
  cwd: path,
2279
2287
  encoding: "utf-8",
2280
2288
  timeout: TIMEOUT_MS,
@@ -2661,8 +2669,7 @@ export function createPixiBom(path, options) {
2661
2669
  // Add parentComponent Details
2662
2670
  const pixiTomlMode = existsSync(pixiToml);
2663
2671
  if (pixiTomlMode) {
2664
- const tmpParentComponent = parsePixiTomlFile(pixiToml);
2665
- parentComponent = tmpParentComponent;
2672
+ parentComponent = parsePixiTomlFile(pixiToml);
2666
2673
  parentComponent.type = "application";
2667
2674
  const ppurl = new PackageURL(
2668
2675
  "pixi",
@@ -3181,7 +3188,7 @@ export async function createGoBom(path, options) {
3181
3188
  const allImports = {};
3182
3189
  let parentComponent = createDefaultParentComponent(path, "golang", options);
3183
3190
  // Is this a binary file
3184
- let maybeBinary = false;
3191
+ let maybeBinary;
3185
3192
  try {
3186
3193
  maybeBinary = statSync(path).isFile();
3187
3194
  } catch (err) {
@@ -3583,7 +3590,7 @@ export async function createRustBom(path, options) {
3583
3590
  let pkgList = [];
3584
3591
  let parentComponent = {};
3585
3592
  // Is this a binary file
3586
- let maybeBinary = false;
3593
+ let maybeBinary;
3587
3594
  try {
3588
3595
  maybeBinary = statSync(path).isFile();
3589
3596
  } catch (err) {
@@ -4195,7 +4202,7 @@ export function createCloudBuildBom(path, options) {
4195
4202
  /**
4196
4203
  * Function to create obom string for the current OS using osquery
4197
4204
  *
4198
- * @param {string} path to the project
4205
+ * @param {string} _path to the project
4199
4206
  * @param {Object} options Parse options from the cli
4200
4207
  */
4201
4208
  export function createOSBom(_path, options) {
@@ -4803,9 +4810,9 @@ export function createPHPBom(path, options) {
4803
4810
  if (DEBUG_MODE) {
4804
4811
  console.log("Parsing version", versionResult.stdout);
4805
4812
  }
4806
- const tmpV = undefined;
4813
+ let tmpV = undefined;
4807
4814
  if (versionResult?.stdout) {
4808
- versionResult.stdout.split(" ");
4815
+ tmpV = versionResult.stdout.split(" ");
4809
4816
  }
4810
4817
  if (tmpV && tmpV.length > 1) {
4811
4818
  composerVersion = tmpV[1];
@@ -6427,7 +6434,7 @@ export async function createBom(path, options) {
6427
6434
  options.path = path;
6428
6435
  options.parentComponent = {};
6429
6436
  // Create parent component based on the inspect config
6430
- const inspectData = exportData.inspectData;
6437
+ const inspectData = exportData?.inspectData;
6431
6438
  if (
6432
6439
  inspectData?.RepoDigests &&
6433
6440
  inspectData.RepoTags &&
@@ -6477,13 +6484,12 @@ export async function createBom(path, options) {
6477
6484
  }
6478
6485
  // Pass the entire export data about the image layers
6479
6486
  options.exportData = exportData;
6480
- options.lastWorkingDir = exportData.lastWorkingDir;
6481
- options.allLayersExplodedDir = exportData.allLayersExplodedDir;
6482
- const bomData = await createMultiXBom(
6483
- [...new Set(exportData.pkgPathList)],
6487
+ options.lastWorkingDir = exportData?.lastWorkingDir;
6488
+ options.allLayersExplodedDir = exportData?.allLayersExplodedDir;
6489
+ return await createMultiXBom(
6490
+ [...new Set(exportData?.pkgPathList)],
6484
6491
  options,
6485
6492
  );
6486
- return bomData;
6487
6493
  }
6488
6494
  if (path.endsWith(".war")) {
6489
6495
  projectType = ["java"];
@@ -1,6 +1,6 @@
1
1
  import fs from "node:fs";
2
2
  import { tmpdir } from "node:os";
3
- import path from "node:path";
3
+ import path, { resolve } from "node:path";
4
4
  import process from "node:process";
5
5
  import { PackageURL } from "packageurl-js";
6
6
  import { Op } from "sequelize";
@@ -8,6 +8,7 @@ import { findCryptoAlgos } from "../helpers/cbomutils.js";
8
8
  import * as db from "../helpers/db.js";
9
9
  import {
10
10
  DEBUG_MODE,
11
+ PROJECT_TYPE_ALIASES,
11
12
  collectGradleDependencies,
12
13
  collectMvnDependencies,
13
14
  executeAtom,
@@ -16,13 +17,15 @@ import {
16
17
  getMavenCommand,
17
18
  getTimestamp,
18
19
  } from "../helpers/utils.js";
20
+ import { createSemanticsSlices } from "./swiftsem.js";
21
+
19
22
  const DB_NAME = "evinser.db";
20
23
  const typePurlsCache = {};
21
24
 
22
25
  /**
23
26
  * Function to create the db for the libraries referred in the sbom.
24
27
  *
25
- * @param {Object} Command line options
28
+ * @param {Object} options Command line options
26
29
  */
27
30
  export const prepareDB = async (options) => {
28
31
  if (!options.dbPath.includes("memory") && !fs.existsSync(options.dbPath)) {
@@ -210,11 +213,6 @@ export const createSlice = (
210
213
  if (!filePath) {
211
214
  return;
212
215
  }
213
- console.log(
214
- `Creating ${sliceType} slice for ${path.resolve(
215
- filePath,
216
- )}. Please wait ...`,
217
- );
218
216
  const firstLanguage = Array.isArray(purlOrLanguages)
219
217
  ? purlOrLanguages[0]
220
218
  : purlOrLanguages;
@@ -224,6 +222,13 @@ export const createSlice = (
224
222
  if (!language) {
225
223
  return undefined;
226
224
  }
225
+ if (
226
+ PROJECT_TYPE_ALIASES.swift.includes(language) &&
227
+ sliceType !== "semantics"
228
+ ) {
229
+ return;
230
+ }
231
+
227
232
  let sliceOutputDir = fs.mkdtempSync(
228
233
  path.join(tmpdir(), `atom-${sliceType}-`),
229
234
  );
@@ -234,8 +239,21 @@ export const createSlice = (
234
239
  ? path.basename(options.output)
235
240
  : path.dirname(options.output);
236
241
  }
237
- const atomFile = path.join(sliceOutputDir, "app.atom");
238
242
  const slicesFile = path.join(sliceOutputDir, `${sliceType}.slices.json`);
243
+ if (sliceType === "semantics") {
244
+ const slicesData = createSemanticsSlices(resolve(filePath), options);
245
+ // Write the semantics slices data
246
+ if (slicesData) {
247
+ fs.writeFileSync(slicesFile, JSON.stringify(slicesData, null, null));
248
+ }
249
+ return { tempDir: sliceOutputDir, slicesFile };
250
+ }
251
+ console.log(
252
+ `Creating ${sliceType} slice for ${path.resolve(
253
+ filePath,
254
+ )}. Please wait ...`,
255
+ );
256
+ const atomFile = path.join(sliceOutputDir, "app.atom");
239
257
  let args = [sliceType];
240
258
  // Support for crypto slices aka CBOM
241
259
  if (sliceType === "reachables" && options.includeCrypto) {
@@ -331,7 +349,7 @@ export const initFromSbom = (components, language) => {
331
349
  * Function to analyze the project
332
350
  *
333
351
  * @param {Object} dbObjMap DB and model instances
334
- * @param {Object} Command line options
352
+ * @param {Object} options Command line options
335
353
  */
336
354
  export const analyzeProject = async (dbObjMap, options) => {
337
355
  const dirPath = options._[0] || ".";
@@ -340,9 +358,11 @@ export const analyzeProject = async (dbObjMap, options) => {
340
358
  let usageSlice = undefined;
341
359
  let dataFlowSlice = undefined;
342
360
  let reachablesSlice = undefined;
361
+ let semanticsSlice = undefined;
343
362
  let usagesSlicesFile = undefined;
344
363
  let dataFlowSlicesFile = undefined;
345
364
  let reachablesSlicesFile = undefined;
365
+ let semanticsSlicesFile = undefined;
346
366
  let dataFlowFrames = {};
347
367
  let servicesMap = {};
348
368
  let retMap = {};
@@ -394,6 +414,29 @@ export const analyzeProject = async (dbObjMap, options) => {
394
414
  usagesSlicesFile = retMap.slicesFile;
395
415
  }
396
416
  }
417
+ // Support for semantics slicing
418
+ if (PROJECT_TYPE_ALIASES.swift.includes(language) && components.length) {
419
+ // Reuse existing semantics slices
420
+ if (
421
+ options.semanticsSlicesFile &&
422
+ fs.existsSync(options.semanticsSlicesFile)
423
+ ) {
424
+ semanticsSlice = JSON.parse(
425
+ fs.readFileSync(options.semanticsSlicesFile, "utf-8"),
426
+ );
427
+ semanticsSlicesFile = options.semanticsSlicesFile;
428
+ } else {
429
+ // Generate our own slices
430
+ retMap = createSlice(language, dirPath, "semantics", options);
431
+ if (retMap?.slicesFile && fs.existsSync(retMap.slicesFile)) {
432
+ semanticsSlice = JSON.parse(
433
+ fs.readFileSync(retMap.slicesFile, "utf-8"),
434
+ );
435
+ semanticsSlicesFile = retMap.slicesFile;
436
+ }
437
+ }
438
+ }
439
+ // Parse usage slices
397
440
  if (usageSlice && Object.keys(usageSlice).length) {
398
441
  const retMap = await parseObjectSlices(
399
442
  language,
@@ -407,6 +450,16 @@ export const analyzeProject = async (dbObjMap, options) => {
407
450
  servicesMap = retMap.servicesMap;
408
451
  userDefinedTypesMap = retMap.userDefinedTypesMap;
409
452
  }
453
+ // Parse the semantics slices
454
+ if (
455
+ semanticsSlice &&
456
+ Object.keys(semanticsSlice).length &&
457
+ components.length
458
+ ) {
459
+ // Identify the purl locations
460
+ const retMap = parseSemanticSlices(components, semanticsSlice);
461
+ purlLocationMap = retMap.purlLocationMap;
462
+ }
410
463
  if (options.withDataFlow) {
411
464
  if (
412
465
  options.dataFlowSlicesFile &&
@@ -435,14 +488,15 @@ export const analyzeProject = async (dbObjMap, options) => {
435
488
  );
436
489
  }
437
490
  return {
438
- atomFile: retMap.atomFile,
491
+ atomFile: retMap?.atomFile,
439
492
  usagesSlicesFile,
440
493
  dataFlowSlicesFile,
441
494
  reachablesSlicesFile,
495
+ semanticsSlicesFile,
442
496
  purlLocationMap,
443
497
  servicesMap,
444
498
  dataFlowFrames,
445
- tempDir: retMap.tempDir,
499
+ tempDir: retMap?.tempDir,
446
500
  userDefinedTypesMap,
447
501
  cryptoComponents,
448
502
  cryptoGeneratePurls,
@@ -501,7 +555,7 @@ export const parseObjectSlices = async (
501
555
  *
502
556
  * @param {string} language Application language
503
557
  * @param {Object} userDefinedTypesMap User Defined types in the application
504
- * @param {Array} usages Usages array for each objectSlice
558
+ * @param {Array} slice Usages array for each objectSlice
505
559
  * @param {Object} dbObjMap DB Models
506
560
  * @param {Object} purlLocationMap Object to track locations where purls are used
507
561
  * @param {Object} purlImportsMap Object to track package urls and their import aliases
@@ -711,6 +765,127 @@ export const parseSliceUsages = async (
711
765
  }
712
766
  };
713
767
 
768
+ /**
769
+ * Method to parse semantic slice data
770
+ *
771
+ * @param {Array} components Components from the input SBOM
772
+ * @param {Object} semanticsSlice Semantic slice data
773
+ * @returns {Object} Parsed metadata
774
+ */
775
+ export function parseSemanticSlices(components, semanticsSlice) {
776
+ const metadata = {};
777
+ // We have two attributes in the semantics slice to expand a given module to its constituent symbols
778
+ // - A less precise buildSymbols, which is obtained by parsing the various output-file-map.json files
779
+ // - A granular and precise moduleInfos, which has the exact classes, protocols, enums etc belonging to each module
780
+ // The objective then is to build the purlLocationMap, which is the list of locations where a given purl is used
781
+ // For this, we have two attributes:
782
+ // - A simpler fileStructures attribute that has the mapping between a swift file and the list of referenced types
783
+ // - A granular fileIndexes attribute that contains information about the clang modules and line number information
784
+
785
+ // We first need to map out the component names to their purls
786
+ // This is because the semantics slice use the module names everywhere
787
+ const componentNamePurlMap = {};
788
+ const componentSymbolsMap = {};
789
+ const allObfuscationsMap = {};
790
+ for (const comp of components) {
791
+ componentNamePurlMap[comp.name] = comp.purl;
792
+ if (!componentSymbolsMap[comp.name]) {
793
+ componentSymbolsMap[comp.name] = new Set();
794
+ }
795
+ if (semanticsSlice?.buildSymbols[comp.name]) {
796
+ for (const asym of semanticsSlice.buildSymbols[comp.name]) {
797
+ componentSymbolsMap[comp.name].add(asym);
798
+ }
799
+ }
800
+ const moduleInfo = semanticsSlice?.moduleInfos[comp.name] || {};
801
+ if (moduleInfo.classes) {
802
+ for (const asym of moduleInfo.classes) {
803
+ componentSymbolsMap[comp.name].add(asym);
804
+ }
805
+ }
806
+ if (moduleInfo.protocols) {
807
+ for (const asym of moduleInfo.protocols) {
808
+ componentSymbolsMap[comp.name].add(asym);
809
+ }
810
+ }
811
+ if (moduleInfo.enums) {
812
+ for (const asym of moduleInfo.enums) {
813
+ componentSymbolsMap[comp.name].add(asym);
814
+ }
815
+ }
816
+ // Now collect the method signatures from class and protocol methods
817
+ if (moduleInfo.classMethods) {
818
+ for (const aclassName of Object.keys(moduleInfo.classMethods)) {
819
+ for (const asym of moduleInfo.classMethods[aclassName]) {
820
+ componentSymbolsMap[comp.name].add(asym);
821
+ }
822
+ }
823
+ }
824
+ if (moduleInfo.protocolMethods) {
825
+ for (const aclassName of Object.keys(moduleInfo.protocolMethods)) {
826
+ for (const asym of moduleInfo.protocolMethods[aclassName]) {
827
+ componentSymbolsMap[comp.name].add(asym);
828
+ }
829
+ }
830
+ }
831
+ // Build a large obfuscation map
832
+ if (moduleInfo.obfuscationMap) {
833
+ for (const asym of Object.keys(moduleInfo.obfuscationMap)) {
834
+ allObfuscationsMap[asym] = moduleInfo.obfuscationMap[asym];
835
+ }
836
+ }
837
+ }
838
+ const purlLocationsSet = {};
839
+ // We now have a good set of data in componentSymbolsMap and allObfuscationsMap
840
+ // We can iterate these symbols and check if they exist under fileIndexes.symbolLocations
841
+ for (const compName of Object.keys(componentSymbolsMap)) {
842
+ const compSymbols = Array.from(componentSymbolsMap[compName]);
843
+ const searchHits = searchSymbolLocations(
844
+ compSymbols,
845
+ semanticsSlice?.fileIndexes,
846
+ );
847
+ // We have an occurrence hit. Let's populate the purlLocationMap
848
+ if (searchHits?.length) {
849
+ const locations =
850
+ purlLocationsSet[componentNamePurlMap[compName]] || new Set();
851
+ for (const ahit of searchHits) {
852
+ for (const aline of ahit.lineNumbers) {
853
+ locations.add(`${ahit.file}#${aline}`);
854
+ }
855
+ }
856
+ purlLocationsSet[componentNamePurlMap[compName]] = locations;
857
+ }
858
+ }
859
+ const purlLocationMap = {};
860
+ for (const apurl of Object.keys(purlLocationsSet)) {
861
+ purlLocationMap[apurl] = Array.from(purlLocationsSet[apurl]).sort();
862
+ }
863
+ return { purlLocationMap };
864
+ }
865
+
866
+ function searchSymbolLocations(compSymbols, fileIndexes) {
867
+ const searchHits = [];
868
+ if (!fileIndexes) {
869
+ return undefined;
870
+ }
871
+ for (const aswiftFile of Object.keys(fileIndexes)) {
872
+ if (!fileIndexes[aswiftFile].symbolLocations) {
873
+ continue;
874
+ }
875
+ for (const asym of Object.keys(fileIndexes[aswiftFile].symbolLocations)) {
876
+ const lineNumbers = fileIndexes[aswiftFile].symbolLocations[asym];
877
+ if (compSymbols.includes(asym)) {
878
+ searchHits.push({
879
+ file: aswiftFile,
880
+ symbol: asym,
881
+ lineNumbers,
882
+ });
883
+ }
884
+ }
885
+ }
886
+ return searchHits;
887
+ }
888
+
714
889
  export const isFilterableType = (
715
890
  language,
716
891
  userDefinedTypesMap,
@@ -786,7 +961,7 @@ export const isFilterableType = (
786
961
  * Method to detect services from annotation objects in the usage slice
787
962
  *
788
963
  * @param {string} language Application language
789
- * @param {Array} usages Usages array for each objectSlice
964
+ * @param {Array} slice Usages array for each objectSlice
790
965
  * @param {Object} servicesMap Existing service map
791
966
  */
792
967
  export const detectServicesFromUsages = (language, slice, servicesMap = {}) => {
@@ -1124,7 +1299,7 @@ export const createEvinseFile = (sliceArtefacts, options) => {
1124
1299
  console.log(evinseOutFile, "created successfully.");
1125
1300
  } else {
1126
1301
  console.log(
1127
- "Unable to identify component evidence for the input SBOM. Only java, javascript, python, and php projects are supported by evinse.",
1302
+ "Unable to identify component evidence for the input SBOM. Only java, javascript, python, swift, and php projects are supported by evinse.",
1128
1303
  );
1129
1304
  }
1130
1305
  if (tempDir?.startsWith(tmpdir())) {
@@ -1141,7 +1316,7 @@ export const createEvinseFile = (sliceArtefacts, options) => {
1141
1316
  * @param {Object} userDefinedTypesMap User Defined types in the application
1142
1317
  * @param {Object} dataFlowSlice Data flow slice object from atom
1143
1318
  * @param {Object} dbObjMap DB models
1144
- * @param {Object} purlLocationMap Object to track locations where purls are used
1319
+ * @param {Object} _purlLocationMap Object to track locations where purls are used
1145
1320
  * @param {Object} purlImportsMap Object to track package urls and their import aliases
1146
1321
  */
1147
1322
  export const collectDataFlowFrames = async (
@@ -1265,7 +1440,7 @@ export const collectDataFlowFrames = async (
1265
1440
  *
1266
1441
  * Implemented based on the logic proposed here - https://github.com/AppThreat/atom/blob/main/specification/docs/slices.md#data-flow-slice
1267
1442
  *
1268
- * @param {string} language Application language
1443
+ * @param {string} _language Application language
1269
1444
  * @param {Object} reachablesSlice Reachables slice object from atom
1270
1445
  */
1271
1446
  export const collectReachableFrames = (_language, reachablesSlice) => {
@@ -4,6 +4,7 @@ import {
4
4
  constructServiceName,
5
5
  detectServicesFromUsages,
6
6
  extractEndpoints,
7
+ parseSemanticSlices,
7
8
  } from "./evinser.js";
8
9
 
9
10
  import { readFileSync } from "node:fs";
@@ -91,3 +92,18 @@ test("extract endpoints test", () => {
91
92
  ),
92
93
  ).toEqual(["/{accountName}"]);
93
94
  });
95
+
96
+ test("parseSemanticSlices", () => {
97
+ const semanticsSlice = JSON.parse(
98
+ readFileSync("./test/data/swiftsem/semantics.slices.json", {
99
+ encoding: "utf-8",
100
+ }),
101
+ );
102
+ const bomJson = JSON.parse(
103
+ readFileSync("./test/data/swiftsem/bom-hakit.json", {
104
+ encoding: "utf-8",
105
+ }),
106
+ );
107
+ const retMap = parseSemanticSlices(bomJson.components, semanticsSlice);
108
+ expect(retMap).toBeDefined();
109
+ });