circle-ir 3.9.8 → 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 (52) hide show
  1. package/dist/analysis/passes/broad-catch-pass.d.ts +29 -0
  2. package/dist/analysis/passes/broad-catch-pass.js +79 -0
  3. package/dist/analysis/passes/broad-catch-pass.js.map +1 -0
  4. package/dist/analysis/passes/cleanup-verify-pass.d.ts +28 -0
  5. package/dist/analysis/passes/cleanup-verify-pass.js +130 -0
  6. package/dist/analysis/passes/cleanup-verify-pass.js.map +1 -0
  7. package/dist/analysis/passes/double-close-pass.d.ts +33 -0
  8. package/dist/analysis/passes/double-close-pass.js +109 -0
  9. package/dist/analysis/passes/double-close-pass.js.map +1 -0
  10. package/dist/analysis/passes/missing-guard-dom-pass.d.ts +25 -0
  11. package/dist/analysis/passes/missing-guard-dom-pass.js +99 -0
  12. package/dist/analysis/passes/missing-guard-dom-pass.js.map +1 -0
  13. package/dist/analysis/passes/missing-override-pass.d.ts +27 -0
  14. package/dist/analysis/passes/missing-override-pass.js +110 -0
  15. package/dist/analysis/passes/missing-override-pass.js.map +1 -0
  16. package/dist/analysis/passes/sink-filter-pass.js +88 -9
  17. package/dist/analysis/passes/sink-filter-pass.js.map +1 -1
  18. package/dist/analysis/passes/swallowed-exception-pass.d.ts +35 -0
  19. package/dist/analysis/passes/swallowed-exception-pass.js +103 -0
  20. package/dist/analysis/passes/swallowed-exception-pass.js.map +1 -0
  21. package/dist/analysis/passes/taint-matcher-pass.js +6 -1
  22. package/dist/analysis/passes/taint-matcher-pass.js.map +1 -1
  23. package/dist/analysis/passes/taint-propagation-pass.js +2 -3
  24. package/dist/analysis/passes/taint-propagation-pass.js.map +1 -1
  25. package/dist/analysis/passes/unhandled-exception-pass.d.ts +34 -0
  26. package/dist/analysis/passes/unhandled-exception-pass.js +123 -0
  27. package/dist/analysis/passes/unhandled-exception-pass.js.map +1 -0
  28. package/dist/analysis/passes/unused-interface-method-pass.d.ts +27 -0
  29. package/dist/analysis/passes/unused-interface-method-pass.js +62 -0
  30. package/dist/analysis/passes/unused-interface-method-pass.js.map +1 -0
  31. package/dist/analysis/passes/use-after-close-pass.d.ts +30 -0
  32. package/dist/analysis/passes/use-after-close-pass.js +100 -0
  33. package/dist/analysis/passes/use-after-close-pass.js.map +1 -0
  34. package/dist/analysis/taint-matcher.d.ts +2 -1
  35. package/dist/analysis/taint-matcher.js +10 -5
  36. package/dist/analysis/taint-matcher.js.map +1 -1
  37. package/dist/analyzer.d.ts +12 -3
  38. package/dist/analyzer.js +30 -3
  39. package/dist/analyzer.js.map +1 -1
  40. package/dist/browser/circle-ir.js +1523 -18
  41. package/dist/core/circle-ir-core.cjs +10 -6
  42. package/dist/core/circle-ir-core.js +10 -6
  43. package/dist/graph/exception-flow-graph.d.ts +44 -0
  44. package/dist/graph/exception-flow-graph.js +75 -0
  45. package/dist/graph/exception-flow-graph.js.map +1 -0
  46. package/dist/graph/index.d.ts +1 -0
  47. package/dist/graph/index.js +1 -0
  48. package/dist/graph/index.js.map +1 -1
  49. package/dist/index.d.ts +1 -0
  50. package/dist/index.js +1 -0
  51. package/dist/index.js.map +1 -1
  52. 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
  }
@@ -10483,7 +10483,8 @@ function findSinks(calls, patterns) {
10483
10483
  cwe: pattern.cwe,
10484
10484
  location,
10485
10485
  line: call.location.line,
10486
- confidence
10486
+ confidence,
10487
+ method: call.method_name
10487
10488
  });
