circle-ir 3.9.10 → 3.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (28) hide show
  1. package/dist/analysis/passes/cleanup-verify-pass.d.ts +28 -0
  2. package/dist/analysis/passes/cleanup-verify-pass.js +130 -0
  3. package/dist/analysis/passes/cleanup-verify-pass.js.map +1 -0
  4. package/dist/analysis/passes/missing-guard-dom-pass.d.ts +25 -0
  5. package/dist/analysis/passes/missing-guard-dom-pass.js +99 -0
  6. package/dist/analysis/passes/missing-guard-dom-pass.js.map +1 -0
  7. package/dist/analysis/passes/missing-override-pass.d.ts +27 -0
  8. package/dist/analysis/passes/missing-override-pass.js +110 -0
  9. package/dist/analysis/passes/missing-override-pass.js.map +1 -0
  10. package/dist/analysis/passes/sink-filter-pass.js +81 -8
  11. package/dist/analysis/passes/sink-filter-pass.js.map +1 -1
  12. package/dist/analysis/passes/taint-matcher-pass.js +6 -1
  13. package/dist/analysis/passes/taint-matcher-pass.js.map +1 -1
  14. package/dist/analysis/passes/taint-propagation-pass.js +2 -3
  15. package/dist/analysis/passes/taint-propagation-pass.js.map +1 -1
  16. package/dist/analysis/passes/unused-interface-method-pass.d.ts +27 -0
  17. package/dist/analysis/passes/unused-interface-method-pass.js +62 -0
  18. package/dist/analysis/passes/unused-interface-method-pass.js.map +1 -0
  19. package/dist/analysis/taint-matcher.d.ts +2 -1
  20. package/dist/analysis/taint-matcher.js +9 -5
  21. package/dist/analysis/taint-matcher.js.map +1 -1
  22. package/dist/analyzer.d.ts +5 -1
  23. package/dist/analyzer.js +13 -1
  24. package/dist/analyzer.js.map +1 -1
  25. package/dist/browser/circle-ir.js +1029 -16
  26. package/dist/core/circle-ir-core.cjs +8 -5
  27. package/dist/core/circle-ir-core.js +8 -5
  28. package/package.json +1 -1
@@ -10257,9 +10257,9 @@ var PYTHON_TAINTED_PATTERNS = [
10257
10257
  { pattern: /\brequest\.query_params\b/, sourceType: "http_param" },
10258
10258
  { pattern: /\brequest\.path_params\b/, sourceType: "http_param" }
10259
10259
  ];
10260
- function analyzeTaint(calls, types, config = getDefaultConfig()) {
10260
+ function analyzeTaint(calls, types, config = getDefaultConfig(), typeHierarchy) {
10261
10261
  const sources = findSources(calls, types, config.sources);
10262
- const sinks = findSinks(calls, config.sinks);
10262
+ const sinks = findSinks(calls, config.sinks, typeHierarchy);
10263
10263
  const sanitizers = findSanitizers(calls, types, config.sanitizers);
10264
10264
  return { sources, sinks, sanitizers };
10265
10265
  }
@@ -10465,11 +10465,11 @@ function isParameterizedQueryCall(call, pattern) {
10465
10465
  }
10466
10466
  return false;
10467
10467
  }
