mirror of
https://github.com/colbymchenry/codegraph.git
synced 2026-10-18 22:02:49 +08:00
fix(cpp): a call on a member reached through other members, like options.cf_paths.emplace_back(…) on a std::vector, no longer links to a project method of that name (#2626)
The extractor records a call on a member chain (`a.b.m()`, `a->b->m()`,
`this->p_.m()`) by its bare method name, so it went to whichever project
method shared the name. A census of the cgstd kit found most such calls
linked by name alone, and most of those links wrong. Above all, they were
on members of standard-library types, whose methods (`size`, `reset`,
`push_back`, `get`, `empty`) a large codebase has namesakes of.
matchCppOutsideMemberChainCall reads the chain where the call is written
(cppMemberChainReceiver) and types it with the existing cppMemberObject:
- a data member is typed by its declared type in its class
(cppDataMemberType);
- `->` goes through a pointer, smart pointer, optional or iterator.
It gives no edge when the object the method is called on is a library type
(isCppLibraryType) or a type under a namespace the project declares nothing
in (isCppOutsideQualifiedType). These are the call-chain resolver's rules.
Unlike a call chain, the operator is read as written, so `.` on a
`std::vector<T>` or a `std::unique_ptr<T>` is that type's own member,
whatever `T` is.
Anything else, a member of a project class included, goes to the name
strategies as before.
Kit A/B against main a2570695 (only removals; nothing added or retargeted):
| repo | calls removed |
|---|---|
| rocksdb | 2,001 |
| protobuf | 234 |
| leveldb | 15 |
| fmt | 12 |
| redis | 0 (identical) |
Every removed link was a bare-name guess. Of the 2,235 removed sites in
rocksdb and protobuf, a judge reading the members' declarations across each
repo rated:
- 2,044 as wrong links (a library type's namesake, or an unrelated class);
- 165 unknown;
- 10 not counted as chains by its pattern (after a cast, like
`(int)data->entries.size()`);
- 16 right. These turned out to be name collisions on the judge's side,
checked in the source (`std::deque logs_`, `std::shared_ptr decompressor`,
`std::vector ids`).
24 random removals were also read by hand, and all were correct.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
+1
-1
@@ -92,7 +92,7 @@ and adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|||||||
- In C++, a base class like `Message` in `class DynamicMessage final : public Message` now links to the class C++ itself would pick: the one declared in the namespaces around the class, or brought in by the file's `using` declarations and namespace aliases, and the class a `typedef` or `using` alias names. Before, a base class was matched by its name alone, so it could land on a class of the same name in another namespace or another language: in protocolbuffers/protobuf, `DynamicMessage` derived from the JSON parser's internal `Message` class, and in google/leveldb and RocksDB, iterators derived from an unrelated nested `Iterator` class such as the skip list's. The wrong base showed up in `codegraph_explore`'s type hierarchy and in impact, and tied the base's methods to the wrong overrides. A template parameter used as a base no longer links to a class that shares its name, and a base written from the global scope, like `public ::testing::Test`, now links to its class. Re-index C++ projects after upgrading.
|
- In C++, a base class like `Message` in `class DynamicMessage final : public Message` now links to the class C++ itself would pick: the one declared in the namespaces around the class, or brought in by the file's `using` declarations and namespace aliases, and the class a `typedef` or `using` alias names. Before, a base class was matched by its name alone, so it could land on a class of the same name in another namespace or another language: in protocolbuffers/protobuf, `DynamicMessage` derived from the JSON parser's internal `Message` class, and in google/leveldb and RocksDB, iterators derived from an unrelated nested `Iterator` class such as the skip list's. The wrong base showed up in `codegraph_explore`'s type hierarchy and in impact, and tied the base's methods to the wrong overrides. A template parameter used as a base no longer links to a class that shares its name, and a base written from the global scope, like `public ::testing::Test`, now links to its class. Re-index C++ projects after upgrading.
|
||||||
- In C and C++, a header or source file named like a test, such as protocolbuffers/protobuf's `conformance_test.h` and `test_runner.h`, now counts as part of the code that `#include`s it, directly or through other headers. Before, calls from that code into it were dropped, so protobuf's conformance suites' `suite_.ReportFailure(…)` and `RunValidInputTest(…)` calls, and its unit tests' calls to the shared `TestUtil` helpers, linked to nothing. A test file that only tests include is still kept out of reach of the rest of your code. Re-index C and C++ projects after upgrading.
|
- In C and C++, a header or source file named like a test, such as protocolbuffers/protobuf's `conformance_test.h` and `test_runner.h`, now counts as part of the code that `#include`s it, directly or through other headers. Before, calls from that code into it were dropped, so protobuf's conformance suites' `suite_.ReportFailure(…)` and `RunValidInputTest(…)` calls, and its unit tests' calls to the shared `TestUtil` helpers, linked to nothing. A test file that only tests include is still kept out of reach of the rest of your code. Re-index C and C++ projects after upgrading.
|
||||||
- In C++, a class, struct or method now keeps the namespaces and the class it is declared in when the parser misreads something earlier in the file, such as an unknown macro in front of a member or in a class header. Before, everything after the misread code could lose them, or land inside a class it isn't in: in protocolbuffers/protobuf, `FieldDescriptor` was indexed without `google::protobuf::`, and in RocksDB, the `Opts` struct declared inside a cache table class was indexed outside it, so finding a class's base classes and the methods called on its objects fell back to guessing by name. Methods that had come loose from their class are its members again, and classes the misread code had hidden, like leveldb's POSIX file and environment classes, are now indexed. Re-index C++ projects after upgrading.
|
- In C++, a class, struct or method now keeps the namespaces and the class it is declared in when the parser misreads something earlier in the file, such as an unknown macro in front of a member or in a class header. Before, everything after the misread code could lose them, or land inside a class it isn't in: in protocolbuffers/protobuf, `FieldDescriptor` was indexed without `google::protobuf::`, and in RocksDB, the `Opts` struct declared inside a cache table class was indexed outside it, so finding a class's base classes and the methods called on its objects fell back to guessing by name. Methods that had come loose from their class are its members again, and classes the misread code had hidden, like leveldb's POSIX file and environment classes, are now indexed. Re-index C++ projects after upgrading.
|
||||||
- In C++, a call on a variable, parameter or member declared as a standard-library or other outside type with a lowercase name, like `std::string`, `std::vector`, `std::set` or `absl::flat_hash_set`, no longer links to a project method that only shares the method's name, even one of a project class in another namespace with the type's name, like your own `util::flat_hash_set`. A variable declared outside the calling function, like another function's or a file's `std::string text`, no longer links its calls to such a class either. In google/leveldb, `saved_key_.clear()` on a `std::string saved_key_;` reached `Slice::clear`, and in protocolbuffers/protobuf, `proto.append(…)` on a `std::string` reached an unrelated `LeftoverBuffer::append`. The same goes for `->` through a raw pointer like `std::string* out`, while `->` through a smart pointer, iterator or optional still links to the type it holds. Comments also no longer pass for a variable's declaration, so more calls now link to the method of the type the variable is really declared with. Re-index C++ projects after upgrading.
|
- In C++, a call on a variable, parameter or member declared as a standard-library or other outside type with a lowercase name, like `std::string`, `std::vector`, `std::set` or `absl::flat_hash_set`, no longer links to a project method that only shares the method's name, even one of a project class in another namespace with the type's name, like your own `util::flat_hash_set`. A variable declared outside the calling function, like another function's or a file's `std::string text`, no longer links its calls to such a class either. In google/leveldb, `saved_key_.clear()` on a `std::string saved_key_;` reached `Slice::clear`, and in protocolbuffers/protobuf, `proto.append(…)` on a `std::string` reached an unrelated `LeftoverBuffer::append`. The same goes for `->` through a raw pointer like `std::string* out`, while `->` through a smart pointer, iterator or optional still links to the type it holds. Comments also no longer pass for a variable's declaration, so more calls now link to the method of the type the variable is really declared with. A call on a member reached through other members, like facebook/rocksdb's `options.cf_paths.emplace_back(…)` on a `std::vector`, `props->rep.db_id.size()` on a `std::string` or `bbto.filter_policy.reset(…)` on a `std::shared_ptr`, no longer links to a project method of that name either: those had reached `autovector::emplace_back`, `BlobContents::size` and `FSWritableFilePtr::reset`. The same goes for a member of an outside type like `absl::Status`. Re-index C++ projects after upgrading.
|
||||||
- In C++, a class written with attribute macros the parser can't read now keeps its members. That covers several macros between `class` and the class name, like protocolbuffers/protobuf's generated `class PROTOBUF_EXPORT PROTOBUF_FUTURE_ADD_EARLY_WARN_UNUSED Any final`, a macro with arguments there, like RocksDB's `struct ALIGN_AS(64U) HandleImpl`, or one before a template specialization, and macros on a member: in front of it (`PROTOBUF_FUTURE_ADD_EARLY_NODISCARD`), between a pointer and its name (`PROTOBUF_NONNULL`), after its parameters or its name (`ABSL_ATTRIBUTE_LIFETIME_BOUND`, google/leveldb's `LOCKS_EXCLUDED(mu_)` and `GUARDED_BY(mu_)`), or alone on a line above a comment ({fmt}'s `FMT_BEGIN_EXPORT`). Before, protobuf's generated message classes were missing and their members, like `GetDescriptor`, `GetReflection` and `unknown_fields`, were indexed as functions outside any class, and leveldb's annotated methods were indexed under the annotation's name, so calls to them linked to nothing. Re-index C++ projects after upgrading.
|
- In C++, a class written with attribute macros the parser can't read now keeps its members. That covers several macros between `class` and the class name, like protocolbuffers/protobuf's generated `class PROTOBUF_EXPORT PROTOBUF_FUTURE_ADD_EARLY_WARN_UNUSED Any final`, a macro with arguments there, like RocksDB's `struct ALIGN_AS(64U) HandleImpl`, or one before a template specialization, and macros on a member: in front of it (`PROTOBUF_FUTURE_ADD_EARLY_NODISCARD`), between a pointer and its name (`PROTOBUF_NONNULL`), after its parameters or its name (`ABSL_ATTRIBUTE_LIFETIME_BOUND`, google/leveldb's `LOCKS_EXCLUDED(mu_)` and `GUARDED_BY(mu_)`), or alone on a line above a comment ({fmt}'s `FMT_BEGIN_EXPORT`). Before, protobuf's generated message classes were missing and their members, like `GetDescriptor`, `GetReflection` and `unknown_fields`, were indexed as functions outside any class, and leveldb's annotated methods were indexed under the annotation's name, so calls to them linked to nothing. Re-index C++ projects after upgrading.
|
||||||
- In C++, a call on a range-based `for` loop's variable, like `suite->SetVerbose(verbose)` inside `for (ConformanceTestSuite *suite : suites)`, now links to the method of the type the loop declares. Before, the loop's declaration was never read, so the call was matched by the variable's name alone: in RocksDB, `log->file()` on a `log::Writer` went to `BlobLogWriter::file`, and a loop variable named like a member of its class was taken for that member. The variable counts only inside its loop. Re-index C++ projects after upgrading.
|
- In C++, a call on a range-based `for` loop's variable, like `suite->SetVerbose(verbose)` inside `for (ConformanceTestSuite *suite : suites)`, now links to the method of the type the loop declares. Before, the loop's declaration was never read, so the call was matched by the variable's name alone: in RocksDB, `log->file()` on a `log::Writer` went to `BlobLogWriter::file`, and a loop variable named like a member of its class was taken for that member. The variable counts only inside its loop. Re-index C++ projects after upgrading.
|
||||||
- In C++, a call made with `->` on a variable, parameter or member declared as a `std::unique_ptr`, `std::shared_ptr` or `std::optional`, like `iter->Valid()` on a `std::unique_ptr<Iterator> iter`, now links to that method of the type it holds, or of a class that type inherits from, with the type looked up the way C++ does it, so a class of the same name nested elsewhere is not mistaken for it. Before, the call went to whichever project method shared its name: in facebook/rocksdb, `iter->Valid()` reached `ArenaWrappedDBIter::Valid` and `fs->GetFileSize(…)` on a `std::shared_ptr<FileSystem>` reached a test's stand-in file system, and calls like `it->Next()` were often not linked at all. When what it holds is a template parameter or a standard-library type, like `std::unique_ptr<std::deque<…>>`, or a class that has no method of that name and neither do the classes it derives from, the call no longer links to a project method; a class derived from something that can't be followed, like a `decltype(…)`, may still have it, so that call is matched as before. The same goes for `->` on a container's iterator, like `it->data()` on a `std::deque<Slice>::const_iterator it` or `loc->path_size()` on protocolbuffers/protobuf's `RepeatedPtrField<SourceCodeInfo_Location>::iterator loc`: the call links to that method of the container's element type, and to no project method when the element is a standard-library type like `std::string`. That includes an iterator an `auto` variable gets from a standard-library container's `begin()`, `find()` and the like, or from a standard search like `std::lower_bound`, as in facebook/rocksdb's `auto it = logs_.begin(); it->IsSyncing()`. A call on a dereferenced iterator or pointer, like `(*it)->Visit()` over a `std::vector<Node*>` or `(*it).Weight()` over a `std::vector<Node>`, and on a variable a dereference sets, like `auto first = *nodes.begin()`, links the same way, where before such a call went to whichever class had a method of that name. So does a call on the key or value a map's iterator points at, like `it->second.Flush()`, `it->second->Flush()` or a range-based `for` loop's `kv.second.Flush()`, and on the `first` or `second` of a `std::pair` the code declares. So does a call on an element read by subscript, like `statuses[i].IsNotFound()` over a `std::vector<Status>`, `buffers[key].Flush()` over a map, `iters[i]->Valid()` over owning pointers, or an element of an array or of a class's `operator[]`. So does a call on what a container's `front()`, `back()` or `at()` hands out, like facebook/rocksdb's `edits.back()->MarkAtomicGroup(…)` or `wals.at(log_number).HasSyncedSize()` over a map, on a `std::stack`'s `top()` or a `std::queue`'s `front()`, and on what a smart pointer's `get()` or an optional's or `absl::StatusOr`'s `value()` returns, like `mock_clock.get()->GetCurrentTime(…)`, where before such calls linked to nothing; an overloaded function given such a value, like `test::PerThreadDBPath(env_.get(), …)`, now takes the overload for its type. Re-index C++ projects after upgrading.
|
- In C++, a call made with `->` on a variable, parameter or member declared as a `std::unique_ptr`, `std::shared_ptr` or `std::optional`, like `iter->Valid()` on a `std::unique_ptr<Iterator> iter`, now links to that method of the type it holds, or of a class that type inherits from, with the type looked up the way C++ does it, so a class of the same name nested elsewhere is not mistaken for it. Before, the call went to whichever project method shared its name: in facebook/rocksdb, `iter->Valid()` reached `ArenaWrappedDBIter::Valid` and `fs->GetFileSize(…)` on a `std::shared_ptr<FileSystem>` reached a test's stand-in file system, and calls like `it->Next()` were often not linked at all. When what it holds is a template parameter or a standard-library type, like `std::unique_ptr<std::deque<…>>`, or a class that has no method of that name and neither do the classes it derives from, the call no longer links to a project method; a class derived from something that can't be followed, like a `decltype(…)`, may still have it, so that call is matched as before. The same goes for `->` on a container's iterator, like `it->data()` on a `std::deque<Slice>::const_iterator it` or `loc->path_size()` on protocolbuffers/protobuf's `RepeatedPtrField<SourceCodeInfo_Location>::iterator loc`: the call links to that method of the container's element type, and to no project method when the element is a standard-library type like `std::string`. That includes an iterator an `auto` variable gets from a standard-library container's `begin()`, `find()` and the like, or from a standard search like `std::lower_bound`, as in facebook/rocksdb's `auto it = logs_.begin(); it->IsSyncing()`. A call on a dereferenced iterator or pointer, like `(*it)->Visit()` over a `std::vector<Node*>` or `(*it).Weight()` over a `std::vector<Node>`, and on a variable a dereference sets, like `auto first = *nodes.begin()`, links the same way, where before such a call went to whichever class had a method of that name. So does a call on the key or value a map's iterator points at, like `it->second.Flush()`, `it->second->Flush()` or a range-based `for` loop's `kv.second.Flush()`, and on the `first` or `second` of a `std::pair` the code declares. So does a call on an element read by subscript, like `statuses[i].IsNotFound()` over a `std::vector<Status>`, `buffers[key].Flush()` over a map, `iters[i]->Valid()` over owning pointers, or an element of an array or of a class's `operator[]`. So does a call on what a container's `front()`, `back()` or `at()` hands out, like facebook/rocksdb's `edits.back()->MarkAtomicGroup(…)` or `wals.at(log_number).HasSyncedSize()` over a map, on a `std::stack`'s `top()` or a `std::queue`'s `front()`, and on what a smart pointer's `get()` or an optional's or `absl::StatusOr`'s `value()` returns, like `mock_clock.get()->GetCurrentTime(…)`, where before such calls linked to nothing; an overloaded function given such a value, like `test::PerThreadDBPath(env_.get(), …)`, now takes the overload for its type. Re-index C++ projects after upgrading.
|
||||||
|
|||||||
@@ -0,0 +1,150 @@
|
|||||||
|
/**
|
||||||
|
* A C++ call on a data member reached through a member chain, like rocksdb's
|
||||||
|
* `options.cf_paths.emplace_back(…)` on a `std::vector<DbPath>` or
|
||||||
|
* `props->rep.db_id.size()` on a `std::string`, calls the library type's own
|
||||||
|
* method, none of the project's. The extractor records such a call by its
|
||||||
|
* bare name, so it went to any project method of that name: rocksdb's
|
||||||
|
* `autovector::emplace_back`, `BlobContents::size`.
|
||||||
|
*/
|
||||||
|
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 of a type from outside the project', () => {
|
||||||
|
let root: string;
|
||||||
|
let cg: CodeGraph;
|
||||||
|
|
||||||
|
beforeAll(async () => {
|
||||||
|
root = fs.mkdtempSync(path.join(os.tmpdir(), 'codegraph-cpp-member-chain-'));
|
||||||
|
const write = (file: string, text: string): void => {
|
||||||
|
fs.mkdirSync(path.dirname(path.join(root, file)), { recursive: true });
|
||||||
|
fs.writeFileSync(path.join(root, file), text);
|
||||||
|
};
|
||||||
|
// The project's namesakes of library methods, which a call matched by its bare name lands on.
|
||||||
|
write('src/types.h', lines(
|
||||||
|
'namespace rocksdb {',
|
||||||
|
'template <class T, int kSize = 8>',
|
||||||
|
'class autovector {',
|
||||||
|
' public:',
|
||||||
|
' void emplace_back(T v) {}',
|
||||||
|
' void clear() {}',
|
||||||
|
' int size() const { return 0; }',
|
||||||
|
'};',
|
||||||
|
'class BlobContents {',
|
||||||
|
' public:',
|
||||||
|
' int size() const { return 0; }',
|
||||||
|
'};',
|
||||||
|
'} // namespace rocksdb',
|
||||||
|
'namespace proto {',
|
||||||
|
'class Option {',
|
||||||
|
' public:',
|
||||||
|
' bool has_value() const { return false; }',
|
||||||
|
'};',
|
||||||
|
'} // namespace proto',
|
||||||
|
'namespace util {',
|
||||||
|
'class Status {',
|
||||||
|
' public:',
|
||||||
|
' bool ok() const { return true; }',
|
||||||
|
'};',
|
||||||
|
'} // namespace util',
|
||||||
|
'class FilePtr {',
|
||||||
|
' public:',
|
||||||
|
' void reset() {}',
|
||||||
|
'};',
|
||||||
|
'class Engine {',
|
||||||
|
' public:',
|
||||||
|
' void Run() {}',
|
||||||
|
'};',
|
||||||
|
));
|
||||||
|
write('src/use.cc', lines(
|
||||||
|
'#include <memory>',
|
||||||
|
'#include <optional>',
|
||||||
|
'#include <string>',
|
||||||
|
'#include <vector>',
|
||||||
|
'#include "absl/status/status.h"',
|
||||||
|
'#include "src/types.h"',
|
||||||
|
'struct Options {',
|
||||||
|
' std::vector<int> cf_paths;',
|
||||||
|
' std::string name;',
|
||||||
|
'};',
|
||||||
|
'struct Rep {',
|
||||||
|
' std::string db_id;',
|
||||||
|
'};',
|
||||||
|
'struct Props {',
|
||||||
|
' Rep rep;',
|
||||||
|
'};',
|
||||||
|
'struct State {',
|
||||||
|
' std::optional<int> child_pid;',
|
||||||
|
' std::shared_ptr<Engine> engine;',
|
||||||
|
' Engine plain;',
|
||||||
|
' absl::Status status;',
|
||||||
|
'};',
|
||||||
|
'void Library(Options& options) { options.cf_paths.emplace_back(1); options.name.size(); }',
|
||||||
|
'void TwoHops(Props* props) { props->rep.db_id.size(); }',
|
||||||
|
'void Optional(State* state) { state->child_pid.has_value(); }',
|
||||||
|
'void HolderOwn(State& state) { state.engine.reset(); }',
|
||||||
|
'void OutsideQualified(State& state) { state.status.ok(); }',
|
||||||
|
'void HolderArrow(State& state) { state.engine->Run(); }',
|
||||||
|
'void ProjectMember(State& state) { state.plain.Run(); }',
|
||||||
|
'class Cache {',
|
||||||
|
' public:',
|
||||||
|
' void Touch() { this->keys_.clear(); this->engine_->Run(); }',
|
||||||
|
' private:',
|
||||||
|
' std::vector<int> keys_;',
|
||||||
|
' Engine* engine_;',
|
||||||
|
'};',
|
||||||
|
'template <class T>',
|
||||||
|
'struct Box {',
|
||||||
|
' std::vector<T> items;',
|
||||||
|
' std::unique_ptr<T> owner;',
|
||||||
|
'};',
|
||||||
|
'template <class T>',
|
||||||
|
'void Drain(Box<T>& box) { box.items.clear(); box.owner.reset(); }',
|
||||||
|
));
|
||||||
|
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 or method `name`, as `qualifiedName`. */
|
||||||
|
const calls = (name: string): string[] => {
|
||||||
|
const caller = [...cg.getNodesByKind('function'), ...cg.getNodesByKind('method')].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 library container's or string's method reached through a member", () => {
|
||||||
|
expect(calls('Library')).toEqual([]);
|
||||||
|
expect(calls('TwoHops')).toEqual([]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("an optional's method, and a smart pointer's own through `.`", () => {
|
||||||
|
expect(calls('Optional')).toEqual([]);
|
||||||
|
expect(calls('HolderOwn')).toEqual([]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('a type under a namespace the project declares nothing in', () => {
|
||||||
|
expect(calls('OutsideQualified')).toEqual([]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("`->` on a smart pointer, and a member of a project class's type, still call the project's", () => {
|
||||||
|
expect(calls('HolderArrow')).toEqual(['Engine::Run']);
|
||||||
|
expect(calls('ProjectMember')).toEqual(['Engine::Run']);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('a chain through `this`', () => {
|
||||||
|
expect(calls('Touch')).toEqual(['Engine::Run']);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("a library template of a template parameter's type is still the library's", () => {
|
||||||
|
expect(calls('Drain')).toEqual([]);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -11025,6 +11025,62 @@ function cppUnspelledReceiver(ref: UnresolvedRef, context: ResolutionContext): {
|
|||||||
return index === null ? null : at(index);
|
return index === null ? null : at(index);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** A member chain and the operator after it: `props->rep.db_id` and `.`, `this->mu_` and `->`. */
|
||||||
|
const CPP_MEMBER_CHAIN = /^([A-Za-z_]\w*(?:\s*(?:\.|->)\s*[A-Za-z_]\w*)*)\s*(\.|->)\s*(?:template\s+)?/;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The member chain a C++ call the extractor recorded by its bare name is made
|
||||||
|
* on, with the operator after it — `options.cf_paths` and `.` for
|
||||||
|
* `options.cf_paths.emplace_back(…)`, `props->rep.db_id` and `.` for
|
||||||
|
* `props->rep.db_id.size()` — read at the call's column, where the chain
|
||||||
|
* starts. Null for any other call, a receiver of one name among them (the
|
||||||
|
* extractor spells those).
|
||||||
|
*/
|
||||||
|
function cppMemberChainReceiver(ref: UnresolvedRef, context: ResolutionContext): { expression: string; op: string } | null {
|
||||||
|
const raw = (context.getFileLines?.(ref.filePath) ?? context.readFile(ref.filePath)?.split(/\r?\n/))?.[ref.line - 1];
|
||||||
|
const line = raw === undefined ? null : cppCodeOf(cppLiteralsBlanked(raw, ref.line, cppNonCodeLines(ref.filePath, context)));
|
||||||
|
if (!line) return null;
|
||||||
|
const at = (column: number): { expression: string; op: string } | null => {
|
||||||
|
const chain = CPP_MEMBER_CHAIN.exec(line.slice(column));
|
||||||
|
if (!chain || !/\.|->/.test(chain[1]!)) return null;
|
||||||
|
const rest = line.slice(column + chain[0].length);
|
||||||
|
if (!rest.startsWith(ref.referenceName) || /[\w$]/.test(rest.charAt(ref.referenceName.length))) return null;
|
||||||
|
return cppArgumentListFollows(rest, ref.referenceName.length) ? { expression: chain[1]!, op: chain[2]! } : null;
|
||||||
|
};
|
||||||
|
const found = at(Math.max(0, ref.column));
|
||||||
|
if (found || !/[^\x00-\x7f]/.test(line)) return found;
|
||||||
|
const index = cppUtf8Column(line, ref.column);
|
||||||
|
return index === null ? null : at(index);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A C++ call on a data member reached through a member chain, which the
|
||||||
|
* extractor records by its bare name, calls no project method when that
|
||||||
|
* member's type is from outside the project: a library type
|
||||||
|
* (isCppLibraryType) or one under a namespace the project declares nothing
|
||||||
|
* in (isCppOutsideQualifiedType). By its bare name the call went to any
|
||||||
|
* project method of that name: rocksdb's `options.cf_paths.emplace_back(…)`
|
||||||
|
* on a `std::vector<DbPath>` reached `autovector::emplace_back`,
|
||||||
|
* `props->rep.db_id.size()` on a `std::string` `BlobContents::size`, and
|
||||||
|
* protobuf's `state_->child_pid.has_value()` on a `std::optional<pid_t>`
|
||||||
|
* `Option::has_value`. Unlike a call chain's, a member chain's operator is
|
||||||
|
* read where it is written, so a library template of a template parameter is
|
||||||
|
* no matter: `.` calls a member of `std::vector<T>` itself, and `->` on a
|
||||||
|
* `std::unique_ptr<T>` one of `T`, which is none of the project's to tell.
|
||||||
|
* - null for such a member: no project method is called.
|
||||||
|
* - undefined for any other call, or a member whose type can't be read or is
|
||||||
|
* the project's: the name strategies decide, as before.
|
||||||
|
*/
|
||||||
|
function matchCppOutsideMemberChainCall(ref: UnresolvedRef, context: ResolutionContext): null | undefined {
|
||||||
|
const receiver = cppMemberChainReceiver(ref, context);
|
||||||
|
if (!receiver) return undefined;
|
||||||
|
const object = cppMemberObject(receiver.expression, receiver.op, ref, context, 0);
|
||||||
|
if (!object || isCppPointerType(object.text)) return undefined;
|
||||||
|
if (isCppLibraryType(object.text, context)) return null;
|
||||||
|
return cppClassWritten(object.text, object, ref, context) === undefined &&
|
||||||
|
isCppOutsideQualifiedType(object.text, object, context) ? null : undefined;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A C++ call the extractor recorded by its bare name on a receiver it drops
|
* A C++ call the extractor recorded by its bare name on a receiver it drops
|
||||||
* (cppUnspelledReceiver) — `(*it)->Visit()` over a `std::vector<Node*>`,
|
* (cppUnspelledReceiver) — `(*it)->Visit()` over a `std::vector<Node*>`,
|
||||||
@@ -16970,6 +17026,10 @@ function matchReferenceInner(
|
|||||||
if (ref.language === 'cpp' && ref.referenceKind === 'calls' && /^[A-Za-z_]\w*$/.test(ref.referenceName)) {
|
if (ref.language === 'cpp' && ref.referenceKind === 'calls' && /^[A-Za-z_]\w*$/.test(ref.referenceName)) {
|
||||||
const unspelled = nmTimedT('cppUnspelled', ref, () => matchCppUnspelledReceiverCall(ref, context));
|
const unspelled = nmTimedT('cppUnspelled', ref, () => matchCppUnspelledReceiverCall(ref, context));
|
||||||
if (unspelled !== undefined) return unspelled;
|
if (unspelled !== undefined) return unspelled;
|
||||||
|
// So is one on a member reached through a chain, `options.cf_paths.emplace_back(…)`,
|
||||||
|
// when that member's type is from outside the project.
|
||||||
|
const outside = nmTimedT('cppOutsideMemberChain', ref, () => matchCppOutsideMemberChainCall(ref, context));
|
||||||
|
if (outside !== undefined) return outside;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Try strategies in order of confidence
|
// Try strategies in order of confidence
|
||||||
|
|||||||
Reference in New Issue
Block a user