mirror of
https://github.com/colbymchenry/codegraph.git
synced 2026-10-28 22:52:00 +08:00
fix(c): a C call, like redis's mstime(), reaches the function its own file can see, past another file's static copy (#2613)
A C call now links to the function of its name the call's own file can see:
its own, or one a header it includes defines, else one with external
linkage, the best placed of them (cppFunctionOverload's C branch). Before,
the name strategies took the function nearest the call; when that was
another source file's `static`, isVisibleAcrossFiles dropped it and the
call was left unlinked, so no call reached redis's `server.c` `mstime()`
or `ustime()`.
- A source file's `static` function is visible to a file that `#include`s
that source file: hiredis's async.c includes dict.c, redis's ae.c its
event-loop backend.
- The coordinator's `resolveImport` hook falls back to matchByFilePath for
a C/C++ `#include` the import resolver can't place, as resolveOneInner
already does, so cppIncluders sees includes only the end of their path
names: protobuf's `google/protobuf/descriptor.upb.h`, which two generated
copies end in, and jemalloc's `jemalloc/internal/*.h`. Standard headers
are left out: otherwise `#include <math.h>` makes jemalloc's
test/math.h visible to its own include through isIncludedCppTestSuite.
A/B on the cgstd kit against main 4a591aee: leveldb, fmt and rocksdb
identical; redis +194 calls linked and 115 retargeted (114 to hiredis
alloc.h's POSIX `static inline`s, 1 known miss: lua-cjson's fpconv_init to
an inline only `#ifdef USE_INTERNAL_FPCONV` builds use); protobuf 412 upb
calls retargeted from the unincluded php-upb.h amalgamation to the headers
the calling file includes.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
4a591aeee8
commit
dc4ccf6b14
@@ -3,8 +3,10 @@
|
||||
* then the files that include those, out to the translation units.
|
||||
*
|
||||
* Read from the `import` node extraction mints for every `#include`, each
|
||||
* resolved by the import resolver exactly as the graph's include edge is, so
|
||||
* "includes" here is the include edge a reader sees. The name matcher asks it
|
||||
* resolved by the coordinator's import lookup as the graph's include edge is
|
||||
* (by the end of its path when nothing else places it, and never to a project
|
||||
* file for a standard header like `<math.h>`), so "includes" here is the
|
||||
* include edge a reader sees. The name matcher asks it
|
||||
* whether a C or C++ file named like a test is part of a reference's
|
||||
* translation unit: protobuf's conformance framework lives in
|
||||
* `conformance/conformance_test.h` and `test_runner.h`, which the suites and
|
||||
|
||||
+10
-2
@@ -21,7 +21,7 @@ import {
|
||||
isImportableKind,
|
||||
CPP_DEFINE_SIGNATURE,
|
||||
} from './types';
|
||||
import { isPythonSelfCall, matchJsStoreBindingCall, isUnresolvedJsMemberCall, matchObjectPathCall, thisScopeCaller, isVisibleAcrossFiles, matchReference, matchFunctionRef, matchDottedCallChain, matchScopedCallChain, matchMethodCall, sameLanguageFamily, crossesCodeBoundary, gateLanguageMatch, dumpNameMatcherProfile, clearNameMatcherMemos, isRustNameInScope, CASE_INSENSITIVE_LANGUAGES, isDartMemberRead, matchDartMemberRead, isDartChainLink, matchDartChainLink, isDartAnnotation, matchDartAnnotation, isStdMethodName, isGoUnknownQualified, isGoProjectLocalCall, GO_STDLIB_PACKAGES, isGoBareName, goTypePositionTarget, GO_TYPE_KINDS, matchGoAssertedCall, isGoAssertedLocal } from './name-matcher';
|
||||
import { isPythonSelfCall, matchJsStoreBindingCall, isUnresolvedJsMemberCall, matchObjectPathCall, thisScopeCaller, isVisibleAcrossFiles, matchReference, matchByFilePath, matchFunctionRef, matchDottedCallChain, matchScopedCallChain, matchMethodCall, sameLanguageFamily, crossesCodeBoundary, gateLanguageMatch, dumpNameMatcherProfile, clearNameMatcherMemos, isRustNameInScope, CASE_INSENSITIVE_LANGUAGES, isDartMemberRead, matchDartMemberRead, isDartChainLink, matchDartChainLink, isDartAnnotation, matchDartAnnotation, isStdMethodName, isGoUnknownQualified, isGoProjectLocalCall, GO_STDLIB_PACKAGES, isGoBareName, goTypePositionTarget, GO_TYPE_KINDS, matchGoAssertedCall, isGoAssertedLocal } from './name-matcher';
|
||||
import { isVisibleCppMacro, clearCppMacroVisibility } from './cpp-macro-visibility';
|
||||
import { isCppConstructorRef, matchCppConstructor } from './cpp-constructor';
|
||||
import { isCppSupertypeRef, matchCppSupertype, clearCppSupertypeMemos } from './cpp-supertypes';
|
||||
@@ -668,7 +668,15 @@ export class ReferenceResolver {
|
||||
*/
|
||||
private createContext(): ResolutionContext {
|
||||
return {
|
||||
resolveImport: (ref) => resolveViaImport(ref, this.context),
|
||||
// A C or C++ `#include` the import resolver can't place is linked by the
|
||||
// end of its path, as resolveOneInner's name matching does: protobuf's
|
||||
// `google/protobuf/descriptor.upb.h`, which two generated copies end in.
|
||||
// cppIncluders reads includes through this, so it sees the include edges
|
||||
// a reader sees. A standard header is never the project's: `<math.h>`
|
||||
// is not jemalloc's test/math.h, which only its includers may see.
|
||||
resolveImport: (ref) => resolveViaImport(ref, this.context) ??
|
||||
((ref.language === 'c' || ref.language === 'cpp') && ref.referenceKind === 'imports' &&
|
||||
!isExternalImport(ref.referenceName, ref.language, this.context) ? matchByFilePath(ref, this.context) : null),
|
||||
// A path alias makes an import the project's only when it maps the
|
||||
// import to a file the index holds. Matching its prefix is not enough:
|
||||
// cord-field's `"*": ["./typings/*"]` matches every package, and
|
||||
|
||||
@@ -3008,7 +3008,10 @@ export function isVisibleAcrossFiles(candidate: Node, ref: UnresolvedRef, contex
|
||||
(!isCsharpTypeVisible(candidate, ref, context) || !isCsharpNestedTypeInScope(candidate, ref, context))) return false;
|
||||
const lang = candidate.language as string;
|
||||
if (lang === 'c' || lang === 'cpp') {
|
||||
if (candidate.kind === 'function' && C_SOURCE_EXT.test(candidate.filePath) && isStaticCFunction(candidate, context)) return false;
|
||||
// A source file's `static` function, only for that file and one that
|
||||
// `#include`s it: hiredis's async.c includes dict.c.
|
||||
if (candidate.kind === 'function' && C_SOURCE_EXT.test(candidate.filePath) && isStaticCFunction(candidate, context) &&
|
||||
!cppIncluders(candidate.filePath, context).has(ref.filePath)) return false;
|
||||
return !isCppTranslationUnitLocal(candidate, ref, context);
|
||||
}
|
||||
if (lang === 'go') {
|
||||
@@ -11211,14 +11214,23 @@ function cppArgumentTypeChoice(tied: Node[], name: string, args: string[], ref:
|
||||
* where it is; protobuf's `ParseWithLimit(limit, data, result)` on an
|
||||
* `int32_t result` is the overload taking an `int32_t&`, not the
|
||||
* `int64_t&` one nearer the call. A function hides a class of its name, so
|
||||
* a call is to the function when `chosen` is that class. `chosen` itself
|
||||
* for anything but a C++ call, or when no function is in sight.
|
||||
* a call is to the function when `chosen` is that class. C has no
|
||||
* overloads: a C call reaches the function of the name its file sees — its
|
||||
* own or one a header it includes defines, else one with external linkage —
|
||||
* the best placed of them (findBestMatch). jemalloc's tests call the
|
||||
* `static inline do_arena_create` of the arena_util.h they include, not a
|
||||
* neighbouring test's own `static do_arena_create`. `chosen` itself for
|
||||
* anything but a C or C++ call, or when no function is in sight.
|
||||
*/
|
||||
function cppFunctionOverload(chosen: Node, candidates: Node[], ref: UnresolvedRef, context: ResolutionContext): Node {
|
||||
if (ref.language !== 'cpp' || ref.referenceKind !== 'calls') return chosen;
|
||||
if ((ref.language !== 'cpp' && ref.language !== 'c') || ref.referenceKind !== 'calls') return chosen;
|
||||
const overloads = candidates.filter((n) => n.kind === 'function' && n.qualifiedName === chosen.qualifiedName &&
|
||||
isVisibleAcrossFiles(n, ref, context));
|
||||
if (overloads.length === 0) return chosen;
|
||||
if (ref.language === 'c') {
|
||||
const own = overloads.filter((n) => n.filePath === ref.filePath || cppIncluders(n.filePath, context).has(ref.filePath));
|
||||
return findBestMatch(ref, own.length > 0 ? own : overloads, context) ?? chosen;
|
||||
}
|
||||
return cppOverloadFor(overloads.includes(chosen) ? [chosen, ...overloads.filter((n) => n !== chosen)] : overloads, chosen.name, ref, context);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user