@coana-tech/cli 14.12.123 → 14.12.125

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.
@@ -9,11 +9,11 @@ import {
9
9
  require_hints,
10
10
  require_parser,
11
11
  require_patching
12
- } from "./chunk-RUAUP2X2.js";
12
+ } from "./chunk-D4F3MOY5.js";
13
13
  import {
14
14
  require_moduleresolver,
15
15
  require_typescript
16
- } from "./chunk-XXE5Z2AZ.js";
16
+ } from "./chunk-L7Z5ACXW.js";
17
17
  import {
18
18
  __commonJS,
19
19
  __name,
@@ -37,7 +37,7 @@ import {
37
37
  require_tokens,
38
38
  require_transform,
39
39
  require_util
40
- } from "./chunk-US4VPHJ4.js";
40
+ } from "./chunk-NNOCQRBG.js";
41
41
 
42
42
  // lib/misc/timer.js
43
43
  var require_timer = __commonJS({
@@ -177,6 +177,8 @@ var require_globalstate = __commonJS({
177
177
  entryFiles = /* @__PURE__ */ new Set();
178
178
  reachedFiles = /* @__PURE__ */ new Set();
179
179
  pendingFiles = new worklist_1.Worklist();
180
+ reachedModulesFull = /* @__PURE__ */ new Set();
181
+ pendingModulesFull = new worklist_1.Worklist();
180
182
  filesWithParseErrors = [];
181
183
  filesAnalyzed = [];
182
184
  timeoutTimer = new timer_1.default();
@@ -257,6 +259,18 @@ var require_globalstate = __commonJS({
257
259
  (parent ? this.functionInfos.get(parent).functions : m.functions).add(f);
258
260
  this.vulnerabilities?.reachedFunction(f);
259
261
  }
262
+ reachedModuleFull(m) {
263
+ if (!m.isIncluded)
264
+ assert_1.default.fail(`Module ${m} is not included in the analysis!`);
265
+ if (options_1.options.modulesOnly)
266
+ assert_1.default.fail("reachedModuleFull called in modulesOnly mode!");
267
+ if (!this.reachedModulesFull.has(m)) {
268
+ if (options_1.options.lazy && logger_1.default.isVerboseEnabled())
269
+ logger_1.default.verbose(`Lazy: Module ${m} added for full analysis`);
270
+ this.reachedModulesFull.add(m);
271
+ this.pendingModulesFull.enqueue(m);
272
+ }
273
+ }
260
274
  reachedFile(tofile, entry, from, local) {
261
275
  let moduleInfo;
262
276
  if (this.reachedFiles.has(tofile))
@@ -484,6 +498,7 @@ var require_fragmentstate = __commonJS({
484
498
  numberOfFunctionToFunctionEdges = 0;
485
499
  numberOfCallToFunctionEdges = 0;
486
500
  functionsWithArguments = /* @__PURE__ */ new Set();
501
+ functionsWithThis = /* @__PURE__ */ new Set();
487
502
  artificialFunctions = [];
488
503
  callLocations = /* @__PURE__ */ new Set();
489
504
  nativeCallLocations = /* @__PURE__ */ new Set();
@@ -510,6 +525,7 @@ var require_fragmentstate = __commonJS({
510
525
  dynamicPropertyWrites = /* @__PURE__ */ new Set();
511
526
  maybeEmptyMethodCalls = /* @__PURE__ */ new Map();
512
527
  functionTokens = /* @__PURE__ */ new Set();
528
+ lazyEscaping = /* @__PURE__ */ new Map();
513
529
  constructor(s) {
514
530
  this.a = s.globalState;
515
531
  this.varProducer = new constraintvarproducer_1.ConstraintVarProducer(s, s.globalState);
@@ -654,6 +670,11 @@ var require_fragmentstate = __commonJS({
654
670
  }
655
671
  return f;
656
672
  }
673
+ registerThis(f) {
674
+ this.functionsWithThis.add(f);
675
+ if (logger_1.default.isDebugEnabled())
676
+ logger_1.default.debug(`Function uses 'this': ${(0, util_1.locationToStringWithFile)(f.loc)}`);
677
+ }
657
678
  getRepresentative(v) {
658
679
  let w = v;
659
680
  const ws = [];
@@ -661,7 +682,7 @@ var require_fragmentstate = __commonJS({
661
682
  const w2 = this.redirections.get(w);
662
683
  if (!w2)
663
684
  break;
664
- (0, assert_1.default)(ws.length < 100);
685
+ (0, assert_1.default)(ws.length < 1e3);
665
686
  ws.push(w);
666
687
  w = w2;
667
688
  }
@@ -986,6 +1007,7 @@ var require_diagnostics = __commonJS({
986
1007
  }
987
1008
  packages = 0;
988
1009
  modules = 0;
1010
+ modulesFull = 0;
989
1011
  functions = 0;
990
1012
  vars = 0;
991
1013
  listeners = 0;
@@ -1016,6 +1038,7 @@ var require_diagnostics = __commonJS({
1016
1038
  unprocessedTokensSize = 0;
1017
1039
  wave = 0;
1018
1040
  round = 0;
1041
+ analyzerRounds = 0;
1019
1042
  listenerNotificationRounds = 0;
1020
1043
  lastPrintDiagnosticsTime = 0;
1021
1044
  tokenListenerNotifications = 0;
@@ -1043,6 +1066,323 @@ var require_diagnostics = __commonJS({
1043
1066
  }
1044
1067
  });
1045
1068
 
1069
+ // lib/misc/bitset.js
1070
+ var require_bitset = __commonJS({
1071
+ "lib/misc/bitset.js"(exports) {
1072
+ "use strict";
1073
+ var __importDefault = exports && exports.__importDefault || function(mod) {
1074
+ return mod && mod.__esModule ? mod : { "default": mod };
1075
+ };
1076
+ Object.defineProperty(exports, "__esModule", { value: true });
1077
+ exports.BitSet = void 0;
1078
+ var node_assert_1 = __importDefault(__require("node:assert"));
1079
+ var BitSet = class {
1080
+ static {
1081
+ __name(this, "BitSet");
1082
+ }
1083
+ static make(size) {
1084
+ if (size <= 32)
1085
+ return new SmallBitSet();
1086
+ return new LargeBitSet(size);
1087
+ }
1088
+ };
1089
+ exports.BitSet = BitSet;
1090
+ var SmallBitSet = class _SmallBitSet extends BitSet {
1091
+ static {
1092
+ __name(this, "SmallBitSet");
1093
+ }
1094
+ v = 0;
1095
+ get value() {
1096
+ return this.v;
1097
+ }
1098
+ isEmpty() {
1099
+ return !this.v;
1100
+ }
1101
+ set(index) {
1102
+ if (index < 0 || index >= 32)
1103
+ throw new RangeError(`Index out of bounds: ${index}`);
1104
+ this.v |= 1 << index;
1105
+ }
1106
+ unionUpdate(other) {
1107
+ (0, node_assert_1.default)(other instanceof _SmallBitSet, "Expected other to be SmallBitSet");
1108
+ this.v |= other.v;
1109
+ }
1110
+ *[Symbol.iterator]() {
1111
+ let { v } = this;
1112
+ for (let i = 0; v; i++, v >>>= 1)
1113
+ if (v & 1)
1114
+ yield i;
1115
+ }
1116
+ };
1117
+ var LargeBitSet = class _LargeBitSet extends BitSet {
1118
+ static {
1119
+ __name(this, "LargeBitSet");
1120
+ }
1121
+ size;
1122
+ words;
1123
+ empty = true;
1124
+ constructor(size) {
1125
+ super();
1126
+ this.size = size;
1127
+ this.words = new Int32Array(size + 31 >>> 5);
1128
+ }
1129
+ isEmpty() {
1130
+ return this.empty;
1131
+ }
1132
+ set(index) {
1133
+ if (index < 0 || index >= this.size)
1134
+ throw new RangeError(`Index out of bounds: ${index}`);
1135
+ this.words[index >>> 5] |= 1 << (index & 31);
1136
+ this.empty = false;
1137
+ }
1138
+ unionUpdate(other) {
1139
+ if (other instanceof SmallBitSet) {
1140
+ this.words[0] |= other.value;
1141
+ this.empty &&= other.isEmpty();
1142
+ } else {
1143
+ (0, node_assert_1.default)(other instanceof _LargeBitSet, "Expected other to be LargeBitSet");
1144
+ node_assert_1.default.strictEqual(this.size, other.size, "BitSets must have same size");
1145
+ for (const [i, w] of other.words.entries())
1146
+ this.words[i] |= w;
1147
+ this.empty &&= other.empty;
1148
+ }
1149
+ }
1150
+ *[Symbol.iterator]() {
1151
+ for (let [wi, w] of this.words.entries()) {
1152
+ if (!w)
1153
+ continue;
1154
+ const baseIndex = wi << 5;
1155
+ for (let i = 0; w; i++, w >>>= 1) {
1156
+ if (w & 1)
1157
+ yield baseIndex + i;
1158
+ }
1159
+ }
1160
+ }
1161
+ };
1162
+ }
1163
+ });
1164
+
1165
+ // lib/analysis/escaping.js
1166
+ var require_escaping = __commonJS({
1167
+ "lib/analysis/escaping.js"(exports) {
1168
+ "use strict";
1169
+ var __importDefault = exports && exports.__importDefault || function(mod) {
1170
+ return mod && mod.__esModule ? mod : { "default": mod };
1171
+ };
1172
+ Object.defineProperty(exports, "__esModule", { value: true });
1173
+ exports.findEscapingObjects = findEscapingObjects;
1174
+ exports.findExportedTokens = findExportedTokens;
1175
+ var tokens_1 = require_tokens();
1176
+ var constraintvars_1 = require_constraintvars();
1177
+ var logger_1 = __importDefault(require_logger());
1178
+ var types_1 = __require("@babel/types");
1179
+ var accesspaths_1 = require_accesspaths();
1180
+ var ecmascript_1 = require_ecmascript();
1181
+ var options_1 = require_options();
1182
+ var packagejson_1 = require_packagejson();
1183
+ function findEscapingObjects(ms, solver) {
1184
+ const a = solver.globalState;
1185
+ const f = solver.fragmentState;
1186
+ const worklist = [];
1187
+ const visited = /* @__PURE__ */ new Set();
1188
+ const escaping = /* @__PURE__ */ new Set();
1189
+ const theUnknownAccessPathToken = a.canonicalizeToken(new tokens_1.AccessPathToken(accesspaths_1.UnknownAccessPath.instance));
1190
+ function addToWorklist(v) {
1191
+ for (const t of v instanceof tokens_1.Token ? [v] : f.getTokens(f.getRepresentative(v)))
1192
+ if ((t instanceof tokens_1.AllocationSiteToken || t instanceof tokens_1.FunctionToken || t instanceof tokens_1.NativeObjectToken && t.name === "exports") && !visited.has(t)) {
1193
+ worklist.push(t);
1194
+ visited.add(t);
1195
+ }
1196
+ }
1197
+ __name(addToWorklist, "addToWorklist");
1198
+ for (const m of Array.isArray(ms) ? ms : [ms])
1199
+ if (1) {
1200
+ const pi = a.packageJsonInfos.get(m.packageInfo.dir);
1201
+ if (!pi?.exports || (0, packagejson_1.isInExports)(`./${m.relativePath}`, pi.exports))
1202
+ addToWorklist(f.varProducer.objPropVar(a.canonicalizeToken(new tokens_1.NativeObjectToken("module", m)), "exports"));
1203
+ }
1204
+ const w2 = [];
1205
+ while (worklist.length !== 0) {
1206
+ const t = worklist.pop();
1207
+ if (t instanceof tokens_1.FunctionToken)
1208
+ w2.push(t);
1209
+ else if (t instanceof tokens_1.ObjectToken || t instanceof tokens_1.NativeObjectToken && t.name === "exports") {
1210
+ for (const p of f.objectProperties.get(t) ?? [])
1211
+ if (!(0, ecmascript_1.isInternalProperty)(p))
1212
+ addToWorklist(f.varProducer.objPropVar(t, p));
1213
+ }
1214
+ }
1215
+ visited.clear();
1216
+ for (const t of w2) {
1217
+ visited.add(t);
1218
+ worklist.push(t);
1219
+ }
1220
+ for (const v of f.maybeEscaping)
1221
+ addToWorklist(v);
1222
+ f.maybeEscaping.clear();
1223
+ while (worklist.length !== 0) {
1224
+ const t = worklist.pop();
1225
+ if (t instanceof tokens_1.ObjectToken) {
1226
+ if (logger_1.default.isDebugEnabled())
1227
+ logger_1.default.debug(`Escaping object: ${t}`);
1228
+ escaping.add(t);
1229
+ }
1230
+ if (t instanceof tokens_1.FunctionToken) {
1231
+ addToWorklist(f.varProducer.returnVar(t.fun));
1232
+ const tv = f.getRepresentative(f.varProducer.thisVar(t.fun));
1233
+ if (Array.from(f.getTokens(tv)).every((y) => !(0, constraintvars_1.isObjectPropertyVarObj)(y)) && ((0, types_1.isClassMethod)(t.fun) ? t.fun.kind === "constructor" : f.functionsWithThis.has(t.fun))) {
1234
+ const q = a.canonicalizeToken(new tokens_1.ObjectToken(t.fun));
1235
+ solver.addToken(q, tv);
1236
+ solver.addInherits(q, solver.varProducer.objPropVar(t, "prototype"));
1237
+ }
1238
+ for (const param of t.fun.params)
1239
+ if ((0, types_1.isIdentifier)(param))
1240
+ solver.addToken(theUnknownAccessPathToken, f.getRepresentative(f.varProducer.nodeVar(param)));
1241
+ solver.addToken(theUnknownAccessPathToken, f.getRepresentative(f.varProducer.thisVar(t.fun)));
1242
+ }
1243
+ for (const p of f.objectProperties.get(t) ?? [])
1244
+ if (!(0, ecmascript_1.isInternalProperty)(p)) {
1245
+ const w = f.varProducer.objPropVar(t, p);
1246
+ addToWorklist(w);
1247
+ solver.addToken(theUnknownAccessPathToken, f.getRepresentative(w));
1248
+ }
1249
+ }
1250
+ if (logger_1.default.isVerboseEnabled()) {
1251
+ const objecttokens = /* @__PURE__ */ new Set();
1252
+ for (const [, ts] of f.getAllVarsAndTokens())
1253
+ for (const t of ts)
1254
+ if (t instanceof tokens_1.ObjectToken)
1255
+ objecttokens.add(t);
1256
+ logger_1.default.verbose(`Escaping objects: ${escaping.size}/${objecttokens.size}`);
1257
+ }
1258
+ return escaping;
1259
+ }
1260
+ __name(findEscapingObjects, "findEscapingObjects");
1261
+ function findExportedTokens(m, solver) {
1262
+ const a = solver.globalState;
1263
+ const f = solver.fragmentState;
1264
+ const vp = f.varProducer;
1265
+ return f.getTokens(f.getRepresentative(vp.objPropVar(a.canonicalizeToken(new tokens_1.NativeObjectToken("module", m)), "exports")));
1266
+ }
1267
+ __name(findExportedTokens, "findExportedTokens");
1268
+ }
1269
+ });
1270
+
1271
+ // lib/analysis/lazy.js
1272
+ var require_lazy = __commonJS({
1273
+ "lib/analysis/lazy.js"(exports) {
1274
+ "use strict";
1275
+ var __importDefault = exports && exports.__importDefault || function(mod) {
1276
+ return mod && mod.__esModule ? mod : { "default": mod };
1277
+ };
1278
+ Object.defineProperty(exports, "__esModule", { value: true });
1279
+ exports.constructTokenReachabilityGraph = constructTokenReachabilityGraph;
1280
+ exports.isLazyAPActive = isLazyAPActive;
1281
+ exports.assertLazyAPActive = assertLazyAPActive;
1282
+ exports.getExportedFunctionsByModule = getExportedFunctionsByModule;
1283
+ var node_assert_1 = __importDefault(__require("node:assert"));
1284
+ var bitset_1 = require_bitset();
1285
+ var scc_1 = require_scc();
1286
+ var util_1 = require_util();
1287
+ var ecmascript_1 = require_ecmascript();
1288
+ var constraintvars_1 = require_constraintvars();
1289
+ var escaping_1 = require_escaping();
1290
+ var tokens_1 = require_tokens();
1291
+ var logger_1 = __importDefault(require_logger());
1292
+ function constructTokenReachabilityGraph(ts, solver) {
1293
+ const f = solver.fragmentState;
1294
+ const vp = f.varProducer;
1295
+ const worklist = [...ts];
1296
+ const graph = new Map(worklist.map((t) => [t, void 0]));
1297
+ const add = /* @__PURE__ */ __name((edges, v) => {
1298
+ for (const t of f.getTokens(f.getRepresentative(v))) {
1299
+ edges.add(t);
1300
+ if (!graph.has(t)) {
1301
+ graph.set(t, void 0);
1302
+ worklist.push(t);
1303
+ }
1304
+ }
1305
+ }, "add");
1306
+ let anyFunction = false;
1307
+ while (worklist.length !== 0) {
1308
+ const t = worklist.pop();
1309
+ if ((0, constraintvars_1.isObjectPropertyVarObj)(t)) {
1310
+ const edges = /* @__PURE__ */ new Set();
1311
+ graph.set(t, edges);
1312
+ for (const p of f.objectProperties.get(t) ?? [])
1313
+ if (p !== ecmascript_1.ARRAY_ALL)
1314
+ for (const ac of ["normal", "get", "set"])
1315
+ add(edges, vp.objPropVar(t, p, ac));
1316
+ if (t instanceof tokens_1.FunctionToken) {
1317
+ anyFunction = true;
1318
+ add(edges, vp.returnVar(t.fun));
1319
+ }
1320
+ }
1321
+ }
1322
+ return [graph, anyFunction];
1323
+ }
1324
+ __name(constructTokenReachabilityGraph, "constructTokenReachabilityGraph");
1325
+ function isLazyAPActive(ap, a) {
1326
+ if (ap.dependents) {
1327
+ return ap.moduleInfo.directDependents.values().some((m) => !a.reachedModulesFull.has(m));
1328
+ } else
1329
+ return !a.reachedModulesFull.has(ap.moduleInfo);
1330
+ }
1331
+ __name(isLazyAPActive, "isLazyAPActive");
1332
+ function assertLazyAPActive(ap, a) {
1333
+ if (ap.dependents) {
1334
+ if (ap.moduleInfo.directDependents.values().every((m) => a.reachedModulesFull.has(m)))
1335
+ node_assert_1.default.fail(`Lazy access path ${ap} is irrelevant, all dependents are already reached`);
1336
+ } else if (a.reachedModulesFull.has(ap.moduleInfo))
1337
+ node_assert_1.default.fail(`Lazy access path ${ap} is irrelevant, module ${ap.moduleInfo} is already reached`);
1338
+ }
1339
+ __name(assertLazyAPActive, "assertLazyAPActive");
1340
+ function getExportedFunctionsByModule(ms, solver) {
1341
+ const roots = /* @__PURE__ */ new Set();
1342
+ for (const m of ms)
1343
+ (0, util_1.addAll)((0, escaping_1.findExportedTokens)(m, solver), roots);
1344
+ const [graph, anyFunction] = constructTokenReachabilityGraph(roots, solver);
1345
+ if (logger_1.default.isVerboseEnabled())
1346
+ logger_1.default.verbose(`Lazy: Exported tokens: ${graph.size}`);
1347
+ if (ms.size === 0 || !anyFunction)
1348
+ return /* @__PURE__ */ new Map();
1349
+ else if (ms.size === 1)
1350
+ return /* @__PURE__ */ new Map([[
1351
+ ms.values().next().value,
1352
+ graph.keys().filter((t) => t instanceof tokens_1.FunctionToken).toArray()
1353
+ ]]);
1354
+ const [sccs, reps] = (0, scc_1.nuutila)(graph.keys(), (t) => graph.get(t));
1355
+ const sets = /* @__PURE__ */ new Map();
1356
+ for (const [t, rep] of reps.entries())
1357
+ (0, util_1.mapArrayAdd)(rep, t, sets);
1358
+ const modules = [...ms];
1359
+ const bitsets = new Map(sccs.map((t) => [t, bitset_1.BitSet.make(ms.size)]));
1360
+ for (const [i, m] of modules.entries())
1361
+ for (const t of (0, escaping_1.findExportedTokens)(m, solver))
1362
+ bitsets.get(reps.get(t)).set(i);
1363
+ const exportedFunctionsByModule = /* @__PURE__ */ new Map();
1364
+ for (const rep of sccs.reverse()) {
1365
+ const bs = bitsets.get(rep);
1366
+ (0, node_assert_1.default)(!bs.isEmpty());
1367
+ for (const t of sets.get(rep)) {
1368
+ const es = graph.get(t);
1369
+ if (es?.size)
1370
+ for (const t2 of es) {
1371
+ const rep2 = reps.get(t2);
1372
+ if (rep !== rep2)
1373
+ bitsets.get(rep2).unionUpdate(bs);
1374
+ }
1375
+ if (t instanceof tokens_1.FunctionToken)
1376
+ for (const i of bs)
1377
+ (0, util_1.mapArrayAdd)(modules[i], t, exportedFunctionsByModule);
1378
+ }
1379
+ }
1380
+ return exportedFunctionsByModule;
1381
+ }
1382
+ __name(getExportedFunctionsByModule, "getExportedFunctionsByModule");
1383
+ }
1384
+ });
1385
+
1046
1386
  // lib/analysis/solver.js
1047
1387
  var require_solver = __commonJS({
1048
1388
  "lib/analysis/solver.js"(exports) {
@@ -1106,6 +1446,7 @@ var require_solver = __commonJS({
1106
1446
  var memory_1 = require_memory();
1107
1447
  var diagnostics_1 = __importDefault(require_diagnostics());
1108
1448
  var ecmascript_1 = require_ecmascript();
1449
+ var lazy_1 = require_lazy();
1109
1450
  var CYCLE_ELIMINATION_MINIMUM = 100;
1110
1451
  var CYCLE_ELIMINATION_FACTOR = 1.2;
1111
1452
  var AbortedException = class extends Error {
@@ -1131,6 +1472,7 @@ var require_solver = __commonJS({
1131
1472
  postponedListenersProcessed = 0;
1132
1473
  phase;
1133
1474
  timer = new timer_1.default();
1475
+ lazyRoundsHook;
1134
1476
  constructor(abort) {
1135
1477
  this.abort = abort;
1136
1478
  }
@@ -1190,6 +1532,8 @@ var require_solver = __commonJS({
1190
1532
  }
1191
1533
  }
1192
1534
  tokenAdded(toRep, t, ws) {
1535
+ if (options_1.options.lazyCleanup && t instanceof tokens_1.AccessPathToken && t.ap instanceof accesspaths_1.LazyAccessPath && !(0, lazy_1.isLazyAPActive)(t.ap, this.globalState))
1536
+ return ws;
1193
1537
  if (logger_1.default.isDebugEnabled())
1194
1538
  logger_1.default.debug(`Added token ${t} to ${toRep}`);
1195
1539
  if (logger_1.default.isVerboseEnabled()) {
@@ -1208,14 +1552,14 @@ var require_solver = __commonJS({
1208
1552
  if (!to)
1209
1553
  return;
1210
1554
  const abstractProp = ap instanceof accesspaths_1.PropertyAccessPath && !(subap instanceof accesspaths_1.ModuleAccessPath && ap.prop === "default") && options_1.patternProperties && !options_1.patternProperties.has(ap.prop);
1211
- const ap2 = this.globalState.canonicalizeAccessPath(subap instanceof accesspaths_1.IgnoredAccessPath || subap instanceof accesspaths_1.UnknownAccessPath && (ap instanceof accesspaths_1.CallResultAccessPath || ap instanceof accesspaths_1.ComponentAccessPath || abstractProp) ? subap : abstractProp ? new accesspaths_1.PropertyAccessPath(ap.base, "?") : ap);
1555
+ const ap2 = this.globalState.canonicalizeAccessPath(subap instanceof accesspaths_1.IgnoredAccessPath || subap instanceof accesspaths_1.LazyAccessPath || subap instanceof accesspaths_1.UnknownAccessPath && (ap instanceof accesspaths_1.CallResultAccessPath || ap instanceof accesspaths_1.ComponentAccessPath || abstractProp) ? subap : abstractProp ? new accesspaths_1.PropertyAccessPath(ap.base, "?") : ap);
1212
1556
  if (logger_1.default.isDebugEnabled())
1213
1557
  logger_1.default.debug(`Adding access path ${ap2}${ap2 !== ap ? ` (${ap})` : ""} at ${to}${subap ? ` (sub-expression access path: ${subap})` : ""}`);
1214
1558
  const f = this.fragmentState;
1215
1559
  const asn = to instanceof constraintvars_1.NodeVar && (0, types_1.isAssignmentExpression)(to.node);
1216
1560
  if (!asn)
1217
1561
  this.addToken(f.a.canonicalizeToken(new tokens_1.AccessPathToken(ap2)), f.getRepresentative(to));
1218
- if (!(ap2 instanceof accesspaths_1.UnknownAccessPath || ap2 instanceof accesspaths_1.IgnoredAccessPath) && (to instanceof constraintvars_1.NodeVar || to instanceof constraintvars_1.IntermediateVar && to.label === "import")) {
1562
+ if (!(ap2 instanceof accesspaths_1.UnknownAccessPath || ap2 instanceof accesspaths_1.IgnoredAccessPath || ap2 instanceof accesspaths_1.LazyAccessPath) && (to instanceof constraintvars_1.NodeVar || to instanceof constraintvars_1.IntermediateVar && to.label === "import")) {
1219
1563
  (0, assert_1.default)(node !== void 0 && encl !== void 0);
1220
1564
  if (ap2 instanceof accesspaths_1.ModuleAccessPath)
1221
1565
  (0, util_1.mapGetMap)(f.moduleAccessPaths, ap2).set(node, encl);
@@ -1793,18 +2137,46 @@ var require_modulefinder = __commonJS({
1793
2137
  Object.defineProperty(exports, "__esModule", { value: true });
1794
2138
  exports.findModules = findModules;
1795
2139
  var types_1 = __require("@babel/types");
2140
+ var util_1 = require_util();
1796
2141
  var traverse_1 = __importDefault(__require("@babel/traverse"));
1797
2142
  var module_1 = __importDefault(__require("module"));
2143
+ var infos_1 = require_infos();
2144
+ var micromatch_1 = __importDefault(require_micromatch());
2145
+ var options_1 = require_options();
2146
+ var logger_1 = __importDefault(require_logger());
2147
+ var asthelpers_1 = require_asthelpers();
1798
2148
  function findModules(ast, f, moduleInfo) {
1799
2149
  function loadModule(mode, str, path) {
1800
- if (!module_1.default.isBuiltin(str))
1801
- f.loadModule(mode, str, path, moduleInfo);
2150
+ if (!module_1.default.isBuiltin(str)) {
2151
+ const m = f.loadModule(mode, str, path, moduleInfo);
2152
+ if (m && options_1.options.lazy) {
2153
+ const s = (0, infos_1.normalizeModuleName)(str);
2154
+ const tracked = options_1.options.trackedModules?.some((e) => micromatch_1.default.isMatch(m.getOfficialName(), e) || micromatch_1.default.isMatch(s, e));
2155
+ if (tracked) {
2156
+ if (logger_1.default.isVerboseEnabled())
2157
+ logger_1.default.verbose(`Lazy: Module ${moduleInfo} imports tracked module ${m}`);
2158
+ f.a.reachedModuleFull(moduleInfo);
2159
+ }
2160
+ }
2161
+ }
1802
2162
  }
1803
2163
  __name(loadModule, "loadModule");
2164
+ function isRequire(path) {
2165
+ if (!path.isExpression() || path.scope.getBinding("require"))
2166
+ return false;
2167
+ const aux = /* @__PURE__ */ __name((path2) => {
2168
+ path2 = (0, asthelpers_1.skipParenthesizedChildren)(path2);
2169
+ if (path2.isIdentifier({ name: "require" }))
2170
+ return true;
2171
+ return path2.isConditionalExpression() && (aux(path2.get("consequent")) || aux(path2.get("alternate")));
2172
+ }, "aux");
2173
+ return aux(path);
2174
+ }
2175
+ __name(isRequire, "isRequire");
1804
2176
  (0, traverse_1.default)(ast, {
1805
2177
  CallExpression(path) {
1806
2178
  const imp = (0, types_1.isImport)(path.node.callee);
1807
- if ((imp || (0, types_1.isIdentifier)(path.node.callee) && path.node.callee.name === "require" && !path.scope.getBinding(path.node.callee.name)) && path.node.arguments.length >= 1) {
2179
+ if ((imp || isRequire(path.get("callee"))) && path.node.arguments.length >= 1) {
1808
2180
  const arg = path.node.arguments[0];
1809
2181
  if ((0, types_1.isStringLiteral)(arg))
1810
2182
  loadModule(imp ? "module" : "commonjs", arg.value, path);
@@ -1821,104 +2193,35 @@ var require_modulefinder = __commonJS({
1821
2193
  ExportNamedDeclaration(path) {
1822
2194
  if (path.node.source)
1823
2195
  loadModule("module", path.node.source.value, path);
1824
- }
2196
+ },
2197
+ ...options_1.options.lazy ? {
2198
+ Identifier(path) {
2199
+ const { node, parent } = path;
2200
+ if (!isRequire(path) || (0, types_1.isCallExpression)(parent, { callee: node }) || ((0, types_1.isMemberExpression)(parent, { object: node }) || (0, types_1.isOptionalMemberExpression)(parent, { object: node })) && ["main", "cache", "resolve", "extensions"].includes((0, asthelpers_1.getProperty)(parent)) || (0, types_1.isUnaryExpression)(parent, { argument: node }))
2201
+ return;
2202
+ let p = path;
2203
+ while (true) {
2204
+ const pp = p.parentPath;
2205
+ if (!pp)
2206
+ return;
2207
+ if (pp.isParenthesizedExpression() || pp.isConditionalExpression({ consequent: p.node }) || pp.isConditionalExpression({ alternate: p.node }))
2208
+ p = pp;
2209
+ else if (pp.isCallExpression({ callee: p.node }) && pp.node.arguments.length >= 1)
2210
+ return;
2211
+ else
2212
+ break;
2213
+ }
2214
+ if (logger_1.default.isVerboseEnabled())
2215
+ logger_1.default.verbose(`Lazy: Aliasing 'require' at ${(0, util_1.locationToStringWithFileAndEnd)(path.node.loc)} in ${moduleInfo}`);
2216
+ f.a.reachedModuleFull(moduleInfo);
2217
+ }
2218
+ } : {}
1825
2219
  });
1826
2220
  }
1827
2221
  __name(findModules, "findModules");
1828
2222
  }
1829
2223
  });
1830
2224
 
1831
- // lib/analysis/escaping.js
1832
- var require_escaping = __commonJS({
1833
- "lib/analysis/escaping.js"(exports) {
1834
- "use strict";
1835
- var __importDefault = exports && exports.__importDefault || function(mod) {
1836
- return mod && mod.__esModule ? mod : { "default": mod };
1837
- };
1838
- Object.defineProperty(exports, "__esModule", { value: true });
1839
- exports.findEscapingObjects = findEscapingObjects;
1840
- var tokens_1 = require_tokens();
1841
- var logger_1 = __importDefault(require_logger());
1842
- var types_1 = __require("@babel/types");
1843
- var accesspaths_1 = require_accesspaths();
1844
- var ecmascript_1 = require_ecmascript();
1845
- var options_1 = require_options();
1846
- var packagejson_1 = require_packagejson();
1847
- function findEscapingObjects(ms, solver) {
1848
- const a = solver.globalState;
1849
- const f = solver.fragmentState;
1850
- const worklist = [];
1851
- const visited = /* @__PURE__ */ new Set();
1852
- const escaping = /* @__PURE__ */ new Set();
1853
- const theUnknownAccessPathToken = a.canonicalizeToken(new tokens_1.AccessPathToken(accesspaths_1.UnknownAccessPath.instance));
1854
- function addToWorklist(v) {
1855
- for (const t of v instanceof tokens_1.Token ? [v] : f.getTokens(f.getRepresentative(v)))
1856
- if ((t instanceof tokens_1.AllocationSiteToken || t instanceof tokens_1.FunctionToken || t instanceof tokens_1.NativeObjectToken && t.name === "exports") && !visited.has(t)) {
1857
- worklist.push(t);
1858
- visited.add(t);
1859
- }
1860
- }
1861
- __name(addToWorklist, "addToWorklist");
1862
- for (const m of Array.isArray(ms) ? ms : [ms])
1863
- if (m.packageInfo.isEntry && (m.getPath().includes("node_modules") || options_1.options.library)) {
1864
- const pi = a.packageJsonInfos.get(m.packageInfo.dir);
1865
- if (!pi?.exports || (0, packagejson_1.isInExports)(`./${m.relativePath}`, pi.exports))
1866
- addToWorklist(f.varProducer.objPropVar(a.canonicalizeToken(new tokens_1.NativeObjectToken("module", m)), "exports"));
1867
- }
1868
- const w2 = [];
1869
- while (worklist.length !== 0) {
1870
- const t = worklist.pop();
1871
- if (t instanceof tokens_1.FunctionToken)
1872
- w2.push(t);
1873
- else if (t instanceof tokens_1.ObjectToken || t instanceof tokens_1.NativeObjectToken && t.name === "exports") {
1874
- for (const p of f.objectProperties.get(t) ?? [])
1875
- if (!(0, ecmascript_1.isInternalProperty)(p))
1876
- addToWorklist(f.varProducer.objPropVar(t, p));
1877
- }
1878
- }
1879
- visited.clear();
1880
- for (const t of w2) {
1881
- visited.add(t);
1882
- worklist.push(t);
1883
- }
1884
- for (const v of f.maybeEscaping)
1885
- addToWorklist(v);
1886
- f.maybeEscaping.clear();
1887
- while (worklist.length !== 0) {
1888
- const t = worklist.pop();
1889
- if (t instanceof tokens_1.ObjectToken) {
1890
- if (logger_1.default.isDebugEnabled())
1891
- logger_1.default.debug(`Escaping object: ${t}`);
1892
- escaping.add(t);
1893
- }
1894
- if (t instanceof tokens_1.FunctionToken) {
1895
- addToWorklist(f.varProducer.returnVar(t.fun));
1896
- for (const param of t.fun.params)
1897
- if ((0, types_1.isIdentifier)(param))
1898
- solver.addToken(theUnknownAccessPathToken, f.getRepresentative(f.varProducer.nodeVar(param)));
1899
- solver.addToken(theUnknownAccessPathToken, f.getRepresentative(f.varProducer.thisVar(t.fun)));
1900
- }
1901
- for (const p of f.objectProperties.get(t) ?? [])
1902
- if (!(0, ecmascript_1.isInternalProperty)(p)) {
1903
- const w = f.varProducer.objPropVar(t, p);
1904
- addToWorklist(w);
1905
- solver.addToken(theUnknownAccessPathToken, f.getRepresentative(w));
1906
- }
1907
- }
1908
- if (logger_1.default.isVerboseEnabled()) {
1909
- const objecttokens = /* @__PURE__ */ new Set();
1910
- for (const [, ts] of f.getAllVarsAndTokens())
1911
- for (const t of ts)
1912
- if (t instanceof tokens_1.ObjectToken)
1913
- objecttokens.add(t);
1914
- logger_1.default.verbose(`Escaping objects: ${escaping.size}/${objecttokens.size}`);
1915
- }
1916
- return escaping;
1917
- }
1918
- __name(findEscapingObjects, "findEscapingObjects");
1919
- }
1920
- });
1921
-
1922
2225
  // lib/natives/nativebuilder.js
1923
2226
  var require_nativebuilder = __commonJS({
1924
2227
  "lib/natives/nativebuilder.js"(exports) {
@@ -2833,6 +3136,14 @@ var require_operations = __commonJS({
2833
3136
  } else if ((0, types_1.isSpreadElement)(args[i]))
2834
3137
  f.warnUnsupported(args[i], "SpreadElement in arguments to external function");
2835
3138
  }
3139
+ if (t.ap instanceof accesspaths_1.LazyAccessPath) {
3140
+ const s = (0, util_1.mapGetSet)(this.solver.fragmentState.lazyEscaping, t.ap);
3141
+ for (let i = 0; i < argVars.length; i++) {
3142
+ const argVar = argVars[i];
3143
+ if (argVar)
3144
+ s.add(argVar);
3145
+ }
3146
+ }
2836
3147
  }
2837
3148
  if (isNew && t instanceof tokens_1.FunctionToken) {
2838
3149
  const q = this.newObjectToken(t.fun);
@@ -3036,6 +3347,8 @@ var require_operations = __commonJS({
3036
3347
  } else if (lVar && base instanceof tokens_1.AccessPathToken) {
3037
3348
  this.solver.addAccessPath(new accesspaths_1.PropertyAccessPath(lVar, prop), this.solver.varProducer.nodeVar(escapeNode), escapeNode, enclosing, base.ap);
3038
3349
  this.solver.fragmentState.registerEscapingToExternal(src, escapeNode, enclosing);
3350
+ if (src && base.ap instanceof accesspaths_1.LazyAccessPath)
3351
+ (0, util_1.mapGetSet)(this.solver.fragmentState.lazyEscaping, base.ap).add(src);
3039
3352
  }
3040
3353
  }
3041
3354
  loadModule(mode, str, resultVar, path) {
@@ -3049,18 +3362,24 @@ var require_operations = __commonJS({
3049
3362
  f.warnUnsupported(path.node, `Ignoring re-export from built-in module '${str}'`);
3050
3363
  } else {
3051
3364
  m = f.loadModule(mode, str, path, this.moduleInfo);
3052
- if (m instanceof infos_1.ModuleInfo && m.isIncluded) {
3053
- const fp = (0, asthelpers_1.getEnclosingFunction)(path)?.node;
3054
- const from = fp ? this.a.functionInfos.get(fp) : this.moduleInfo;
3055
- f.registerRequireEdge(from, m);
3056
- if (!reexport)
3057
- this.solver.addSubsetConstraint(this.solver.varProducer.objPropVar(this.a.canonicalizeToken(new tokens_1.NativeObjectToken("module", m)), "exports"), resultVar);
3058
- }
3059
3365
  if (m) {
3366
+ if (m instanceof infos_1.ModuleInfo && m.isIncluded) {
3367
+ const fp = (0, asthelpers_1.getEnclosingFunction)(path)?.node;
3368
+ const from = fp ? this.a.functionInfos.get(fp) : this.moduleInfo;
3369
+ f.registerRequireEdge(from, m);
3370
+ if (!reexport)
3371
+ this.solver.addSubsetConstraint(this.solver.varProducer.objPropVar(this.a.canonicalizeToken(new tokens_1.NativeObjectToken("module", m)), "exports"), resultVar);
3372
+ if (options_1.options.lazy && !this.a.reachedModulesFull.has(m) && resultVar) {
3373
+ const ap = new accesspaths_1.LazyAccessPath(m, false);
3374
+ if (logger_1.default.isVerboseEnabled())
3375
+ logger_1.default.verbose(`Lazy: Injecting ${ap} at ${resultVar}`);
3376
+ this.solver.addAccessPath(ap, resultVar);
3377
+ }
3378
+ }
3060
3379
  const analyzed = m instanceof infos_1.ModuleInfo && m.isIncluded;
3061
3380
  if (!analyzed || options_1.options.vulnerabilities || options_1.options.vulnerabilitiesJson) {
3062
3381
  const s = (0, infos_1.normalizeModuleName)(str);
3063
- const tracked = options_1.options.trackedModules && options_1.options.trackedModules.find((e) => micromatch_1.default.isMatch(m.getOfficialName(), e) || micromatch_1.default.isMatch(s, e));
3382
+ const tracked = options_1.options.trackedModules && options_1.options.trackedModules.some((e) => micromatch_1.default.isMatch(m.getOfficialName(), e) || micromatch_1.default.isMatch(s, e));
3064
3383
  const ap = tracked ? new accesspaths_1.ModuleAccessPath(m, s) : accesspaths_1.IgnoredAccessPath.instance;
3065
3384
  this.solver.addAccessPath(ap, resultVar, path.node, this.a.getEnclosingFunctionOrModule(path));
3066
3385
  if ((0, types_1.isExportAllDeclaration)(path.node))
@@ -3517,7 +3836,7 @@ var require_finalization = __commonJS({
3517
3836
  }
3518
3837
  }
3519
3838
  }
3520
- solver.diagnostics.finalizationTime = finalTimer.elapsed();
3839
+ solver.diagnostics.finalizationTime += finalTimer.elapsed();
3521
3840
  }
3522
3841
  __name(finalizeCallEdges, "finalizeCallEdges");
3523
3842
  }
@@ -3863,6 +4182,9 @@ var require_analyzer = __commonJS({
3863
4182
  return result;
3864
4183
  };
3865
4184
  })();
4185
+ var __importDefault = exports && exports.__importDefault || function(mod) {
4186
+ return mod && mod.__esModule ? mod : { "default": mod };
4187
+ };
3866
4188
  Object.defineProperty(exports, "__esModule", { value: true });
3867
4189
  exports.analyzeFiles = analyzeFiles;
3868
4190
  var fs_1 = __importStar(__require("fs"));
@@ -3877,6 +4199,7 @@ var require_analyzer = __commonJS({
3877
4199
  var modulefinder_1 = require_modulefinder();
3878
4200
  var parser_1 = require_parser();
3879
4201
  var escaping_1 = require_escaping();
4202
+ var lazy_1 = require_lazy();
3880
4203
  var nativebuilder_1 = require_nativebuilder();
3881
4204
  var analysisstatereporter_1 = require_analysisstatereporter();
3882
4205
  var operations_1 = require_operations();
@@ -3888,6 +4211,10 @@ var require_analyzer = __commonJS({
3888
4211
  var patching_1 = require_patching();
3889
4212
  var diagnostics_1 = require_diagnostics2();
3890
4213
  var patchthis_1 = require_patchthis();
4214
+ var assert_1 = __importDefault(__require("assert"));
4215
+ var accesspaths_1 = require_accesspaths();
4216
+ var tokens_1 = require_tokens();
4217
+ var types_1 = __require("@babel/types");
3891
4218
  async function analyzeFiles(files, solver) {
3892
4219
  const a = solver.globalState;
3893
4220
  const d = solver.diagnostics;
@@ -3912,49 +4239,63 @@ var require_analyzer = __commonJS({
3912
4239
  logger_1.default.info(`Loading ${options_1.options.approxLoad}`);
3913
4240
  a.approx.add(JSON.parse((0, fs_1.readFileSync)(options_1.options.approxLoad, "utf-8")));
3914
4241
  }
3915
- while (a.pendingFiles.isNonEmpty()) {
3916
- while (a.pendingFiles.isNonEmpty()) {
3917
- for (const file of a.pendingFiles) {
3918
- const moduleInfo = a.getModuleInfo(file);
3919
- d.modules++;
3920
- d.packages = a.packageInfos.size;
3921
- if (!options_1.options.modulesOnly && options_1.options.printProgress)
3922
- logger_1.default.info(`Analyzing module ${moduleInfo} (${d.modules})`);
3923
- const str = fs_1.default.readFileSync(file, "utf8");
3924
- (0, logger_1.writeStdOutIfActive)(`Parsing ${file} (${Math.ceil(str.length / 1024)}KB)...`);
3925
- const ast = (0, parser_1.parseAndDesugar)(str, file, solver.fragmentState);
3926
- if (!ast) {
3927
- a.filesWithParseErrors.push(file);
3928
- continue;
3929
- }
3930
- moduleInfo.loc = ast.program.loc;
3931
- a.modules.set(moduleInfo, ast.program);
3932
- a.filesAnalyzed.push(file);
3933
- const fileSize = (0, fs_1.statSync)(file).size;
3934
- d.codeSize += fileSize;
3935
- if (moduleInfo.packageInfo.isEntry)
3936
- d.codeSizeMain += fileSize;
3937
- else
3938
- d.codeSizeDependencies += fileSize;
3939
- if (options_1.options.approx) {
3940
- if (a.approx.hints.moduleIndex.has(moduleInfo.toString())) {
3941
- if (logger_1.default.isVerboseEnabled())
3942
- logger_1.default.verbose(`Skipping approximate interpretation of module ${file}, already visited`);
4242
+ while (a.pendingFiles.isNonEmpty() || a.pendingModulesFull.isNonEmpty()) {
4243
+ d.analyzerRounds++;
4244
+ const prevModulesFull = d.modulesFull;
4245
+ while (a.pendingFiles.isNonEmpty() || a.pendingModulesFull.isNonEmpty()) {
4246
+ while (a.pendingFiles.isNonEmpty() || a.pendingModulesFull.isNonEmpty()) {
4247
+ for (const file of a.pendingFiles) {
4248
+ const moduleInfo = a.getModuleInfo(file);
4249
+ d.packages = a.packageInfos.size;
4250
+ if (!options_1.options.modulesOnly && options_1.options.printProgress)
4251
+ logger_1.default.info(`Loading ${moduleInfo} (${d.modules})`);
4252
+ const str = fs_1.default.readFileSync(file, "utf8");
4253
+ (0, logger_1.writeStdOutIfActive)(`Parsing ${file} (${Math.ceil(str.length / 1024)}KB)...`);
4254
+ const ast = (0, parser_1.parseAndDesugar)(str, file, solver.fragmentState);
4255
+ if (!ast) {
4256
+ a.filesWithParseErrors.push(file);
4257
+ continue;
4258
+ }
4259
+ moduleInfo.loc = ast.program.loc;
4260
+ moduleInfo.ast = ast;
4261
+ a.modules.set(moduleInfo, ast.program);
4262
+ a.filesAnalyzed.push(file);
4263
+ const fileSize = (0, fs_1.statSync)(file).size;
4264
+ d.modules++;
4265
+ d.codeSize += fileSize;
4266
+ if (moduleInfo.packageInfo.isEntry)
4267
+ d.codeSizeMain += fileSize;
4268
+ else
4269
+ d.codeSizeDependencies += fileSize;
4270
+ if (options_1.options.approx) {
4271
+ if (a.approx.hints.moduleIndex.has(moduleInfo.toString())) {
4272
+ if (logger_1.default.isVerboseEnabled())
4273
+ logger_1.default.verbose(`Skipping approximate interpretation of module ${file}, already visited`);
4274
+ } else {
4275
+ (0, logger_1.writeStdOutIfActive)(`Approximate interpretation...`);
4276
+ await a.approx.execute(file);
4277
+ }
4278
+ }
4279
+ if (options_1.options.modulesOnly || options_1.options.lazy) {
4280
+ (0, modulefinder_1.findModules)(ast, solver.fragmentState, moduleInfo);
3943
4281
  } else {
3944
- (0, logger_1.writeStdOutIfActive)(`Approximate interpretation...`);
3945
- await a.approx.execute(file);
4282
+ a.reachedModuleFull(moduleInfo);
3946
4283
  }
3947
- }
3948
- if (options_1.options.modulesOnly) {
3949
- (0, modulefinder_1.findModules)(ast, solver.fragmentState, moduleInfo);
3950
4284
  if (d.modules % 16 === 0)
3951
4285
  a.timeoutTimer.checkTimeout();
3952
- } else {
4286
+ }
4287
+ for (const moduleInfo of a.pendingModulesFull) {
4288
+ const ast = moduleInfo.ast;
4289
+ (0, assert_1.default)(ast);
4290
+ d.modulesFull++;
4291
+ if (!options_1.options.modulesOnly && options_1.options.printProgress)
4292
+ logger_1.default.info(`Analyzing ${moduleInfo}`);
3953
4293
  const moduleParams = (0, extras_1.preprocessAst)(ast, moduleInfo);
3954
4294
  (0, logger_1.writeStdOutIfActive)("Traversing AST...");
3955
4295
  (0, astvisitor_1.visit)(ast, new operations_1.Operations(moduleInfo, solver, (0, nativebuilder_1.buildModuleNatives)(solver, moduleInfo, moduleParams)));
4296
+ ast.tokens = void 0;
4297
+ moduleInfo.ast = void 0;
3956
4298
  }
3957
- ast.tokens = void 0;
3958
4299
  }
3959
4300
  await solver.propagate("Analyzing");
3960
4301
  solver.updateDiagnostics();
@@ -3985,6 +4326,107 @@ var require_analyzer = __commonJS({
3985
4326
  }
3986
4327
  solver.updateDiagnostics();
3987
4328
  }
4329
+ if (options_1.options.lazy && d.modulesFull > prevModulesFull) {
4330
+ const f2 = solver.fragmentState;
4331
+ (0, logger_1.writeStdOutIfActive)("Lazy module analysis...");
4332
+ solver.lazyRoundsHook?.();
4333
+ const funs = /* @__PURE__ */ new Map();
4334
+ for (const [vs, fun] of a.vulnerabilities.patternMatch(solver.fragmentState, void 0, solver.diagnostics).values())
4335
+ (0, util_1.addAll)(vs, (0, util_1.mapGetSet)(funs, fun));
4336
+ const reachingFunctions = /* @__PURE__ */ new Set();
4337
+ if (funs.size) {
4338
+ (0, finalization_1.finalizeCallEdges)(solver);
4339
+ (0, util_1.addAll)(a.vulnerabilities.findFunctionsThatMayReachVulnerableFunctions(funs, f2).keys(), reachingFunctions);
4340
+ }
4341
+ if (logger_1.default.isVerboseEnabled())
4342
+ logger_1.default.verbose(`Lazy: Functions that may reach vulnerable functions: ${reachingFunctions.size}`);
4343
+ const isCandidate = /* @__PURE__ */ __name((m) => m.directDependents.values().some((d2) => !a.reachedModulesFull.has(d2)), "isCandidate");
4344
+ const candidateModules = new Set(a.reachedModulesFull.values().filter(isCandidate));
4345
+ if (logger_1.default.isVerboseEnabled())
4346
+ logger_1.default.verbose(`Lazy: Candidate modules: ${candidateModules.size}`);
4347
+ let reevaluateCandidates = false;
4348
+ for (const r of reachingFunctions)
4349
+ if (r instanceof infos_1.ModuleInfo && candidateModules.has(r)) {
4350
+ logger_1.default.verbose(`Lazy: top-level function ${r} may reach vulnerable function`);
4351
+ for (const d2 of r.directDependents)
4352
+ a.reachedModuleFull(d2);
4353
+ candidateModules.delete(r);
4354
+ reevaluateCandidates = true;
4355
+ }
4356
+ if (reevaluateCandidates) {
4357
+ for (const m of candidateModules)
4358
+ if (!isCandidate(m)) {
4359
+ logger_1.default.verbose(`Lazy: Candidate module ${m}'s non-analyzed dependents were all reached`);
4360
+ candidateModules.delete(m);
4361
+ }
4362
+ }
4363
+ for (const [m, ts] of (0, lazy_1.getExportedFunctionsByModule)(candidateModules, solver)) {
4364
+ const ap = new accesspaths_1.LazyAccessPath(m, true);
4365
+ for (const t of ts) {
4366
+ for (const param of t.fun.params)
4367
+ if ((0, types_1.isIdentifier)(param))
4368
+ solver.addAccessPath(ap, f2.varProducer.nodeVar(param));
4369
+ if (f2.functionsWithThis.has(t.fun))
4370
+ solver.addAccessPath(ap, f2.varProducer.thisVar(t.fun));
4371
+ if (f2.functionsWithArguments.has(t.fun))
4372
+ solver.addAccessPath(ap, f2.varProducer.argumentsVar(t.fun));
4373
+ }
4374
+ }
4375
+ await solver.propagate("Lazy loading");
4376
+ for (const m of candidateModules) {
4377
+ const ap = a.canonicalizeAccessPath(new accesspaths_1.LazyAccessPath(m, true));
4378
+ (0, lazy_1.assertLazyAPActive)(ap, a);
4379
+ const v = f2.varProducer.objPropVar(a.canonicalizeToken(new tokens_1.NativeObjectToken("module", m)), "exports");
4380
+ (0, util_1.mapGetSet)(f2.lazyEscaping, ap).add(v);
4381
+ }
4382
+ const roots = /* @__PURE__ */ new Set();
4383
+ const lazyEscapingTokens = /* @__PURE__ */ new Map();
4384
+ for (const [ap, vs] of f2.lazyEscaping) {
4385
+ if (!(0, lazy_1.isLazyAPActive)(ap, a)) {
4386
+ f2.lazyEscaping.delete(ap);
4387
+ continue;
4388
+ }
4389
+ const ts = /* @__PURE__ */ new Set();
4390
+ for (const v of vs)
4391
+ (0, util_1.addAll)(f2.getTokens(f2.getRepresentative(v)), ts);
4392
+ (0, util_1.addAll)(ts, roots);
4393
+ lazyEscapingTokens.set(ap, ts);
4394
+ }
4395
+ const [graph] = (0, lazy_1.constructTokenReachabilityGraph)(roots, solver);
4396
+ const vulnTokens = graph.keys().filter((t) => t instanceof tokens_1.AccessPathToken && !(t.ap instanceof accesspaths_1.UnknownAccessPath || t.ap instanceof accesspaths_1.IgnoredAccessPath || t.ap instanceof accesspaths_1.LazyAccessPath) || t instanceof tokens_1.FunctionToken && reachingFunctions.has(a.functionInfos.get(t.fun))).toArray();
4397
+ if (vulnTokens.length) {
4398
+ const rGraph = (0, util_1.getReverseGraph)(graph);
4399
+ const worklist = vulnTokens;
4400
+ const visited = new Set(worklist);
4401
+ while (worklist.length) {
4402
+ const t = worklist.pop();
4403
+ const es = rGraph.get(t);
4404
+ if (es) {
4405
+ for (const t2 of es)
4406
+ if (!visited.has(t2)) {
4407
+ visited.add(t2);
4408
+ worklist.push(t2);
4409
+ }
4410
+ }
4411
+ }
4412
+ for (const [ap, ts] of lazyEscapingTokens)
4413
+ for (const t of ts)
4414
+ if (visited.has(t)) {
4415
+ if (ap.dependents) {
4416
+ if (logger_1.default.isVerboseEnabled())
4417
+ logger_1.default.verbose(`Lazy: Including ${ap.moduleInfo.directDependents.size} dependents of ${ap.moduleInfo}`);
4418
+ for (const d2 of ap.moduleInfo.directDependents)
4419
+ a.reachedModuleFull(d2);
4420
+ } else {
4421
+ if (logger_1.default.isVerboseEnabled())
4422
+ logger_1.default.verbose(`Lazy: Including ${ap.moduleInfo}`);
4423
+ a.reachedModuleFull(ap.moduleInfo);
4424
+ }
4425
+ break;
4426
+ }
4427
+ }
4428
+ solver.updateDiagnostics();
4429
+ }
3988
4430
  }
3989
4431
  }
3990
4432
  const f = solver.fragmentState;
@@ -4030,7 +4472,7 @@ var require_analyzer = __commonJS({
4030
4472
  d.functionsWithZeroCallers = r.getZeroCallerFunctions().size;
4031
4473
  d.reachableFunctions = Array.from(r.getReachableModulesAndFunctions(r.getEntryModules())).filter((r2) => r2 instanceof infos_1.FunctionInfo).length;
4032
4474
  if (logger_1.default.isInfoEnabled()) {
4033
- logger_1.default.info(`Analyzed packages: ${d.packages}, modules: ${d.modules}, functions: ${a.functionInfos.size}, code size main: ${Math.ceil(d.codeSizeMain / 1024)}KB, dependencies: ${Math.ceil(d.codeSizeDependencies / 1024)}KB`);
4475
+ logger_1.default.info(`Analyzed packages: ${d.packages}, modules: ${d.modules} (full: ${d.modulesFull}), functions: ${a.functionInfos.size}, code size main: ${Math.ceil(d.codeSizeMain / 1024)}KB, dependencies: ${Math.ceil(d.codeSizeDependencies / 1024)}KB`);
4034
4476
  logger_1.default.info(`Call edges function->function: ${d.functionToFunctionEdges}, call->function: ${d.callToFunctionEdges}`);
4035
4477
  const total = d.totalCallSites, zeroOne = d.callsWithNoCallee + d.callsWithUniqueCallee, nativeExternal = d.nativeOnlyCalls + d.externalOnlyCalls + d.nativeOrExternalCalls;
4036
4478
  if (total > 0)
@@ -8148,7 +8590,7 @@ var require_patternmatcher = __commonJS({
8148
8590
  const f = this.fragmentState;
8149
8591
  for (const [v, m] of f.maybeEscapingToExternal)
8150
8592
  for (const t of f.getTokens(f.getRepresentative(v)))
8151
- if (t instanceof tokens_1.AccessPathToken)
8593
+ if (t instanceof tokens_1.AccessPathToken && !(t.ap instanceof accesspaths_1.LazyAccessPath))
8152
8594
  for (const [n, encl] of m)
8153
8595
  (0, util_1.mapGetMap)(this.escapingToExternal, t.ap).set(n, encl);
8154
8596
  }
@@ -11982,7 +12424,7 @@ var require_paths = __commonJS({
11982
12424
  }
11983
12425
  __name(isPathExistInArray, "isPathExistInArray");
11984
12426
  function removeBestCandidate(candidates) {
11985
- return candidates.sort((a, b) => b.totalCost - a.totalCost).pop();
12427
+ return candidates.sort((a, b) => a.totalCost - b.totalCost).shift();
11986
12428
  }
11987
12429
  __name(removeBestCandidate, "removeBestCandidate");
11988
12430
  function dijkstra(g, source, removed) {
@@ -12572,8 +13014,9 @@ ${p} (${(0, vulnerabilities_1.getVulnerabilityId)(v)})`);
12572
13014
  target.edges.push({ to, weight: 1 });
12573
13015
  g.nodes.push(to);
12574
13016
  g.targets = [to];
13017
+ const orderedSources = g.sources.toSorted((a, b) => (0, util_1.locationToStringWithFileAndEnd)(a.node?.loc, true).localeCompare((0, util_1.locationToStringWithFileAndEnd)(b.node?.loc, true)));
12575
13018
  let i = 0;
12576
- for (const source of g.sources) {
13019
+ for (const source of orderedSources) {
12577
13020
  if (maxSources !== void 0 && i++ > maxSources)
12578
13021
  break;
12579
13022
  const t = logger_1.default.isVerboseEnabled() && new timer_1.default();
@@ -12732,17 +13175,9 @@ var require_apiexported = __commonJS({
12732
13175
  return void 0;
12733
13176
  }
12734
13177
  __name(findFunctionAtLocation, "findFunctionAtLocation");
12735
- function getReverseGraph(g) {
12736
- const r = /* @__PURE__ */ new Map();
12737
- for (const [from, tos] of g)
12738
- for (const to of tos)
12739
- (0, util_1.mapGetSet)(r, to).add(from);
12740
- return r;
12741
- }
12742
- __name(getReverseGraph, "getReverseGraph");
12743
13178
  function findReachingFunctions(f, fun) {
12744
- const callers = getReverseGraph(f.functionToFunction);
12745
- const requires = getReverseGraph(f.requireGraph);
13179
+ const callers = (0, util_1.getReverseGraph)(f.functionToFunction);
13180
+ const requires = (0, util_1.getReverseGraph)(f.requireGraph);
12746
13181
  const r = /* @__PURE__ */ new Map();
12747
13182
  const w = /* @__PURE__ */ new Set();
12748
13183
  r.set(fun, /* @__PURE__ */ new Set());
@@ -12978,9 +13413,10 @@ var require_main = __commonJS({
12978
13413
  var apiexported_1 = require_apiexported();
12979
13414
  var merge_1 = require_merge();
12980
13415
  var processmanager_1 = require_processmanager();
13416
+ var assert_1 = __importDefault(__require("assert"));
12981
13417
  var semver_1 = __importDefault(require_semver2());
12982
13418
  var ENGINES_NODE = require_package()?.engines?.node;
12983
- commander_1.program.name("jelly").version(options_1.VERSION).addHelpText("before", options_1.COPYRIGHT).option("-b, --basedir <directory>", "base directory for files to analyze (default: auto-detect)").option("-f, --logfile <file>", "log to file (default: log to stdout)").option("-l, --loglevel <level>", "log level (debug/verbose/info/warn/error)", "info").option("-i, --timeout <seconds>", "limit analysis time").option("-a, --dataflow-html <file>", "save data-flow graph as HTML file").option("-m, --callgraph-html <file>", "save call graph as HTML file").option("-j, --callgraph-json <file>", "save call graph as JSON file").option("-s, --soundness <file>", "compare with dynamic call graph").option("-n, --graal-home <directory>", "home of graal-nodejs (default: $GRAAL_HOME)").option("-d, --dynamic <file>", "generate call graph dynamically, no static analysis").option("--approx", "enable approximate interpretation").option("--approx-only <file>", "perform approximate interpretation, no static analysis").option("--approx-load <file>", "use pre-computed approximate interpretation results").option("-p, --patterns <file...>", "files containing API usage patterns to detect").option("-v, --vulnerabilities <file>", "report vulnerability matches").option("--vulnerabilities-json <json>", "report vulnerability matches (patterns given as JSON string)").option("--include-packages <package...>", "include only dependencies in this list").option("--exclude-packages <package...>", "exclude dependencies in this list").option("--ignore-dependencies", "don't include dependencies in analysis").option("--ignore-unresolved", "don't report errors about unresolved modules").option("--npm-test <dir>", "run 'npm test' instead of 'node' (use with -d)").option("--callgraph", "report call graph").option("--tokens-json <file>", "save tokens for constraint variables as JSON file").option("--tokens", "report tokens for constraint variables").option("--largest", "report largest token sets and subset relations").option("--no-cycle-elimination", "disable cycle elimination").option("--no-natives", "disable nonessential models of native libraries").option("--test-graal", "test graal-nodejs (use with -d)").option("--no-print-progress", "don't print analysis progress information").option("--no-tty", "don't print solver progress for TTY").option("--warnings-unsupported", "print warnings about unsupported features").option("--gc", "enable garbage collection for more accurate memory usage reporting").option("--typescript", "enable TypeScript type inference (use with -p)").option("--api-usage", "report API usage of external packages (implies --ignore-dependencies)").option("--api-exported", "report API of modules").option("--find-access-paths <location>", "find access paths for source location (file:line)").option("--higher-order-functions", "report higher-order functions").option("--zeros", "report calls with zero callees and functions with zero callers").option("--exclude-entries <glob...>", "files to exclude when specifying entry directories").option("--tracked-modules <glob...>", "modules to track usage of (default: auto-detect)").option("--external-matches", "enable pattern matches from external code").option("--no-callgraph-implicit", "omit implicit calls in call graph").option("--no-callgraph-native", "omit native calls in call graph").option("--no-callgraph-require", "omit module loading in call graph").option("--no-callgraph-external", "omit heuristic external callbacks in call graph").option("--diagnostics", "report internal analysis diagnostics").option("--diagnostics-json <file>", "save analysis diagnostics in JSON file").option("--variable-kinds", "report constraint variable kinds").option("--max-waves <number>", "limit number of fixpoint waves").option("--max-indirections <number>", "limit number of function call and property write indirections").option("--full-indirection-bounding", "enable indirection bounding for method calls and property reads (use with --max-indirections)").option("--typescript-library-usage <file>", "save TypeScript library usage in JSON file, no analysis").option("--modules-only", "report reachable packages and modules only, no analysis").option("--compare-callgraphs", "compare two call graphs given as JSON files, no analysis").option("--reachability", "compare call graph reachability (use with -s or --compare-callgraphs)").option("--library", "assume program is a library (default: true if in node_modules)").option("--skip-tests", "skip files that look like tests").option("--no-patch-escaping", "disable patching using escape analysis").option("--patch-dynamics", "enable dynamic property access patching heuristic").option("--patch-method-calls", "enable method call patching heuristic").option("--no-patch-this", "disable 'this' patching heuristic").option("--read-neighbors", "enable package neighbor heuristic").option("--proto", "enable model of assignments to the __proto__ property").option("--obj-spread", "enable model of spread syntax for object literals ({...obj})").option("--native-overwrites", "allow overwriting of native object properties").option("--ignore-imprecise-native-calls", "ignore imprecise native calls").option("--matches-json <file>", "save vulnerability pattern matches in JSON file").option("--reachable-json <file>", "save reachable packages and modules in JSON file").option("--callstacks-json <file>", "save vulnerability call stacks in JSON file").option("--vulnerabilities-full", "full report of vulnerabilities").option("--modules-json <file>", "save modules dependencies in JSON file").usage("[options] [files]").addHelpText("after", `
13419
+ commander_1.program.name("jelly").version(options_1.VERSION).addHelpText("before", options_1.COPYRIGHT).option("-b, --basedir <directory>", "base directory for files to analyze (default: auto-detect)").option("-f, --logfile <file>", "log to file (default: log to stdout)").option("-l, --loglevel <level>", "log level (debug/verbose/info/warn/error)", "info").option("-i, --timeout <seconds>", "limit analysis time").option("-a, --dataflow-html <file>", "save data-flow graph as HTML file").option("-m, --callgraph-html <file>", "save call graph as HTML file").option("-j, --callgraph-json <file>", "save call graph as JSON file").option("-s, --soundness <file>", "compare with dynamic call graph").option("-n, --graal-home <directory>", "home of graal-nodejs (default: $GRAAL_HOME)").option("-d, --dynamic <file>", "generate call graph dynamically, no static analysis").option("--approx", "enable approximate interpretation").option("--approx-only <file>", "perform approximate interpretation, no static analysis").option("--approx-load <file>", "use pre-computed approximate interpretation results").option("-p, --patterns <file...>", "files containing API usage patterns to detect").option("-v, --vulnerabilities <file>", "report vulnerability matches").option("--vulnerabilities-json <json>", "report vulnerability matches (patterns given as JSON string)").option("--include-packages <package...>", "include only dependencies in this list").option("--exclude-packages <package...>", "exclude dependencies in this list").option("--ignore-dependencies", "don't include dependencies in analysis").option("--ignore-unresolved", "don't report errors about unresolved modules").option("--npm-test <dir>", "run 'npm test' instead of 'node' (use with -d)").option("--callgraph", "report call graph").option("--tokens-json <file>", "save tokens for constraint variables as JSON file").option("--tokens", "report tokens for constraint variables").option("--largest", "report largest token sets and subset relations").option("--no-cycle-elimination", "disable cycle elimination").option("--no-natives", "disable nonessential models of native libraries").option("--test-graal", "test graal-nodejs (use with -d)").option("--no-print-progress", "don't print analysis progress information").option("--no-tty", "don't print solver progress for TTY").option("--warnings-unsupported", "print warnings about unsupported features").option("--gc", "enable garbage collection for more accurate memory usage reporting").option("--typescript", "enable TypeScript type inference (use with -p)").option("--api-usage", "report API usage of external packages (implies --ignore-dependencies)").option("--api-exported", "report API of modules").option("--find-access-paths <location>", "find access paths for source location (file:line)").option("--higher-order-functions", "report higher-order functions").option("--zeros", "report calls with zero callees and functions with zero callers").option("--exclude-entries <glob...>", "files to exclude when specifying entry directories").option("--tracked-modules <glob...>", "modules to track usage of (default: auto-detect)").option("--external-matches", "enable pattern matches from external code").option("--no-callgraph-implicit", "omit implicit calls in call graph").option("--no-callgraph-native", "omit native calls in call graph").option("--no-callgraph-require", "omit module loading in call graph").option("--no-callgraph-external", "omit heuristic external callbacks in call graph").option("--diagnostics", "report internal analysis diagnostics").option("--diagnostics-json <file>", "save analysis diagnostics in JSON file").option("--variable-kinds", "report constraint variable kinds").option("--max-waves <number>", "limit number of fixpoint waves").option("--max-indirections <number>", "limit number of function call and property write indirections").option("--full-indirection-bounding", "enable indirection bounding for method calls and property reads (use with --max-indirections)").option("--typescript-library-usage <file>", "save TypeScript library usage in JSON file, no analysis").option("--modules-only", "report reachable packages and modules only, no analysis").option("--compare-callgraphs", "compare two call graphs given as JSON files, no analysis").option("--reachability", "compare call graph reachability (use with -s or --compare-callgraphs)").option("--library", "assume program is a library (default: true if in node_modules)").option("--skip-tests", "skip files that look like tests").option("--no-patch-escaping", "disable patching using escape analysis").option("--patch-dynamics", "enable dynamic property access patching heuristic").option("--patch-method-calls", "enable method call patching heuristic").option("--no-patch-this", "disable 'this' patching heuristic").option("--proto", "enable model of assignments to the __proto__ property").option("--obj-spread", "enable model of spread syntax for object literals ({...obj})").option("--native-overwrites", "allow overwriting of native object properties").option("--ignore-imprecise-native-calls", "ignore imprecise native calls").option("--matches-json <file>", "save vulnerability pattern matches in JSON file").option("--reachable-json <file>", "save reachable packages and modules in JSON file").option("--callstacks-json <file>", "save vulnerability call stacks in JSON file").option("--vulnerabilities-full", "full report of vulnerabilities").option("--modules-json <file>", "save modules dependencies in JSON file").option("--lazy", "lazy analysis of modules").option("--lazy-cleanup", "lazily clean up redundant tokens for lazy module analysis").usage("[options] [files]").addHelpText("after", `
12984
13420
  All modules reachable by require/import from the given files are included in the analysis
12985
13421
  (except when using --ignore-dependencies, --include-packages or --exclude-packages).
12986
13422
  If specifying directories instead of files, the files in the directories and their
@@ -13170,7 +13606,7 @@ Memory limit is ${(0, memory_1.getMemoryLimit)()}MB.${options_1.PKG ? "" : " Cha
13170
13606
  }
13171
13607
  if (options_1.options.vulnerabilities || options_1.options.vulnerabilitiesJson) {
13172
13608
  if (options_1.options.vulnerabilities)
13173
- logger_1.default.info(`Loading vulnerability patterns from ${options_1.options.vulnerabilities}`);
13609
+ logger_1.default.info(`Loading vulnerability descriptors from ${options_1.options.vulnerabilities}`);
13174
13610
  vulnerabilityDetector = new vulnerabilitydetector_1.VulnerabilityDetector(JSON.parse(options_1.options.vulnerabilities ? (0, fs_1.readFileSync)(options_1.options.vulnerabilities, "utf8") : options_1.options.vulnerabilitiesJson));
13175
13611
  const ps = vulnerabilityDetector.getPatterns();
13176
13612
  (0, util_1.addAll)((0, patternloader_1.getGlobs)(ps), globs = globs ?? /* @__PURE__ */ new Set());
@@ -13178,6 +13614,12 @@ Memory limit is ${(0, memory_1.getMemoryLimit)()}MB.${options_1.PKG ? "" : " Cha
13178
13614
  }
13179
13615
  (0, options_1.setDefaultTrackedModules)(globs);
13180
13616
  (0, options_1.setPatternProperties)(options_1.options.apiUsage ? void 0 : props ?? /* @__PURE__ */ new Set());
13617
+ if (options_1.options.lazy && !options_1.options.vulnerabilities && !options_1.options.vulnerabilitiesJson) {
13618
+ logger_1.default.error("Error: --lazy enabled but no vulnerabilities (use --vulnerabilities or --vulnerabilities-JSON)");
13619
+ process.exitCode = -1;
13620
+ return;
13621
+ }
13622
+ (0, assert_1.default)(!options_1.options.lazy || options_1.options.trackedModules);
13181
13623
  const solver = new solver_1.default();
13182
13624
  const a = solver.globalState;
13183
13625
  a.vulnerabilities = vulnerabilityDetector;