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
|
@@ -10,5 +10,16 @@ import { type NodeCache } from '../parser.js';
|
|
|
10
10
|
* @param cache Optional node cache for performance
|
|
11
11
|
* @param language Optional language hint ('java' | 'javascript' | 'typescript' | 'python' | 'rust')
|
|
12
12
|
*/
|
|
13
|
-
|
|
13
|
+
/**
|
|
14
|
+
* Options for `extractCalls`. Every field is off by default, and the default
|
|
15
|
+
* output is unchanged: `ir.calls` is read by the SAST passes.
|
|
16
|
+
*/
|
|
17
|
+
export interface CallExtractionOptions {
|
|
18
|
+
/**
|
|
19
|
+
* Java only: give each argument its static type and start position
|
|
20
|
+
* (`ArgumentInfo.type` / `.location`), which overload selection needs.
|
|
21
|
+
*/
|
|
22
|
+
navigationTypes?: boolean;
|
|
23
|
+
}
|
|
24
|
+
export declare function extractCalls(tree: Tree, cache?: NodeCache, language?: string, options?: CallExtractionOptions): CallInfo[];
|
|
14
25
|
//# sourceMappingURL=calls.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"calls.d.ts","sourceRoot":"","sources":["../../../src/core/extractors/calls.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAQ,IAAI,EAAE,MAAM,iBAAiB,CAAC;AAClD,OAAO,KAAK,EAAE,QAAQ,EAAgC,MAAM,sBAAsB,CAAC;AACnF,OAAO,EAA2D,KAAK,SAAS,EAAE,MAAM,cAAc,CAAC;AA8EvG;;;;;GAKG;AACH,wBAAgB,YAAY,
|
|
1
|
+
{"version":3,"file":"calls.d.ts","sourceRoot":"","sources":["../../../src/core/extractors/calls.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAQ,IAAI,EAAE,MAAM,iBAAiB,CAAC;AAClD,OAAO,KAAK,EAAE,QAAQ,EAAgC,MAAM,sBAAsB,CAAC;AACnF,OAAO,EAA2D,KAAK,SAAS,EAAE,MAAM,cAAc,CAAC;AA8EvG;;;;;GAKG;AACH;;;GAGG;AACH,MAAM,WAAW,qBAAqB;IACpC;;;OAGG;IACH,eAAe,CAAC,EAAE,OAAO,CAAC;CAC3B;AAED,wBAAgB,YAAY,CAC1B,IAAI,EAAE,IAAI,EACV,KAAK,CAAC,EAAE,SAAS,EACjB,QAAQ,CAAC,EAAE,MAAM,EACjB,OAAO,GAAE,qBAA0B,GAClC,QAAQ,EAAE,CAqDZ"}
|
|
@@ -47,13 +47,7 @@ function detectLanguageFromTree(tree, cache) {
|
|
|
47
47
|
// Default to Java for backwards compatibility
|
|
48
48
|
return javaIndicators >= jsIndicators ? 'java' : 'javascript';
|
|
49
49
|
}
|
|
50
|
-
|
|
51
|
-
* Extract all method calls from the tree.
|
|
52
|
-
* @param tree The parsed AST tree
|
|
53
|
-
* @param cache Optional node cache for performance
|
|
54
|
-
* @param language Optional language hint ('java' | 'javascript' | 'typescript' | 'python' | 'rust')
|
|
55
|
-
*/
|
|
56
|
-
export function extractCalls(tree, cache, language) {
|
|
50
|
+
export function extractCalls(tree, cache, language, options = {}) {
|
|
57
51
|
const calls = [];
|
|
58
52
|
// Use language hint if provided, otherwise detect from tree
|
|
59
53
|
const detectedLanguage = language ?? detectLanguageFromTree(tree, cache);
|
|
@@ -82,13 +76,14 @@ export function extractCalls(tree, cache, language) {
|
|
|
82
76
|
const context = buildResolutionContext(tree, cache);
|
|
83
77
|
// Find all method invocations
|
|
84
78
|
const invocations = getNodesFromCache(tree.rootNode, 'method_invocation', cache);
|
|
79
|
+
const typed = options.navigationTypes === true;
|
|
85
80
|
for (const inv of invocations) {
|
|
86
|
-
calls.push(extractCallInfo(inv, context));
|
|
81
|
+
calls.push(extractCallInfo(inv, context, typed));
|
|
87
82
|
}
|
|
88
83
|
// Find object creation expressions (constructor calls)
|
|
89
84
|
const objectCreations = getNodesFromCache(tree.rootNode, 'object_creation_expression', cache);
|
|
90
85
|
for (const creation of objectCreations) {
|
|
91
|
-
const callInfo = extractObjectCreation(creation, context);
|
|
86
|
+
const callInfo = extractObjectCreation(creation, context, typed);
|
|
92
87
|
if (callInfo) {
|
|
93
88
|
calls.push(callInfo);
|
|
94
89
|
}
|
|
@@ -115,15 +110,16 @@ export function extractCalls(tree, cache, language) {
|
|
|
115
110
|
* match only covers for static calls. Type comes from the declaration
|
|
116
111
|
* (`HttpClient c = …`) or, for `var`, from the `new T(…)` initializer.
|
|
117
112
|
*/
|
|
118
|
-
function buildCSharpReceiverTypeMap(tree, cache) {
|
|
113
|
+
function buildCSharpReceiverTypeMap(tree, cache, scope) {
|
|
119
114
|
const map = new Map();
|
|
115
|
+
const nodesOf = (type) => scope ? scope.descendantsOfType(type).filter((n) => n !== null) : getNodesFromCache(tree.rootNode, type, cache);
|
|
120
116
|
const simple = (t) => t.replace(/<[^>]*>/g, '').split('.').pop()?.trim() ?? t;
|
|
121
117
|
// cognium-dev#542 — method/constructor parameters. A receiver is just as often a parameter as a
|
|
122
118
|
// local — `void Bad(HttpRequest req, HttpResponse resp)` is the canonical
|
|
123
119
|
// ASP.NET handler shape — and omitting them meant every receiver gate keyed on
|
|
124
120
|
// a declared type silently failed for parameters. Locals below win on a name
|
|
125
121
|
// clash, since an inner declaration shadows the parameter.
|
|
126
|
-
for (const prm of
|
|
122
|
+
for (const prm of nodesOf('parameter')) {
|
|
127
123
|
const nameNode = prm.childForFieldName('name');
|
|
128
124
|
const typeNode = prm.childForFieldName('type');
|
|
129
125
|
if (nameNode?.type !== 'identifier' || !typeNode)
|
|
@@ -132,7 +128,7 @@ function buildCSharpReceiverTypeMap(tree, cache) {
|
|
|
132
128
|
if (t && t !== 'var')
|
|
133
129
|
map.set(getNodeText(nameNode), simple(t));
|
|
134
130
|
}
|
|
135
|
-
for (const vd of
|
|
131
|
+
for (const vd of nodesOf('variable_declaration')) {
|
|
136
132
|
const typeNode = vd.childForFieldName('type');
|
|
137
133
|
const declaredType = typeNode ? getNodeText(typeNode) : null;
|
|
138
134
|
for (let i = 0; i < vd.childCount; i++) {
|
|
@@ -243,6 +239,25 @@ function csharpCreatedSimpleType(creation) {
|
|
|
243
239
|
function extractCSharpCalls(tree, cache) {
|
|
244
240
|
const calls = [];
|
|
245
241
|
const typeMap = buildCSharpReceiverTypeMap(tree, cache);
|
|
242
|
+
// The file-wide map keeps one type per name, so `ProcessStartInfo proc` in one
|
|
243
|
+
// method hides `Process proc` in another. For a receiver check, look at the
|
|
244
|
+
// declarations of the method that holds the call first, then the file-wide map
|
|
245
|
+
// (fields, members declared outside any method).
|
|
246
|
+
const methodTypeMaps = new Map();
|
|
247
|
+
const csharpReceiverTypeAt = (inv, name) => {
|
|
248
|
+
let n = inv.parent;
|
|
249
|
+
while (n && n.type !== 'method_declaration' && n.type !== 'constructor_declaration' &&
|
|
250
|
+
n.type !== 'local_function_statement' && n.type !== 'accessor_declaration')
|
|
251
|
+
n = n.parent;
|
|
252
|
+
if (!n)
|
|
253
|
+
return typeMap.get(name);
|
|
254
|
+
let scoped = methodTypeMaps.get(n.id);
|
|
255
|
+
if (!scoped) {
|
|
256
|
+
scoped = buildCSharpReceiverTypeMap(tree, cache, n);
|
|
257
|
+
methodTypeMaps.set(n.id, scoped);
|
|
258
|
+
}
|
|
259
|
+
return scoped.get(name) ?? typeMap.get(name);
|
|
260
|
+
};
|
|
246
261
|
const invocations = getNodesFromCache(tree.rootNode, 'invocation_expression', cache);
|
|
247
262
|
for (const inv of invocations) {
|
|
248
263
|
const fn = inv.childForFieldName('function');
|
|
@@ -271,6 +286,14 @@ function extractCSharpCalls(tree, cache) {
|
|
|
271
286
|
if (receiver && CSHARP_COMMAND_EXECUTE_METHODS.has(methodName)) {
|
|
272
287
|
args = [{ position: 0, expression: receiver, variable: receiver, literal: null, value: null }, ...args];
|
|
273
288
|
}
|
|
289
|
+
// cognium-dev#594: `proc.Start()` takes no argument; the executable rides the
|
|
290
|
+
// `Process` object (`proc.StartInfo.FileName = tainted`, seeded in
|
|
291
|
+
// taint-propagation). Surface the receiver as arg[0], as for `Execute*()`
|
|
292
|
+
// above. Gated on the receiver being a declared `Process` so `Stopwatch`,
|
|
293
|
+
// `Timer` and `Thread` `Start()` calls are untouched.
|
|
294
|
+
if (receiver && methodName === 'Start' && args.length === 0 && csharpReceiverTypeAt(inv, receiver) === 'Process') {
|
|
295
|
+
args = [{ position: 0, expression: receiver, variable: receiver, literal: null, value: null }];
|
|
296
|
+
}
|
|
274
297
|
// `Response.Headers.Add(name, value)` is the ASP.NET Core response-header
|
|
275
298
|
// writer, but a bare `Add` is far too common to register as a CRLF sink
|
|
276
299
|
// (List.Add, Dictionary.Add, …). Normalize the header-dictionary form to the
|
|
@@ -288,7 +311,7 @@ function extractCSharpCalls(tree, cache) {
|
|
|
288
311
|
calls.push({
|
|
289
312
|
method_name: methodName,
|
|
290
313
|
receiver,
|
|
291
|
-
receiver_type: receiverTypeFromCreation ?? (receiver ? (
|
|
314
|
+
receiver_type: receiverTypeFromCreation ?? (receiver ? (csharpReceiverTypeAt(inv, receiver) ?? null) : null),
|
|
292
315
|
receiver_type_fqn: null,
|
|
293
316
|
arguments: args,
|
|
294
317
|
location: { line: inv.startPosition.row + 1, column: inv.startPosition.column },
|
|
@@ -1505,7 +1528,7 @@ const JAVA_LANG_TYPES = new Set([
|
|
|
1505
1528
|
/**
|
|
1506
1529
|
* Extract call information from a method_invocation node.
|
|
1507
1530
|
*/
|
|
1508
|
-
function extractCallInfo(node, context) {
|
|
1531
|
+
function extractCallInfo(node, context, typed = false) {
|
|
1509
1532
|
// Get method name
|
|
1510
1533
|
const nameNode = node.childForFieldName('name');
|
|
1511
1534
|
const methodName = nameNode ? getNodeText(nameNode) : 'unknown';
|
|
@@ -1514,13 +1537,23 @@ function extractCallInfo(node, context) {
|
|
|
1514
1537
|
const receiver = objectNode ? getNodeText(objectNode) : null;
|
|
1515
1538
|
// Get arguments
|
|
1516
1539
|
const argsNode = node.childForFieldName('arguments');
|
|
1517
|
-
const args = argsNode ? extractArguments(argsNode) : [];
|
|
1540
|
+
const args = argsNode ? extractArguments(argsNode, typed) : [];
|
|
1518
1541
|
// Find enclosing method
|
|
1519
1542
|
const enclosingMethod = findEnclosingMethod(node);
|
|
1520
1543
|
// Resolve the call
|
|
1521
1544
|
const { resolved, resolution } = resolveMethodCall(methodName, receiver, context);
|
|
1522
|
-
// Resolve the receiver's declared type and FQN
|
|
1523
|
-
|
|
1545
|
+
// Resolve the receiver's declared type and FQN. For navigation, a plain
|
|
1546
|
+
// variable is looked up where it is used, so a lambda parameter or a name
|
|
1547
|
+
// two methods declare differently gets its own type, or none; the
|
|
1548
|
+
// file-wide table is the fallback for what no enclosing scope declares.
|
|
1549
|
+
let { simpleName, fqn } = resolveReceiverType(receiver, context);
|
|
1550
|
+
if (typed && objectNode?.type === 'identifier') {
|
|
1551
|
+
const scoped = lookupInScope(objectNode, getNodeText(objectNode));
|
|
1552
|
+
if (scoped === null)
|
|
1553
|
+
({ simpleName, fqn } = { simpleName: null, fqn: null });
|
|
1554
|
+
else if (scoped !== undefined)
|
|
1555
|
+
({ simpleName, fqn } = resolveFqn(stripGenerics(scoped), context));
|
|
1556
|
+
}
|
|
1524
1557
|
return {
|
|
1525
1558
|
method_name: methodName,
|
|
1526
1559
|
receiver,
|
|
@@ -1539,7 +1572,7 @@ function extractCallInfo(node, context) {
|
|
|
1539
1572
|
/**
|
|
1540
1573
|
* Extract call information from an object_creation_expression (new ...).
|
|
1541
1574
|
*/
|
|
1542
|
-
function extractObjectCreation(node, context) {
|
|
1575
|
+
function extractObjectCreation(node, context, typed = false) {
|
|
1543
1576
|
// Get the type being instantiated
|
|
1544
1577
|
const typeNode = node.childForFieldName('type');
|
|
1545
1578
|
if (!typeNode)
|
|
@@ -1547,7 +1580,7 @@ function extractObjectCreation(node, context) {
|
|
|
1547
1580
|
const typeName = getNodeText(typeNode);
|
|
1548
1581
|
// Get arguments
|
|
1549
1582
|
const argsNode = node.childForFieldName('arguments');
|
|
1550
|
-
const args = argsNode ? extractArguments(argsNode) : [];
|
|
1583
|
+
const args = argsNode ? extractArguments(argsNode, typed) : [];
|
|
1551
1584
|
// Find enclosing method
|
|
1552
1585
|
const enclosingMethod = findEnclosingMethod(node);
|
|
1553
1586
|
// Constructor calls are always resolved (we know the class)
|
|
@@ -1578,7 +1611,7 @@ function extractObjectCreation(node, context) {
|
|
|
1578
1611
|
/**
|
|
1579
1612
|
* Extract arguments from an argument_list node.
|
|
1580
1613
|
*/
|
|
1581
|
-
function extractArguments(argsNode) {
|
|
1614
|
+
function extractArguments(argsNode, typed = false) {
|
|
1582
1615
|
const args = [];
|
|
1583
1616
|
let position = 0;
|
|
1584
1617
|
for (let i = 0; i < argsNode.childCount; i++) {
|
|
@@ -1591,16 +1624,293 @@ function extractArguments(argsNode) {
|
|
|
1591
1624
|
}
|
|
1592
1625
|
const expression = getNodeText(child);
|
|
1593
1626
|
const { variable, literal } = analyzeArgument(child);
|
|
1594
|
-
|
|
1627
|
+
const arg = {
|
|
1595
1628
|
position,
|
|
1596
1629
|
expression,
|
|
1597
1630
|
variable,
|
|
1598
1631
|
literal,
|
|
1599
|
-
}
|
|
1632
|
+
};
|
|
1633
|
+
if (typed) {
|
|
1634
|
+
arg.type = javaStaticType(child);
|
|
1635
|
+
arg.location = { line: child.startPosition.row + 1, column: child.startPosition.column };
|
|
1636
|
+
}
|
|
1637
|
+
args.push(arg);
|
|
1600
1638
|
position++;
|
|
1601
1639
|
}
|
|
1602
1640
|
return args;
|
|
1603
1641
|
}
|
|
1642
|
+
const NUMERIC_RANK = { byte: 1, short: 2, char: 2, int: 3, long: 4, float: 5, double: 6 };
|
|
1643
|
+
const BOOLEAN_OPERATORS = new Set(['==', '!=', '<', '>', '<=', '>=', '&&', '||', 'instanceof']);
|
|
1644
|
+
/**
|
|
1645
|
+
* The static type of a Java expression, as far as the syntax and the
|
|
1646
|
+
* declarations in scope say it — and `null` past that.
|
|
1647
|
+
*
|
|
1648
|
+
* Read for overload selection, so it errs one way only: a type it cannot be
|
|
1649
|
+
* sure of is `null` ("could be anything"), never a guess. A method call's
|
|
1650
|
+
* result is not typed here; the navigation index types it by binding that
|
|
1651
|
+
* call, which needs the whole tree.
|
|
1652
|
+
*/
|
|
1653
|
+
function javaStaticType(node) {
|
|
1654
|
+
const text = getNodeText(node);
|
|
1655
|
+
switch (node.type) {
|
|
1656
|
+
case 'string_literal':
|
|
1657
|
+
case 'text_block':
|
|
1658
|
+
return 'String';
|
|
1659
|
+
case 'character_literal':
|
|
1660
|
+
return 'char';
|
|
1661
|
+
case 'decimal_integer_literal':
|
|
1662
|
+
case 'hex_integer_literal':
|
|
1663
|
+
case 'octal_integer_literal':
|
|
1664
|
+
case 'binary_integer_literal':
|
|
1665
|
+
return /[lL]$/.test(text) ? 'long' : 'int';
|
|
1666
|
+
case 'decimal_floating_point_literal':
|
|
1667
|
+
case 'hex_floating_point_literal':
|
|
1668
|
+
return /[fF]$/.test(text) ? 'float' : 'double';
|
|
1669
|
+
case 'true':
|
|
1670
|
+
case 'false':
|
|
1671
|
+
case 'instanceof_expression':
|
|
1672
|
+
return 'boolean';
|
|
1673
|
+
case 'null_literal':
|
|
1674
|
+
return 'null';
|
|
1675
|
+
case 'class_literal':
|
|
1676
|
+
return 'Class';
|
|
1677
|
+
case 'parenthesized_expression': {
|
|
1678
|
+
const inner = node.namedChild(0);
|
|
1679
|
+
return inner ? javaStaticType(inner) : null;
|
|
1680
|
+
}
|
|
1681
|
+
case 'cast_expression': {
|
|
1682
|
+
const t = node.childForFieldName('type');
|
|
1683
|
+
return t ? getNodeText(t) : null;
|
|
1684
|
+
}
|
|
1685
|
+
case 'object_creation_expression': {
|
|
1686
|
+
const t = node.childForFieldName('type');
|
|
1687
|
+
return t ? getNodeText(t) : null;
|
|
1688
|
+
}
|
|
1689
|
+
case 'array_creation_expression': {
|
|
1690
|
+
const t = node.childForFieldName('type');
|
|
1691
|
+
if (!t)
|
|
1692
|
+
return null;
|
|
1693
|
+
const dims = (text.match(/\[/g) ?? []).length - (getNodeText(t).match(/\[/g) ?? []).length;
|
|
1694
|
+
return `${getNodeText(t)}${'[]'.repeat(Math.max(dims, 1))}`;
|
|
1695
|
+
}
|
|
1696
|
+
case 'unary_expression': {
|
|
1697
|
+
const op = node.childForFieldName('operator');
|
|
1698
|
+
const operand = node.childForFieldName('operand');
|
|
1699
|
+
if (op && getNodeText(op) === '!')
|
|
1700
|
+
return 'boolean';
|
|
1701
|
+
const t = operand ? javaStaticType(operand) : null;
|
|
1702
|
+
return t && t in NUMERIC_RANK ? (NUMERIC_RANK[t] < 3 ? 'int' : t) : null;
|
|
1703
|
+
}
|
|
1704
|
+
case 'binary_expression': {
|
|
1705
|
+
const op = node.childForFieldName('operator');
|
|
1706
|
+
const opText = op ? getNodeText(op) : '';
|
|
1707
|
+
if (BOOLEAN_OPERATORS.has(opText))
|
|
1708
|
+
return 'boolean';
|
|
1709
|
+
const left = node.childForFieldName('left');
|
|
1710
|
+
const right = node.childForFieldName('right');
|
|
1711
|
+
const lt = left ? javaStaticType(left) : null;
|
|
1712
|
+
const rt = right ? javaStaticType(right) : null;
|
|
1713
|
+
if (opText === '+' && (lt === 'String' || rt === 'String'))
|
|
1714
|
+
return 'String';
|
|
1715
|
+
// Binary numeric promotion, only when both sides are known primitives.
|
|
1716
|
+
if (lt && rt && lt in NUMERIC_RANK && rt in NUMERIC_RANK) {
|
|
1717
|
+
const wider = NUMERIC_RANK[lt] >= NUMERIC_RANK[rt] ? lt : rt;
|
|
1718
|
+
return NUMERIC_RANK[wider] < 3 ? 'int' : wider;
|
|
1719
|
+
}
|
|
1720
|
+
return null;
|
|
1721
|
+
}
|
|
1722
|
+
case 'ternary_expression': {
|
|
1723
|
+
const a = node.childForFieldName('consequence');
|
|
1724
|
+
const b = node.childForFieldName('alternative');
|
|
1725
|
+
const at = a ? javaStaticType(a) : null;
|
|
1726
|
+
const bt = b ? javaStaticType(b) : null;
|
|
1727
|
+
return at !== null && at === bt ? at : null;
|
|
1728
|
+
}
|
|
1729
|
+
case 'identifier':
|
|
1730
|
+
return declaredTypeInScope(node, text);
|
|
1731
|
+
case 'field_access': {
|
|
1732
|
+
const obj = node.childForFieldName('object');
|
|
1733
|
+
const field = node.childForFieldName('field');
|
|
1734
|
+
if (obj?.type !== 'this' || !field)
|
|
1735
|
+
return null;
|
|
1736
|
+
const body = findAncestor(node, 'class_body') ?? findAncestor(node, 'enum_body_declarations');
|
|
1737
|
+
return body ? fieldTypeIn(body, getNodeText(field)) : null;
|
|
1738
|
+
}
|
|
1739
|
+
default:
|
|
1740
|
+
return null;
|
|
1741
|
+
}
|
|
1742
|
+
}
|
|
1743
|
+
/**
|
|
1744
|
+
* The declared type of the variable `name` as seen from `node`: the nearest
|
|
1745
|
+
* enclosing local, parameter, resource, loop or catch variable, then a field
|
|
1746
|
+
* of an enclosing type. `null` when the nearest declaration has no written
|
|
1747
|
+
* type (a lambda's inferred parameter, `var` without a readable initializer)
|
|
1748
|
+
* or when nothing in the file declares it — an inherited field, say.
|
|
1749
|
+
*/
|
|
1750
|
+
function declaredTypeInScope(node, name) {
|
|
1751
|
+
return lookupInScope(node, name) ?? null;
|
|
1752
|
+
}
|
|
1753
|
+
/** `declaredTypeInScope`, telling "declared without a written type" (`null`) from "not declared here" (`undefined`). */
|
|
1754
|
+
function lookupInScope(node, name) {
|
|
1755
|
+
const typeOf = (typeNode, value) => {
|
|
1756
|
+
if (!typeNode)
|
|
1757
|
+
return null;
|
|
1758
|
+
const t = getNodeText(typeNode);
|
|
1759
|
+
if (t !== 'var')
|
|
1760
|
+
return t;
|
|
1761
|
+
return value ? javaStaticType(value) : null;
|
|
1762
|
+
};
|
|
1763
|
+
const fromDeclaration = (decl) => {
|
|
1764
|
+
for (const d of decl.namedChildren) {
|
|
1765
|
+
if (d?.type !== 'variable_declarator')
|
|
1766
|
+
continue;
|
|
1767
|
+
const n = d.childForFieldName('name');
|
|
1768
|
+
if (n && getNodeText(n) === name)
|
|
1769
|
+
return typeOf(decl.childForFieldName('type'), d.childForFieldName('value'));
|
|
1770
|
+
}
|
|
1771
|
+
return undefined;
|
|
1772
|
+
};
|
|
1773
|
+
const fromParameters = (params) => {
|
|
1774
|
+
for (const p of params.namedChildren) {
|
|
1775
|
+
if (!p)
|
|
1776
|
+
continue;
|
|
1777
|
+
if (p.type === 'formal_parameter') {
|
|
1778
|
+
const n = p.childForFieldName('name');
|
|
1779
|
+
if (n && getNodeText(n) === name)
|
|
1780
|
+
return typeOf(p.childForFieldName('type'), null);
|
|
1781
|
+
}
|
|
1782
|
+
else if (p.type === 'spread_parameter') {
|
|
1783
|
+
const d = p.namedChildren.find(c => c?.type === 'variable_declarator');
|
|
1784
|
+
const n = d?.childForFieldName('name');
|
|
1785
|
+
const t = p.namedChildren.find(c => c !== null && /type/.test(c.type));
|
|
1786
|
+
if (n && getNodeText(n) === name)
|
|
1787
|
+
return t ? `${getNodeText(t)}[]` : null;
|
|
1788
|
+
}
|
|
1789
|
+
else if (p.type === 'identifier' && getNodeText(p) === name) {
|
|
1790
|
+
return null; // an inferred lambda parameter
|
|
1791
|
+
}
|
|
1792
|
+
}
|
|
1793
|
+
return undefined;
|
|
1794
|
+
};
|
|
1795
|
+
let child = node;
|
|
1796
|
+
for (let scope = node.parent; scope; child = scope, scope = scope.parent) {
|
|
1797
|
+
let found;
|
|
1798
|
+
switch (scope.type) {
|
|
1799
|
+
case 'block':
|
|
1800
|
+
case 'constructor_body':
|
|
1801
|
+
case 'switch_block_statement_group':
|
|
1802
|
+
for (const stmt of scope.namedChildren) {
|
|
1803
|
+
if (!stmt || stmt.startIndex >= child.startIndex)
|
|
1804
|
+
break;
|
|
1805
|
+
if (stmt.type === 'local_variable_declaration')
|
|
1806
|
+
found = fromDeclaration(stmt) ?? found;
|
|
1807
|
+
}
|
|
1808
|
+
break;
|
|
1809
|
+
case 'for_statement':
|
|
1810
|
+
for (const init of scope.childrenForFieldName('init')) {
|
|
1811
|
+
if (init?.type === 'local_variable_declaration')
|
|
1812
|
+
found = fromDeclaration(init) ?? found;
|
|
1813
|
+
}
|
|
1814
|
+
break;
|
|
1815
|
+
case 'enhanced_for_statement': {
|
|
1816
|
+
const n = scope.childForFieldName('name');
|
|
1817
|
+
if (n && getNodeText(n) === name)
|
|
1818
|
+
found = typeOf(scope.childForFieldName('type'), null);
|
|
1819
|
+
break;
|
|
1820
|
+
}
|
|
1821
|
+
case 'catch_clause': {
|
|
1822
|
+
const p = scope.namedChildren.find(c => c?.type === 'catch_formal_parameter');
|
|
1823
|
+
const n = p?.childForFieldName('name');
|
|
1824
|
+
if (p && n && getNodeText(n) === name) {
|
|
1825
|
+
const ct = p.namedChildren.find(c => c?.type === 'catch_type');
|
|
1826
|
+
// A multi-catch has no single static type that names a class here.
|
|
1827
|
+
found = ct && ct.namedChildCount === 1 ? getNodeText(ct) : null;
|
|
1828
|
+
}
|
|
1829
|
+
break;
|
|
1830
|
+
}
|
|
1831
|
+
case 'try_with_resources_statement': {
|
|
1832
|
+
const spec = scope.childForFieldName('resources');
|
|
1833
|
+
for (const r of spec?.namedChildren ?? []) {
|
|
1834
|
+
const n = r?.childForFieldName('name');
|
|
1835
|
+
if (r && n && getNodeText(n) === name)
|
|
1836
|
+
found = typeOf(r.childForFieldName('type'), r.childForFieldName('value'));
|
|
1837
|
+
}
|
|
1838
|
+
break;
|
|
1839
|
+
}
|
|
1840
|
+
case 'lambda_expression': {
|
|
1841
|
+
const params = scope.childForFieldName('parameters');
|
|
1842
|
+
if (params?.type === 'identifier')
|
|
1843
|
+
found = getNodeText(params) === name ? forEachElementType(scope) : undefined;
|
|
1844
|
+
else if (params?.type === 'inferred_parameters') {
|
|
1845
|
+
found = params.namedChildren.some(c => c && getNodeText(c) === name) ? null : undefined;
|
|
1846
|
+
}
|
|
1847
|
+
else if (params)
|
|
1848
|
+
found = fromParameters(params);
|
|
1849
|
+
break;
|
|
1850
|
+
}
|
|
1851
|
+
case 'method_declaration':
|
|
1852
|
+
case 'constructor_declaration':
|
|
1853
|
+
case 'record_declaration': {
|
|
1854
|
+
const params = scope.childForFieldName('parameters');
|
|
1855
|
+
if (params)
|
|
1856
|
+
found = fromParameters(params);
|
|
1857
|
+
break;
|
|
1858
|
+
}
|
|
1859
|
+
case 'class_body':
|
|
1860
|
+
case 'interface_body':
|
|
1861
|
+
case 'enum_body_declarations': {
|
|
1862
|
+
const t = fieldTypeIn(scope, name);
|
|
1863
|
+
if (t !== null)
|
|
1864
|
+
found = t;
|
|
1865
|
+
break;
|
|
1866
|
+
}
|
|
1867
|
+
default:
|
|
1868
|
+
break;
|
|
1869
|
+
}
|
|
1870
|
+
if (found !== undefined)
|
|
1871
|
+
return found;
|
|
1872
|
+
}
|
|
1873
|
+
return undefined;
|
|
1874
|
+
}
|
|
1875
|
+
const ITERABLE_TYPES = new Set([
|
|
1876
|
+
'Iterable', 'Collection', 'List', 'Set', 'SortedSet', 'NavigableSet', 'Queue', 'Deque',
|
|
1877
|
+
'ArrayList', 'LinkedList', 'HashSet', 'LinkedHashSet', 'TreeSet', 'ArrayDeque',
|
|
1878
|
+
]);
|
|
1879
|
+
/**
|
|
1880
|
+
* The parameter type of `xs.forEach(x -> …)`, where `xs` is declared as a JDK
|
|
1881
|
+
* collection of one element type: `List<Loan> xs` gives `Loan`. Anything
|
|
1882
|
+
* else — another method, a wildcard, a map, an untyped receiver — is `null`.
|
|
1883
|
+
*/
|
|
1884
|
+
function forEachElementType(lambda) {
|
|
1885
|
+
const args = lambda.parent;
|
|
1886
|
+
const inv = args?.parent;
|
|
1887
|
+
if (args?.type !== 'argument_list' || inv?.type !== 'method_invocation' || args.namedChildCount !== 1)
|
|
1888
|
+
return null;
|
|
1889
|
+
const name = inv.childForFieldName('name');
|
|
1890
|
+
const obj = inv.childForFieldName('object');
|
|
1891
|
+
if (!name || getNodeText(name) !== 'forEach' || obj?.type !== 'identifier')
|
|
1892
|
+
return null;
|
|
1893
|
+
const declared = declaredTypeInScope(obj, getNodeText(obj));
|
|
1894
|
+
const m = declared ? /^(?:java\.util\.|java\.lang\.)?(\w+)\s*<\s*([\w.]+)\s*>$/.exec(declared.trim()) : null;
|
|
1895
|
+
return m && ITERABLE_TYPES.has(m[1]) ? m[2] : null;
|
|
1896
|
+
}
|
|
1897
|
+
/** The written type of field `name` declared directly in a type body, or `null`. */
|
|
1898
|
+
function fieldTypeIn(body, name) {
|
|
1899
|
+
for (const f of body.namedChildren) {
|
|
1900
|
+
if (f?.type !== 'field_declaration' && f?.type !== 'constant_declaration')
|
|
1901
|
+
continue;
|
|
1902
|
+
for (const d of f.namedChildren) {
|
|
1903
|
+
if (d?.type !== 'variable_declarator')
|
|
1904
|
+
continue;
|
|
1905
|
+
const n = d.childForFieldName('name');
|
|
1906
|
+
if (n && getNodeText(n) === name) {
|
|
1907
|
+
const t = f.childForFieldName('type');
|
|
1908
|
+
return t ? getNodeText(t) : null;
|
|
1909
|
+
}
|
|
1910
|
+
}
|
|
1911
|
+
}
|
|
1912
|
+
return null;
|
|
1913
|
+
}
|
|
1604
1914
|
/**
|
|
1605
1915
|
* Analyze an argument to extract variable name or literal value.
|
|
1606
1916
|
*/
|