circle-ir 3.42.0 → 3.44.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/dist/analysis/taint-matcher.d.ts.map +1 -1
- package/dist/analysis/taint-matcher.js +47 -0
- package/dist/analysis/taint-matcher.js.map +1 -1
- package/dist/browser/circle-ir.js +144 -5
- package/dist/core/circle-ir-core.cjs +144 -5
- package/dist/core/circle-ir-core.js +144 -5
- package/dist/core/extractors/calls.d.ts.map +1 -1
- package/dist/core/extractors/calls.js +161 -8
- package/dist/core/extractors/calls.js.map +1 -1
- package/dist/types/index.d.ts +13 -0
- package/dist/types/index.d.ts.map +1 -1
- package/package.json +1 -1
|
@@ -6030,11 +6030,22 @@ function inferJSTypeFromReceiver(receiver) {
|
|
|
6030
6030
|
function buildResolutionContext(tree, cache) {
|
|
6031
6031
|
const context = {
|
|
6032
6032
|
className: null,
|
|
6033
|
+
packageName: null,
|
|
6033
6034
|
methodNames: /* @__PURE__ */ new Set(),
|
|
6034
6035
|
fieldTypes: /* @__PURE__ */ new Map(),
|
|
6035
6036
|
localVarTypes: /* @__PURE__ */ new Map(),
|
|
6036
|
-
|
|
6037
|
+
paramTypes: /* @__PURE__ */ new Map(),
|
|
6038
|
+
imports: /* @__PURE__ */ new Map(),
|
|
6039
|
+
wildcardImports: []
|
|
6037
6040
|
};
|
|
6041
|
+
const packages = getNodesFromCache(tree.rootNode, "package_declaration", cache);
|
|
6042
|
+
if (packages.length > 0) {
|
|
6043
|
+
const text = getNodeText(packages[0]);
|
|
6044
|
+
const match = text.match(/package\s+([a-zA-Z0-9_.]+)/);
|
|
6045
|
+
if (match) {
|
|
6046
|
+
context.packageName = match[1];
|
|
6047
|
+
}
|
|
6048
|
+
}
|
|
6038
6049
|
const classes = getNodesFromCache(tree.rootNode, "class_declaration", cache);
|
|
6039
6050
|
if (classes.length > 0) {
|
|
6040
6051
|
const nameNode = classes[0].childForFieldName("name");
|
|
@@ -6048,6 +6059,17 @@ function buildResolutionContext(tree, cache) {
|
|
|
6048
6059
|
if (nameNode) {
|
|
6049
6060
|
context.methodNames.add(getNodeText(nameNode));
|
|
6050
6061
|
}
|
|
6062
|
+
const paramsNode = method.childForFieldName("parameters");
|
|
6063
|
+
if (paramsNode) {
|
|
6064
|
+
collectParameterTypes(paramsNode, context.paramTypes);
|
|
6065
|
+
}
|
|
6066
|
+
}
|
|
6067
|
+
const constructors = getNodesFromCache(tree.rootNode, "constructor_declaration", cache);
|
|
6068
|
+
for (const ctor of constructors) {
|
|
6069
|
+
const paramsNode = ctor.childForFieldName("parameters");
|
|
6070
|
+
if (paramsNode) {
|
|
6071
|
+
collectParameterTypes(paramsNode, context.paramTypes);
|
|
6072
|
+
}
|
|
6051
6073
|
}
|
|
6052
6074
|
const fields = getNodesFromCache(tree.rootNode, "field_declaration", cache);
|
|
6053
6075
|
for (const field of fields) {
|
|
@@ -6080,14 +6102,106 @@ function buildResolutionContext(tree, cache) {
|
|
|
6080
6102
|
const imports = getNodesFromCache(tree.rootNode, "import_declaration", cache);
|
|
6081
6103
|
for (const imp of imports) {
|
|
6082
6104
|
const text = getNodeText(imp);
|
|
6083
|
-
const match = text.match(/import\s+(?:static\s+)?([a-zA-Z0-9_.]+)
|
|
6084
|
-
if (match)
|
|
6085
|
-
|
|
6086
|
-
|
|
6105
|
+
const match = text.match(/import\s+(?:static\s+)?([a-zA-Z0-9_.]+)(\.\*)?/);
|
|
6106
|
+
if (!match) continue;
|
|
6107
|
+
const fqn = match[1];
|
|
6108
|
+
const isWildcard = match[2] === ".*";
|
|
6109
|
+
if (isWildcard) {
|
|
6110
|
+
context.wildcardImports.push(fqn);
|
|
6111
|
+
} else {
|
|
6112
|
+
const parts2 = fqn.split(".");
|
|
6113
|
+
const simple = parts2[parts2.length - 1];
|
|
6114
|
+
context.imports.set(simple, fqn);
|
|
6087
6115
|
}
|
|
6088
6116
|
}
|
|
6089
6117
|
return context;
|
|
6090
6118
|
}
|
|
6119
|
+
function collectParameterTypes(paramsNode, out2) {
|
|
6120
|
+
for (let i2 = 0; i2 < paramsNode.childCount; i2++) {
|
|
6121
|
+
const child = paramsNode.child(i2);
|
|
6122
|
+
if (!child) continue;
|
|
6123
|
+
if (child.type !== "formal_parameter" && child.type !== "spread_parameter") {
|
|
6124
|
+
continue;
|
|
6125
|
+
}
|
|
6126
|
+
const typeNode = child.childForFieldName("type");
|
|
6127
|
+
const nameNode = child.childForFieldName("name");
|
|
6128
|
+
if (typeNode && nameNode) {
|
|
6129
|
+
out2.set(getNodeText(nameNode), getNodeText(typeNode));
|
|
6130
|
+
}
|
|
6131
|
+
}
|
|
6132
|
+
}
|
|
6133
|
+
function stripGenerics(type) {
|
|
6134
|
+
const ltIdx = type.indexOf("<");
|
|
6135
|
+
return ltIdx === -1 ? type : type.substring(0, ltIdx);
|
|
6136
|
+
}
|
|
6137
|
+
function resolveReceiverType(receiver, context) {
|
|
6138
|
+
if (!receiver) return { simpleName: null, fqn: null };
|
|
6139
|
+
if (receiver === "this") {
|
|
6140
|
+
return resolveFqn(context.className, context);
|
|
6141
|
+
}
|
|
6142
|
+
if (receiver.startsWith("this.")) {
|
|
6143
|
+
const fieldName = receiver.substring("this.".length);
|
|
6144
|
+
const fieldType = context.fieldTypes.get(fieldName);
|
|
6145
|
+
if (fieldType) return resolveFqn(stripGenerics(fieldType), context);
|
|
6146
|
+
return { simpleName: null, fqn: null };
|
|
6147
|
+
}
|
|
6148
|
+
if (receiver === "super") return { simpleName: null, fqn: null };
|
|
6149
|
+
const declaredType = context.localVarTypes.get(receiver) ?? context.paramTypes.get(receiver) ?? context.fieldTypes.get(receiver);
|
|
6150
|
+
if (declaredType) {
|
|
6151
|
+
return resolveFqn(stripGenerics(declaredType), context);
|
|
6152
|
+
}
|
|
6153
|
+
if (/^[A-Z]/.test(receiver)) {
|
|
6154
|
+
const simple = receiver.includes(".") ? receiver.substring(receiver.lastIndexOf(".") + 1) : receiver;
|
|
6155
|
+
if (/^[A-Z][A-Za-z0-9_]*$/.test(simple)) {
|
|
6156
|
+
return resolveFqn(simple, context);
|
|
6157
|
+
}
|
|
6158
|
+
}
|
|
6159
|
+
return { simpleName: null, fqn: null };
|
|
6160
|
+
}
|
|
6161
|
+
function resolveFqn(simpleName, context) {
|
|
6162
|
+
if (!simpleName) return { simpleName: null, fqn: null };
|
|
6163
|
+
const importedFqn = context.imports.get(simpleName);
|
|
6164
|
+
if (importedFqn) {
|
|
6165
|
+
return { simpleName, fqn: importedFqn };
|
|
6166
|
+
}
|
|
6167
|
+
if (context.className === simpleName && context.packageName) {
|
|
6168
|
+
return { simpleName, fqn: `${context.packageName}.${simpleName}` };
|
|
6169
|
+
}
|
|
6170
|
+
if (JAVA_LANG_TYPES.has(simpleName)) {
|
|
6171
|
+
return { simpleName, fqn: `java.lang.${simpleName}` };
|
|
6172
|
+
}
|
|
6173
|
+
return { simpleName, fqn: null };
|
|
6174
|
+
}
|
|
6175
|
+
var JAVA_LANG_TYPES = /* @__PURE__ */ new Set([
|
|
6176
|
+
"String",
|
|
6177
|
+
"StringBuilder",
|
|
6178
|
+
"StringBuffer",
|
|
6179
|
+
"Object",
|
|
6180
|
+
"Class",
|
|
6181
|
+
"Integer",
|
|
6182
|
+
"Long",
|
|
6183
|
+
"Double",
|
|
6184
|
+
"Float",
|
|
6185
|
+
"Boolean",
|
|
6186
|
+
"Character",
|
|
6187
|
+
"Byte",
|
|
6188
|
+
"Short",
|
|
6189
|
+
"Number",
|
|
6190
|
+
"Math",
|
|
6191
|
+
"System",
|
|
6192
|
+
"Thread",
|
|
6193
|
+
"Runnable",
|
|
6194
|
+
"Throwable",
|
|
6195
|
+
"Exception",
|
|
6196
|
+
"RuntimeException",
|
|
6197
|
+
"Error",
|
|
6198
|
+
"Process",
|
|
6199
|
+
"ProcessBuilder",
|
|
6200
|
+
"Iterable",
|
|
6201
|
+
"Comparable",
|
|
6202
|
+
"CharSequence",
|
|
6203
|
+
"Enum"
|
|
6204
|
+
]);
|
|
6091
6205
|
function extractCallInfo(node, context) {
|
|
6092
6206
|
const nameNode = node.childForFieldName("name");
|
|
6093
6207
|
const methodName = nameNode ? getNodeText(nameNode) : "unknown";
|
|
@@ -6097,9 +6211,12 @@ function extractCallInfo(node, context) {
|
|
|
6097
6211
|
const args2 = argsNode ? extractArguments(argsNode) : [];
|
|
6098
6212
|
const enclosingMethod = findEnclosingMethod(node);
|
|
6099
6213
|
const { resolved, resolution } = resolveMethodCall(methodName, receiver, context);
|
|
6214
|
+
const { simpleName, fqn } = resolveReceiverType(receiver, context);
|
|
6100
6215
|
return {
|
|
6101
6216
|
method_name: methodName,
|
|
6102
6217
|
receiver,
|
|
6218
|
+
receiver_type: simpleName,
|
|
6219
|
+
receiver_type_fqn: fqn,
|
|
6103
6220
|
arguments: args2,
|
|
6104
6221
|
location: {
|
|
6105
6222
|
line: node.startPosition.row + 1,
|
|
@@ -6121,10 +6238,14 @@ function extractObjectCreation(node, context) {
|
|
|
6121
6238
|
status: "resolved",
|
|
6122
6239
|
target: `${typeName}.<init>`
|
|
6123
6240
|
};
|
|
6241
|
+
const simpleType = stripGenerics(typeName);
|
|
6242
|
+
const { simpleName, fqn } = resolveFqn(simpleType, context);
|
|
6124
6243
|
return {
|
|
6125
6244
|
method_name: typeName,
|
|
6126
6245
|
// Constructor name is the class name
|
|
6127
6246
|
receiver: null,
|
|
6247
|
+
receiver_type: simpleName,
|
|
6248
|
+
receiver_type_fqn: fqn,
|
|
6128
6249
|
arguments: args2,
|
|
6129
6250
|
location: {
|
|
6130
6251
|
line: node.startPosition.row + 1,
|
|
@@ -11921,6 +12042,14 @@ function isKnownSafeReceiverForMethod(receiver, method, sinkType) {
|
|
|
11921
12042
|
}
|
|
11922
12043
|
return false;
|
|
11923
12044
|
}
|
|
12045
|
+
var SINK_FQN_EXCLUSIONS = {
|
|
12046
|
+
sql_injection: [
|
|
12047
|
+
// JSqlParser AST library: Statement.execute(StatementVisitor),
|
|
12048
|
+
// Select.accept(SelectVisitor), Insert/Update/Delete.execute(...),
|
|
12049
|
+
// Expression.accept(ExpressionVisitor), etc.
|
|
12050
|
+
"net.sf.jsqlparser."
|
|
12051
|
+
]
|
|
12052
|
+
};
|
|
11924
12053
|
function matchesSinkPattern(call, pattern, typeHierarchy, language) {
|
|
11925
12054
|
if (pattern.languages && pattern.languages.length > 0 && language !== void 0) {
|
|
11926
12055
|
if (!pattern.languages.includes(language)) {
|
|
@@ -11937,6 +12066,16 @@ function matchesSinkPattern(call, pattern, typeHierarchy, language) {
|
|
|
11937
12066
|
if (!methodMatches) {
|
|
11938
12067
|
return false;
|
|
11939
12068
|
}
|
|
12069
|
+
if (call.receiver_type_fqn) {
|
|
12070
|
+
const exclusions = SINK_FQN_EXCLUSIONS[pattern.type];
|
|
12071
|
+
if (exclusions) {
|
|
12072
|
+
for (const prefix of exclusions) {
|
|
12073
|
+
if (call.receiver_type_fqn.startsWith(prefix)) {
|
|
12074
|
+
return false;
|
|
12075
|
+
}
|
|
12076
|
+
}
|
|
12077
|
+
}
|
|
12078
|
+
}
|
|
11940
12079
|
if (pattern.class) {
|
|
11941
12080
|
if (pattern.class === "constructor") {
|
|
11942
12081
|
return true;
|
|
@@ -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;
|
|
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,CAAC,IAAI,EAAE,IAAI,EAAE,KAAK,CAAC,EAAE,SAAS,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,QAAQ,EAAE,CAgDzF"}
|
|
@@ -499,11 +499,23 @@ function inferJSTypeFromReceiver(receiver) {
|
|
|
499
499
|
function buildResolutionContext(tree, cache) {
|
|
500
500
|
const context = {
|
|
501
501
|
className: null,
|
|
502
|
+
packageName: null,
|
|
502
503
|
methodNames: new Set(),
|
|
503
504
|
fieldTypes: new Map(),
|
|
504
505
|
localVarTypes: new Map(),
|
|
505
|
-
|
|
506
|
+
paramTypes: new Map(),
|
|
507
|
+
imports: new Map(),
|
|
508
|
+
wildcardImports: [],
|
|
506
509
|
};
|
|
510
|
+
// Find package declaration (java grammar: `package_declaration`)
|
|
511
|
+
const packages = getNodesFromCache(tree.rootNode, 'package_declaration', cache);
|
|
512
|
+
if (packages.length > 0) {
|
|
513
|
+
const text = getNodeText(packages[0]);
|
|
514
|
+
const match = text.match(/package\s+([a-zA-Z0-9_.]+)/);
|
|
515
|
+
if (match) {
|
|
516
|
+
context.packageName = match[1];
|
|
517
|
+
}
|
|
518
|
+
}
|
|
507
519
|
// Find class name
|
|
508
520
|
const classes = getNodesFromCache(tree.rootNode, 'class_declaration', cache);
|
|
509
521
|
if (classes.length > 0) {
|
|
@@ -512,13 +524,25 @@ function buildResolutionContext(tree, cache) {
|
|
|
512
524
|
context.className = getNodeText(nameNode);
|
|
513
525
|
}
|
|
514
526
|
}
|
|
515
|
-
// Collect method names from the class
|
|
527
|
+
// Collect method names + parameter types from the class
|
|
516
528
|
const methods = getNodesFromCache(tree.rootNode, 'method_declaration', cache);
|
|
517
529
|
for (const method of methods) {
|
|
518
530
|
const nameNode = method.childForFieldName('name');
|
|
519
531
|
if (nameNode) {
|
|
520
532
|
context.methodNames.add(getNodeText(nameNode));
|
|
521
533
|
}
|
|
534
|
+
const paramsNode = method.childForFieldName('parameters');
|
|
535
|
+
if (paramsNode) {
|
|
536
|
+
collectParameterTypes(paramsNode, context.paramTypes);
|
|
537
|
+
}
|
|
538
|
+
}
|
|
539
|
+
// Also collect parameters from constructors
|
|
540
|
+
const constructors = getNodesFromCache(tree.rootNode, 'constructor_declaration', cache);
|
|
541
|
+
for (const ctor of constructors) {
|
|
542
|
+
const paramsNode = ctor.childForFieldName('parameters');
|
|
543
|
+
if (paramsNode) {
|
|
544
|
+
collectParameterTypes(paramsNode, context.paramTypes);
|
|
545
|
+
}
|
|
522
546
|
}
|
|
523
547
|
// Collect field types
|
|
524
548
|
const fields = getNodesFromCache(tree.rootNode, 'field_declaration', cache);
|
|
@@ -550,19 +574,137 @@ function buildResolutionContext(tree, cache) {
|
|
|
550
574
|
}
|
|
551
575
|
}
|
|
552
576
|
}
|
|
553
|
-
// Collect imports
|
|
577
|
+
// Collect imports — map simple class name to FQN
|
|
554
578
|
const imports = getNodesFromCache(tree.rootNode, 'import_declaration', cache);
|
|
555
579
|
for (const imp of imports) {
|
|
556
580
|
const text = getNodeText(imp);
|
|
557
|
-
//
|
|
558
|
-
const match = text.match(/import\s+(?:static\s+)?([a-zA-Z0-9_.]+)
|
|
559
|
-
if (match)
|
|
560
|
-
|
|
561
|
-
|
|
581
|
+
// Match `import [static] some.qualified.Name;` (with optional trailing `.*`)
|
|
582
|
+
const match = text.match(/import\s+(?:static\s+)?([a-zA-Z0-9_.]+)(\.\*)?/);
|
|
583
|
+
if (!match)
|
|
584
|
+
continue;
|
|
585
|
+
const fqn = match[1];
|
|
586
|
+
const isWildcard = match[2] === '.*';
|
|
587
|
+
if (isWildcard) {
|
|
588
|
+
context.wildcardImports.push(fqn);
|
|
589
|
+
}
|
|
590
|
+
else {
|
|
591
|
+
const parts = fqn.split('.');
|
|
592
|
+
const simple = parts[parts.length - 1];
|
|
593
|
+
context.imports.set(simple, fqn);
|
|
562
594
|
}
|
|
563
595
|
}
|
|
564
596
|
return context;
|
|
565
597
|
}
|
|
598
|
+
/**
|
|
599
|
+
* Collect `paramName → typeName` from a `formal_parameters` node.
|
|
600
|
+
* Handles formal_parameter and spread_parameter; receiver_parameter (Java
|
|
601
|
+
* inner-class `this` reference) is skipped because it has no `name`.
|
|
602
|
+
*/
|
|
603
|
+
function collectParameterTypes(paramsNode, out) {
|
|
604
|
+
for (let i = 0; i < paramsNode.childCount; i++) {
|
|
605
|
+
const child = paramsNode.child(i);
|
|
606
|
+
if (!child)
|
|
607
|
+
continue;
|
|
608
|
+
if (child.type !== 'formal_parameter' && child.type !== 'spread_parameter') {
|
|
609
|
+
continue;
|
|
610
|
+
}
|
|
611
|
+
const typeNode = child.childForFieldName('type');
|
|
612
|
+
const nameNode = child.childForFieldName('name');
|
|
613
|
+
if (typeNode && nameNode) {
|
|
614
|
+
out.set(getNodeText(nameNode), getNodeText(typeNode));
|
|
615
|
+
}
|
|
616
|
+
}
|
|
617
|
+
}
|
|
618
|
+
/**
|
|
619
|
+
* Strip generic parameters from a Java type expression.
|
|
620
|
+
* `List<String>` → `List`, `Map<String, User>` → `Map`, `User` → `User`.
|
|
621
|
+
*/
|
|
622
|
+
function stripGenerics(type) {
|
|
623
|
+
const ltIdx = type.indexOf('<');
|
|
624
|
+
return ltIdx === -1 ? type : type.substring(0, ltIdx);
|
|
625
|
+
}
|
|
626
|
+
/**
|
|
627
|
+
* Resolve the receiver expression to its declared simple type and (if
|
|
628
|
+
* available) fully-qualified name. Returns `{ simpleName: null, fqn: null }`
|
|
629
|
+
* for receivers that cannot be statically resolved — dynamic dispatch,
|
|
630
|
+
* complex chained expressions, and missing declarations all fall back to
|
|
631
|
+
* the conservative null result so downstream consumers can choose their
|
|
632
|
+
* own heuristic (substring matching, hierarchy walk, etc.).
|
|
633
|
+
*/
|
|
634
|
+
function resolveReceiverType(receiver, context) {
|
|
635
|
+
if (!receiver)
|
|
636
|
+
return { simpleName: null, fqn: null };
|
|
637
|
+
// `this` and `this.field` — current class methods/fields
|
|
638
|
+
if (receiver === 'this') {
|
|
639
|
+
return resolveFqn(context.className, context);
|
|
640
|
+
}
|
|
641
|
+
if (receiver.startsWith('this.')) {
|
|
642
|
+
const fieldName = receiver.substring('this.'.length);
|
|
643
|
+
const fieldType = context.fieldTypes.get(fieldName);
|
|
644
|
+
if (fieldType)
|
|
645
|
+
return resolveFqn(stripGenerics(fieldType), context);
|
|
646
|
+
return { simpleName: null, fqn: null };
|
|
647
|
+
}
|
|
648
|
+
// `super` — defer; cannot determine parent class without hierarchy
|
|
649
|
+
if (receiver === 'super')
|
|
650
|
+
return { simpleName: null, fqn: null };
|
|
651
|
+
// Local variable, parameter, or field declared in this file
|
|
652
|
+
const declaredType = context.localVarTypes.get(receiver) ??
|
|
653
|
+
context.paramTypes.get(receiver) ??
|
|
654
|
+
context.fieldTypes.get(receiver);
|
|
655
|
+
if (declaredType) {
|
|
656
|
+
return resolveFqn(stripGenerics(declaredType), context);
|
|
657
|
+
}
|
|
658
|
+
// Receiver starts with uppercase letter — likely a static class reference.
|
|
659
|
+
// Strip dotted prefix (`com.example.Foo` → `Foo`) and look up imports.
|
|
660
|
+
if (/^[A-Z]/.test(receiver)) {
|
|
661
|
+
const simple = receiver.includes('.')
|
|
662
|
+
? receiver.substring(receiver.lastIndexOf('.') + 1)
|
|
663
|
+
: receiver;
|
|
664
|
+
if (/^[A-Z][A-Za-z0-9_]*$/.test(simple)) {
|
|
665
|
+
return resolveFqn(simple, context);
|
|
666
|
+
}
|
|
667
|
+
}
|
|
668
|
+
return { simpleName: null, fqn: null };
|
|
669
|
+
}
|
|
670
|
+
/**
|
|
671
|
+
* Look up the FQN of a simple type name in the file's imports map.
|
|
672
|
+
* Falls back to same-package resolution when the type was declared in
|
|
673
|
+
* the file's package, or `null` when only wildcards / external types
|
|
674
|
+
* exist (the simple name is still surfaced).
|
|
675
|
+
*/
|
|
676
|
+
function resolveFqn(simpleName, context) {
|
|
677
|
+
if (!simpleName)
|
|
678
|
+
return { simpleName: null, fqn: null };
|
|
679
|
+
// Explicit import wins
|
|
680
|
+
const importedFqn = context.imports.get(simpleName);
|
|
681
|
+
if (importedFqn) {
|
|
682
|
+
return { simpleName, fqn: importedFqn };
|
|
683
|
+
}
|
|
684
|
+
// Type declared in this file → use the file's package
|
|
685
|
+
if (context.className === simpleName && context.packageName) {
|
|
686
|
+
return { simpleName, fqn: `${context.packageName}.${simpleName}` };
|
|
687
|
+
}
|
|
688
|
+
// java.lang.* is implicitly imported
|
|
689
|
+
if (JAVA_LANG_TYPES.has(simpleName)) {
|
|
690
|
+
return { simpleName, fqn: `java.lang.${simpleName}` };
|
|
691
|
+
}
|
|
692
|
+
// Wildcard imports — cannot disambiguate, return simple name only
|
|
693
|
+
return { simpleName, fqn: null };
|
|
694
|
+
}
|
|
695
|
+
/**
|
|
696
|
+
* Common types from `java.lang` that are implicitly imported in every
|
|
697
|
+
* compilation unit. Not exhaustive; kept to the subset most likely to
|
|
698
|
+
* appear as call receivers (String, Object, Class, Thread, …) so we
|
|
699
|
+
* can answer the FQN for the common cases without false-positive risk.
|
|
700
|
+
*/
|
|
701
|
+
const JAVA_LANG_TYPES = new Set([
|
|
702
|
+
'String', 'StringBuilder', 'StringBuffer', 'Object', 'Class',
|
|
703
|
+
'Integer', 'Long', 'Double', 'Float', 'Boolean', 'Character', 'Byte', 'Short',
|
|
704
|
+
'Number', 'Math', 'System', 'Thread', 'Runnable', 'Throwable', 'Exception',
|
|
705
|
+
'RuntimeException', 'Error', 'Process', 'ProcessBuilder',
|
|
706
|
+
'Iterable', 'Comparable', 'CharSequence', 'Enum',
|
|
707
|
+
]);
|
|
566
708
|
/**
|
|
567
709
|
* Extract call information from a method_invocation node.
|
|
568
710
|
*/
|
|
@@ -580,9 +722,13 @@ function extractCallInfo(node, context) {
|
|
|
580
722
|
const enclosingMethod = findEnclosingMethod(node);
|
|
581
723
|
// Resolve the call
|
|
582
724
|
const { resolved, resolution } = resolveMethodCall(methodName, receiver, context);
|
|
725
|
+
// Resolve the receiver's declared type and FQN
|
|
726
|
+
const { simpleName, fqn } = resolveReceiverType(receiver, context);
|
|
583
727
|
return {
|
|
584
728
|
method_name: methodName,
|
|
585
729
|
receiver,
|
|
730
|
+
receiver_type: simpleName,
|
|
731
|
+
receiver_type_fqn: fqn,
|
|
586
732
|
arguments: args,
|
|
587
733
|
location: {
|
|
588
734
|
line: node.startPosition.row + 1,
|
|
@@ -612,9 +758,16 @@ function extractObjectCreation(node, context) {
|
|
|
612
758
|
status: 'resolved',
|
|
613
759
|
target: `${typeName}.<init>`,
|
|
614
760
|
};
|
|
761
|
+
// For a constructor `new Foo(...)`, the call site already names the class —
|
|
762
|
+
// surface that as receiver_type/receiver_type_fqn so downstream consumers
|
|
763
|
+
// see consistent type info regardless of dispatch shape.
|
|
764
|
+
const simpleType = stripGenerics(typeName);
|
|
765
|
+
const { simpleName, fqn } = resolveFqn(simpleType, context);
|
|
615
766
|
return {
|
|
616
767
|
method_name: typeName, // Constructor name is the class name
|
|
617
768
|
receiver: null,
|
|
769
|
+
receiver_type: simpleName,
|
|
770
|
+
receiver_type_fqn: fqn,
|
|
618
771
|
arguments: args,
|
|
619
772
|
location: {
|
|
620
773
|
line: node.startPosition.row + 1,
|