10468
- function findSinks(calls, patterns) {
10468
+ function findSinks(calls, patterns, typeHierarchy) {
10469
10469
  const sinkMap = /* @__PURE__ */ new Map();
10470
10470
  for (const call of calls) {
10471
10471
  for (const pattern of patterns) {
10472
- if (matchesSinkPattern(call, pattern)) {
10472
+ if (matchesSinkPattern(call, pattern, typeHierarchy)) {
10473
10473
  if (isParameterizedQueryCall(call, pattern)) {
10474
10474
  continue;
10475
10475
  }
@@ -10561,7 +10561,7 @@ function isJavaScriptTaintedArgument(argExpression, sourcePatterns) {
10561
10561
  }
10562
10562
  return { isTainted: false, sourceType: null };
10563
10563
  }
10564
- function matchesSinkPattern(call, pattern) {
10564
+ function matchesSinkPattern(call, pattern, typeHierarchy) {
10565
10565
  const callMethodName = call.method_name;
10566
10566
  const patternMethod = pattern.method;
10567
10567
  let methodMatches = callMethodName === patternMethod;
@@ -10577,6 +10577,9 @@ function matchesSinkPattern(call, pattern) {
10577
10577
  return true;
10578
10578
  }
10579
10579
  if (call.receiver && !receiverMightBeClass(call.receiver, pattern.class)) {
10580
+ if (typeHierarchy && typeHierarchy.couldBeType(call.receiver, pattern.class)) {
10581
+ return true;
10582
+ }
10580
10583
  return false;
10581
10584
  }
10582
10585
  if (!call.receiver) {
@@ -11279,6 +11282,633 @@ var CodeGraph = class {
11279
11282
  }
11280
11283
  };
11281
11284
 
11285
+ // src/resolution/type-hierarchy.ts
11286
+ var TypeHierarchyResolver = class {
11287
+ // All known types by FQN
11288
+ types = /* @__PURE__ */ new Map();
11289
+ // Simple name to FQN mapping (for resolution)
11290
+ nameToFqn = /* @__PURE__ */ new Map();
11291
+ // Subtype relationships: parent FQN -> child FQNs
11292
+ subtypes = /* @__PURE__ */ new Map();
11293
+ // Implementation relationships: interface FQN -> implementing class FQNs
11294
+ implementations = /* @__PURE__ */ new Map();
11295
+ /**
11296
+ * Add types from a CircleIR analysis result
11297
+ */
11298
+ addFromIR(ir, filePath) {
11299
+ for (const type of ir.types) {
11300
+ this.addType(type, filePath, ir.meta.package || null);
11301
+ }
11302
+ }
11303
+ /**
11304
+ * Add a single type to the hierarchy
11305
+ */
11306
+ addType(type, filePath, defaultPackage = null) {
11307
+ const pkg = type.package || defaultPackage || "";
11308
+ const fqn = pkg ? `${pkg}.${type.name}` : type.name;
11309
+ const node = {
11310
+ name: type.name,
11311
+ fqn,
11312
+ kind: type.kind,
11313
+ extends: type.extends,
11314
+ implements: type.implements,
11315
+ extendsInterfaces: type.kind === "interface" ? type.implements : [],
11316
+ file: filePath,
11317
+ line: type.start_line
11318
+ };
11319
+ this.types.set(fqn, node);
11320
+ if (!this.nameToFqn.has(type.name)) {
11321
+ this.nameToFqn.set(type.name, /* @__PURE__ */ new Set());
11322
+ }
11323
+ this.nameToFqn.get(type.name).add(fqn);
11324
+ if (type.kind === "class" || type.kind === "enum") {
11325
+ if (type.extends) {
11326
+ const parentFqn = this.resolveTypeName(type.extends, pkg);
11327
+ if (!this.subtypes.has(parentFqn)) {
11328
+ this.subtypes.set(parentFqn, /* @__PURE__ */ new Set());
11329
+ }
11330
+ this.subtypes.get(parentFqn).add(fqn);
11331
+ }
11332
+ for (const iface of type.implements) {
11333
+ const ifaceFqn = this.resolveTypeName(iface, pkg);
11334
+ if (!this.implementations.has(ifaceFqn)) {
11335
+ this.implementations.set(ifaceFqn, /* @__PURE__ */ new Set());
11336
+ }
11337
+ this.implementations.get(ifaceFqn).add(fqn);
11338
+ }
11339
+ } else if (type.kind === "interface") {
11340
+ for (const parentIface of type.implements) {
11341
+ const parentFqn = this.resolveTypeName(parentIface, pkg);
11342
+ if (!this.subtypes.has(parentFqn)) {
11343
+ this.subtypes.set(parentFqn, /* @__PURE__ */ new Set());
11344
+ }
11345
+ this.subtypes.get(parentFqn).add(fqn);
11346
+ }
11347
+ }
11348
+ }
11349
+ /**
11350
+ * Get all direct subtypes of a class
11351
+ */
11352
+ getDirectSubtypes(className) {
11353
+ const fqn = this.resolveFqn(className);
11354
+ return Array.from(this.subtypes.get(fqn) || []);
11355
+ }
11356
+ /**
11357
+ * Get all subtypes (transitive) of a class
11358
+ */
11359
+ getAllSubtypes(className) {
11360
+ const fqn = this.resolveFqn(className);
11361
+ const result = /* @__PURE__ */ new Set();
11362
+ const queue = [fqn];
11363
+ while (queue.length > 0) {
11364
+ const current = queue.shift();
11365
+ const children = this.subtypes.get(current);
11366
+ if (children) {
11367
+ for (const child of children) {
11368
+ if (!result.has(child)) {
11369
+ result.add(child);
11370
+ queue.push(child);
11371
+ }
11372
+ }
11373
+ }
11374
+ }
11375
+ return Array.from(result);
11376
+ }
11377
+ /**
11378
+ * Get all direct implementations of an interface
11379
+ */
11380
+ getDirectImplementations(interfaceName) {
11381
+ const fqn = this.resolveFqn(interfaceName);
11382
+ return Array.from(this.implementations.get(fqn) || []);
11383
+ }
11384
+ /**
11385
+ * Get all implementations (including through subinterfaces) of an interface
11386
+ */
11387
+ getAllImplementations(interfaceName) {
11388
+ const fqn = this.resolveFqn(interfaceName);
11389
+ const result = /* @__PURE__ */ new Set();
11390
+ const visited = /* @__PURE__ */ new Set();
11391
+ const queue = [fqn];
11392
+ while (queue.length > 0) {
11393
+ const current = queue.shift();
11394
+ if (visited.has(current)) continue;
11395
+ visited.add(current);
11396
+ const impls = this.implementations.get(current);
11397
+ if (impls) {
11398
+ for (const impl of impls) {
11399
+ result.add(impl);
11400
+ const subtypes = this.getAllSubtypes(impl);
11401
+ for (const subtype of subtypes) {
11402
+ result.add(subtype);
11403
+ }
11404
+ }
11405
+ }
11406
+ const subInterfaces = this.subtypes.get(current);
11407
+ if (subInterfaces) {
11408
+ for (const sub of subInterfaces) {
11409
+ queue.push(sub);
11410
+ }
11411
+ }
11412
+ }
11413
+ return Array.from(result);
11414
+ }
11415
+ /**
11416
+ * Check if a type is a subtype of another (including transitive)
11417
+ */
11418
+ isSubtypeOf(childName, parentName) {
11419
+ const childFqn = this.resolveFqn(childName);
11420
+ const parentFqn = this.resolveFqn(parentName);
11421
+ if (childFqn === parentFqn) return true;
11422
+ const allSubtypes = this.getAllSubtypes(parentFqn);
11423
+ if (allSubtypes.includes(childFqn)) return true;
11424
+ const allImpls = this.getAllImplementations(parentFqn);
11425
+ if (allImpls.includes(childFqn)) return true;
11426
+ return false;
11427
+ }
11428
+ /**
11429
+ * Check if a type implements an interface (directly or through inheritance)
11430
+ * Also handles interface-extends-interface relationships
11431
+ */
11432
+ implementsInterface(typeName, interfaceName) {
11433
+ const typeFqn = this.resolveFqn(typeName);
11434
+ const ifaceFqn = this.resolveFqn(interfaceName);
11435
+ const allImpls = this.getAllImplementations(ifaceFqn);
11436
+ if (allImpls.includes(typeFqn)) return true;
11437
+ const allSubtypes = this.getAllSubtypes(ifaceFqn);
11438
+ if (allSubtypes.includes(typeFqn)) return true;
11439
+ return false;
11440
+ }
11441
+ /**
11442
+ * Get type info by name
11443
+ */
11444
+ getType(name2) {
11445
+ const fqn = this.resolveFqn(name2);
11446
+ return this.types.get(fqn);
11447
+ }
11448
+ /**
11449
+ * Get all types matching a simple name
11450
+ */
11451
+ getTypesByName(simpleName) {
11452
+ const fqns = this.nameToFqn.get(simpleName);
11453
+ if (!fqns) return [];
11454
+ return Array.from(fqns).map((fqn) => this.types.get(fqn)).filter((t) => t !== void 0);
11455
+ }
11456
+ /**
11457
+ * Get the file where a type is defined
11458
+ */
11459
+ getTypeFile(name2) {
11460
+ const type = this.getType(name2);
11461
+ return type?.file;
11462
+ }
11463
+ /**
11464
+ * Check if a receiver type could match a target class
11465
+ * Handles: exact match, subtype, implementation, simple name match
11466
+ */
11467
+ couldBeType(receiverType, targetClass) {
11468
+ if (receiverType === targetClass) return true;
11469
+ const receiverSimple = this.getSimpleName(receiverType);
11470
+ const targetSimple = this.getSimpleName(targetClass);
11471
+ if (receiverSimple === targetSimple) return true;
11472
+ if (this.isSubtypeOf(receiverType, targetClass)) return true;
11473
+ const allSubtypes = this.getAllSubtypes(targetClass);
11474
+ const allImpls = this.getAllImplementations(targetClass);
11475
+ for (const sub of [...allSubtypes, ...allImpls]) {
11476
+ const subSimple = this.getSimpleName(sub);
11477
+ if (subSimple === receiverSimple) return true;
11478
+ }
11479
+ return false;
11480
+ }
11481
+ /**
11482
+ * Export hierarchy data in the CircleIR format
11483
+ */
11484
+ toTypeHierarchyData() {
11485
+ const classes = {};
11486
+ const interfaces = {};
11487
+ for (const [fqn, node] of this.types) {
11488
+ if (node.kind === "class" || node.kind === "enum") {
11489
+ classes[fqn] = {
11490
+ file: node.file,
11491
+ extends: node.extends ? this.resolveTypeName(node.extends, this.getPackage(fqn)) : null,
11492
+ implements: node.implements.map((i2) => this.resolveTypeName(i2, this.getPackage(fqn))),
11493
+ subclasses: this.getDirectSubtypes(fqn)
11494
+ };
11495
+ } else if (node.kind === "interface") {
11496
+ interfaces[fqn] = {
11497
+ file: node.file,
11498
+ extends: node.extendsInterfaces.map((i2) => this.resolveTypeName(i2, this.getPackage(fqn))),
11499
+ implementations: this.getDirectImplementations(fqn)
11500
+ };
11501
+ }
11502
+ }
11503
+ return { classes, interfaces };
11504
+ }
11505
+ /**
11506
+ * Get statistics about the hierarchy
11507
+ */
11508
+ getStats() {
11509
+ let classes = 0, interfaces = 0, enums = 0;
11510
+ for (const node of this.types.values()) {
11511
+ if (node.kind === "class") classes++;
11512
+ else if (node.kind === "interface") interfaces++;
11513
+ else if (node.kind === "enum") enums++;
11514
+ }
11515
+ return { totalTypes: this.types.size, classes, interfaces, enums };
11516
+ }
11517
+ /**
11518
+ * Get all types in the hierarchy
11519
+ */
11520
+ getAllTypes() {
11521
+ return Array.from(this.types.values());
11522
+ }
11523
+ /**
11524
+ * Clear all data
11525
+ */
11526
+ clear() {
11527
+ this.types.clear();
11528
+ this.nameToFqn.clear();
11529
+ this.subtypes.clear();
11530
+ this.implementations.clear();
11531
+ }
11532
+ // --- Private helpers ---
11533
+ /**
11534
+ * Resolve a type name to its FQN
11535
+ */
11536
+ resolveTypeName(name2, currentPackage) {
11537
+ if (name2.includes(".")) return name2;
11538
+ const fqns = this.nameToFqn.get(name2);
11539
+ if (fqns && fqns.size === 1) {
11540
+ return Array.from(fqns)[0];
11541
+ }
11542
+ return currentPackage ? `${currentPackage}.${name2}` : name2;
11543
+ }
11544
+ /**
11545
+ * Resolve a name (simple or FQN) to its FQN
11546
+ */
11547
+ resolveFqn(name2) {
11548
+ if (this.types.has(name2)) return name2;
11549
+ const fqns = this.nameToFqn.get(name2);
11550
+ if (fqns && fqns.size > 0) {
11551
+ return Array.from(fqns)[0];
11552
+ }
11553
+ return name2;
11554
+ }
11555
+ /**
11556
+ * Get simple name from FQN
11557
+ */
11558
+ getSimpleName(name2) {
11559
+ const lastDot = name2.lastIndexOf(".");
11560
+ return lastDot === -1 ? name2 : name2.substring(lastDot + 1);
11561
+ }
11562
+ /**
11563
+ * Get package from FQN
11564
+ */
11565
+ getPackage(fqn) {
11566
+ const lastDot = fqn.lastIndexOf(".");
11567
+ return lastDot === -1 ? "" : fqn.substring(0, lastDot);
11568
+ }
11569
+ };
11570
+ function createWithJdkTypes() {
11571
+ const resolver = new TypeHierarchyResolver();
11572
+ const jdbcTypes = [
11573
+ {
11574
+ name: "Statement",
11575
+ kind: "interface",
11576
+ package: "java.sql",
11577
+ extends: null,
11578
+ implements: [],
11579
+ annotations: [],
11580
+ methods: [],
11581
+ fields: [],
11582
+ start_line: 0,
11583
+ end_line: 0
11584
+ },
11585
+ {
11586
+ name: "PreparedStatement",
11587
+ kind: "interface",
11588
+ package: "java.sql",
11589
+ extends: null,
11590
+ implements: ["Statement"],
11591
+ annotations: [],
11592
+ methods: [],
11593
+ fields: [],
11594
+ start_line: 0,
11595
+ end_line: 0
11596
+ },
11597
+ {
11598
+ name: "CallableStatement",
11599
+ kind: "interface",
11600
+ package: "java.sql",
11601
+ extends: null,
11602
+ implements: ["PreparedStatement"],
11603
+ annotations: [],
11604
+ methods: [],
11605
+ fields: [],
11606
+ start_line: 0,
11607
+ end_line: 0
11608
+ }
11609
+ ];
11610
+ const ioTypes = [
11611
+ {
11612
+ name: "InputStream",
11613
+ kind: "class",
11614
+ package: "java.io",
11615
+ extends: null,
11616
+ implements: [],
11617
+ annotations: [],
11618
+ methods: [],
11619
+ fields: [],
11620
+ start_line: 0,
11621
+ end_line: 0
11622
+ },
11623
+ {
11624
+ name: "FileInputStream",
11625
+ kind: "class",
11626
+ package: "java.io",
11627
+ extends: "InputStream",
11628
+ implements: [],
11629
+ annotations: [],
11630
+ methods: [],
11631
+ fields: [],
11632
+ start_line: 0,
11633
+ end_line: 0
11634
+ },
11635
+ {
11636
+ name: "OutputStream",
11637
+ kind: "class",
11638
+ package: "java.io",
11639
+ extends: null,
11640
+ implements: [],
11641
+ annotations: [],
11642
+ methods: [],
11643
+ fields: [],
11644
+ start_line: 0,
11645
+ end_line: 0
11646
+ },
11647
+ {
11648
+ name: "FileOutputStream",
11649
+ kind: "class",
11650
+ package: "java.io",
11651
+ extends: "OutputStream",
11652
+ implements: [],
11653
+ annotations: [],
11654
+ methods: [],
11655
+ fields: [],
11656
+ start_line: 0,
11657
+ end_line: 0
11658
+ },
11659
+ {
11660
+ name: "Writer",
11661
+ kind: "class",
11662
+ package: "java.io",
11663
+ extends: null,
11664
+ implements: [],
11665
+ annotations: [],
11666
+ methods: [],
11667
+ fields: [],
11668
+ start_line: 0,
11669
+ end_line: 0
11670
+ },
11671
+ {
11672
+ name: "PrintWriter",
11673
+ kind: "class",
11674
+ package: "java.io",
11675
+ extends: "Writer",
11676
+ implements: [],
11677
+ annotations: [],
11678
+ methods: [],
11679
+ fields: [],
11680
+ start_line: 0,
11681
+ end_line: 0
11682
+ }
11683
+ ];
11684
+ const servletTypes = [
11685
+ {
11686
+ name: "ServletRequest",
11687
+ kind: "interface",
11688
+ package: "javax.servlet",
11689
+ extends: null,
11690
+ implements: [],
11691
+ annotations: [],
11692
+ methods: [],
11693
+ fields: [],
11694
+ start_line: 0,
11695
+ end_line: 0
11696
+ },
11697
+ {
11698
+ name: "HttpServletRequest",
11699
+ kind: "interface",
11700
+ package: "javax.servlet.http",
11701
+ extends: null,
11702
+ implements: ["javax.servlet.ServletRequest"],
11703
+ // FQN for cross-package reference
11704
+ annotations: [],
11705
+ methods: [],
11706
+ fields: [],
11707
+ start_line: 0,
11708
+ end_line: 0
11709
+ },
11710
+ {
11711
+ name: "ServletResponse",
11712
+ kind: "interface",
11713
+ package: "javax.servlet",
11714
+ extends: null,
11715
+ implements: [],
11716
+ annotations: [],
11717
+ methods: [],
11718
+ fields: [],
11719
+ start_line: 0,
11720
+ end_line: 0
11721
+ },
11722
+ {
11723
+ name: "HttpServletResponse",
11724
+ kind: "interface",
11725
+ package: "javax.servlet.http",
11726
+ extends: null,
11727
+ implements: ["javax.servlet.ServletResponse"],
11728
+ // FQN for cross-package reference
11729
+ annotations: [],
11730
+ methods: [],
11731
+ fields: [],
11732
+ start_line: 0,
11733
+ end_line: 0
11734
+ }
11735
+ ];
11736
+ for (const type of [...jdbcTypes, ...ioTypes, ...servletTypes]) {
11737
+ resolver.addType(type, "jdk", type.package);
11738
+ }
11739
+ return resolver;
11740
+ }
11741
+
11742
+ // src/graph/dominator-graph.ts
11743
+ function computeRPO(cfg, entryId) {
11744
+ const outgoing = /* @__PURE__ */ new Map();
11745
+ for (const edge of cfg.edges) {
11746
+ const list = outgoing.get(edge.from) ?? [];
11747
+ list.push(edge.to);
11748
+ outgoing.set(edge.from, list);
11749
+ }
11750
+ const visited = /* @__PURE__ */ new Set();
11751
+ const postOrder = [];
11752
+ const stack = [{ id: entryId, childIndex: 0 }];
11753
+ visited.add(entryId);
11754
+ while (stack.length > 0) {
11755
+ const top = stack[stack.length - 1];
11756
+ const children = outgoing.get(top.id) ?? [];
11757
+ let pushed = false;
11758
+ while (top.childIndex < children.length) {
11759
+ const child = children[top.childIndex++];
11760
+ if (!visited.has(child)) {
11761
+ visited.add(child);
11762
+ stack.push({ id: child, childIndex: 0 });
11763
+ pushed = true;
11764
+ break;
11765
+ }
11766
+ }
11767
+ if (!pushed) {
11768
+ postOrder.push(top.id);
11769
+ stack.pop();
11770
+ }
11771
+ }
11772
+ const rpoOrder = postOrder.reverse();
11773
+ const rpoIndex = /* @__PURE__ */ new Map();
11774
+ for (let i2 = 0; i2 < rpoOrder.length; i2++) {
11775
+ rpoIndex.set(rpoOrder[i2], i2);
11776
+ }
11777
+ return { rpoOrder, rpoIndex };
11778
+ }
11779
+ function intersect(b1, b2, idom, rpoIndex) {
11780
+ let finger1 = b1;
11781
+ let finger2 = b2;
11782
+ while (finger1 !== finger2) {
11783
+ while ((rpoIndex.get(finger1) ?? Number.MAX_SAFE_INTEGER) > (rpoIndex.get(finger2) ?? Number.MAX_SAFE_INTEGER)) {
11784
+ const parent = idom.get(finger1);
11785
+ if (parent === void 0 || parent === finger1) break;
11786
+ finger1 = parent;
11787
+ }
11788
+ while ((rpoIndex.get(finger2) ?? Number.MAX_SAFE_INTEGER) > (rpoIndex.get(finger1) ?? Number.MAX_SAFE_INTEGER)) {
11789
+ const parent = idom.get(finger2);
11790
+ if (parent === void 0 || parent === finger2) break;
11791
+ finger2 = parent;
11792
+ }
11793
+ if (finger1 === finger2) break;
11794
+ const rpo1 = rpoIndex.get(finger1) ?? Number.MAX_SAFE_INTEGER;
11795
+ const rpo2 = rpoIndex.get(finger2) ?? Number.MAX_SAFE_INTEGER;
11796
+ if (rpo1 === rpo2 && finger1 !== finger2) break;
11797
+ }
11798
+ return finger1;
11799
+ }
11800
+ function computeIdom(cfg, rpoOrder, rpoIndex, entryId) {
11801
+ const incoming = /* @__PURE__ */ new Map();
11802
+ for (const edge of cfg.edges) {
11803
+ const list = incoming.get(edge.to) ?? [];
11804
+ list.push(edge.from);
11805
+ incoming.set(edge.to, list);
11806
+ }
11807
+ const idom = /* @__PURE__ */ new Map();
11808
+ idom.set(entryId, entryId);
11809
+ let changed = true;
11810
+ while (changed) {
11811
+ changed = false;
11812
+ for (let i2 = 1; i2 < rpoOrder.length; i2++) {
11813
+ const b = rpoOrder[i2];
11814
+ const preds = incoming.get(b) ?? [];
11815
+ let newIdom;
11816
+ for (const p of preds) {
11817
+ if (idom.has(p)) {
11818
+ newIdom = p;
11819
+ break;
11820
+ }
11821
+ }
11822
+ if (newIdom === void 0) continue;
11823
+ for (const p of preds) {
11824
+ if (p === newIdom) continue;
11825
+ if (idom.has(p)) {
11826
+ newIdom = intersect(p, newIdom, idom, rpoIndex);
11827
+ }
11828
+ }
11829
+ if (idom.get(b) !== newIdom) {
11830
+ idom.set(b, newIdom);
11831
+ changed = true;
11832
+ }
11833
+ }
11834
+ }
11835
+ return idom;
11836
+ }
11837
+ var DominatorGraph = class {
11838
+ idom;
11839
+ rpoIndex;
11840
+ entryId;
11841
+ /** Cached reverse map: blockId → all blockIds it strictly dominates. */
11842
+ _dominated = null;
11843
+ constructor(cfg, entryId) {
11844
+ if (cfg.blocks.length === 0) {
11845
+ this.entryId = entryId ?? 0;
11846
+ this.idom = /* @__PURE__ */ new Map();
11847
+ this.rpoIndex = /* @__PURE__ */ new Map();
11848
+ return;
11849
+ }
11850
+ this.entryId = entryId ?? cfg.blocks.find((b) => b.type === "entry")?.id ?? cfg.blocks.reduce((a, b) => a.id < b.id ? a : b).id;
11851
+ const { rpoOrder, rpoIndex } = computeRPO(cfg, this.entryId);
11852
+ this.rpoIndex = rpoIndex;
11853
+ this.idom = computeIdom(cfg, rpoOrder, rpoIndex, this.entryId);
11854
+ this.idom.delete(this.entryId);
11855
+ }
11856
+ /**
11857
+ * Returns true if block `a` dominates block `b`.
11858
+ * A block dominates itself (reflexive).
11859
+ */
11860
+ dominates(a, b) {
11861
+ if (a === b) return true;
11862
+ return this.strictlyDominates(a, b);
11863
+ }
11864
+ /**
11865
+ * Returns true if block `a` strictly dominates block `b` (a ≠ b and a dom b).
11866
+ */
11867
+ strictlyDominates(a, b) {
11868
+ if (a === b) return false;
11869
+ const visited = /* @__PURE__ */ new Set();
11870
+ let cur = this.idom.get(b);
11871
+ while (cur !== void 0 && !visited.has(cur)) {
11872
+ if (cur === a) return true;
11873
+ visited.add(cur);
11874
+ cur = this.idom.get(cur);
11875
+ }
11876
+ return false;
11877
+ }
11878
+ /**
11879
+ * Returns the immediate dominator of `blockId`, or null for the entry block
11880
+ * (or any block not in the dominator tree).
11881
+ */
11882
+ immediateDominator(blockId) {
11883
+ return this.idom.get(blockId) ?? null;
11884
+ }
11885
+ /**
11886
+ * Returns all block IDs strictly dominated by `blockId`.
11887
+ * (Computed lazily and cached on first call.)
11888
+ */
11889
+ dominated(blockId) {
11890
+ if (!this._dominated) {
11891
+ this._dominated = /* @__PURE__ */ new Map();
11892
+ for (const [child, parent] of this.idom.entries()) {
11893
+ const ancestors = [];
11894
+ const seen = /* @__PURE__ */ new Set();
11895
+ let cur = parent;
11896
+ while (cur !== void 0 && !seen.has(cur)) {
11897
+ seen.add(cur);
11898
+ ancestors.push(cur);
11899
+ cur = this.idom.get(cur);
11900
+ }
11901
+ for (const anc of ancestors) {
11902
+ const list = this._dominated.get(anc) ?? [];
11903
+ list.push(child);
11904
+ this._dominated.set(anc, list);
11905
+ }
11906
+ }
11907
+ }
11908
+ return this._dominated.get(blockId) ?? [];
11909
+ }
11910
+ };
11911
+
11282
11912
  // src/graph/exception-flow-graph.ts
11283
11913
  var ExceptionFlowGraph = class {
11284
11914
  /** All try/catch pairs found in the CFG. */
@@ -16899,7 +17529,9 @@ var TaintMatcherPass = class {
16899
17529
  };
16900
17530
  }
16901
17531
  }
16902
- const taint = analyzeTaint(calls, types, mergedConfig);
17532
+ const hierarchy = createWithJdkTypes();
17533
+ hierarchy.addFromIR(graph.ir, graph.ir.meta.file);
17534
+ const taint = analyzeTaint(calls, types, mergedConfig, hierarchy);
16903
17535
  const sanitizerMethods = [];
16904
17536
  for (const type of types) {
16905
17537
  for (const method of type.methods) {
@@ -17424,6 +18056,73 @@ var SinkFilterPass = class {
17424
18056
  return { sources, sinks: filtered, sanitizers };
17425
18057
  }
17426
18058
  };
18059
+ function evalArithmetic(input) {
18060
+ let pos = 0;
18061
+ function peek() {
18062
+ return input[pos] ?? "";
18063
+ }
18064
+ function consume() {
18065
+ return input[pos++] ?? "";
18066
+ }
18067
+ function skipWs() {
18068
+ while (pos < input.length && input[pos] === " ") pos++;
18069
+ }
18070
+ function parseNumber() {
18071
+ skipWs();
18072
+ let s = "";
18073
+ if (peek() === "-") {
18074
+ s += consume();
18075
+ }
18076
+ while (pos < input.length && /[\d.]/.test(input[pos])) s += consume();
18077
+ if (s === "" || s === "-") return null;
18078
+ const n = parseFloat(s);
18079
+ return isFinite(n) ? n : null;
18080
+ }
18081
+ function parseFactor() {
18082
+ skipWs();
18083
+ if (peek() === "(") {
18084
+ consume();
18085
+ const val = parseExpr();
18086
+ skipWs();
18087
+ if (peek() === ")") consume();
18088
+ return val;
18089
+ }
18090
+ return parseNumber();
18091
+ }
18092
+ function parseTerm() {
18093
+ let left = parseFactor();
18094
+ if (left === null) return null;
18095
+ while (true) {
18096
+ skipWs();
18097
+ const op = peek();
18098
+ if (op !== "*" && op !== "/") break;
18099
+ consume();
18100
+ const right = parseFactor();
18101
+ if (right === null) return null;
18102
+ left = op === "*" ? left * right : right === 0 ? null : left / right;
18103
+ if (left === null) return null;
18104
+ }
18105
+ return left;
18106
+ }
18107
+ function parseExpr() {
18108
+ let left = parseTerm();
18109
+ if (left === null) return null;
18110
+ while (true) {
18111
+ skipWs();
18112
+ const op = peek();
18113
+ if (op !== "+" && op !== "-") break;
18114
+ consume();
18115
+ const right = parseTerm();
18116
+ if (right === null) return null;
18117
+ left = op === "+" ? left + right : left - right;
18118
+ }
18119
+ return left;
18120
+ }
18121
+ if (!/^[\d\s+\-*/().]+$/.test(input)) return null;
18122
+ const result = parseExpr();
18123
+ skipWs();
18124
+ return pos === input.length ? result : null;
18125
+ }
17427
18126
  function evaluateSimpleExpression(expr, symbols) {
17428
18127
  let evaluated = expr;
17429
18128
  for (const [name2, val] of symbols) {
@@ -17432,13 +18131,8 @@ function evaluateSimpleExpression(expr, symbols) {
17432
18131
  evaluated = evaluated.replace(regex, String(val.value));
17433
18132
  }
17434
18133
  }
17435
- try {
17436
- if (/^[\d\s+\-*/().]+$/.test(evaluated)) {
17437
- const result = Function('"use strict"; return (' + evaluated + ")")();
17438
- if (typeof result === "number" && !isNaN(result)) return String(Math.floor(result));
17439
- }
17440
- } catch {
17441
- }
18134
+ const result = evalArithmetic(evaluated);
18135
+ if (result !== null && !isNaN(result)) return String(Math.floor(result));
17442
18136
  return expr;
17443
18137
  }
17444
18138
  function isStringLiteralExpression(expr) {
@@ -17606,8 +18300,8 @@ var TaintPropagationPass = class {
17606
18300
  for (const f of collectionFlows) {
17607
18301
  if (flows.some((x) => x.source_line === f.source_line && x.sink_line === f.sink_line)) continue;
17608
18302
  const flowForCheck = {
17609
- source: { line: f.source_line, type: f.source_type },
17610
- sink: { line: f.sink_line, type: f.sink_type },
18303
+ source: { line: f.source_line },
18304
+ sink: { line: f.sink_line },
17611
18305
  path: f.path.map((p) => ({ variable: p.variable, line: p.line }))
17612
18306
  };
17613
18307
  if (isCorrelatedPredicateFP(constProp, flowForCheck)) continue;
@@ -20182,6 +20876,325 @@ var UseAfterClosePass = class {
20182
20876
  }
20183
20877
  };
20184
20878
 
20879
+ // src/analysis/passes/missing-guard-dom-pass.ts
20880
+ var AUTH_METHODS = /* @__PURE__ */ new Set([
20881
+ "authenticate",
20882
+ "isAuthenticated",
20883
+ "isAuthorized",
20884
+ "isAdmin",
20885
+ "checkAuth",
20886
+ "hasPermission",
20887
+ "requiresAuth",
20888
+ "verifyToken",
20889
+ "validateToken",
20890
+ "checkRole",
20891
+ "authorize",
20892
+ "isLoggedIn"
20893
+ ]);
20894
+ var SENSITIVE_METHODS = /* @__PURE__ */ new Set([
20895
+ "delete",
20896
+ "deleteById",
20897
+ "drop",
20898
+ "truncate",
20899
+ "executeUpdate",
20900
+ "createUser",
20901
+ "createAdmin",
20902
+ "modifyPermission",
20903
+ "grantRole",
20904
+ "setAdmin",
20905
+ "elevatePrivilege"
20906
+ ]);
20907
+ var MissingGuardDomPass = class {
20908
+ name = "missing-guard-dom";
20909
+ category = "security";
20910
+ run(ctx) {
20911
+ const { graph, language } = ctx;
20912
+ if (language !== "java") return { findings: 0 };
20913
+ const { cfg, calls } = graph.ir;
20914
+ if (cfg.blocks.length === 0 || cfg.edges.length === 0) return { findings: 0 };
20915
+ const dom = new DominatorGraph(cfg);
20916
+ const file = graph.ir.meta.file;
20917
+ const authCallLines = [];
20918
+ const sensitiveOps = [];
20919
+ for (const call of calls) {
20920
+ if (AUTH_METHODS.has(call.method_name)) {
20921
+ authCallLines.push(call.location.line);
20922
+ }
20923
+ if (SENSITIVE_METHODS.has(call.method_name)) {
20924
+ sensitiveOps.push({ line: call.location.line, method: call.method_name });
20925
+ }
20926
+ }
20927
+ if (sensitiveOps.length === 0) return { findings: 0 };
20928
+ const blockContainingLine = (line) => cfg.blocks.find((b) => b.start_line <= line && line <= b.end_line) ?? null;
20929
+ const reportedMethods = /* @__PURE__ */ new Set();
20930
+ let count = 0;
20931
+ for (const op of sensitiveOps) {
20932
+ const opBlock = blockContainingLine(op.line);
20933
+ if (!opBlock) continue;
20934
+ const methodInfo = graph.methodAtLine(op.line);
20935
+ if (!methodInfo) continue;
20936
+ const methodKey = `${methodInfo.type.name}::${methodInfo.method.name}`;
20937
+ if (reportedMethods.has(methodKey)) continue;
20938
+ const { start_line, end_line } = methodInfo.method;
20939
+ const authInMethod = authCallLines.filter((l) => l >= start_line && l <= end_line);
20940
+ const dominated = authInMethod.some((authLine) => {
20941
+ const authBlock = blockContainingLine(authLine);
20942
+ return authBlock !== null && dom.dominates(authBlock.id, opBlock.id);
20943
+ });
20944
+ if (!dominated) {
20945
+ reportedMethods.add(methodKey);
20946
+ count++;
20947
+ ctx.addFinding({
20948
+ id: `missing-guard-dom-${file}-${op.line}`,
20949
+ pass: this.name,
20950
+ category: this.category,
20951
+ rule_id: "missing-guard-dom",
20952
+ cwe: "CWE-285",
20953
+ severity: "high",
20954
+ level: "error",
20955
+ message: `Sensitive operation \`${op.method}()\` at line ${op.line} is not dominated by an authentication check`,
20956
+ file,
20957
+ line: op.line,
20958
+ fix: `Add authentication/authorization check on all paths leading to line ${op.line}`,
20959
+ evidence: { method: op.method }
20960
+ });
20961
+ }
20962
+ }
20963
+ return { findings: count };
20964
+ }
20965
+ };
20966
+
20967
+ // src/analysis/passes/cleanup-verify-pass.ts
20968
+ var RESOURCE_CTORS4 = /* @__PURE__ */ new Set([
20969
+ "FileInputStream",
20970
+ "FileOutputStream",
20971
+ "FileReader",
20972
+ "FileWriter",
20973
+ "BufferedReader",
20974
+ "BufferedWriter",
20975
+ "PrintWriter",
20976
+ "InputStreamReader",
20977
+ "OutputStreamWriter",
20978
+ "RandomAccessFile",
20979
+ "DataInputStream",
20980
+ "DataOutputStream",
20981
+ "ObjectInputStream",
20982
+ "ObjectOutputStream",
20983
+ "ZipInputStream",
20984
+ "ZipOutputStream",
20985
+ "JarInputStream",
20986
+ "JarOutputStream",
20987
+ "GZIPInputStream",
20988
+ "GZIPOutputStream",
20989
+ "FileChannel",
20990
+ "Socket",
20991
+ "ServerSocket",
20992
+ "DatagramSocket"
20993
+ ]);
20994
+ var RESOURCE_FACTORY_METHODS4 = /* @__PURE__ */ new Set([
20995
+ "openConnection",
20996
+ "openStream",
20997
+ "newInputStream",
20998
+ "newOutputStream",
20999
+ "newBufferedReader",
21000
+ "newBufferedWriter",
21001
+ "newByteChannel",
21002
+ "open",
21003
+ "createReadStream",
21004
+ "createWriteStream",
21005
+ "createConnection"
21006
+ ]);
21007
+ var CLOSE_METHODS4 = /* @__PURE__ */ new Set([
21008
+ "close",
21009
+ "dispose",
21010
+ "shutdown",
21011
+ "disconnect",
21012
+ "release",
21013
+ "destroy",
21014
+ "free",
21015
+ "shutdownNow",
21016
+ "terminate"
21017
+ ]);
21018
+ function buildPostDomGraph(cfg) {
21019
+ const exitBlock = cfg.blocks.find((b) => b.type === "exit") ?? cfg.blocks.find((b) => !cfg.edges.some((e) => e.from === b.id));
21020
+ if (!exitBlock || cfg.blocks.length === 0) {
21021
+ return new DominatorGraph({ blocks: [], edges: [] });
21022
+ }
21023
+ const reversed = {
21024
+ blocks: cfg.blocks,
21025
+ edges: cfg.edges.map((e) => ({ from: e.to, to: e.from, type: e.type }))
21026
+ };
21027
+ return new DominatorGraph(reversed, exitBlock.id);
21028
+ }
21029
+ var CleanupVerifyPass = class {
21030
+ name = "cleanup-verify";
21031
+ category = "reliability";
21032
+ run(ctx) {
21033
+ const { graph, language } = ctx;
21034
+ if (language === "rust" || language === "bash") return { findings: 0 };
21035
+ const { cfg, calls } = graph.ir;
21036
+ const file = graph.ir.meta.file;
21037
+ if (cfg.blocks.length === 0) return { findings: 0 };
21038
+ const postDom = buildPostDomGraph(cfg);
21039
+ const blockContainingLine = (line) => cfg.blocks.find((b) => b.start_line <= line && line <= b.end_line) ?? null;
21040
+ let count = 0;
21041
+ for (const call of calls) {
21042
+ const name2 = call.method_name;
21043
+ const isConstructor = call.is_constructor === true && RESOURCE_CTORS4.has(name2);
21044
+ const isFactory = !call.is_constructor && RESOURCE_FACTORY_METHODS4.has(name2);
21045
+ if (!isConstructor && !isFactory) continue;
21046
+ const openLine = call.location.line;
21047
+ const defs = graph.defsAtLine(openLine);
21048
+ if (defs.length === 0) continue;
21049
+ const resourceVar = defs[0].variable;
21050
+ const methodInfo = graph.methodAtLine(openLine);
21051
+ if (!methodInfo) continue;
21052
+ const methodEnd = methodInfo.method.end_line;
21053
+ const closeCall = calls.find(
21054
+ (c) => CLOSE_METHODS4.has(c.method_name) && c.receiver === resourceVar && c.location.line > openLine && c.location.line <= methodEnd
21055
+ );
21056
+ if (!closeCall) continue;
21057
+ const openBlock = blockContainingLine(openLine);
21058
+ const closeBlock = blockContainingLine(closeCall.location.line);
21059
+ if (!openBlock || !closeBlock) continue;
21060
+ if (postDom.dominates(closeBlock.id, openBlock.id)) continue;
21061
+ count++;
21062
+ ctx.addFinding({
21063
+ id: `cleanup-verify-${file}-${openLine}`,
21064
+ pass: this.name,
21065
+ category: this.category,
21066
+ rule_id: "cleanup-verify",
21067
+ cwe: "CWE-772",
21068
+ severity: "medium",
21069
+ level: "warning",
21070
+ message: `Resource \`${resourceVar}\` opened at line ${openLine} may not close on all paths \u2014 close() at line ${closeCall.location.line} does not post-dominate the acquisition`,
21071
+ file,
21072
+ line: openLine,
21073
+ fix: "Use try-with-resources (Java) or a finally block to guarantee cleanup on all paths",
21074
+ evidence: {
21075
+ resource: name2,
21076
+ variable: resourceVar,
21077
+ close_line: closeCall.location.line
21078
+ }
21079
+ });
21080
+ }
21081
+ return { findings: count };
21082
+ }
21083
+ };
21084
+
21085
+ // src/analysis/passes/missing-override-pass.ts
21086
+ var MissingOverridePass = class {
21087
+ name = "missing-override";
21088
+ category = "maintainability";
21089
+ run(ctx) {
21090
+ const { graph, language } = ctx;
21091
+ if (language !== "java") return { findings: 0 };
21092
+ const { types } = graph.ir;
21093
+ const file = graph.ir.meta.file;
21094
+ if (types.length === 0) return { findings: 0 };
21095
+ const methodsByClass = /* @__PURE__ */ new Map();
21096
+ for (const type of types) {
21097
+ methodsByClass.set(type.name, new Set(type.methods.map((m) => m.name)));
21098
+ }
21099
+ const parentMap = /* @__PURE__ */ new Map();
21100
+ for (const type of types) {
21101
+ if (type.extends) {
21102
+ const parent = type.extends.replace(/<[^>]*>/g, "").trim();
21103
+ parentMap.set(type.name, parent);
21104
+ }
21105
+ }
21106
+ if (parentMap.size === 0) return { findings: 0 };
21107
+ const getAncestorMethods = (className) => {
21108
+ const methods = /* @__PURE__ */ new Set();
21109
+ const visited = /* @__PURE__ */ new Set();
21110
+ let current = parentMap.get(className);
21111
+ let hops = 0;
21112
+ while (current && !visited.has(current) && hops < 10) {
21113
+ visited.add(current);
21114
+ const parentMethods = methodsByClass.get(current);
21115
+ if (parentMethods) {
21116
+ for (const m of parentMethods) methods.add(m);
21117
+ }
21118
+ current = parentMap.get(current);
21119
+ hops++;
21120
+ }
21121
+ return methods;
21122
+ };
21123
+ const dedup = /* @__PURE__ */ new Set();
21124
+ let count = 0;
21125
+ for (const type of types) {
21126
+ if (!parentMap.has(type.name)) continue;
21127
+ const ancestorMethods = getAncestorMethods(type.name);
21128
+ if (ancestorMethods.size === 0) continue;
21129
+ for (const method of type.methods) {
21130
+ if (method.name === type.name) continue;
21131
+ if (method.modifiers.includes("private")) continue;
21132
+ if (method.modifiers.includes("static")) continue;
21133
+ if (method.modifiers.includes("abstract")) continue;
21134
+ if (!ancestorMethods.has(method.name)) continue;
21135
+ if (method.annotations.includes("Override")) continue;
21136
+ const key = `${type.name}:${method.name}`;
21137
+ if (dedup.has(key)) continue;
21138
+ dedup.add(key);
21139
+ count++;
21140
+ ctx.addFinding({
21141
+ id: `missing-override-${file}-${method.start_line}`,
21142
+ pass: this.name,
21143
+ category: this.category,
21144
+ rule_id: "missing-override",
21145
+ severity: "low",
21146
+ level: "warning",
21147
+ message: `Method \`${method.name}()\` in \`${type.name}\` overrides a parent method but lacks @Override`,
21148
+ file,
21149
+ line: method.start_line,
21150
+ fix: "Add @Override to make the intent explicit and catch signature mismatches at compile time",
21151
+ evidence: { className: type.name, methodName: method.name }
21152
+ });
21153
+ }
21154
+ }
21155
+ return { findings: count };
21156
+ }
21157
+ };
21158
+
21159
+ // src/analysis/passes/unused-interface-method-pass.ts
21160
+ var UnusedInterfaceMethodPass = class {
21161
+ name = "unused-interface-method";
21162
+ category = "maintainability";
21163
+ run(ctx) {
21164
+ const { graph, language } = ctx;
21165
+ if (language !== "java" && language !== "typescript") return { findings: 0 };
21166
+ const { types, calls } = graph.ir;
21167
+ const file = graph.ir.meta.file;
21168
+ const calledMethods = new Set(calls.map((c) => c.method_name));
21169
+ const dedup = /* @__PURE__ */ new Set();
21170
+ let count = 0;
21171
+ for (const type of types) {
21172
+ if (type.kind !== "interface") continue;
21173
+ for (const method of type.methods) {
21174
+ if (calledMethods.has(method.name)) continue;
21175
+ const key = `${type.name}:${method.name}`;
21176
+ if (dedup.has(key)) continue;
21177
+ dedup.add(key);
21178
+ count++;
21179
+ ctx.addFinding({
21180
+ id: `unused-interface-method-${file}-${method.start_line}`,
21181
+ pass: this.name,
21182
+ category: this.category,
21183
+ rule_id: "unused-interface-method",
21184
+ severity: "low",
21185
+ level: "note",
21186
+ message: `Interface method \`${method.name}()\` in \`${type.name}\` is never called in this file`,
21187
+ file,
21188
+ line: method.start_line,
21189
+ fix: "Remove this method or verify it is used from other files; unused interface methods inflate the public API",
21190
+ evidence: { interfaceName: type.name, methodName: method.name }
21191
+ });
21192
+ }
21193
+ }
21194
+ return { findings: count };
21195
+ }
21196
+ };
21197
+
20185
21198
  // src/analysis/metrics/passes/size-metrics-pass.ts
20186
21199
  var SizeMetricsPass = class {
20187
21200
  name = "size-metrics";
@@ -20981,7 +21994,7 @@ async function analyze(code, filePath, language, options = {}) {
20981
21994
  enriched: {}
20982
21995
  });
20983
21996
  const config = options.taintConfig ?? getDefaultConfig();
20984
- const { results, findings } = new AnalysisPipeline().add(new TaintMatcherPass()).add(new ConstantPropagationPass(tree)).add(new LanguageSourcesPass()).add(new SinkFilterPass()).add(new TaintPropagationPass()).add(new InterproceduralPass()).add(new DeadCodePass()).add(new MissingAwaitPass()).add(new NPlusOnePass()).add(new MissingPublicDocPass()).add(new TodoInProdPass()).add(new StringConcatLoopPass()).add(new SyncIoAsyncPass()).add(new UncheckedReturnPass()).add(new NullDerefPass()).add(new ResourceLeakPass()).add(new VariableShadowingPass()).add(new LeakedGlobalPass()).add(new UnusedVariablePass()).add(new DependencyFanOutPass()).add(new StaleDocRefPass()).add(new InfiniteLoopPass()).add(new DeepInheritancePass()).add(new RedundantLoopPass()).add(new UnboundedCollectionPass()).add(new SerialAwaitPass()).add(new ReactInlineJsxPass()).add(new SwallowedExceptionPass()).add(new BroadCatchPass()).add(new UnhandledExceptionPass()).add(new DoubleClosePass()).add(new UseAfterClosePass()).run(graph, code, language, config);
21997
+ const { results, findings } = new AnalysisPipeline().add(new TaintMatcherPass()).add(new ConstantPropagationPass(tree)).add(new LanguageSourcesPass()).add(new SinkFilterPass()).add(new TaintPropagationPass()).add(new InterproceduralPass()).add(new DeadCodePass()).add(new MissingAwaitPass()).add(new NPlusOnePass()).add(new MissingPublicDocPass()).add(new TodoInProdPass()).add(new StringConcatLoopPass()).add(new SyncIoAsyncPass()).add(new UncheckedReturnPass()).add(new NullDerefPass()).add(new ResourceLeakPass()).add(new VariableShadowingPass()).add(new LeakedGlobalPass()).add(new UnusedVariablePass()).add(new DependencyFanOutPass()).add(new StaleDocRefPass()).add(new InfiniteLoopPass()).add(new DeepInheritancePass()).add(new RedundantLoopPass()).add(new UnboundedCollectionPass()).add(new SerialAwaitPass()).add(new ReactInlineJsxPass()).add(new SwallowedExceptionPass()).add(new BroadCatchPass()).add(new UnhandledExceptionPass()).add(new DoubleClosePass()).add(new UseAfterClosePass()).add(new MissingGuardDomPass()).add(new CleanupVerifyPass()).add(new MissingOverridePass()).add(new UnusedInterfaceMethodPass()).run(graph, code, language, config);
20985
21998
  const sinkFilter = results.get("sink-filter");
20986
21999
  const interProc = results.get("interprocedural");
20987
22000
  const taint = {