circle-ir 4.11.0 → 4.13.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 (40) hide show
  1. package/README.md +9 -9
  2. package/dist/analysis/config-loader.d.ts.map +1 -1
  3. package/dist/analysis/config-loader.js +8 -2
  4. package/dist/analysis/config-loader.js.map +1 -1
  5. package/dist/analysis/constant-propagation/propagator.d.ts +2 -0
  6. package/dist/analysis/constant-propagation/propagator.d.ts.map +1 -1
  7. package/dist/analysis/constant-propagation/propagator.js +56 -2
  8. package/dist/analysis/constant-propagation/propagator.js.map +1 -1
  9. package/dist/analysis/entry-point-detection.d.ts +7 -0
  10. package/dist/analysis/entry-point-detection.d.ts.map +1 -1
  11. package/dist/analysis/entry-point-detection.js +13 -1
  12. package/dist/analysis/entry-point-detection.js.map +1 -1
  13. package/dist/analysis/interprocedural.d.ts.map +1 -1
  14. package/dist/analysis/interprocedural.js +4 -0
  15. package/dist/analysis/interprocedural.js.map +1 -1
  16. package/dist/analysis/passes/csharp-sanitizer-credit.d.ts +147 -0
  17. package/dist/analysis/passes/csharp-sanitizer-credit.d.ts.map +1 -0
  18. package/dist/analysis/passes/csharp-sanitizer-credit.js +564 -0
  19. package/dist/analysis/passes/csharp-sanitizer-credit.js.map +1 -0
  20. package/dist/analysis/passes/interprocedural-pass.d.ts.map +1 -1
  21. package/dist/analysis/passes/interprocedural-pass.js +89 -0
  22. package/dist/analysis/passes/interprocedural-pass.js.map +1 -1
  23. package/dist/analysis/passes/language-sources-pass.d.ts +6 -1
  24. package/dist/analysis/passes/language-sources-pass.d.ts.map +1 -1
  25. package/dist/analysis/passes/language-sources-pass.js +15 -2
  26. package/dist/analysis/passes/language-sources-pass.js.map +1 -1
  27. package/dist/analysis/passes/sink-filter-pass.d.ts.map +1 -1
  28. package/dist/analysis/passes/sink-filter-pass.js +49 -52
  29. package/dist/analysis/passes/sink-filter-pass.js.map +1 -1
  30. package/dist/analysis/passes/taint-propagation-pass.d.ts.map +1 -1
  31. package/dist/analysis/passes/taint-propagation-pass.js +348 -16
  32. package/dist/analysis/passes/taint-propagation-pass.js.map +1 -1
  33. package/dist/analysis/require-entry-path.js +50 -4
  34. package/dist/analysis/require-entry-path.js.map +1 -1
  35. package/dist/browser/circle-ir.js +861 -65
  36. package/dist/core/circle-ir-core.cjs +81 -4
  37. package/dist/core/circle-ir-core.js +81 -4
  38. package/dist/core/extractors/dfg.js +49 -0
  39. package/dist/core/extractors/dfg.js.map +1 -1
  40. package/package.json +1 -1
@@ -9667,6 +9667,7 @@ function buildCSharpDFG(tree, cache) {
9667
9667
  currentScope(scopeStack).set(name2, def.id);
9668
9668
  }
9669
9669
  }
9670
+ const plainTargets = [];
9670
9671
  for (const asn of findNodes(body2, "assignment_expression")) {
9671
9672
  const left = asn.childForFieldName("left");
9672
9673
  if (left?.type === "identifier") {
@@ -9674,9 +9675,16 @@ function buildCSharpDFG(tree, cache) {
9674
9675
  const def = { id: defIdCounter++, variable: name2, line: asn.startPosition.row + 1, kind: "local" };
9675
9676
  defs.push(def);
9676
9677
  currentScope(scopeStack).set(name2, def.id);
9678
+ if (asn.children.some((c) => c?.type === "=")) {
9679
+ plainTargets.push({ name: name2, line: left.startPosition.row + 1 });
9680
+ }
9677
9681
  }
9678
9682
  }
9679
9683
  const bodyUses = extractUses(body2, useIdCounter, scopeStack, false);
9684
+ for (const target of plainTargets) {
9685
+ const idx = bodyUses.uses.findIndex((u) => u.variable === target.name && u.line === target.line);
9686
+ if (idx !== -1) bodyUses.uses.splice(idx, 1);
9687
+ }
9680
9688
  resolveReassignedUses(defs.slice(methodDefStart), bodyUses.uses);
9681
9689
  uses.push(...bodyUses.uses);
9682
9690
  useIdCounter = bodyUses.nextId;
@@ -10016,6 +10024,30 @@ function processStatement2(stmt, startDefId, startUseId, scopeStack, isJavaScrip
10016
10024
  }
10017
10025
  break;
10018
10026
  }
