mirror of
https://github.com/colbymchenry/codegraph.git
synced 2026-10-21 16:57:09 +08:00
fix(c,cpp): a name in C or C++ code outside tests links to what the file's own includes declare (#2684)
PR #2675 narrowed a C or C++ test's same-named candidates to the ones its translation unit declares. Production code now gets the same rule: protobuf's Python extension called the copies of upb's C API that the PHP extension bundles in php-upb.c, and its Java generator's calls of the free HasHasbit() went to ImmutableFieldGenerator::HasHasbit, a method beside the caller. Refinements the wider scope needed: - a source implements the same-stem header beside it, when it includes one: pyext/message.cc is no implementation of google/protobuf/message.h; - a header's own overloads reach the definitions its source holds: conformance/binary_wireformat.h's template Varint calls the .cc's; - narrowing never leaves a call with only functions its arguments can't fill, past one outside the unit that they can: glog's three-argument GetStackTrace(...) is not utilities.h's GetStackTrace(). This also mends 9 breakpad unittest calls #2675 had narrowed the same way; - nor, for a C call, to definitions only a flag nothing sets compiles, past one the build without it does: that stays #2615's choice. A file-level "call" (a macro, or a declaration read as a call) is left as it was. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -129,6 +129,7 @@ and adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||
- In C++, an alias that names a class through a class derived from the one that declares it, like protocolbuffers/protobuf's `using ConformanceRequestSetting = BinaryAndJsonConformanceSuite::ConformanceRequestSetting;`, now leads to that class, `ConformanceTestSuite::ConformanceRequestSetting`. Before, an alias was followed only to a class declared under exactly the name it spells, so objects created through this one linked to the alias rather than the class. Re-index C++ projects after upgrading.
|
||||
- In C++, a name written in a googletest `TEST_F` or `TEST_P` body now finds the test fixture's nested types, those of its base classes included, before anything in the surrounding namespace, as C++ does for the member function such a body is: facebook/rocksdb's `new TestUserDefinedIndexFactory()` links to the class nested in the fixture's base. A class declared inside a test body, or inside any function, is now only found from that function, so the local classes many RocksDB tests each declare under one name, like `FakeWF` or `MyManager`, no longer stand in for one another, and a call like `fwf->SetIOError(true)` reaches the `FakeWF` its own test declares. Re-index C++ projects after upgrading.
|
||||
- In C and C++ tests, a name that several files declare now links to the one the test's own includes declare, when there is one: protocolbuffers/protobuf's C# generator tests call the `TestSourceDir()` that `googletest.h` declares, not another test helper's they never include; its upb tests call `upb_Arena_New()` and the rest of upb's C API in upb's own files, not the copies bundled for PHP and Ruby, which had left those calls unlinked; and facebook/rocksdb's C API test reaches `rocksdb_open` and its siblings in `db/c.cc`. Re-index C and C++ projects after upgrading.
|
||||
- In C and C++ code outside tests too, a name that several files declare now links to the one the file's own includes declare, when there is one: protocolbuffers/protobuf's Python extension calls functions like `upb_Encode()` in upb's own files rather than the copies bundled for PHP, and facebook/rocksdb's stress tool calls the `StringToHex()` its own header declares rather than `LDBCommand::StringToHex`. Re-index C and C++ projects after upgrading.
|
||||
- In C++, a declaration continued on a line that starts with `*`, like facebook/rocksdb's `const auto& el =` over `*reinterpret_cast<std::shared_ptr<EventListener>*>(jhandle);`, is now read, so calls such as `el->OnFlushCompleted(…)` link to `EventListener`'s methods; such a line used to be skipped as part of a comment. Code commented out in a `/* … */` block, and the text of a string literal, like protocolbuffers/protobuf's `[[deprecated("Please use SpaceUsedLong() instead")]]`, are no longer read as declarations, which had sent a call to the wrong method or to none. Re-index C++ projects after upgrading.
|
||||
- In C++, a call with a comparison or a shift among its arguments, like protocolbuffers/protobuf's `p->Emit({{"declare", has_bit_word_index < 0 ? … : ""}, …}, "…")` or `WriteVarint(num << 3, s)`, is now read with the right number of arguments, so it links to the overload that fits rather than one taking fewer. A parameter or variable whose type has template arguments is now read even after another such parameter on the same line, like facebook/rocksdb's `const autovector<uint32_t>& cf_ids`, or with empty ones, like fmt's `parse_context<>& ctx`: calls on it link to that class's methods, and calls on a `std::vector` declared that way no longer link to a project class's method of the same name. Re-index C++ projects after upgrading.
|
||||
- In C++, a call passing `nullptr` now links to the overload that takes a `std::nullptr_t`, as C++ picks it, rather than one taking a pointer, and a call no longer links to an overload declared `= delete`, which no call that compiles can reach: protocolbuffers/protobuf's `ptr.reset(nullptr)` on an `Arena::UniquePtr` reaches `reset(std::nullptr_t)`, and `field.Add("foo")` on a `RepeatedPtrField` reaches `Add(Element&&)` instead of the deleted `Add(const Element&)`. Re-index C++ projects after upgrading.
|
||||
|
||||
@@ -0,0 +1,368 @@
|
||||
/**
|
||||
* A C or C++ name is one its translation unit declares, in production code
|
||||
* as in tests (cpp-test-translation-unit.test.ts). Of several same-named
|
||||
* candidates, what the file's includes declare goes first: protobuf's Python
|
||||
* extension calls `upb_Encode()` from upb/wire/encode.h, not the copy the PHP
|
||||
* extension bundles in php-upb.c; its Java generator calls the `HasHasbit()`
|
||||
* helpers.h declares, not `ImmutableFieldGenerator::HasHasbit`, a method
|
||||
* beside the caller. A source implements the header beside it (the
|
||||
* Python extension's pyext/message.cc is no implementation of
|
||||
* google/protobuf/message.h), a header's inline overload reaches the
|
||||
* definition its own source holds, and a call keeps a function outside the
|
||||
* unit that takes its arguments over one inside that can't (glog's
|
||||
* three-argument `GetStackTrace(…)` is not utilities.h's `GetStackTrace()`).
|
||||
*/
|
||||
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';
|
||||
|
||||
let root = '';
|
||||
let cg: CodeGraph;
|
||||
|
||||
beforeAll(async () => {
|
||||
root = fs.mkdtempSync(path.join(os.tmpdir(), 'cg-cpp-tu-'));
|
||||
const files: Record<string, string> = {
|
||||
// upb's encoder, and the copy the PHP extension's amalgamation carries.
|
||||
'upb/wire/encode.h': `#ifndef UPB_WIRE_ENCODE_H_
|
||||
#define UPB_WIRE_ENCODE_H_
|
||||
|
||||
#include <stddef.h>
|
||||
|
||||
int upb_Encode(const void* msg, int options, char** buf, size_t* size);
|
||||
|
||||
#endif
|
||||
`,
|
||||
'upb/wire/encode.c': `#include "upb/wire/encode.h"
|
||||
|
||||
int upb_Encode(const void* msg, int options, char** buf, size_t* size) {
|
||||
*size = 0;
|
||||
return 0;
|
||||
}
|
||||
`,
|
||||
'php/ext/google/protobuf/php-upb.h': `#include <stddef.h>
|
||||
|
||||
int upb_Encode(const void* msg, int options, char** buf, size_t* size);
|
||||
`,
|
||||
'php/ext/google/protobuf/php-upb.c': `#include "php-upb.h"
|
||||
|
||||
int upb_Encode(const void* msg, int options, char** buf, size_t* size) {
|
||||
*size = 0;
|
||||
return 0;
|
||||
}
|
||||
`,
|
||||
'python/message.c': `#include "upb/wire/encode.h"
|
||||
|
||||
int PyUpb_Message_Serialize(const void* msg) {
|
||||
char* buf;
|
||||
size_t size;
|
||||
return upb_Encode(msg, 0, &buf, &size);
|
||||
}
|
||||
`,
|
||||
// A free function the Java generator's helpers.h declares, and a field
|
||||
// generator's method of the same name beside the caller.
|
||||
'src/google/protobuf/compiler/java/helpers.h': `namespace google {
|
||||
namespace protobuf {
|
||||
class FieldDescriptor;
|
||||
namespace compiler {
|
||||
namespace java {
|
||||
|
||||
inline bool HasHasbit(const FieldDescriptor* descriptor) {
|
||||
return descriptor != nullptr;
|
||||
}
|
||||
|
||||
} // namespace java
|
||||
} // namespace compiler
|
||||
} // namespace protobuf
|
||||
} // namespace google
|
||||
`,
|
||||
'src/google/protobuf/compiler/java/full/field_generator.h': `namespace google {
|
||||
namespace protobuf {
|
||||
class FieldDescriptor;
|
||||
namespace compiler {
|
||||
namespace java {
|
||||
|
||||
class ImmutableFieldGenerator {
|
||||
public:
|
||||
bool HasHasbit() const;
|
||||
|
||||
private:
|
||||
const FieldDescriptor* descriptor_;
|
||||
};
|
||||
|
||||
} // namespace java
|
||||
} // namespace compiler
|
||||
} // namespace protobuf
|
||||
} // namespace google
|
||||
`,
|
||||
'src/google/protobuf/compiler/java/full/field_generator.cc': `#include "google/protobuf/compiler/java/full/field_generator.h"
|
||||
|
||||
#include "google/protobuf/compiler/java/helpers.h"
|
||||
|
||||
namespace google {
|
||||
namespace protobuf {
|
||||
namespace compiler {
|
||||
namespace java {
|
||||
|
||||
bool ImmutableFieldGenerator::HasHasbit() const {
|
||||
return ::google::protobuf::compiler::java::HasHasbit(descriptor_);
|
||||
}
|
||||
|
||||
} // namespace java
|
||||
} // namespace compiler
|
||||
} // namespace protobuf
|
||||
} // namespace google
|
||||
`,
|
||||
'src/google/protobuf/compiler/java/full/enum_field.cc': `#include "google/protobuf/compiler/java/helpers.h"
|
||||
#include "google/protobuf/compiler/java/full/field_generator.h"
|
||||
|
||||
namespace google {
|
||||
namespace protobuf {
|
||||
namespace compiler {
|
||||
namespace java {
|
||||
|
||||
namespace {
|
||||
|
||||
int SetEnumVariables(const FieldDescriptor* descriptor) {
|
||||
if (HasHasbit(descriptor)) {
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
} // namespace java
|
||||
} // namespace compiler
|
||||
} // namespace protobuf
|
||||
} // namespace google
|
||||
`,
|
||||
// A header's template overload calls the one its own source defines.
|
||||
'conformance/binary_wireformat.h': `#include <cstdint>
|
||||
#include <string>
|
||||
|
||||
namespace google {
|
||||
namespace protobuf {
|
||||
namespace conformance {
|
||||
|
||||
using Wire = std::string;
|
||||
|
||||
Wire Varint(uint64_t x);
|
||||
|
||||
// Overloads to avoid forcing sign/size conversion warnings on call-sites.
|
||||
template <typename T>
|
||||
Wire Varint(T x) {
|
||||
return Varint(static_cast<uint64_t>(x));
|
||||
}
|
||||
|
||||
} // namespace conformance
|
||||
} // namespace protobuf
|
||||
} // namespace google
|
||||
`,
|
||||
'conformance/binary_wireformat.cc': `#include "conformance/binary_wireformat.h"
|
||||
|
||||
namespace google {
|
||||
namespace protobuf {
|
||||
namespace conformance {
|
||||
|
||||
Wire Varint(uint64_t x) {
|
||||
return std::string(1, static_cast<char>(x));
|
||||
}
|
||||
|
||||
} // namespace conformance
|
||||
} // namespace protobuf
|
||||
} // namespace google
|
||||
`,
|
||||
// glog's three-parameter GetStackTrace is declared in stacktrace.h and
|
||||
// defined in a header stacktrace.cc includes; utilities.h declares a
|
||||
// GetStackTrace() of no parameters.
|
||||
'src/stacktrace.h': `namespace google {
|
||||
namespace glog_internal_namespace_ {
|
||||
|
||||
int GetStackTrace(void** result, int max_depth, int skip_count);
|
||||
|
||||
} // namespace glog_internal_namespace_
|
||||
} // namespace google
|
||||
`,
|
||||
'src/stacktrace_generic-inl.h': `#include "stacktrace.h"
|
||||
|
||||
namespace google {
|
||||
namespace glog_internal_namespace_ {
|
||||
|
||||
int GetStackTrace(void** result, int max_depth, int skip_count) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
} // namespace glog_internal_namespace_
|
||||
} // namespace google
|
||||
`,
|
||||
'src/stacktrace.cc': `#include "stacktrace.h"
|
||||
|
||||
#include "stacktrace_generic-inl.h"
|
||||
`,
|
||||
'src/utilities.h': `#include <string>
|
||||
|
||||
namespace google {
|
||||
|
||||
std::string GetStackTrace();
|
||||
|
||||
} // namespace google
|
||||
`,
|
||||
'src/utilities.cc': `#include "utilities.h"
|
||||
|
||||
namespace google {
|
||||
|
||||
std::string GetStackTrace() {
|
||||
return "";
|
||||
}
|
||||
|
||||
} // namespace google
|
||||
`,
|
||||
'src/logging.cc': `#include <string>
|
||||
|
||||
#include "stacktrace.h"
|
||||
#include "utilities.h"
|
||||
|
||||
namespace google {
|
||||
|
||||
using namespace glog_internal_namespace_;
|
||||
|
||||
int RecordCrashReason(void** stack) {
|
||||
std::string trace = GetStackTrace();
|
||||
int depth = GetStackTrace(stack, 32, 4);
|
||||
return depth + static_cast<int>(trace.size());
|
||||
}
|
||||
|
||||
} // namespace google
|
||||
`,
|
||||
// The Python extension's message.cc implements pyext/message.h beside
|
||||
// it, though it includes google/protobuf/message.h too.
|
||||
'src/google/protobuf/message.h': `namespace google {
|
||||
namespace protobuf {
|
||||
|
||||
class Message {
|
||||
public:
|
||||
virtual ~Message() {}
|
||||
};
|
||||
|
||||
} // namespace protobuf
|
||||
} // namespace google
|
||||
`,
|
||||
'src/google/protobuf/any.pb.h': `#include "google/protobuf/message.h"
|
||||
|
||||
namespace google {
|
||||
namespace protobuf {
|
||||
|
||||
class Any : public Message {
|
||||
public:
|
||||
void MergeFrom(const Any& from) {}
|
||||
};
|
||||
|
||||
} // namespace protobuf
|
||||
} // namespace google
|
||||
`,
|
||||
'src/google/protobuf/descriptor.pb.h': `#include "google/protobuf/message.h"
|
||||
|
||||
namespace google {
|
||||
namespace protobuf {
|
||||
|
||||
class FileDescriptorSet : public Message {
|
||||
public:
|
||||
void CopyFrom(const FileDescriptorSet& from);
|
||||
};
|
||||
|
||||
} // namespace protobuf
|
||||
} // namespace google
|
||||
`,
|
||||
'src/google/protobuf/descriptor.pb.cc': `#include "google/protobuf/descriptor.pb.h"
|
||||
|
||||
namespace google {
|
||||
namespace protobuf {
|
||||
|
||||
void FileDescriptorSet::CopyFrom(const FileDescriptorSet& from) {
|
||||
if (&from == this) return;
|
||||
MergeFrom(from);
|
||||
}
|
||||
|
||||
} // namespace protobuf
|
||||
} // namespace google
|
||||
`,
|
||||
'python/google/protobuf/pyext/message.h': `namespace google {
|
||||
namespace protobuf {
|
||||
namespace python {
|
||||
namespace cmessage {
|
||||
|
||||
int MergeFrom(void* self, void* arg);
|
||||
|
||||
} // namespace cmessage
|
||||
} // namespace python
|
||||
} // namespace protobuf
|
||||
} // namespace google
|
||||
`,
|
||||
'python/google/protobuf/pyext/message.cc': `#include "google/protobuf/message.h"
|
||||
#include "google/protobuf/pyext/message.h"
|
||||
|
||||
namespace google {
|
||||
namespace protobuf {
|
||||
namespace python {
|
||||
namespace cmessage {
|
||||
|
||||
int MergeFrom(void* self, void* arg) { return 0; }
|
||||
|
||||
} // namespace cmessage
|
||||
} // namespace python
|
||||
} // namespace protobuf
|
||||
} // namespace google
|
||||
`,
|
||||
};
|
||||
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);
|
||||
}
|
||||
cg = await CodeGraph.init(root, { index: true });
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
cg?.close();
|
||||
if (root) fs.rmSync(root, { recursive: true, force: true });
|
||||
});
|
||||
|
||||
/** `qualifiedName (file)` of every call the file's symbols make. */
|
||||
const callsFrom = (file: string): string[] => {
|
||||
const ids = cg.getNodesInFile(file).map((n) => n.id);
|
||||
return cg.getOutgoingEdgesFrom(ids).filter((e) => e.kind === 'calls').map((e) => {
|
||||
const target = cg.getNode(e.target)!;
|
||||
return `${target.qualifiedName} (${target.filePath})`;
|
||||
});
|
||||
};
|
||||
|
||||
describe('a C/C++ reference links what its translation unit declares', () => {
|
||||
it('calls the C function its includes declare, not a bundled copy', () => {
|
||||
const calls = callsFrom('python/message.c');
|
||||
expect(calls).toContain('upb_Encode (upb/wire/encode.c)');
|
||||
expect(calls.some((c) => c.includes('php-upb'))).toBe(false);
|
||||
});
|
||||
|
||||
it('calls the free function its includes declare, not a class\'s method beside it', () => {
|
||||
const calls = callsFrom('src/google/protobuf/compiler/java/full/enum_field.cc');
|
||||
expect(calls).toContain('google::protobuf::compiler::java::HasHasbit (src/google/protobuf/compiler/java/helpers.h)');
|
||||
expect(calls.some((c) => c.includes('ImmutableFieldGenerator'))).toBe(false);
|
||||
});
|
||||
|
||||
it('lets a header\'s overload call the definition its own source holds', () => {
|
||||
const calls = callsFrom('conformance/binary_wireformat.h');
|
||||
expect(calls).toContain('google::protobuf::conformance::Varint (conformance/binary_wireformat.cc)');
|
||||
});
|
||||
|
||||
it('keeps a call off a function its arguments cannot fill', () => {
|
||||
const calls = callsFrom('src/logging.cc');
|
||||
expect(calls).toContain('google::GetStackTrace (src/utilities.cc)');
|
||||
expect(calls).toContain('google::glog_internal_namespace_::GetStackTrace (src/stacktrace_generic-inl.h)');
|
||||
});
|
||||
|
||||
it('reads a source as implementing the header beside it, not a namesake elsewhere', () => {
|
||||
const calls = callsFrom('src/google/protobuf/descriptor.pb.cc');
|
||||
expect(calls.some((c) => c.includes('pyext/message.cc'))).toBe(false);
|
||||
});
|
||||
});
|
||||
@@ -1617,38 +1617,34 @@ const CPP_STEM_HEADERS = new WeakMap<ResolutionContext, Map<string, string[]>>()
|
||||
/**
|
||||
* The headers a C or C++ file includes under its own name
|
||||
* (`conformance_test.cc` → `conformance_test.h`): the declarations its
|
||||
* definitions implement.
|
||||
* definitions implement. One beside it, when it includes one, is the only
|
||||
* such header: protobuf's pyext/message.cc implements pyext/message.h, not
|
||||
* the google/protobuf/message.h it also includes; fmt's src/format.cc, with
|
||||
* none beside it, implements include/fmt/format.h.
|
||||
*/
|
||||
function cppSameStemHeaders(filePath: string, context: ResolutionContext): string[] {
|
||||
let memo = CPP_STEM_HEADERS.get(context);
|
||||
if (!memo) CPP_STEM_HEADERS.set(context, (memo = new Map()));
|
||||
let headers = memo.get(filePath);
|
||||
if (!headers) {
|
||||
headers = [];
|
||||
const found: string[] = [];
|
||||
const stem = fileStem(filePath);
|
||||
for (const include of context.getNodesInFile(filePath)) {
|
||||
if (include.kind !== 'import' || fileStem(include.name) !== stem) continue;
|
||||
const header = cppIncludedFile(include, context);
|
||||
if (header && header !== filePath && fileStem(header) === stem) headers.push(header);
|
||||
if (header && header !== filePath && fileStem(header) === stem) found.push(header);
|
||||
}
|
||||
const dir = filePath.slice(0, filePath.lastIndexOf('/') + 1);
|
||||
const beside = found.filter((header) => header.slice(0, header.lastIndexOf('/') + 1) === dir);
|
||||
headers = beside.length > 0 ? beside : found;
|
||||
memo.set(filePath, headers);
|
||||
}
|
||||
return headers;
|
||||
}
|
||||
|
||||
const TEST_FILES = new WeakMap<ResolutionContext, Map<string, boolean>>();
|
||||
const TEST_SUITE_FILES = new WeakMap<ResolutionContext, Map<string, boolean>>();
|
||||
|
||||
/** isTestPath, memoized: it is asked of every reference with an ambiguous C or C++ name. */
|
||||
function isTestFile(filePath: string, context: ResolutionContext): boolean {
|
||||
let memo = TEST_FILES.get(context);
|
||||
if (!memo) TEST_FILES.set(context, (memo = new Map()));
|
||||
let hit = memo.get(filePath);
|
||||
if (hit === undefined) memo.set(filePath, (hit = isTestPath(filePath)));
|
||||
return hit;
|
||||
}
|
||||
|
||||
/** isTestSuitePath, memoized: it is asked of every candidate of such a name. */
|
||||
/** isTestSuitePath, memoized: it is asked of every candidate of an ambiguous C or C++ name. */
|
||||
function isTestSuiteFile(filePath: string, context: ResolutionContext): boolean {
|
||||
let memo = TEST_SUITE_FILES.get(context);
|
||||
if (!memo) TEST_SUITE_FILES.set(context, (memo = new Map()));
|
||||
@@ -1675,8 +1671,10 @@ function isInCppTranslationUnit(candidate: Node, ref: UnresolvedRef, context: Re
|
||||
((candidate.kind === 'function' || candidate.kind === 'variable' || candidate.kind === 'constant') &&
|
||||
isStaticCFunction(candidate, context))) return false;
|
||||
const suite = isTestSuiteFile(candidate.filePath, context);
|
||||
return cppSameStemHeaders(candidate.filePath, context)
|
||||
.some((header) => (!suite || isTestSuiteFile(header, context)) && cppIncluders(header, context).has(ref.filePath));
|
||||
// (The header may be the reference's own: json.h's inline overloads call
|
||||
// the ones json.cc defines.)
|
||||
return cppSameStemHeaders(candidate.filePath, context).some((header) => (!suite || isTestSuiteFile(header, context)) &&
|
||||
(header === ref.filePath || cppIncluders(header, context).has(ref.filePath)));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1701,29 +1699,49 @@ function isCppNameAloneTarget(n: Node, ref: UnresolvedRef, context: ResolutionCo
|
||||
}
|
||||
|
||||
/**
|
||||
* Same-named candidates for a C or C++ reference in a test, narrowed to the
|
||||
* ones its translation unit declares, when there are any. A test is a
|
||||
* program of its own, so the name it uses is the one its includes declare:
|
||||
* protobuf's csharp generator tests call `TestSourceDir()`, which
|
||||
* googletest.h, an include of theirs, declares, and test_util2.h's
|
||||
* `TestUtil::TestSourceDir`, a test suite none of them includes, took the
|
||||
* calls; a test of the hpb backend calls upb's `upb_Arena_New()` from
|
||||
* upb/mem/arena.h, not the copies php-upb.h and ruby-upb.h bundle. For a
|
||||
* name the reference writes alone (`byName`), only what the name alone can
|
||||
* mean counts (isCppNameAloneTarget), and a call the extractor recorded
|
||||
* without the receiver it is written on (`columns()[0].value()`) is left to
|
||||
* the name strategies. When the unit declares none of them — a test whose
|
||||
* includes don't resolve — the candidates stay as they are. Not for an
|
||||
* `#include`, which names a file, nor for production code, which
|
||||
* isVisibleAcrossFiles keeps from test suites.
|
||||
* Same-named candidates for a C or C++ reference, narrowed to the ones its
|
||||
* translation unit declares, when there are any: the name a file uses is
|
||||
* one its includes declare. protobuf's csharp generator tests call the
|
||||
* `TestSourceDir()` googletest.h declares, not test_util2.h's
|
||||
* `TestUtil::TestSourceDir`, a test suite none of them includes; its Python
|
||||
* and Ruby extensions and upb's own code call `upb_Encode()` in
|
||||
* upb/wire/encode.c, not the copy php-upb.c bundles; rocksdb's stress tool
|
||||
* calls the `StringToHex()` its db_stress_common.h declares, not
|
||||
* `LDBCommand::StringToHex`. For a name the reference writes alone
|
||||
* (`byName`), only what the name alone can mean counts
|
||||
* (isCppNameAloneTarget), and a call the extractor recorded without the
|
||||
* receiver it is written on (`columns()[0].value()`) is left to the name
|
||||
* strategies. When the unit declares none of them — a file whose includes
|
||||
* don't resolve — the candidates stay as they are. Not for an `#include`,
|
||||
* which names a file.
|
||||
*/
|
||||
function inCppTestTranslationUnit(candidates: Node[], ref: UnresolvedRef, context: ResolutionContext, byName = false): Node[] {
|
||||
if (candidates.length < 2 || (ref.language !== 'c' && ref.language !== 'cpp') ||
|
||||
ref.referenceKind === 'imports' || !isTestFile(ref.filePath, context)) return candidates;
|
||||
function withinCppTranslationUnit(candidates: Node[], ref: UnresolvedRef, context: ResolutionContext, byName = false): Node[] {
|
||||
if (candidates.length < 2 || (ref.language !== 'c' && ref.language !== 'cpp') || ref.referenceKind === 'imports') return candidates;
|
||||
if (byName && ref.referenceKind === 'calls' && cppImplicitReceiver(ref, context) !== 'bare') return candidates;
|
||||
// A "call" outside any function is a macro, or a declaration read as a call
|
||||
// (rocksdb's c.h `extern … rocksdb_t*` above `rocksdb_open(…);`): no call.
|
||||
if (ref.referenceKind === 'calls' && context.getNodeById?.(ref.fromNodeId)?.kind === 'file') return candidates;
|
||||
const within = candidates.filter((n) => isInCppTranslationUnit(n, ref, context) &&
|
||||
(!byName || n.filePath === ref.filePath || isCppNameAloneTarget(n, ref, context)));
|
||||
return within.length > 0 ? within : candidates;
|
||||
if (within.length === 0 || within.length === candidates.length) return candidates;
|
||||
// Nor, for a C call, to definitions only a flag nothing sets compiles, past
|
||||
// one the build without it does: that is cppFunctionOverload's choice
|
||||
// (lua-cjson's fpconv.h defines `fpconv_init` under `#ifdef
|
||||
// USE_INTERNAL_FPCONV`; redis's build calls fpconv.c's).
|
||||
if (ref.language === 'c' && ref.referenceKind === 'calls' && within.every((n) => isCFeatureVariant(n, ref, context)) &&
|
||||
!isOffByDefault(ref.filePath, ref.line, context) &&
|
||||
candidates.some((n) => !within.includes(n) && n.kind === 'function' && !isStaticCFunction(n, context) &&
|
||||
!isCFeatureVariant(n, ref, context))) return candidates;
|
||||
// Nor to functions too few or too many arguments for the call, past one
|
||||
// that takes them: glog's `GetStackTrace(stack, ARRAYSIZE(stack), 1)` is
|
||||
// the one stacktrace.h declares, which the stacktrace_*-inl.h headers its
|
||||
// stacktrace.cc includes define, not utilities.h's `GetStackTrace()`.
|
||||
if (ref.referenceKind === 'calls') {
|
||||
const args = cppCallArguments(ref, within[0]!.name, context);
|
||||
if (args && within.every((n) => cppArgumentCountFits(n, n.name, args, context) === false) &&
|
||||
candidates.some((n) => !within.includes(n) && cppArgumentCountFits(n, n.name, args, context) === true)) return candidates;
|
||||
}
|
||||
return within;
|
||||
}
|
||||
|
||||
const MINIFIED_SCRIPTS = new WeakMap<ResolutionContext, Map<string, boolean>>();
|
||||
@@ -8146,7 +8164,7 @@ export function matchByExactName(
|
||||
: swiftShape && swiftShape.shape !== 'chained' ? nearestSwiftMembers(filtered, ref, context)
|
||||
: kotlinBare ? lexicalKotlinMembers(filtered, ref, context) : cppSpecializationPool(filtered, ref, context);
|
||||
// (Above the ceiling below nothing is picked, so no translation unit is read.)
|
||||
const candidates = ranked.length <= AMBIGUOUS_NAME_CEILING ? inCppTestTranslationUnit(ranked, ref, context, true) : ranked;
|
||||
const candidates = ranked.length <= AMBIGUOUS_NAME_CEILING ? withinCppTranslationUnit(ranked, ref, context, true) : ranked;
|
||||
|
||||
if (candidates.length === 0) {
|
||||
return null;
|
||||
@@ -9240,7 +9258,7 @@ export function resolveMethodOnType(
|
||||
// intentionally in ANOTHER file (#314) — is unaffected: those blocks return
|
||||
// early whenever an import pins the class.
|
||||
if (ref.referenceKind === 'function_ref' && matches.length !== 1) return null;
|
||||
const ordered = preferCallSiteFile(inCppTestTranslationUnit(cppVisibleFirst(matches, ref, context), ref, context), ref.filePath);
|
||||
const ordered = preferCallSiteFile(withinCppTranslationUnit(cppVisibleFirst(matches, ref, context), ref, context), ref.filePath);
|
||||
return {
|
||||
original: ref,
|
||||
targetNodeId: ordered[0]!.id,
|
||||
@@ -13185,7 +13203,6 @@ export function clearNameMatcherMemos(context: ResolutionContext): void {
|
||||
clearCppRawStringMemos(context);
|
||||
CPP_CLASSES_IN_FILE.delete(context);
|
||||
CPP_STEM_HEADERS.delete(context);
|
||||
TEST_FILES.delete(context);
|
||||
TEST_SUITE_FILES.delete(context);
|
||||
CPP_BASE_SPECS.delete(context);
|
||||
CPP_USING_MEMBERS.delete(context);
|
||||
@@ -14784,7 +14801,7 @@ export function matchMethodCall(
|
||||
// resolves to `a/`'s method (#1079).
|
||||
const strat1 = nmTimedT('mc-class', ref, (): ResolvedRef | null => {
|
||||
let classCandidates = preferCallSiteFile(
|
||||
inCppTestTranslationUnit(context.getNodesByName(objectOrClass!).filter(isMethodOwnerKind), ref, context),
|
||||
withinCppTranslationUnit(context.getNodesByName(objectOrClass!).filter(isMethodOwnerKind), ref, context),
|
||||
ref.filePath,
|
||||
);
|
||||
// A C# class the call's namespaces can see before one they can't:
|
||||
@@ -14887,7 +14904,7 @@ export function matchMethodCall(
|
||||
if (capitalizedReceiver !== objectOrClass) {
|
||||
const strat2 = nmTimedT('mc-capital', ref, (): ResolvedRef | null => {
|
||||
const fuzzyClassCandidates = preferCallSiteFile(
|
||||
inCppTestTranslationUnit(context.getNodesByName(capitalizedReceiver).filter(isMethodOwnerKind), ref, context),
|
||||
withinCppTranslationUnit(context.getNodesByName(capitalizedReceiver).filter(isMethodOwnerKind), ref, context),
|
||||
ref.filePath,
|
||||
);
|
||||
for (const classNode of fuzzyClassCandidates) {
|
||||
@@ -15022,7 +15039,7 @@ export function matchMethodCall(
|
||||
// the first seen) resolves to the call site's own file rather than the
|
||||
// first-indexed duplicate (#1079).
|
||||
const std = isStdMethodName(ref.language, methodName!) && !/^(?:self|Self|this|base)$/.test(objectOrClass!);
|
||||
for (const method of preferCallSiteFile(inCppTestTranslationUnit(targetMethods, ref, context), ref.filePath)) {
|
||||
for (const method of preferCallSiteFile(withinCppTranslationUnit(targetMethods, ref, context), ref.filePath)) {
|
||||
if (std && !receiverNamesOwner(receiverLink(objectOrClass!), method, context)) continue;
|
||||
// The owner type's own name — not its namespace (`eShop.ClientApp…`
|
||||
// shares `Client` with every `httpClient`) nor the method's.
|
||||
@@ -18569,20 +18586,35 @@ function cppOverloadParameters(n: Node, name: string, context: ResolutionContext
|
||||
return found;
|
||||
}
|
||||
|
||||
/** A C++ parameter that is a pack: `T&&... args`, not the `...` inside `format_string<T...>`. */
|
||||
function isCppParameterPack(p: string): boolean {
|
||||
let flat = p;
|
||||
for (let prev = ''; prev !== flat;) { prev = flat; flat = flat.replace(/<[^<>]*>/g, ''); }
|
||||
return flat.includes('...');
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether as many arguments as `args` can fill the parameters of the C or C++
|
||||
* function `n`, its defaults and a pack counted; null when `n` is no
|
||||
* function or its parameter list can't be read.
|
||||
*/
|
||||
function cppArgumentCountFits(n: Node, name: string, args: string[], context: ResolutionContext): boolean | null {
|
||||
if (n.kind !== 'function' && n.kind !== 'method') return null;
|
||||
const list = cppOverloadParameters(n, name, context);
|
||||
if (list === null) return null;
|
||||
const params = splitCppTopLevel(cppWithoutComments(list)).filter((p) => p !== 'void');
|
||||
const required = params.filter((p) => !isCppParameterPack(p) && !/=/.test(p)).length;
|
||||
return args.length >= required && (params.some(isCppParameterPack) || args.length <= params.length);
|
||||
}
|
||||
|
||||
/** How well a call's arguments fit an overload's parameters: arity, and a string literal's first slot. */
|
||||
function cppOverloadFit(n: Node, name: string, args: string[], context: ResolutionContext): number {
|
||||
if (n.kind !== 'function' && n.kind !== 'method') return -1;
|
||||
const list = cppOverloadParameters(n, name, context);
|
||||
if (list === null) return 0;
|
||||
const params = splitCppTopLevel(cppWithoutComments(list)).filter((p) => p !== 'void');
|
||||
// A pack is `T&&... args`, not the `...` inside `format_string<T...>`.
|
||||
const isPack = (p: string): boolean => {
|
||||
let flat = p;
|
||||
for (let prev = ''; prev !== flat;) { prev = flat; flat = flat.replace(/<[^<>]*>/g, ''); }
|
||||
return flat.includes('...');
|
||||
};
|
||||
const variadic = params.some(isPack);
|
||||
const required = params.filter((p) => !isPack(p) && !/=/.test(p)).length;
|
||||
const variadic = params.some(isCppParameterPack);
|
||||
const required = params.filter((p) => !isCppParameterPack(p) && !/=/.test(p)).length;
|
||||
let score = 0;
|
||||
if (args.length < required || (!variadic && args.length > params.length)) score -= 3;
|
||||
// Each string literal against its parameter: a string type by name over a
|
||||
@@ -18596,7 +18628,7 @@ function cppOverloadFit(n: Node, name: string, args: string[], context: Resoluti
|
||||
for (let i = 0; i < args.length && i < params.length; i++) {
|
||||
const arg = args[i]!;
|
||||
const param = params[i]!;
|
||||
if (isPack(param)) break;
|
||||
if (isCppParameterPack(param)) break;
|
||||
if (!/^(?:u8|u|U|L)?R?"|^FMT_STRING\s*\(/.test(arg)) continue;
|
||||
const wideArg = /^LR?"/.test(arg);
|
||||
const wideParam = /\bw(?:string|char_t|format|string_view)|wchar_t/.test(param);
|
||||
|
||||
Reference in New Issue
Block a user