circle-ir 3.8.3 → 3.9.7
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 +82 -5
- package/dist/analysis/config-loader.js +1 -1
- package/dist/analysis/config-loader.js.map +1 -1
- package/dist/analysis/dfg-verifier.d.ts +3 -14
- package/dist/analysis/dfg-verifier.js +43 -74
- package/dist/analysis/dfg-verifier.js.map +1 -1
- package/dist/analysis/interprocedural.d.ts +5 -1
- package/dist/analysis/interprocedural.js +62 -60
- package/dist/analysis/interprocedural.js.map +1 -1
- package/dist/analysis/metrics/index.d.ts +2 -0
- package/dist/analysis/metrics/index.js +2 -0
- package/dist/analysis/metrics/index.js.map +1 -0
- package/dist/analysis/metrics/metric-pass.d.ts +27 -0
- package/dist/analysis/metrics/metric-pass.js +2 -0
- package/dist/analysis/metrics/metric-pass.js.map +1 -0
- package/dist/analysis/metrics/metric-runner.d.ts +21 -0
- package/dist/analysis/metrics/metric-runner.js +47 -0
- package/dist/analysis/metrics/metric-runner.js.map +1 -0
- package/dist/analysis/metrics/passes/cohesion-metrics-pass.d.ts +21 -0
- package/dist/analysis/metrics/passes/cohesion-metrics-pass.js +100 -0
- package/dist/analysis/metrics/passes/cohesion-metrics-pass.js.map +1 -0
- package/dist/analysis/metrics/passes/complexity-metrics-pass.d.ts +15 -0
- package/dist/analysis/metrics/passes/complexity-metrics-pass.js +76 -0
- package/dist/analysis/metrics/passes/complexity-metrics-pass.js.map +1 -0
- package/dist/analysis/metrics/passes/composite-metrics-pass.d.ts +17 -0
- package/dist/analysis/metrics/passes/composite-metrics-pass.js +77 -0
- package/dist/analysis/metrics/passes/composite-metrics-pass.js.map +1 -0
- package/dist/analysis/metrics/passes/coupling-metrics-pass.d.ts +19 -0
- package/dist/analysis/metrics/passes/coupling-metrics-pass.js +94 -0
- package/dist/analysis/metrics/passes/coupling-metrics-pass.js.map +1 -0
- package/dist/analysis/metrics/passes/data-flow-metrics-pass.d.ts +14 -0
- package/dist/analysis/metrics/passes/data-flow-metrics-pass.js +25 -0
- package/dist/analysis/metrics/passes/data-flow-metrics-pass.js.map +1 -0
- package/dist/analysis/metrics/passes/documentation-metrics-pass.d.ts +15 -0
- package/dist/analysis/metrics/passes/documentation-metrics-pass.js +64 -0
- package/dist/analysis/metrics/passes/documentation-metrics-pass.js.map +1 -0
- package/dist/analysis/metrics/passes/halstead-metrics-pass.d.ts +16 -0
- package/dist/analysis/metrics/passes/halstead-metrics-pass.js +95 -0
- package/dist/analysis/metrics/passes/halstead-metrics-pass.js.map +1 -0
- package/dist/analysis/metrics/passes/inheritance-metrics-pass.d.ts +18 -0
- package/dist/analysis/metrics/passes/inheritance-metrics-pass.js +73 -0
- package/dist/analysis/metrics/passes/inheritance-metrics-pass.js.map +1 -0
- package/dist/analysis/metrics/passes/size-metrics-pass.d.ts +11 -0
- package/dist/analysis/metrics/passes/size-metrics-pass.js +64 -0
- package/dist/analysis/metrics/passes/size-metrics-pass.js.map +1 -0
- package/dist/analysis/passes/circular-dependency-pass.d.ts +18 -0
- package/dist/analysis/passes/circular-dependency-pass.js +39 -0
- package/dist/analysis/passes/circular-dependency-pass.js.map +1 -0
- package/dist/analysis/passes/constant-propagation-pass.d.ts +22 -0
- package/dist/analysis/passes/constant-propagation-pass.js +44 -0
- package/dist/analysis/passes/constant-propagation-pass.js.map +1 -0
- package/dist/analysis/passes/cross-file-pass.d.ts +27 -0
- package/dist/analysis/passes/cross-file-pass.js +102 -0
- package/dist/analysis/passes/cross-file-pass.js.map +1 -0
- package/dist/analysis/passes/dead-code-pass.d.ts +25 -0
- package/dist/analysis/passes/dead-code-pass.js +117 -0
- package/dist/analysis/passes/dead-code-pass.js.map +1 -0
- package/dist/analysis/passes/dependency-fan-out-pass.d.ts +19 -0
- package/dist/analysis/passes/dependency-fan-out-pass.js +35 -0
- package/dist/analysis/passes/dependency-fan-out-pass.js.map +1 -0
- package/dist/analysis/passes/interprocedural-pass.d.ts +29 -0
- package/dist/analysis/passes/interprocedural-pass.js +169 -0
- package/dist/analysis/passes/interprocedural-pass.js.map +1 -0
- package/dist/analysis/passes/language-sources-pass.d.ts +76 -0
- package/dist/analysis/passes/language-sources-pass.js +491 -0
- package/dist/analysis/passes/language-sources-pass.js.map +1 -0
- package/dist/analysis/passes/leaked-global-pass.d.ts +34 -0
- package/dist/analysis/passes/leaked-global-pass.js +108 -0
- package/dist/analysis/passes/leaked-global-pass.js.map +1 -0
- package/dist/analysis/passes/missing-await-pass.d.ts +29 -0
- package/dist/analysis/passes/missing-await-pass.js +90 -0
- package/dist/analysis/passes/missing-await-pass.js.map +1 -0
- package/dist/analysis/passes/missing-public-doc-pass.d.ts +35 -0
- package/dist/analysis/passes/missing-public-doc-pass.js +148 -0
- package/dist/analysis/passes/missing-public-doc-pass.js.map +1 -0
- package/dist/analysis/passes/n-plus-one-pass.d.ts +29 -0
- package/dist/analysis/passes/n-plus-one-pass.js +100 -0
- package/dist/analysis/passes/n-plus-one-pass.js.map +1 -0
- package/dist/analysis/passes/null-deref-pass.d.ts +32 -0
- package/dist/analysis/passes/null-deref-pass.js +130 -0
- package/dist/analysis/passes/null-deref-pass.js.map +1 -0
- package/dist/analysis/passes/orphan-module-pass.d.ts +21 -0
- package/dist/analysis/passes/orphan-module-pass.js +38 -0
- package/dist/analysis/passes/orphan-module-pass.js.map +1 -0
- package/dist/analysis/passes/resource-leak-pass.d.ts +43 -0
- package/dist/analysis/passes/resource-leak-pass.js +156 -0
- package/dist/analysis/passes/resource-leak-pass.js.map +1 -0
- package/dist/analysis/passes/sink-filter-pass.d.ts +39 -0
- package/dist/analysis/passes/sink-filter-pass.js +231 -0
- package/dist/analysis/passes/sink-filter-pass.js.map +1 -0
- package/dist/analysis/passes/stale-doc-ref-pass.d.ts +21 -0
- package/dist/analysis/passes/stale-doc-ref-pass.js +96 -0
- package/dist/analysis/passes/stale-doc-ref-pass.js.map +1 -0
- package/dist/analysis/passes/string-concat-loop-pass.d.ts +26 -0
- package/dist/analysis/passes/string-concat-loop-pass.js +87 -0
- package/dist/analysis/passes/string-concat-loop-pass.js.map +1 -0
- package/dist/analysis/passes/sync-io-async-pass.d.ts +28 -0
- package/dist/analysis/passes/sync-io-async-pass.js +80 -0
- package/dist/analysis/passes/sync-io-async-pass.js.map +1 -0
- package/dist/analysis/passes/taint-matcher-pass.d.ts +24 -0
- package/dist/analysis/passes/taint-matcher-pass.js +71 -0
- package/dist/analysis/passes/taint-matcher-pass.js.map +1 -0
- package/dist/analysis/passes/taint-propagation-pass.d.ts +22 -0
- package/dist/analysis/passes/taint-propagation-pass.js +266 -0
- package/dist/analysis/passes/taint-propagation-pass.js.map +1 -0
- package/dist/analysis/passes/todo-in-prod-pass.d.ts +28 -0
- package/dist/analysis/passes/todo-in-prod-pass.js +71 -0
- package/dist/analysis/passes/todo-in-prod-pass.js.map +1 -0
- package/dist/analysis/passes/unchecked-return-pass.d.ts +34 -0
- package/dist/analysis/passes/unchecked-return-pass.js +106 -0
- package/dist/analysis/passes/unchecked-return-pass.js.map +1 -0
- package/dist/analysis/passes/unused-variable-pass.d.ts +36 -0
- package/dist/analysis/passes/unused-variable-pass.js +150 -0
- package/dist/analysis/passes/unused-variable-pass.js.map +1 -0
- package/dist/analysis/passes/variable-shadowing-pass.d.ts +41 -0
- package/dist/analysis/passes/variable-shadowing-pass.js +211 -0
- package/dist/analysis/passes/variable-shadowing-pass.js.map +1 -0
- package/dist/analysis/path-finder.d.ts +3 -13
- package/dist/analysis/path-finder.js +48 -63
- package/dist/analysis/path-finder.js.map +1 -1
- package/dist/analysis/taint-matcher.js +8 -1
- package/dist/analysis/taint-matcher.js.map +1 -1
- package/dist/analysis/taint-propagation.d.ts +5 -1
- package/dist/analysis/taint-propagation.js +44 -41
- package/dist/analysis/taint-propagation.js.map +1 -1
- package/dist/analyzer.d.ts +42 -1
- package/dist/analyzer.js +234 -1476
- package/dist/analyzer.js.map +1 -1
- package/dist/browser/circle-ir.js +3414 -1272
- package/dist/core/circle-ir-core.cjs +361 -107
- package/dist/core/circle-ir-core.js +361 -107
- package/dist/core/extractors/imports.js +18 -0
- package/dist/core/extractors/imports.js.map +1 -1
- package/dist/graph/analysis-pass.d.ts +68 -0
- package/dist/graph/analysis-pass.js +51 -0
- package/dist/graph/analysis-pass.js.map +1 -0
- package/dist/graph/code-graph.d.ts +92 -0
- package/dist/graph/code-graph.js +262 -0
- package/dist/graph/code-graph.js.map +1 -0
- package/dist/graph/import-graph.d.ts +33 -0
- package/dist/graph/import-graph.js +170 -0
- package/dist/graph/import-graph.js.map +1 -0
- package/dist/graph/index.d.ts +4 -0
- package/dist/graph/index.js +5 -0
- package/dist/graph/index.js.map +1 -0
- package/dist/graph/project-graph.d.ts +43 -0
- package/dist/graph/project-graph.js +80 -0
- package/dist/graph/project-graph.js.map +1 -0
- package/dist/graph/scope-graph.d.ts +63 -0
- package/dist/graph/scope-graph.js +89 -0
- package/dist/graph/scope-graph.js.map +1 -0
- package/dist/index.d.ts +2 -2
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/resolution/cross-file.js +52 -19
- package/dist/resolution/cross-file.js.map +1 -1
- package/dist/types/index.d.ts +151 -0
- package/docs/SPEC.md +10 -6
- package/package.json +3 -2
|
@@ -6865,6 +6865,20 @@ function extractJavaScriptImports(tree) {
|
|
|
6865
6865
|
const importInfos = extractJSImportInfo(importStmt);
|
|
6866
6866
|
imports.push(...importInfos);
|
|
6867
6867
|
}
|
|
6868
|
+
const exportStatements = findNodes(tree.rootNode, "export_statement");
|
|
6869
|
+
for (const exportStmt of exportStatements) {
|
|
6870
|
+
const sourceNode = exportStmt.childForFieldName("source");
|
|
6871
|
+
if (!sourceNode) continue;
|
|
6872
|
+
const fromPackage = getNodeText(sourceNode).replace(/['"]/g, "");
|
|
6873
|
+
if (!fromPackage) continue;
|
|
6874
|
+
imports.push({
|
|
6875
|
+
imported_name: "*",
|
|
6876
|
+
from_package: fromPackage,
|
|
6877
|
+
alias: null,
|
|
6878
|
+
is_wildcard: true,
|
|
6879
|
+
line_number: exportStmt.startPosition.row + 1
|
|
6880
|
+
});
|
|
6881
|
+
}
|
|
6868
6882
|
const requireCalls = findRequireCalls(tree);
|
|
6869
6883
|
imports.push(...requireCalls);
|
|
6870
6884
|
return imports;
|
|
@@ -9631,7 +9645,7 @@ var DEFAULT_SINKS = [
|
|
|
9631
9645
|
// Jenkins/CI Pipeline execution
|
|
9632
9646
|
{ method: "executeScript", type: "code_injection", cwe: "CWE-94", severity: "critical", arg_positions: [0] },
|
|
9633
9647
|
{ method: "runScript", type: "code_injection", cwe: "CWE-94", severity: "critical", arg_positions: [0] },
|
|
9634
|
-
{ method: "evaluate", type: "code_injection", cwe: "CWE-94", severity: "critical", arg_positions: [0] },
|
|
9648
|
+
{ method: "evaluate", class: "ScriptEngine", type: "code_injection", cwe: "CWE-94", severity: "critical", arg_positions: [0] },
|
|
9635
9649
|
{ method: "execute", class: "Script", type: "code_injection", cwe: "CWE-94", severity: "critical", arg_positions: [] },
|
|
9636
9650
|
{ method: "run", class: "Script", type: "code_injection", cwe: "CWE-94", severity: "critical", arg_positions: [] },
|
|
9637
9651
|
{ method: "checkout", class: "SCM", type: "code_injection", cwe: "CWE-94", severity: "high", arg_positions: [0] },
|
|
@@ -10591,7 +10605,10 @@ function matchesSourcePattern(call, pattern) {
|
|
|
10591
10605
|
return false;
|
|
10592
10606
|
}
|
|
10593
10607
|
if (pattern.class && pattern.class !== "constructor") {
|
|
10594
|
-
if (
|
|
10608
|
+
if (!call.receiver) {
|
|
10609
|
+
return false;
|
|
10610
|
+
}
|
|
10611
|
+
if (!receiverMightBeClass(call.receiver, pattern.class)) {
|
|
10595
10612
|
return false;
|
|
10596
10613
|
}
|
|
10597
10614
|
}
|
|
@@ -10950,38 +10967,296 @@ function formatSanitizerMethod(call) {
|
|
|
10950
10967
|
return `${call.method_name}()`;
|
|
10951
10968
|
}
|
|
10952
10969
|
|
|
10953
|
-
// src/
|
|
10954
|
-
|
|
10955
|
-
|
|
10956
|
-
|
|
10957
|
-
|
|
10958
|
-
|
|
10959
|
-
|
|
10960
|
-
|
|
10961
|
-
|
|
10962
|
-
|
|
10963
|
-
|
|
10964
|
-
|
|
10965
|
-
|
|
10966
|
-
|
|
10967
|
-
|
|
10970
|
+
// src/graph/code-graph.ts
|
|
10971
|
+
var CodeGraph = class {
|
|
10972
|
+
ir;
|
|
10973
|
+
constructor(ir) {
|
|
10974
|
+
this.ir = ir;
|
|
10975
|
+
}
|
|
10976
|
+
// ---------------------------------------------------------------------------
|
|
10977
|
+
// DFG indexes
|
|
10978
|
+
// ---------------------------------------------------------------------------
|
|
10979
|
+
_defById = null;
|
|
10980
|
+
get defById() {
|
|
10981
|
+
if (!this._defById) {
|
|
10982
|
+
this._defById = /* @__PURE__ */ new Map();
|
|
10983
|
+
for (const def of this.ir.dfg.defs) {
|
|
10984
|
+
this._defById.set(def.id, def);
|
|
10985
|
+
}
|
|
10986
|
+
}
|
|
10987
|
+
return this._defById;
|
|
10988
|
+
}
|
|
10989
|
+
_defsByLine = null;
|
|
10990
|
+
get defsByLine() {
|
|
10991
|
+
if (!this._defsByLine) {
|
|
10992
|
+
this._defsByLine = /* @__PURE__ */ new Map();
|
|
10993
|
+
for (const def of this.ir.dfg.defs) {
|
|
10994
|
+
const arr = this._defsByLine.get(def.line) ?? [];
|
|
10995
|
+
arr.push(def);
|
|
10996
|
+
this._defsByLine.set(def.line, arr);
|
|
10997
|
+
}
|
|
10998
|
+
}
|
|
10999
|
+
return this._defsByLine;
|
|
11000
|
+
}
|
|
11001
|
+
_defsByVar = null;
|
|
11002
|
+
get defsByVar() {
|
|
11003
|
+
if (!this._defsByVar) {
|
|
11004
|
+
this._defsByVar = /* @__PURE__ */ new Map();
|
|
11005
|
+
for (const def of this.ir.dfg.defs) {
|
|
11006
|
+
const arr = this._defsByVar.get(def.variable) ?? [];
|
|
11007
|
+
arr.push(def);
|
|
11008
|
+
this._defsByVar.set(def.variable, arr);
|
|
11009
|
+
}
|
|
11010
|
+
}
|
|
11011
|
+
return this._defsByVar;
|
|
11012
|
+
}
|
|
11013
|
+
_usesByLine = null;
|
|
11014
|
+
get usesByLine() {
|
|
11015
|
+
if (!this._usesByLine) {
|
|
11016
|
+
this._usesByLine = /* @__PURE__ */ new Map();
|
|
11017
|
+
for (const use of this.ir.dfg.uses) {
|
|
11018
|
+
const arr = this._usesByLine.get(use.line) ?? [];
|
|
11019
|
+
arr.push(use);
|
|
11020
|
+
this._usesByLine.set(use.line, arr);
|
|
11021
|
+
}
|
|
11022
|
+
}
|
|
11023
|
+
return this._usesByLine;
|
|
11024
|
+
}
|
|
11025
|
+
_usesByDefId = null;
|
|
11026
|
+
get usesByDefId() {
|
|
11027
|
+
if (!this._usesByDefId) {
|
|
11028
|
+
this._usesByDefId = /* @__PURE__ */ new Map();
|
|
11029
|
+
for (const use of this.ir.dfg.uses) {
|
|
11030
|
+
if (use.def_id !== null) {
|
|
11031
|
+
const arr = this._usesByDefId.get(use.def_id) ?? [];
|
|
11032
|
+
arr.push(use);
|
|
11033
|
+
this._usesByDefId.set(use.def_id, arr);
|
|
11034
|
+
}
|
|
11035
|
+
}
|
|
11036
|
+
}
|
|
11037
|
+
return this._usesByDefId;
|
|
11038
|
+
}
|
|
11039
|
+
_chainsByFromDef = null;
|
|
11040
|
+
get chainsByFromDef() {
|
|
11041
|
+
if (!this._chainsByFromDef) {
|
|
11042
|
+
this._chainsByFromDef = /* @__PURE__ */ new Map();
|
|
11043
|
+
for (const chain of this.ir.dfg.chains ?? []) {
|
|
11044
|
+
const arr = this._chainsByFromDef.get(chain.from_def) ?? [];
|
|
11045
|
+
arr.push(chain);
|
|
11046
|
+
this._chainsByFromDef.set(chain.from_def, arr);
|
|
11047
|
+
}
|
|
11048
|
+
}
|
|
11049
|
+
return this._chainsByFromDef;
|
|
11050
|
+
}
|
|
11051
|
+
// ---------------------------------------------------------------------------
|
|
11052
|
+
// Call indexes
|
|
11053
|
+
// ---------------------------------------------------------------------------
|
|
11054
|
+
_callsByLine = null;
|
|
11055
|
+
get callsByLine() {
|
|
11056
|
+
if (!this._callsByLine) {
|
|
11057
|
+
this._callsByLine = /* @__PURE__ */ new Map();
|
|
11058
|
+
for (const call of this.ir.calls) {
|
|
11059
|
+
const arr = this._callsByLine.get(call.location.line) ?? [];
|
|
11060
|
+
arr.push(call);
|
|
11061
|
+
this._callsByLine.set(call.location.line, arr);
|
|
11062
|
+
}
|
|
11063
|
+
}
|
|
11064
|
+
return this._callsByLine;
|
|
11065
|
+
}
|
|
11066
|
+
_callsByMethod = null;
|
|
11067
|
+
get callsByMethod() {
|
|
11068
|
+
if (!this._callsByMethod) {
|
|
11069
|
+
this._callsByMethod = /* @__PURE__ */ new Map();
|
|
11070
|
+
for (const call of this.ir.calls) {
|
|
11071
|
+
const arr = this._callsByMethod.get(call.method_name) ?? [];
|
|
11072
|
+
arr.push(call);
|
|
11073
|
+
this._callsByMethod.set(call.method_name, arr);
|
|
11074
|
+
}
|
|
11075
|
+
}
|
|
11076
|
+
return this._callsByMethod;
|
|
10968
11077
|
}
|
|
10969
|
-
|
|
10970
|
-
|
|
10971
|
-
|
|
10972
|
-
|
|
11078
|
+
// ---------------------------------------------------------------------------
|
|
11079
|
+
// Type / method indexes
|
|
11080
|
+
// ---------------------------------------------------------------------------
|
|
11081
|
+
_methodsByName = null;
|
|
11082
|
+
get methodsByName() {
|
|
11083
|
+
if (!this._methodsByName) {
|
|
11084
|
+
this._methodsByName = /* @__PURE__ */ new Map();
|
|
11085
|
+
for (const type of this.ir.types) {
|
|
11086
|
+
for (const method of type.methods) {
|
|
11087
|
+
const arr = this._methodsByName.get(method.name) ?? [];
|
|
11088
|
+
arr.push({ type, method });
|
|
11089
|
+
this._methodsByName.set(method.name, arr);
|
|
11090
|
+
}
|
|
11091
|
+
}
|
|
11092
|
+
}
|
|
11093
|
+
return this._methodsByName;
|
|
10973
11094
|
}
|
|
10974
|
-
|
|
10975
|
-
|
|
10976
|
-
|
|
10977
|
-
|
|
11095
|
+
/**
|
|
11096
|
+
* Returns the TypeInfo + MethodInfo whose line range contains `line`, or null.
|
|
11097
|
+
* Used by passes that need the enclosing method context for a given line.
|
|
11098
|
+
*/
|
|
11099
|
+
methodAtLine(line) {
|
|
11100
|
+
for (const type of this.ir.types) {
|
|
11101
|
+
for (const method of type.methods) {
|
|
11102
|
+
if (line >= method.start_line && line <= method.end_line) {
|
|
11103
|
+
return { type, method };
|
|
11104
|
+
}
|
|
11105
|
+
}
|
|
11106
|
+
}
|
|
11107
|
+
return null;
|
|
11108
|
+
}
|
|
11109
|
+
// ---------------------------------------------------------------------------
|
|
11110
|
+
// Taint indexes
|
|
11111
|
+
// ---------------------------------------------------------------------------
|
|
11112
|
+
_sanitizersByLine = null;
|
|
11113
|
+
get sanitizersByLine() {
|
|
11114
|
+
if (!this._sanitizersByLine) {
|
|
11115
|
+
this._sanitizersByLine = /* @__PURE__ */ new Map();
|
|
11116
|
+
for (const san of this.ir.taint.sanitizers ?? []) {
|
|
11117
|
+
const arr = this._sanitizersByLine.get(san.line) ?? [];
|
|
11118
|
+
arr.push(san);
|
|
11119
|
+
this._sanitizersByLine.set(san.line, arr);
|
|
11120
|
+
}
|
|
11121
|
+
}
|
|
11122
|
+
return this._sanitizersByLine;
|
|
11123
|
+
}
|
|
11124
|
+
// ---------------------------------------------------------------------------
|
|
11125
|
+
// Query primitives
|
|
11126
|
+
// ---------------------------------------------------------------------------
|
|
11127
|
+
/** All DFGDefs at a given line. Returns [] if none. */
|
|
11128
|
+
defsAtLine(line) {
|
|
11129
|
+
return this.defsByLine.get(line) ?? [];
|
|
11130
|
+
}
|
|
11131
|
+
/** All DFGUses at a given line. Returns [] if none. */
|
|
11132
|
+
usesAtLine(line) {
|
|
11133
|
+
return this.usesByLine.get(line) ?? [];
|
|
11134
|
+
}
|
|
11135
|
+
/** All DFGUses that reach a specific definition ID. Returns [] if none. */
|
|
11136
|
+
usesOfDef(defId) {
|
|
11137
|
+
return this.usesByDefId.get(defId) ?? [];
|
|
10978
11138
|
}
|
|
10979
|
-
|
|
10980
|
-
|
|
10981
|
-
|
|
10982
|
-
|
|
11139
|
+
/** All CallInfos at a given line. Returns [] if none. */
|
|
11140
|
+
callsAtLine(line) {
|
|
11141
|
+
return this.callsByLine.get(line) ?? [];
|
|
11142
|
+
}
|
|
11143
|
+
/** DFGChains outgoing from a definition ID. Returns [] if none. */
|
|
11144
|
+
chainsFrom(defId) {
|
|
11145
|
+
return this.chainsByFromDef.get(defId) ?? [];
|
|
11146
|
+
}
|
|
11147
|
+
/**
|
|
11148
|
+
* All definitions of `variable` that appear strictly after `afterLine`
|
|
11149
|
+
* and at or before `upToLine`. Used to detect whether a variable is
|
|
11150
|
+
* redefined between a taint source and a sink.
|
|
11151
|
+
*/
|
|
11152
|
+
laterDefsOfVar(variable, afterLine, upToLine) {
|
|
11153
|
+
return (this.defsByVar.get(variable) ?? []).filter(
|
|
11154
|
+
(d) => d.line > afterLine && d.line <= upToLine
|
|
11155
|
+
);
|
|
10983
11156
|
}
|
|
10984
|
-
|
|
11157
|
+
// ---------------------------------------------------------------------------
|
|
11158
|
+
// CFG indexes
|
|
11159
|
+
// ---------------------------------------------------------------------------
|
|
11160
|
+
_blockById = null;
|
|
11161
|
+
get blockById() {
|
|
11162
|
+
if (!this._blockById) {
|
|
11163
|
+
this._blockById = /* @__PURE__ */ new Map();
|
|
11164
|
+
for (const block of this.ir.cfg.blocks) {
|
|
11165
|
+
this._blockById.set(block.id, block);
|
|
11166
|
+
}
|
|
11167
|
+
}
|
|
11168
|
+
return this._blockById;
|
|
11169
|
+
}
|
|
11170
|
+
/**
|
|
11171
|
+
* Returns the line range of each detected loop body in the file.
|
|
11172
|
+
*
|
|
11173
|
+
* A loop is identified by CFG back-edges (type = "back"). For each back edge
|
|
11174
|
+
* `A → B`, B is the loop header and A is the last block before the back-jump.
|
|
11175
|
+
* The loop body spans from `header.start_line` to `A.end_line` (inclusive).
|
|
11176
|
+
*
|
|
11177
|
+
* Returns `{ start_line, end_line }` — one entry per back-edge.
|
|
11178
|
+
* Overlapping ranges are returned separately; callers can merge as needed.
|
|
11179
|
+
*
|
|
11180
|
+
* Usage: check whether line L is inside any loop with
|
|
11181
|
+
* `graph.loopBodies().some(r => L >= r.start_line && L <= r.end_line)`
|
|
11182
|
+
*/
|
|
11183
|
+
loopBodies() {
|
|
11184
|
+
const loops = [];
|
|
11185
|
+
for (const edge of this.ir.cfg.edges) {
|
|
11186
|
+
if (edge.type !== "back") continue;
|
|
11187
|
+
const header = this.blockById.get(edge.to);
|
|
11188
|
+
const tail = this.blockById.get(edge.from);
|
|
11189
|
+
if (header && tail) {
|
|
11190
|
+
loops.push({ start_line: header.start_line, end_line: tail.end_line });
|
|
11191
|
+
}
|
|
11192
|
+
}
|
|
11193
|
+
return loops;
|
|
11194
|
+
}
|
|
11195
|
+
/**
|
|
11196
|
+
* Propagate a set of tainted definition IDs through DFGChains to a fixpoint.
|
|
11197
|
+
*
|
|
11198
|
+
* Returns a new Set (does not mutate the input). Each chain edge
|
|
11199
|
+
* `from_def → to_def` spreads taint: if `from_def` is tainted, `to_def`
|
|
11200
|
+
* becomes tainted. Iterates until no new IDs are added.
|
|
11201
|
+
*/
|
|
11202
|
+
propagateTaintedDefIds(seed) {
|
|
11203
|
+
const result = new Set(seed);
|
|
11204
|
+
let changed = true;
|
|
11205
|
+
while (changed) {
|
|
11206
|
+
changed = false;
|
|
11207
|
+
for (const [fromDef, chains] of this.chainsByFromDef) {
|
|
11208
|
+
if (!result.has(fromDef)) continue;
|
|
11209
|
+
for (const chain of chains) {
|
|
11210
|
+
if (!result.has(chain.to_def)) {
|
|
11211
|
+
result.add(chain.to_def);
|
|
11212
|
+
changed = true;
|
|
11213
|
+
}
|
|
11214
|
+
}
|
|
11215
|
+
}
|
|
11216
|
+
}
|
|
11217
|
+
return result;
|
|
11218
|
+
}
|
|
11219
|
+
};
|
|
11220
|
+
|
|
11221
|
+
// src/analysis/taint-propagation.ts
|
|
11222
|
+
function propagateTaint(graphOrDfg, callsOrSources, sourcesOrSinks, sinksOrSanitizers, sanitizersArg) {
|
|
11223
|
+
let graph;
|
|
11224
|
+
let sources;
|
|
11225
|
+
let sinks;
|
|
11226
|
+
let sanitizers;
|
|
11227
|
+
if (graphOrDfg instanceof CodeGraph) {
|
|
11228
|
+
graph = graphOrDfg;
|
|
11229
|
+
sources = callsOrSources;
|
|
11230
|
+
sinks = sourcesOrSinks;
|
|
11231
|
+
sanitizers = sinksOrSanitizers;
|
|
11232
|
+
} else {
|
|
11233
|
+
const dfg = graphOrDfg;
|
|
11234
|
+
const calls = callsOrSources;
|
|
11235
|
+
sources = sourcesOrSinks;
|
|
11236
|
+
sinks = sinksOrSanitizers;
|
|
11237
|
+
sanitizers = sanitizersArg ?? [];
|
|
11238
|
+
graph = new CodeGraph({
|
|
11239
|
+
meta: { circle_ir: "3.0", file: "", language: "java", loc: 0, hash: "" },
|
|
11240
|
+
types: [],
|
|
11241
|
+
calls,
|
|
11242
|
+
cfg: { blocks: [], edges: [] },
|
|
11243
|
+
dfg,
|
|
11244
|
+
taint: { sources: [], sinks: [], sanitizers },
|
|
11245
|
+
imports: [],
|
|
11246
|
+
exports: [],
|
|
11247
|
+
unresolved: [],
|
|
11248
|
+
enriched: {}
|
|
11249
|
+
});
|
|
11250
|
+
}
|
|
11251
|
+
const taintedVars = [];
|
|
11252
|
+
const flows = [];
|
|
11253
|
+
const reachableSinks = /* @__PURE__ */ new Map();
|
|
11254
|
+
const defsByLine = graph.defsByLine;
|
|
11255
|
+
const usesByLine = graph.usesByLine;
|
|
11256
|
+
const callsByLine = graph.callsByLine;
|
|
11257
|
+
const sanitizersByLine = graph.sanitizersByLine;
|
|
11258
|
+
const defById = graph.defById;
|
|
11259
|
+
const rawInitialTaint = findInitialTaint(sources, callsByLine, defsByLine);
|
|
10985
11260
|
const initialTaint = rawInitialTaint.filter((tv) => {
|
|
10986
11261
|
if (tv.line === tv.sourceLine) return true;
|
|
10987
11262
|
const sanCheck = checkSanitized(tv.sourceLine, tv.line, tv.sourceType, sanitizersByLine);
|
|
@@ -10990,7 +11265,7 @@ function propagateTaint(dfg, calls, sources, sinks, sanitizers) {
|
|
|
10990
11265
|
taintedVars.push(...initialTaint);
|
|
10991
11266
|
const propagatedTaint = propagateThroughChains(
|
|
10992
11267
|
initialTaint,
|
|
10993
|
-
|
|
11268
|
+
graph.chainsByFromDef,
|
|
10994
11269
|
defById,
|
|
10995
11270
|
sanitizersByLine
|
|
10996
11271
|
);
|
|
@@ -11024,9 +11299,7 @@ function propagateTaint(dfg, calls, sources, sinks, sanitizers) {
|
|
|
11024
11299
|
const flow = buildTaintFlow(
|
|
11025
11300
|
source,
|
|
11026
11301
|
sink,
|
|
11027
|
-
taintInfo
|
|
11028
|
-
dfg,
|
|
11029
|
-
defById
|
|
11302
|
+
taintInfo
|
|
11030
11303
|
);
|
|
11031
11304
|
flows.push(flow);
|
|
11032
11305
|
const existingSources = reachableSinks.get(sink) ?? [];
|
|
@@ -11046,7 +11319,7 @@ function propagateTaint(dfg, calls, sources, sinks, sanitizers) {
|
|
|
11046
11319
|
}
|
|
11047
11320
|
return { taintedVars, flows, reachableSinks };
|
|
11048
11321
|
}
|
|
11049
|
-
function findInitialTaint(sources,
|
|
11322
|
+
function findInitialTaint(sources, callsByLine, defsByLine) {
|
|
11050
11323
|
const tainted = [];
|
|
11051
11324
|
for (const source of sources) {
|
|
11052
11325
|
const defsOnLine = defsByLine.get(source.line) ?? [];
|
|
@@ -11078,19 +11351,13 @@ function findInitialTaint(sources, dfg, callsByLine, defsByLine) {
|
|
|
11078
11351
|
}
|
|
11079
11352
|
return tainted;
|
|
11080
11353
|
}
|
|
11081
|
-
function propagateThroughChains(initialTaint,
|
|
11354
|
+
function propagateThroughChains(initialTaint, chainsByFromDef, defById, sanitizersByLine) {
|
|
11082
11355
|
const propagated = [];
|
|
11083
11356
|
const taintedDefIds = new Set(initialTaint.map((t) => t.defId));
|
|
11084
11357
|
const taintInfoByDefId = /* @__PURE__ */ new Map();
|
|
11085
11358
|
for (const t of initialTaint) {
|
|
11086
11359
|
taintInfoByDefId.set(t.defId, t);
|
|
11087
11360
|
}
|
|
11088
|
-
const chainsByFromDef = /* @__PURE__ */ new Map();
|
|
11089
|
-
for (const chain of chains) {
|
|
11090
|
-
const existing = chainsByFromDef.get(chain.from_def) ?? [];
|
|
11091
|
-
existing.push(chain);
|
|
11092
|
-
chainsByFromDef.set(chain.from_def, existing);
|
|
11093
|
-
}
|
|
11094
11361
|
const queue = [...initialTaint.map((t) => t.defId)];
|
|
11095
11362
|
const visited = new Set(queue);
|
|
11096
11363
|
while (queue.length > 0) {
|
|
@@ -11160,7 +11427,7 @@ function checkSanitized(_fromLine, toLine, sinkType, sanitizersByLine) {
|
|
|
11160
11427
|
}
|
|
11161
11428
|
return { sanitized: false };
|
|
11162
11429
|
}
|
|
11163
|
-
function buildTaintFlow(source, sink, taintInfo
|
|
11430
|
+
function buildTaintFlow(source, sink, taintInfo) {
|
|
11164
11431
|
const path = [];
|
|
11165
11432
|
path.push({
|
|
11166
11433
|
variable: taintInfo.variable,
|
|
@@ -13537,65 +13804,54 @@ function normalizeCondition(cond) {
|
|
|
13537
13804
|
|
|
13538
13805
|
// src/analysis/path-finder.ts
|
|
13539
13806
|
var PathFinder = class {
|
|
13540
|
-
|
|
13541
|
-
calls;
|
|
13807
|
+
graph;
|
|
13542
13808
|
sources;
|
|
13543
13809
|
sinks;
|
|
13544
13810
|
sanitizers;
|
|
13545
13811
|
config;
|
|
13546
|
-
|
|
13547
|
-
|
|
13548
|
-
|
|
13549
|
-
|
|
13550
|
-
|
|
13551
|
-
|
|
13552
|
-
|
|
13553
|
-
|
|
13554
|
-
|
|
13555
|
-
|
|
13556
|
-
|
|
13557
|
-
|
|
13558
|
-
|
|
13559
|
-
|
|
13560
|
-
|
|
13561
|
-
|
|
13562
|
-
|
|
13563
|
-
|
|
13564
|
-
|
|
13565
|
-
|
|
13566
|
-
|
|
13567
|
-
|
|
13568
|
-
|
|
13569
|
-
|
|
13570
|
-
|
|
13571
|
-
|
|
13572
|
-
|
|
13573
|
-
|
|
13574
|
-
|
|
13575
|
-
|
|
13576
|
-
|
|
13577
|
-
|
|
13578
|
-
|
|
13579
|
-
this.
|
|
13580
|
-
|
|
13581
|
-
|
|
13582
|
-
|
|
13583
|
-
|
|
13584
|
-
|
|
13585
|
-
|
|
13586
|
-
|
|
13587
|
-
byDefId.push(use);
|
|
13588
|
-
this.usesByDefId.set(use.def_id, byDefId);
|
|
13589
|
-
}
|
|
13590
|
-
}
|
|
13591
|
-
for (const call of this.calls) {
|
|
13592
|
-
const byLine = this.callsByLine.get(call.location.line) ?? [];
|
|
13593
|
-
byLine.push(call);
|
|
13594
|
-
this.callsByLine.set(call.location.line, byLine);
|
|
13595
|
-
}
|
|
13596
|
-
for (const sanitizer of this.sanitizers) {
|
|
13597
|
-
this.sanitizerLines.add(sanitizer.line);
|
|
13812
|
+
sanitizerLines;
|
|
13813
|
+
constructor(graphOrDfg, callsOrSources, sourcesOrSinks, sinksOrSanitizers, sanitizersOrConfig, config = {}) {
|
|
13814
|
+
if (graphOrDfg instanceof CodeGraph) {
|
|
13815
|
+
this.graph = graphOrDfg;
|
|
13816
|
+
this.sources = callsOrSources;
|
|
13817
|
+
this.sinks = sourcesOrSinks;
|
|
13818
|
+
this.sanitizers = sinksOrSanitizers;
|
|
13819
|
+
const cfg = sanitizersOrConfig;
|
|
13820
|
+
this.config = {
|
|
13821
|
+
maxPathLength: cfg?.maxPathLength ?? 50,
|
|
13822
|
+
maxPathsPerSink: cfg?.maxPathsPerSink ?? 10,
|
|
13823
|
+
includeCode: cfg?.includeCode ?? false,
|
|
13824
|
+
sourceLines: cfg?.sourceLines ?? []
|
|
13825
|
+
};
|
|
13826
|
+
} else {
|
|
13827
|
+
const dfg = graphOrDfg;
|
|
13828
|
+
const calls = callsOrSources;
|
|
13829
|
+
const sources = sourcesOrSinks;
|
|
13830
|
+
const sinks = sinksOrSanitizers;
|
|
13831
|
+
const sanitizers = sanitizersOrConfig ?? [];
|
|
13832
|
+
this.graph = new CodeGraph({
|
|
13833
|
+
meta: { circle_ir: "3.0", file: "", language: "java", loc: 0, hash: "" },
|
|
13834
|
+
types: [],
|
|
13835
|
+
calls,
|
|
13836
|
+
cfg: { blocks: [], edges: [] },
|
|
13837
|
+
dfg,
|
|
13838
|
+
taint: { sources: [], sinks: [], sanitizers },
|
|
13839
|
+
imports: [],
|
|
13840
|
+
exports: [],
|
|
13841
|
+
unresolved: [],
|
|
13842
|
+
enriched: {}
|
|
13843
|
+
});
|
|
13844
|
+
this.sources = sources;
|
|
13845
|
+
this.sinks = sinks;
|
|
13846
|
+
this.sanitizers = sanitizers;
|
|
13847
|
+
this.config = {
|
|
13848
|
+
maxPathLength: config.maxPathLength ?? 50,
|
|
13849
|
+
maxPathsPerSink: config.maxPathsPerSink ?? 10,
|
|
13850
|
+
includeCode: config.includeCode ?? false,
|
|
13851
|
+
sourceLines: config.sourceLines ?? []
|
|
13852
|
+
};
|
|
13598
13853
|
}
|
|
13854
|
+
this.sanitizerLines = new Set(this.sanitizers.map((s) => s.line));
|
|
13599
13855
|
}
|
|
13600
13856
|
/**
|
|
13601
13857
|
* Find all taint paths from sources to sinks
|
|
@@ -13604,7 +13860,7 @@ var PathFinder = class {
|
|
|
13604
13860
|
const paths = [];
|
|
13605
13861
|
let pathId = 1;
|
|
13606
13862
|
for (const source of this.sources) {
|
|
13607
|
-
const sourceDefs = this.
|
|
13863
|
+
const sourceDefs = this.graph.defsAtLine(source.line);
|
|
13608
13864
|
for (const sourceDef of sourceDefs) {
|
|
13609
13865
|
const pathsFromSource = this.findPathsFromSource(source, sourceDef, pathId);
|
|
13610
13866
|
paths.push(...pathsFromSource);
|
|
@@ -13660,7 +13916,7 @@ var PathFinder = class {
|
|
|
13660
13916
|
description: `Flows into ${sink.type} sink`,
|
|
13661
13917
|
code: this.getCodeAtLine(sink.line)
|
|
13662
13918
|
};
|
|
13663
|
-
const call = this.
|
|
13919
|
+
const call = this.graph.callsAtLine(sink.line)[0];
|
|
13664
13920
|
paths.push({
|
|
13665
13921
|
id: `path-${startPathId + paths.length}`,
|
|
13666
13922
|
source: {
|
|
@@ -13684,7 +13940,7 @@ var PathFinder = class {
|
|
|
13684
13940
|
pathsPerSink.set(sink.line, sinkCount + 1);
|
|
13685
13941
|
}
|
|
13686
13942
|
}
|
|
13687
|
-
const uses = this.
|
|
13943
|
+
const uses = this.graph.usesOfDef(state.currentDef.id);
|
|
13688
13944
|
for (const use of uses) {
|
|
13689
13945
|
let sanitizer = state.sanitizer;
|
|
13690
13946
|
if (this.sanitizerLines.has(use.line) && !sanitizer) {
|
|
@@ -13693,7 +13949,7 @@ var PathFinder = class {
|
|
|
13693
13949
|
sanitizer = { line: san.line, method: san.method };
|
|
13694
13950
|
}
|
|
13695
13951
|
}
|
|
13696
|
-
const nextDefs = this.
|
|
13952
|
+
const nextDefs = this.graph.defsAtLine(use.line);
|
|
13697
13953
|
for (const nextDef of nextDefs) {
|
|
13698
13954
|
if (state.visited.has(nextDef.id)) continue;
|
|
13699
13955
|
const hop = this.createHop(state.currentDef, nextDef, use);
|
|
@@ -13706,7 +13962,7 @@ var PathFinder = class {
|
|
|
13706
13962
|
sanitizer
|
|
13707
13963
|
});
|
|
13708
13964
|
}
|
|
13709
|
-
const laterDefs = (this.defsByVar.get(use.variable) ?? []).filter((d) => d.line > use.line && !state.visited.has(d.id));
|
|
13965
|
+
const laterDefs = (this.graph.defsByVar.get(use.variable) ?? []).filter((d) => d.line > use.line && !state.visited.has(d.id));
|
|
13710
13966
|
for (const laterDef of laterDefs.slice(0, 3)) {
|
|
13711
13967
|
const hop = {
|
|
13712
13968
|
line: laterDef.line,
|
|
@@ -13732,14 +13988,12 @@ var PathFinder = class {
|
|
|
13732
13988
|
* Check if a definition reaches a sink
|
|
13733
13989
|
*/
|
|
13734
13990
|
reachesSink(def, sink) {
|
|
13735
|
-
const
|
|
13736
|
-
for (const use of uses) {
|
|
13991
|
+
for (const use of this.graph.usesAtLine(sink.line)) {
|
|
13737
13992
|
if (use.variable === def.variable || use.def_id === def.id) {
|
|
13738
13993
|
return true;
|
|
13739
13994
|
}
|
|
13740
13995
|
}
|
|
13741
|
-
const
|
|
13742
|
-
for (const call of calls) {
|
|
13996
|
+
for (const call of this.graph.callsAtLine(sink.line)) {
|
|
13743
13997
|
for (const arg of call.arguments) {
|
|
13744
13998
|
if (arg.variable === def.variable) {
|
|
13745
13999
|
return true;
|
|
@@ -13752,7 +14006,7 @@ var PathFinder = class {
|
|
|
13752
14006
|
* Create a hop description between two definitions
|
|
13753
14007
|
*/
|
|
13754
14008
|
createHop(fromDef, toDef, use) {
|
|
13755
|
-
const call = this.
|
|
14009
|
+
const call = this.graph.callsAtLine(toDef.line)[0];
|
|
13756
14010
|
let operation = "assign";
|
|
13757
14011
|
let description = `Assigned to ${toDef.variable}`;
|
|
13758
14012
|
if (call) {
|