circle-ir 4.14.2 → 4.16.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/configs/sinks/golang.json +4 -4
- package/dist/analysis/config-loader.d.ts.map +1 -1
- package/dist/analysis/config-loader.js +11 -7
- package/dist/analysis/config-loader.js.map +1 -1
- package/dist/analysis/const-table-lookup.d.ts +29 -0
- package/dist/analysis/const-table-lookup.d.ts.map +1 -0
- package/dist/analysis/const-table-lookup.js +145 -0
- package/dist/analysis/const-table-lookup.js.map +1 -0
- package/dist/analysis/constant-caller-gate.d.ts +39 -0
- package/dist/analysis/constant-caller-gate.d.ts.map +1 -0
- package/dist/analysis/constant-caller-gate.js +298 -0
- package/dist/analysis/constant-caller-gate.js.map +1 -0
- package/dist/analysis/constant-propagation/evaluator.d.ts.map +1 -1
- package/dist/analysis/constant-propagation/evaluator.js +3 -1
- package/dist/analysis/constant-propagation/evaluator.js.map +1 -1
- package/dist/analysis/constant-propagation/patterns.d.ts +19 -0
- package/dist/analysis/constant-propagation/patterns.d.ts.map +1 -1
- package/dist/analysis/constant-propagation/patterns.js +33 -0
- package/dist/analysis/constant-propagation/patterns.js.map +1 -1
- package/dist/analysis/constant-propagation/propagator.d.ts +59 -0
- package/dist/analysis/constant-propagation/propagator.d.ts.map +1 -1
- package/dist/analysis/constant-propagation/propagator.js +396 -49
- package/dist/analysis/constant-propagation/propagator.js.map +1 -1
- package/dist/analysis/constant-propagation/types.d.ts +9 -0
- package/dist/analysis/constant-propagation/types.d.ts.map +1 -1
- package/dist/analysis/findings.d.ts.map +1 -1
- package/dist/analysis/findings.js +149 -4
- package/dist/analysis/findings.js.map +1 -1
- package/dist/analysis/go-strings-replacer.d.ts +21 -0
- package/dist/analysis/go-strings-replacer.d.ts.map +1 -0
- package/dist/analysis/go-strings-replacer.js +145 -0
- package/dist/analysis/go-strings-replacer.js.map +1 -0
- package/dist/analysis/interprocedural.d.ts.map +1 -1
- package/dist/analysis/interprocedural.js +148 -0
- package/dist/analysis/interprocedural.js.map +1 -1
- package/dist/analysis/java-array-element-keys.d.ts +14 -0
- package/dist/analysis/java-array-element-keys.d.ts.map +1 -0
- package/dist/analysis/java-array-element-keys.js +36 -0
- package/dist/analysis/java-array-element-keys.js.map +1 -0
- package/dist/analysis/java-canonical-prefix.d.ts +42 -0
- package/dist/analysis/java-canonical-prefix.d.ts.map +1 -0
- package/dist/analysis/java-canonical-prefix.js +145 -0
- package/dist/analysis/java-canonical-prefix.js.map +1 -0
- package/dist/analysis/java-crlf-strip.d.ts +30 -0
- package/dist/analysis/java-crlf-strip.d.ts.map +1 -0
- package/dist/analysis/java-crlf-strip.js +195 -0
- package/dist/analysis/java-crlf-strip.js.map +1 -0
- package/dist/analysis/js-crlf-strip.d.ts +26 -0
- package/dist/analysis/js-crlf-strip.d.ts.map +1 -0
- package/dist/analysis/js-crlf-strip.js +244 -0
- package/dist/analysis/js-crlf-strip.js.map +1 -0
- package/dist/analysis/passes/cleartext-credential-transport-pass.d.ts +2 -0
- package/dist/analysis/passes/cleartext-credential-transport-pass.d.ts.map +1 -1
- package/dist/analysis/passes/cleartext-credential-transport-pass.js +64 -12
- package/dist/analysis/passes/cleartext-credential-transport-pass.js.map +1 -1
- package/dist/analysis/passes/cross-file-pass.d.ts.map +1 -1
- package/dist/analysis/passes/cross-file-pass.js +43 -0
- package/dist/analysis/passes/cross-file-pass.js.map +1 -1
- package/dist/analysis/passes/csharp-sanitizer-credit.d.ts +12 -0
- package/dist/analysis/passes/csharp-sanitizer-credit.d.ts.map +1 -1
- package/dist/analysis/passes/csharp-sanitizer-credit.js +127 -12
- package/dist/analysis/passes/csharp-sanitizer-credit.js.map +1 -1
- package/dist/analysis/passes/interprocedural-pass.d.ts +28 -0
- package/dist/analysis/passes/interprocedural-pass.d.ts.map +1 -1
- package/dist/analysis/passes/interprocedural-pass.js +20 -1
- package/dist/analysis/passes/interprocedural-pass.js.map +1 -1
- package/dist/analysis/passes/language-sources-pass.d.ts +76 -2
- package/dist/analysis/passes/language-sources-pass.d.ts.map +1 -1
- package/dist/analysis/passes/language-sources-pass.js +2045 -156
- package/dist/analysis/passes/language-sources-pass.js.map +1 -1
- package/dist/analysis/passes/param-source-flow-gate-pass.d.ts +8 -0
- package/dist/analysis/passes/param-source-flow-gate-pass.d.ts.map +1 -1
- package/dist/analysis/passes/param-source-flow-gate-pass.js +214 -6
- package/dist/analysis/passes/param-source-flow-gate-pass.js.map +1 -1
- package/dist/analysis/passes/plaintext-password-storage-pass.d.ts +6 -0
- package/dist/analysis/passes/plaintext-password-storage-pass.d.ts.map +1 -1
- package/dist/analysis/passes/plaintext-password-storage-pass.js +160 -0
- package/dist/analysis/passes/plaintext-password-storage-pass.js.map +1 -1
- package/dist/analysis/passes/scan-secrets-pass.d.ts +1 -1
- package/dist/analysis/passes/scan-secrets-pass.d.ts.map +1 -1
- package/dist/analysis/passes/scan-secrets-pass.js +7 -1
- package/dist/analysis/passes/scan-secrets-pass.js.map +1 -1
- package/dist/analysis/passes/sink-filter-pass.d.ts +1 -1
- package/dist/analysis/passes/sink-filter-pass.d.ts.map +1 -1
- package/dist/analysis/passes/sink-filter-pass.js +509 -27
- package/dist/analysis/passes/sink-filter-pass.js.map +1 -1
- package/dist/analysis/passes/taint-matcher-pass.d.ts.map +1 -1
- package/dist/analysis/passes/taint-matcher-pass.js +2 -2
- package/dist/analysis/passes/taint-matcher-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 +1606 -60
- package/dist/analysis/passes/taint-propagation-pass.js.map +1 -1
- package/dist/analysis/passes/weak-hash-pass.d.ts +6 -0
- package/dist/analysis/passes/weak-hash-pass.d.ts.map +1 -1
- package/dist/analysis/passes/weak-hash-pass.js +191 -4
- package/dist/analysis/passes/weak-hash-pass.js.map +1 -1
- package/dist/analysis/passes/weak-random-pass.d.ts +5 -0
- package/dist/analysis/passes/weak-random-pass.d.ts.map +1 -1
- package/dist/analysis/passes/weak-random-pass.js +30 -0
- package/dist/analysis/passes/weak-random-pass.js.map +1 -1
- package/dist/analysis/project-callee-escape-gate.d.ts +40 -0
- package/dist/analysis/project-callee-escape-gate.d.ts.map +1 -0
- package/dist/analysis/project-callee-escape-gate.js +243 -0
- package/dist/analysis/project-callee-escape-gate.js.map +1 -0
- package/dist/analysis/rust-literal-guard-rebind.d.ts +34 -0
- package/dist/analysis/rust-literal-guard-rebind.d.ts.map +1 -0
- package/dist/analysis/rust-literal-guard-rebind.js +197 -0
- package/dist/analysis/rust-literal-guard-rebind.js.map +1 -0
- package/dist/analysis/rust-literal-guard.d.ts +33 -0
- package/dist/analysis/rust-literal-guard.d.ts.map +1 -0
- package/dist/analysis/rust-literal-guard.js +422 -0
- package/dist/analysis/rust-literal-guard.js.map +1 -0
- package/dist/analysis/scalar-derivation.d.ts +33 -0
- package/dist/analysis/scalar-derivation.d.ts.map +1 -0
- package/dist/analysis/scalar-derivation.js +368 -0
- package/dist/analysis/scalar-derivation.js.map +1 -0
- package/dist/analysis/taint-matcher.d.ts +2 -2
- package/dist/analysis/taint-matcher.d.ts.map +1 -1
- package/dist/analysis/taint-matcher.js +695 -34
- package/dist/analysis/taint-matcher.js.map +1 -1
- package/dist/analysis/taint-propagation.d.ts +26 -1
- package/dist/analysis/taint-propagation.d.ts.map +1 -1
- package/dist/analysis/taint-propagation.js +375 -24
- package/dist/analysis/taint-propagation.js.map +1 -1
- package/dist/analyzer.d.ts +16 -2
- package/dist/analyzer.d.ts.map +1 -1
- package/dist/analyzer.js +19 -2
- package/dist/analyzer.js.map +1 -1
- package/dist/browser/circle-ir.js +25539 -19200
- package/dist/core/circle-ir-core.cjs +3945 -2126
- package/dist/core/circle-ir-core.js +3945 -2126
- package/dist/core/extractors/calls.d.ts +12 -1
- package/dist/core/extractors/calls.d.ts.map +1 -1
- package/dist/core/extractors/calls.js +332 -22
- package/dist/core/extractors/calls.js.map +1 -1
- package/dist/core/extractors/dfg.js +336 -10
- package/dist/core/extractors/dfg.js.map +1 -1
- package/dist/core/extractors/types.d.ts +5 -1
- package/dist/core/extractors/types.d.ts.map +1 -1
- package/dist/core/extractors/types.js +62 -0
- package/dist/core/extractors/types.js.map +1 -1
- package/dist/graph/analysis-pass.d.ts +6 -1
- package/dist/graph/analysis-pass.d.ts.map +1 -1
- package/dist/graph/analysis-pass.js +2 -1
- package/dist/graph/analysis-pass.js.map +1 -1
- package/dist/graph/code-graph.d.ts +9 -0
- package/dist/graph/code-graph.d.ts.map +1 -1
- package/dist/graph/code-graph.js +11 -0
- package/dist/graph/code-graph.js.map +1 -1
- package/dist/navigation/navigation-index.d.ts +134 -14
- package/dist/navigation/navigation-index.d.ts.map +1 -1
- package/dist/navigation/navigation-index.js +1158 -104
- package/dist/navigation/navigation-index.js.map +1 -1
- package/dist/navigation/types.d.ts +59 -2
- package/dist/navigation/types.d.ts.map +1 -1
- package/dist/resolution/cross-file.d.ts +16 -0
- package/dist/resolution/cross-file.d.ts.map +1 -1
- package/dist/resolution/cross-file.js +110 -42
- package/dist/resolution/cross-file.js.map +1 -1
- package/dist/resolution/type-hierarchy.d.ts +3 -0
- package/dist/resolution/type-hierarchy.d.ts.map +1 -1
- package/dist/resolution/type-hierarchy.js +45 -1
- package/dist/resolution/type-hierarchy.js.map +1 -1
- package/dist/types/index.d.ts +33 -0
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/index.js.map +1 -1
- package/dist/wasm/web-tree-sitter.wasm +0 -0
- package/package.json +3 -3
|
@@ -168,6 +168,7 @@ function buildCSharpDFG(tree, cache) {
|
|
|
168
168
|
* Build DFG for Java code.
|
|
169
169
|
*/
|
|
170
170
|
function buildJavaDFG(tree, cache) {
|
|
171
|
+
multiLineValueLinks = [];
|
|
171
172
|
const defs = [];
|
|
172
173
|
const uses = [];
|
|
173
174
|
let defIdCounter = 1;
|
|
@@ -219,6 +220,7 @@ function buildJavaDFG(tree, cache) {
|
|
|
219
220
|
}
|
|
220
221
|
// Compute def-use chains
|
|
221
222
|
const chains = computeChains(defs, uses);
|
|
223
|
+
linkMultiLineValues(chains, uses);
|
|
222
224
|
return { defs, uses, chains };
|
|
223
225
|
}
|
|
224
226
|
/**
|
|
@@ -352,6 +354,51 @@ function processBlock(block, startDefId, startUseId, scopeStack, isJavaScript) {
|
|
|
352
354
|
}
|
|
353
355
|
return { defs, uses, nextDefId: defId, nextUseId: useId };
|
|
354
356
|
}
|
|
357
|
+
/**
|
|
358
|
+
* Java: a local whose initializer / assigned value wraps over several lines
|
|
359
|
+
* (`label = new String(\n Base64.decode(\n picked.getBytes()));`) holds uses on
|
|
360
|
+
* lines after the definition's own line, and `computeChains` links a use to a
|
|
361
|
+
* definition only on the same line. Each such definition is recorded here with
|
|
362
|
+
* the uses of its value, and `buildJavaDFG` links them once every use has its
|
|
363
|
+
* final reaching definition (cognium-dev#597).
|
|
364
|
+
*/
|
|
365
|
+
let multiLineValueLinks = [];
|
|
366
|
+
function recordMultiLineValue(value, defId, valueUses, isJavaScript) {
|
|
367
|
+
if (isJavaScript || value.endPosition.row <= value.startPosition.row)
|
|
368
|
+
return;
|
|
369
|
+
// A lambda, anonymous class or switch expression inside the value has its own
|
|
370
|
+
// statements; their uses do not feed the value itself. Leave those alone.
|
|
371
|
+
if (findNodes(value, 'lambda_expression').length > 0
|
|
372
|
+
|| findNodes(value, 'class_body').length > 0
|
|
373
|
+
|| findNodes(value, 'switch_expression').length > 0)
|
|
374
|
+
return;
|
|
375
|
+
const useIds = valueUses.filter(u => u.line > value.startPosition.row + 1).map(u => u.id);
|
|
376
|
+
if (useIds.length > 0)
|
|
377
|
+
multiLineValueLinks.push({ defId, useIds });
|
|
378
|
+
}
|
|
379
|
+
/** Chain each use inside a wrapped value to the definition it feeds (#597). */
|
|
380
|
+
function linkMultiLineValues(chains, uses) {
|
|
381
|
+
if (multiLineValueLinks.length === 0)
|
|
382
|
+
return;
|
|
383
|
+
const useById = new Map();
|
|
384
|
+
for (const u of uses)
|
|
385
|
+
useById.set(u.id, u);
|
|
386
|
+
const seen = new Set(chains.map(c => `${c.from_def}|${c.to_def}|${c.via}`));
|
|
387
|
+
for (const { defId, useIds } of multiLineValueLinks) {
|
|
388
|
+
for (const id of useIds) {
|
|
389
|
+
const u = useById.get(id);
|
|
390
|
+
if (!u || u.def_id === null || u.def_id === defId)
|
|
391
|
+
continue;
|
|
392
|
+
const key = `${u.def_id}|${defId}|${u.variable}`;
|
|
393
|
+
if (seen.has(key))
|
|
394
|
+
continue;
|
|
395
|
+
seen.add(key);
|
|
396
|
+
chains.push({ from_def: u.def_id, to_def: defId, via: u.variable });
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
chains.sort((a, b) => a.from_def - b.from_def || a.to_def - b.to_def);
|
|
400
|
+
multiLineValueLinks = [];
|
|
401
|
+
}
|
|
355
402
|
/**
|
|
356
403
|
* Process a single statement.
|
|
357
404
|
*/
|
|
@@ -368,6 +415,7 @@ function processStatement(stmt, startDefId, startUseId, scopeStack, isJavaScript
|
|
|
368
415
|
const declarators = findNodes(stmt, 'variable_declarator');
|
|
369
416
|
for (const decl of declarators) {
|
|
370
417
|
const nameNode = decl.childForFieldName('name');
|
|
418
|
+
let declaredDefId = null;
|
|
371
419
|
if (nameNode) {
|
|
372
420
|
// Handle simple identifiers
|
|
373
421
|
if (nameNode.type === 'identifier') {
|
|
@@ -380,6 +428,8 @@ function processStatement(stmt, startDefId, startUseId, scopeStack, isJavaScript
|
|
|
380
428
|
};
|
|
381
429
|
defs.push(def);
|
|
382
430
|
currentScope(scopeStack).set(name, def.id);
|
|
431
|
+
markDeclared(currentScope(scopeStack), name);
|
|
432
|
+
declaredDefId = def.id;
|
|
383
433
|
}
|
|
384
434
|
else if (isJavaScript) {
|
|
385
435
|
// Handle destructuring patterns in JavaScript
|
|
@@ -396,6 +446,8 @@ function processStatement(stmt, startDefId, startUseId, scopeStack, isJavaScript
|
|
|
396
446
|
const initUses = extractUses(init, useId, scopeStack, isJavaScript);
|
|
397
447
|
uses.push(...initUses.uses);
|
|
398
448
|
useId = initUses.nextId;
|
|
449
|
+
if (declaredDefId !== null)
|
|
450
|
+
recordMultiLineValue(init, declaredDefId, initUses.uses, isJavaScript);
|
|
399
451
|
}
|
|
400
452
|
}
|
|
401
453
|
}
|
|
@@ -433,21 +485,37 @@ function processStatement(stmt, startDefId, startUseId, scopeStack, isJavaScript
|
|
|
433
485
|
}
|
|
434
486
|
// Process branches
|
|
435
487
|
const consequence = stmt.childForFieldName('consequence');
|
|
488
|
+
const alternative = stmt.childForFieldName('alternative');
|
|
489
|
+
const topAlts = alternateDefs.get(currentScope(scopeStack));
|
|
490
|
+
const before = new Map();
|
|
491
|
+
if (topAlts)
|
|
492
|
+
for (const [name, ids] of topAlts)
|
|
493
|
+
before.set(name, [...ids]);
|
|
494
|
+
let armA = null;
|
|
495
|
+
let armB = null;
|
|
436
496
|
if (consequence) {
|
|
437
|
-
const result =
|
|
497
|
+
const result = processArm(consequence, defId, useId, scopeStack, isJavaScript);
|
|
498
|
+
armA = result.popped;
|
|
438
499
|
defs.push(...result.defs);
|
|
439
500
|
uses.push(...result.uses);
|
|
440
501
|
defId = result.nextDefId;
|
|
441
502
|
useId = result.nextUseId;
|
|
442
503
|
}
|
|
443
|
-
const alternative = stmt.childForFieldName('alternative');
|
|
444
504
|
if (alternative) {
|
|
445
|
-
const result =
|
|
505
|
+
const result = processArm(alternative, defId, useId, scopeStack, isJavaScript);
|
|
506
|
+
armB = result.popped;
|
|
446
507
|
defs.push(...result.defs);
|
|
447
508
|
uses.push(...result.uses);
|
|
448
509
|
defId = result.nextDefId;
|
|
449
510
|
useId = result.nextUseId;
|
|
450
511
|
}
|
|
512
|
+
// An `else if` arm is merged too: its own merge has already put the chain's
|
|
513
|
+
// final def on the arm's scope only when every arm of the chain assigns, so
|
|
514
|
+
// a chain with no final `else` (or one arm that skips the name) keeps the
|
|
515
|
+
// reaching def.
|
|
516
|
+
if (armA && armB) {
|
|
517
|
+
mergeIfElseDefs(armA, armB, before, scopeStack);
|
|
518
|
+
}
|
|
451
519
|
break;
|
|
452
520
|
}
|
|
453
521
|
case 'for_statement':
|
|
@@ -525,7 +593,7 @@ function processStatement(stmt, startDefId, startUseId, scopeStack, isJavaScript
|
|
|
525
593
|
// Process body
|
|
526
594
|
const body = stmt.childForFieldName('body');
|
|
527
595
|
if (body) {
|
|
528
|
-
const result =
|
|
596
|
+
const result = processArm(body, defId, useId, scopeStack, isJavaScript);
|
|
529
597
|
defs.push(...result.defs);
|
|
530
598
|
uses.push(...result.uses);
|
|
531
599
|
defId = result.nextDefId;
|
|
@@ -555,13 +623,22 @@ function processStatement(stmt, startDefId, startUseId, scopeStack, isJavaScript
|
|
|
555
623
|
const group = body.child(i);
|
|
556
624
|
if (!group)
|
|
557
625
|
continue;
|
|
626
|
+
// JS (#582): each `case` / `default` gets a scope of its own and its
|
|
627
|
+
// defs are parked beside the others, so a constant in `default` does
|
|
628
|
+
// not replace a tainted assignment in an earlier `case`.
|
|
629
|
+
if (isJavaScript)
|
|
630
|
+
scopeStack.push(new Map());
|
|
558
631
|
const result = processBlock(group, defId, useId, scopeStack, isJavaScript);
|
|
559
632
|
defs.push(...result.defs);
|
|
560
633
|
uses.push(...result.uses);
|
|
561
634
|
defId = result.nextDefId;
|
|
562
635
|
useId = result.nextUseId;
|
|
636
|
+
if (isJavaScript)
|
|
637
|
+
carryBranchDefsOut(scopeStack.pop(), scopeStack, false);
|
|
563
638
|
}
|
|
564
|
-
|
|
639
|
+
// Alternates parked on the switch scope go on to the enclosing scope.
|
|
640
|
+
const poppedSwitch = scopeStack.pop();
|
|
641
|
+
carryBranchDefsOut(poppedSwitch, scopeStack, false, true);
|
|
565
642
|
}
|
|
566
643
|
break;
|
|
567
644
|
}
|
|
@@ -574,7 +651,33 @@ function processStatement(stmt, startDefId, startUseId, scopeStack, isJavaScript
|
|
|
574
651
|
uses.push(...result.uses);
|
|
575
652
|
defId = result.nextDefId;
|
|
576
653
|
useId = result.nextUseId;
|
|
577
|
-
scopeStack.pop();
|
|
654
|
+
const popped = scopeStack.pop();
|
|
655
|
+
// A bare `{ … }` (or `synchronized (x) { … }`) always runs, so its defs
|
|
656
|
+
// replace the enclosing scope's; an if/loop/try/switch/lambda body may
|
|
657
|
+
// not, so its defs are kept beside them.
|
|
658
|
+
// JS: a bare `{ … }` inside a statement_block likewise always runs; the
|
|
659
|
+
// block of an `if` / `else` / loop / `try` may not, so (#582) its defs are
|
|
660
|
+
// carried out as alternates, as for Java (#692).
|
|
661
|
+
const alwaysRuns = stmt.parent?.type === 'block' || stmt.parent?.type === 'synchronized_statement' ||
|
|
662
|
+
(isJavaScript && stmt.parent?.type === 'statement_block');
|
|
663
|
+
lastPoppedBlock = popped;
|
|
664
|
+
carryBranchDefsOut(popped, scopeStack, alwaysRuns);
|
|
665
|
+
break;
|
|
666
|
+
}
|
|
667
|
+
case 'else_clause': {
|
|
668
|
+
// JavaScript: `else { … }` / `else if (…) { … }` hold their arm one level
|
|
669
|
+
// down; without this the arm fell to `default` and recorded no defs, so an
|
|
670
|
+
// assignment in an `else` never reached a use after the `if` (#582).
|
|
671
|
+
for (let i = 0; i < stmt.childCount; i++) {
|
|
672
|
+
const arm = stmt.child(i);
|
|
673
|
+
if (!arm || arm.type === 'else')
|
|
674
|
+
continue;
|
|
675
|
+
const result = processStatement(arm, defId, useId, scopeStack, isJavaScript);
|
|
676
|
+
defs.push(...result.defs);
|
|
677
|
+
uses.push(...result.uses);
|
|
678
|
+
defId = result.nextDefId;
|
|
679
|
+
useId = result.nextUseId;
|
|
680
|
+
}
|
|
578
681
|
break;
|
|
579
682
|
}
|
|
580
683
|
default: {
|
|
@@ -600,6 +703,7 @@ function processExpression(expr, startDefId, startUseId, scopeStack, isJavaScrip
|
|
|
600
703
|
// 2. Then create a def for the left side
|
|
601
704
|
const left = expr.childForFieldName('left');
|
|
602
705
|
const right = expr.childForFieldName('right');
|
|
706
|
+
let assignedValueUses = [];
|
|
603
707
|
// Process right side first - may contain nested assignments
|
|
604
708
|
if (right) {
|
|
605
709
|
if (right.type === 'assignment_expression') {
|
|
@@ -615,6 +719,7 @@ function processExpression(expr, startDefId, startUseId, scopeStack, isJavaScrip
|
|
|
615
719
|
const rightUses = extractUses(right, useId, scopeStack, isJavaScript);
|
|
616
720
|
uses.push(...rightUses.uses);
|
|
617
721
|
useId = rightUses.nextId;
|
|
722
|
+
assignedValueUses = rightUses.uses;
|
|
618
723
|
}
|
|
619
724
|
}
|
|
620
725
|
// Now process left side as a definition
|
|
@@ -628,6 +733,10 @@ function processExpression(expr, startDefId, startUseId, scopeStack, isJavaScrip
|
|
|
628
733
|
};
|
|
629
734
|
defs.push(def);
|
|
630
735
|
currentScope(scopeStack).set(name, def.id);
|
|
736
|
+
if (right && assignedValueUses.length > 0)
|
|
737
|
+
recordMultiLineValue(right, def.id, assignedValueUses, isJavaScript);
|
|
738
|
+
// A straight-line reassignment replaces any branch defs carried out (#596).
|
|
739
|
+
alternateDefs.get(currentScope(scopeStack))?.delete(name);
|
|
631
740
|
}
|
|
632
741
|
else if (left) {
|
|
633
742
|
// Field access or array access - extract uses
|
|
@@ -740,6 +849,12 @@ function extractUses(node, startId, scopeStack, isJavaScript) {
|
|
|
740
849
|
line: n.startPosition.row + 1,
|
|
741
850
|
def_id: reachingDef,
|
|
742
851
|
});
|
|
852
|
+
// A def made inside a nested block may also reach this use (#596).
|
|
853
|
+
for (const alt of findAlternateDefs(name, scopeStack)) {
|
|
854
|
+
if (alt === reachingDef)
|
|
855
|
+
continue;
|
|
856
|
+
uses.push({ id: id++, variable: name, line: n.startPosition.row + 1, def_id: alt });
|
|
857
|
+
}
|
|
743
858
|
}
|
|
744
859
|
});
|
|
745
860
|
return { uses, nextId: id };
|
|
@@ -825,7 +940,7 @@ function extractJSParameterDefs(params, startId) {
|
|
|
825
940
|
addPatternDefs(nameNode, line);
|
|
826
941
|
return;
|
|
827
942
|
}
|
|
828
|
-
if (patternNode.type
|
|
943
|
+
if (isDestructuringDefaultPattern(patternNode.type)) {
|
|
829
944
|
const leftNode = patternNode.childForFieldName('left');
|
|
830
945
|
if (leftNode)
|
|
831
946
|
addPatternDefs(leftNode, line);
|
|
@@ -869,7 +984,7 @@ function extractJSParameterDefs(params, startId) {
|
|
|
869
984
|
}
|
|
870
985
|
}
|
|
871
986
|
// Assignment pattern (default value): name = defaultValue
|
|
872
|
-
else if (param.type
|
|
987
|
+
else if (isDestructuringDefaultPattern(param.type)) {
|
|
873
988
|
const leftNode = param.childForFieldName('left');
|
|
874
989
|
if (leftNode && leftNode.type === 'identifier') {
|
|
875
990
|
defs.push({
|
|
@@ -932,6 +1047,10 @@ function extractJSFieldDefs(body, startId) {
|
|
|
932
1047
|
}
|
|
933
1048
|
return defs;
|
|
934
1049
|
}
|
|
1050
|
+
/** Tree-sitter-javascript object destructuring default (`{ sort = "name" }`). */
|
|
1051
|
+
function isDestructuringDefaultPattern(type) {
|
|
1052
|
+
return type === 'assignment_pattern' || type === 'object_assignment_pattern';
|
|
1053
|
+
}
|
|
935
1054
|
/**
|
|
936
1055
|
* Extract definitions from destructuring patterns.
|
|
937
1056
|
*/
|
|
@@ -982,7 +1101,7 @@ function extractDestructuringDefs(node, startId) {
|
|
|
982
1101
|
});
|
|
983
1102
|
}
|
|
984
1103
|
}
|
|
985
|
-
else if (child.type
|
|
1104
|
+
else if (isDestructuringDefaultPattern(child.type)) {
|
|
986
1105
|
// { foo = defaultValue }
|
|
987
1106
|
const leftNode = child.childForFieldName('left');
|
|
988
1107
|
if (leftNode && leftNode.type === 'shorthand_property_identifier_pattern') {
|
|
@@ -1028,7 +1147,7 @@ function extractDestructuringDefs(node, startId) {
|
|
|
1028
1147
|
});
|
|
1029
1148
|
}
|
|
1030
1149
|
}
|
|
1031
|
-
else if (child.type
|
|
1150
|
+
else if (isDestructuringDefaultPattern(child.type)) {
|
|
1032
1151
|
// [x = defaultValue]
|
|
1033
1152
|
const leftNode = child.childForFieldName('left');
|
|
1034
1153
|
if (leftNode && leftNode.type === 'identifier') {
|
|
@@ -1056,6 +1175,213 @@ function extractDestructuringDefs(node, startId) {
|
|
|
1056
1175
|
function currentScope(scopeStack) {
|
|
1057
1176
|
return scopeStack[scopeStack.length - 1];
|
|
1058
1177
|
}
|
|
1178
|
+
/**
|
|
1179
|
+
* Java: an assignment inside a braced block to a variable declared outside it
|
|
1180
|
+
* was bound in the block's own scope, so it was dropped when the block ended
|
|
1181
|
+
* and later uses saw only the outer def (cognium-dev#596 / #597:
|
|
1182
|
+
* `String t = ""; if (a) { if (b) t = tainted; } exec(c + t)` had no flow).
|
|
1183
|
+
* The block def may or may not run, so it is kept as an *alternate* reaching
|
|
1184
|
+
* def of the enclosing scope, next to the outer one, not in place of it.
|
|
1185
|
+
*/
|
|
1186
|
+
/** The scope the most recent `block` / `statement_block` case popped (read right after processing a block arm). */
|
|
1187
|
+
let lastPoppedBlock = null;
|
|
1188
|
+
/**
|
|
1189
|
+
* True when the `if` / loop that owns `arm` sits directly in a bare `{ … }` or
|
|
1190
|
+
* `synchronized` block. Only there is a braceless body given a scope of its own:
|
|
1191
|
+
* at method level a braceless `if (!ALLOWED.contains(c)) c = "name";` overwrites
|
|
1192
|
+
* the def on purpose (the allowlist idiom).
|
|
1193
|
+
*/
|
|
1194
|
+
function inAlwaysRunBlock(arm) {
|
|
1195
|
+
let n = arm.parent;
|
|
1196
|
+
while (n && (n.type === 'if_statement' || n.type === 'for_statement' || n.type === 'enhanced_for_statement' ||
|
|
1197
|
+
n.type === 'while_statement' || n.type === 'do_statement'))
|
|
1198
|
+
n = n.parent;
|
|
1199
|
+
return !!n && n.type === 'block' && !!n.parent &&
|
|
1200
|
+
(n.parent.type === 'block' || n.parent.type === 'synchronized_statement');
|
|
1201
|
+
}
|
|
1202
|
+
/**
|
|
1203
|
+
* True when the `if` / loop that owns the braceless `arm` sits in a braced
|
|
1204
|
+
* block that is not a method body (a braced `if` / `else` / loop / `try` arm, or
|
|
1205
|
+
* a bare / `synchronized` block). That block shares one scope slot per name, so
|
|
1206
|
+
* a braceless body there must not assign into it: the write may not run, and it
|
|
1207
|
+
* would hide an earlier write in the same block.
|
|
1208
|
+
*/
|
|
1209
|
+
/** Braceless `if` / `else` arm at statement level (parent has an alternative). */
|
|
1210
|
+
function isBracelessIfElseArm(arm) {
|
|
1211
|
+
const parent = arm.parent;
|
|
1212
|
+
if (!parent || parent.type !== 'if_statement')
|
|
1213
|
+
return false;
|
|
1214
|
+
return parent.childForFieldName('alternative') != null;
|
|
1215
|
+
}
|
|
1216
|
+
function inNestedBlock(arm) {
|
|
1217
|
+
let n = arm.parent;
|
|
1218
|
+
while (n && (n.type === 'if_statement' || n.type === 'for_statement' || n.type === 'enhanced_for_statement' ||
|
|
1219
|
+
n.type === 'while_statement' || n.type === 'do_statement'))
|
|
1220
|
+
n = n.parent;
|
|
1221
|
+
if (!n || n.type !== 'block' || !n.parent)
|
|
1222
|
+
return false;
|
|
1223
|
+
switch (n.parent.type) {
|
|
1224
|
+
case 'block':
|
|
1225
|
+
case 'synchronized_statement':
|
|
1226
|
+
case 'if_statement':
|
|
1227
|
+
case 'for_statement':
|
|
1228
|
+
case 'enhanced_for_statement':
|
|
1229
|
+
case 'while_statement':
|
|
1230
|
+
case 'do_statement':
|
|
1231
|
+
case 'try_statement':
|
|
1232
|
+
case 'try_with_resources_statement':
|
|
1233
|
+
case 'catch_clause':
|
|
1234
|
+
case 'finally_clause':
|
|
1235
|
+
case 'labeled_statement':
|
|
1236
|
+
return true;
|
|
1237
|
+
default:
|
|
1238
|
+
return false;
|
|
1239
|
+
}
|
|
1240
|
+
}
|
|
1241
|
+
/**
|
|
1242
|
+
* Process the body of an `if` / `else` / loop. A braced body is a `block` and
|
|
1243
|
+
* pops its own scope. A braceless body (`if (c) t = x;`) is a single statement
|
|
1244
|
+
* that would otherwise assign in the enclosing scope as if it always ran, so for
|
|
1245
|
+
* Java inside an always-run block it gets a scope of its own and its defs are
|
|
1246
|
+
* carried out as alternates. Returns the scope the body assigned in (null
|
|
1247
|
+
* elsewhere), for `if` / `else` merging.
|
|
1248
|
+
*/
|
|
1249
|
+
function processArm(node, defId, useId, scopeStack, isJavaScript) {
|
|
1250
|
+
const isBlock = node.type === 'block' || node.type === 'statement_block';
|
|
1251
|
+
const bracelessTopIfElse = !isBlock && isBracelessIfElseArm(node);
|
|
1252
|
+
if (isJavaScript || (isBlock ? !inAlwaysRunBlock(node) : !inNestedBlock(node) && !bracelessTopIfElse)) {
|
|
1253
|
+
lastPoppedBlock = null;
|
|
1254
|
+
const result = processStatement(node, defId, useId, scopeStack, isJavaScript);
|
|
1255
|
+
return { ...result, popped: null };
|
|
1256
|
+
}
|
|
1257
|
+
if (isBlock) {
|
|
1258
|
+
lastPoppedBlock = null;
|
|
1259
|
+
const result = processStatement(node, defId, useId, scopeStack, isJavaScript);
|
|
1260
|
+
return { ...result, popped: lastPoppedBlock };
|
|
1261
|
+
}
|
|
1262
|
+
scopeStack.push(new Map());
|
|
1263
|
+
const result = processStatement(node, defId, useId, scopeStack, isJavaScript);
|
|
1264
|
+
const popped = scopeStack.pop();
|
|
1265
|
+
carryBranchDefsOut(popped, scopeStack, false);
|
|
1266
|
+
return { ...result, popped };
|
|
1267
|
+
}
|
|
1268
|
+
/**
|
|
1269
|
+
* After an `if` / `else` whose two arms both assign `name` directly, every path
|
|
1270
|
+
* writes it: the arm defs replace the reaching def (and the alternates that
|
|
1271
|
+
* existed before the `if`) instead of sitting beside them.
|
|
1272
|
+
*/
|
|
1273
|
+
function mergeIfElseDefs(armA, armB, before, scopeStack) {
|
|
1274
|
+
const top = scopeStack[scopeStack.length - 1];
|
|
1275
|
+
if (!top)
|
|
1276
|
+
return;
|
|
1277
|
+
const alts = alternateDefs.get(top);
|
|
1278
|
+
for (const [name, idA] of armA) {
|
|
1279
|
+
const idB = armB.get(name);
|
|
1280
|
+
if (idB === undefined)
|
|
1281
|
+
continue;
|
|
1282
|
+
if (declaredNames.get(armA)?.has(name) || declaredNames.get(armB)?.has(name))
|
|
1283
|
+
continue;
|
|
1284
|
+
if (!scopeStack.some((scope) => scope.has(name)))
|
|
1285
|
+
continue;
|
|
1286
|
+
const prior = new Set(before.get(name) ?? []);
|
|
1287
|
+
const arms = (alts?.get(name) ?? []).filter((id) => !prior.has(id) && id !== idB);
|
|
1288
|
+
if (!arms.includes(idA))
|
|
1289
|
+
arms.push(idA);
|
|
1290
|
+
top.set(name, idB);
|
|
1291
|
+
let map = alts;
|
|
1292
|
+
if (!map) {
|
|
1293
|
+
map = new Map();
|
|
1294
|
+
alternateDefs.set(top, map);
|
|
1295
|
+
}
|
|
1296
|
+
map.set(name, arms);
|
|
1297
|
+
}
|
|
1298
|
+
}
|
|
1299
|
+
const declaredNames = new WeakMap();
|
|
1300
|
+
const alternateDefs = new WeakMap();
|
|
1301
|
+
function markDeclared(scope, name) {
|
|
1302
|
+
let set = declaredNames.get(scope);
|
|
1303
|
+
if (!set) {
|
|
1304
|
+
set = new Set();
|
|
1305
|
+
declaredNames.set(scope, set);
|
|
1306
|
+
}
|
|
1307
|
+
set.add(name);
|
|
1308
|
+
alternateDefs.get(scope)?.delete(name);
|
|
1309
|
+
}
|
|
1310
|
+
function carryBranchDefsOut(popped, scopeStack, alwaysRuns = false, altsOnly = false) {
|
|
1311
|
+
const declared = declaredNames.get(popped);
|
|
1312
|
+
const poppedAlts = alternateDefs.get(popped);
|
|
1313
|
+
const top = scopeStack[scopeStack.length - 1];
|
|
1314
|
+
if (!top)
|
|
1315
|
+
return;
|
|
1316
|
+
if (alwaysRuns) {
|
|
1317
|
+
// Straight-line block: its defs run whenever it does, so they are as if
|
|
1318
|
+
// made directly in the enclosing scope. They become that scope's reaching
|
|
1319
|
+
// def and drop the alternates for the name; defs the block itself held as
|
|
1320
|
+
// alternates stay alternates. (A def under a braceless `if` / loop body
|
|
1321
|
+
// never lands in `popped`: that body has its own scope, see `processArm`.)
|
|
1322
|
+
for (const [name, id] of popped) {
|
|
1323
|
+
if (declared?.has(name))
|
|
1324
|
+
continue;
|
|
1325
|
+
if (!scopeStack.some((scope) => scope.has(name)))
|
|
1326
|
+
continue;
|
|
1327
|
+
top.set(name, id);
|
|
1328
|
+
let alts = alternateDefs.get(top);
|
|
1329
|
+
if (!alts) {
|
|
1330
|
+
alts = new Map();
|
|
1331
|
+
alternateDefs.set(top, alts);
|
|
1332
|
+
}
|
|
1333
|
+
alts.delete(name);
|
|
1334
|
+
const kept = poppedAlts?.get(name);
|
|
1335
|
+
if (kept && kept.length > 0)
|
|
1336
|
+
alts.set(name, [...kept]);
|
|
1337
|
+
}
|
|
1338
|
+
// Alternates for a name the block did not assign itself stay alternates.
|
|
1339
|
+
if (poppedAlts)
|
|
1340
|
+
for (const [name, ids] of poppedAlts)
|
|
1341
|
+
if (!popped.has(name))
|
|
1342
|
+
carry(name, ids);
|
|
1343
|
+
return;
|
|
1344
|
+
}
|
|
1345
|
+
// The alternate is parked on the immediate parent whenever some outer scope
|
|
1346
|
+
// declares the name, so a later assignment in that parent (or an enclosing
|
|
1347
|
+
// straight-line block) can still kill it; the parent hands it on when it ends.
|
|
1348
|
+
function carry(name, ids) {
|
|
1349
|
+
if (declared?.has(name))
|
|
1350
|
+
return;
|
|
1351
|
+
if (!scopeStack.some((scope) => scope.has(name)))
|
|
1352
|
+
return;
|
|
1353
|
+
let alts = alternateDefs.get(top);
|
|
1354
|
+
if (!alts) {
|
|
1355
|
+
alts = new Map();
|
|
1356
|
+
alternateDefs.set(top, alts);
|
|
1357
|
+
}
|
|
1358
|
+
const list = alts.get(name) ?? [];
|
|
1359
|
+
for (const id of ids)
|
|
1360
|
+
if (!list.includes(id))
|
|
1361
|
+
list.push(id);
|
|
1362
|
+
alts.set(name, list);
|
|
1363
|
+
}
|
|
1364
|
+
if (!altsOnly)
|
|
1365
|
+
for (const [name, id] of popped)
|
|
1366
|
+
carry(name, [id]);
|
|
1367
|
+
if (poppedAlts)
|
|
1368
|
+
for (const [name, ids] of poppedAlts)
|
|
1369
|
+
carry(name, ids);
|
|
1370
|
+
}
|
|
1371
|
+
/** Alternate reaching defs of `name`: those parked on every scope down to the one that binds it. */
|
|
1372
|
+
function findAlternateDefs(name, scopeStack) {
|
|
1373
|
+
const out = [];
|
|
1374
|
+
for (let i = scopeStack.length - 1; i >= 0; i--) {
|
|
1375
|
+
const alts = alternateDefs.get(scopeStack[i])?.get(name);
|
|
1376
|
+
if (alts)
|
|
1377
|
+
for (const id of alts)
|
|
1378
|
+
if (!out.includes(id))
|
|
1379
|
+
out.push(id);
|
|
1380
|
+
if (scopeStack[i].has(name))
|
|
1381
|
+
break;
|
|
1382
|
+
}
|
|
1383
|
+
return out;
|
|
1384
|
+
}
|
|
1059
1385
|
/**
|
|
1060
1386
|
* Find the reaching definition for a variable.
|
|
1061
1387
|
*/
|