circle-ir 3.176.0 → 3.178.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 (43) hide show
  1. package/dist/analysis/config-loader.d.ts.map +1 -1
  2. package/dist/analysis/config-loader.js +75 -0
  3. package/dist/analysis/config-loader.js.map +1 -1
  4. package/dist/analysis/constant-propagation/propagator.d.ts +1 -2
  5. package/dist/analysis/constant-propagation/propagator.d.ts.map +1 -1
  6. package/dist/analysis/constant-propagation/propagator.js +28 -30
  7. package/dist/analysis/constant-propagation/propagator.js.map +1 -1
  8. package/dist/analysis/dependency-versions.d.ts +102 -0
  9. package/dist/analysis/dependency-versions.d.ts.map +1 -0
  10. package/dist/analysis/dependency-versions.js +153 -0
  11. package/dist/analysis/dependency-versions.js.map +1 -0
  12. package/dist/analysis/dfg-walk.d.ts.map +1 -1
  13. package/dist/analysis/dfg-walk.js +28 -1
  14. package/dist/analysis/dfg-walk.js.map +1 -1
  15. package/dist/analysis/note-coalescer.d.ts +46 -0
  16. package/dist/analysis/note-coalescer.d.ts.map +1 -0
  17. package/dist/analysis/note-coalescer.js +106 -0
  18. package/dist/analysis/note-coalescer.js.map +1 -0
  19. package/dist/analysis/passes/deserialization-safety-gate-pass.d.ts +58 -0
  20. package/dist/analysis/passes/deserialization-safety-gate-pass.d.ts.map +1 -0
  21. package/dist/analysis/passes/deserialization-safety-gate-pass.js +122 -0
  22. package/dist/analysis/passes/deserialization-safety-gate-pass.js.map +1 -0
  23. package/dist/analysis/passes/sink-filter-pass.d.ts.map +1 -1
  24. package/dist/analysis/passes/sink-filter-pass.js +33 -2
  25. package/dist/analysis/passes/sink-filter-pass.js.map +1 -1
  26. package/dist/analysis/taint-matcher.d.ts.map +1 -1
  27. package/dist/analysis/taint-matcher.js +55 -33
  28. package/dist/analysis/taint-matcher.js.map +1 -1
  29. package/dist/analyzer.d.ts +34 -0
  30. package/dist/analyzer.d.ts.map +1 -1
  31. package/dist/analyzer.js +21 -1
  32. package/dist/analyzer.js.map +1 -1
  33. package/dist/browser/circle-ir.js +351 -47
  34. package/dist/core/circle-ir-core.cjs +197 -45
  35. package/dist/core/circle-ir-core.js +197 -45
  36. package/dist/core/extractors/calls.js +131 -1
  37. package/dist/core/extractors/calls.js.map +1 -1
  38. package/dist/core/extractors/cfg.d.ts.map +1 -1
  39. package/dist/core/extractors/cfg.js +17 -9
  40. package/dist/core/extractors/cfg.js.map +1 -1
  41. package/dist/types/index.d.ts +24 -0
  42. package/dist/types/index.d.ts.map +1 -1
  43. package/package.json +1 -1
