mirror of
https://github.com/colbymchenry/codegraph.git
synced 2026-10-28 22:52:00 +08:00
fix(cpp): -> on a value of one of the project's classes follows its operator->, like rocksdb's FileSystemPtr and MultiGetContext::Range::Iterator (#2735)
C++ reads `x->m` on a class value as `x.operator->()->m`, and applies `->` again to a class it returns. Only a library holder's `->` and a container iterator's (`std::vector<T>::iterator`) were read. A call through a project iterator or smart-pointer wrapper was guessed by the method's name: - rocksdb's `fs_->NewWritableFile(…)` on a `FileSystemPtr`, whose `operator->` returns a `std::shared_ptr<FileSystem>`, reached `FaultInjectionTestFS`; - `writable_file_->Truncate(…)` on an `FSWritableFilePtr` reached the legacy `WritableFile::Truncate`; - `iter->lkey->user_key()` through `MultiGetContext::Range::Iterator`, whose `KeyContext* operator->()` is a `KeyContext*`, reached `BlobCountingIterator::user_key`. Both a member chain's `->` and a `->` call on a declared receiver end in cppIteratorTarget. For a type that is no container's iterator, it now falls back to cppOperatorTarget: - what the class's `operator->` returns, through the pointer it is, or with `->` applied again to a class it is: a library holder, or another of these; - else what its `operator*` returns; - the return type is read where the class declares it, and the class's own template parameters are bound to the type's written arguments (cppTemplateBound): `UnownedPtr<DB>`'s `T* operator->()` is a `DB*`; - a parameter the type doesn't bind names no particular class. A log-only census on mainbdf288a7found 304 rocksdb and 42 protobuf dereferences of a project class with an `operator->` that gave no element. Among the rocksdb calls, 77 were bare-name guesses and 56 were unlinked. Kit A/B againstbdf288a7: | repo | removed | added | retargeted | same target, now typed | |---|---|---|---|---| | rocksdb | 2 | 49 | 158 | 89 | | protobuf | 0 | 0 | 7 | 3 | | leveldb, fmt, redis | identical | | | | - Retargeted: - FaultInjectionTestFS → FileSystem (57), WritableFile → FSWritableFile (32), and TruncatedRangeDelIterator through UnownedPtr (16); - LookupKey and GetContext through MultiGetContext's iterator; - SequentialFile and RandomAccessFile → FSSequentialFile and FSRandomAccessFile; - protobuf's `map_field_->` calls from WeakRepeatedPtrField to the map field bases; - overloads (DB::Put, PinnableSlice) chosen on the now-known class. - Removed: `iter->timestamp->assign(…)` on a `std::string*`, which had reached `autovector::assign`. - Added: Status::PermitUncheckedError, FileSystem::OptimizeFor…, Cleanable::RegisterCleanup, and gtest's ParamGeneratorInterface through linked_ptr. - About 40 changed sites read by hand were all right. Mutations: dropping the fallback, the operator* fallback, the second `->` or the template binding each fail the new tests. Accepting an inexact class, found only as the sole one of its name, survives; the guard stays, as in the member-chain matcher. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
4e35e0cb78
commit
0c28480576
@@ -14610,16 +14610,70 @@ function cppArrowTarget(
|
||||
|
||||
/**
|
||||
* The element a container's iterator of the type `iterator` points at, read
|
||||
* from the container's template arguments (cppArrowTarget). Null for any
|
||||
* other type, and for an element a class template's own `begin()` or
|
||||
* from the container's template arguments (cppArrowTarget), or what `->` on
|
||||
* a value of one of the project's classes reaches (cppOperatorTarget). Null
|
||||
* for any other type, and for an element a class template's own `begin()` or
|
||||
* `operator*` writes with its template parameter.
|
||||
*/
|
||||
function cppIteratorTarget(iterator: CppWrittenType, at: UnresolvedRef, context: ResolutionContext): CppWrittenType | null {
|
||||
const container = CPP_ITERATOR_OF.exec(cppWithoutTypeKeywords(iterator.text))?.[1];
|
||||
const element = container ? cppRangeElementOf({ ...iterator, text: container }, at, context, 1) : null;
|
||||
const element = container ? cppRangeElementOf({ ...iterator, text: container }, at, context, 1) : cppOperatorTarget(iterator, at, context);
|
||||
return element && (element.scope === iterator.scope || !isCppTemplateParameterType(element)) ? element : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* What `->` on a value of one of the project's classes of the type `type`
|
||||
* reaches, as C++ reads `x->m`: what the class's `operator->` returns,
|
||||
* through the pointer it is, or with `->` applied again to a class it is (a
|
||||
* library holder's held type, or another of these); else what its
|
||||
* `operator*` returns. The return type is read where the class declares it,
|
||||
* with the class's own template parameters bound to the arguments `type` is
|
||||
* written with (cppTemplateBound). rocksdb's `MultiGetContext::Range::Iterator`
|
||||
* declares `KeyContext* operator->()`, its `UnownedPtr<DB>` a `T*
|
||||
* operator->()`, a `DB*`, and its `FileSystemPtr` returns a
|
||||
* `std::shared_ptr<FileSystem>`. Null when the class declares neither, or
|
||||
* what it returns can't be read.
|
||||
*/
|
||||
function cppOperatorTarget(type: CppWrittenType, at: UnresolvedRef, context: ResolutionContext, hops = 0): CppWrittenType | null {
|
||||
const written = cppWithoutTypeKeywords(type.text);
|
||||
if (hops > 2 || !written || isCppPointerType(written) || isCppLibraryType(written, context)) return null;
|
||||
const found = cppClassWritten(written, type, at, context);
|
||||
if (!found?.exact || !CPP_CLASS_KINDS.has(found.node.kind)) return null;
|
||||
const arrow = cppMethodsOf(found.node, 'operator->', at, context);
|
||||
const returned = cppReturnTypeOf(arrow.length > 0 ? arrow : cppMethodsOf(found.node, 'operator*', at, context), context);
|
||||
const bound = returned && cppTemplateBound(returned, found.node, { ...type, text: written }, at, context);
|
||||
if (!bound) return null;
|
||||
if (arrow.length === 0) return { ...bound, text: bound.text.replace(/&+\s*$/, '').trim() };
|
||||
return cppPointee(bound) ?? cppOperatorTarget(bound, at, context, hops + 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* The type `returned`, which the class template `cls` writes, with `cls`'s
|
||||
* own template parameters bound to the arguments of `type`, a specialization
|
||||
* of it: the `T*` of `UnownedPtr<T>` is a `DB*` for an `UnownedPtr<DB>`. An
|
||||
* alias the class declares (`pointer`) is followed first, and an argument is
|
||||
* looked up where `type` is written. A type of a class that is no template
|
||||
* comes back as written; null for a parameter `type` doesn't bind.
|
||||
*/
|
||||
function cppTemplateBound(returned: CppWrittenType, cls: Node, type: CppWrittenType, at: UnresolvedRef, context: ResolutionContext): CppWrittenType | null {
|
||||
const params = cppOwnTemplateParameters(cls, context);
|
||||
if (params.length === 0) return returned;
|
||||
const args = cppTemplateArguments(type.text) ?? [];
|
||||
const followed = cppFollowWrittenAliases(returned, at, context) ?? returned;
|
||||
const core = followed.text.replace(/\b(?:const|volatile|typename)\b|[*&]/g, ' ').trim();
|
||||
const index = params.indexOf(core);
|
||||
if (index >= 0) {
|
||||
const arg = args[index]?.trim();
|
||||
return arg ? { ...type, text: followed.text.replace(new RegExp(`\\b${core}\\b`), arg) } : null;
|
||||
}
|
||||
let text = followed.text;
|
||||
params.forEach((param, i) => {
|
||||
const arg = args[i]?.trim();
|
||||
if (param && arg) text = text.replace(new RegExp(`\\b${param}\\b`, 'g'), arg);
|
||||
});
|
||||
return { ...followed, text };
|
||||
}
|
||||
|
||||
/**
|
||||
* What dereferencing a value of the type `type` gives, for `*x` and `x->`:
|
||||
* an array's first element, what a pointer points at or a holder holds
|
||||
|
||||
Reference in New Issue
Block a user