10027
+ case "switch_expression":
10028
+ case "switch_statement": {
10029
+ const value = stmt.childForFieldName("condition") ?? stmt.childForFieldName("value");
10030
+ if (value) {
10031
+ const valueUses = extractUses(value, useId, scopeStack, isJavaScript);
10032
+ uses.push(...valueUses.uses);
10033
+ useId = valueUses.nextId;
10034
+ }
10035
+ const body2 = stmt.childForFieldName("body");
10036
+ if (body2) {
10037
+ scopeStack.push(/* @__PURE__ */ new Map());
10038
+ for (let i2 = 0; i2 < body2.childCount; i2++) {
10039
+ const group = body2.child(i2);
10040
+ if (!group) continue;
10041
+ const result = processBlock(group, defId, useId, scopeStack, isJavaScript);
10042
+ defs.push(...result.defs);
10043
+ uses.push(...result.uses);
10044
+ defId = result.nextDefId;
10045
+ useId = result.nextUseId;
10046
+ }
10047
+ scopeStack.pop();
10048
+ }
10049
+ break;
10050
+ }
10019
10051
  case "block":
10020
10052
  case "statement_block": {
10021
10053
  scopeStack.push(/* @__PURE__ */ new Map());
@@ -11416,6 +11448,7 @@ var DEFAULT_SOURCES = [
11416
11448
  { method: "get", class: "ConcurrentHashMap", type: "plugin_param", severity: "high", return_tainted: true },
11417
11449
  { method: "get", class: "Hashtable", type: "plugin_param", severity: "high", return_tainted: true },
11418
11450
  { method: "get", class: "Properties", type: "config_param", severity: "high", return_tainted: true },
11451
+ { method: "getProperty", class: "Properties", type: "config_param", severity: "high", return_tainted: true },
11419
11452
  // Message/Event sources
11420
11453
  { method: "getText", class: "Message", type: "io_input", severity: "high", return_tainted: true },
11421
11454
  { method: "getPayload", type: "io_input", severity: "high", return_tainted: true },
@@ -12393,8 +12426,13 @@ var DEFAULT_SINKS = [
12393
12426
  { method: "write", class: "PrintWriter", type: "xss", cwe: "CWE-79", severity: "high", arg_positions: [0] },
12394
12427
  { method: "println", class: "PrintWriter", type: "xss", cwe: "CWE-79", severity: "high", arg_positions: [0] },
12395
12428
  { method: "print", class: "PrintWriter", type: "xss", cwe: "CWE-79", severity: "high", arg_positions: [0] },
12396
- { method: "format", class: "PrintWriter", type: "xss", cwe: "CWE-79", severity: "high", arg_positions: [0, 1] },
12397
- { method: "printf", class: "PrintWriter", type: "xss", cwe: "CWE-79", severity: "high", arg_positions: [0, 1] },
12429
+ // format/printf take `(String fmt, Object... args)` or `(Locale l, String
12430
+ // fmt, Object... args)`. Every argument after the format can reach the page,
12431
+ // and the Locale overload shifts them all one place right, so `[0, 1]` missed
12432
+ // `printf(Locale.US, "%s", tainted)` and any second vararg. Enumerated, not
12433
+ // `[]` — see the ProcessBuilder note above.
12434
+ { method: "format", class: "PrintWriter", type: "xss", cwe: "CWE-79", severity: "high", arg_positions: [0, 1, 2, 3, 4, 5, 6, 7] },
12435
+ { method: "printf", class: "PrintWriter", type: "xss", cwe: "CWE-79", severity: "high", arg_positions: [0, 1, 2, 3, 4, 5, 6, 7] },
12398
12436
  // ServletOutputStream
12399
12437
  { method: "write", class: "ServletOutputStream", type: "xss", cwe: "CWE-79", severity: "high", arg_positions: [0] },
12400
12438
  { method: "print", class: "ServletOutputStream", type: "xss", cwe: "CWE-79", severity: "high", arg_positions: [0] },
@@ -19309,7 +19347,16 @@ var ConstantPropagator = class _ConstantPropagator {
19309
19347
  break;
19310
19348
  }
19311
19349
  }
19312
- const switchBlock = node.children.find((c) => c.type === "switch_block");
19350
+ if (!switchValue) {
19351
+ const valueNode = node.childForFieldName("value");
19352
+ if (valueNode) {
19353
+ const literal = this.literalValue(valueNode);
19354
+ switchValue = literal !== null ? { type: typeof literal === "number" ? "int" : "string", value: literal, sourceLine: getNodeLine(valueNode) } : this.evaluateExpression(valueNode);
19355
+ }
19356
+ }
19357
+ const switchBlock = node.children.find(
19358
+ (c) => c.type === "switch_block" || c.type === "switch_body"
19359
+ );
19313
19360
  if (!switchBlock) {
19314
19361
  for (const child of node.children) {
19315
19362
  this.visit(child);
@@ -19317,7 +19364,7 @@ var ConstantPropagator = class _ConstantPropagator {
19317
19364
  return;
19318
19365
  }
19319
19366
  const caseGroups = switchBlock.children.filter(
19320
- (c) => c.type === "switch_block_statement_group" || c.type === "switch_rule"
19367
+ (c) => c.type === "switch_block_statement_group" || c.type === "switch_rule" || c.type === "switch_section"
19321
19368
  );
19322
19369
  if (switchValue && isKnown(switchValue)) {
19323
19370
  let matchingIdx = -1;
@@ -19335,6 +19382,16 @@ var ConstantPropagator = class _ConstantPropagator {
19335
19382
  matchingIdx = i2;
19336
19383
  }
19337
19384
  }
19385
+ } else if (caseGroup.type === "switch_section") {
19386
+ if (child.type === "default") {
19387
+ defaultIdx = i2;
19388
+ } else if (child.type === "constant_pattern") {
19389
+ const inner = child.children.find((c) => c.isNamed);
19390
+ const caseValue = inner ? this.literalValue(inner) : null;
19391
+ if (caseValue !== null && caseValue === switchValue.value) {
19392
+ matchingIdx = i2;
19393
+ }
19394
+ }
19338
19395
  }
19339
19396
  }
19340
19397
  }
@@ -19355,9 +19412,17 @@ var ConstantPropagator = class _ConstantPropagator {
19355
19412
  }
19356
19413
  }
