circle-ir 3.8.4 → 3.9.8
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/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/deep-inheritance-pass.d.ts +30 -0
- package/dist/analysis/passes/deep-inheritance-pass.js +82 -0
- package/dist/analysis/passes/deep-inheritance-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/infinite-loop-pass.d.ts +31 -0
- package/dist/analysis/passes/infinite-loop-pass.js +126 -0
- package/dist/analysis/passes/infinite-loop-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/react-inline-jsx-pass.d.ts +36 -0
- package/dist/analysis/passes/react-inline-jsx-pass.js +140 -0
- package/dist/analysis/passes/react-inline-jsx-pass.js.map +1 -0
- package/dist/analysis/passes/redundant-loop-pass.d.ts +30 -0
- package/dist/analysis/passes/redundant-loop-pass.js +146 -0
- package/dist/analysis/passes/redundant-loop-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/serial-await-pass.d.ts +36 -0
- package/dist/analysis/passes/serial-await-pass.js +132 -0
- package/dist/analysis/passes/serial-await-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/unbounded-collection-pass.d.ts +32 -0
- package/dist/analysis/passes/unbounded-collection-pass.js +128 -0
- package/dist/analysis/passes/unbounded-collection-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 +48 -1
- package/dist/analyzer.js +252 -1476
- package/dist/analyzer.js.map +1 -1
- package/dist/browser/circle-ir.js +3952 -1270
- package/dist/core/circle-ir-core.cjs +360 -106
- package/dist/core/circle-ir-core.js +360 -106
- 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/dominator-graph.d.ts +53 -0
- package/dist/graph/dominator-graph.js +256 -0
- package/dist/graph/dominator-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 +5 -0
- package/dist/graph/index.js +6 -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 +3 -2
- package/dist/index.js +3 -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 +1 -1
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pass #21: resource-leak (CWE-772, category: reliability)
|
|
3
|
+
*
|
|
4
|
+
* Detects I/O resources (streams, connections, sockets) that are opened but
|
|
5
|
+
* not closed on all exit paths. Unclosed resources exhaust file descriptors
|
|
6
|
+
* or connection pools and cause subtle failures under load.
|
|
7
|
+
*
|
|
8
|
+
* Detection strategy:
|
|
9
|
+
* 1. Find resource-opening calls: known constructors (FileInputStream, etc.)
|
|
10
|
+
* or factory methods (open, createReadStream, etc.).
|
|
11
|
+
* 2. Get the variable bound to the resource from DFG defs at the open line.
|
|
12
|
+
* 3. Within the enclosing method, look for a close()/dispose() call whose
|
|
13
|
+
* receiver matches the resource variable.
|
|
14
|
+
* 4a. No close call found → definite leak (high, error).
|
|
15
|
+
* 4b. Close found but no `finally` keyword in the method after the open
|
|
16
|
+
* → potential leak (medium, warning): an exception skips the close.
|
|
17
|
+
*
|
|
18
|
+
* Note: Java try-with-resources generates no explicit close() in the source;
|
|
19
|
+
* the pass treats the absence of both an explicit close AND a finally as a
|
|
20
|
+
* definite leak.
|
|
21
|
+
*/
|
|
22
|
+
/** Constructors that produce closeable resources. */
|
|
23
|
+
const RESOURCE_CTORS = new Set([
|
|
24
|
+
// Java IO
|
|
25
|
+
'FileInputStream', 'FileOutputStream', 'FileReader', 'FileWriter',
|
|
26
|
+
'BufferedReader', 'BufferedWriter', 'PrintWriter', 'InputStreamReader',
|
|
27
|
+
'OutputStreamWriter', 'RandomAccessFile', 'DataInputStream', 'DataOutputStream',
|
|
28
|
+
'ObjectInputStream', 'ObjectOutputStream', 'ZipInputStream', 'ZipOutputStream',
|
|
29
|
+
'JarInputStream', 'JarOutputStream', 'GZIPInputStream', 'GZIPOutputStream',
|
|
30
|
+
// Java NIO
|
|
31
|
+
'FileChannel',
|
|
32
|
+
// Java Net
|
|
33
|
+
'Socket', 'ServerSocket', 'DatagramSocket',
|
|
34
|
+
]);
|
|
35
|
+
/** Factory / open methods that return closeable resources. */
|
|
36
|
+
const RESOURCE_FACTORY_METHODS = new Set([
|
|
37
|
+
// Java NIO/IO
|
|
38
|
+
'openConnection', 'openStream', 'newInputStream', 'newOutputStream',
|
|
39
|
+
'newBufferedReader', 'newBufferedWriter', 'newByteChannel',
|
|
40
|
+
// Python built-in
|
|
41
|
+
'open',
|
|
42
|
+
// Node.js streams
|
|
43
|
+
'createReadStream', 'createWriteStream', 'createConnection',
|
|
44
|
+
]);
|
|
45
|
+
/** Methods that properly release a resource. */
|
|
46
|
+
const CLOSE_METHODS = new Set([
|
|
47
|
+
'close', 'dispose', 'shutdown', 'disconnect', 'release', 'destroy', 'free',
|
|
48
|
+
'shutdownNow', 'terminate',
|
|
49
|
+
]);
|
|
50
|
+
export class ResourceLeakPass {
|
|
51
|
+
name = 'resource-leak';
|
|
52
|
+
category = 'reliability';
|
|
53
|
+
run(ctx) {
|
|
54
|
+
const { graph, code } = ctx;
|
|
55
|
+
const file = graph.ir.meta.file;
|
|
56
|
+
const codeLines = code.split('\n');
|
|
57
|
+
const leaks = [];
|
|
58
|
+
for (const call of graph.ir.calls) {
|
|
59
|
+
const name = call.method_name;
|
|
60
|
+
const isConstructor = call.is_constructor === true && RESOURCE_CTORS.has(name);
|
|
61
|
+
const isFactory = !call.is_constructor && RESOURCE_FACTORY_METHODS.has(name);
|
|
62
|
+
if (!isConstructor && !isFactory)
|
|
63
|
+
continue;
|
|
64
|
+
const openLine = call.location.line;
|
|
65
|
+
// Resource must be captured in a variable to be trackable
|
|
66
|
+
const defs = graph.defsAtLine(openLine);
|
|
67
|
+
if (defs.length === 0)
|
|
68
|
+
continue;
|
|
69
|
+
const resourceVar = defs[0].variable;
|
|
70
|
+
// Limit search to the enclosing method
|
|
71
|
+
const methodInfo = graph.methodAtLine(openLine);
|
|
72
|
+
if (!methodInfo)
|
|
73
|
+
continue;
|
|
74
|
+
const methodEnd = methodInfo.method.end_line;
|
|
75
|
+
// Look for a close() call on this resource within the method
|
|
76
|
+
const closeCall = graph.ir.calls.find(c => CLOSE_METHODS.has(c.method_name) &&
|
|
77
|
+
c.receiver === resourceVar &&
|
|
78
|
+
c.location.line > openLine &&
|
|
79
|
+
c.location.line <= methodEnd);
|
|
80
|
+
const snippet = (codeLines[openLine - 1] ?? '').trim();
|
|
81
|
+
if (!closeCall) {
|
|
82
|
+
// Also accept try-with-resources or with-statement as implicit close
|
|
83
|
+
if (this.hasTryWithResources(codeLines, openLine, methodEnd))
|
|
84
|
+
continue;
|
|
85
|
+
// Definite leak: resource is never explicitly released
|
|
86
|
+
leaks.push({ line: openLine, resource: name, variable: resourceVar, kind: 'definite' });
|
|
87
|
+
ctx.addFinding({
|
|
88
|
+
id: `resource-leak-${file}-${openLine}`,
|
|
89
|
+
pass: this.name,
|
|
90
|
+
category: this.category,
|
|
91
|
+
rule_id: this.name,
|
|
92
|
+
cwe: 'CWE-772',
|
|
93
|
+
severity: 'high',
|
|
94
|
+
level: 'error',
|
|
95
|
+
message: `Resource leak: \`${name}\` assigned to '${resourceVar}' at line ${openLine} ` +
|
|
96
|
+
`is never closed — file descriptors or connections may be exhausted`,
|
|
97
|
+
file,
|
|
98
|
+
line: openLine,
|
|
99
|
+
snippet,
|
|
100
|
+
fix: `Use try-with-resources (Java 7+): \`try (${name} ${resourceVar} = ...) { ... }\`, ` +
|
|
101
|
+
`or call \`${resourceVar}.close()\` in a finally block`,
|
|
102
|
+
evidence: { resource: name, variable: resourceVar },
|
|
103
|
+
});
|
|
104
|
+
continue;
|
|
105
|
+
}
|
|
106
|
+
// Close found — check if it is protected by a finally block
|
|
107
|
+
if (this.hasFinallyBlock(codeLines, openLine, methodEnd))
|
|
108
|
+
continue;
|
|
109
|
+
// Potential leak: close() exists but may be skipped on exception
|
|
110
|
+
leaks.push({ line: openLine, resource: name, variable: resourceVar, kind: 'potential' });
|
|
111
|
+
ctx.addFinding({
|
|
112
|
+
id: `resource-leak-${file}-${openLine}`,
|
|
113
|
+
pass: this.name,
|
|
114
|
+
category: this.category,
|
|
115
|
+
rule_id: this.name,
|
|
116
|
+
cwe: 'CWE-772',
|
|
117
|
+
severity: 'medium',
|
|
118
|
+
level: 'warning',
|
|
119
|
+
message: `Potential resource leak: \`${name}\` ('${resourceVar}') is closed at ` +
|
|
120
|
+
`line ${closeCall.location.line} but not inside a finally block — ` +
|
|
121
|
+
`an exception could skip the close`,
|
|
122
|
+
file,
|
|
123
|
+
line: openLine,
|
|
124
|
+
snippet,
|
|
125
|
+
fix: `Move \`${resourceVar}.close()\` into a finally block, or use try-with-resources`,
|
|
126
|
+
evidence: {
|
|
127
|
+
resource: name,
|
|
128
|
+
variable: resourceVar,
|
|
129
|
+
close_line: closeCall.location.line,
|
|
130
|
+
},
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
return { leaks };
|
|
134
|
+
}
|
|
135
|
+
/** True if a `finally` keyword appears in the method body after the open line. */
|
|
136
|
+
hasFinallyBlock(lines, fromLine, toLine) {
|
|
137
|
+
for (let l = fromLine; l <= toLine && l <= lines.length; l++) {
|
|
138
|
+
if (/\bfinally\b/.test(lines[l - 1] ?? ''))
|
|
139
|
+
return true;
|
|
140
|
+
}
|
|
141
|
+
return false;
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* True if a try-with-resources or Python `with` statement wraps the resource,
|
|
145
|
+
* indicating implicit close. Detects `try (` or `with open(` patterns.
|
|
146
|
+
*/
|
|
147
|
+
hasTryWithResources(lines, fromLine, toLine) {
|
|
148
|
+
for (let l = fromLine; l <= toLine && l <= lines.length; l++) {
|
|
149
|
+
const text = lines[l - 1] ?? '';
|
|
150
|
+
if (/\btry\s*\(/.test(text) || /\bwith\b.*\bopen\b/.test(text))
|
|
151
|
+
return true;
|
|
152
|
+
}
|
|
153
|
+
return false;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
//# sourceMappingURL=resource-leak-pass.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"resource-leak-pass.js","sourceRoot":"","sources":["../../../src/analysis/passes/resource-leak-pass.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pass #32: serial-await (category: performance)
|
|
3
|
+
*
|
|
4
|
+
* Detects sequential `await` expressions in JavaScript/TypeScript where the
|
|
5
|
+
* two awaited operations have no data dependency — they could be parallelised
|
|
6
|
+
* with `Promise.all()`.
|
|
7
|
+
*
|
|
8
|
+
* Detection strategy:
|
|
9
|
+
* 1. Per function (group lines by enclosing method via `graph.methodAtLine()`),
|
|
10
|
+
* scan lines in order for `await` patterns.
|
|
11
|
+
* 2. For consecutive pairs `(line1, line2)`:
|
|
12
|
+
* a. Find the DFG def created at `line1` (if any) — this is the result
|
|
13
|
+
* variable bound to the first await.
|
|
14
|
+
* b. Check whether that variable name appears verbatim in the source
|
|
15
|
+
* line at `line2`.
|
|
16
|
+
* c. Also check whether any def on `line2` appears in `line1`'s source.
|
|
17
|
+
* d. If neither direction has a textual dependency: the two awaits are
|
|
18
|
+
* independent.
|
|
19
|
+
* 3. If a function has ≥ 2 independent consecutive awaits: emit one finding
|
|
20
|
+
* per function at the first independent pair's line.
|
|
21
|
+
*
|
|
22
|
+
* Languages: JavaScript and TypeScript only.
|
|
23
|
+
*/
|
|
24
|
+
import type { AnalysisPass, PassContext } from '../../graph/analysis-pass.js';
|
|
25
|
+
export interface SerialAwaitResult {
|
|
26
|
+
serialAwaits: Array<{
|
|
27
|
+
functionLine: number;
|
|
28
|
+
firstAwaitLine: number;
|
|
29
|
+
secondAwaitLine: number;
|
|
30
|
+
}>;
|
|
31
|
+
}
|
|
32
|
+
export declare class SerialAwaitPass implements AnalysisPass<SerialAwaitResult> {
|
|
33
|
+
readonly name = "serial-await";
|
|
34
|
+
readonly category: "performance";
|
|
35
|
+
run(ctx: PassContext): SerialAwaitResult;
|
|
36
|
+
}
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pass #32: serial-await (category: performance)
|
|
3
|
+
*
|
|
4
|
+
* Detects sequential `await` expressions in JavaScript/TypeScript where the
|
|
5
|
+
* two awaited operations have no data dependency — they could be parallelised
|
|
6
|
+
* with `Promise.all()`.
|
|
7
|
+
*
|
|
8
|
+
* Detection strategy:
|
|
9
|
+
* 1. Per function (group lines by enclosing method via `graph.methodAtLine()`),
|
|
10
|
+
* scan lines in order for `await` patterns.
|
|
11
|
+
* 2. For consecutive pairs `(line1, line2)`:
|
|
12
|
+
* a. Find the DFG def created at `line1` (if any) — this is the result
|
|
13
|
+
* variable bound to the first await.
|
|
14
|
+
* b. Check whether that variable name appears verbatim in the source
|
|
15
|
+
* line at `line2`.
|
|
16
|
+
* c. Also check whether any def on `line2` appears in `line1`'s source.
|
|
17
|
+
* d. If neither direction has a textual dependency: the two awaits are
|
|
18
|
+
* independent.
|
|
19
|
+
* 3. If a function has ≥ 2 independent consecutive awaits: emit one finding
|
|
20
|
+
* per function at the first independent pair's line.
|
|
21
|
+
*
|
|
22
|
+
* Languages: JavaScript and TypeScript only.
|
|
23
|
+
*/
|
|
24
|
+
/** Matches: const/let/var? varName = await or bare await */
|
|
25
|
+
const AWAIT_ASSIGN_RE = /(?:const|let|var)?\s*(\w+)\s*=\s*await\s/;
|
|
26
|
+
const AWAIT_RE = /\bawait\s/;
|
|
27
|
+
export class SerialAwaitPass {
|
|
28
|
+
name = 'serial-await';
|
|
29
|
+
category = 'performance';
|
|
30
|
+
run(ctx) {
|
|
31
|
+
const { graph, code, language } = ctx;
|
|
32
|
+
if (language !== 'javascript' && language !== 'typescript') {
|
|
33
|
+
return { serialAwaits: [] };
|
|
34
|
+
}
|
|
35
|
+
const file = graph.ir.meta.file;
|
|
36
|
+
const codeLines = code.split('\n');
|
|
37
|
+
const totalLines = codeLines.length;
|
|
38
|
+
const serialAwaits = [];
|
|
39
|
+
const reportedFunctions = new Set();
|
|
40
|
+
// Collect all await lines
|
|
41
|
+
const awaitLines = [];
|
|
42
|
+
for (let i = 0; i < totalLines; i++) {
|
|
43
|
+
const lineText = codeLines[i];
|
|
44
|
+
if (!AWAIT_RE.test(lineText))
|
|
45
|
+
continue;
|
|
46
|
+
const m = AWAIT_ASSIGN_RE.exec(lineText);
|
|
47
|
+
const boundVar = m ? m[1] : null;
|
|
48
|
+
awaitLines.push({ line: i + 1, boundVar });
|
|
49
|
+
}
|
|
50
|
+
if (awaitLines.length < 2)
|
|
51
|
+
return { serialAwaits: [] };
|
|
52
|
+
// Check consecutive pairs
|
|
53
|
+
for (let i = 0; i + 1 < awaitLines.length; i++) {
|
|
54
|
+
const a1 = awaitLines[i];
|
|
55
|
+
const a2 = awaitLines[i + 1];
|
|
56
|
+
// Must be in the same function
|
|
57
|
+
const method1 = graph.methodAtLine(a1.line);
|
|
58
|
+
const method2 = graph.methodAtLine(a2.line);
|
|
59
|
+
const methodKey1 = method1
|
|
60
|
+
? `${method1.type.name}.${method1.method.name}.${method1.method.start_line}`
|
|
61
|
+
: `top.${a1.line}`;
|
|
62
|
+
const methodKey2 = method2
|
|
63
|
+
? `${method2.type.name}.${method2.method.name}.${method2.method.start_line}`
|
|
64
|
+
: `top.${a2.line}`;
|
|
65
|
+
if (methodKey1 !== methodKey2)
|
|
66
|
+
continue;
|
|
67
|
+
// Skip if lines are not consecutive (allow up to 3 lines apart for formatting)
|
|
68
|
+
if (a2.line - a1.line > 4)
|
|
69
|
+
continue;
|
|
70
|
+
// Check dependency: does line2 reference the variable bound by line1?
|
|
71
|
+
const line2Text = codeLines[a2.line - 1] ?? '';
|
|
72
|
+
const line1Text = codeLines[a1.line - 1] ?? '';
|
|
73
|
+
let dependent = false;
|
|
74
|
+
// Forward dependency: var from line1 used in line2
|
|
75
|
+
if (a1.boundVar && new RegExp(`\\b${a1.boundVar}\\b`).test(line2Text)) {
|
|
76
|
+
dependent = true;
|
|
77
|
+
}
|
|
78
|
+
// Reverse dependency: var from line2's def used in line1 (rare but possible)
|
|
79
|
+
if (!dependent && a2.boundVar && new RegExp(`\\b${a2.boundVar}\\b`).test(line1Text)) {
|
|
80
|
+
dependent = true;
|
|
81
|
+
}
|
|
82
|
+
// DFG-level check: any def at line2 whose variable appears in defs of line1 args
|
|
83
|
+
if (!dependent) {
|
|
84
|
+
const defs1 = graph.defsAtLine(a1.line);
|
|
85
|
+
const defs2 = graph.defsAtLine(a2.line);
|
|
86
|
+
for (const d1 of defs1) {
|
|
87
|
+
for (const d2 of defs2) {
|
|
88
|
+
if (d1.variable === d2.variable) {
|
|
89
|
+
dependent = true;
|
|
90
|
+
break;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
if (dependent)
|
|
94
|
+
break;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
if (dependent)
|
|
98
|
+
continue;
|
|
99
|
+
// Skip if already reported for this function
|
|
100
|
+
if (reportedFunctions.has(methodKey1))
|
|
101
|
+
continue;
|
|
102
|
+
reportedFunctions.add(methodKey1);
|
|
103
|
+
const funcLine = method1?.method.start_line ?? a1.line;
|
|
104
|
+
serialAwaits.push({ functionLine: funcLine, firstAwaitLine: a1.line, secondAwaitLine: a2.line });
|
|
105
|
+
// Extract readable names from the await expressions
|
|
106
|
+
const expr1 = line1Text.trim().replace(/^(?:const|let|var)\s+/, '');
|
|
107
|
+
const expr2 = line2Text.trim().replace(/^(?:const|let|var)\s+/, '');
|
|
108
|
+
ctx.addFinding({
|
|
109
|
+
id: `serial-await-${file}-${a1.line}`,
|
|
110
|
+
pass: this.name,
|
|
111
|
+
category: this.category,
|
|
112
|
+
rule_id: this.name,
|
|
113
|
+
cwe: undefined,
|
|
114
|
+
severity: 'low',
|
|
115
|
+
level: 'note',
|
|
116
|
+
message: `Serial awaits: \`${expr1}\` (line ${a1.line}) and \`${expr2}\` (line ${a2.line}) ` +
|
|
117
|
+
`have no data dependency; consider using Promise.all()`,
|
|
118
|
+
file,
|
|
119
|
+
line: a1.line,
|
|
120
|
+
end_line: a2.line,
|
|
121
|
+
fix: `const [result1, result2] = await Promise.all([operation1, operation2]);`,
|
|
122
|
+
evidence: {
|
|
123
|
+
first_await_line: a1.line,
|
|
124
|
+
second_await_line: a2.line,
|
|
125
|
+
function_line: funcLine,
|
|
126
|
+
},
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
return { serialAwaits };
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
//# sourceMappingURL=serial-await-pass.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"serial-await-pass.js","sourceRoot":"","sources":["../../../src/analysis/passes/serial-await-pass.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SinkFilterPass
|
|
3
|
+
*
|
|
4
|
+
* Applies the four-stage sink filtering pipeline to eliminate false positives,
|
|
5
|
+
* followed by language-specific XPath/XSS suppression.
|
|
6
|
+
*
|
|
7
|
+
* Filter stages (applied in order):
|
|
8
|
+
* 1. Dead code — remove sinks on unreachable lines
|
|
9
|
+
* 2. Clean array elements — strong updates via constant propagation
|
|
10
|
+
* 3. Clean variables — arguments proven non-tainted by constant propagation
|
|
11
|
+
* 4. Sanitized sinks — sinks wrapped by a recognised sanitizer call
|
|
12
|
+
* 5. Python XPath FP reduction
|
|
13
|
+
* 6. JavaScript XSS FP reduction
|
|
14
|
+
*
|
|
15
|
+
* Depends on: taint-matcher, constant-propagation, language-sources
|
|
16
|
+
*/
|
|
17
|
+
import type { TaintSource, TaintSink, TaintSanitizer } from '../../types/index.js';
|
|
18
|
+
import type { AnalysisPass, PassContext } from '../../graph/analysis-pass.js';
|
|
19
|
+
export interface SinkFilterResult {
|
|
20
|
+
/** Merged sources: taint-matcher + language-sources. */
|
|
21
|
+
sources: TaintSource[];
|
|
22
|
+
/** Filtered sinks. */
|
|
23
|
+
sinks: TaintSink[];
|
|
24
|
+
sanitizers: TaintSanitizer[];
|
|
25
|
+
}
|
|
26
|
+
export declare class SinkFilterPass implements AnalysisPass<SinkFilterResult> {
|
|
27
|
+
readonly name = "sink-filter";
|
|
28
|
+
readonly category: "security";
|
|
29
|
+
run(ctx: PassContext): SinkFilterResult;
|
|
30
|
+
}
|
|
31
|
+
import type { CircleIR } from '../../types/index.js';
|
|
32
|
+
type Symbols = Map<string, {
|
|
33
|
+
value: string | number | boolean | null;
|
|
34
|
+
type: string;
|
|
35
|
+
sourceLine: number;
|
|
36
|
+
}>;
|
|
37
|
+
export declare function filterCleanVariableSinks(sinks: CircleIR['taint']['sinks'], calls: CircleIR['calls'], taintedVars: Set<string>, symbols: Symbols, dfg?: CircleIR['dfg'], sanitizedVars?: Set<string>, synchronizedLines?: Set<number>): CircleIR['taint']['sinks'];
|
|
38
|
+
export declare function filterSanitizedSinks(sinks: CircleIR['taint']['sinks'], sanitizers: CircleIR['taint']['sanitizers'], calls: CircleIR['calls']): CircleIR['taint']['sinks'];
|
|
39
|
+
export {};
|
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SinkFilterPass
|
|
3
|
+
*
|
|
4
|
+
* Applies the four-stage sink filtering pipeline to eliminate false positives,
|
|
5
|
+
* followed by language-specific XPath/XSS suppression.
|
|
6
|
+
*
|
|
7
|
+
* Filter stages (applied in order):
|
|
8
|
+
* 1. Dead code — remove sinks on unreachable lines
|
|
9
|
+
* 2. Clean array elements — strong updates via constant propagation
|
|
10
|
+
* 3. Clean variables — arguments proven non-tainted by constant propagation
|
|
11
|
+
* 4. Sanitized sinks — sinks wrapped by a recognised sanitizer call
|
|
12
|
+
* 5. Python XPath FP reduction
|
|
13
|
+
* 6. JavaScript XSS FP reduction
|
|
14
|
+
*
|
|
15
|
+
* Depends on: taint-matcher, constant-propagation, language-sources
|
|
16
|
+
*/
|
|
17
|
+
import { JS_TAINTED_PATTERNS } from './language-sources-pass.js';
|
|
18
|
+
export class SinkFilterPass {
|
|
19
|
+
name = 'sink-filter';
|
|
20
|
+
category = 'security';
|
|
21
|
+
run(ctx) {
|
|
22
|
+
const { graph, language } = ctx;
|
|
23
|
+
const { calls, dfg } = graph.ir;
|
|
24
|
+
const taintMatcher = ctx.getResult('taint-matcher');
|
|
25
|
+
const constProp = ctx.getResult('constant-propagation');
|
|
26
|
+
const langSources = ctx.getResult('language-sources');
|
|
27
|
+
// Merge sources and sinks from both upstream passes.
|
|
28
|
+
const sources = [...taintMatcher.sources, ...langSources.additionalSources];
|
|
29
|
+
// Build merged sinks, deduplicating JS DOM sinks that may overlap with config sinks.
|
|
30
|
+
const sinks = [...taintMatcher.sinks];
|
|
31
|
+
for (const s of langSources.additionalSinks) {
|
|
32
|
+
if (!sinks.some(x => x.line === s.line && x.cwe === s.cwe && x.type === s.type)) {
|
|
33
|
+
sinks.push(s);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
const sanitizers = taintMatcher.sanitizers;
|
|
37
|
+
// Stage 1 — dead code
|
|
38
|
+
let filtered = sinks.filter(sink => !constProp.unreachableLines.has(sink.line));
|
|
39
|
+
// Stage 2 — clean array elements
|
|
40
|
+
filtered = filterCleanArraySinks(filtered, calls, constProp.taintedArrayElements, constProp.symbols);
|
|
41
|
+
// Stage 3 — clean variables
|
|
42
|
+
filtered = filterCleanVariableSinks(filtered, calls, constProp.tainted, constProp.symbols, dfg, constProp.sanitizedVars, constProp.synchronizedLines);
|
|
43
|
+
// Stage 4 — sanitized sinks
|
|
44
|
+
filtered = filterSanitizedSinks(filtered, sanitizers, calls);
|
|
45
|
+
// Stage 5 — Python XPath FP reduction
|
|
46
|
+
if (language === 'python') {
|
|
47
|
+
const { pyTaintedVars, pySanitizedVars } = langSources;
|
|
48
|
+
const sourceLines = ctx.code.split('\n');
|
|
49
|
+
filtered = filtered.filter(sink => {
|
|
50
|
+
if (sink.type !== 'xpath_injection')
|
|
51
|
+
return true;
|
|
52
|
+
const sinkLineText = sourceLines[sink.line - 1] ?? '';
|
|
53
|
+
const taintedVarOnLine = [...pyTaintedVars.keys()].find(v => new RegExp(`\\b${v}\\b`).test(sinkLineText));
|
|
54
|
+
if (!taintedVarOnLine)
|
|
55
|
+
return false;
|
|
56
|
+
if (pySanitizedVars.has(taintedVarOnLine))
|
|
57
|
+
return false;
|
|
58
|
+
if (new RegExp(`\\.xpath\\s*\\([^)]*\\b\\w+\\s*=\\s*\\b${taintedVarOnLine}\\b`).test(sinkLineText))
|
|
59
|
+
return false;
|
|
60
|
+
return true;
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
// Stage 6 — JavaScript XSS FP reduction
|
|
64
|
+
if (['javascript', 'typescript'].includes(language)) {
|
|
65
|
+
const { jsTaintedVars } = langSources;
|
|
66
|
+
if (jsTaintedVars.size > 0) {
|
|
67
|
+
const sourceLines = ctx.code.split('\n');
|
|
68
|
+
filtered = filtered.filter(sink => {
|
|
69
|
+
if (sink.type !== 'xss')
|
|
70
|
+
return true;
|
|
71
|
+
const sinkLineText = sourceLines[sink.line - 1] ?? '';
|
|
72
|
+
if ([...jsTaintedVars.keys()].some(v => new RegExp(`\\b${v}\\b`).test(sinkLineText)))
|
|
73
|
+
return true;
|
|
74
|
+
if (JS_TAINTED_PATTERNS.some(p => p.pattern.test(sinkLineText)))
|
|
75
|
+
return true;
|
|
76
|
+
return false;
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
return { sources, sinks: filtered, sanitizers };
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
function evaluateSimpleExpression(expr, symbols) {
|
|
84
|
+
let evaluated = expr;
|
|
85
|
+
for (const [name, val] of symbols) {
|
|
86
|
+
if (val.type === 'int' || val.type === 'float') {
|
|
87
|
+
const regex = new RegExp(`\\b${name}\\b`, 'g');
|
|
88
|
+
evaluated = evaluated.replace(regex, String(val.value));
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
try {
|
|
92
|
+
if (/^[\d\s+\-*/().]+$/.test(evaluated)) {
|
|
93
|
+
const result = Function('"use strict"; return (' + evaluated + ')')();
|
|
94
|
+
if (typeof result === 'number' && !isNaN(result))
|
|
95
|
+
return String(Math.floor(result));
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
catch { /* evaluation failed */ }
|
|
99
|
+
return expr;
|
|
100
|
+
}
|
|
101
|
+
function isStringLiteralExpression(expr) {
|
|
102
|
+
const trimmed = expr.trim();
|
|
103
|
+
return (trimmed.startsWith('"') && trimmed.endsWith('"')) ||
|
|
104
|
+
(trimmed.startsWith("'") && trimmed.endsWith("'"));
|
|
105
|
+
}
|
|
106
|
+
function filterCleanArraySinks(sinks, calls, taintedArrayElements, symbols) {
|
|
107
|
+
const callsByLine = new Map();
|
|
108
|
+
for (const call of calls) {
|
|
109
|
+
const existing = callsByLine.get(call.location.line) ?? [];
|
|
110
|
+
existing.push(call);
|
|
111
|
+
callsByLine.set(call.location.line, existing);
|
|
112
|
+
}
|
|
113
|
+
return sinks.filter(sink => {
|
|
114
|
+
const callsAtSink = callsByLine.get(sink.line) ?? [];
|
|
115
|
+
for (const call of callsAtSink) {
|
|
116
|
+
for (const arg of call.arguments) {
|
|
117
|
+
const arrayAccessMatch = arg.expression?.match(/^(\w+)\[(\d+|[^[\]]+)\]$/);
|
|
118
|
+
if (arrayAccessMatch) {
|
|
119
|
+
const arrayName = arrayAccessMatch[1];
|
|
120
|
+
let indexStr = arrayAccessMatch[2];
|
|
121
|
+
indexStr = evaluateSimpleExpression(indexStr, symbols);
|
|
122
|
+
const taintedIndices = taintedArrayElements.get(arrayName);
|
|
123
|
+
if (taintedIndices !== undefined) {
|
|
124
|
+
const isTainted = taintedIndices.has(indexStr) || taintedIndices.has('*');
|
|
125
|
+
if (!isTainted)
|
|
126
|
+
return false;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
return true;
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
export function filterCleanVariableSinks(sinks, calls, taintedVars, symbols, dfg, sanitizedVars, synchronizedLines) {
|
|
135
|
+
const fieldNames = new Set();
|
|
136
|
+
if (dfg) {
|
|
137
|
+
for (const def of dfg.defs) {
|
|
138
|
+
if (def.kind === 'field')
|
|
139
|
+
fieldNames.add(def.variable);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
const callsByLine = new Map();
|
|
143
|
+
for (const call of calls) {
|
|
144
|
+
const existing = callsByLine.get(call.location.line) ?? [];
|
|
145
|
+
existing.push(call);
|
|
146
|
+
callsByLine.set(call.location.line, existing);
|
|
147
|
+
}
|
|
148
|
+
return sinks.filter(sink => {
|
|
149
|
+
const callsAtSink = callsByLine.get(sink.line) ?? [];
|
|
150
|
+
const isInSynchronizedBlock = synchronizedLines?.has(sink.line) ?? false;
|
|
151
|
+
for (const call of callsAtSink) {
|
|
152
|
+
let allArgsAreClean = true;
|
|
153
|
+
const methodName = call.in_method;
|
|
154
|
+
for (const arg of call.arguments) {
|
|
155
|
+
if (arg.variable && !arg.expression?.includes('[')) {
|
|
156
|
+
const varName = arg.variable;
|
|
157
|
+
const scopedName = methodName ? `${methodName}:${varName}` : varName;
|
|
158
|
+
if (fieldNames.has(varName) && !isInSynchronizedBlock) {
|
|
159
|
+
allArgsAreClean = false;
|
|
160
|
+
continue;
|
|
161
|
+
}
|
|
162
|
+
if (sanitizedVars?.has(scopedName) || sanitizedVars?.has(varName))
|
|
163
|
+
continue;
|
|
164
|
+
if (taintedVars.has(scopedName) || taintedVars.has(varName)) {
|
|
165
|
+
allArgsAreClean = false;
|
|
166
|
+
continue;
|
|
167
|
+
}
|
|
168
|
+
const symbolValue = symbols.get(scopedName) ?? symbols.get(varName);
|
|
169
|
+
if (symbolValue && symbolValue.type !== 'unknown')
|
|
170
|
+
continue;
|
|
171
|
+
allArgsAreClean = false;
|
|
172
|
+
}
|
|
173
|
+
else {
|
|
174
|
+
if (arg.literal != null)
|
|
175
|
+
continue;
|
|
176
|
+
if (arg.expression && !arg.variable && isStringLiteralExpression(arg.expression))
|
|
177
|
+
continue;
|
|
178
|
+
allArgsAreClean = false;
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
if (allArgsAreClean && call.arguments.length > 0)
|
|
182
|
+
return false;
|
|
183
|
+
}
|
|
184
|
+
return true;
|
|
185
|
+
});
|
|
186
|
+
}
|
|
187
|
+
export function filterSanitizedSinks(sinks, sanitizers, calls) {
|
|
188
|
+
if (!sanitizers || sanitizers.length === 0)
|
|
189
|
+
return sinks;
|
|
190
|
+
const sanitizersByLine = new Map();
|
|
191
|
+
for (const san of sanitizers) {
|
|
192
|
+
const existing = sanitizersByLine.get(san.line) ?? [];
|
|
193
|
+
existing.push(san);
|
|
194
|
+
sanitizersByLine.set(san.line, existing);
|
|
195
|
+
}
|
|
196
|
+
const callsByLine = new Map();
|
|
197
|
+
for (const call of calls) {
|
|
198
|
+
const existing = callsByLine.get(call.location.line) ?? [];
|
|
199
|
+
existing.push(call);
|
|
200
|
+
callsByLine.set(call.location.line, existing);
|
|
201
|
+
}
|
|
202
|
+
return sinks.filter(sink => {
|
|
203
|
+
const lineSanitizers = sanitizersByLine.get(sink.line);
|
|
204
|
+
if (!lineSanitizers || lineSanitizers.length === 0)
|
|
205
|
+
return true;
|
|
206
|
+
for (const san of lineSanitizers) {
|
|
207
|
+
if (san.sanitizes.includes(sink.type)) {
|
|
208
|
+
const lineCalls = callsByLine.get(sink.line) ?? [];
|
|
209
|
+
for (const call of lineCalls) {
|
|
210
|
+
for (const arg of call.arguments) {
|
|
211
|
+
const expr = arg.expression || '';
|
|
212
|
+
const sanMethodMatch = san.method.match(/(?:(\w+)\.)?(\w+)\(\)/);
|
|
213
|
+
if (sanMethodMatch) {
|
|
214
|
+
const sanMethodName = sanMethodMatch[2];
|
|
215
|
+
const sanClassName = sanMethodMatch[1];
|
|
216
|
+
if (sanClassName) {
|
|
217
|
+
if (expr.includes(`${sanClassName}.${sanMethodName}(`))
|
|
218
|
+
return false;
|
|
219
|
+
}
|
|
220
|
+
else if (expr.includes(`${sanMethodName}(`)) {
|
|
221
|
+
return false;
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
return true;
|
|
229
|
+
});
|
|
230
|
+
}
|
|
231
|
+
//# sourceMappingURL=sink-filter-pass.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sink-filter-pass.js","sourceRoot":"","sources":["../../../src/analysis/passes/sink-filter-pass.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pass #33: stale-doc-ref
|
|
3
|
+
*
|
|
4
|
+
* Flags doc comment references ({@link ClassName} and @see ClassName) that
|
|
5
|
+
* point to symbols not found in the file's type declarations or imports.
|
|
6
|
+
* Stale doc refs cause confusion and erode documentation trustworthiness.
|
|
7
|
+
*
|
|
8
|
+
* Category: maintainability | Severity: low | Level: note | CWE: none
|
|
9
|
+
*/
|
|
10
|
+
import type { AnalysisPass, PassContext } from '../../graph/analysis-pass.js';
|
|
11
|
+
export interface StaleDocRefResult {
|
|
12
|
+
staleRefs: Array<{
|
|
13
|
+
line: number;
|
|
14
|
+
ref: string;
|
|
15
|
+
}>;
|
|
16
|
+
}
|
|
17
|
+
export declare class StaleDocRefPass implements AnalysisPass<StaleDocRefResult> {
|
|
18
|
+
readonly name = "stale-doc-ref";
|
|
19
|
+
readonly category: "maintainability";
|
|
20
|
+
run(ctx: PassContext): StaleDocRefResult;
|
|
21
|
+
}
|