mirror of
https://github.com/colbymchenry/codegraph.git
synced 2026-10-25 03:49:59 +08:00
Step 3 of member chains, after #2626 and #2627. The type of `x.member` was
read from x's class alone, so a member that class inherits was never found
and the call fell back to its bare name.
A temporary diagnostic on main measured why chain calls still fell through:
2,801 of 4,884 were a member declared in a base class. Nearly all were
rocksdb's `Options`, which declares no members of its own and inherits
`statistics`, `table_factory`, `listeners`, `prefix_extractor` and the rest
from `DBOptions` and `ColumnFamilyOptions`. The other causes are left for
later:
- a chain whose base can't be typed: 875;
- a class the index lacks, e.g. protobuf's generated `_impl_`: 626;
- a member found nowhere: 337.
cppExpressionType's member branch now uses cppInheritedDataMember, the
lookup the calling class's own members already use (#2533):
- the class's own declaration first;
- then the classes it derives from, through their base edges, to depth 4;
- a base that depends on the class's template parameters is skipped
unless a using-declaration brings the name in.
Kit A/B against main e8ec7fd8:
| repo | removed | added | retargeted | same target, now typed |
|---|---|---|---|---|
| rocksdb | 1,410 | 634 | 392 | 184 |
| protobuf | 0 | 2 | 149 | 55 |
| leveldb, fmt, redis | identical | | | |
- Removed: the bare-name guesses on inherited std::shared_ptr and
std::vector members, such as `options.table_factory.reset(…)` →
FSWritableFilePtr::reset (654) and `options_.db_paths.emplace_back(…)` →
autovector::emplace_back (256).
- Added: `options.statistics->getTickerCount(…)` → Statistics (432),
`rate_limiter` → RateLimiter (51), `comparator` → Comparator (26).
- Retargeted: DummyOldStats -> Statistics, DB -> Env, ClockCacheShard -> Cache.
- protobuf's generated `this_._internal_metadata_.Clear<…>()`,
`MergeFrom<…>` and `unknown_fields<…>` now reach InternalMetadata, a
member every message inherits from MessageLite. Before, they reached the
message's own `Clear` or `Any::MergeFrom`.
All 50 random sites read by hand (36 rocksdb, 14 protobuf) were correct.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
132 lines
4.6 KiB
TypeScript
132 lines
4.6 KiB
TypeScript
/**
|
|
* A C++ call on a data member that the object's class inherits, reached
|
|
* through a member chain, calls a method of the member's class. rocksdb's
|
|
* `Options` declares no members of its own: `options.statistics` and
|
|
* `options.table_factory` are `DBOptions`' and `ColumnFamilyOptions`'. Only
|
|
* the object's own class was read for the member, so such calls were guessed
|
|
* by the method's name.
|
|
*/
|
|
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';
|
|
import type { Node } from '../src/types';
|
|
|
|
const lines = (...l: string[]): string => [...l, ''].join('\n');
|
|
|
|
describe('a C++ call on a member chain through an inherited member', () => {
|
|
let root: string;
|
|
let cg: CodeGraph;
|
|
|
|
beforeAll(async () => {
|
|
root = fs.mkdtempSync(path.join(os.tmpdir(), 'codegraph-cpp-member-chain-base-'));
|
|
const write = (file: string, text: string): void => {
|
|
fs.mkdirSync(path.dirname(path.join(root, file)), { recursive: true });
|
|
fs.writeFileSync(path.join(root, file), text);
|
|
};
|
|
// Each namesake comes first: a call matched by its bare name lands on it.
|
|
write('src/types.h', lines(
|
|
'class ArenaStats {',
|
|
' public:',
|
|
' long getTickerCount(int t) const { return 0; }',
|
|
'};',
|
|
'class Statistics {',
|
|
' public:',
|
|
' virtual long getTickerCount(int t) const { return 1; }',
|
|
'};',
|
|
'class BlockNamesake {',
|
|
' public:',
|
|
' const char* Name() const { return "block"; }',
|
|
'};',
|
|
'class TableFactory {',
|
|
' public:',
|
|
' virtual const char* Name() const { return "table"; }',
|
|
'};',
|
|
'class MemTableNamesake {',
|
|
' public:',
|
|
' void CreateMemTableRep() {}',
|
|
'};',
|
|
'class MemTableRepFactory {',
|
|
' public:',
|
|
' virtual void CreateMemTableRep() {}',
|
|
'};',
|
|
'class FilePtr {',
|
|
' public:',
|
|
' void reset() {}',
|
|
'};',
|
|
'class Other {',
|
|
' public:',
|
|
' void Flush() {}',
|
|
'};',
|
|
'class Inner {',
|
|
' public:',
|
|
' void Flush() {}',
|
|
'};',
|
|
));
|
|
write('src/options.h', lines(
|
|
'#include <memory>',
|
|
'#include "src/types.h"',
|
|
'struct DBOptions {',
|
|
' Statistics* statistics = nullptr;',
|
|
'};',
|
|
'struct AdvancedColumnFamilyOptions {',
|
|
' MemTableRepFactory* memtable_factory = nullptr;',
|
|
'};',
|
|
'struct ColumnFamilyOptions : public AdvancedColumnFamilyOptions {',
|
|
' std::shared_ptr<TableFactory> table_factory;',
|
|
'};',
|
|
'struct Options : public DBOptions, public ColumnFamilyOptions {};',
|
|
'struct Shadowing : public DBOptions {',
|
|
' Inner statistics;',
|
|
'};',
|
|
));
|
|
write('src/use.cc', lines(
|
|
'#include "src/options.h"',
|
|
'void Stats(Options& options) { options.statistics->getTickerCount(0); }',
|
|
'void Table(Options* options) { options->table_factory->Name(); }',
|
|
'void Memtable(const Options& opts) { opts.memtable_factory->CreateMemTableRep(); }',
|
|
'void HolderOwn(Options& options) { options.table_factory.reset(); }',
|
|
'void Shadowed(Shadowing& s) { s.statistics.Flush(); }',
|
|
'struct Holder {',
|
|
' Options opts;',
|
|
'};',
|
|
'void Deep(Holder* h) { h->opts.statistics->getTickerCount(1); }',
|
|
));
|
|
cg = await CodeGraph.init(root, { index: true });
|
|
}, 120_000);
|
|
|
|
afterAll(() => {
|
|
cg?.close();
|
|
if (root) fs.rmSync(root, { recursive: true, force: true });
|
|
});
|
|
|
|
/** `calls` callees of the function `name`, as `qualifiedName`. */
|
|
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.qualifiedName).sort();
|
|
};
|
|
|
|
it("a member the object's class inherits from either base", () => {
|
|
expect(calls('Stats')).toEqual(['Statistics::getTickerCount']);
|
|
expect(calls('Table')).toEqual(['TableFactory::Name']);
|
|
});
|
|
|
|
it('a member inherited through two levels', () => {
|
|
expect(calls('Memtable')).toEqual(['MemTableRepFactory::CreateMemTableRep']);
|
|
});
|
|
|
|
it("an inherited library member's own method calls none of the project's", () => {
|
|
expect(calls('HolderOwn')).toEqual([]);
|
|
});
|
|
|
|
it("the class's own member before a base's of the same name", () => {
|
|
expect(calls('Shadowed')).toEqual(['Inner::Flush']);
|
|
});
|
|
|
|
it('an inherited member further down a chain', () => {
|
|
expect(calls('Deep')).toEqual(['Statistics::getTickerCount']);
|
|
});
|
|
});
|