10488
10489
  }
10489
10490
  }
@@ -10560,7 +10561,7 @@ function isJavaScriptTaintedArgument(argExpression, sourcePatterns) {
10560
10561
  }
10561
10562
  return { isTainted: false, sourceType: null };
10562
10563
  }
10563
- function matchesSinkPattern(call, pattern) {
10564
+ function matchesSinkPattern(call, pattern, typeHierarchy) {
10564
10565
  const callMethodName = call.method_name;
10565
10566
  const patternMethod = pattern.method;
10566
10567
  let methodMatches = callMethodName === patternMethod;
@@ -10576,6 +10577,9 @@ function matchesSinkPattern(call, pattern) {
10576
10577
  return true;
10577
10578
  }
10578
10579
  if (call.receiver && !receiverMightBeClass(call.receiver, pattern.class)) {
10580
+ if (typeHierarchy && typeHierarchy.couldBeType(call.receiver, pattern.class)) {
10581
+ return true;
10582
+ }
10579
10583
  return false;
10580
10584
  }
10581
10585
  if (!call.receiver) {
@@ -11278,6 +11282,698 @@ var CodeGraph = class {
11278
11282
  }
11279
11283
  };
11280
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
+
11912
+ // src/graph/exception-flow-graph.ts
11913
+ var ExceptionFlowGraph = class {
11914
+ /** All try/catch pairs found in the CFG. */
11915
+ pairs;
11916
+ /** Block IDs that are catch-handler entry blocks. */
11917
+ catchEntryIds;
11918
+ /** Block IDs that are try-body entry blocks. */
11919
+ tryEntryIds;
11920
+ tryCatchMap;
11921
+ // tryEntryId → [catchEntryId, …]
11922
+ catchTryMap;
11923
+ // catchEntryId → tryEntryId
11924
+ constructor(cfg, blockById) {
11925
+ this.pairs = [];
11926
+ this.catchEntryIds = /* @__PURE__ */ new Set();
11927
+ this.tryEntryIds = /* @__PURE__ */ new Set();
11928
+ this.tryCatchMap = /* @__PURE__ */ new Map();
11929
+ this.catchTryMap = /* @__PURE__ */ new Map();
11930
+ for (const edge of cfg.edges) {
11931
+ if (edge.type !== "exception") continue;
11932
+ const tryBlock = blockById.get(edge.from);
11933
+ const catchBlock = blockById.get(edge.to);
11934
+ if (!tryBlock || !catchBlock) continue;
11935
+ this.tryEntryIds.add(edge.from);
11936
+ this.catchEntryIds.add(edge.to);
11937
+ const catches = this.tryCatchMap.get(edge.from) ?? [];
11938
+ catches.push(edge.to);
11939
+ this.tryCatchMap.set(edge.from, catches);
11940
+ this.catchTryMap.set(edge.to, edge.from);
11941
+ this.pairs.push({
11942
+ tryEntryId: edge.from,
11943
+ catchEntryId: edge.to,
11944
+ tryBlock,
11945
+ catchBlock
11946
+ });
11947
+ }
11948
+ }
11949
+ /** True if at least one try/catch pair was found. */
11950
+ get hasTryCatch() {
11951
+ return this.pairs.length > 0;
11952
+ }
11953
+ /** True if the given block ID is a catch-handler entry block. */
11954
+ isCatchEntry(blockId) {
11955
+ return this.catchEntryIds.has(blockId);
11956
+ }
11957
+ /** True if the given block ID is a try-body entry block. */
11958
+ isTryEntry(blockId) {
11959
+ return this.tryEntryIds.has(blockId);
11960
+ }
11961
+ /**
11962
+ * Returns the catch-entry block IDs for the given try-entry block.
11963
+ * Multiple values mean multiple catch clauses for the same try.
11964
+ */
11965
+ catchBlocksFor(tryEntryId) {
11966
+ return this.tryCatchMap.get(tryEntryId) ?? [];
11967
+ }
11968
+ /**
11969
+ * Returns the try-entry block ID corresponding to a catch-entry block,
11970
+ * or `undefined` if the block is not a catch entry.
11971
+ */
11972
+ tryBlockFor(catchEntryId) {
11973
+ return this.catchTryMap.get(catchEntryId);
11974
+ }
11975
+ };
11976
+
11281
11977
  // src/graph/analysis-pass.ts
11282
11978
  var AnalysisPipeline = class {
11283
11979
  passes = [];
@@ -16833,7 +17529,9 @@ var TaintMatcherPass = class {
16833
17529
  };
16834
17530
  }
16835
17531
  }
16836
- 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);
16837
17535
  const sanitizerMethods = [];
