mirror of
https://github.com/colbymchenry/codegraph.git
synced 2026-10-26 04:19:33 +08:00
lua-cjson's fpconv.h defines `static inline void fpconv_init()` only under
`#ifdef USE_INTERNAL_FPCONV` and otherwise declares it `extern` for fpconv.c
to define. Nothing in redis's tree sets the flag, so its build calls
fpconv.c's, but #2613's rule (the file's own or an included header's
definition first) took the header's.
c-feature-variants.ts decides whether a line is compiled only by a build
that sets a flag nothing sets. It reads the conditionals around the line
(cpp-macro-visibility's directiveLines, now exported): `#ifdef`/`#ifndef`,
and `#if` with defined, !, &&, ||, parentheses, integers and bare flags,
then `#elif` and `#else`. A flag is set nowhere when the line's own file
doesn't define it, no build file from the project root down to the
definition or the call names it, and no C/C++ file of their components
(the tree under the nearest directory with a build file) defines it.
Reserved and platform names stay unknown.
cppFunctionOverload's C branch then takes a non-static, non-variant
definition outside the file's tier when one exists, when every definition
the file sees is such a variant, and when the call itself is compiled by
the default build. A branch around both the definition and the call decides
nothing: redis's quicklist.c tests call their own `static mstime` inside
`#ifdef REDIS_TEST`.
A/B against main dc4ccf6b: leveldb, fmt, rocksdb and protobuf identical;
redis 1 call retargeted, lua_cjson.c's fpconv_init() from fpconv.h to
fpconv.c. Indexing time within noise (redis 10.5-11.9 s on main, 10.2-12.3 s
here).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
187 lines
8.7 KiB
TypeScript
187 lines
8.7 KiB
TypeScript
/**
|
|
* 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']);
|
|
});
|
|
});
|