19357
19414
  } else {
19415
+ const taintedBefore = new Set(this.tainted);
19416
+ const taintedAfter = new Set(taintedBefore);
19417
+ const wasInConditional = this.inConditionalBranch;
19418
+ this.inConditionalBranch = true;
19358
19419
  for (const caseGroup of caseGroups) {
19420
+ this.tainted = new Set(taintedBefore);
19359
19421
  this.visit(caseGroup);
19422
+ for (const v of this.tainted) taintedAfter.add(v);
19360
19423
  }
19424
+ this.inConditionalBranch = wasInConditional;
19425
+ this.tainted = taintedAfter;
19361
19426
  }
19362
19427
  }
19363
19428
  handleTernary(node) {
@@ -19408,6 +19473,18 @@ var ConstantPropagator = class _ConstantPropagator {
19408
19473
  }
19409
19474
  return false;
19410
19475
  }
19476
+ /** The value of an integer, character or string literal node; null for anything else. */
19477
+ literalValue(node) {
19478
+ const text = getNodeText2(node, this.source);
19479
+ if (node.type === "integer_literal" || node.type === "decimal_integer_literal") {
19480
+ const n = Number(text.replace(/_/g, "").replace(/[uUlL]+$/, ""));
19481
+ return Number.isInteger(n) ? n : null;
19482
+ }
19483
+ if (node.type === "character_literal" || node.type === "string_literal") {
19484
+ return text.slice(1, -1);
19485
+ }
19486
+ return null;
19487
+ }
19411
19488
  extractCaseValue(labelNode) {
19412
19489
  for (const child of labelNode.children) {
19413
19490
  if (child.type === "decimal_integer_literal") {
@@ -9601,6 +9601,7 @@ function buildCSharpDFG(tree, cache) {
9601
9601
  currentScope(scopeStack).set(name2, def.id);
9602
9602
  }
9603
9603
  }
9604
+ const plainTargets = [];
9604
9605
  for (const asn of findNodes(body2, "assignment_expression")) {
9605
9606
  const left = asn.childForFieldName("left");
9606
9607
  if (left?.type === "identifier") {
@@ -9608,9 +9609,16 @@ function buildCSharpDFG(tree, cache) {
9608
9609
  const def = { id: defIdCounter++, variable: name2, line: asn.startPosition.row + 1, kind: "local" };
9609
9610
  defs.push(def);
9610
9611
  currentScope(scopeStack).set(name2, def.id);
9612
+ if (asn.children.some((c) => c?.type === "=")) {
9613
+ plainTargets.push({ name: name2, line: left.startPosition.row + 1 });
9614
+ }
9611
9615
  }
9612
9616
  }
9613
9617
  const bodyUses = extractUses(body2, useIdCounter, scopeStack, false);
9618
+ for (const target of plainTargets) {
9619
+ const idx = bodyUses.uses.findIndex((u) => u.variable === target.name && u.line === target.line);
9620
+ if (idx !== -1) bodyUses.uses.splice(idx, 1);
9621
+ }
9614
9622
  resolveReassignedUses(defs.slice(methodDefStart), bodyUses.uses);
9615
9623
  uses.push(...bodyUses.uses);
9616
9624
  useIdCounter = bodyUses.nextId;
@@ -9950,6 +9958,30 @@ function processStatement2(stmt, startDefId, startUseId, scopeStack, isJavaScrip
9950
9958
  }
9951
9959
  break;
9952
9960
  }
9961
+ case "switch_expression":
9962
+ case "switch_statement": {
9963
+ const value = stmt.childForFieldName("condition") ?? stmt.childForFieldName("value");
9964
+ if (value) {
9965
+ const valueUses = extractUses(value, useId, scopeStack, isJavaScript);
9966
+ uses.push(...valueUses.uses);
9967
+ useId = valueUses.nextId;
9968
+ }
9969
+ const body2 = stmt.childForFieldName("body");
9970
+ if (body2) {
9971
+ scopeStack.push(/* @__PURE__ */ new Map());
9972
+ for (let i2 = 0; i2 < body2.childCount; i2++) {
9973
+ const group = body2.child(i2);
9974
+ if (!group) continue;
9975
+ const result = processBlock(group, defId, useId, scopeStack, isJavaScript);
9976
+ defs.push(...result.defs);
9977
+ uses.push(...result.uses);
9978
+ defId = result.nextDefId;
9979
+ useId = result.nextUseId;
9980
+ }
9981
+ scopeStack.pop();
9982
+ }
9983
+ break;
9984
+ }
9953
9985
  case "block":
9954
9986
  case "statement_block": {
9955
9987
  scopeStack.push(/* @__PURE__ */ new Map());
@@ -11350,6 +11382,7 @@ var DEFAULT_SOURCES = [
11350
11382
  { method: "get", class: "ConcurrentHashMap", type: "plugin_param", severity: "high", return_tainted: true },
11351
11383
  { method: "get", class: "Hashtable", type: "plugin_param", severity: "high", return_tainted: true },
11352
11384
  { method: "get", class: "Properties", type: "config_param", severity: "high", return_tainted: true },
11385
+ { method: "getProperty", class: "Properties", type: "config_param", severity: "high", return_tainted: true },
11353
11386
  // Message/Event sources
11354
11387
  { method: "getText", class: "Message", type: "io_input", severity: "high", return_tainted: true },
11355
11388
  { method: "getPayload", type: "io_input", severity: "high", return_tainted: true },
@@ -12327,8 +12360,13 @@ var DEFAULT_SINKS = [
12327
12360
  { method: "write", class: "PrintWriter", type: "xss", cwe: "CWE-79", severity: "high", arg_positions: [0] },
12328
12361
  { method: "println", class: "PrintWriter", type: "xss", cwe: "CWE-79", severity: "high", arg_positions: [0] },
12329
12362
  { method: "print", class: "PrintWriter", type: "xss", cwe: "CWE-79", severity: "high", arg_positions: [0] },
12330
- { method: "format", class: "PrintWriter", type: "xss", cwe: "CWE-79", severity: "high", arg_positions: [0, 1] },
12331
- { method: "printf", class: "PrintWriter", type: "xss", cwe: "CWE-79", severity: "high", arg_positions: [0, 1] },
12363
+ // format/printf take `(String fmt, Object... args)` or `(Locale l, String
12364
+ // fmt, Object... args)`. Every argument after the format can reach the page,
12365
+ // and the Locale overload shifts them all one place right, so `[0, 1]` missed
12366
+ // `printf(Locale.US, "%s", tainted)` and any second vararg. Enumerated, not
12367
+ // `[]` — see the ProcessBuilder note above.
12368
+ { method: "format", class: "PrintWriter", type: "xss", cwe: "CWE-79", severity: "high", arg_positions: [0, 1, 2, 3, 4, 5, 6, 7] },
12369
+ { method: "printf", class: "PrintWriter", type: "xss", cwe: "CWE-79", severity: "high", arg_positions: [0, 1, 2, 3, 4, 5, 6, 7] },
12332
12370
  // ServletOutputStream
12333
12371
  { method: "write", class: "ServletOutputStream", type: "xss", cwe: "CWE-79", severity: "high", arg_positions: [0] },
12334
12372
  { method: "print", class: "ServletOutputStream", type: "xss", cwe: "CWE-79", severity: "high", arg_positions: [0] },
@@ -19243,7 +19281,16 @@ var ConstantPropagator = class _ConstantPropagator {
19243
19281
  break;
19244
19282
  }
19245
19283
  }
19246
- const switchBlock = node.children.find((c) => c.type === "switch_block");
19284
+ if (!switchValue) {
19285
+ const valueNode = node.childForFieldName("value");
19286
+ if (valueNode) {
19287
+ const literal = this.literalValue(valueNode);
19288
+ switchValue = literal !== null ? { type: typeof literal === "number" ? "int" : "string", value: literal, sourceLine: getNodeLine(valueNode) } : this.evaluateExpression(valueNode);
19289
+ }
19290
+ }
19291
+ const switchBlock = node.children.find(
19292
+ (c) => c.type === "switch_block" || c.type === "switch_body"
19293
+ );
19247
19294
  if (!switchBlock) {
19248
19295
  for (const child of node.children) {
19249
19296
  this.visit(child);
@@ -19251,7 +19298,7 @@ var ConstantPropagator = class _ConstantPropagator {
19251
19298
  return;
19252
19299
  }
19253
19300
  const caseGroups = switchBlock.children.filter(
19254
- (c) => c.type === "switch_block_statement_group" || c.type === "switch_rule"
19301
+ (c) => c.type === "switch_block_statement_group" || c.type === "switch_rule" || c.type === "switch_section"
19255
19302
  );
19256
19303
  if (switchValue && isKnown(switchValue)) {
19257
19304
  let matchingIdx = -1;
@@ -19269,6 +19316,16 @@ var ConstantPropagator = class _ConstantPropagator {
19269
19316
  matchingIdx = i2;
19270
19317
  }
19271
19318
  }
19319
+ } else if (caseGroup.type === "switch_section") {
19320
+ if (child.type === "default") {
19321
+ defaultIdx = i2;
19322
+ } else if (child.type === "constant_pattern") {
19323
+ const inner = child.children.find((c) => c.isNamed);
19324
+ const caseValue = inner ? this.literalValue(inner) : null;
19325
+ if (caseValue !== null && caseValue === switchValue.value) {
19326
+ matchingIdx = i2;
19327
+ }
19328
+ }
19272
19329
  }
19273
19330
  }
19274
19331
  }
@@ -19289,9 +19346,17 @@ var ConstantPropagator = class _ConstantPropagator {
19289
19346
  }
19290
19347
  }
19291
19348
  } else {
19349
+ const taintedBefore = new Set(this.tainted);
19350
+ const taintedAfter = new Set(taintedBefore);
19351
+ const wasInConditional = this.inConditionalBranch;
19352
+ this.inConditionalBranch = true;
19292
19353
  for (const caseGroup of caseGroups) {
19354
+ this.tainted = new Set(taintedBefore);
19293
19355
  this.visit(caseGroup);
19356
+ for (const v of this.tainted) taintedAfter.add(v);
19294
19357
  }
19358
+ this.inConditionalBranch = wasInConditional;
19359
+ this.tainted = taintedAfter;
19295
19360
  }
19296
19361
  }
