circle-ir 4.16.0 → 4.17.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 (59) hide show
  1. package/dist/analysis/constant-propagation/index.d.ts +2 -0
  2. package/dist/analysis/constant-propagation/index.d.ts.map +1 -1
  3. package/dist/analysis/constant-propagation/index.js +10 -0
  4. package/dist/analysis/constant-propagation/index.js.map +1 -1
  5. package/dist/analysis/constant-propagation/propagator.d.ts +11 -0
  6. package/dist/analysis/constant-propagation/propagator.d.ts.map +1 -1
  7. package/dist/analysis/constant-propagation/propagator.js +125 -20
  8. package/dist/analysis/constant-propagation/propagator.js.map +1 -1
  9. package/dist/analysis/constant-propagation/types.d.ts +2 -0
  10. package/dist/analysis/constant-propagation/types.d.ts.map +1 -1
  11. package/dist/analysis/constant-propagation.d.ts +1 -1
  12. package/dist/analysis/constant-propagation.d.ts.map +1 -1
  13. package/dist/analysis/constant-propagation.js +1 -1
  14. package/dist/analysis/constant-propagation.js.map +1 -1
  15. package/dist/analysis/interprocedural.d.ts.map +1 -1
  16. package/dist/analysis/interprocedural.js +37 -8
  17. package/dist/analysis/interprocedural.js.map +1 -1
  18. package/dist/analysis/passes/cross-file-pass.d.ts.map +1 -1
  19. package/dist/analysis/passes/cross-file-pass.js +3 -1
  20. package/dist/analysis/passes/cross-file-pass.js.map +1 -1
  21. package/dist/analysis/passes/csharp-sanitizer-credit.d.ts +13 -0
  22. package/dist/analysis/passes/csharp-sanitizer-credit.d.ts.map +1 -1
  23. package/dist/analysis/passes/csharp-sanitizer-credit.js +48 -0
  24. package/dist/analysis/passes/csharp-sanitizer-credit.js.map +1 -1
  25. package/dist/analysis/passes/language-sources-pass.d.ts.map +1 -1
  26. package/dist/analysis/passes/language-sources-pass.js +80 -8
  27. package/dist/analysis/passes/language-sources-pass.js.map +1 -1
  28. package/dist/analysis/passes/param-source-flow-gate-pass.d.ts.map +1 -1
  29. package/dist/analysis/passes/param-source-flow-gate-pass.js +17 -1
  30. package/dist/analysis/passes/param-source-flow-gate-pass.js.map +1 -1
  31. package/dist/analysis/passes/sink-filter-pass.d.ts.map +1 -1
  32. package/dist/analysis/passes/sink-filter-pass.js +19 -3
  33. package/dist/analysis/passes/sink-filter-pass.js.map +1 -1
  34. package/dist/analysis/passes/taint-propagation-pass.d.ts.map +1 -1
  35. package/dist/analysis/passes/taint-propagation-pass.js +205 -22
  36. package/dist/analysis/passes/taint-propagation-pass.js.map +1 -1
  37. package/dist/analysis/rust-literal-guard.d.ts.map +1 -1
  38. package/dist/analysis/rust-literal-guard.js +100 -1
  39. package/dist/analysis/rust-literal-guard.js.map +1 -1
  40. package/dist/analysis/taint-matcher.d.ts.map +1 -1
  41. package/dist/analysis/taint-matcher.js +1 -0
  42. package/dist/analysis/taint-matcher.js.map +1 -1
  43. package/dist/browser/circle-ir.js +561 -41
  44. package/dist/core/circle-ir-core.cjs +149 -17
  45. package/dist/core/circle-ir-core.js +149 -17
  46. package/dist/core/extractors/types.js +53 -9
  47. package/dist/core/extractors/types.js.map +1 -1
  48. package/dist/navigation/navigation-index.d.ts +40 -1
  49. package/dist/navigation/navigation-index.d.ts.map +1 -1
  50. package/dist/navigation/navigation-index.js +300 -45
  51. package/dist/navigation/navigation-index.js.map +1 -1
  52. package/dist/resolution/cross-file.d.ts +9 -0
  53. package/dist/resolution/cross-file.d.ts.map +1 -1
  54. package/dist/resolution/cross-file.js +134 -0
  55. package/dist/resolution/cross-file.js.map +1 -1
  56. package/dist/types/index.d.ts +18 -0
  57. package/dist/types/index.d.ts.map +1 -1
  58. package/dist/types/index.js.map +1 -1
  59. package/package.json +1 -1
@@ -4709,14 +4709,43 @@ function extractJavaTypes(tree, cache, options) {
4709
4709
  );
4710
4710
  const path = node ? enclosingTypePath(node) : [];
4711
4711
  if (path.length > 0) t.enclosing_type = path.join(".");
