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:
thismilktea
2026-05-26 15:17:07 -05:00
committed by GitHub
co-authored by chenyuxuan Colby McHenry Claude Opus 4.7
parent 72c08c2bef
commit c0cf9c1e7d
5 changed files with 294 additions and 3 deletions
+47
View File
@@ -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;