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.
@@ -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
- imports: /* @__PURE__ */ new Set()
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
- const parts2 = match[1].split(".");
6086
- context.imports.add(parts2[parts2.length - 1]);
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;AAkEvG;;;;;GAKG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,IAAI,EAAE,KAAK,CAAC,EAAE,SAAS,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,QAAQ,EAAE,CAgDzF"}
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
- imports: new Set(),
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
- // Extract class name from import (last part)
558
- const match = text.match(/import\s+(?:static\s+)?([a-zA-Z0-9_.]+)/);
559
- if (match) {
560
- const parts = match[1].split('.');
561
- context.imports.add(parts[parts.length - 1]);
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,