feat(extraction): universal recovery of macro-mangled C/C++ function names (#1102)

* feat(extraction): universal recovery of macro-mangled C/C++ function names

The curated inline-macro blank list (#1100/#1101) can't enumerate every
library's macro. Add a universal post-parse net so a function is findable by
name regardless of which macro decorates it, plus a batch of common libraries
to the curated list for full name+return-type recovery.

- recoverMangledCppName: after extraction, recover the real identifier from a
  name still mangled by an un-blanked macro (`MACRO Ret name(…)` misparses to
  "Ret name"). It's a new `recoverMangledName` extractor hook wired only onto
  C/C++, applied to every name they produce. Safe by construction: it only
  touches an already-mangled name (an internal space that isn't a legit
  `operator …`/destructor), so a clean name is returned unchanged; guarded
  against the `Ret (name)` parenthesized-name idiom and bare primitives. Scoped
  to C/C++ so Kotlin/Scala backtick identifiers (which legitimately contain
  spaces) are never touched.
- Curated list extended past UE/pugixml/Godot/Boost to Qt (Q_INVOKABLE, …),
  Folly, Abseil, LLVM, V8, Eigen, and rapidjson.

Validated on CARLA (large UE project, 1131 C++/h files) vs the pre-fix baseline:
function-name mangles 440 -> 6, 431 fixed, and — critically — 0 regressions
(the salvage also recovers names that the pre-parse's own non-local error-recovery
shifts would otherwise re-mangle, erasing the 7 shifts seen in #1101). The 6
residual are all the moodycamel `Ret (name)` idiom, correctly left alone. On a
made-up macro with no list entry (`WEBKIT_EXPORT WTFString compute()`), the name
`compute` is still recovered. Full suite green; eleven regression/safety tests added.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* docs(changelog): note universal C++ macro-mangled name recovery (#1102)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Colby Mchenry
2026-07-01 09:29:20 -05:00
committed by GitHub
co-authored by Claude Opus 4.8
parent a164ceae8b
commit cb20a3bf7f
5 changed files with 122 additions and 1 deletions
+56 -1
View File
@@ -11,7 +11,7 @@ import * as os from 'os';
import { CodeGraph } from '../src';
import { extractFromSource, scanDirectory, buildDefaultIgnore, discoverEmbeddedRepoRoots, buildScopeIgnore } from '../src/extraction';
import { detectLanguage, isLanguageSupported, getSupportedLanguages, initGrammars, loadAllGrammars, isSourceFile } from '../src/extraction/grammars';
import { stripCppTemplateArgs, blankCppExportMacros, blankCppInlineMacros } from '../src/extraction/languages/c-cpp';
import { stripCppTemplateArgs, blankCppExportMacros, blankCppInlineMacros, recoverMangledCppName } from '../src/extraction/languages/c-cpp';
import { normalizePath } from '../src/utils';
beforeAll(async () => {
@@ -2995,6 +2995,61 @@ class APXCharacter { // the one real definition
});
});
describe('C++ universal macro-mangled name recovery', () => {
// Curated pre-parse blanking can't list every library's inline macro, so a
// post-parse salvage recovers the real function name from ANY leftover
// `MACRO Ret name(…)` mangle — no list needed. It only ever touches an
// already-mangled name, so it can't corrupt a clean one.
const namesOf = (code: string, file = 's.cpp') =>
extractFromSource(file, code).nodes
.filter((n) => n.kind === 'method' || n.kind === 'function')
.map((n) => n.name);
it('recovers the name from a completely unknown macro (no list entry)', () => {
expect(namesOf('WEBKIT_EXPORT WTFString computeThing(int x) { return H(x); }')).toContain('computeThing');
expect(namesOf('SOMELIB_INLINE MyResult doWork(int x) { return H(x); }')).toContain('doWork');
expect(namesOf('MZ_FORCEINLINE char_t* to_str(double v) { return H(v); }')).toContain('to_str');
});
it('recoverMangledCppName only touches already-mangled names, with guards', () => {
// Recovered:
expect(recoverMangledCppName('WTFString computeThing')).toBe('computeThing');
expect(recoverMangledCppName('char_t* to_str(double v)')).toBe('to_str');
expect(recoverMangledCppName('unspecified_bool_type() const')).toBe('unspecified_bool_type');
// Left unchanged — clean names, operators, destructors, the `Ret (name)`
// idiom, and non-identifier tails:
expect(recoverMangledCppName('computeThing')).toBe('computeThing');
expect(recoverMangledCppName('operator EALSMovementState')).toBe('operator EALSMovementState');
expect(recoverMangledCppName('~Widget')).toBe('~Widget');
expect(recoverMangledCppName('bool (likely)')).toBe('bool (likely)');
expect(recoverMangledCppName('void (free)')).toBe('void (free)');
expect(recoverMangledCppName('QDockWidget *')).toBe('QDockWidget *');
});
it('does not disturb clean C++ names or non-C++ (Kotlin backtick) names', () => {
expect(namesOf('int foo(int x) { return x; }')).toEqual(['foo']);
// Kotlin backtick identifiers legitimately contain spaces; the salvage is
// C/C++-only, so they are untouched.
const kt = extractFromSource('T.kt', 'class T {\n fun `decode simple cert`() { }\n}').nodes
.filter((n) => n.kind === 'method' || n.kind === 'function')
.map((n) => n.name);
expect(kt).toContain('`decode simple cert`');
});
it('curated list now also covers Qt / Folly / Abseil / LLVM / V8 / Eigen / rapidjson (full recovery)', () => {
const info = (c: string) =>
extractFromSource('x.cpp', c).nodes
.filter((n) => n.kind === 'method' || n.kind === 'function')
.map((n) => ({ name: n.name, ret: n.returnType }));
expect(info('FOLLY_ALWAYS_INLINE Str f(int x) { return H(x); }')).toEqual([{ name: 'f', ret: 'Str' }]);
expect(namesOf('Q_INVOKABLE void onClicked() { H(); }')).toContain('onClicked');
expect(namesOf('ABSL_ATTRIBUTE_ALWAYS_INLINE int hash(int x) { return H(x); }')).toContain('hash');
expect(namesOf('EIGEN_STRONG_INLINE Scalar dot(const V& v) { return H(v); }')).toContain('dot');
expect(namesOf('V8_INLINE MaybeLocal Get(int i) { return H(i); }')).toContain('Get');
expect(namesOf('RAPIDJSON_FORCEINLINE bool Parse(const char* s) { return H(s); }')).toContain('Parse');
});
});
describe('C++ templated base-class inheritance (#1043)', () => {
// Inheriting from a template (`class D : public Base<int>`) recorded the base
// ref as the full instantiation `Base<int>`, which never name-matched the