19297
19362
  handleTernary(node) {
@@ -19342,6 +19407,18 @@ var ConstantPropagator = class _ConstantPropagator {
19342
19407
  }
19343
19408
  return false;
19344
19409
  }
19410
+ /** The value of an integer, character or string literal node; null for anything else. */
19411
+ literalValue(node) {
19412
+ const text = getNodeText2(node, this.source);
19413
+ if (node.type === "integer_literal" || node.type === "decimal_integer_literal") {
19414
+ const n = Number(text.replace(/_/g, "").replace(/[uUlL]+$/, ""));
19415
+ return Number.isInteger(n) ? n : null;
19416
+ }
19417
+ if (node.type === "character_literal" || node.type === "string_literal") {
19418
+ return text.slice(1, -1);
19419
+ }
19420
+ return null;
19421
+ }
19345
19422
  extractCaseValue(labelNode) {
19346
19423
  for (const child of labelNode.children) {
19347
19424
  if (child.type === "decimal_integer_literal") {
@@ -115,6 +115,9 @@ function buildCSharpDFG(tree, cache) {
115
115
  }
116
116
  }
117
117
  // Assignment target defs: `x = …;` → assignment_expression.left.
118
+ // `plainTargets` collects the targets of plain `=` assignments: the name
119
+ // on the left is written, not read (`x += …` does read it).
120
+ const plainTargets = [];
118
121
  for (const asn of findNodes(body, 'assignment_expression')) {
119
122
  const left = asn.childForFieldName('left');
120
123
  if (left?.type === 'identifier') {
@@ -122,10 +125,24 @@ function buildCSharpDFG(tree, cache) {
122
125
  const def = { id: defIdCounter++, variable: name, line: asn.startPosition.row + 1, kind: 'local' };
123
126
  defs.push(def);
124
127
  currentScope(scopeStack).set(name, def.id);
128
+ if (asn.children.some(c => c?.type === '=')) {
129
+ plainTargets.push({ name, line: left.startPosition.row + 1 });
130
+ }
125
131
  }
126
132
  }
127
133
  // Uses: all identifier references, reaching defs resolved from scope.
128
134
  const bodyUses = extractUses(body, useIdCounter, scopeStack, false);
135
+ // `extractUses` reports the left-hand name of `s = q;` as a read of `s`,
136
+ // which chains the previous `s` into the new one although nothing flows
137
+ // between them. A sanitizer on the old value was then credited to the
138
+ // new one (cognium-dev#579: encode, reassign to raw input, write). Uses
139
+ // arrive in source order, so the target is the first use of that name on
140
+ // its line.
141
+ for (const target of plainTargets) {
142
+ const idx = bodyUses.uses.findIndex(u => u.variable === target.name && u.line === target.line);
143
+ if (idx !== -1)
144
+ bodyUses.uses.splice(idx, 1);
145
+ }
129
146
  resolveReassignedUses(defs.slice(methodDefStart), bodyUses.uses);
130
147
  uses.push(...bodyUses.uses);
131
148
  useIdCounter = bodyUses.nextId;
@@ -516,6 +533,38 @@ function processStatement(stmt, startDefId, startUseId, scopeStack, isJavaScript
516
533
  }
517
534
  break;
518
535
  }
536
+ case 'switch_expression':
537
+ case 'switch_statement': {
538
+ // Java parses a switch statement as `switch_expression` (condition +
539
+ // switch_block of statement groups / `->` rules); JS as
540
+ // `switch_statement` (value + switch_body of switch_case). Without this
541
+ // case a switch fell to `default` and only had its uses extracted, so
542
+ // `case 'A': bar = param;` recorded no def of `bar` and its taint never
543
+ // reached a later sink.
544
+ const value = stmt.childForFieldName('condition') ?? stmt.childForFieldName('value');
545
+ if (value) {
546
+ const valueUses = extractUses(value, useId, scopeStack, isJavaScript);
547
+ uses.push(...valueUses.uses);
548
+ useId = valueUses.nextId;
549
+ }
550
+ const body = stmt.childForFieldName('body');
551
+ if (body) {
552
+ // One scope for the whole body: Java and JS cases share it.
553
+ scopeStack.push(new Map());
554
+ for (let i = 0; i < body.childCount; i++) {
555
+ const group = body.child(i);
556
+ if (!group)
557
+ continue;
558
+ const result = processBlock(group, defId, useId, scopeStack, isJavaScript);
559
+ defs.push(...result.defs);
560
+ uses.push(...result.uses);
561
+ defId = result.nextDefId;
562
+ useId = result.nextUseId;
563
+ }
564
+ scopeStack.pop();
565
+ }
566
+ break;
567
+ }
519
568
  case 'block':
520
569
  case 'statement_block': {
521
570
  // New scope for block