fix(cpp): resolve callers for typed pointer method calls (#445)
Resolves typed member-pointer method calls like `m_cpAlg->Processing()` so `codegraph callers CDetect::Processing` returns the expected callers.
- Extract C/C++ `field_expression` member calls as receiver-qualified references, so `ptr->method()` is preserved as a receiver-aware reference.
- Surface out-of-line C++ method definitions (`int CDetect::Processing() {...}` in `.cpp` with class in `.hpp`) as proper method nodes with the correct qualified identity.
- C++ receiver-type inference: declarator regex requires a terminator after the receiver (rules out matching `return m_cpAlg->...`), handles `Type*x`/`Type *x`/`Type* x` uniformly, and rejects C++ keywords as a final guard.
- `resolveMethodOnType` matches by `Class::method` qualified-name suffix, so out-of-line definitions across files resolve (typical `.hpp`/`.cpp` split).
Validated on bitcoin-core (1306 .cpp files): 38,180 → 40,503 cpp method incoming-call edges (+6.1%), deterministic across re-indexes. Regression test added for the ambiguous-name + `return ptr->m()` / `Type x = ptr->m()` patterns.
Closes #445
Co-authored-by: chenyuxuan <458254969@qq.com>
Co-authored-by: Colby McHenry <me@colbymchenry.com>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
chenyuxuan
Colby McHenry
Claude Opus 4.7
parent
72c08c2bef
commit
c0cf9c1e7d
@@ -2,6 +2,51 @@ import type { Node as SyntaxNode } from 'web-tree-sitter';
|
||||
import { getChildByField, getNodeText } from '../tree-sitter-helpers';
|
||||
import type { LanguageExtractor } from '../tree-sitter-types';
|
||||
|
||||
function extractCppQualifiedMethodName(node: SyntaxNode, source: string): string | undefined {
|
||||
const declarator = getChildByField(node, 'declarator');
|
||||
if (!declarator) return undefined;
|
||||
|
||||
const queue: SyntaxNode[] = [declarator];
|
||||
while (queue.length > 0) {
|
||||
const current = queue.shift()!;
|
||||
if (current.type === 'qualified_identifier') {
|
||||
const text = getNodeText(current, source).trim();
|
||||
const parts = text.split('::').filter(Boolean);
|
||||
return parts[parts.length - 1];
|
||||
}
|
||||
for (let i = 0; i < current.namedChildCount; i++) {
|
||||
const child = current.namedChild(i);
|
||||
if (child) queue.push(child);
|
||||
}
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
function extractCppReceiverType(node: SyntaxNode, source: string): string | undefined {
|
||||
const declarator = getChildByField(node, 'declarator');
|
||||
if (!declarator) return undefined;
|
||||
|
||||
const queue: SyntaxNode[] = [declarator];
|
||||
while (queue.length > 0) {
|
||||
const current = queue.shift()!;
|
||||
if (current.type === 'qualified_identifier') {
|
||||
const text = getNodeText(current, source).trim();
|
||||
const parts = text.split('::').filter(Boolean);
|
||||
if (parts.length > 1) {
|
||||
return parts.slice(0, -1).join('::');
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
for (let i = 0; i < current.namedChildCount; i++) {
|
||||
const child = current.namedChild(i);
|
||||
if (child) queue.push(child);
|
||||
}
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
export const cExtractor: LanguageExtractor = {
|
||||
functionTypes: ['function_definition'],
|
||||
classTypes: [],
|
||||
@@ -62,6 +107,8 @@ export const cppExtractor: LanguageExtractor = {
|
||||
nameField: 'declarator',
|
||||
bodyField: 'body',
|
||||
paramsField: 'parameters',
|
||||
resolveName: extractCppQualifiedMethodName,
|
||||
getReceiverType: extractCppReceiverType,
|
||||
getVisibility: (node) => {
|
||||
// Check for access specifier in parent
|
||||
const parent = node.parent;
|
||||
|
||||
@@ -1504,10 +1504,11 @@ export class TreeSitterExtractor {
|
||||
const func = getChildByField(node, 'function') || node.namedChild(0);
|
||||
|
||||
if (func) {
|
||||
if (func.type === 'member_expression' || func.type === 'attribute' || func.type === 'selector_expression' || func.type === 'navigation_expression') {
|
||||
if (func.type === 'member_expression' || func.type === 'attribute' || func.type === 'selector_expression' || func.type === 'navigation_expression' || func.type === 'field_expression') {
|
||||
// Method call: obj.method() or obj.field.method()
|
||||
// Go uses selector_expression with 'field', JS/TS uses member_expression with 'property'
|
||||
// Kotlin uses navigation_expression with navigation_suffix > simple_identifier
|
||||
// C/C++ use field_expression for both `obj.method()` and `ptr->method()`
|
||||
let property = getChildByField(func, 'property') || getChildByField(func, 'field');
|
||||
if (!property) {
|
||||
const child1 = func.namedChild(1);
|
||||
@@ -1524,9 +1525,13 @@ export class TreeSitterExtractor {
|
||||
// This helps the resolver distinguish method calls from bare function calls
|
||||
// (e.g., Python's console.print() vs builtin print())
|
||||
// Skip self/this/cls as they don't aid resolution
|
||||
const receiver = getChildByField(func, 'object') || getChildByField(func, 'operand') || func.namedChild(0);
|
||||
const receiver =
|
||||
getChildByField(func, 'object') ||
|
||||
getChildByField(func, 'operand') ||
|
||||
getChildByField(func, 'argument') ||
|
||||
func.namedChild(0);
|
||||
const SKIP_RECEIVERS = new Set(['self', 'this', 'cls', 'super']);
|
||||
if (receiver && (receiver.type === 'identifier' || receiver.type === 'simple_identifier')) {
|
||||
if (receiver && (receiver.type === 'identifier' || receiver.type === 'simple_identifier' || receiver.type === 'field_identifier')) {
|
||||
const receiverName = getNodeText(receiver, this.source);
|
||||
if (!SKIP_RECEIVERS.has(receiverName)) {
|
||||
calleeName = `${receiverName}.${methodName}`;
|
||||
|
||||
Reference in New Issue
Block a user