4712
- const params = node?.childForFieldName("type_parameters");
4713
- if (params) {
4714
- const names = params.namedChildren.filter((c) => c?.type === "type_parameter").map((c) => c.namedChildren.find((x) => x?.type === "type_identifier" || x?.type === "identifier")).map((x) => x ? getNodeText(x) : "").filter(Boolean);
4715
- if (names.length > 0) t.type_parameters = names;
4712
+ const tp = typeParameters(node);
4713
+ if (tp.names.length > 0) t.type_parameters = tp.names;
4714
+ if (Object.keys(tp.bounds).length > 0) t.type_parameter_bounds = tp.bounds;
4715
+ }
4716
+ const allMethods = /* @__PURE__ */ new Map();
4717
+ for (const t of types) {
4718
+ for (const m of t.methods) allMethods.set(`${m.start_line}:${m.name}`, m);
4719
+ }
4720
+ for (const kind of ["method_declaration", "constructor_declaration"]) {
4721
+ for (const node of getNodesFromCache(tree.rootNode, kind, cache)) {
4722
+ const name2 = getIdentifier(node, "name");
4723
+ const info2 = name2 ? allMethods.get(`${node.startPosition.row + 1}:${name2}`) : void 0;
4724
+ if (!info2) continue;
4725
+ const tp = typeParameters(node);
4726
+ if (tp.names.length > 0) info2.type_parameters = tp.names;
4727
+ if (Object.keys(tp.bounds).length > 0) info2.type_parameter_bounds = tp.bounds;
4716
4728
  }
4717
4729
  }
4718
4730
  return types;
4719
4731
  }
