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.
- package/README.md +9 -9
- package/dist/analysis/config-loader.d.ts.map +1 -1
- package/dist/analysis/config-loader.js +8 -2
- package/dist/analysis/config-loader.js.map +1 -1
- package/dist/analysis/constant-propagation/propagator.d.ts +2 -0
- package/dist/analysis/constant-propagation/propagator.d.ts.map +1 -1
- package/dist/analysis/constant-propagation/propagator.js +56 -2
- package/dist/analysis/constant-propagation/propagator.js.map +1 -1
- package/dist/analysis/entry-point-detection.d.ts +7 -0
- package/dist/analysis/entry-point-detection.d.ts.map +1 -1
- package/dist/analysis/entry-point-detection.js +13 -1
- package/dist/analysis/entry-point-detection.js.map +1 -1
- package/dist/analysis/interprocedural.d.ts.map +1 -1
- package/dist/analysis/interprocedural.js +4 -0
- package/dist/analysis/interprocedural.js.map +1 -1
- package/dist/analysis/passes/csharp-sanitizer-credit.d.ts +147 -0
- package/dist/analysis/passes/csharp-sanitizer-credit.d.ts.map +1 -0
- package/dist/analysis/passes/csharp-sanitizer-credit.js +564 -0
- package/dist/analysis/passes/csharp-sanitizer-credit.js.map +1 -0
- package/dist/analysis/passes/interprocedural-pass.d.ts.map +1 -1
- package/dist/analysis/passes/interprocedural-pass.js +89 -0
- package/dist/analysis/passes/interprocedural-pass.js.map +1 -1
- package/dist/analysis/passes/language-sources-pass.d.ts +6 -1
- package/dist/analysis/passes/language-sources-pass.d.ts.map +1 -1
- package/dist/analysis/passes/language-sources-pass.js +15 -2
- package/dist/analysis/passes/language-sources-pass.js.map +1 -1
- package/dist/analysis/passes/sink-filter-pass.d.ts.map +1 -1
- package/dist/analysis/passes/sink-filter-pass.js +49 -52
- package/dist/analysis/passes/sink-filter-pass.js.map +1 -1
- package/dist/analysis/passes/taint-propagation-pass.d.ts.map +1 -1
- package/dist/analysis/passes/taint-propagation-pass.js +348 -16
- package/dist/analysis/passes/taint-propagation-pass.js.map +1 -1
- package/dist/analysis/require-entry-path.js +50 -4
- package/dist/analysis/require-entry-path.js.map +1 -1
- package/dist/browser/circle-ir.js +861 -65
- package/dist/core/circle-ir-core.cjs +81 -4
- package/dist/core/circle-ir-core.js +81 -4
- package/dist/core/extractors/dfg.js +49 -0
- package/dist/core/extractors/dfg.js.map +1 -1
- 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
|
-
|
|
12397
|
-
|
|
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
|
-
|
|
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
|
-
|
|
12331
|
-
|
|
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
|
-
|
|
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
|