Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
00d7430
fix(cpp): a qualified receiver type never reaches a class that only s…
colbymchenry Oct 15, 2026
759acbb
fix(cpp): an alias of a type written from the global scope names that…
colbymchenry Oct 15, 2026
8f1f842
fix(cpp): a receiver type written from the global scope is looked up …
colbymchenry Oct 15, 2026
c71a3d0
fix(cpp): a receiver declared as a class template's member type, like…
colbymchenry Oct 15, 2026
8fe3156
fix(cpp): a `->` call on a declared container iterator calls the elem…
colbymchenry Oct 15, 2026
c29caf2
fix(cpp): an `auto` local holding a library container's iterator call…
colbymchenry Oct 15, 2026
18efc8c
fix(cpp): a call on a dereferenced iterator or pointer, `(*it)->Visit…
colbymchenry Oct 15, 2026
8f951e5
fix(cpp): a call on the member of the pair a map's iterator points at…
colbymchenry Oct 15, 2026
4727a06
fix(cpp): a call on an element read by subscript, `statuses[i].IsNotF…
colbymchenry Oct 15, 2026
f7951a1
fix(cpp): a method a macro of its file defines, like protobuf's `PROT…
colbymchenry Oct 16, 2026
627018e
fix(cpp): an explicit specialization, like protobuf's `WireFormatLite…
colbymchenry Oct 16, 2026
4a591ae
fix(cpp): a call to an overloaded free function, like protobuf's `Par…
colbymchenry Oct 16, 2026
dc4ccf6
fix(c): a C call, like redis's `mstime()`, reaches the function its o…
colbymchenry Oct 16, 2026
98127e5
fix(c): a header's definition for a flag nothing sets, like lua-cjson…
colbymchenry Oct 16, 2026
0893606
fix(cpp): a call on what a container's `front()`/`back()`/`at()` or a…
colbymchenry Oct 16, 2026
1981354
fix(cpp): of two overloads alike but for a `const`, like protobuf's `…
colbymchenry Oct 16, 2026
384b541
merge: reconcile upstream through 19813544 (16 C/C++ resolution fixes)
bompus Oct 16, 2026
77fc74d
fix(kernel): link C++ calls through iterators, dereferences and eleme…
bompus Oct 16, 2026
69dd1bd
test(cpp): keep two iterator calls unlinked where the element type is…
bompus Oct 16, 2026
c0d1458
fix(kernel): look C++ receiver types up from the global scope and thr…
bompus Oct 16, 2026
02892a9
test(cpp): keep two outside-type calls unlinked where the receiver's …
bompus Oct 16, 2026
dcad03b
fix(kernel): read a C++ receiver declared as a class template's membe…
bompus Oct 16, 2026
39907fc
fix(kernel): pick the C++ free-function overload a call's arguments fit
bompus Oct 16, 2026
568c069
fix(kernel): pick the C++ overload an argument's constness and value …
bompus Oct 16, 2026
c59f3b9
test(cpp): expect the overloads and element types the upstream specs …
bompus Oct 16, 2026
8780836
fix(kernel): link a C call to the function its own file can see
bompus Oct 16, 2026
aa7f5db
fix(kernel): skip a C header definition only an unset build flag comp…
bompus Oct 16, 2026
543a705
fix(kernel): read a macro-defined C++ method's return type and parame…
bompus Oct 16, 2026
d2905a8
fix(kernel): pick the C++ explicit specialization a call's template a…
bompus Oct 16, 2026
f494cb6
docs: record the upstream 19813544 merge point and the fork's C++ dif…
bompus Oct 16, 2026
afa4c2f
fix(kernel): a macro-defined C++ method keeps the defaults its class …
bompus Oct 16, 2026
3b3272b
fix(kernel): read a C++ map's value_type as the library's pair, not i…
bompus Oct 16, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 26 additions & 10 deletions CHANGELOG.md

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ Follow [@getcodegraph](https://x.com/getcodegraph) on X for updates.

## About this fork

This is **bompus/codegraph**, a fork of [colbymchenry/codegraph](https://github.com/colbymchenry/codegraph). Its default branch, `fork/consolidated`, contains upstream `main` through [`eec9288a`](https://github.com/colbymchenry/codegraph/commit/eec9288a) (after v1.6.2) plus the fork's own work, and it takes upstream changes as they land. Changes that suit upstream are also offered there as pull requests.
This is **bompus/codegraph**, a fork of [colbymchenry/codegraph](https://github.com/colbymchenry/codegraph). Its default branch, `fork/consolidated`, contains upstream `main` through [`19813544`](https://github.com/colbymchenry/codegraph/commit/19813544) (after v1.6.2) plus the fork's own work, and it takes upstream changes as they land. Changes that suit upstream are also offered there as pull requests.

The fork publishes no releases. The install scripts, npm package, badges and `codegraph upgrade` further down this page install **upstream's** releases. To run the fork, build it from source (below).

Expand Down Expand Up @@ -154,7 +154,7 @@ Call resolution the fork adds:
- Kotlin multi-dollar strings keep their interpolation threshold.
- C++ typedef and using aliases follow their declaration scope, including nested owners and inherited aliases. Pointer, array and function members are not supertypes. Visible class-scoped aliases and declared complex receivers keep their method owners; unsubstituted template parameters and ambiguous owners stay unresolved.
- C++ `->` calls on a `std::unique_ptr`, `std::shared_ptr` or `std::optional` reach the type it holds, looked up through the caller's enclosing classes and their bases, its namespaces, `using` directives and aliases. A held type the project does not declare leaves the call unresolved, as a plain pointer to one does; upstream guesses a callee from the receiver's name there. A partial class template specialization is indexed as an anonymous class, so a call to a member only it declares stays unresolved.
- C++ calls on an `auto` local, on a chained call and on a data member of the caller's class (one a base class declares, or a gtest `TEST_F` / `TEST_P` fixture's) take the type its declaration writes, as upstream's do. Where that type cannot be read (a template parameter's, a `std::shared_ptr<T>` of one, a trait of one, what a function template returns), the call stays unlinked, as does a local of a type the file does not declare; upstream guesses a callee from the receiver's name at 0.7. A chain of up to four calls is read from its declarations, where upstream reads its first inner call; a longer one is looked up by the last name, as before.
- C++ calls on an `auto` local, on a chained call and on a data member of the caller's class (one a base class declares, or a gtest `TEST_F` / `TEST_P` fixture's) take the type its declaration writes, as upstream's do. Where that type cannot be read (a template parameter's, a `std::shared_ptr<T>` of one, a trait of one, what a function template returns, an iterator's element or a map's key or value that a class template writes with its parameter), the call stays unlinked, as does a local of a type the file does not declare; upstream guesses a callee from the receiver's name at 0.7. A chain of up to four calls is read from its declarations, where upstream reads its first inner call; a longer one is looked up by the last name, as before.
- C++ namespace aliases and declarations in macro-opened namespaces are looked up only in the caller's include closure; upstream pools them across all files.
- C++ object creation sees a header under a test directory, such as nlohmann/json's old-release copy in `tests/abi/`, only from inside that directory's tree or through an `#include`; upstream sees every header. A class declared in an included `.cc` file is the includer's too, and of two headers declaring one class the creation takes the one in its include closure.
- C# namespace `using` directives and `using` aliases apply only inside their enclosing namespace, not to sibling namespaces in the same file (`using static` is still file-wide, as upstream).
Expand Down
186 changes: 186 additions & 0 deletions __tests__/c-feature-macro-variants.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,186 @@
/**
* A C call the file's headers answer only when the build sets a flag nothing
* in the project sets reaches the definition the build without it calls.
* lua-cjson's fpconv.h defines `fpconv_init` under `#ifdef
* USE_INTERNAL_FPCONV` and otherwise declares it for fpconv.c to define; the
* included header's definition was taken, though no build of redis compiles
* it.
*/
import { describe, it, expect, beforeAll, afterAll } from 'vitest';
import * as fs from 'fs';
import * as os from 'os';
import * as path from 'path';
import CodeGraph from '../src/index';
import type { Node } from '../src/types';