4732
+ function typeParameters(node) {
4733
+ const names = [];
4734
+ const bounds = {};
4735
+ const list = node ? node.childForFieldName("type_parameters") ?? node.namedChildren.find((c) => c?.type === "type_parameters") ?? null : null;
4736
+ if (!list) return { names, bounds };
4737
+ for (const c of list.namedChildren) {
4738
+ if (c?.type !== "type_parameter") continue;
4739
+ const id = c.namedChildren.find((x) => x?.type === "type_identifier" || x?.type === "identifier");
4740
+ const name2 = id ? getNodeText(id) : "";
4741
+ if (!name2) continue;
4742
+ names.push(name2);
4743
+ const bound = c.namedChildren.find((x) => x?.type === "type_bound");
4744
+ const first = bound?.namedChildren.find((x) => x !== null && x.type !== "annotation" && x.type !== "marker_annotation");
4745
+ if (first) bounds[name2] = getNodeText(first);
4746
+ }
4747
+ return { names, bounds };
4748
+ }
4720
4749
  function recordComponents(node) {
4721
4750
  const out2 = [];
4722
4751
  const params = node.childForFieldName("parameters");
@@ -16490,6 +16519,9 @@ function isInterproceduralTaintableType(typeName, language) {
16490
16519
  "IEnumerable",
16491
16520
  "ICollection",
16492
16521
  "IList",
16522
+ "List",
16523
+ "Hashtable",
16524
+ "ArrayList",
16493
16525
  "Dictionary",
16494
16526
  "IDictionary",
16495
16527
  "IReadOnlyList",
@@ -19793,6 +19825,11 @@ var ConstantPropagator = class _ConstantPropagator {
19793
19825
  taintedArrayElements = /* @__PURE__ */ new Map();
19794
19826
  // Track current method name for scoping local variables
19795
19827
  currentMethod = null;
19828
+ /** Start lines of nested `{ ... }` blocks — disambiguate same-named locals (#476 Collections10). */
19829
+ blockScopeStack = [];
19830
+ lineBlockScopes = /* @__PURE__ */ new Map();
19831
+ /** Skip block-scope push on the method/constructor body `{ ... }` (#476). */
19832
+ skipMethodBodyBlockScope = false;
19796
19833
  // Track conditional taints: which variables were tainted under which conditions
19797
19834
  // Maps condition expression string → set of variables tainted under that condition
19798
19835
  conditionalTaints = /* @__PURE__ */ new Map();
@@ -19806,6 +19843,8 @@ var ConstantPropagator = class _ConstantPropagator {
19806
19843
  inSynchronizedBlock = false;
19807
19844
  // Track iterator sources: iterator variable name → collection name it was created from
19808
19845
  iteratorSources = /* @__PURE__ */ new Map();
19846
+ /** Vars assigned from `iter.next()` — do not retro-clear on later iterator rebind (#476). */
19847
+ taintedFromIteratorNext = /* @__PURE__ */ new Set();
19809
19848
  // Track class field names (declared at class level, not local variables)
19810
19849
  classFields = /* @__PURE__ */ new Set();
19811
19850
  // Track tainted method parameters for inter-procedural analysis
@@ -19863,12 +19902,16 @@ var ConstantPropagator = class _ConstantPropagator {
19863
19902
  this.sanitizedVars.clear();
19864
19903
  this.sanitizedVarUncovered.clear();
19865
19904
  this.currentMethod = null;
19905
+ this.blockScopeStack = [];
19906
+ this.lineBlockScopes.clear();
19907
+ this.skipMethodBodyBlockScope = false;
19866
19908
  this.conditionalTaints.clear();
19867
19909
  this.conditionStack = [];
19868
19910
  this.lineConditions.clear();
19869
19911
  this.synchronizedLines.clear();
19870
19912
  this.inSynchronizedBlock = false;
19871
19913
  this.iteratorSources.clear();
19914
+ this.taintedFromIteratorNext.clear();
19872
19915
  this.classFields.clear();
19873
19916
  this.instanceFieldTaint.clear();
19874
19917
  this.currentClassName = null;
@@ -19893,6 +19936,9 @@ var ConstantPropagator = class _ConstantPropagator {
19893
19936
  const resultSymbols = new Map(this.symbols);
19894
19937
  for (const name2 of this.tainted) {
19895
19938
  if (name2.includes(":")) {
19939
+ if (/^[^:]+:b\d+:/.test(name2)) {
19940
+ continue;
19941
+ }
19896
19942
  const unscoped = name2.substring(name2.indexOf(":") + 1);
19897
19943
  resultTainted.add(unscoped);
19898
19944
  }
@@ -19907,6 +19953,9 @@ var ConstantPropagator = class _ConstantPropagator {
19907
19953
  for (const [name2, value] of this.symbols) {
19908
19954
  if (this.goScopedNames.has(name2)) continue;
19909
19955
  if (name2.includes(":")) {
19956
+ if (/^[^:]+:b\d+:/.test(name2)) {
19957
+ continue;
19958
+ }
19910
19959
  const unscoped = name2.substring(name2.indexOf(":") + 1);
19911
19960
  if (!resultSymbols.has(unscoped)) {
19912
19961
  resultSymbols.set(unscoped, value);
@@ -19916,6 +19965,7 @@ var ConstantPropagator = class _ConstantPropagator {
19916
19965
  return {
19917
19966
  symbols: resultSymbols,
19918
19967
  tainted: resultTainted,
19968
+ lineBlockScopes: new Map(this.lineBlockScopes),
19919
19969
  unreachableLines: new Set(this.unreachableLines),
19920
19970
  taintedCollections: new Map(this.taintedCollections),
19921
19971
  taintedArrayElements: new Map(this.taintedArrayElements),
@@ -20459,6 +20509,18 @@ var ConstantPropagator = class _ConstantPropagator {
20459
20509
  // ===========================================================================
20460
20510
  // Taint Refinement
20461
20511
  // ===========================================================================
20512
+ /** True when `node` is `iter.next()` (possibly under a cast). */
20513
+ isIteratorNextExpression(node) {
20514
+ const text = getNodeText2(node, this.source);
20515
+ if (/\.\s*next\s*\(/.test(text)) return true;
20516
+ let current = node;
20517
+ if (current.type === "cast_expression" || current.type === "parenthesized_expression") {
20518
+ current = current.childForFieldName("operand") ?? current.childForFieldName("expression");
20519
+ }
20520
+ if (!current || current.type !== "method_invocation") return false;
20521
+ const nameNode = current.childForFieldName("name");
20522
+ return nameNode ? getNodeText2(nameNode, this.source) === "next" : false;
20523
+ }
20462
20524
  refineTaintFromConstants() {
20463
20525
  let changed = true;
20464
20526
  let iterations = 0;
@@ -20468,6 +20530,9 @@ var ConstantPropagator = class _ConstantPropagator {
20468
20530
  iterations++;
20469
20531
  const toRemove = [];
20470
20532
  for (const varName of this.tainted) {
20533
+ if (this.taintedFromIteratorNext.has(varName)) {
20534
+ continue;
20535
+ }
20471
20536
  const symbol = this.symbols.get(varName);
20472
20537
  if (symbol && symbol.type !== "unknown") {
20473
20538
  toRemove.push(varName);
@@ -20516,6 +20581,9 @@ var ConstantPropagator = class _ConstantPropagator {
20516
20581
  if (this.unreachableLines.has(line)) {
20517
20582
  return true;
20518
20583
  }
20584
+ if (this.blockScopeStack.length > 0) {
20585
+ this.lineBlockScopes.set(line, this.blockScopeStack[this.blockScopeStack.length - 1]);
20586
+ }
20519
20587
  if (this.conditionStack.length > 0 && !this.lineConditions.has(line)) {
20520
20588
  this.lineConditions.set(line, this.conditionStack[this.conditionStack.length - 1]);
20521
20589
  }
@@ -20547,6 +20615,15 @@ var ConstantPropagator = class _ConstantPropagator {
20547
20615
  case "if_statement":
20548
20616
  this.handleIfStatement(node);
20549
20617
  return true;
20618
+ case "block": {
20619
+ if (this.skipMethodBodyBlockScope) {
20620
+ this.skipMethodBodyBlockScope = false;
20621
+ }
20622
+ for (const child of node.children) {
20623
+ this.visit(child);
20624
+ }
20625
+ return true;
20626
+ }
20550
20627
  case "switch_expression":
20551
20628
  case "switch_statement":
20552
20629
  this.handleSwitch(node);
@@ -20632,7 +20709,9 @@ var ConstantPropagator = class _ConstantPropagator {
20632
20709
  }
20633
20710
  const body2 = node.childForFieldName("body");
20634
20711
  if (body2) {
20712
+ this.skipMethodBodyBlockScope = true;
20635
20713
  this.visit(body2);
20714
+ this.skipMethodBodyBlockScope = false;
20636
20715
  }
20637
20716
  this.currentMethod = prevMethod;
20638
20717
  this.inConstructor = prevInConstructor;
@@ -20646,10 +20725,31 @@ var ConstantPropagator = class _ConstantPropagator {
20646
20725
  if (varName.includes(".") || varName.includes(":")) {
20647
20726
  return varName;
20648
20727
  }
20728
+ const blockId = this.blockScopeStack.length > 0 ? this.blockScopeStack[this.blockScopeStack.length - 1] : void 0;
20649
20729
  if (this.currentMethod) {
20650
- return `${this.currentMethod}:${varName}`;
20730
+ return blockId !== void 0 ? `${this.currentMethod}:b${blockId}:${varName}` : `${this.currentMethod}:${varName}`;
20651
20731
  }
20652
- return varName;
20732
+ return blockId !== void 0 ? `b${blockId}:${varName}` : varName;
20733
+ }
20734
+ /** Innermost block → method → unscoped, for reads that may bind to an outer declaration. */
20735
+ getScopedNameCandidates(varName) {
20736
+ if (varName.includes(".") || varName.includes(":")) {
20737
+ return [varName];
20738
+ }
20739
+ const out2 = [];
20740
+ for (let i2 = this.blockScopeStack.length - 1; i2 >= 0; i2--) {
20741
+ const blockId = this.blockScopeStack[i2];
20742
+ if (this.currentMethod) {
20743
+ out2.push(`${this.currentMethod}:b${blockId}:${varName}`);
20744
+ } else {
20745
+ out2.push(`b${blockId}:${varName}`);
20746
+ }
20747
+ }
20748
+ if (this.currentMethod) {
20749
+ out2.push(`${this.currentMethod}:${varName}`);
20750
+ }
20751
+ out2.push(varName);
20752
+ return out2;
20653
20753
  }
20654
20754
  /**
20655
20755
  * Look up a variable value, checking both scoped and unscoped names.
@@ -20657,11 +20757,12 @@ var ConstantPropagator = class _ConstantPropagator {
20657
20757
  * either local (scoped) or global (unscoped, like class fields).
20658
20758
  */
20659
20759
  lookupSymbol(varName) {
20660
- if (this.currentMethod && !varName.includes(".") && !varName.includes(":")) {
20661
- const scopedName = `${this.currentMethod}:${varName}`;
20662
- const scopedValue = this.symbols.get(scopedName);
20663
- if (scopedValue) {
20664
- return scopedValue;
20760
+ if (!varName.includes(".") && !varName.includes(":")) {
20761
+ for (const scopedName of this.getScopedNameCandidates(varName)) {
20762
+ const scopedValue = this.symbols.get(scopedName);
20763
+ if (scopedValue) {
20764
+ return scopedValue;
20765
+ }
20665
20766
  }
20666
20767
  }
20667
20768
  return this.symbols.get(varName);
@@ -20713,8 +20814,22 @@ var ConstantPropagator = class _ConstantPropagator {
20713
20814
  this.trackIteratorsInNode(initNode);
20714
20815
  }
20715
20816
  }
20716
- for (const child of node.children) {
20717
- this.visit(child);
20817
+ if (node.type === "while_statement" || node.type === "do_statement") {
20818
+ const body2 = node.childForFieldName("body");
20819
+ for (const child of node.children) {
20820
+ if (child !== body2) this.visit(child);
20821
+ }
20822
+ if (body2?.type === "block") {
20823
+ this.blockScopeStack.push(getNodeLine(body2));
20824
+ this.visit(body2);
20825
+ this.blockScopeStack.pop();
20826
+ } else if (body2) {
20827
+ this.visit(body2);
20828
+ }
20829
+ } else {
20830
+ for (const child of node.children) {
20831
+ this.visit(child);
20832
+ }
20718
20833
  }
20719
20834
  for (const varName of loopVarNames) {
20720
20835
  this.symbols.set(varName, createUnknown(getNodeLine(node)));
@@ -20856,6 +20971,10 @@ var ConstantPropagator = class _ConstantPropagator {
20856
20971
  this.sanitizedVars.delete(scopedName);
20857
20972
  this.sanitizedVarUncovered.delete(scopedName);
20858
20973
  this.definitionNodes.set(scopedName, valueNode);
20974
+ if (this.isIteratorNextExpression(valueNode)) {
20975
+ this.taintedFromIteratorNext.add(scopedName);
20976
+ this.taintedFromIteratorNext.add(varName);
20977
+ }
20859
20978
  this.symbols.set(scopedName, createUnknown(line));
20860
20979
  } else {
20861
20980
  if (this.inConditionalBranch) {
@@ -20983,6 +21102,12 @@ var ConstantPropagator = class _ConstantPropagator {
20983
21102
  this.sanitizedVars.delete(scopedName);
20984
21103
  this.sanitizedVarUncovered.delete(scopedName);
20985
21104
  this.definitionNodes.set(scopedName, right);
21105
+ if (this.isIteratorNextExpression(right)) {
21106
+ this.taintedFromIteratorNext.add(scopedName);
21107
+ if (!varName.includes(".")) {
21108
+ this.taintedFromIteratorNext.add(varName);
21109
+ }
21110
+ }
20986
21111
  this.symbols.set(scopedName, createUnknown(line));
20987
21112
  if (this.inConstructor && varName.startsWith("this.")) {
20988
21113
  const fieldName = varName.substring(5);
@@ -21973,10 +22098,17 @@ var ConstantPropagator = class _ConstantPropagator {
21973
22098
  }
21974
22099
  }
21975
22100
  if (node.type === "identifier") {
21976
- const scopedName = this.currentMethod ? `${this.currentMethod}:${text}` : text;
21977
- const isTainted = this.tainted.has(scopedName) || this.tainted.has(text);
21978
- if (isTainted) {
21979
- if (this.isExcludedByCondition(scopedName) || this.isExcludedByCondition(text)) {
22101
+ let isTainted = false;
22102
+ let boundName = null;
22103
+ for (const candidate of this.getScopedNameCandidates(text)) {
22104
+ if (this.tainted.has(candidate)) {
22105
+ isTainted = true;
22106
+ boundName = candidate;
22107
+ break;
22108
+ }
22109
+ }
22110
+ if (isTainted && boundName) {
22111
+ if (this.isExcludedByCondition(boundName) || this.isExcludedByCondition(text)) {
21980
22112
  return false;
21981
22113
  }
21982
22114
  }
@@ -4643,14 +4643,43 @@ function extractJavaTypes(tree, cache, options) {
4643
4643
  );
4644
4644
  const path = node ? enclosingTypePath(node) : [];
4645
4645
  if (path.length > 0) t.enclosing_type = path.join(".");
4646
- const params = node?.childForFieldName("type_parameters");
4647
- if (params) {
4648
- const names = params.namedChildren.filter((c) => c?.type === "type_parameter").map((c) => c.namedChildren.find((x) => x?.type === "type_identifier" || x?.type === "identifier")).map((x) => x ? getNodeText(x) : "").filter(Boolean);
4649
- if (names.length > 0) t.type_parameters = names;
4646
+ const tp = typeParameters(node);
4647
+ if (tp.names.length > 0) t.type_parameters = tp.names;
4648
+ if (Object.keys(tp.bounds).length > 0) t.type_parameter_bounds = tp.bounds;
4649
+ }
4650
+ const allMethods = /* @__PURE__ */ new Map();
4651
+ for (const t of types) {
4652
+ for (const m of t.methods) allMethods.set(`${m.start_line}:${m.name}`, m);
4653
+ }
4654
+ for (const kind of ["method_declaration", "constructor_declaration"]) {
4655
+ for (const node of getNodesFromCache(tree.rootNode, kind, cache)) {
4656
+ const name2 = getIdentifier(node, "name");
4657
+ const info2 = name2 ? allMethods.get(`${node.startPosition.row + 1}:${name2}`) : void 0;
4658
+ if (!info2) continue;
4659
+ const tp = typeParameters(node);
4660
+ if (tp.names.length > 0) info2.type_parameters = tp.names;
4661
+ if (Object.keys(tp.bounds).length > 0) info2.type_parameter_bounds = tp.bounds;
4650
4662
  }
4651
4663
  }
4652
4664
  return types;
4653
4665
  }
4666
+ function typeParameters(node) {
4667
+ const names = [];
4668
+ const bounds = {};
4669
+ const list = node ? node.childForFieldName("type_parameters") ?? node.namedChildren.find((c) => c?.type === "type_parameters") ?? null : null;
4670
+ if (!list) return { names, bounds };
4671
+ for (const c of list.namedChildren) {
4672
+ if (c?.type !== "type_parameter") continue;
4673
+ const id = c.namedChildren.find((x) => x?.type === "type_identifier" || x?.type === "identifier");
4674
+ const name2 = id ? getNodeText(id) : "";
4675
+ if (!name2) continue;
4676
+ names.push(name2);
4677
+ const bound = c.namedChildren.find((x) => x?.type === "type_bound");
4678
+ const first = bound?.namedChildren.find((x) => x !== null && x.type !== "annotation" && x.type !== "marker_annotation");
4679
+ if (first) bounds[name2] = getNodeText(first);
4680
+ }
4681
+ return { names, bounds };
4682
+ }
4654
4683
  function recordComponents(node) {
4655
4684
  const out2 = [];
4656
4685
  const params = node.childForFieldName("parameters");
@@ -16424,6 +16453,9 @@ function isInterproceduralTaintableType(typeName, language) {
16424
16453
  "IEnumerable",
16425
16454
  "ICollection",
16426
16455
  "IList",
16456
+ "List",
16457
+ "Hashtable",
16458
+ "ArrayList",
16427
16459
  "Dictionary",
16428
16460
  "IDictionary",
16429
16461
  "IReadOnlyList",
@@ -19727,6 +19759,11 @@ var ConstantPropagator = class _ConstantPropagator {
19727
19759
  taintedArrayElements = /* @__PURE__ */ new Map();
19728
19760
  // Track current method name for scoping local variables
19729
19761
  currentMethod = null;
19762
+ /** Start lines of nested `{ ... }` blocks — disambiguate same-named locals (#476 Collections10). */
19763
+ blockScopeStack = [];
19764
+ lineBlockScopes = /* @__PURE__ */ new Map();
19765
+ /** Skip block-scope push on the method/constructor body `{ ... }` (#476). */
19766
+ skipMethodBodyBlockScope = false;
19730
19767
  // Track conditional taints: which variables were tainted under which conditions
19731
19768
  // Maps condition expression string → set of variables tainted under that condition
19732
19769
  conditionalTaints = /* @__PURE__ */ new Map();
@@ -19740,6 +19777,8 @@ var ConstantPropagator = class _ConstantPropagator {
19740
19777
  inSynchronizedBlock = false;
19741
19778
  // Track iterator sources: iterator variable name → collection name it was created from
19742
19779
  iteratorSources = /* @__PURE__ */ new Map();
19780
+ /** Vars assigned from `iter.next()` — do not retro-clear on later iterator rebind (#476). */
19781
+ taintedFromIteratorNext = /* @__PURE__ */ new Set();
19743
19782
  // Track class field names (declared at class level, not local variables)
19744
19783
  classFields = /* @__PURE__ */ new Set();
19745
19784
  // Track tainted method parameters for inter-procedural analysis
@@ -19797,12 +19836,16 @@ var ConstantPropagator = class _ConstantPropagator {
19797
19836
  this.sanitizedVars.clear();
19798
19837
  this.sanitizedVarUncovered.clear();
19799
19838
  this.currentMethod = null;
19839
+ this.blockScopeStack = [];
19840
+ this.lineBlockScopes.clear();
19841
+ this.skipMethodBodyBlockScope = false;
19800
19842
  this.conditionalTaints.clear();
19801
19843
  this.conditionStack = [];
19802
19844
  this.lineConditions.clear();
19803
19845
  this.synchronizedLines.clear();
19804
19846
  this.inSynchronizedBlock = false;
19805
19847
  this.iteratorSources.clear();
19848
+ this.taintedFromIteratorNext.clear();
19806
19849
  this.classFields.clear();
19807
19850
  this.instanceFieldTaint.clear();
19808
19851
  this.currentClassName = null;
@@ -19827,6 +19870,9 @@ var ConstantPropagator = class _ConstantPropagator {
19827
19870
  const resultSymbols = new Map(this.symbols);
19828
19871
  for (const name2 of this.tainted) {
19829
19872
  if (name2.includes(":")) {
19873
+ if (/^[^:]+:b\d+:/.test(name2)) {
19874
+ continue;
19875
+ }
19830
19876
  const unscoped = name2.substring(name2.indexOf(":") + 1);
19831
19877
  resultTainted.add(unscoped);
19832
19878
  }
@@ -19841,6 +19887,9 @@ var ConstantPropagator = class _ConstantPropagator {
19841
19887
  for (const [name2, value] of this.symbols) {
19842
19888
  if (this.goScopedNames.has(name2)) continue;
19843
19889
  if (name2.includes(":")) {
19890
+ if (/^[^:]+:b\d+:/.test(name2)) {
19891
+ continue;
19892
+ }
19844
19893
  const unscoped = name2.substring(name2.indexOf(":") + 1);
19845
19894
  if (!resultSymbols.has(unscoped)) {
19846
19895
  resultSymbols.set(unscoped, value);
@@ -19850,6 +19899,7 @@ var ConstantPropagator = class _ConstantPropagator {
19850
19899
  return {
19851
19900
  symbols: resultSymbols,
19852
19901
  tainted: resultTainted,
19902
+ lineBlockScopes: new Map(this.lineBlockScopes),
19853
19903
  unreachableLines: new Set(this.unreachableLines),
19854
19904
  taintedCollections: new Map(this.taintedCollections),
19855
19905
  taintedArrayElements: new Map(this.taintedArrayElements),
@@ -20393,6 +20443,18 @@ var ConstantPropagator = class _ConstantPropagator {
20393
20443
  // ===========================================================================
20394
20444
  // Taint Refinement
20395
20445
  // ===========================================================================
20446
+ /** True when `node` is `iter.next()` (possibly under a cast). */
20447
+ isIteratorNextExpression(node) {
20448
+ const text = getNodeText2(node, this.source);
20449
+ if (/\.\s*next\s*\(/.test(text)) return true;
20450
+ let current = node;
20451
+ if (current.type === "cast_expression" || current.type === "parenthesized_expression") {
20452
+ current = current.childForFieldName("operand") ?? current.childForFieldName("expression");
20453
+ }
20454
+ if (!current || current.type !== "method_invocation") return false;
20455
+ const nameNode = current.childForFieldName("name");
20456
+ return nameNode ? getNodeText2(nameNode, this.source) === "next" : false;
20457
+ }
20396
20458
  refineTaintFromConstants() {
20397
20459
  let changed = true;
20398
20460
  let iterations = 0;
@@ -20402,6 +20464,9 @@ var ConstantPropagator = class _ConstantPropagator {
20402
20464
  iterations++;
20403
20465
  const toRemove = [];
20404
20466
  for (const varName of this.tainted) {
20467
+ if (this.taintedFromIteratorNext.has(varName)) {
20468
+ continue;
20469
+ }
20405
20470
  const symbol = this.symbols.get(varName);
20406
20471
  if (symbol && symbol.type !== "unknown") {
20407
20472
  toRemove.push(varName);
@@ -20450,6 +20515,9 @@ var ConstantPropagator = class _ConstantPropagator {
20450
20515
  if (this.unreachableLines.has(line)) {
20451
20516
  return true;
20452
20517
  }
20518
+ if (this.blockScopeStack.length > 0) {
20519
+ this.lineBlockScopes.set(line, this.blockScopeStack[this.blockScopeStack.length - 1]);
20520
+ }
20453
20521
  if (this.conditionStack.length > 0 && !this.lineConditions.has(line)) {
20454
20522
  this.lineConditions.set(line, this.conditionStack[this.conditionStack.length - 1]);
20455
20523
  }
@@ -20481,6 +20549,15 @@ var ConstantPropagator = class _ConstantPropagator {
20481
20549
  case "if_statement":
20482
20550
  this.handleIfStatement(node);
20483
20551
  return true;
20552
+ case "block": {
20553
+ if (this.skipMethodBodyBlockScope) {
20554
+ this.skipMethodBodyBlockScope = false;
20555
+ }
20556
+ for (const child of node.children) {
20557
+ this.visit(child);
20558
+ }
20559
+ return true;
20560
+ }
20484
20561
  case "switch_expression":
20485
20562
  case "switch_statement":
20486
20563
  this.handleSwitch(node);
@@ -20566,7 +20643,9 @@ var ConstantPropagator = class _ConstantPropagator {
20566
20643
  }
20567
20644
  const body2 = node.childForFieldName("body");
20568
20645
  if (body2) {
20646
+ this.skipMethodBodyBlockScope = true;
20569
20647
  this.visit(body2);
20648
+ this.skipMethodBodyBlockScope = false;
20570
20649
  }
20571
20650
  this.currentMethod = prevMethod;
20572
20651
  this.inConstructor = prevInConstructor;
@@ -20580,10 +20659,31 @@ var ConstantPropagator = class _ConstantPropagator {
20580
20659
  if (varName.includes(".") || varName.includes(":")) {
20581
20660
  return varName;
20582
20661
  }
20662
+ const blockId = this.blockScopeStack.length > 0 ? this.blockScopeStack[this.blockScopeStack.length - 1] : void 0;
20583
20663
  if (this.currentMethod) {
20584
- return `${this.currentMethod}:${varName}`;
20664
+ return blockId !== void 0 ? `${this.currentMethod}:b${blockId}:${varName}` : `${this.currentMethod}:${varName}`;
20585
20665
  }
20586
- return varName;
20666
+ return blockId !== void 0 ? `b${blockId}:${varName}` : varName;
20667
+ }
20668
+ /** Innermost block → method → unscoped, for reads that may bind to an outer declaration. */
20669
+ getScopedNameCandidates(varName) {
20670
+ if (varName.includes(".") || varName.includes(":")) {
20671
+ return [varName];
20672
+ }
20673
+ const out2 = [];
20674
+ for (let i2 = this.blockScopeStack.length - 1; i2 >= 0; i2--) {
20675
+ const blockId = this.blockScopeStack[i2];
20676
+ if (this.currentMethod) {
20677
+ out2.push(`${this.currentMethod}:b${blockId}:${varName}`);
20678
+ } else {
20679
+ out2.push(`b${blockId}:${varName}`);
20680
+ }
20681
+ }
20682
+ if (this.currentMethod) {
20683
+ out2.push(`${this.currentMethod}:${varName}`);
20684
+ }
20685
+ out2.push(varName);
20686
+ return out2;
20587
20687
  }
20588
20688
  /**
20589
20689
  * Look up a variable value, checking both scoped and unscoped names.
@@ -20591,11 +20691,12 @@ var ConstantPropagator = class _ConstantPropagator {
20591
20691
  * either local (scoped) or global (unscoped, like class fields).
20592
20692
  */
20593
20693
  lookupSymbol(varName) {
20594
- if (this.currentMethod && !varName.includes(".") && !varName.includes(":")) {
20595
- const scopedName = `${this.currentMethod}:${varName}`;
20596
- const scopedValue = this.symbols.get(scopedName);
20597
- if (scopedValue) {
20598
- return scopedValue;
20694
+ if (!varName.includes(".") && !varName.includes(":")) {
20695
+ for (const scopedName of this.getScopedNameCandidates(varName)) {
20696
+ const scopedValue = this.symbols.get(scopedName);
20697
+ if (scopedValue) {
20698
+ return scopedValue;
20699
+ }
20599
20700
  }
20600
20701
  }
20601
20702
  return this.symbols.get(varName);
@@ -20647,8 +20748,22 @@ var ConstantPropagator = class _ConstantPropagator {
20647
20748
  this.trackIteratorsInNode(initNode);
20648
20749
  }
20649
20750
  }
20650
- for (const child of node.children) {
20651
- this.visit(child);
20751
+ if (node.type === "while_statement" || node.type === "do_statement") {
20752
+ const body2 = node.childForFieldName("body");
20753
+ for (const child of node.children) {
20754
+ if (child !== body2) this.visit(child);
20755
+ }
20756
+ if (body2?.type === "block") {
20757
+ this.blockScopeStack.push(getNodeLine(body2));
20758
+ this.visit(body2);
20759
+ this.blockScopeStack.pop();
20760
+ } else if (body2) {
20761
+ this.visit(body2);
20762
+ }
20763
+ } else {
20764
+ for (const child of node.children) {
20765
+ this.visit(child);
20766
+ }
20652
20767
  }
20653
20768
  for (const varName of loopVarNames) {
20654
20769
  this.symbols.set(varName, createUnknown(getNodeLine(node)));
@@ -20790,6 +20905,10 @@ var ConstantPropagator = class _ConstantPropagator {
20790
20905
  this.sanitizedVars.delete(scopedName);
20791
20906
  this.sanitizedVarUncovered.delete(scopedName);
20792
20907
  this.definitionNodes.set(scopedName, valueNode);
20908
+ if (this.isIteratorNextExpression(valueNode)) {
20909
+ this.taintedFromIteratorNext.add(scopedName);
20910
+ this.taintedFromIteratorNext.add(varName);
20911
+ }
20793
20912
  this.symbols.set(scopedName, createUnknown(line));
20794
20913
  } else {
20795
20914
  if (this.inConditionalBranch) {
@@ -20917,6 +21036,12 @@ var ConstantPropagator = class _ConstantPropagator {
20917
21036
  this.sanitizedVars.delete(scopedName);
20918
21037
  this.sanitizedVarUncovered.delete(scopedName);
20919
21038
  this.definitionNodes.set(scopedName, right);
21039
+ if (this.isIteratorNextExpression(right)) {
21040
+ this.taintedFromIteratorNext.add(scopedName);
21041
+ if (!varName.includes(".")) {
21042
+ this.taintedFromIteratorNext.add(varName);
21043
+ }
21044
+ }
20920
21045
  this.symbols.set(scopedName, createUnknown(line));
20921
21046
  if (this.inConstructor && varName.startsWith("this.")) {
20922
21047
  const fieldName = varName.substring(5);
@@ -21907,10 +22032,17 @@ var ConstantPropagator = class _ConstantPropagator {
21907
22032
  }
21908
22033
  }
21909
22034
  if (node.type === "identifier") {
21910
- const scopedName = this.currentMethod ? `${this.currentMethod}:${text}` : text;
21911
- const isTainted = this.tainted.has(scopedName) || this.tainted.has(text);
21912
- if (isTainted) {
21913
- if (this.isExcludedByCondition(scopedName) || this.isExcludedByCondition(text)) {
22035
+ let isTainted = false;
22036
+ let boundName = null;
22037
+ for (const candidate of this.getScopedNameCandidates(text)) {
22038
+ if (this.tainted.has(candidate)) {
22039
+ isTainted = true;
22040
+ boundName = candidate;
22041
+ break;
22042
+ }
22043
+ }
22044
+ if (isTainted && boundName) {
22045
+ if (this.isExcludedByCondition(boundName) || this.isExcludedByCondition(text)) {
21914
22046
  return false;
21915
22047
  }
21916
22048
  }