@@ -7415,8 +7415,16 @@ function extractGoCallInfo(node) {
7415
7415
  if (funcNode.type === "selector_expression") {
7416
7416
  const operand = funcNode.childForFieldName("operand");
7417
7417
  const field = funcNode.childForFieldName("field");
7418
- receiver = operand ? getNodeText(operand) : null;
7419
7418
  methodName = field ? getNodeText(field) : getNodeText(funcNode);
7419
+ if (operand) {
7420
+ const opText = getNodeText(operand);
7421
+ if (operand.type === "identifier") {
7422
+ const resolved = resolveGoLocalReceiverType(opText, node);
7423
+ receiver = resolved !== null ? resolved : opText;
7424
+ } else {
7425
+ receiver = opText;
7426
+ }
7427
+ }
7420
7428
  } else if (funcNode.type === "identifier") {
7421
7429
  methodName = getNodeText(funcNode);
7422
7430
  } else {
@@ -7494,6 +7502,80 @@ function findGoEnclosingFunction(node) {
7494
7502
  }
7495
7503
  return null;
7496
7504
  }
7505
+ function resolveGoLocalReceiverType(operandName, callNode) {
7506
+ let cur = callNode.parent;
7507
+ while (cur) {
7508
+ if (cur.type === "method_declaration") {
7509
+ const receiver = cur.childForFieldName("receiver");
7510
+ if (receiver) {
7511
+ const t = extractGoParamTypeForName(receiver, operandName);
7512
+ if (t !== null) return t;
7513
+ }
7514
+ const params = cur.childForFieldName("parameters");
7515
+ if (params) {
7516
+ const t = extractGoParamTypeForName(params, operandName);
7517
+ if (t !== null) return t;
7518
+ }
7519
+ return null;
7520
+ }
7521
+ if (cur.type === "function_declaration" || cur.type === "func_literal") {
7522
+ const params = cur.childForFieldName("parameters");
7523
+ if (params) {
7524
+ const t = extractGoParamTypeForName(params, operandName);
7525
+ if (t !== null) return t;
7526
+ }
7527
+ return null;
7528
+ }
7529
+ cur = cur.parent;
7530
+ }
7531
+ return null;
7532
+ }
7533
+ function extractGoParamTypeForName(list, operandName) {
7534
+ for (let i2 = 0; i2 < list.namedChildCount; i2++) {
7535
+ const child = list.namedChild(i2);
7536
+ if (!child || child.type !== "parameter_declaration") continue;
7537
+ const typeNode = child.childForFieldName("type");
7538
+ if (!typeNode) continue;
7539
+ let matched = false;
7540
+ for (let j = 0; j < child.namedChildCount; j++) {
7541
+ const c = child.namedChild(j);
7542
+ if (!c) continue;
7543
+ if (c.type === "identifier" && getNodeText(c) === operandName) {
7544
+ matched = true;
7545
+ break;
7546
+ }
7547
+ if (c.type === "identifier_list") {
7548
+ for (let k = 0; k < c.namedChildCount; k++) {
7549
+ const id = c.namedChild(k);
7550
+ if (id && id.type === "identifier" && getNodeText(id) === operandName) {
7551
+ matched = true;
7552
+ break;
7553
+ }
7554
+ }
7555
+ if (matched) break;
7556
+ }
7557
+ }
7558
+ if (!matched) continue;
7559
+ return extractGoTypeLastSegment(typeNode);
7560
+ }
7561
+ return null;
7562
+ }
7563
+ function extractGoTypeLastSegment(typeNode) {
7564
+ if (typeNode.type === "pointer_type") {
7565
+ const inner = typeNode.namedChild(0);
7566
+ return inner ? extractGoTypeLastSegment(inner) : null;
7567
+ }
7568
+ if (typeNode.type === "qualified_type") {
7569
+ const name2 = typeNode.childForFieldName("name");
7570
+ if (name2) return getNodeText(name2);
7571
+ const last = typeNode.namedChild(typeNode.namedChildCount - 1);
7572
+ return last ? getNodeText(last) : null;
7573
+ }
7574
+ if (typeNode.type === "type_identifier" || typeNode.type === "identifier") {
7575
+ return getNodeText(typeNode);
7576
+ }
7577
+ return null;
7578
+ }
7497
7579
 
7498
7580
  // src/core/extractors/imports.ts
7499
7581
  function detectLanguage2(tree) {
@@ -8242,10 +8324,10 @@ function buildCFG(tree, language, cache) {
8242
8324
  const allEdges = [];
8243
8325
  let blockIdCounter = 0;
8244
8326
  if (effectiveLanguage === "bash") {
8245
- return buildBashCFG(tree, blockIdCounter);
8327
+ return buildBashCFG(tree, blockIdCounter, cache);
8246
8328
  }
8247
8329
  if (effectiveLanguage === "go") {
8248
- return buildGoCFG(tree, blockIdCounter);
8330
+ return buildGoCFG(tree, blockIdCounter, cache);
8249
8331
  }
8250
8332
  if (isJavaScript) {
8251
8333
  const functions = [
@@ -8602,11 +8684,11 @@ function processSwitchStatement(stmt, startId, blocks, edges, isJavaScript) {
8602
8684
  nextId: currentId
8603
8685
  };
8604
8686
  }
8605
- function buildBashCFG(tree, startId) {
8687
+ function buildBashCFG(tree, startId, cache) {
8606
8688
  const allBlocks = [];
8607
8689
  const allEdges = [];
8608
8690
  let blockIdCounter = startId;
8609
- const functions = findNodes(tree.rootNode, "function_definition");
8691
+ const functions = getNodesFromCache(tree.rootNode, "function_definition", cache);
8610
8692
  for (const func2 of functions) {
8611
8693
  const body2 = func2.childForFieldName("body");
8612
8694
  if (!body2) continue;
@@ -8723,12 +8805,12 @@ function isStatement(node, isJavaScript) {
8723
8805
  ]);
8724
8806
  return isJavaScript ? jsStatementTypes.has(node.type) : javaStatementTypes.has(node.type);
8725
8807
  }
8726
- function buildGoCFG(tree, blockIdCounter) {
8808
+ function buildGoCFG(tree, blockIdCounter, cache) {
8727
8809
  const allBlocks = [];
8728
8810
  const allEdges = [];
8729
8811
  const functions = [
8730
- ...findNodes(tree.rootNode, "function_declaration"),
8731
- ...findNodes(tree.rootNode, "method_declaration")
8812
+ ...getNodesFromCache(tree.rootNode, "function_declaration", cache),
8813
+ ...getNodesFromCache(tree.rootNode, "method_declaration", cache)
8732
8814
  ];
8733
8815
  for (const func2 of functions) {
8734
8816
  const body2 = func2.childForFieldName("body");
@@ -10642,6 +10724,59 @@ var OPEN_REDIRECT_FRAMEWORK_SINKS = [
10642
10724
  { method: "Redirect", class: "Context", type: "open_redirect", cwe: "CWE-601", severity: "medium", arg_positions: [1], languages: ["go"] },
10643
10725
  { method: "Redirect", class: "Ctx", type: "open_redirect", cwe: "CWE-601", severity: "medium", arg_positions: [0], languages: ["go"] }
10644
10726
  ];
10727
+ var DESERIALIZATION_FRAMEWORK_SINKS = [
10728
+ // --- Python: stdlib + popular third-party --------------------------------
10729
+ // pickle: known-dangerous, any unpickle on untrusted bytes is RCE.
10730
+ { method: "loads", class: "pickle", type: "deserialization", cwe: "CWE-502", severity: "critical", arg_positions: [0], languages: ["python"] },
10731
+ { method: "load", class: "pickle", type: "deserialization", cwe: "CWE-502", severity: "critical", arg_positions: [0], languages: ["python"] },
10732
+ // cPickle alias (Python 2 name, still around in older codebases).
10733
+ { method: "loads", class: "cPickle", type: "deserialization", cwe: "CWE-502", severity: "critical", arg_positions: [0], languages: ["python"] },
10734
+ { method: "load", class: "cPickle", type: "deserialization", cwe: "CWE-502", severity: "critical", arg_positions: [0], languages: ["python"] },
10735
+ // marshal: stdlib code-object deserializer. Loading a tainted bytestring
10736
+ // as a code object followed by `exec` is arbitrary-code execution.
10737
+ { method: "loads", class: "marshal", type: "deserialization", cwe: "CWE-502", severity: "critical", arg_positions: [0], languages: ["python"] },
10738
+ { method: "load", class: "marshal", type: "deserialization", cwe: "CWE-502", severity: "critical", arg_positions: [0], languages: ["python"] },
10739
+ // dill: pickle superset — same RCE profile.
10740
+ { method: "loads", class: "dill", type: "deserialization", cwe: "CWE-502", severity: "critical", arg_positions: [0], languages: ["python"] },
10741
+ { method: "load", class: "dill", type: "deserialization", cwe: "CWE-502", severity: "critical", arg_positions: [0], languages: ["python"] },
10742
+ // jsonpickle: JSON wrapper around pickle — trusts `py/object` marker.
10743
+ { method: "decode", class: "jsonpickle", type: "deserialization", cwe: "CWE-502", severity: "critical", arg_positions: [0], languages: ["python"] },
10744
+ // --- Go: encoding/gob + yaml.Unmarshal -----------------------------------
10745
+ // gob.NewDecoder(r).Decode(&v): tainted io.Reader → arbitrary Go values.
10746
+ { method: "Decode", class: "Decoder", type: "deserialization", cwe: "CWE-502", severity: "high", arg_positions: [0], languages: ["go"] },
10747
+ // gopkg.in/yaml.v2 + v3 top-level function; interface{} target is unsafe.
10748
+ { method: "Unmarshal", class: "yaml", type: "deserialization", cwe: "CWE-502", severity: "high", arg_positions: [0], languages: ["go"] },
10749
+ // --- JS/TS: node-serialize ------------------------------------------------
10750
+ // Known-dangerous — accepts embedded IIFE that runs during deserialize.
10751
+ { method: "unserialize", class: "nodeSerialize", type: "deserialization", cwe: "CWE-502", severity: "critical", arg_positions: [0], languages: ["javascript", "typescript"] }
10752
+ ];
10753
+ var NOSQL_FRAMEWORK_SINKS = [
10754
+ // --- Python: pymongo Collection ------------------------------------------
10755
+ // Every filter-taking Collection method; filter arg[0] is the query dict.
10756
+ { method: "find", class: "Collection", type: "nosql_injection", cwe: "CWE-943", severity: "high", arg_positions: [0], languages: ["python"] },
10757
+ { method: "find_one", class: "Collection", type: "nosql_injection", cwe: "CWE-943", severity: "high", arg_positions: [0], languages: ["python"] },
10758
+ { method: "aggregate", class: "Collection", type: "nosql_injection", cwe: "CWE-943", severity: "high", arg_positions: [0], languages: ["python"] },
10759
+ { method: "update_one", class: "Collection", type: "nosql_injection", cwe: "CWE-943", severity: "high", arg_positions: [0, 1], languages: ["python"] },
10760
+ { method: "update_many", class: "Collection", type: "nosql_injection", cwe: "CWE-943", severity: "high", arg_positions: [0, 1], languages: ["python"] },
10761
+ { method: "delete_one", class: "Collection", type: "nosql_injection", cwe: "CWE-943", severity: "high", arg_positions: [0], languages: ["python"] },
10762
+ { method: "delete_many", class: "Collection", type: "nosql_injection", cwe: "CWE-943", severity: "high", arg_positions: [0], languages: ["python"] },
10763
+ { method: "count_documents", class: "Collection", type: "nosql_injection", cwe: "CWE-943", severity: "high", arg_positions: [0], languages: ["python"] },
10764
+ // --- Java: Spring Data MongoTemplate + native MongoCollection ------------
10765
+ { method: "find", class: "MongoTemplate", type: "nosql_injection", cwe: "CWE-943", severity: "high", arg_positions: [0], languages: ["java"] },
10766
+ { method: "findOne", class: "MongoTemplate", type: "nosql_injection", cwe: "CWE-943", severity: "high", arg_positions: [0], languages: ["java"] },
10767
+ { method: "findAll", class: "MongoTemplate", type: "nosql_injection", cwe: "CWE-943", severity: "high", arg_positions: [0], languages: ["java"] },
10768
+ { method: "find", class: "MongoCollection", type: "nosql_injection", cwe: "CWE-943", severity: "high", arg_positions: [0], languages: ["java"] },
10769
+ { method: "aggregate", class: "MongoCollection", type: "nosql_injection", cwe: "CWE-943", severity: "high", arg_positions: [0], languages: ["java"] },
10770
+ // --- Go: go.mongodb.org/mongo-driver Collection --------------------------
10771
+ // These fire once Go local-receiver type resolution lands (see
10772
+ // taint-matcher.ts + #240 ship 2 Go receiver work). Same gate as the
10773
+ // gin/fiber Ctx sinks in ship 1.
10774
+ { method: "Find", class: "Collection", type: "nosql_injection", cwe: "CWE-943", severity: "high", arg_positions: [1], languages: ["go"] },
10775
+ { method: "FindOne", class: "Collection", type: "nosql_injection", cwe: "CWE-943", severity: "high", arg_positions: [1], languages: ["go"] },
10776
+ { method: "UpdateOne", class: "Collection", type: "nosql_injection", cwe: "CWE-943", severity: "high", arg_positions: [1, 2], languages: ["go"] },
10777
+ { method: "UpdateMany", class: "Collection", type: "nosql_injection", cwe: "CWE-943", severity: "high", arg_positions: [1, 2], languages: ["go"] },
10778
+ { method: "DeleteOne", class: "Collection", type: "nosql_injection", cwe: "CWE-943", severity: "high", arg_positions: [1], languages: ["go"] }
10779
+ ];
10645
10780
  var TRUST_BOUNDARY_FRAMEWORK_SINKS = [
10646
10781
  // --- Python: Django cache write -----------------------------------------
10647
10782
  { method: "set", class: "cache", type: "trust_boundary", cwe: "CWE-501", severity: "medium", arg_positions: [1], languages: ["python"] },
@@ -12325,7 +12460,12 @@ var DEFAULT_SINKS = [
12325
12460
  // keep the DEFAULT_SINKS literal within TypeScript's union-type
12326
12461
  // inference complexity limit (TS2590). See ~lines 661-752.
12327
12462
  ...OPEN_REDIRECT_FRAMEWORK_SINKS,
12328
- ...TRUST_BOUNDARY_FRAMEWORK_SINKS
12463
+ ...TRUST_BOUNDARY_FRAMEWORK_SINKS,
12464
+ // cognium-dev #240 ship 2 — extended framework sinks for
12465
+ // deserialization (CWE-502) and nosql_injection (CWE-943). Same
12466
+ // pattern: constants defined near the ship-1 blocks and spread here.
12467
+ ...DESERIALIZATION_FRAMEWORK_SINKS,
12468
+ ...NOSQL_FRAMEWORK_SINKS
12329
12469
  ];
12330
12470
  var DEFAULT_SANITIZERS = [
12331
12471
  // SQL Injection - proper parameter binding sanitizes input
@@ -12845,11 +12985,11 @@ function attachSourceLineCode(sources, sinks, code) {
12845
12985
  }
12846
12986
  function findSources(calls, types, patterns, sourceLines, language) {
12847
12987
  const sources = [];
12988
+ const patternsForLanguage = language === void 0 ? patterns : patterns.filter(
12989
+ (p) => !p.languages || p.languages.length === 0 || p.languages.includes(language)
12990
+ );
12848
12991
  for (const call of calls) {
12849
- for (const pattern of patterns) {
12850
- if (pattern.languages && pattern.languages.length > 0 && language !== void 0 && !pattern.languages.includes(language)) {
12851
- continue;
12852
- }
12992
+ for (const pattern of patternsForLanguage) {
12853
12993
  if (matchesSourcePattern(call, pattern)) {
12854
12994
  sources.push({
12855
12995
  type: pattern.type,
@@ -12865,11 +13005,8 @@ function findSources(calls, types, patterns, sourceLines, language) {
12865
13005
  for (const type of types) {
12866
13006
  for (const method of type.methods) {
12867
13007
  for (const param of method.parameters) {
12868
- for (const pattern of patterns) {
13008
+ for (const pattern of patternsForLanguage) {
12869
13009
  if (pattern.annotation && pattern.param_tainted) {
12870
- if (pattern.languages && pattern.languages.length > 0 && language !== void 0 && !pattern.languages.includes(language)) {
12871
- continue;
12872
- }
12873
13010
  if (matchesAnnotation(param.annotations, pattern.annotation)) {
12874
13011
  const paramLine = param.line ?? method.start_line;
12875
13012
  sources.push({
@@ -12888,11 +13025,8 @@ function findSources(calls, types, patterns, sourceLines, language) {
12888
13025
  }
12889
13026
  for (const type of types) {
12890
13027
  for (const method of type.methods) {
12891
- for (const pattern of patterns) {
13028
+ for (const pattern of patternsForLanguage) {
12892
13029
  if (!pattern.method_annotation) continue;
12893
- if (pattern.languages && pattern.languages.length > 0 && language !== void 0 && !pattern.languages.includes(language)) {
12894
- continue;
12895
- }
12896
13030
  if (!matchesAnnotation(method.annotations, pattern.method_annotation)) continue;
12897
13031
  for (const param of method.parameters) {
12898
13032
  const paramLine = param.line ?? method.start_line;
@@ -13514,9 +13648,12 @@ function isSafeJinjaRenderCall(call, pattern, language, sourceLines) {
13514
13648
  return false;
13515
13649
  }
13516
13650
  function findSinks(calls, patterns, typeHierarchy, language, sourceLines, types) {
13651
+ const patternsForLanguage = language === void 0 ? patterns : patterns.filter(
13652
+ (p) => !p.languages || p.languages.length === 0 || p.languages.includes(language)
13653
+ );
13517
13654
  const sinkMap = /* @__PURE__ */ new Map();
13518
13655
  for (const call of calls) {
13519
- for (const pattern of patterns) {
13656
+ for (const pattern of patternsForLanguage) {
13520
13657
  if (matchesSinkPattern(call, pattern, typeHierarchy, language)) {
13521
13658
  if (isParameterizedQueryCall(call, pattern)) {
13522
13659
  continue;
@@ -13934,7 +14071,20 @@ function matchesAnnotation(annotations, targetAnnotation) {
13934
14071
  }
13935
14072
  return false;
13936
14073
  }
14074
+ var RECEIVER_MIGHT_BE_CLASS_CACHE = /* @__PURE__ */ new Map();
14075
+ var RECEIVER_MIGHT_BE_CLASS_CACHE_CAP = 1e4;
13937
14076
  function receiverMightBeClass(receiver, className) {
14077
+ const key = receiver + "\0" + className;
14078
+ const cached = RECEIVER_MIGHT_BE_CLASS_CACHE.get(key);
14079
+ if (cached !== void 0) return cached;
14080
+ const result = receiverMightBeClassImpl(receiver, className);
14081
+ if (RECEIVER_MIGHT_BE_CLASS_CACHE.size >= RECEIVER_MIGHT_BE_CLASS_CACHE_CAP) {
14082
+ RECEIVER_MIGHT_BE_CLASS_CACHE.clear();
14083
+ }
14084
+ RECEIVER_MIGHT_BE_CLASS_CACHE.set(key, result);
14085
+ return result;
14086
+ }
14087
+ function receiverMightBeClassImpl(receiver, className) {
13938
14088
  if (className.startsWith("*") && className.length > 1) {
13939
14089
  const suffix = className.slice(1).toLowerCase();
13940
14090
  let simpleReceiver = receiver;
@@ -14638,8 +14788,15 @@ var CodeGraph = class {
14638
14788
  };
14639
14789
 
14640
14790
  // src/analysis/dfg-walk.ts
14791
+ var walkBackwardDefsMemo = /* @__PURE__ */ new WeakMap();
14641
14792
  function walkBackwardDefs(startDefId, chainsByToDef, defById, options = {}) {
14642
14793
  const maxHops = options.maxHops ?? 32;
14794
+ let perFile = walkBackwardDefsMemo.get(chainsByToDef);
14795
+ if (perFile !== void 0) {
14796
+ const key = `${startDefId}|${maxHops}`;
14797
+ const hit = perFile.get(key);
14798
+ if (hit !== void 0) return hit;
14799
+ }
14643
14800
  const visited = /* @__PURE__ */ new Set();
14644
14801
  const lines = /* @__PURE__ */ new Set();
14645
14802
  let hopCapReached = false;
@@ -14671,7 +14828,13 @@ function walkBackwardDefs(startDefId, chainsByToDef, defById, options = {}) {
14671
14828
  queue.push(fromId);
14672
14829
  }
14673
14830
  }
14674
- return { visited, lines, hopCapReached };
14831
+ const result = { visited, lines, hopCapReached };
14832
+ if (perFile === void 0) {
14833
+ perFile = /* @__PURE__ */ new Map();
14834
+ walkBackwardDefsMemo.set(chainsByToDef, perFile);
14835
+ }
14836
+ perFile.set(`${startDefId}|${maxHops}`, result);
14837
+ return result;
14675
14838
  }
14676
14839
 
14677
14840
  // src/analysis/sanitizer-index.ts
@@ -15654,7 +15817,7 @@ var ConstantPropagator = class _ConstantPropagator {
15654
15817
  this.constructorParamPositions.clear();
15655
15818
  this.safePatternFieldsCache = null;
15656
15819
  this.isTaintedExpressionCache = null;
15657
- this.collectClassFields(tree.rootNode);
15820
+ const prePassMethods = this.collectClassFieldsAndMethods(tree.rootNode);
15658
15821
  for (const methodName of sanitizerMethods) {
15659
15822
  this.methodReturnsSanitized.add(methodName);
15660
15823
  }
@@ -15662,7 +15825,7 @@ var ConstantPropagator = class _ConstantPropagator {
15662
15825
  this.source,
15663
15826
  (name2) => this.lookupSymbol(name2)
15664
15827
  );
15665
- this.analyzeMethodReturns(tree.rootNode);
15828
+ this.analyzeMethodReturns(prePassMethods);
15666
15829
  this.seedPythonModuleConstants(tree.rootNode);
15667
15830
  this.visit(tree.rootNode);
15668
15831
  this.refineTaintFromConstants();
@@ -15732,8 +15895,7 @@ var ConstantPropagator = class _ConstantPropagator {
15732
15895
  /**
15733
15896
  * Pre-pass: Analyze all methods to detect those that always return constants or sanitized values.
15734
15897
  */
15735
- analyzeMethodReturns(root) {
15736
- const methods = this.findAllMethods(root);
15898
+ analyzeMethodReturns(methods) {
15737
15899
  for (const method of methods) {
15738
15900
  const methodName = this.getMethodName(method);
15739
15901
  if (!methodName) continue;
@@ -15997,11 +16159,15 @@ var ConstantPropagator = class _ConstantPropagator {
15997
16159
  * Collect all class field names (instance/static variables declared at class level).
15998
16160
  * These are variables declared directly in the class body, not inside methods.
15999
16161
  */
16000
- collectClassFields(root) {
16162
+ collectClassFieldsAndMethods(root) {
16163
+ const methods = [];
16001
16164
  const stack = [root];
16002
16165
  while (stack.length > 0) {
16003
16166
  const n = stack.pop();
16004
16167
  if (!n) continue;
16168
+ if (n.type === "method_declaration" || n.type === "function_declaration") {
16169
+ methods.push(n);
16170
+ }
16005
16171
  if (n.type === "class_body") {
16006
16172
  for (const child of n.children) {
16007
16173
  if (child.type === "field_declaration") {
@@ -16015,14 +16181,15 @@ var ConstantPropagator = class _ConstantPropagator {
16015
16181
  }
16016
16182
  }
16017
16183
  }
16018
- stack.push(child);
16184
+ if (child) stack.push(child);
16019
16185
  }
16020
16186
  continue;
16021
16187
  }
16022
16188
  for (const child of n.children) {
16023
- stack.push(child);
16189
+ if (child) stack.push(child);
16024
16190
  }
16025
16191
  }
16192
+ return methods;
16026
16193
  }
16027
16194
  /**
16028
16195
  * Sprint 9 #55 — seed the symbol table with Python module-level constant
@@ -16180,21 +16347,6 @@ var ConstantPropagator = class _ConstantPropagator {
16180
16347
  this.symbols.set(name2, value);
16181
16348
  }
16182
16349
  }
16183
- findAllMethods(node) {
16184
- const methods = [];
16185
- const stack = [node];
16186
- while (stack.length > 0) {
16187
- const n = stack.pop();
16188
- if (!n) continue;
16189
- if (n.type === "method_declaration" || n.type === "function_declaration") {
16190
- methods.push(n);
16191
- }
16192
- for (const child of n.children) {
16193
- if (child) stack.push(child);
16194
- }
16195
- }
16196
- return methods;
16197
- }
16198
16350
  getMethodName(method) {
16199
16351
  const nameNode = method.childForFieldName("name");
16200
16352
  if (nameNode) {