@coana-tech/cli 14.12.149 → 14.12.151

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/cli.mjs CHANGED
@@ -250895,7 +250895,7 @@ async function onlineScan(dependencyTree, apiKey, timeout) {
250895
250895
  }
250896
250896
 
250897
250897
  // dist/version.js
250898
- var version3 = "14.12.149";
250898
+ var version3 = "14.12.151";
250899
250899
 
250900
250900
  // dist/cli-core.js
250901
250901
  var { mapValues, omit, partition, pickBy: pickBy2 } = import_lodash15.default;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@coana-tech/cli",
3
- "version": "14.12.149",
3
+ "version": "14.12.151",
4
4
  "description": "Coana CLI",
5
5
  "type": "module",
6
6
  "bin": {
@@ -113576,13 +113576,15 @@ var RubyCodeAwareVulnerabilityScanner = class {
113576
113576
  const cmd = cmdt`
113577
113577
  ${await getNodeExecutable(ToolPathResolver.nodeExecutablePath)} --max-old-space-size=${this.options.memoryLimitInMB}
113578
113578
  ${analyzerPath}
113579
+ analyze
113579
113580
  --timeout ${timeoutInSeconds}
113580
- --load-path ${loadPaths}
113581
- --vulnerabilities ${vulnAccPaths}
113582
113581
  --output-diagnostics ${diagnosticsOutputFile}
113583
113582
  --output-reached-packages ${reachedPackagesOutputFile}
113584
113583
  --output-vulnerabilities ${vulnsOutputFile}
113585
- .`;
113584
+ ruby
113585
+ --load-path ${loadPaths}
113586
+ --vulnerabilities ${vulnAccPaths}
113587
+ -- .`;
113586
113588
  if (PRINT_ANALYSIS_COMMAND)
113587
113589
  logger.info("Ruby analysis command:", cmd.join(" "));
113588
113590
  try {
@@ -14373,10 +14373,10 @@ var init_esm2 = __esm({
14373
14373
  }
14374
14374
  });
14375
14375
 
14376
- // ../../node_modules/.pnpm/minimatch@10.0.3/node_modules/minimatch/dist/esm/assert-valid-pattern.js
14376
+ // ../../node_modules/.pnpm/minimatch@10.1.1/node_modules/minimatch/dist/esm/assert-valid-pattern.js
14377
14377
  var MAX_PATTERN_LENGTH, assertValidPattern;
14378
14378
  var init_assert_valid_pattern = __esm({
14379
- "../../node_modules/.pnpm/minimatch@10.0.3/node_modules/minimatch/dist/esm/assert-valid-pattern.js"() {
14379
+ "../../node_modules/.pnpm/minimatch@10.1.1/node_modules/minimatch/dist/esm/assert-valid-pattern.js"() {
14380
14380
  MAX_PATTERN_LENGTH = 1024 * 64;
14381
14381
  assertValidPattern = (pattern) => {
14382
14382
  if (typeof pattern !== "string") {
@@ -14389,10 +14389,10 @@ var init_assert_valid_pattern = __esm({
14389
14389
  }
14390
14390
  });
14391
14391
 
14392
- // ../../node_modules/.pnpm/minimatch@10.0.3/node_modules/minimatch/dist/esm/brace-expressions.js
14392
+ // ../../node_modules/.pnpm/minimatch@10.1.1/node_modules/minimatch/dist/esm/brace-expressions.js
14393
14393
  var posixClasses, braceEscape, regexpEscape, rangesToString, parseClass;
14394
14394
  var init_brace_expressions = __esm({
14395
- "../../node_modules/.pnpm/minimatch@10.0.3/node_modules/minimatch/dist/esm/brace-expressions.js"() {
14395
+ "../../node_modules/.pnpm/minimatch@10.1.1/node_modules/minimatch/dist/esm/brace-expressions.js"() {
14396
14396
  posixClasses = {
14397
14397
  "[:alnum:]": ["\\p{L}\\p{Nl}\\p{Nd}", true],
14398
14398
  "[:alpha:]": ["\\p{L}\\p{Nl}", true],
@@ -14503,20 +14503,23 @@ var init_brace_expressions = __esm({
14503
14503
  }
14504
14504
  });
14505
14505
 
14506
- // ../../node_modules/.pnpm/minimatch@10.0.3/node_modules/minimatch/dist/esm/unescape.js
14506
+ // ../../node_modules/.pnpm/minimatch@10.1.1/node_modules/minimatch/dist/esm/unescape.js
14507
14507
  var unescape;
14508
14508
  var init_unescape = __esm({
14509
- "../../node_modules/.pnpm/minimatch@10.0.3/node_modules/minimatch/dist/esm/unescape.js"() {
14510
- unescape = (s, { windowsPathsNoEscape = false } = {}) => {
14511
- return windowsPathsNoEscape ? s.replace(/\[([^\/\\])\]/g, "$1") : s.replace(/((?!\\).|^)\[([^\/\\])\]/g, "$1$2").replace(/\\([^\/])/g, "$1");
14509
+ "../../node_modules/.pnpm/minimatch@10.1.1/node_modules/minimatch/dist/esm/unescape.js"() {
14510
+ unescape = (s, { windowsPathsNoEscape = false, magicalBraces = true } = {}) => {
14511
+ if (magicalBraces) {
14512
+ return windowsPathsNoEscape ? s.replace(/\[([^\/\\])\]/g, "$1") : s.replace(/((?!\\).|^)\[([^\/\\])\]/g, "$1$2").replace(/\\([^\/])/g, "$1");
14513
+ }
14514
+ return windowsPathsNoEscape ? s.replace(/\[([^\/\\{}])\]/g, "$1") : s.replace(/((?!\\).|^)\[([^\/\\{}])\]/g, "$1$2").replace(/\\([^\/{}])/g, "$1");
14512
14515
  };
14513
14516
  }
14514
14517
  });
14515
14518
 
14516
- // ../../node_modules/.pnpm/minimatch@10.0.3/node_modules/minimatch/dist/esm/ast.js
14519
+ // ../../node_modules/.pnpm/minimatch@10.1.1/node_modules/minimatch/dist/esm/ast.js
14517
14520
  var types, isExtglobType, startNoTraversal, startNoDot, addPatternStart, justDots, reSpecials, regExpEscape, qmark, star, starNoEmpty, AST;
14518
14521
  var init_ast = __esm({
14519
- "../../node_modules/.pnpm/minimatch@10.0.3/node_modules/minimatch/dist/esm/ast.js"() {
14522
+ "../../node_modules/.pnpm/minimatch@10.1.1/node_modules/minimatch/dist/esm/ast.js"() {
14520
14523
  init_brace_expressions();
14521
14524
  init_unescape();
14522
14525
  types = /* @__PURE__ */ new Set(["!", "?", "+", "*", "@"]);
@@ -14869,7 +14872,7 @@ var init_ast = __esm({
14869
14872
  if (this.#root === this)
14870
14873
  this.#fillNegs();
14871
14874
  if (!this.type) {
14872
- const noEmpty = this.isStart() && this.isEnd();
14875
+ const noEmpty = this.isStart() && this.isEnd() && !this.#parts.some((s) => typeof s !== "string");
14873
14876
  const src = this.#parts.map((p) => {
14874
14877
  const [re, _, hasMagic2, uflag] = typeof p === "string" ? _AST.#parseGlob(p, this.#hasMagic, noEmpty) : p.toRegExpSource(allowDot);
14875
14878
  this.#hasMagic = this.#hasMagic || hasMagic2;
@@ -14979,10 +14982,7 @@ var init_ast = __esm({
14979
14982
  }
14980
14983
  }
14981
14984
  if (c === "*") {
14982
- if (noEmpty && glob2 === "*")
14983
- re += starNoEmpty;
14984
- else
14985
- re += star;
14985
+ re += noEmpty && glob2 === "*" ? starNoEmpty : star;
14986
14986
  hasMagic2 = true;
14987
14987
  continue;
14988
14988
  }
@@ -14999,20 +14999,23 @@ var init_ast = __esm({
14999
14999
  }
15000
15000
  });
15001
15001
 
15002
- // ../../node_modules/.pnpm/minimatch@10.0.3/node_modules/minimatch/dist/esm/escape.js
15002
+ // ../../node_modules/.pnpm/minimatch@10.1.1/node_modules/minimatch/dist/esm/escape.js
15003
15003
  var escape;
15004
15004
  var init_escape = __esm({
15005
- "../../node_modules/.pnpm/minimatch@10.0.3/node_modules/minimatch/dist/esm/escape.js"() {
15006
- escape = (s, { windowsPathsNoEscape = false } = {}) => {
15005
+ "../../node_modules/.pnpm/minimatch@10.1.1/node_modules/minimatch/dist/esm/escape.js"() {
15006
+ escape = (s, { windowsPathsNoEscape = false, magicalBraces = false } = {}) => {
15007
+ if (magicalBraces) {
15008
+ return windowsPathsNoEscape ? s.replace(/[?*()[\]{}]/g, "[$&]") : s.replace(/[?*()[\]\\{}]/g, "\\$&");
15009
+ }
15007
15010
  return windowsPathsNoEscape ? s.replace(/[?*()[\]]/g, "[$&]") : s.replace(/[?*()[\]\\]/g, "\\$&");
15008
15011
  };
15009
15012
  }
15010
15013
  });
15011
15014
 
15012
- // ../../node_modules/.pnpm/minimatch@10.0.3/node_modules/minimatch/dist/esm/index.js
15015
+ // ../../node_modules/.pnpm/minimatch@10.1.1/node_modules/minimatch/dist/esm/index.js
15013
15016
  var minimatch, starDotExtRE, starDotExtTest, starDotExtTestDot, starDotExtTestNocase, starDotExtTestNocaseDot, starDotStarRE, starDotStarTest, starDotStarTestDot, dotStarRE, dotStarTest, starRE, starTest, starTestDot, qmarksRE, qmarksTestNocase, qmarksTestNocaseDot, qmarksTestDot, qmarksTest, qmarksTestNoExt, qmarksTestNoExtDot, defaultPlatform, path, sep2, GLOBSTAR, qmark2, star2, twoStarDot, twoStarNoDot, filter, ext, defaults, braceExpand, makeRe, match, globMagic, regExpEscape2, Minimatch;
15014
15017
  var init_esm3 = __esm({
15015
- "../../node_modules/.pnpm/minimatch@10.0.3/node_modules/minimatch/dist/esm/index.js"() {
15018
+ "../../node_modules/.pnpm/minimatch@10.1.1/node_modules/minimatch/dist/esm/index.js"() {
15016
15019
  init_esm2();
15017
15020
  init_assert_valid_pattern();
15018
15021
  init_ast();
@@ -15657,16 +15660,27 @@ var init_esm3 = __esm({
15657
15660
  pp[i] = twoStar;
15658
15661
  }
15659
15662
  } else if (next === void 0) {
15660
- pp[i - 1] = prev + "(?:\\/|" + twoStar + ")?";
15663
+ pp[i - 1] = prev + "(?:\\/|\\/" + twoStar + ")?";
15661
15664
  } else if (next !== GLOBSTAR) {
15662
15665
  pp[i - 1] = prev + "(?:\\/|\\/" + twoStar + "\\/)" + next;
15663
15666
  pp[i + 1] = GLOBSTAR;
15664
15667
  }
15665
15668
  });
15666
- return pp.filter((p) => p !== GLOBSTAR).join("/");
15669
+ const filtered = pp.filter((p) => p !== GLOBSTAR);
15670
+ if (this.partial && filtered.length >= 1) {
15671
+ const prefixes = [];
15672
+ for (let i = 1; i <= filtered.length; i++) {
15673
+ prefixes.push(filtered.slice(0, i).join("/"));
15674
+ }
15675
+ return "(?:" + prefixes.join("|") + ")";
15676
+ }
15677
+ return filtered.join("/");
15667
15678
  }).join("|");
15668
15679
  const [open, close] = set.length > 1 ? ["(?:", ")"] : ["", ""];
15669
15680
  re = "^" + open + re + close + "$";
15681
+ if (this.partial) {
15682
+ re = "^(?:\\/|" + open + re.slice(1, -1) + close + ")$";
15683
+ }
15670
15684
  if (this.negate)
15671
15685
  re = "^(?!" + re + ").+$";
15672
15686
  try {
@@ -18029,7 +18043,7 @@ var init_esm5 = __esm({
18029
18043
  }
18030
18044
  });
18031
18045
 
18032
- // ../../node_modules/.pnpm/path-scurry@2.0.0/node_modules/path-scurry/dist/esm/index.js
18046
+ // ../../node_modules/.pnpm/path-scurry@2.0.1/node_modules/path-scurry/dist/esm/index.js
18033
18047
  import { posix, win32 } from "node:path";
18034
18048
  import { fileURLToPath } from "node:url";
18035
18049
  import { lstatSync, readdir as readdirCB, readdirSync, readlinkSync, realpathSync as rps } from "fs";
@@ -18037,7 +18051,7 @@ import * as actualFS from "node:fs";
18037
18051
  import { lstat, readdir as readdir2, readlink, realpath } from "node:fs/promises";
18038
18052
  var realpathSync, defaultFS, fsFromOption, uncDriveRegexp, uncToDrive, eitherSep, UNKNOWN, IFIFO, IFCHR, IFDIR, IFBLK, IFREG, IFLNK, IFSOCK, IFMT, IFMT_UNKNOWN, READDIR_CALLED, LSTAT_CALLED, ENOTDIR, ENOENT, ENOREADLINK, ENOREALPATH, ENOCHILD, TYPEMASK, entToType, normalizeCache, normalize, normalizeNocaseCache, normalizeNocase, ResolveCache, ChildrenCache, setAsCwd, PathBase, PathWin32, PathPosix, PathScurryBase, PathScurryWin32, PathScurryPosix, PathScurryDarwin, Path, PathScurry;
18039
18053
  var init_esm6 = __esm({
18040
- "../../node_modules/.pnpm/path-scurry@2.0.0/node_modules/path-scurry/dist/esm/index.js"() {
18054
+ "../../node_modules/.pnpm/path-scurry@2.0.1/node_modules/path-scurry/dist/esm/index.js"() {
18041
18055
  init_esm4();
18042
18056
  init_esm5();
18043
18057
  realpathSync = rps.native;
@@ -18084,7 +18098,7 @@ var init_esm6 = __esm({
18084
18098
  ENOCHILD = ENOTDIR | ENOENT | ENOREALPATH;
18085
18099
  TYPEMASK = 1023;
18086
18100
  entToType = (s) => s.isFile() ? IFREG : s.isDirectory() ? IFDIR : s.isSymbolicLink() ? IFLNK : s.isCharacterDevice() ? IFCHR : s.isBlockDevice() ? IFBLK : s.isSocket() ? IFSOCK : s.isFIFO() ? IFIFO : UNKNOWN;
18087
- normalizeCache = /* @__PURE__ */ new Map();
18101
+ normalizeCache = new LRUCache({ max: 2 ** 12 });
18088
18102
  normalize = (s) => {
18089
18103
  const c = normalizeCache.get(s);
18090
18104
  if (c)
@@ -18093,7 +18107,7 @@ var init_esm6 = __esm({
18093
18107
  normalizeCache.set(s, n12);
18094
18108
  return n12;
18095
18109
  };
18096
- normalizeNocaseCache = /* @__PURE__ */ new Map();
18110
+ normalizeNocaseCache = new LRUCache({ max: 2 ** 12 });
18097
18111
  normalizeNocase = (s) => {
18098
18112
  const c = normalizeNocaseCache.get(s);
18099
18113
  if (c)
@@ -18250,6 +18264,7 @@ var init_esm6 = __esm({
18250
18264
  get parentPath() {
18251
18265
  return (this.parent || this).fullpath();
18252
18266
  }
18267
+ /* c8 ignore start */
18253
18268
  /**
18254
18269
  * Deprecated alias for Dirent['parentPath'] Somewhat counterintuitively,
18255
18270
  * this property refers to the *parent* path, not the path object itself.
@@ -18259,6 +18274,7 @@ var init_esm6 = __esm({
18259
18274
  get path() {
18260
18275
  return this.parentPath;
18261
18276
  }
18277
+ /* c8 ignore stop */
18262
18278
  /**
18263
18279
  * Do not create new Path objects directly. They should always be accessed
18264
18280
  * via the PathScurry class or other methods on the Path class.
@@ -19767,10 +19783,10 @@ var init_esm6 = __esm({
19767
19783
  }
19768
19784
  });
19769
19785
 
19770
- // ../../node_modules/.pnpm/glob@11.0.3/node_modules/glob/dist/esm/pattern.js
19786
+ // ../../node_modules/.pnpm/glob@13.0.0/node_modules/glob/dist/esm/pattern.js
19771
19787
  var isPatternList, isGlobList, Pattern;
19772
19788
  var init_pattern = __esm({
19773
- "../../node_modules/.pnpm/glob@11.0.3/node_modules/glob/dist/esm/pattern.js"() {
19789
+ "../../node_modules/.pnpm/glob@13.0.0/node_modules/glob/dist/esm/pattern.js"() {
19774
19790
  init_esm3();
19775
19791
  isPatternList = (pl) => pl.length >= 1;
19776
19792
  isGlobList = (gl) => gl.length >= 1;
@@ -19938,10 +19954,10 @@ var init_pattern = __esm({
19938
19954
  }
19939
19955
  });
19940
19956
 
19941
- // ../../node_modules/.pnpm/glob@11.0.3/node_modules/glob/dist/esm/ignore.js
19957
+ // ../../node_modules/.pnpm/glob@13.0.0/node_modules/glob/dist/esm/ignore.js
19942
19958
  var defaultPlatform2, Ignore;
19943
19959
  var init_ignore = __esm({
19944
- "../../node_modules/.pnpm/glob@11.0.3/node_modules/glob/dist/esm/ignore.js"() {
19960
+ "../../node_modules/.pnpm/glob@13.0.0/node_modules/glob/dist/esm/ignore.js"() {
19945
19961
  init_esm3();
19946
19962
  init_pattern();
19947
19963
  defaultPlatform2 = typeof process === "object" && process && typeof process.platform === "string" ? process.platform : "linux";
@@ -20032,10 +20048,10 @@ var init_ignore = __esm({
20032
20048
  }
20033
20049
  });
20034
20050
 
20035
- // ../../node_modules/.pnpm/glob@11.0.3/node_modules/glob/dist/esm/processor.js
20051
+ // ../../node_modules/.pnpm/glob@13.0.0/node_modules/glob/dist/esm/processor.js
20036
20052
  var HasWalkedCache, MatchRecord, SubWalks, Processor;
20037
20053
  var init_processor = __esm({
20038
- "../../node_modules/.pnpm/glob@11.0.3/node_modules/glob/dist/esm/processor.js"() {
20054
+ "../../node_modules/.pnpm/glob@13.0.0/node_modules/glob/dist/esm/processor.js"() {
20039
20055
  init_esm3();
20040
20056
  HasWalkedCache = class _HasWalkedCache {
20041
20057
  store;
@@ -20259,10 +20275,10 @@ var init_processor = __esm({
20259
20275
  }
20260
20276
  });
20261
20277
 
20262
- // ../../node_modules/.pnpm/glob@11.0.3/node_modules/glob/dist/esm/walker.js
20278
+ // ../../node_modules/.pnpm/glob@13.0.0/node_modules/glob/dist/esm/walker.js
20263
20279
  var makeIgnore, GlobUtil, GlobWalker, GlobStream;
20264
20280
  var init_walker = __esm({
20265
- "../../node_modules/.pnpm/glob@11.0.3/node_modules/glob/dist/esm/walker.js"() {
20281
+ "../../node_modules/.pnpm/glob@13.0.0/node_modules/glob/dist/esm/walker.js"() {
20266
20282
  init_esm5();
20267
20283
  init_ignore();
20268
20284
  init_processor();
@@ -20594,11 +20610,11 @@ var init_walker = __esm({
20594
20610
  }
20595
20611
  });
20596
20612
 
20597
- // ../../node_modules/.pnpm/glob@11.0.3/node_modules/glob/dist/esm/glob.js
20613
+ // ../../node_modules/.pnpm/glob@13.0.0/node_modules/glob/dist/esm/glob.js
20598
20614
  import { fileURLToPath as fileURLToPath2 } from "node:url";
20599
20615
  var defaultPlatform3, Glob;
20600
20616
  var init_glob = __esm({
20601
- "../../node_modules/.pnpm/glob@11.0.3/node_modules/glob/dist/esm/glob.js"() {
20617
+ "../../node_modules/.pnpm/glob@13.0.0/node_modules/glob/dist/esm/glob.js"() {
20602
20618
  init_esm3();
20603
20619
  init_esm6();
20604
20620
  init_pattern();
@@ -20804,10 +20820,10 @@ var init_glob = __esm({
20804
20820
  }
20805
20821
  });
20806
20822
 
20807
- // ../../node_modules/.pnpm/glob@11.0.3/node_modules/glob/dist/esm/has-magic.js
20823
+ // ../../node_modules/.pnpm/glob@13.0.0/node_modules/glob/dist/esm/has-magic.js
20808
20824
  var hasMagic;
20809
20825
  var init_has_magic = __esm({
20810
- "../../node_modules/.pnpm/glob@11.0.3/node_modules/glob/dist/esm/has-magic.js"() {
20826
+ "../../node_modules/.pnpm/glob@13.0.0/node_modules/glob/dist/esm/has-magic.js"() {
20811
20827
  init_esm3();
20812
20828
  hasMagic = (pattern, options = {}) => {
20813
20829
  if (!Array.isArray(pattern)) {
@@ -20822,7 +20838,7 @@ var init_has_magic = __esm({
20822
20838
  }
20823
20839
  });
20824
20840
 
20825
- // ../../node_modules/.pnpm/glob@11.0.3/node_modules/glob/dist/esm/index.js
20841
+ // ../../node_modules/.pnpm/glob@13.0.0/node_modules/glob/dist/esm/index.js
20826
20842
  var esm_exports = {};
20827
20843
  __export(esm_exports, {
20828
20844
  Glob: () => Glob,
@@ -20862,7 +20878,7 @@ function globIterate(pattern, options = {}) {
20862
20878
  }
20863
20879
  var streamSync, stream, iterateSync, iterate, sync, glob;
20864
20880
  var init_esm7 = __esm({
20865
- "../../node_modules/.pnpm/glob@11.0.3/node_modules/glob/dist/esm/index.js"() {
20881
+ "../../node_modules/.pnpm/glob@13.0.0/node_modules/glob/dist/esm/index.js"() {
20866
20882
  init_esm3();
20867
20883
  init_glob();
20868
20884
  init_has_magic();
@@ -21608,15 +21624,19 @@ var CallGraph = class {
21608
21624
  functionToFunction = /* @__PURE__ */ new Map();
21609
21625
  callToFunctionOrModule = new FullNodeMap();
21610
21626
  callToContainingFunction = new NodeMap();
21627
+ _edges = 0;
21628
+ get numberOfEdges() {
21629
+ return this._edges;
21630
+ }
21611
21631
  addEdge(call, from, to) {
21612
21632
  const fs4 = mapGetSet(this.functionToFunction, from);
21613
21633
  fs4.add(to);
21614
21634
  const cs = mapGetSet(this.callToFunctionOrModule, call);
21615
- if (!cs.has(to)) {
21635
+ if (setAdd(cs, to)) {
21636
+ this._edges++;
21616
21637
  if (logger.isVerboseEnabled())
21617
21638
  logger.verbose(`Adding call edge from call ${nodeLocationToString(call)}, function ${from} -> ${to}`);
21618
21639
  }
21619
- cs.add(to);
21620
21640
  this.callToContainingFunction.set(call, from);
21621
21641
  }
21622
21642
  getEdges() {
@@ -21777,6 +21797,9 @@ var TimeoutException = class extends Error {
21777
21797
  function nanoToMs(n12) {
21778
21798
  return `${n12 / 1000000n}ms`;
21779
21799
  }
21800
+ function nanoToMsNumber(n12) {
21801
+ return Number(n12 / 1000000n);
21802
+ }
21780
21803
 
21781
21804
  // ../analysis-engine/dist/abstract-constraintvar.js
21782
21805
  var ConstraintVar = class {
@@ -23731,7 +23754,7 @@ var RubyAstVisitor = class {
23731
23754
  if (name2)
23732
23755
  scopes.push(name2);
23733
23756
  }
23734
- let fqName = name;
23757
+ let fqName;
23735
23758
  if (isClassNode(node))
23736
23759
  fqName = "<constructor>";
23737
23760
  else if (isSingletonMethodNode(node))
@@ -23815,7 +23838,7 @@ var RubyAnalyzer = class {
23815
23838
  if (moduleInfo.isEntry)
23816
23839
  calledFunctions.add(moduleInfo);
23817
23840
  const patched = /* @__PURE__ */ new Set();
23818
- for (const t15 of this.op.a.allTokens())
23841
+ for (const t15 of this.solver.allTokens())
23819
23842
  if (t15 instanceof FunctionToken && t15.kind !== "method" && (isBlockNode(t15.fun) || isDoBlockNode(t15.fun))) {
23820
23843
  const functionInfo = assertDefined(functionInfos.get(t15.fun));
23821
23844
  if (!patched.has(functionInfo) && !calledFunctions.has(functionInfo) && calledFunctions.has(functionInfo.parent)) {
@@ -23929,19 +23952,42 @@ function scopeAnalysis(tree) {
23929
23952
 
23930
23953
  // ../analysis-engine/dist/solver.js
23931
23954
  import assert10 from "node:assert";
23932
- var Solver = class {
23955
+ var Solver = class _Solver {
23933
23956
  globalState;
23957
+ vars = /* @__PURE__ */ new Set();
23958
+ tokens = /* @__PURE__ */ new Map();
23959
+ numberOfTokens = 0;
23960
+ tokenListeners = /* @__PURE__ */ new Map();
23961
+ subsetEdges = /* @__PURE__ */ new Map();
23962
+ reverseSubsetEdges = /* @__PURE__ */ new Map();
23963
+ numberOfSubsetEdges = 0;
23964
+ static EMPTY_TOKENS_SIZE = [0, []];
23934
23965
  listeners = /* @__PURE__ */ new Map();
23935
23966
  callGraph = new CallGraph();
23936
23967
  listenersProcessed = /* @__PURE__ */ new Map();
23937
23968
  varsToUnprocessedTokens = /* @__PURE__ */ new Map();
23938
23969
  diagnostics;
23939
23970
  nodesWithNewEdges = /* @__PURE__ */ new Set();
23971
+ postponedListenerCalls = [];
23940
23972
  postponedListenersProcessed = 0;
23973
+ lastTelemetryTime = 0;
23974
+ telemetryTimer = new Timer();
23941
23975
  constructor(globalState) {
23942
23976
  this.globalState = globalState;
23943
23977
  this.diagnostics = globalState.diagnostics;
23944
23978
  }
23979
+ emitTelemetry() {
23980
+ const { globalState: a, diagnostics: d } = this;
23981
+ a.telemetry?.diagnostics({
23982
+ propagations: d.propagations,
23983
+ tokens: this.numberOfTokens,
23984
+ subsetEdges: this.numberOfSubsetEdges,
23985
+ unprocessedTokensSize: d.unprocessedTokensSize,
23986
+ modules: a.moduleInfos.size,
23987
+ functions: a.functionInfos.size,
23988
+ callEdges: this.callGraph.numberOfEdges
23989
+ });
23990
+ }
23945
23991
  addTokenConstraint(t15, to) {
23946
23992
  if (to === void 0)
23947
23993
  return;
@@ -23949,25 +23995,45 @@ var Solver = class {
23949
23995
  logger.debug(`Adding constraint ${t15} \u2208 ${to}`);
23950
23996
  this.addToken(t15, to);
23951
23997
  }
23952
- addToken(t15, toRep) {
23953
- const state = this.globalState;
23954
- if (state.addToken(t15, toRep)) {
23955
- state.vars.add(toRep);
23956
- this.tokenAdded(toRep, t15);
23957
- return true;
23958
- }
23959
- return false;
23998
+ addToken(t15, v) {
23999
+ const ts = this.tokens.get(v);
24000
+ if (!ts)
24001
+ this.tokens.set(v, t15);
24002
+ else if (ts instanceof TokenInterface) {
24003
+ if (ts === t15)
24004
+ return false;
24005
+ this.tokens.set(v, /* @__PURE__ */ new Set([ts, t15]));
24006
+ } else if (!setAdd(ts, t15))
24007
+ return false;
24008
+ this.vars.add(v);
24009
+ this.numberOfTokens++;
24010
+ this.tokenAdded(v, t15);
24011
+ return true;
23960
24012
  }
23961
24013
  addTokens(ts, to) {
23962
- const state = this.globalState;
23963
- state.vars.add(to);
23964
- if (ts instanceof TokenInterface) {
23965
- if (state.addToken(ts, to))
23966
- this.tokenAdded(to, ts);
23967
- } else {
24014
+ if (ts instanceof TokenInterface)
24015
+ this.addToken(ts, to);
24016
+ else {
24017
+ this.vars.add(to);
23968
24018
  let ws;
23969
- for (const t15 of state.addTokens(ts, to))
23970
- ws = this.tokenAdded(to, t15, ws);
24019
+ let vs = this.tokens.get(to);
24020
+ for (const t15 of ts) {
24021
+ let add = false;
24022
+ if (!vs) {
24023
+ this.tokens.set(to, vs = t15);
24024
+ add = true;
24025
+ } else if (vs instanceof TokenInterface) {
24026
+ if (vs !== t15) {
24027
+ this.tokens.set(to, vs = /* @__PURE__ */ new Set([vs, t15]));
24028
+ add = true;
24029
+ }
24030
+ } else
24031
+ add = setAdd(vs, t15);
24032
+ if (add) {
24033
+ this.numberOfTokens++;
24034
+ ws = this.tokenAdded(to, t15, ws);
24035
+ }
24036
+ }
23971
24037
  }
23972
24038
  }
23973
24039
  tokenAdded(toRep, t15, ws) {
@@ -23987,12 +24053,12 @@ var Solver = class {
23987
24053
  this.addForAllTokensConstraintPrivate(v, this.getListenerID(lkey), listener);
23988
24054
  }
23989
24055
  addForAllTokensConstraintPrivate(v, id, listener) {
23990
- const m = mapGetMap(this.globalState.tokenListeners, v);
24056
+ const m = mapGetMap(this.tokenListeners, v);
23991
24057
  if (!m.has(id)) {
23992
- for (const t15 of this.globalState.getTokens(v))
24058
+ for (const t15 of this.getTokens(v))
23993
24059
  this.callTokenListener(id, listener, t15);
23994
24060
  m.set(id, listener);
23995
- this.globalState.vars.add(v);
24061
+ this.vars.add(v);
23996
24062
  return true;
23997
24063
  }
23998
24064
  return false;
@@ -24003,14 +24069,11 @@ var Solver = class {
24003
24069
  if (now)
24004
24070
  listener(t15);
24005
24071
  else {
24006
- this.enqueueListenerCall([listener, t15]);
24072
+ this.postponedListenerCalls.push([listener, t15]);
24007
24073
  this.diagnostics.tokenListenerNotifications++;
24008
24074
  }
24009
24075
  }
24010
24076
  }
24011
- enqueueListenerCall(la) {
24012
- this.globalState.postponedListenerCalls.push(la);
24013
- }
24014
24077
  getListenerID(key) {
24015
24078
  let id = 0n;
24016
24079
  if (key.t) {
@@ -24043,14 +24106,13 @@ var Solver = class {
24043
24106
  }
24044
24107
  addSubsetEdge(from, to) {
24045
24108
  if (from !== to) {
24046
- const state = this.globalState;
24047
- const s = mapGetSet(state.subsetEdges, from);
24109
+ const s = mapGetSet(this.subsetEdges, from);
24048
24110
  if (setAdd(s, to)) {
24049
- state.numberOfSubsetEdges++;
24050
- mapGetSet(state.reverseSubsetEdges, to).add(from);
24051
- state.vars.add(from);
24052
- state.vars.add(to);
24053
- const [size, ts] = state.getTokensSize(from);
24111
+ this.numberOfSubsetEdges++;
24112
+ mapGetSet(this.reverseSubsetEdges, to).add(from);
24113
+ this.vars.add(from);
24114
+ this.vars.add(to);
24115
+ const [size, ts] = this.getTokensSize(from);
24054
24116
  if (size > 0) {
24055
24117
  if (logger.isDebugEnabled())
24056
24118
  logger.debug(`Worklist size: ${this.diagnostics.unprocessedTokensSize}, propagating ${size} token${size === 1 ? "" : "s"} from ${from}`);
@@ -24069,6 +24131,11 @@ var Solver = class {
24069
24131
  }
24070
24132
  }
24071
24133
  printDiagnostics() {
24134
+ const elapsed = nanoToMsNumber(this.telemetryTimer.elapsed());
24135
+ if (elapsed > this.lastTelemetryTime + 3e3) {
24136
+ this.lastTelemetryTime = elapsed;
24137
+ this.emitTelemetry();
24138
+ }
24072
24139
  }
24073
24140
  async propagate() {
24074
24141
  const timer = new Timer();
@@ -24077,19 +24144,19 @@ var Solver = class {
24077
24144
  this.nodesWithNewEdges.clear();
24078
24145
  for (const v of this.varsToUnprocessedTokens.keys())
24079
24146
  this.processTokens(v);
24080
- if (state.postponedListenerCalls.length > 0) {
24147
+ if (this.postponedListenerCalls.length > 0) {
24081
24148
  if (logger.isVerboseEnabled())
24082
- logger.verbose(`Processing listener calls: ${state.postponedListenerCalls.length}`);
24149
+ logger.verbose(`Processing listener calls: ${this.postponedListenerCalls.length}`);
24083
24150
  const timer2 = new Timer();
24084
24151
  d.listenerNotificationRounds++;
24085
- for (const [fun, arg] of state.postponedListenerCalls) {
24152
+ for (const [fun, arg] of this.postponedListenerCalls) {
24086
24153
  fun(arg);
24087
24154
  if (++this.postponedListenersProcessed % 100 === 0) {
24088
24155
  state.timeoutTimer.checkTimeout();
24089
24156
  this.printDiagnostics();
24090
24157
  }
24091
24158
  }
24092
- state.postponedListenerCalls.length = this.postponedListenersProcessed = 0;
24159
+ this.postponedListenerCalls.length = this.postponedListenersProcessed = 0;
24093
24160
  d.timings.listenerCall += timer2.elapsed();
24094
24161
  }
24095
24162
  }
@@ -24103,15 +24170,14 @@ var Solver = class {
24103
24170
  if (logger.isDebugEnabled())
24104
24171
  logger.debug(`Worklist size: ${this.diagnostics.unprocessedTokensSize}, propagating ${size} token${size === 1 ? "" : "s"} from ${v}`);
24105
24172
  this.diagnostics.unprocessedTokensSize -= size;
24106
- const state = this.globalState;
24107
- assert10(state.vars.has(v));
24108
- const s = state.subsetEdges.get(v);
24173
+ assert10(this.vars.has(v));
24174
+ const s = this.subsetEdges.get(v);
24109
24175
  if (s) {
24110
24176
  for (const to of s)
24111
24177
  this.addTokens(Array.isArray(ts) ? ts : [ts], to);
24112
24178
  this.incrementPropagations();
24113
24179
  }
24114
- const tr = state.tokenListeners.get(v);
24180
+ const tr = this.tokenListeners.get(v);
24115
24181
  if (tr)
24116
24182
  if (Array.isArray(ts))
24117
24183
  for (const t15 of ts)
@@ -24122,6 +24188,31 @@ var Solver = class {
24122
24188
  this.callTokenListener(id, listener, ts);
24123
24189
  }
24124
24190
  }
24191
+ getTokens(v) {
24192
+ const ts = this.tokens.get(v);
24193
+ if (ts instanceof TokenInterface)
24194
+ return [ts];
24195
+ return ts ?? [];
24196
+ }
24197
+ getTokensSize(v) {
24198
+ if (v) {
24199
+ const ts = this.tokens.get(v);
24200
+ if (ts) {
24201
+ if (ts instanceof TokenInterface)
24202
+ return [1, [ts]];
24203
+ return [ts.size, ts];
24204
+ }
24205
+ }
24206
+ return _Solver.EMPTY_TOKENS_SIZE;
24207
+ }
24208
+ *allTokens() {
24209
+ for (const ts of this.tokens.values()) {
24210
+ if (ts instanceof TokenInterface)
24211
+ yield ts;
24212
+ else
24213
+ yield* ts;
24214
+ }
24215
+ }
24125
24216
  listenersProcessedByTokenListener() {
24126
24217
  const m = /* @__PURE__ */ new Map();
24127
24218
  for (const [id, s] of this.listenersProcessed) {
@@ -24147,6 +24238,33 @@ import { resolve as resolve3 } from "node:path";
24147
24238
  import assert11 from "node:assert";
24148
24239
  import { join as join3, relative as relative2 } from "node:path";
24149
24240
 
24241
+ // ../utils/dist/telemetry.js
24242
+ import { appendFileSync } from "node:fs";
24243
+ var TelemetryEmitter = class {
24244
+ filePath;
24245
+ constructor(filePath) {
24246
+ this.filePath = filePath;
24247
+ }
24248
+ emit(event) {
24249
+ try {
24250
+ appendFileSync(this.filePath, JSON.stringify({ data: event }) + "\n");
24251
+ } catch {
24252
+ }
24253
+ }
24254
+ phaseStarted(phase, metadata = {}) {
24255
+ this.emit({ type: "phase.started", phase, ...metadata });
24256
+ }
24257
+ phaseCompleted(phase, durationMs, metadata = {}) {
24258
+ this.emit({ type: "phase.completed", phase, durationMs, ...metadata });
24259
+ }
24260
+ diagnostics(metrics) {
24261
+ this.emit({ type: "diagnostics", memory: getMemoryUsageBytes(), ...metrics });
24262
+ }
24263
+ };
24264
+ function getMemoryUsageBytes() {
24265
+ return process.memoryUsage().heapUsed;
24266
+ }
24267
+
24150
24268
  // ../analysis-engine/dist/diagnostics.js
24151
24269
  import { inspect } from "node:util";
24152
24270
  var AnalysisDiagnostics = class {
@@ -24174,7 +24292,7 @@ var AnalysisDiagnostics = class {
24174
24292
  };
24175
24293
 
24176
24294
  // ../analysis-engine/dist/global-state.js
24177
- var GlobalState = class _GlobalState {
24295
+ var GlobalState = class {
24178
24296
  rootDir;
24179
24297
  options;
24180
24298
  moduleInfos = /* @__PURE__ */ new Map();
@@ -24184,20 +24302,15 @@ var GlobalState = class _GlobalState {
24184
24302
  functionInfos = new NodeMap();
24185
24303
  diagnostics = new AnalysisDiagnostics();
24186
24304
  warningsUnsupported = new NodeMap();
24187
- vars = /* @__PURE__ */ new Set();
24188
- tokens = /* @__PURE__ */ new Map();
24189
- tokenListeners = /* @__PURE__ */ new Map();
24190
- numberOfTokens = 0;
24191
- subsetEdges = /* @__PURE__ */ new Map();
24192
- reverseSubsetEdges = /* @__PURE__ */ new Map();
24193
- numberOfSubsetEdges = 0;
24194
- static EMPTY_TOKENS_SIZE = [0, []];
24195
24305
  timeoutTimer;
24196
- postponedListenerCalls = [];
24306
+ telemetry;
24197
24307
  constructor(rootDir, options) {
24198
24308
  this.rootDir = rootDir;
24199
24309
  this.options = options;
24200
24310
  this.timeoutTimer = new Timer(options.timeout);
24311
+ if (options.telemetryFilePath) {
24312
+ this.telemetry = new TelemetryEmitter(options.telemetryFilePath);
24313
+ }
24201
24314
  }
24202
24315
  discoverFile(packageInfo, filePath, isEntry = false) {
24203
24316
  let moduleInfo = this.moduleInfos.get(filePath);
@@ -24220,89 +24333,18 @@ var GlobalState = class _GlobalState {
24220
24333
  if (setAdd(warnings, message) && logger.isWarnEnabled())
24221
24334
  logger.warn(`Unsupported: ${message} at ${nodeToString(node)}`);
24222
24335
  }
24223
- getTokens(v) {
24224
- if (v) {
24225
- const ts = this.tokens.get(v);
24226
- if (ts) {
24227
- if (ts instanceof TokenInterface)
24228
- return [ts];
24229
- return ts;
24230
- }
24231
- }
24232
- return [];
24233
- }
24234
- *allTokens() {
24235
- for (const ts of this.tokens.values()) {
24236
- if (ts instanceof TokenInterface)
24237
- yield ts;
24238
- else
24239
- yield* ts;
24240
- }
24241
- }
24242
- getTokensSize(v) {
24243
- if (v) {
24244
- const ts = this.tokens.get(v);
24245
- if (ts) {
24246
- if (ts instanceof TokenInterface)
24247
- return [1, [ts]];
24248
- return [ts.size, ts];
24249
- }
24250
- }
24251
- return _GlobalState.EMPTY_TOKENS_SIZE;
24252
- }
24253
- hasToken(v, t15) {
24254
- const ts = this.tokens.get(v);
24255
- if (!ts)
24256
- return false;
24257
- else if (ts instanceof TokenInterface)
24258
- return ts === t15;
24259
- else
24260
- return ts.has(t15);
24261
- }
24262
- addToken(t15, v) {
24263
- const ts = this.tokens.get(v);
24264
- if (!ts)
24265
- this.tokens.set(v, t15);
24266
- else if (ts instanceof TokenInterface) {
24267
- if (ts === t15)
24268
- return false;
24269
- this.tokens.set(v, /* @__PURE__ */ new Set([ts, t15]));
24270
- } else if (!setAdd(ts, t15))
24271
- return false;
24272
- this.numberOfTokens++;
24273
- return true;
24274
- }
24275
- addTokens(ts, v) {
24276
- const added = [];
24277
- let vs = this.tokens.get(v);
24278
- for (const t15 of ts) {
24279
- let add = false;
24280
- if (!vs) {
24281
- vs = t15;
24282
- this.tokens.set(v, vs);
24283
- add = true;
24284
- } else if (vs instanceof TokenInterface) {
24285
- if (vs !== t15) {
24286
- vs = /* @__PURE__ */ new Set([vs, t15]);
24287
- this.tokens.set(v, vs);
24288
- add = true;
24289
- }
24290
- } else
24291
- add = setAdd(vs, t15);
24292
- if (add)
24293
- added.push(t15);
24294
- }
24295
- this.numberOfTokens += added.length;
24296
- return added;
24297
- }
24298
24336
  };
24299
24337
 
24300
24338
  // ../analysis-engine/dist/vulnerabilities.js
24301
- function analyzeVulnerabilities(d, callGraph, targets) {
24339
+ function analyzeVulnerabilities(globalState, callGraph, targets) {
24340
+ const { diagnostics: d, telemetry } = globalState;
24302
24341
  const timer = new Timer();
24342
+ telemetry?.phaseStarted("vuln_analysis", { vulnerabilities: targets.size });
24303
24343
  const parents = callGraph.reachableFunctions(callGraph.functionToFunction.keys().filter((f) => f.isEntry).toArray());
24304
24344
  d.timings.vulnAnalysis = timer.elapsed();
24345
+ telemetry?.phaseCompleted("vuln_analysis", nanoToMsNumber(timer.elapsed()));
24305
24346
  timer.reset();
24347
+ telemetry?.phaseStarted("vuln_paths", { reachableFunctions: parents.size });
24306
24348
  const results = Object.fromEntries(targets.entries().map(([vuln, funcs]) => {
24307
24349
  if (!funcs.length)
24308
24350
  return [vuln, { reachable: false, reason: "Function not found" }];
@@ -24328,6 +24370,7 @@ function analyzeVulnerabilities(d, callGraph, targets) {
24328
24370
  ];
24329
24371
  }));
24330
24372
  d.timings.vulnPaths += timer.elapsed();
24373
+ telemetry?.phaseCompleted("vuln_paths", nanoToMsNumber(timer.elapsed()));
24331
24374
  return results;
24332
24375
  }
24333
24376
 
@@ -24342,127 +24385,145 @@ async function runAnalysis(Solver2, getAnalyzer, path2, options = {}) {
24342
24385
  const [, isNew] = globalState.discoverFile(p, file, true);
24343
24386
  assert12(isNew, `File ${file} should be new in the global state`);
24344
24387
  }
24388
+ const { moduleWorklist, diagnostics, telemetry } = globalState;
24389
+ const analysisTimer = new Timer();
24390
+ telemetry?.phaseStarted("analysis", { fileCount: files.length });
24391
+ solver.emitTelemetry();
24345
24392
  try {
24346
24393
  while (true) {
24347
- while (!globalState.moduleWorklist.isEmpty()) {
24348
- const moduleInfo = globalState.moduleWorklist.pop();
24394
+ while (!moduleWorklist.isEmpty()) {
24395
+ const moduleInfo = moduleWorklist.pop();
24349
24396
  assert12(moduleInfo.isIncluded);
24350
24397
  const relPath = moduleInfo.getPath();
24351
- logger.verbose("Analyzing module: %s, %d remaining", relPath, globalState.moduleWorklist.size);
24398
+ logger.verbose("Analyzing module: %s, %d remaining", relPath, moduleWorklist.size);
24352
24399
  const timer = new Timer();
24353
24400
  const tree = await analyzer.parse(resolve3(path2, relPath));
24354
- globalState.diagnostics.timings.parsing += timer.elapsed();
24401
+ diagnostics.timings.parsing += timer.elapsed();
24355
24402
  if (tree) {
24356
24403
  tree.filename = relPath;
24357
24404
  moduleInfo.loc = getSourceLocation(tree.rootNode);
24358
24405
  analyzer.visit(moduleInfo, tree);
24359
24406
  } else
24360
- globalState.diagnostics.errors.parse++;
24407
+ diagnostics.errors.parse++;
24361
24408
  }
24362
24409
  await solver.propagate();
24363
- if (!analyzer.patch?.() && globalState.moduleWorklist.isEmpty())
24410
+ if (!analyzer.patch?.() && moduleWorklist.isEmpty())
24364
24411
  break;
24365
24412
  }
24366
24413
  } catch (e5) {
24367
24414
  if (e5 instanceof TimeoutException) {
24368
24415
  logger.error("Analysis timed out");
24369
- globalState.diagnostics.timedOut = true;
24416
+ diagnostics.timedOut = true;
24370
24417
  } else
24371
24418
  throw e5;
24372
24419
  }
24373
24420
  analyzer.finalize?.();
24421
+ solver.emitTelemetry();
24422
+ telemetry?.phaseCompleted("analysis", nanoToMsNumber(analysisTimer.elapsed()), {
24423
+ modules: globalState.moduleInfos.size,
24424
+ timedOut: diagnostics.timedOut
24425
+ });
24374
24426
  return [
24375
24427
  solver,
24376
- options.vulnerabilities?.length ? analyzeVulnerabilities(globalState.diagnostics, solver.callGraph, analyzer.getVulnTargets()) : void 0
24428
+ options.vulnerabilities?.length ? analyzeVulnerabilities(globalState, solver.callGraph, analyzer.getVulnTargets()) : void 0
24377
24429
  ];
24378
24430
  }
24379
24431
 
24380
24432
  // dist/commands/analyze.js
24381
- var analyze_default = new Command("analyze").addOption(new Option("-d, --debug", "Enable debug logging").conflicts("logLevel").implies({ logLevel: "debug" }).hideHelp()).addOption(new Option("--log-level <level>", "Set the logging level").choices(["error", "warn", "info", "verbose", "debug"]).default("info")).option("--timeout <number>", "Timeout for the analysis in seconds", Number.parseFloat).option("-l, --load-path <path...>", "Value for the $LOAD_PATH global variable - used to resolve `require` calls").option("--vulnerabilities <vuln...>", "Perform reachability analysis for the given vulnerabilities", (v, prev) => {
24382
- assert13(validateVulnerabilityID(v), `Invalid vulnerability ID: ${v}`);
24383
- prev.push(v);
24384
- return prev;
24385
- }, []).option("-s, --soundness <path>", "Path to dynamic call graph for soundness checking").option("--soundness-debug", "Print dynamic edges that will improve reachability recall").option("--output-callgraph <path>", "Output the call graph to a JSON file").option("--output-vulnerabilities <path>", "Output the vulnerability analysis results to a JSON file").option("--output-diagnostics <path>", "Output the diagnostics to a JSON file").option("--output-reached-packages <path>", "Output a list of reached non-entry packages to a JSON file").argument("<path>", "File system path to folder containing the project").configureHelp({ sortOptions: true }).action(async (path2, options) => {
24386
- if (options.outputVulnerabilities && !options.vulnerabilities.length)
24387
- throw new Error("The --output-vulnerabilities option requires at least one vulnerability to be specified.");
24388
- setLogLevel(options.logLevel);
24389
- path2 = resolve4(path2);
24390
- const loadPath = options.loadPath?.map((p) => resolve4(p)) ?? [];
24391
- const timer = new Timer();
24392
- const [solver, vulnRes] = await runAnalysis(RubySolver, (solver2) => new RubyAnalyzer(solver2, loadPath), path2, t10(options, ["timeout", "vulnerabilities"]));
24393
- console.log(`Analysis finished in ${nanoToMs(timer.elapsed())}.`);
24394
- const { callGraph, globalState } = solver;
24395
- const edges = callGraph.getEdges();
24396
- console.log(`Analyzed ${globalState.moduleInfos.size} modules and ${globalState.functionInfos.size} functions.
24433
+ function analyzeAction(f) {
24434
+ return async (path2, _, command) => {
24435
+ const options = command.optsWithGlobals();
24436
+ if (options.outputVulnerabilities && !options.vulnerabilities.length)
24437
+ throw new Error("The --output-vulnerabilities option requires at least one vulnerability to be specified.");
24438
+ setLogLevel(options.logLevel);
24439
+ path2 = resolve4(path2);
24440
+ const timer = new Timer();
24441
+ const [solver, vulnRes] = await runAnalysis(...f(options), path2, {
24442
+ ...t10(options, ["timeout", "vulnerabilities"]),
24443
+ telemetryFilePath: options.outputTelemetry
24444
+ });
24445
+ console.log(`Analysis finished in ${nanoToMs(timer.elapsed())}.`);
24446
+ const { callGraph, globalState } = solver;
24447
+ const edges = callGraph.getEdges();
24448
+ console.log(`Analyzed ${globalState.moduleInfos.size} modules and ${globalState.functionInfos.size} functions.
24397
24449
  Call graph has ${edges.length} edges.`);
24398
- console.log(`Diagnostics:
24450
+ console.log(`Diagnostics:
24399
24451
  ${inspect2(globalState.diagnostics, { depth: Infinity })}`);
24400
- console.log("Listeners processed:");
24401
- const lp = solver.listenersProcessedByTokenListener().entries().map(([l, cnt]) => [solver.listenerName(l) + ":", cnt]).toArray().sort(([, c1], [, c2]) => c2 - c1);
24402
- const width = Math.max(...lp.map(([l]) => l.length));
24403
- console.log(lp.map(([l, cnt]) => ` ${l.padEnd(width)} ${cnt}`).join("\n"));
24404
- console.log("Unsupported warnings:");
24405
- console.log(t5(t3(globalState.warningsUnsupported.values().flatMap(e3()).toArray(), e3())).sort((a, b) => b[1] - a[1]).map(([w, c]) => ` ${w}: ${c}`).join("\n"));
24406
- if (options.soundness) {
24407
- const dynCG = JSON.parse(await readFile2(options.soundness, "utf8"));
24408
- const comparison = compareStaticWithDynamicCG(callGraph, globalState.moduleInfos.values(), dynCG);
24409
- console.log(comparisonReport(comparison));
24410
- if (options.soundnessDebug) {
24411
- const sse = new Set(comparison.staticEdges);
24412
- const ssr = new Set(comparison.staticReachable.map((x) => {
24413
- const i = x.lastIndexOf(":");
24414
- assert13(i !== -1);
24415
- return x.slice(0, i);
24416
- }));
24417
- const imp = comparison.dynamicEdges.filter((e5) => {
24418
- if (sse.has(e5))
24419
- return false;
24420
- const [a, b] = e5.split(" -> ", 2);
24421
- const match2 = assertDefined(a?.match(/^(.*) \d+$/));
24422
- return ssr.has(assertDefined(match2[1])) && !ssr.has(b);
24423
- });
24424
- console.log(`Dynamic edges that would improve reachability recall (${imp.length}):
24452
+ console.log("Listeners processed:");
24453
+ const lp = solver.listenersProcessedByTokenListener().entries().map(([l, cnt]) => [solver.listenerName(l) + ":", cnt]).toArray().sort(([, c1], [, c2]) => c2 - c1);
24454
+ const width = Math.max(...lp.map(([l]) => l.length));
24455
+ console.log(lp.map(([l, cnt]) => ` ${l.padEnd(width)} ${cnt}`).join("\n"));
24456
+ console.log("Unsupported warnings:");
24457
+ console.log(t5(t3(globalState.warningsUnsupported.values().flatMap(e3()).toArray(), e3())).sort((a, b) => b[1] - a[1]).map(([w, c]) => ` ${w}: ${c}`).join("\n"));
24458
+ if (options.soundness) {
24459
+ const dynCG = JSON.parse(await readFile2(options.soundness, "utf8"));
24460
+ const comparison = compareStaticWithDynamicCG(callGraph, globalState.moduleInfos.values(), dynCG);
24461
+ console.log(comparisonReport(comparison));
24462
+ if (options.soundnessDebug) {
24463
+ const sse = new Set(comparison.staticEdges);
24464
+ const ssr = new Set(comparison.staticReachable.map((x) => {
24465
+ const i = x.lastIndexOf(":");
24466
+ assert13(i !== -1);
24467
+ return x.slice(0, i);
24468
+ }));
24469
+ const imp = comparison.dynamicEdges.filter((e5) => {
24470
+ if (sse.has(e5))
24471
+ return false;
24472
+ const [a, b] = e5.split(" -> ", 2);
24473
+ const match2 = assertDefined(a?.match(/^(.*) \d+$/));
24474
+ return ssr.has(assertDefined(match2[1])) && !ssr.has(b);
24475
+ });
24476
+ console.log(`Dynamic edges that would improve reachability recall (${imp.length}):
24425
24477
  ${imp.join("\n ")}`);
24478
+ }
24426
24479
  }
24427
- }
24428
- if (options.outputCallgraph) {
24429
- await writeFile(options.outputCallgraph, JSON.stringify(callGraph.serialize()), "utf8");
24430
- console.log(`Call graph written to ${options.outputCallgraph}`);
24431
- }
24432
- if (options.outputDiagnostics)
24433
- await writeFile(options.outputDiagnostics, JSON.stringify(globalState.diagnostics, (_, v) => typeof v === "bigint" ? Number(v / 1000000n) : v, 2));
24434
- if (vulnRes) {
24435
- if (process.stdout.isTTY) {
24436
- console.log("\nVulnerability analysis results:");
24437
- for (const [v, r3] of Object.entries(vulnRes)) {
24438
- if (r3.reachable) {
24439
- console.log(` ${v}: reachable`);
24440
- for (const [i, stack] of r3.stacks.entries()) {
24441
- console.log(` Path ${i + 1}/${r3.stacks.length}:`);
24442
- const pkglen = Math.max(...stack.map((node) => node.package.length)) + 1;
24443
- let prevPkg = "";
24444
- for (const node of stack) {
24445
- const sloc = locationToString(node.sourceLocation);
24446
- const pkgs = node.package === prevPkg ? "" : `${prevPkg = node.package}:`;
24447
- console.log(` ${pkgs.padEnd(pkglen)} ${sloc} ${node.confidence.toFixed(2)}`);
24480
+ if (options.outputCallgraph) {
24481
+ await writeFile(options.outputCallgraph, JSON.stringify(callGraph.serialize()), "utf8");
24482
+ console.log(`Call graph written to ${options.outputCallgraph}`);
24483
+ }
24484
+ if (options.outputDiagnostics)
24485
+ await writeFile(options.outputDiagnostics, JSON.stringify(globalState.diagnostics, (_2, v) => typeof v === "bigint" ? Number(v / 1000000n) : v, 2));
24486
+ if (vulnRes) {
24487
+ if (process.stdout.isTTY) {
24488
+ console.log("\nVulnerability analysis results:");
24489
+ for (const [v, r3] of Object.entries(vulnRes)) {
24490
+ if (r3.reachable) {
24491
+ console.log(` ${v}: reachable`);
24492
+ for (const [i, stack] of r3.stacks.entries()) {
24493
+ console.log(` Path ${i + 1}/${r3.stacks.length}:`);
24494
+ const pkglen = Math.max(...stack.map((node) => node.package.length)) + 1;
24495
+ let prevPkg = "";
24496
+ for (const node of stack) {
24497
+ const sloc = locationToString(node.sourceLocation);
24498
+ const pkgs = node.package === prevPkg ? "" : `${prevPkg = node.package}:`;
24499
+ console.log(` ${pkgs.padEnd(pkglen)} ${sloc} ${node.confidence.toFixed(2)}`);
24500
+ }
24448
24501
  }
24449
- }
24450
- } else
24451
- console.log(` ${v}: unreachable (${r3.reason})`);
24502
+ } else
24503
+ console.log(` ${v}: unreachable (${r3.reason})`);
24504
+ }
24505
+ }
24506
+ if (options.outputVulnerabilities) {
24507
+ const callerToString = t6({ caller: (c) => c.toString() });
24508
+ const matches = t9(t11(vulnRes, (r3) => r3.reachable), (r3) => r3.stacks.map((stack) => stack.map(callerToString)));
24509
+ await writeFile(options.outputVulnerabilities, JSON.stringify(matches), "utf8");
24510
+ console.log(`Vulnerability results written to ${options.outputVulnerabilities}`);
24452
24511
  }
24453
24512
  }
24454
- if (options.outputVulnerabilities) {
24455
- const callerToString = t6({ caller: (c) => c.toString() });
24456
- const matches = t9(t11(vulnRes, (r3) => r3.reachable), (r3) => r3.stacks.map((stack) => stack.map(callerToString)));
24457
- await writeFile(options.outputVulnerabilities, JSON.stringify(matches), "utf8");
24458
- console.log(`Vulnerability results written to ${options.outputVulnerabilities}`);
24513
+ if (options.outputReachedPackages) {
24514
+ await writeFile(options.outputReachedPackages, JSON.stringify(globalState.packageInfos.values().filter((p) => !p.isEntry).map(t10(["name", "version"])).toArray()), "utf8");
24515
+ console.log(`Reached packages written to ${options.outputReachedPackages}`);
24459
24516
  }
24460
- }
24461
- if (options.outputReachedPackages) {
24462
- await writeFile(options.outputReachedPackages, JSON.stringify(globalState.packageInfos.values().filter((p) => !p.isEntry).map(t10(["name", "version"])).toArray()), "utf8");
24463
- console.log(`Reached packages written to ${options.outputReachedPackages}`);
24464
- }
24465
- });
24517
+ };
24518
+ }
24519
+ var analyze_default = new Command("analyze").addOption(new Option("-d, --debug", "Enable debug logging").conflicts("logLevel").implies({ logLevel: "debug" }).hideHelp()).addOption(new Option("--log-level <level>", "Set the logging level").choices(["error", "warn", "info", "verbose", "debug"]).default("info")).option("--timeout <number>", "Timeout for the analysis in seconds", Number.parseFloat).option("-s, --soundness <path>", "Path to dynamic call graph for soundness checking").option("--soundness-debug", "Print dynamic edges that will improve reachability recall").option("--output-callgraph <path>", "Output the call graph to a JSON file").option("--output-vulnerabilities <path>", "Output the vulnerability analysis results to a JSON file").option("--output-diagnostics <path>", "Output the diagnostics to a JSON file").option("--output-reached-packages <path>", "Output a list of reached non-entry packages to a JSON file").option("--output-telemetry <path>", "Output streaming telemetry to a JSONL file (default: $ANALYZER_TELEMETRY_FILE_PATH)", process.env["ANALYZER_TELEMETRY_FILE_PATH"]).configureHelp({ sortOptions: true }).addCommand(new Command("ruby").description("Analyze a Ruby project").argument("<path>", "File system path to folder containing the project").option("-l, --load-path <path...>", "Value for the $LOAD_PATH global variable - used to resolve `require` calls").option("--vulnerabilities <vuln...>", "Perform reachability analysis for the given vulnerabilities", (v, prev) => {
24520
+ assert13(validateVulnerabilityID(v), `Invalid vulnerability ID: ${v}`);
24521
+ prev.push(v);
24522
+ return prev;
24523
+ }, []).action(analyzeAction((options) => {
24524
+ const loadPath = options.loadPath?.map((p) => resolve4(p)) ?? [];
24525
+ return [RubySolver, (s) => new RubyAnalyzer(s, loadPath)];
24526
+ })));
24466
24527
 
24467
24528
  // dist/commands/vis-cg.js
24468
24529
  import assert14 from "node:assert";