mirror of
https://github.com/colbymchenry/codegraph.git
synced 2026-10-16 20:53:57 +08:00
7a79654157
The resolver's name-existence pre-filter read no leading `::`: its `::` branch wanted the separator past the first character, and the single-`:` loop skips names holding `::`. So `::leveldb::RepairDB(…)`, `::ROCKSDB_NAMESPACE::RepairDB(…)` or protobuf's `::_pbi::…` calls were dropped before any strategy ran, unless a detected framework claimed them. protobuf's got through only because it ships three Swift files: the Swift ↔ Objective-C bridge claims any name with a `:` in it, which is why #2415 left claimsReference() universal. - The pre-filter checks a C or C++ name without its leading `::`. - claimsReference() is asked only of the frameworks that resolve the reference's language (frameworksFor), like resolve() since #2415. - matchByQualifiedName takes a global-qualified C/C++ name as exactly that qualified name. Its suffix match took a namesake nested in another namespace or class: with the pre-filter alone, 23 of the 24 edges it added on leveldb, fmt and rocksdb were wrong, mostly wrappers like fmt's mock `test::open` linked to the `::open(…)` they wrap, and protobuf's `::operator delete` went to `DynamicMessage::operator delete`. A declaration inside a namespace a macro opens (fmt's `struct pipe`) only looks global in the index, so it is skipped; step 5 still reads those. - `::std::…` and `::memset` count as `std::…` and `memset` for the built-in check, and step 5 returns early for a single-segment name. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
198 lines
6.2 KiB
TypeScript
198 lines
6.2 KiB
TypeScript
/**
|
||
* A C or C++ name written from the global scope — `::store::Repair(…)`,
|
||
* protobuf's `::_pbi::PrivateAccess::GenerateParseTable(…)` through
|
||
* `namespace _pbi = ::google::protobuf::internal;` — never got past the
|
||
* resolver's name-existence pre-filter, which read no leading `::`. protobuf's
|
||
* calls resolved only because it ships a few Swift and Objective-C files, and
|
||
* the Swift ↔ Objective-C bridge claimed every name with a `:` in it; C++-only
|
||
* rocksdb, leveldb and fmt dropped theirs. Past the pre-filter, the name is the
|
||
* declaration of exactly that qualified name: never one nested in another
|
||
* namespace or class, as the wrapper `int open(…) { return ::open(…); }` and an
|
||
* external `::testing::` call show.
|
||
*/
|
||
import { describe, it, expect, afterAll, beforeAll } from 'vitest';
|
||
import * as fs from 'fs';
|
||
import * as os from 'os';
|
||
import * as path from 'path';
|
||
import { CodeGraph } from '../src';
|
||
|
||
function writeProject(prefix: string, files: Record<string, string>): string {
|
||
const root = fs.mkdtempSync(path.join(os.tmpdir(), prefix));
|
||
for (const [rel, content] of Object.entries(files)) {
|
||
fs.mkdirSync(path.dirname(path.join(root, rel)), { recursive: true });
|
||
fs.writeFileSync(path.join(root, rel), content);
|
||
}
|
||
return root;
|
||
}
|
||
|
||
/**
|
||
* `name -> target qualified name` for every reference the named function or
|
||
* method in `file` resolves — a call to a struct is an `instantiates` edge.
|
||
*/
|
||
function callsFrom(cg: CodeGraph, file: string, name: string): string[] {
|
||
const from = cg.getNodesInFile(file).filter((n) => n.name === name && (n.kind === 'function' || n.kind === 'method'));
|
||
expect(from.length).toBeGreaterThan(0);
|
||
return cg
|
||
.getOutgoingEdgesFrom(from.map((n) => n.id))
|
||
.flatMap((e) => {
|
||
const refName = (e.metadata as { refName?: string } | undefined)?.refName;
|
||
return refName ? [`${refName} -> ${cg.getNode(e.target)!.qualifiedName}`] : [];
|
||
})
|
||
.sort();
|
||
}
|
||
|
||
const PB_INTERNAL = `#pragma once
|
||
namespace pb {
|
||
namespace internal {
|
||
inline int Prefetch(const void* ptr) { return ptr != nullptr; }
|
||
} // namespace internal
|
||
} // namespace pb
|
||
|
||
namespace _pbi = ::pb::internal;
|
||
`;
|
||
|
||
describe('C++ names written from the global scope', () => {
|
||
let root = '';
|
||
let cg: CodeGraph;
|
||
|
||
beforeAll(async () => {
|
||
root = writeProject('cg-cpp-global-', {
|
||
'src/store/repair.cc': `namespace store {
|
||
int Repair(const char* name) { return name != nullptr; }
|
||
int move(int value) { return value; }
|
||
|
||
namespace testing {
|
||
const char* TempDir() { return "/tmp/store"; }
|
||
} // namespace testing
|
||
} // namespace store
|
||
`,
|
||
'src/pb/internal.h': PB_INTERNAL,
|
||
'src/store/posix.cc': `#include <unistd.h>
|
||
|
||
namespace store {
|
||
namespace posix {
|
||
int close(int fd) { return ::close(fd); }
|
||
} // namespace posix
|
||
} // namespace store
|
||
`,
|
||
'tools/repair_tool.cc': `#include <utility>
|
||
#include "pb/internal.h"
|
||
|
||
namespace tools {
|
||
namespace store {
|
||
int Repair(const char* name) { return 0; }
|
||
} // namespace store
|
||
|
||
int Run(const char* name) {
|
||
int fixed = ::store::Repair(name);
|
||
int warmed = ::_pbi::Prefetch(name);
|
||
return ::std::move(fixed) + warmed;
|
||
}
|
||
} // namespace tools
|
||
`,
|
||
'include/lib/os.h': `#pragma once
|
||
#define LIB_BEGIN_NAMESPACE \\
|
||
namespace lib { \\
|
||
inline namespace v1 {
|
||
#define LIB_END_NAMESPACE \\
|
||
} \\
|
||
}
|
||
|
||
LIB_BEGIN_NAMESPACE
|
||
struct pipe {
|
||
int read_end;
|
||
int write_end;
|
||
};
|
||
LIB_END_NAMESPACE
|
||
`,
|
||
'tests/posix-mock.cc': `#include <unistd.h>
|
||
#include "lib/os.h"
|
||
|
||
namespace test {
|
||
int pipe(int fds[2]) { return ::pipe(fds); }
|
||
} // namespace test
|
||
`,
|
||
'tests/repair_test.cc': `#include <gtest/gtest.h>
|
||
|
||
class RepairTest : public ::testing::Test {};
|
||
|
||
int RunAll(int argc, char** argv) {
|
||
::testing::InitGoogleTest(&argc, argv);
|
||
return ::testing::TempDir() != nullptr;
|
||
}
|
||
`,
|
||
});
|
||
cg = await CodeGraph.init(root, { index: true });
|
||
}, 60_000);
|
||
|
||
afterAll(() => {
|
||
cg?.close();
|
||
if (root) fs.rmSync(root, { recursive: true, force: true });
|
||
});
|
||
|
||
it('links `::ns::f()` to the global namespace’s f, and an alias’s call to the aliased namespace', () => {
|
||
// Inside `namespace tools`, `store::Repair` would be tools' own; `::store::Repair` is the global one.
|
||
expect(callsFrom(cg, 'tools/repair_tool.cc', 'Run')).toEqual([
|
||
'::_pbi::Prefetch -> pb::internal::Prefetch',
|
||
'::store::Repair -> store::Repair',
|
||
]);
|
||
});
|
||
|
||
it('never links a global name to a namesake nested in a namespace or class', () => {
|
||
// The wrapper calls the C library's `close`, not itself.
|
||
expect(callsFrom(cg, 'src/store/posix.cc', 'close')).toEqual([]);
|
||
// Nor `lib::pipe`, whose namespace a macro opens (fmt's FMT_BEGIN_NAMESPACE):
|
||
// the index cannot see it, so the struct only looks global there.
|
||
expect(callsFrom(cg, 'tests/posix-mock.cc', 'pipe')).toEqual([]);
|
||
// gtest is not in the project: `::testing::TempDir` is not `store::testing::TempDir`.
|
||
expect(callsFrom(cg, 'tests/repair_test.cc', 'RunAll')).toEqual([]);
|
||
});
|
||
|
||
it('leaves an external `::testing::Test` base unresolved', () => {
|
||
const test = cg.getNodesInFile('tests/repair_test.cc').find((n) => n.name === 'RepairTest')!;
|
||
expect(test).toBeDefined();
|
||
expect(cg.getOutgoingEdges(test.id).filter((e) => e.kind === 'extends' || e.kind === 'implements')).toEqual([]);
|
||
});
|
||
});
|
||
|
||
describe('C++ global names beside Swift and Objective-C (protobuf)', () => {
|
||
let root = '';
|
||
let cg: CodeGraph;
|
||
|
||
beforeAll(async () => {
|
||
root = writeProject('cg-cpp-global-objc-', {
|
||
'objectivec/GPBLegacy.m': `#import <Foundation/Foundation.h>
|
||
|
||
@interface GPBLegacy : NSObject
|
||
- (void)start;
|
||
@end
|
||
|
||
@implementation GPBLegacy
|
||
- (void)start {}
|
||
@end
|
||
`,
|
||
'swift/Bridge.swift': `import Foundation
|
||
|
||
final class Bridge: NSObject {
|
||
func run() {}
|
||
}
|
||
`,
|
||
'src/pb/internal.h': PB_INTERNAL,
|
||
'src/message.cc': `#include "pb/internal.h"
|
||
|
||
int Parse(const void* data) { return ::_pbi::Prefetch(data); }
|
||
`,
|
||
});
|
||
cg = await CodeGraph.init(root, { index: true });
|
||
}, 60_000);
|
||
|
||
afterAll(() => {
|
||
cg?.close();
|
||
if (root) fs.rmSync(root, { recursive: true, force: true });
|
||
});
|
||
|
||
it('still links a C++ `::_pbi::` call, which no longer rides the Swift ↔ Objective-C bridge’s claim', () => {
|
||
expect(callsFrom(cg, 'src/message.cc', 'Parse')).toEqual(['::_pbi::Prefetch -> pb::internal::Prefetch']);
|
||
});
|
||
});
|