circle-ir 4.13.0 → 4.13.2
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/dist/analysis/constant-propagation/propagator.d.ts +6 -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/passes/csharp-sanitizer-credit.d.ts +31 -1
- package/dist/analysis/passes/csharp-sanitizer-credit.d.ts.map +1 -1
- package/dist/analysis/passes/csharp-sanitizer-credit.js +121 -6
- package/dist/analysis/passes/csharp-sanitizer-credit.js.map +1 -1
- package/dist/analysis/passes/interprocedural-pass.d.ts.map +1 -1
- package/dist/analysis/passes/interprocedural-pass.js +2 -1
- package/dist/analysis/passes/interprocedural-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 +7 -7
- package/dist/analysis/passes/sink-filter-pass.js.map +1 -1
- package/dist/browser/circle-ir.js +149 -11
- package/dist/core/circle-ir-core.cjs +49 -2
- package/dist/core/circle-ir-core.js +49 -2
- package/package.json +1 -1
|
@@ -22021,7 +22021,13 @@ var ConstantPropagator = class _ConstantPropagator {
|
|
|
22021
22021
|
}
|
|
22022
22022
|
return;
|
|
22023
22023
|
}
|
|
22024
|
-
|
|
22024
|
+
let condValue = this.evaluateExpression(condition);
|
|
22025
|
+
if (!(isKnown(condValue) && condValue.type === "bool")) {
|
|
22026
|
+
const literal = this.literalCondition(condition);
|
|
22027
|
+
if (literal !== null) {
|
|
22028
|
+
condValue = { type: "bool", value: literal, sourceLine: getNodeLine(condition) };
|
|
22029
|
+
}
|
|
22030
|
+
}
|
|
22025
22031
|
if (isKnown(condValue) && condValue.type === "bool") {
|
|
22026
22032
|
if (condValue.value === true) {
|
|
22027
22033
|
if (alternative) {
|
|
@@ -22185,13 +22191,27 @@ var ConstantPropagator = class _ConstantPropagator {
|
|
|
22185
22191
|
return;
|
|
22186
22192
|
}
|
|
22187
22193
|
const caseGroups = switchBlock.children.filter(
|
|
22188
|
-
(c) => c.type === "switch_block_statement_group" || c.type === "switch_rule" || c.type === "switch_section"
|
|
22194
|
+
(c) => c.type === "switch_block_statement_group" || c.type === "switch_rule" || c.type === "switch_section" || c.type === "switch_case" || c.type === "switch_default"
|
|
22189
22195
|
);
|
|
22196
|
+
if (caseGroups.length === 0) {
|
|
22197
|
+
this.visit(switchBlock);
|
|
22198
|
+
return;
|
|
22199
|
+
}
|
|
22190
22200
|
if (switchValue && isKnown(switchValue)) {
|
|
22191
22201
|
let matchingIdx = -1;
|
|
22192
22202
|
let defaultIdx = -1;
|
|
22193
22203
|
for (let i2 = 0; i2 < caseGroups.length; i2++) {
|
|
22194
22204
|
const caseGroup = caseGroups[i2];
|
|
22205
|
+
if (caseGroup.type === "switch_default") {
|
|
22206
|
+
defaultIdx = i2;
|
|
22207
|
+
continue;
|
|
22208
|
+
}
|
|
22209
|
+
if (caseGroup.type === "switch_case") {
|
|
22210
|
+
const label = caseGroup.childForFieldName("value");
|
|
22211
|
+
const caseValue = label ? this.literalValue(label) : null;
|
|
22212
|
+
if (caseValue !== null && caseValue === switchValue.value) matchingIdx = i2;
|
|
22213
|
+
continue;
|
|
22214
|
+
}
|
|
22195
22215
|
for (const child of caseGroup.children) {
|
|
22196
22216
|
if (child.type === "switch_label") {
|
|
22197
22217
|
const labelText = getNodeText2(child, this.source);
|
|
@@ -22294,6 +22314,33 @@ var ConstantPropagator = class _ConstantPropagator {
|
|
|
22294
22314
|
}
|
|
22295
22315
|
return false;
|
|
22296
22316
|
}
|
|
22317
|
+
/**
|
|
22318
|
+
* A condition written with literals only, in the C# grammar's node names:
|
|
22319
|
+
* `true`, `false`, `5 == 5`, `5 != 5`. Null when it is anything else. The
|
|
22320
|
+
* shared evaluator does not know `boolean_literal` / `integer_literal`.
|
|
22321
|
+
*/
|
|
22322
|
+
literalCondition(node) {
|
|
22323
|
+
if (node.type === "parenthesized_expression") {
|
|
22324
|
+
const inner = node.children.find((c) => c.isNamed);
|
|
22325
|
+
return inner ? this.literalCondition(inner) : null;
|
|
22326
|
+
}
|
|
22327
|
+
if (node.type === "boolean_literal") {
|
|
22328
|
+
const text = getNodeText2(node, this.source);
|
|
22329
|
+
return text === "true" ? true : text === "false" ? false : null;
|
|
22330
|
+
}
|
|
22331
|
+
if (node.type === "binary_expression") {
|
|
22332
|
+
const left = node.childForFieldName("left");
|
|
22333
|
+
const right = node.childForFieldName("right");
|
|
22334
|
+
const op = node.childForFieldName("operator");
|
|
22335
|
+
const opText = op ? getNodeText2(op, this.source) : "";
|
|
22336
|
+
if (!left || !right || opText !== "==" && opText !== "!=") return null;
|
|
22337
|
+
const l = this.literalValue(left);
|
|
22338
|
+
const r = this.literalValue(right);
|
|
22339
|
+
if (l === null || r === null) return null;
|
|
22340
|
+
return opText === "==" ? l === r : l !== r;
|
|
22341
|
+
}
|
|
22342
|
+
return null;
|
|
22343
|
+
}
|
|
22297
22344
|
/** The value of an integer, character or string literal node; null for anything else. */
|
|
22298
22345
|
literalValue(node) {
|
|
22299
22346
|
const text = getNodeText2(node, this.source);
|
|
@@ -35149,6 +35196,9 @@ var ENDS_STATEMENT_RE = /[;{}]\s*(?:\/\/.*)?$/;
|
|
|
35149
35196
|
function escapeRegex(s) {
|
|
35150
35197
|
return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
35151
35198
|
}
|
|
35199
|
+
function withoutStringLiterals(text) {
|
|
35200
|
+
return text.replace(/@"(?:[^"]|"")*"|"(?:[^"\\]|\\.)*"/g, '""');
|
|
35201
|
+
}
|
|
35152
35202
|
function mentions(text, name2) {
|
|
35153
35203
|
return new RegExp(`(?<![\\w])${escapeRegex(name2)}(?![\\w])`).test(text);
|
|
35154
35204
|
}
|
|
@@ -35160,6 +35210,16 @@ function insideParens(text, open) {
|
|
|
35160
35210
|
}
|
|
35161
35211
|
return null;
|
|
35162
35212
|
}
|
|
35213
|
+
function lastCallArguments(text) {
|
|
35214
|
+
const close = text.lastIndexOf(")");
|
|
35215
|
+
if (close < 0) return null;
|
|
35216
|
+
let depth = 0;
|
|
35217
|
+
for (let i2 = close; i2 >= 0; i2--) {
|
|
35218
|
+
if (text[i2] === ")") depth++;
|
|
35219
|
+
else if (text[i2] === "(" && --depth === 0) return text.slice(i2 + 1, close);
|
|
35220
|
+
}
|
|
35221
|
+
return null;
|
|
35222
|
+
}
|
|
35163
35223
|
function leadingIf(text) {
|
|
35164
35224
|
const m = /^\s*(?:\}\s*)?(?:else\s+)?if\s*\(/.exec(text);
|
|
35165
35225
|
if (!m) return null;
|
|
@@ -35285,6 +35345,84 @@ var CSharpCredit = class {
|
|
|
35285
35345
|
sanitizedVarsAt(type, sinkLine) {
|
|
35286
35346
|
return this.sanitizedVarsIn(type, this.methodAt(sinkLine)?.start_line ?? 1, sinkLine, true);
|
|
35287
35347
|
}
|
|
35348
|
+
/**
|
|
35349
|
+
* True when the sink on `sinkLine` also receives a tainted value that no
|
|
35350
|
+
* sanitizer covers. In `Write(HtmlEncode(a) + b)` the encoder covers `a`
|
|
35351
|
+
* only: `b` arrives raw, so the sanitizer on the line must not clear the
|
|
35352
|
+
* sink. `credited` are the names already known safe for `type` there;
|
|
35353
|
+
* `seeds` are the taint sources that name a variable.
|
|
35354
|
+
*
|
|
35355
|
+
* Taint is followed through the sink's method top to bottom, last
|
|
35356
|
+
* assignment wins. The sink text is then read without its string literals
|
|
35357
|
+
* and without the sanitizer calls for `type`; a tainted, uncredited name
|
|
35358
|
+
* left over is a raw operand.
|
|
35359
|
+
*/
|
|
35360
|
+
rawTaintReaches(type, sinkLine, credited, seeds) {
|
|
35361
|
+
if (seeds.length === 0) return false;
|
|
35362
|
+
const method = this.methodAt(sinkLine);
|
|
35363
|
+
const from = method?.start_line ?? 1;
|
|
35364
|
+
const tainted = /* @__PURE__ */ new Set();
|
|
35365
|
+
const stmts = this.statements(from, sinkLine);
|
|
35366
|
+
for (let i2 = 0; i2 < stmts.length; i2++) {
|
|
35367
|
+
const st = stmts[i2];
|
|
35368
|
+
const end = i2 + 1 < stmts.length ? stmts[i2 + 1].line : sinkLine;
|
|
35369
|
+
const m = ASSIGN_RE.exec(st.text);
|
|
35370
|
+
if (m) {
|
|
35371
|
+
const rhs = withoutStringLiterals(m[2]);
|
|
35372
|
+
if ([...tainted].some((v) => mentions(rhs, v))) tainted.add(m[1]);
|
|
35373
|
+
else tainted.delete(m[1]);
|
|
35374
|
+
}
|
|
35375
|
+
for (const s of seeds) if (s.line >= st.line && s.line < end) tainted.add(s.variable);
|
|
35376
|
+
}
|
|
35377
|
+
if (tainted.size === 0) return false;
|
|
35378
|
+
const rest = this.withoutSanitizerCalls(type, this.receivedOperands(type, sinkLine));
|
|
35379
|
+
if (this.builtinSanitizes(type, rest)) return false;
|
|
35380
|
+
for (const v of tainted) if (!credited.has(v) && mentions(rest, v)) return true;
|
|
35381
|
+
return false;
|
|
35382
|
+
}
|
|
35383
|
+
/**
|
|
35384
|
+
* The operands a sink on `sinkLine` receives, string literals blanked. For a
|
|
35385
|
+
* property sink (`searcher.Filter = rhs;`) that is the right-hand side. For
|
|
35386
|
+
* a call sink it is the call's arguments, so a tainted receiver or cast
|
|
35387
|
+
* target (`((XmlDocument)ctx).SelectSingleNode(q)`) is not an operand. The
|
|
35388
|
+
* last call on the line is the sink only when it is not itself a sanitizer:
|
|
35389
|
+
* in `x.Filter = Escape(input);` it is the sanitizer (cognium-dev#643).
|
|
35390
|
+
*/
|
|
35391
|
+
receivedOperands(type, sinkLine) {
|
|
35392
|
+
const text = withoutStringLiterals(this.sinkText(sinkLine)).replace(/\s*;?\s*(?:\/\/.*)?$/, "");
|
|
35393
|
+
const property = /^\s*[A-Za-z_][\w.]*\.[A-Za-z_]\w*\s*=(?!=)\s*(.*)$/.exec(text);
|
|
35394
|
+
if (property) return property[1];
|
|
35395
|
+
if (!text.endsWith(")")) return text;
|
|
35396
|
+
const args2 = lastCallArguments(text);
|
|
35397
|
+
if (args2 === null) return text;
|
|
35398
|
+
const open = text.length - args2.length - 2;
|
|
35399
|
+
const callStart = text.slice(0, open).search(/[A-Za-z_][\w.]*\s*$/);
|
|
35400
|
+
const lastCall = callStart < 0 ? "" : text.slice(callStart);
|
|
35401
|
+
if (lastCall && this.withoutSanitizerCalls(type, lastCall).trim() === "") return text;
|
|
35402
|
+
return args2;
|
|
35403
|
+
}
|
|
35404
|
+
/** `text` with every sanitizer call for `type`, arguments included, removed. */
|
|
35405
|
+
withoutSanitizerCalls(type, text) {
|
|
35406
|
+
const res = [];
|
|
35407
|
+
for (const { re, type: t } of CSHARP_SANITIZER_RES) if (t === type) res.push(re);
|
|
35408
|
+
for (const name2 of this.helpers(type)) res.push(new RegExp(`(?<![\\w.])${escapeRegex(name2)}\\s*\\(`));
|
|
35409
|
+
let out2 = text;
|
|
35410
|
+
for (const re of res) {
|
|
35411
|
+
for (let guard = 0; guard < 16; guard++) {
|
|
35412
|
+
const m = re.exec(out2);
|
|
35413
|
+
if (!m) break;
|
|
35414
|
+
if (!m[0].endsWith("(")) {
|
|
35415
|
+
out2 = out2.slice(0, m.index) + out2.slice(m.index + m[0].length);
|
|
35416
|
+
continue;
|
|
35417
|
+
}
|
|
35418
|
+
const open = m.index + m[0].length - 1;
|
|
35419
|
+
const inner = insideParens(out2, open);
|
|
35420
|
+
if (inner === null) break;
|
|
35421
|
+
out2 = out2.slice(0, m.index) + out2.slice(open + inner.length + 2);
|
|
35422
|
+
}
|
|
35423
|
+
}
|
|
35424
|
+
return out2;
|
|
35425
|
+
}
|
|
35288
35426
|
/**
|
|
35289
35427
|
* The text of the statement at `line` that a sink call can be in: for
|
|
35290
35428
|
* `if (COND) sink(x);` that is `sink(x);`, without the condition. A name
|
|
@@ -35501,16 +35639,16 @@ var CSharpCredit = class {
|
|
|
35501
35639
|
* sink type. Used for the untyped "tainted data reaches an external call"
|
|
35502
35640
|
* fallback, which has no sink type of its own to ask about.
|
|
35503
35641
|
*/
|
|
35504
|
-
creditedOnLine(line) {
|
|
35642
|
+
creditedOnLine(line, seeds = []) {
|
|
35505
35643
|
const text = this.sinkText(line);
|
|
35506
35644
|
const types = new Set(CSHARP_SANITIZER_RES.map((r) => r.type));
|
|
35507
35645
|
for (const t of INERT_WHEN_ALPHANUMERIC) types.add(t);
|
|
35508
35646
|
types.add("ssrf");
|
|
35509
35647
|
for (const type of types) {
|
|
35510
|
-
if (this.sanitizes(type, text)) return true;
|
|
35511
35648
|
const credited = /* @__PURE__ */ new Set([...this.sanitizedVarsAt(type, line), ...this.guardedVarsAt(type, line)]);
|
|
35512
35649
|
for (const v of this.safeBuilders(credited, line)) credited.add(v);
|
|
35513
|
-
|
|
35650
|
+
const covered = this.sanitizes(type, text) || [...credited].some((v) => mentions(text, v));
|
|
35651
|
+
if (covered && !this.rawTaintReaches(type, line, credited, seeds)) return true;
|
|
35514
35652
|
}
|
|
35515
35653
|
return this.fixedHostRequest(line);
|
|
35516
35654
|
}
|
|
@@ -36676,11 +36814,12 @@ var SinkFilterPass = class {
|
|
|
36676
36814
|
const sanitizedByType = csharpSanitizedVarsByType(ctx.code);
|
|
36677
36815
|
const csFields = new Set(ctx.graph.ir.types.flatMap((t) => (t.fields ?? []).map((f) => f.name)));
|
|
36678
36816
|
const credit = new CSharpCredit(ctx.code, ctx.graph.ir.types.flatMap((t) => t.methods));
|
|
36817
|
+
const csSeeds = mergedSources.flatMap((s) => s.variable ? [{ variable: s.variable, line: s.line }] : []);
|
|
36679
36818
|
const xxeHardened = /\bXmlResolver\s*=\s*null\b/.test(ctx.code) || /\bDtdProcessing\s*=\s*(?:DtdProcessing\s*\.\s*)?(?:Prohibit|Ignore)\b/.test(ctx.code);
|
|
36680
36819
|
filtered = filtered.filter((sink) => {
|
|
36681
36820
|
if (sink.type === "xxe" && xxeHardened) return false;
|
|
36682
36821
|
const sinkLineText = sourceLines[sink.line - 1] ?? "";
|
|
36683
|
-
|
|
36822
|
+
const inlineSanitized = credit.sanitizes(sink.type, sinkLineText);
|
|
36684
36823
|
if (sink.type === "ssrf" && credit.fixedHostRequest(sink.line)) return false;
|
|
36685
36824
|
const scoped = credit.sanitizedVarsAt(sink.type, sink.line);
|
|
36686
36825
|
const fileWide = sanitizedByType.get(sink.type);
|
|
@@ -36691,10 +36830,8 @@ var SinkFilterPass = class {
|
|
|
36691
36830
|
for (const v of credit.guardedVarsAt(sink.type, sink.line)) sanitizedVars.add(v);
|
|
36692
36831
|
for (const v of credit.safeBuilders(sanitizedVars, sink.line)) sanitizedVars.add(v);
|
|
36693
36832
|
const received = credit.sinkText(sink.line);
|
|
36694
|
-
|
|
36695
|
-
|
|
36696
|
-
}
|
|
36697
|
-
return true;
|
|
36833
|
+
const covered = inlineSanitized || [...sanitizedVars].some((v) => new RegExp(`(?<![\\w])${escapeRegex2(v)}(?![\\w])`).test(received));
|
|
36834
|
+
return !covered || credit.rawTaintReaches(sink.type, sink.line, sanitizedVars, csSeeds);
|
|
36698
36835
|
});
|
|
36699
36836
|
}
|
|
36700
36837
|
if (language === "csharp") {
|
|
@@ -40223,9 +40360,10 @@ var InterproceduralPass = class {
|
|
|
40223
40360
|
});
|
|
40224
40361
|
}
|
|
40225
40362
|
if (csCredit) {
|
|
40363
|
+
const csSeeds = sources.flatMap((s) => s.variable ? [{ variable: s.variable, line: s.line }] : []);
|
|
40226
40364
|
const credited = /* @__PURE__ */ new Set();
|
|
40227
40365
|
filteredAdditionalFlows = filteredAdditionalFlows.filter((f) => {
|
|
40228
|
-
if (f.sink_type !== "external_taint_escape" || !csCredit.creditedOnLine(f.sink_line)) return true;
|
|
40366
|
+
if (f.sink_type !== "external_taint_escape" || !csCredit.creditedOnLine(f.sink_line, csSeeds)) return true;
|
|
40229
40367
|
credited.add(f.sink_line);
|
|
40230
40368
|
return false;
|
|
40231
40369
|
});
|
|
@@ -19200,7 +19200,13 @@ var ConstantPropagator = class _ConstantPropagator {
|
|
|
19200
19200
|
}
|
|
19201
19201
|
return;
|
|
19202
19202
|
}
|
|
19203
|
-
|
|
19203
|
+
let condValue = this.evaluateExpression(condition);
|
|
19204
|
+
if (!(isKnown(condValue) && condValue.type === "bool")) {
|
|
19205
|
+
const literal = this.literalCondition(condition);
|
|
19206
|
+
if (literal !== null) {
|
|
19207
|
+
condValue = { type: "bool", value: literal, sourceLine: getNodeLine(condition) };
|
|
19208
|
+
}
|
|
19209
|
+
}
|
|
19204
19210
|
if (isKnown(condValue) && condValue.type === "bool") {
|
|
19205
19211
|
if (condValue.value === true) {
|
|
19206
19212
|
if (alternative) {
|
|
@@ -19364,13 +19370,27 @@ var ConstantPropagator = class _ConstantPropagator {
|
|
|
19364
19370
|
return;
|
|
19365
19371
|
}
|
|
19366
19372
|
const caseGroups = switchBlock.children.filter(
|
|
19367
|
-
(c) => c.type === "switch_block_statement_group" || c.type === "switch_rule" || c.type === "switch_section"
|
|
19373
|
+
(c) => c.type === "switch_block_statement_group" || c.type === "switch_rule" || c.type === "switch_section" || c.type === "switch_case" || c.type === "switch_default"
|
|
19368
19374
|
);
|
|
19375
|
+
if (caseGroups.length === 0) {
|
|
19376
|
+
this.visit(switchBlock);
|
|
19377
|
+
return;
|
|
19378
|
+
}
|
|
19369
19379
|
if (switchValue && isKnown(switchValue)) {
|
|
19370
19380
|
let matchingIdx = -1;
|
|
19371
19381
|
let defaultIdx = -1;
|
|
19372
19382
|
for (let i2 = 0; i2 < caseGroups.length; i2++) {
|
|
19373
19383
|
const caseGroup = caseGroups[i2];
|
|
19384
|
+
if (caseGroup.type === "switch_default") {
|
|
19385
|
+
defaultIdx = i2;
|
|
19386
|
+
continue;
|
|
19387
|
+
}
|
|
19388
|
+
if (caseGroup.type === "switch_case") {
|
|
19389
|
+
const label = caseGroup.childForFieldName("value");
|
|
19390
|
+
const caseValue = label ? this.literalValue(label) : null;
|
|
19391
|
+
if (caseValue !== null && caseValue === switchValue.value) matchingIdx = i2;
|
|
19392
|
+
continue;
|
|
19393
|
+
}
|
|
19374
19394
|
for (const child of caseGroup.children) {
|
|
19375
19395
|
if (child.type === "switch_label") {
|
|
19376
19396
|
const labelText = getNodeText2(child, this.source);
|
|
@@ -19473,6 +19493,33 @@ var ConstantPropagator = class _ConstantPropagator {
|
|
|
19473
19493
|
}
|
|
19474
19494
|
return false;
|
|
19475
19495
|
}
|
|
19496
|
+
/**
|
|
19497
|
+
* A condition written with literals only, in the C# grammar's node names:
|
|
19498
|
+
* `true`, `false`, `5 == 5`, `5 != 5`. Null when it is anything else. The
|
|
19499
|
+
* shared evaluator does not know `boolean_literal` / `integer_literal`.
|
|
19500
|
+
*/
|
|
19501
|
+
literalCondition(node) {
|
|
19502
|
+
if (node.type === "parenthesized_expression") {
|
|
19503
|
+
const inner = node.children.find((c) => c.isNamed);
|
|
19504
|
+
return inner ? this.literalCondition(inner) : null;
|
|
19505
|
+
}
|
|
19506
|
+
if (node.type === "boolean_literal") {
|
|
19507
|
+
const text = getNodeText2(node, this.source);
|
|
19508
|
+
return text === "true" ? true : text === "false" ? false : null;
|
|
19509
|
+
}
|
|
19510
|
+
if (node.type === "binary_expression") {
|
|
19511
|
+
const left = node.childForFieldName("left");
|
|
19512
|
+
const right = node.childForFieldName("right");
|
|
19513
|
+
const op = node.childForFieldName("operator");
|
|
19514
|
+
const opText = op ? getNodeText2(op, this.source) : "";
|
|
19515
|
+
if (!left || !right || opText !== "==" && opText !== "!=") return null;
|
|
19516
|
+
const l = this.literalValue(left);
|
|
19517
|
+
const r = this.literalValue(right);
|
|
19518
|
+
if (l === null || r === null) return null;
|
|
19519
|
+
return opText === "==" ? l === r : l !== r;
|
|
19520
|
+
}
|
|
19521
|
+
return null;
|
|
19522
|
+
}
|
|
19476
19523
|
/** The value of an integer, character or string literal node; null for anything else. */
|
|
19477
19524
|
literalValue(node) {
|
|
19478
19525
|
const text = getNodeText2(node, this.source);
|
|
@@ -19134,7 +19134,13 @@ var ConstantPropagator = class _ConstantPropagator {
|
|
|
19134
19134
|
}
|
|
19135
19135
|
return;
|
|
19136
19136
|
}
|
|
19137
|
-
|
|
19137
|
+
let condValue = this.evaluateExpression(condition);
|
|
19138
|
+
if (!(isKnown(condValue) && condValue.type === "bool")) {
|
|
19139
|
+
const literal = this.literalCondition(condition);
|
|
19140
|
+
if (literal !== null) {
|
|
19141
|
+
condValue = { type: "bool", value: literal, sourceLine: getNodeLine(condition) };
|
|
19142
|
+
}
|
|
19143
|
+
}
|
|
19138
19144
|
if (isKnown(condValue) && condValue.type === "bool") {
|
|
19139
19145
|
if (condValue.value === true) {
|
|
19140
19146
|
if (alternative) {
|
|
@@ -19298,13 +19304,27 @@ var ConstantPropagator = class _ConstantPropagator {
|
|
|
19298
19304
|
return;
|
|
19299
19305
|
}
|
|
19300
19306
|
const caseGroups = switchBlock.children.filter(
|
|
19301
|
-
(c) => c.type === "switch_block_statement_group" || c.type === "switch_rule" || c.type === "switch_section"
|
|
19307
|
+
(c) => c.type === "switch_block_statement_group" || c.type === "switch_rule" || c.type === "switch_section" || c.type === "switch_case" || c.type === "switch_default"
|
|
19302
19308
|
);
|
|
19309
|
+
if (caseGroups.length === 0) {
|
|
19310
|
+
this.visit(switchBlock);
|
|
19311
|
+
return;
|
|
19312
|
+
}
|
|
19303
19313
|
if (switchValue && isKnown(switchValue)) {
|
|
19304
19314
|
let matchingIdx = -1;
|
|
19305
19315
|
let defaultIdx = -1;
|
|
19306
19316
|
for (let i2 = 0; i2 < caseGroups.length; i2++) {
|
|
19307
19317
|
const caseGroup = caseGroups[i2];
|
|
19318
|
+
if (caseGroup.type === "switch_default") {
|
|
19319
|
+
defaultIdx = i2;
|
|
19320
|
+
continue;
|
|
19321
|
+
}
|
|
19322
|
+
if (caseGroup.type === "switch_case") {
|
|
19323
|
+
const label = caseGroup.childForFieldName("value");
|
|
19324
|
+
const caseValue = label ? this.literalValue(label) : null;
|
|
19325
|
+
if (caseValue !== null && caseValue === switchValue.value) matchingIdx = i2;
|
|
19326
|
+
continue;
|
|
19327
|
+
}
|
|
19308
19328
|
for (const child of caseGroup.children) {
|
|
19309
19329
|
if (child.type === "switch_label") {
|
|
19310
19330
|
const labelText = getNodeText2(child, this.source);
|
|
@@ -19407,6 +19427,33 @@ var ConstantPropagator = class _ConstantPropagator {
|
|
|
19407
19427
|
}
|
|
19408
19428
|
return false;
|
|
19409
19429
|
}
|
|
19430
|
+
/**
|
|
19431
|
+
* A condition written with literals only, in the C# grammar's node names:
|
|
19432
|
+
* `true`, `false`, `5 == 5`, `5 != 5`. Null when it is anything else. The
|
|
19433
|
+
* shared evaluator does not know `boolean_literal` / `integer_literal`.
|
|
19434
|
+
*/
|
|
19435
|
+
literalCondition(node) {
|
|
19436
|
+
if (node.type === "parenthesized_expression") {
|
|
19437
|
+
const inner = node.children.find((c) => c.isNamed);
|
|
19438
|
+
return inner ? this.literalCondition(inner) : null;
|
|
19439
|
+
}
|
|
19440
|
+
if (node.type === "boolean_literal") {
|
|
19441
|
+
const text = getNodeText2(node, this.source);
|
|
19442
|
+
return text === "true" ? true : text === "false" ? false : null;
|
|
19443
|
+
}
|
|
19444
|
+
if (node.type === "binary_expression") {
|
|
19445
|
+
const left = node.childForFieldName("left");
|
|
19446
|
+
const right = node.childForFieldName("right");
|
|
19447
|
+
const op = node.childForFieldName("operator");
|
|
19448
|
+
const opText = op ? getNodeText2(op, this.source) : "";
|
|
19449
|
+
if (!left || !right || opText !== "==" && opText !== "!=") return null;
|
|
19450
|
+
const l = this.literalValue(left);
|
|
19451
|
+
const r = this.literalValue(right);
|
|
19452
|
+
if (l === null || r === null) return null;
|
|
19453
|
+
return opText === "==" ? l === r : l !== r;
|
|
19454
|
+
}
|
|
19455
|
+
return null;
|
|
19456
|
+
}
|
|
19410
19457
|
/** The value of an integer, character or string literal node; null for anything else. */
|
|
19411
19458
|
literalValue(node) {
|
|
19412
19459
|
const text = getNodeText2(node, this.source);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "circle-ir",
|
|
3
|
-
"version": "4.13.
|
|
3
|
+
"version": "4.13.2",
|
|
4
4
|
"description": "High-performance Static Application Security Testing (SAST) library for detecting security vulnerabilities through taint analysis",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"module": "dist/index.js",
|