const lines = (...l: string[]): string => [...l, ''].join('\n');

describe('a C call past a definition only a flag nothing sets compiles', () => {
let root: string;
let cg: CodeGraph;

beforeAll(async () => {
root = fs.mkdtempSync(path.join(os.tmpdir(), 'codegraph-c-feature-variants-'));
const write = (file: string, text: string): void => {
fs.mkdirSync(path.dirname(path.join(root, file)), { recursive: true });
fs.writeFileSync(path.join(root, file), text);
};
// lua-cjson in redis: the header's definition is for a build that sets USE_INTERNAL_FPCONV.
write('deps/lua/src/fpconv.h', lines(
'#ifndef FPCONV_H',
'#define FPCONV_H',
'#ifdef USE_INTERNAL_FPCONV',
'static inline void fpconv_init()',
'{',
'}',
'#else',
'extern void fpconv_init();',
'#endif',
'#endif',
));
write('deps/lua/src/fpconv.c', lines('#include "fpconv.h"', 'void fpconv_init()', '{', '}'));
// ...nor the internal one's own definition.
write('deps/lua/src/dtoa_fpconv.c', lines('#ifdef USE_INTERNAL_FPCONV', 'void fpconv_init()', '{', '}', '#endif'));
write('deps/lua/src/lua_cjson.c', lines('#include "fpconv.h"', 'int luaopen_cjson(void) { fpconv_init(); return 0; }'));
// hiredis: the header's definition is for the build that leaves the flag off.
write('deps/hiredis/alloc.h', lines(
'#ifndef HIREDIS_ALLOC_H',
'#define HIREDIS_ALLOC_H',
'#ifndef HIREDIS_EXTERN_ALLOC',
'static inline void hi_free(void *ptr) {',
'}',
'#else',
'void hi_free(void *ptr);',
'#endif',
'#endif',
));
write('deps/hiredis/alloc_ext.c', lines('void hi_free(void *ptr) {', '}'));
write('deps/hiredis/sds.c', lines('#include "alloc.h"', 'void sdsfree(void *s) { hi_free(s); }'));
// A flag the project defines may be set.
write('include/config.h', lines('#define USE_FAST_CRC 1'));
write('include/crc.h', lines(
'#ifdef USE_FAST_CRC',
'static inline unsigned crc32c(unsigned crc) { return crc; }',
'#else',
'unsigned crc32c(unsigned crc);',
'#endif',
));
write('src/crc.c', lines('unsigned crc32c(unsigned crc) { return ~crc; }'));
write('src/store.c', lines('#include "include/config.h"', '#include "include/crc.h"', 'unsigned checksum(void) { return crc32c(0); }'));
// So may one a build file names.
write('lib/Makefile', lines('CFLAGS += -DUSE_SIMD_HASH'));
write('lib/hash.h', lines('#ifdef USE_SIMD_HASH', 'static inline unsigned hash_bytes(const char *p) { return 0; }', '#endif'));
write('lib/hash_scalar.c', lines('unsigned hash_bytes(const char *p) { return 1; }'));
write('lib/table.c', lines('#include "hash.h"', 'unsigned slot(const char *k) { return hash_bytes(k); }'));
// And one of the platform's.
write('include/clock.h', lines('#ifdef __linux__', 'static inline long clock_ns(void) { return 0; }', '#else', 'long clock_ns(void);', '#endif'));
write('src/clock_posix.c', lines('long clock_ns(void) { return 1; }'));
write('src/timer.c', lines('#include "include/clock.h"', 'long tick(void) { return clock_ns(); }'));
// `#if` expressions, `#elif` and `#else`.
write('include/codec.h', lines(
'#if defined(CODEC_ZSTD) && !defined(CODEC_DISABLE)',
'static inline int codec_level(void) { return 3; }',
'#elif CODEC_LZ4',
'static inline int codec_bound(int n) { return n; }',
'#else',
'static inline int codec_name(void) { return 0; }',
'#endif',
));
write('src/codec_impl.c', lines(
'int codec_level(void) { return 1; }',
'int codec_bound(int n) { return n + 1; }',
'int codec_name(void) { return 1; }',
));
write('src/pack.c', lines('#include "include/codec.h"', 'int pack(void) { return codec_level() + codec_bound(1) + codec_name(); }'));
// An `#else` after a test that holds.
write('include/log.h', lines(
'#ifndef LOG_EXTERNAL',
'static inline void log_write(const char *m) {}',
'#else',
'static inline void log_flush(void) {}',
'#endif',
));
write('src/log_ext.c', lines('void log_flush(void) {}'));
write('src/server.c', lines('#include "include/log.h"', 'void shutdown_server(void) { log_flush(); }'));
// A header that defines the function for either build.
write('include/prof.h', lines(
'#ifdef PROF_ENABLED',
'static inline int prof_tick(void) { return 1; }',
'#else',
'static inline int prof_tick(void) { return 0; }',
'#endif',
));
write('src/prof_impl.c', lines('int prof_tick(void) { return 2; }'));
write('src/loop.c', lines('#include "include/prof.h"', 'int loop_tick(void) { return prof_tick(); }'));
// redis's quicklist.c: a call compiled only with the flag reaches what the flag compiles with it.
write('src/quicklist.c', lines(
'#ifdef REDIS_TEST',
'static long long ustime(void) { return 0; }',
'int quicklistTest(void) { return (int)ustime(); }',
'#endif',
));
write('src/server_time.c', lines('long long ustime(void) { return 1; }'));
// ...in another file too.
write('include/simd.h', lines('#ifdef USE_SIMD_CRC', 'static inline unsigned crc_simd(unsigned c) { return c; }', '#endif'));
write('src/crc_soft.c', lines('unsigned crc_simd(unsigned c) { return ~c; }'));
write('src/checksum_simd.c', lines('#include "include/simd.h"', '#ifdef USE_SIMD_CRC', 'unsigned fast_sum(void) { return crc_simd(0); }', '#endif'));
// A flag another component defines is not this one's.
write('fpx/Makefile', lines('all: fpx.o use.o'));
write('fpx/fpx.h', lines('#ifdef FPX_FAST', 'static inline int fpx_round(int v) { return v; }', '#endif'));
write('fpx/fpx.c', lines('int fpx_round(int v) { return v + 1; }'));
write('fpx/use.c', lines('#include "fpx.h"', 'int use_round(void) { return fpx_round(1); }'));
write('other/config.h', lines('#define FPX_FAST 1'));
// A test in a comment is no test.
write('include/api.h', lines('/*', '#ifdef OLD_API', '*/', 'static inline int api_version(void) { return 2; }'));
write('src/api_v1.c', lines('int api_version(void) { return 1; }'));
write('src/client.c', lines('#include "include/api.h"', 'int client_version(void) { return api_version(); }'));
cg = await CodeGraph.init(root, { index: true });
}, 120_000);

afterAll(() => {
cg?.close();
if (root) fs.rmSync(root, { recursive: true, force: true });
});

/** `<file>:<line>` of what the function `name` calls, sorted. */
const calls = (name: string): string[] => {
const caller = cg.getNodesByKind('function').find((n: Node) => n.name === name)!;
expect(caller, name).toBeDefined();
return cg.getCallees(caller.id).filter((r) => r.edge.kind === 'calls').map((r) => `${r.node.filePath}:${r.node.startLine}`).sort();
};

it('the definition the build without the flag compiles', () => {
expect(calls('luaopen_cjson')).toEqual(['deps/lua/src/fpconv.c:2']);
expect(calls('use_round')).toEqual(['fpx/fpx.c:1']);
});

it("the header's own, when the build without the flag compiles it", () => {
expect(calls('sdsfree')).toEqual(['deps/hiredis/alloc.h:4']);
});

it("the header's own, when the project or its build may set the flag", () => {
expect(calls('checksum')).toEqual(['include/crc.h:2']);
expect(calls('slot')).toEqual(['lib/hash.h:2']);
expect(calls('tick')).toEqual(['include/clock.h:2']);
});

it('reads `#if` expressions, `#elif` and `#else`', () => {
expect(calls('pack')).toEqual(['include/codec.h:6', 'src/codec_impl.c:1', 'src/codec_impl.c:2']);
expect(calls('shutdown_server')).toEqual(['src/log_ext.c:1']);
});

it('the definition the flag compiles with a call it compiles too', () => {
expect(calls('quicklistTest')).toEqual(['src/quicklist.c:2']);
expect(calls('fast_sum')).toEqual(['include/simd.h:2']);
});

it("the header's own, when it defines the function for either build", () => {
const callees = calls('loop_tick');
expect(callees).toHaveLength(1);
expect(callees[0]).toMatch(/^include\/prof\.h:/);
});

it('reads no test in a comment', () => {
expect(calls('client_version')).toEqual(['include/api.h:4']);
});
});
Loading
Loading