16838
17536
  for (const type of types) {
16839
17537
  for (const method of type.methods) {
@@ -17358,6 +18056,73 @@ var SinkFilterPass = class {
17358
18056
  return { sources, sinks: filtered, sanitizers };
17359
18057
  }
17360
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
+ }
17361
18126
  function evaluateSimpleExpression(expr, symbols) {
17362
18127
  let evaluated = expr;
17363
18128
  for (const [name2, val] of symbols) {
@@ -17366,13 +18131,8 @@ function evaluateSimpleExpression(expr, symbols) {
17366
18131
  evaluated = evaluated.replace(regex, String(val.value));
17367
18132
  }
17368
18133
  }
17369
- try {
17370
- if (/^[\d\s+\-*/().]+$/.test(evaluated)) {
17371
- const result = Function('"use strict"; return (' + evaluated + ")")();
17372
- if (typeof result === "number" && !isNaN(result)) return String(Math.floor(result));
17373
- }
17374
- } catch {
17375
- }
18134
+ const result = evalArithmetic(evaluated);
18135
+ if (result !== null && !isNaN(result)) return String(Math.floor(result));
17376
18136
  return expr;
17377
18137
  }
17378
18138
  function isStringLiteralExpression(expr) {
@@ -17422,7 +18182,8 @@ function filterCleanVariableSinks(sinks, calls, taintedVars, symbols, dfg, sanit
17422
18182
  return sinks.filter((sink) => {
17423
18183
  const callsAtSink = callsByLine.get(sink.line) ?? [];
17424
18184
  const isInSynchronizedBlock = synchronizedLines?.has(sink.line) ?? false;
17425
- for (const call of callsAtSink) {
18185
+ const relevantCalls = sink.method ? callsAtSink.filter((c) => c.method_name === sink.method) : callsAtSink;
18186
+ for (const call of relevantCalls) {
17426
18187
  let allArgsAreClean = true;
17427
18188
  const methodName = call.in_method;
17428
18189
  for (const arg of call.arguments) {
@@ -17539,8 +18300,8 @@ var TaintPropagationPass = class {
17539
18300
  for (const f of collectionFlows) {
17540
18301
  if (flows.some((x) => x.source_line === f.source_line && x.sink_line === f.sink_line)) continue;
17541
18302
  const flowForCheck = {
17542
- source: { line: f.source_line, type: f.source_type },
17543
- sink: { line: f.sink_line, type: f.sink_type },
18303
+ source: { line: f.source_line },
18304
+ sink: { line: f.sink_line },
17544
18305
  path: f.path.map((p) => ({ variable: p.variable, line: p.line }))
17545
18306
  };
17546
18307
  if (isCorrelatedPredicateFP(constProp, flowForCheck)) continue;
@@ -19690,6 +20451,750 @@ var ReactInlineJsxPass = class {
19690
20451
  }
19691
20452
  };
19692
20453
 
20454
+ // src/analysis/passes/swallowed-exception-pass.ts
20455
+ var MEANINGFUL_ACTION_RE = /\b(throw|raise|log|logger|console\.(error|warn|log|debug|info)|System\.(out|err)\.|print(?:ln|f)?|warn|error|debug|info|fatal|LOGGER|LOG|logging\.(warning|error|debug|info|critical))\b|\breturn\s+\S/;
20456
+ var SwallowedExceptionPass = class {
20457
+ name = "swallowed-exception";
20458
+ category = "reliability";
20459
+ run(ctx) {
20460
+ const { graph, code, language } = ctx;
20461
+ if (language === "rust" || language === "bash") {
20462
+ return { swallowed: [] };
20463
+ }
20464
+ const { cfg } = graph.ir;
20465
+ if (cfg.blocks.length === 0) return { swallowed: [] };
20466
+ const exGraph = new ExceptionFlowGraph(cfg, graph.blockById);
20467
+ if (!exGraph.hasTryCatch) return { swallowed: [] };
20468
+ const file = graph.ir.meta.file;
20469
+ const codeLines = code.split("\n");
20470
+ const swallowed = [];
20471
+ const reported = /* @__PURE__ */ new Set();
20472
+ for (const pair of exGraph.pairs) {
20473
+ const catchLine = pair.catchBlock.start_line;
20474
+ if (reported.has(catchLine)) continue;
20475
+ const methodInfo = graph.methodAtLine(catchLine);
20476
+ const scanEnd = methodInfo ? methodInfo.method.end_line : codeLines.length;
20477
+ const catchBodyEnd = this.findCatchBodyEnd(codeLines, catchLine, scanEnd);
20478
+ let hasAction = false;
20479
+ for (let ln = catchLine; ln <= catchBodyEnd && ln <= codeLines.length; ln++) {
20480
+ if (MEANINGFUL_ACTION_RE.test(codeLines[ln - 1] ?? "")) {
20481
+ hasAction = true;
20482
+ break;
20483
+ }
20484
+ }
20485
+ if (!hasAction) {
20486
+ reported.add(catchLine);
20487
+ swallowed.push({ line: catchLine });
20488
+ const snippet = (codeLines[catchLine - 1] ?? "").trim();
20489
+ ctx.addFinding({
20490
+ id: `swallowed-exception-${file}-${catchLine}`,
20491
+ pass: this.name,
20492
+ category: this.category,
20493
+ rule_id: this.name,
20494
+ cwe: "CWE-390",
20495
+ severity: "medium",
20496
+ level: "warning",
20497
+ message: `Swallowed exception: catch block at line ${catchLine} has no throw, log, or return \u2014 the exception is silently discarded`,
20498
+ file,
20499
+ line: catchLine,
20500
+ snippet,
20501
+ fix: "At minimum log the exception, or re-throw it; never silently discard exceptions"
20502
+ });
20503
+ }
20504
+ }
20505
+ return { swallowed };
20506
+ }
20507
+ /**
20508
+ * Walks source lines starting at `startLine` counting brace depth.
20509
+ * Returns the line where the brace depth first returns to zero after
20510
+ * the opening brace (i.e., the closing brace of the catch block).
20511
+ * Capped at `maxLine`.
20512
+ */
20513
+ findCatchBodyEnd(lines, startLine, maxLine) {
20514
+ let depth = 0;
20515
+ let started = false;
20516
+ for (let ln = startLine; ln <= maxLine && ln <= lines.length; ln++) {
20517
+ const text = lines[ln - 1] ?? "";
20518
+ for (const ch of text) {
20519
+ if (ch === "{") {
20520
+ depth++;
20521
+ started = true;
20522
+ } else if (ch === "}" && started) {
20523
+ depth--;
20524
+ }
20525
+ }
20526
+ if (started && depth <= 0) return ln;
20527
+ }
20528
+ return maxLine;
20529
+ }
20530
+ };
20531
+
20532
+ // src/analysis/passes/broad-catch-pass.ts
20533
+ var JAVA_BROAD_RE = /catch\s*\(\s*(Exception|Throwable|RuntimeException|Error)\s/;
20534
+ var PYTHON_BROAD_RE = /^\s*except\s*:|except\s+(Exception|BaseException)\b/;
20535
+ var BroadCatchPass = class {
20536
+ name = "broad-catch";
20537
+ category = "reliability";
20538
+ run(ctx) {
20539
+ const { graph, code, language } = ctx;
20540
+ if (language !== "java" && language !== "python") {
20541
+ return { broadCatches: [] };
20542
+ }
20543
+ const { cfg } = graph.ir;
20544
+ if (cfg.blocks.length === 0) return { broadCatches: [] };
20545
+ const exGraph = new ExceptionFlowGraph(cfg, graph.blockById);
20546
+ if (!exGraph.hasTryCatch) return { broadCatches: [] };
20547
+ const file = graph.ir.meta.file;
20548
+ const codeLines = code.split("\n");
20549
+ const broadCatches = [];
20550
+ const reported = /* @__PURE__ */ new Set();
20551
+ const pattern = language === "java" ? JAVA_BROAD_RE : PYTHON_BROAD_RE;
20552
+ for (const pair of exGraph.pairs) {
20553
+ const catchLine = pair.catchBlock.start_line;
20554
+ if (reported.has(catchLine)) continue;
20555
+ const lineText = codeLines[catchLine - 1] ?? "";
20556
+ const match = pattern.exec(lineText);
20557
+ if (!match) continue;
20558
+ const caughtType = match[1] ?? "Exception";
20559
+ reported.add(catchLine);
20560
+ broadCatches.push({ line: catchLine, type: caughtType });
20561
+ const snippet = lineText.trim();
20562
+ ctx.addFinding({
20563
+ id: `broad-catch-${file}-${catchLine}`,
20564
+ pass: this.name,
20565
+ category: this.category,
20566
+ rule_id: this.name,
20567
+ cwe: "CWE-396",
20568
+ severity: "low",
20569
+ level: "warning",
20570
+ message: `Broad catch: catching \`${caughtType}\` at line ${catchLine} suppresses unexpected errors and hides bugs`,
20571
+ file,
20572
+ line: catchLine,
20573
+ snippet,
20574
+ fix: language === "java" ? `Catch the specific exception types your code can handle (e.g., \`IOException\`, \`SQLException\`)` : `Catch the specific exception types your code can handle (e.g., \`ValueError\`, \`KeyError\`)`,
20575
+ evidence: { caughtType }
20576
+ });
20577
+ }
20578
+ return { broadCatches };
20579
+ }
20580
+ };
20581
+
20582
+ // src/analysis/passes/unhandled-exception-pass.ts
20583
+ var JS_THROW_RE = /^\s*throw\s+/;
20584
+ var PYTHON_RAISE_RE = /^\s*raise\b/;
20585
+ var UnhandledExceptionPass = class {
20586
+ name = "unhandled-exception";
20587
+ category = "reliability";
20588
+ run(ctx) {
20589
+ const { graph, code, language } = ctx;
20590
+ if (language !== "javascript" && language !== "typescript" && language !== "python") {
20591
+ return { unhandled: [] };
20592
+ }
20593
+ const { cfg } = graph.ir;
20594
+ const file = graph.ir.meta.file;
20595
+ const codeLines = code.split("\n");
20596
+ const exGraph = new ExceptionFlowGraph(cfg, graph.blockById);
20597
+ const coveredRanges = [];
20598
+ for (const pair of exGraph.pairs) {
20599
+ if (pair.catchBlock.start_line > pair.tryBlock.start_line) {
20600
+ coveredRanges.push({
20601
+ start: pair.tryBlock.start_line,
20602
+ end: pair.catchBlock.start_line - 1
20603
+ });
20604
+ }
20605
+ }
20606
+ const catchStarts = new Set(
20607
+ exGraph.pairs.map((p) => p.catchBlock.start_line)
20608
+ );
20609
+ const throwRe = language === "python" ? PYTHON_RAISE_RE : JS_THROW_RE;
20610
+ const unhandled = [];
20611
+ const reportedMethods = /* @__PURE__ */ new Set();
20612
+ for (let ln = 1; ln <= codeLines.length; ln++) {
20613
+ const lineText = codeLines[ln - 1] ?? "";
20614
+ if (!throwRe.test(lineText)) continue;
20615
+ let inCatch = false;
20616
+ for (const cs of catchStarts) {
20617
+ if (ln >= cs) {
20618
+ inCatch = true;
20619
+ break;
20620
+ }
20621
+ }
20622
+ inCatch = false;
20623
+ for (const pair of exGraph.pairs) {
20624
+ if (ln >= pair.catchBlock.start_line) {
20625
+ const mThrow = graph.methodAtLine(ln);
20626
+ const mCatch = graph.methodAtLine(pair.catchBlock.start_line);
20627
+ if (mThrow && mCatch && mThrow.method.start_line === mCatch.method.start_line) {
20628
+ inCatch = true;
20629
+ break;
20630
+ }
20631
+ }
20632
+ }
20633
+ if (inCatch) continue;
20634
+ const isCovered = coveredRanges.some((r) => ln >= r.start && ln <= r.end);
20635
+ if (isCovered) continue;
20636
+ const methodInfo = graph.methodAtLine(ln);
20637
+ const methodKey = methodInfo ? `${methodInfo.method.start_line}-${methodInfo.method.end_line}` : `global-${ln}`;
20638
+ if (reportedMethods.has(methodKey)) continue;
20639
+ reportedMethods.add(methodKey);
20640
+ const methodName = methodInfo?.method.name ?? "<anonymous>";
20641
+ unhandled.push({ line: ln, method: methodName });
20642
+ const snippet = lineText.trim();
20643
+ ctx.addFinding({
20644
+ id: `unhandled-exception-${file}-${ln}`,
20645
+ pass: this.name,
20646
+ category: this.category,
20647
+ rule_id: this.name,
20648
+ cwe: "CWE-390",
20649
+ severity: "medium",
20650
+ level: "warning",
20651
+ message: `Unhandled exception: \`throw\` at line ${ln} in \`${methodName}\` is not inside a try/catch \u2014 callers receive an unexpected exception`,
20652
+ file,
20653
+ line: ln,
20654
+ snippet,
20655
+ fix: "Wrap throwing code in a try/catch, or document the exception in the function signature",
20656
+ evidence: { method: methodName }
20657
+ });
20658
+ }
20659
+ return { unhandled };
20660
+ }
20661
+ };
20662
+
20663
+ // src/analysis/passes/double-close-pass.ts
20664
+ var RESOURCE_CTORS2 = /* @__PURE__ */ new Set([
20665
+ "FileInputStream",
20666
+ "FileOutputStream",
20667
+ "FileReader",
20668
+ "FileWriter",
20669
+ "BufferedReader",
20670
+ "BufferedWriter",
20671
+ "PrintWriter",
20672
+ "InputStreamReader",
20673
+ "OutputStreamWriter",
20674
+ "RandomAccessFile",
20675
+ "DataInputStream",
20676
+ "DataOutputStream",
20677
+ "ObjectInputStream",
20678
+ "ObjectOutputStream",
20679
+ "ZipInputStream",
20680
+ "ZipOutputStream",
20681
+ "JarInputStream",
20682
+ "JarOutputStream",
20683
+ "GZIPInputStream",
20684
+ "GZIPOutputStream",
20685
+ "FileChannel",
20686
+ "Socket",
20687
+ "ServerSocket",
20688
+ "DatagramSocket"
20689
+ ]);
20690
+ var RESOURCE_FACTORY_METHODS2 = /* @__PURE__ */ new Set([
20691
+ "openConnection",
20692
+ "openStream",
20693
+ "newInputStream",
20694
+ "newOutputStream",
20695
+ "newBufferedReader",
20696
+ "newBufferedWriter",
20697
+ "newByteChannel",
20698
+ "open",
20699
+ "createReadStream",
20700
+ "createWriteStream",
20701
+ "createConnection"
20702
+ ]);
20703
+ var CLOSE_METHODS2 = /* @__PURE__ */ new Set([
20704
+ "close",
20705
+ "dispose",
20706
+ "shutdown",
20707
+ "disconnect",
20708
+ "release",
20709
+ "destroy",
20710
+ "free",
20711
+ "shutdownNow",
20712
+ "terminate"
20713
+ ]);
20714
+ var DoubleClosePass = class {
20715
+ name = "double-close";
20716
+ category = "reliability";
20717
+ run(ctx) {
20718
+ const { graph, code } = ctx;
20719
+ if (ctx.language === "bash") return { doubleCloses: [] };
20720
+ const file = graph.ir.meta.file;
20721
+ const codeLines = code.split("\n");
20722
+ const doubleCloses = [];
20723
+ for (const call of graph.ir.calls) {
20724
+ const name2 = call.method_name;
20725
+ const isConstructor = call.is_constructor === true && RESOURCE_CTORS2.has(name2);
20726
+ const isFactory = !call.is_constructor && RESOURCE_FACTORY_METHODS2.has(name2);
20727
+ if (!isConstructor && !isFactory) continue;
20728
+ const openLine = call.location.line;
20729
+ const defs = graph.defsAtLine(openLine);
20730
+ if (defs.length === 0) continue;
20731
+ const resourceVar = defs[0].variable;
20732
+ const methodInfo = graph.methodAtLine(openLine);
20733
+ if (!methodInfo) continue;
20734
+ const { start_line: methodStart, end_line: methodEnd } = methodInfo.method;
20735
+ const closeCalls = graph.ir.calls.filter(
20736
+ (c) => CLOSE_METHODS2.has(c.method_name) && c.receiver === resourceVar && c.location.line > openLine && c.location.line <= methodEnd
20737
+ );
20738
+ if (closeCalls.length < 2) continue;
20739
+ const closeLines = closeCalls.map((c) => c.location.line);
20740
+ const allInFinally = closeLines.every(
20741
+ (cl) => this.isInFinallyBlock(codeLines, cl, methodStart, methodEnd)
20742
+ );
20743
+ if (allInFinally) continue;
20744
+ doubleCloses.push({ openLine, closeLines, variable: resourceVar });
20745
+ const snippet = (codeLines[openLine - 1] ?? "").trim();
20746
+ const linesStr = closeLines.join(" and ");
20747
+ ctx.addFinding({
20748
+ id: `double-close-${file}-${openLine}`,
20749
+ pass: this.name,
20750
+ category: this.category,
20751
+ rule_id: this.name,
20752
+ cwe: "CWE-675",
20753
+ severity: "medium",
20754
+ level: "warning",
20755
+ message: `Double close: \`${resourceVar}\` is closed at lines ${linesStr} \u2014 closing an already-closed resource may throw`,
20756
+ file,
20757
+ line: openLine,
20758
+ snippet,
20759
+ fix: `Close the resource exactly once in a finally block; add a null/isClosed guard before the second close if closing on multiple paths`,
20760
+ evidence: { variable: resourceVar, close_lines: closeLines }
20761
+ });
20762
+ }
20763
+ return { doubleCloses };
20764
+ }
20765
+ /** True if the given line is inside a `finally` block in the method. */
20766
+ isInFinallyBlock(lines, targetLine, methodStart, methodEnd) {
20767
+ for (let ln = methodStart; ln <= targetLine && ln <= methodEnd && ln <= lines.length; ln++) {
20768
+ if (/\bfinally\b/.test(lines[ln - 1] ?? "")) return true;
20769
+ }
20770
+ return false;
20771
+ }
20772
+ };
20773
+
20774
+ // src/analysis/passes/use-after-close-pass.ts
20775
+ var RESOURCE_CTORS3 = /* @__PURE__ */ new Set([
20776
+ "FileInputStream",
20777
+ "FileOutputStream",
20778
+ "FileReader",
20779
+ "FileWriter",
20780
+ "BufferedReader",
20781
+ "BufferedWriter",
20782
+ "PrintWriter",
20783
+ "InputStreamReader",
20784
+ "OutputStreamWriter",
20785
+ "RandomAccessFile",
20786
+ "DataInputStream",
20787
+ "DataOutputStream",
20788
+ "ObjectInputStream",
20789
+ "ObjectOutputStream",
20790
+ "ZipInputStream",
20791
+ "ZipOutputStream",
20792
+ "JarInputStream",
20793
+ "JarOutputStream",
20794
+ "GZIPInputStream",
20795
+ "GZIPOutputStream",
20796
+ "FileChannel",
20797
+ "Socket",
20798
+ "ServerSocket",
20799
+ "DatagramSocket"
20800
+ ]);
20801
+ var RESOURCE_FACTORY_METHODS3 = /* @__PURE__ */ new Set([
20802
+ "openConnection",
20803
+ "openStream",
20804
+ "newInputStream",
20805
+ "newOutputStream",
20806
+ "newBufferedReader",
20807
+ "newBufferedWriter",
20808
+ "newByteChannel",
20809
+ "open",
20810
+ "createReadStream",
20811
+ "createWriteStream",
20812
+ "createConnection"
20813
+ ]);
20814
+ var CLOSE_METHODS3 = /* @__PURE__ */ new Set([
20815
+ "close",
20816
+ "dispose",
20817
+ "shutdown",
20818
+ "disconnect",
20819
+ "release",
20820
+ "destroy",
20821
+ "free",
20822
+ "shutdownNow",
20823
+ "terminate"
20824
+ ]);
20825
+ var UseAfterClosePass = class {
20826
+ name = "use-after-close";
20827
+ category = "reliability";
20828
+ run(ctx) {
20829
+ const { graph, code } = ctx;
20830
+ if (ctx.language === "bash") return { useAfterCloses: [] };
20831
+ const file = graph.ir.meta.file;
20832
+ const codeLines = code.split("\n");
20833
+ const useAfterCloses = [];
20834
+ for (const call of graph.ir.calls) {
20835
+ const name2 = call.method_name;
20836
+ const isConstructor = call.is_constructor === true && RESOURCE_CTORS3.has(name2);
20837
+ const isFactory = !call.is_constructor && RESOURCE_FACTORY_METHODS3.has(name2);
20838
+ if (!isConstructor && !isFactory) continue;
20839
+ const openLine = call.location.line;
20840
+ const defs = graph.defsAtLine(openLine);
20841
+ if (defs.length === 0) continue;
20842
+ const resourceVar = defs[0].variable;
20843
+ const methodInfo = graph.methodAtLine(openLine);
20844
+ if (!methodInfo) continue;
20845
+ const methodEnd = methodInfo.method.end_line;
20846
+ const firstClose = graph.ir.calls.filter(
20847
+ (c) => CLOSE_METHODS3.has(c.method_name) && c.receiver === resourceVar && c.location.line > openLine && c.location.line <= methodEnd
20848
+ ).sort((a, b) => a.location.line - b.location.line)[0];
20849
+ if (!firstClose) continue;
20850
+ const closeLine = firstClose.location.line;
20851
+ const usesAfterClose = graph.ir.calls.filter(
20852
+ (c) => c.receiver === resourceVar && c.location.line > closeLine && c.location.line <= methodEnd && !CLOSE_METHODS3.has(c.method_name)
20853
+ );
20854
+ for (const use of usesAfterClose) {
20855
+ const useLine = use.location.line;
20856
+ useAfterCloses.push({ openLine, closeLine, useLine, variable: resourceVar });
20857
+ const snippet = (codeLines[useLine - 1] ?? "").trim();
20858
+ ctx.addFinding({
20859
+ id: `use-after-close-${file}-${useLine}`,
20860
+ pass: this.name,
20861
+ category: this.category,
20862
+ rule_id: this.name,
20863
+ cwe: "CWE-672",
20864
+ severity: "high",
20865
+ level: "error",
20866
+ message: `Use after close: \`${resourceVar}.${use.method_name}()\` at line ${useLine} is called after \`${resourceVar}.close()\` at line ${closeLine}`,
20867
+ file,
20868
+ line: useLine,
20869
+ snippet,
20870
+ fix: `Do not use a resource after closing it; keep \`${resourceVar}\` open until all uses are complete`,
20871
+ evidence: { variable: resourceVar, close_line: closeLine, open_line: openLine }
20872
+ });
20873
+ }
20874
+ }
20875
+ return { useAfterCloses };
20876
+ }
20877
+ };
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
+
19693
21198
  // src/analysis/metrics/passes/size-metrics-pass.ts
19694
21199
  var SizeMetricsPass = class {
19695
21200
  name = "size-metrics";
@@ -20489,7 +21994,7 @@ async function analyze(code, filePath, language, options = {}) {
20489
21994
  enriched: {}
20490
21995
  });
20491
21996
  const config = options.taintConfig ?? getDefaultConfig();
20492
- 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()).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);
20493
21998
  const sinkFilter = results.get("sink-filter");
20494
21999
  const interProc = results.get("interprocedural");
20495
22000
  const taint = {