circle-ir 3.176.0 → 3.177.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.
@@ -7369,8 +7369,16 @@ function extractGoCallInfo(node) {
7369
7369
  if (funcNode.type === "selector_expression") {
7370
7370
  const operand = funcNode.childForFieldName("operand");
7371
7371
  const field = funcNode.childForFieldName("field");
7372
- receiver = operand ? getNodeText(operand) : null;
7373
7372
  methodName = field ? getNodeText(field) : getNodeText(funcNode);
7373
+ if (operand) {
7374
+ const opText = getNodeText(operand);
7375
+ if (operand.type === "identifier") {
7376
+ const resolved = resolveGoLocalReceiverType(opText, node);
7377
+ receiver = resolved !== null ? resolved : opText;
7378
+ } else {
7379
+ receiver = opText;
7380
+ }
7381
+ }
7374
7382
  } else if (funcNode.type === "identifier") {
7375
7383
  methodName = getNodeText(funcNode);
7376
7384
  } else {
@@ -7448,6 +7456,80 @@ function findGoEnclosingFunction(node) {
7448
7456
  }
7449
7457
  return null;
7450
7458
  }
7459
+ function resolveGoLocalReceiverType(operandName, callNode) {
7460
+ let cur = callNode.parent;
7461
+ while (cur) {
7462
+ if (cur.type === "method_declaration") {
7463
+ const receiver = cur.childForFieldName("receiver");
7464
+ if (receiver) {
7465
+ const t = extractGoParamTypeForName(receiver, operandName);
7466
+ if (t !== null) return t;
7467
+ }
7468
+ const params = cur.childForFieldName("parameters");
7469
+ if (params) {
7470
+ const t = extractGoParamTypeForName(params, operandName);
7471
+ if (t !== null) return t;
7472
+ }
7473
+ return null;
7474
+ }
7475
+ if (cur.type === "function_declaration" || cur.type === "func_literal") {
7476
+ const params = cur.childForFieldName("parameters");
7477
+ if (params) {
7478
+ const t = extractGoParamTypeForName(params, operandName);
7479
+ if (t !== null) return t;
7480
+ }
7481
+ return null;
7482
+ }
7483
+ cur = cur.parent;
7484
+ }
7485
+ return null;
7486
+ }
7487
+ function extractGoParamTypeForName(list, operandName) {
7488
+ for (let i2 = 0; i2 < list.namedChildCount; i2++) {
7489
+ const child = list.namedChild(i2);
7490
+ if (!child || child.type !== "parameter_declaration") continue;
7491
+ const typeNode = child.childForFieldName("type");
7492
+ if (!typeNode) continue;
7493
+ let matched = false;
7494
+ for (let j = 0; j < child.namedChildCount; j++) {
7495
+ const c = child.namedChild(j);
7496
+ if (!c) continue;
7497
+ if (c.type === "identifier" && getNodeText(c) === operandName) {
7498
+ matched = true;
7499
+ break;
7500
+ }
7501
+ if (c.type === "identifier_list") {
7502
+ for (let k = 0; k < c.namedChildCount; k++) {
7503
+ const id = c.namedChild(k);
7504
+ if (id && id.type === "identifier" && getNodeText(id) === operandName) {
7505
+ matched = true;
7506
+ break;
7507
+ }
7508
+ }
7509
+ if (matched) break;
7510
+ }
7511
+ }
7512
+ if (!matched) continue;
7513
+ return extractGoTypeLastSegment(typeNode);
7514
+ }
7515
+ return null;
7516
+ }
7517
+ function extractGoTypeLastSegment(typeNode) {
7518
+ if (typeNode.type === "pointer_type") {
7519
+ const inner = typeNode.namedChild(0);
7520
+ return inner ? extractGoTypeLastSegment(inner) : null;
7521
+ }
7522
+ if (typeNode.type === "qualified_type") {
7523
+ const name2 = typeNode.childForFieldName("name");
7524
+ if (name2) return getNodeText(name2);
7525
+ const last = typeNode.namedChild(typeNode.namedChildCount - 1);
7526
+ return last ? getNodeText(last) : null;
7527
+ }
7528
+ if (typeNode.type === "type_identifier" || typeNode.type === "identifier") {
7529
+ return getNodeText(typeNode);
7530
+ }
7531
+ return null;
7532
+ }
7451
7533
 
7452
7534
  // src/core/extractors/imports.ts
7453
7535
  function detectLanguage2(tree) {
@@ -8196,10 +8278,10 @@ function buildCFG(tree, language, cache) {
8196
8278
  const allEdges = [];
8197
8279
  let blockIdCounter = 0;
8198
8280
  if (effectiveLanguage === "bash") {
8199
- return buildBashCFG(tree, blockIdCounter);
8281
+ return buildBashCFG(tree, blockIdCounter, cache);
8200
8282
  }
8201
8283
  if (effectiveLanguage === "go") {
8202
- return buildGoCFG(tree, blockIdCounter);
8284
+ return buildGoCFG(tree, blockIdCounter, cache);
8203
8285
  }
8204
8286
  if (isJavaScript) {
8205
8287
  const functions = [
@@ -8556,11 +8638,11 @@ function processSwitchStatement(stmt, startId, blocks, edges, isJavaScript) {
8556
8638
  nextId: currentId
8557
8639
  };
8558
8640
  }
8559
- function buildBashCFG(tree, startId) {
8641
+ function buildBashCFG(tree, startId, cache) {
8560
8642
  const allBlocks = [];
8561
8643
  const allEdges = [];
8562
8644
  let blockIdCounter = startId;
8563
- const functions = findNodes(tree.rootNode, "function_definition");
8645
+ const functions = getNodesFromCache(tree.rootNode, "function_definition", cache);
8564
8646
  for (const func2 of functions) {
8565
8647
  const body2 = func2.childForFieldName("body");
8566
8648
  if (!body2) continue;
@@ -8677,12 +8759,12 @@ function isStatement(node, isJavaScript) {
8677
8759
  ]);
8678
8760
  return isJavaScript ? jsStatementTypes.has(node.type) : javaStatementTypes.has(node.type);
8679
8761
  }
8680
- function buildGoCFG(tree, blockIdCounter) {
8762
+ function buildGoCFG(tree, blockIdCounter, cache) {
8681
8763
  const allBlocks = [];
8682
8764
  const allEdges = [];
8683
8765
  const functions = [
8684
- ...findNodes(tree.rootNode, "function_declaration"),
8685
- ...findNodes(tree.rootNode, "method_declaration")
8766
+ ...getNodesFromCache(tree.rootNode, "function_declaration", cache),
8767
+ ...getNodesFromCache(tree.rootNode, "method_declaration", cache)
8686
8768
  ];
8687
8769
  for (const func2 of functions) {
8688
8770
  const body2 = func2.childForFieldName("body");
@@ -11247,6 +11329,59 @@ var OPEN_REDIRECT_FRAMEWORK_SINKS = [
11247
11329
  { method: "Redirect", class: "Context", type: "open_redirect", cwe: "CWE-601", severity: "medium", arg_positions: [1], languages: ["go"] },
11248
11330
  { method: "Redirect", class: "Ctx", type: "open_redirect", cwe: "CWE-601", severity: "medium", arg_positions: [0], languages: ["go"] }
11249
11331
  ];
11332
+ var DESERIALIZATION_FRAMEWORK_SINKS = [
11333
+ // --- Python: stdlib + popular third-party --------------------------------
11334
+ // pickle: known-dangerous, any unpickle on untrusted bytes is RCE.
11335
+ { method: "loads", class: "pickle", type: "deserialization", cwe: "CWE-502", severity: "critical", arg_positions: [0], languages: ["python"] },
11336
+ { method: "load", class: "pickle", type: "deserialization", cwe: "CWE-502", severity: "critical", arg_positions: [0], languages: ["python"] },
11337
+ // cPickle alias (Python 2 name, still around in older codebases).
11338
+ { method: "loads", class: "cPickle", type: "deserialization", cwe: "CWE-502", severity: "critical", arg_positions: [0], languages: ["python"] },
11339
+ { method: "load", class: "cPickle", type: "deserialization", cwe: "CWE-502", severity: "critical", arg_positions: [0], languages: ["python"] },
11340
+ // marshal: stdlib code-object deserializer. Loading a tainted bytestring
11341
+ // as a code object followed by `exec` is arbitrary-code execution.
11342
+ { method: "loads", class: "marshal", type: "deserialization", cwe: "CWE-502", severity: "critical", arg_positions: [0], languages: ["python"] },
11343
+ { method: "load", class: "marshal", type: "deserialization", cwe: "CWE-502", severity: "critical", arg_positions: [0], languages: ["python"] },
11344
+ // dill: pickle superset — same RCE profile.
11345
+ { method: "loads", class: "dill", type: "deserialization", cwe: "CWE-502", severity: "critical", arg_positions: [0], languages: ["python"] },
11346
+ { method: "load", class: "dill", type: "deserialization", cwe: "CWE-502", severity: "critical", arg_positions: [0], languages: ["python"] },
11347
+ // jsonpickle: JSON wrapper around pickle — trusts `py/object` marker.
11348
+ { method: "decode", class: "jsonpickle", type: "deserialization", cwe: "CWE-502", severity: "critical", arg_positions: [0], languages: ["python"] },
11349
+ // --- Go: encoding/gob + yaml.Unmarshal -----------------------------------
11350
+ // gob.NewDecoder(r).Decode(&v): tainted io.Reader → arbitrary Go values.
11351
+ { method: "Decode", class: "Decoder", type: "deserialization", cwe: "CWE-502", severity: "high", arg_positions: [0], languages: ["go"] },
11352
+ // gopkg.in/yaml.v2 + v3 top-level function; interface{} target is unsafe.
11353
+ { method: "Unmarshal", class: "yaml", type: "deserialization", cwe: "CWE-502", severity: "high", arg_positions: [0], languages: ["go"] },
11354
+ // --- JS/TS: node-serialize ------------------------------------------------
11355
+ // Known-dangerous — accepts embedded IIFE that runs during deserialize.
11356
+ { method: "unserialize", class: "nodeSerialize", type: "deserialization", cwe: "CWE-502", severity: "critical", arg_positions: [0], languages: ["javascript", "typescript"] }
11357
+ ];
11358
+ var NOSQL_FRAMEWORK_SINKS = [
11359
+ // --- Python: pymongo Collection ------------------------------------------
11360
+ // Every filter-taking Collection method; filter arg[0] is the query dict.
11361
+ { method: "find", class: "Collection", type: "nosql_injection", cwe: "CWE-943", severity: "high", arg_positions: [0], languages: ["python"] },
11362
+ { method: "find_one", class: "Collection", type: "nosql_injection", cwe: "CWE-943", severity: "high", arg_positions: [0], languages: ["python"] },
11363
+ { method: "aggregate", class: "Collection", type: "nosql_injection", cwe: "CWE-943", severity: "high", arg_positions: [0], languages: ["python"] },
11364
+ { method: "update_one", class: "Collection", type: "nosql_injection", cwe: "CWE-943", severity: "high", arg_positions: [0, 1], languages: ["python"] },
11365
+ { method: "update_many", class: "Collection", type: "nosql_injection", cwe: "CWE-943", severity: "high", arg_positions: [0, 1], languages: ["python"] },
11366
+ { method: "delete_one", class: "Collection", type: "nosql_injection", cwe: "CWE-943", severity: "high", arg_positions: [0], languages: ["python"] },
11367
+ { method: "delete_many", class: "Collection", type: "nosql_injection", cwe: "CWE-943", severity: "high", arg_positions: [0], languages: ["python"] },
11368
+ { method: "count_documents", class: "Collection", type: "nosql_injection", cwe: "CWE-943", severity: "high", arg_positions: [0], languages: ["python"] },
11369
+ // --- Java: Spring Data MongoTemplate + native MongoCollection ------------
11370
+ { method: "find", class: "MongoTemplate", type: "nosql_injection", cwe: "CWE-943", severity: "high", arg_positions: [0], languages: ["java"] },
11371
+ { method: "findOne", class: "MongoTemplate", type: "nosql_injection", cwe: "CWE-943", severity: "high", arg_positions: [0], languages: ["java"] },
11372
+ { method: "findAll", class: "MongoTemplate", type: "nosql_injection", cwe: "CWE-943", severity: "high", arg_positions: [0], languages: ["java"] },
11373
+ { method: "find", class: "MongoCollection", type: "nosql_injection", cwe: "CWE-943", severity: "high", arg_positions: [0], languages: ["java"] },
11374
+ { method: "aggregate", class: "MongoCollection", type: "nosql_injection", cwe: "CWE-943", severity: "high", arg_positions: [0], languages: ["java"] },
11375
+ // --- Go: go.mongodb.org/mongo-driver Collection --------------------------
11376
+ // These fire once Go local-receiver type resolution lands (see
11377
+ // taint-matcher.ts + #240 ship 2 Go receiver work). Same gate as the
11378
+ // gin/fiber Ctx sinks in ship 1.
11379
+ { method: "Find", class: "Collection", type: "nosql_injection", cwe: "CWE-943", severity: "high", arg_positions: [1], languages: ["go"] },
11380
+ { method: "FindOne", class: "Collection", type: "nosql_injection", cwe: "CWE-943", severity: "high", arg_positions: [1], languages: ["go"] },
11381
+ { method: "UpdateOne", class: "Collection", type: "nosql_injection", cwe: "CWE-943", severity: "high", arg_positions: [1, 2], languages: ["go"] },
11382
+ { method: "UpdateMany", class: "Collection", type: "nosql_injection", cwe: "CWE-943", severity: "high", arg_positions: [1, 2], languages: ["go"] },
11383
+ { method: "DeleteOne", class: "Collection", type: "nosql_injection", cwe: "CWE-943", severity: "high", arg_positions: [1], languages: ["go"] }
11384
+ ];
11250
11385
  var TRUST_BOUNDARY_FRAMEWORK_SINKS = [
11251
11386
  // --- Python: Django cache write -----------------------------------------
11252
11387
  { method: "set", class: "cache", type: "trust_boundary", cwe: "CWE-501", severity: "medium", arg_positions: [1], languages: ["python"] },
@@ -12930,7 +13065,12 @@ var DEFAULT_SINKS = [
12930
13065
  // keep the DEFAULT_SINKS literal within TypeScript's union-type
12931
13066
  // inference complexity limit (TS2590). See ~lines 661-752.
12932
13067
  ...OPEN_REDIRECT_FRAMEWORK_SINKS,
12933
- ...TRUST_BOUNDARY_FRAMEWORK_SINKS
13068
+ ...TRUST_BOUNDARY_FRAMEWORK_SINKS,
13069
+ // cognium-dev #240 ship 2 — extended framework sinks for
13070
+ // deserialization (CWE-502) and nosql_injection (CWE-943). Same
13071
+ // pattern: constants defined near the ship-1 blocks and spread here.
13072
+ ...DESERIALIZATION_FRAMEWORK_SINKS,
13073
+ ...NOSQL_FRAMEWORK_SINKS
12934
13074
  ];
12935
13075
  var DEFAULT_SANITIZERS = [
12936
13076
  // SQL Injection - proper parameter binding sanitizes input
@@ -13545,11 +13685,11 @@ function attachSourceLineCode(sources, sinks, code) {
13545
13685
  }
13546
13686
  function findSources(calls, types, patterns, sourceLines, language) {
13547
13687
  const sources = [];
13688
+ const patternsForLanguage = language === void 0 ? patterns : patterns.filter(
13689
+ (p) => !p.languages || p.languages.length === 0 || p.languages.includes(language)
13690
+ );
13548
13691
  for (const call of calls) {
13549
- for (const pattern of patterns) {
13550
- if (pattern.languages && pattern.languages.length > 0 && language !== void 0 && !pattern.languages.includes(language)) {
13551
- continue;
13552
- }
13692
+ for (const pattern of patternsForLanguage) {
13553
13693
  if (matchesSourcePattern(call, pattern)) {
13554
13694
  sources.push({
13555
13695
  type: pattern.type,
@@ -13565,11 +13705,8 @@ function findSources(calls, types, patterns, sourceLines, language) {
13565
13705
  for (const type of types) {
13566
13706
  for (const method of type.methods) {
13567
13707
  for (const param of method.parameters) {
13568
- for (const pattern of patterns) {
13708
+ for (const pattern of patternsForLanguage) {
13569
13709
  if (pattern.annotation && pattern.param_tainted) {
13570
- if (pattern.languages && pattern.languages.length > 0 && language !== void 0 && !pattern.languages.includes(language)) {
13571
- continue;
13572
- }
13573
13710
  if (matchesAnnotation(param.annotations, pattern.annotation)) {
13574
13711
  const paramLine = param.line ?? method.start_line;
13575
13712
  sources.push({
@@ -13588,11 +13725,8 @@ function findSources(calls, types, patterns, sourceLines, language) {
13588
13725
  }
13589
13726
  for (const type of types) {
13590
13727
  for (const method of type.methods) {
13591
- for (const pattern of patterns) {
13728
+ for (const pattern of patternsForLanguage) {
13592
13729
  if (!pattern.method_annotation) continue;
13593
- if (pattern.languages && pattern.languages.length > 0 && language !== void 0 && !pattern.languages.includes(language)) {
13594
- continue;
13595
- }
13596
13730
  if (!matchesAnnotation(method.annotations, pattern.method_annotation)) continue;
13597
13731
  for (const param of method.parameters) {
13598
13732
  const paramLine = param.line ?? method.start_line;
@@ -14214,9 +14348,12 @@ function isSafeJinjaRenderCall(call, pattern, language, sourceLines) {
14214
14348
  return false;
14215
14349
  }
14216
14350
  function findSinks(calls, patterns, typeHierarchy, language, sourceLines, types) {
14351
+ const patternsForLanguage = language === void 0 ? patterns : patterns.filter(
14352
+ (p) => !p.languages || p.languages.length === 0 || p.languages.includes(language)
14353
+ );
14217
14354
  const sinkMap = /* @__PURE__ */ new Map();
14218
14355
  for (const call of calls) {
14219
- for (const pattern of patterns) {
14356
+ for (const pattern of patternsForLanguage) {
14220
14357
  if (matchesSinkPattern(call, pattern, typeHierarchy, language)) {
14221
14358
  if (isParameterizedQueryCall(call, pattern)) {
14222
14359
  continue;
@@ -14634,7 +14771,20 @@ function matchesAnnotation(annotations, targetAnnotation) {
14634
14771
  }
14635
14772
  return false;
14636
14773
  }
14774
+ var RECEIVER_MIGHT_BE_CLASS_CACHE = /* @__PURE__ */ new Map();
14775
+ var RECEIVER_MIGHT_BE_CLASS_CACHE_CAP = 1e4;
14637
14776
  function receiverMightBeClass(receiver, className) {
14777
+ const key = receiver + "\0" + className;
14778
+ const cached = RECEIVER_MIGHT_BE_CLASS_CACHE.get(key);
14779
+ if (cached !== void 0) return cached;
14780
+ const result = receiverMightBeClassImpl(receiver, className);
14781
+ if (RECEIVER_MIGHT_BE_CLASS_CACHE.size >= RECEIVER_MIGHT_BE_CLASS_CACHE_CAP) {
14782
+ RECEIVER_MIGHT_BE_CLASS_CACHE.clear();
14783
+ }
14784
+ RECEIVER_MIGHT_BE_CLASS_CACHE.set(key, result);
14785
+ return result;
14786
+ }
14787
+ function receiverMightBeClassImpl(receiver, className) {
14638
14788
  if (className.startsWith("*") && className.length > 1) {
14639
14789
  const suffix = className.slice(1).toLowerCase();
14640
14790
  let simpleReceiver = receiver;
@@ -16653,8 +16803,15 @@ var AnalysisPipeline = class {
16653
16803
  };
16654
16804
 
16655
16805
  // src/analysis/dfg-walk.ts
16806
+ var walkBackwardDefsMemo = /* @__PURE__ */ new WeakMap();
16656
16807
  function walkBackwardDefs(startDefId, chainsByToDef, defById, options = {}) {
16657
16808
  const maxHops = options.maxHops ?? 32;
16809
+ let perFile = walkBackwardDefsMemo.get(chainsByToDef);
16810
+ if (perFile !== void 0) {
16811
+ const key = `${startDefId}|${maxHops}`;
16812
+ const hit = perFile.get(key);
16813
+ if (hit !== void 0) return hit;
16814
+ }
16658
16815
  const visited = /* @__PURE__ */ new Set();
16659
16816
  const lines = /* @__PURE__ */ new Set();
16660
16817
  let hopCapReached = false;
@@ -16686,7 +16843,13 @@ function walkBackwardDefs(startDefId, chainsByToDef, defById, options = {}) {
16686
16843
  queue.push(fromId);
16687
16844
  }
16688
16845
  }
16689
- return { visited, lines, hopCapReached };
16846
+ const result = { visited, lines, hopCapReached };
16847
+ if (perFile === void 0) {
16848
+ perFile = /* @__PURE__ */ new Map();
16849
+ walkBackwardDefsMemo.set(chainsByToDef, perFile);
16850
+ }
16851
+ perFile.set(`${startDefId}|${maxHops}`, result);
16852
+ return result;
16690
16853
  }
16691
16854
 
16692
16855
  // src/analysis/sanitizer-index.ts
@@ -18196,7 +18359,7 @@ var ConstantPropagator = class _ConstantPropagator {
18196
18359
  this.constructorParamPositions.clear();
18197
18360
  this.safePatternFieldsCache = null;
18198
18361
  this.isTaintedExpressionCache = null;
18199
- this.collectClassFields(tree.rootNode);
18362
+ const prePassMethods = this.collectClassFieldsAndMethods(tree.rootNode);
18200
18363
  for (const methodName of sanitizerMethods) {
18201
18364
  this.methodReturnsSanitized.add(methodName);
18202
18365
  }
@@ -18204,7 +18367,7 @@ var ConstantPropagator = class _ConstantPropagator {
18204
18367
  this.source,
18205
18368
  (name2) => this.lookupSymbol(name2)
18206
18369
  );
18207
- this.analyzeMethodReturns(tree.rootNode);
18370
+ this.analyzeMethodReturns(prePassMethods);
18208
18371
  this.seedPythonModuleConstants(tree.rootNode);
18209
18372
  this.visit(tree.rootNode);
18210
18373
  this.refineTaintFromConstants();
@@ -18274,8 +18437,7 @@ var ConstantPropagator = class _ConstantPropagator {
18274
18437
  /**
18275
18438
  * Pre-pass: Analyze all methods to detect those that always return constants or sanitized values.
18276
18439
  */
18277
- analyzeMethodReturns(root) {
18278
- const methods = this.findAllMethods(root);
18440
+ analyzeMethodReturns(methods) {
18279
18441
  for (const method of methods) {
18280
18442
  const methodName = this.getMethodName(method);
18281
18443
  if (!methodName) continue;
@@ -18539,11 +18701,15 @@ var ConstantPropagator = class _ConstantPropagator {
18539
18701
  * Collect all class field names (instance/static variables declared at class level).
18540
18702
  * These are variables declared directly in the class body, not inside methods.
18541
18703
  */
18542
- collectClassFields(root) {
18704
+ collectClassFieldsAndMethods(root) {
18705
+ const methods = [];
18543
18706
  const stack = [root];
18544
18707
  while (stack.length > 0) {
18545
18708
  const n = stack.pop();
18546
18709
  if (!n) continue;
18710
+ if (n.type === "method_declaration" || n.type === "function_declaration") {
18711
+ methods.push(n);
18712
+ }
18547
18713
  if (n.type === "class_body") {
18548
18714
  for (const child of n.children) {
18549
18715
  if (child.type === "field_declaration") {
@@ -18557,14 +18723,15 @@ var ConstantPropagator = class _ConstantPropagator {
18557
18723
  }
18558
18724
  }
18559
18725
  }
18560
- stack.push(child);
18726
+ if (child) stack.push(child);
18561
18727
  }
18562
18728
  continue;
18563
18729
  }
18564
18730
  for (const child of n.children) {
18565
- stack.push(child);
18731
+ if (child) stack.push(child);
18566
18732
  }
18567
18733
  }
18734
+ return methods;
18568
18735
  }
18569
18736
  /**
18570
18737
  * Sprint 9 #55 — seed the symbol table with Python module-level constant
@@ -18722,21 +18889,6 @@ var ConstantPropagator = class _ConstantPropagator {
18722
18889
  this.symbols.set(name2, value);
18723
18890
  }
18724
18891
  }
18725
- findAllMethods(node) {
18726
- const methods = [];
18727
- const stack = [node];
18728
- while (stack.length > 0) {
18729
- const n = stack.pop();
18730
- if (!n) continue;
18731
- if (n.type === "method_declaration" || n.type === "function_declaration") {
18732
- methods.push(n);
18733
- }
18734
- for (const child of n.children) {
18735
- if (child) stack.push(child);
18736
- }
18737
- }
18738
- return methods;
18739
- }
18740
18892
  getMethodName(method) {
18741
18893
  const nameNode = method.childForFieldName("name");
18742
18894
  if (nameNode) {
@@ -31287,6 +31439,12 @@ var DATA_PARSER_TYPES = /* @__PURE__ */ new Set([
31287
31439
  "OptionParser",
31288
31440
  "CmdLineParser"
31289
31441
  ]);
31442
+ var JAVA_EVAL_PARSER_DENYLIST = /* @__PURE__ */ new Set([
31443
+ "GroovyShell",
31444
+ "GroovyClassLoader",
31445
+ "ScriptEngine",
31446
+ "CronParser"
31447
+ ]);
31290
31448
  var COMPILED_TEMPLATE_TYPES = /* @__PURE__ */ new Set([
31291
31449
  "Template",
31292
31450
  // Freemarker, Velocity
@@ -31729,7 +31887,12 @@ var SinkFilterPass = class {
31729
31887
  const method = sink.method ?? receiverMatch?.[2];
31730
31888
  if (method === "parse" && receiver) {
31731
31889
  const recvType = resolveJavaReceiverType(receiver, sink.line, sourceLines);
31732
- if (recvType && DATA_PARSER_TYPES.has(recvType)) return false;
31890
+ if (recvType) {
31891
+ if (DATA_PARSER_TYPES.has(recvType)) return false;
31892
+ if (recvType.endsWith("Parser") && !JAVA_EVAL_PARSER_DENYLIST.has(recvType)) {
31893
+ return false;
31894
+ }
31895
+ }
31733
31896
  }
31734
31897
  if ((method === "render" || method === "process" || method === "merge" || method === "renderTo") && receiver) {
31735
31898
  const recvType = resolveJavaReceiverType(receiver, sink.line, sourceLines);