246aee837341183912c82b3e727410e9fe1a1567
14
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
703629edc3 |
feat(c/c++): resolve function-pointer command tables — macro-built, conditional-compilation & bare arrays (#991) (#1003)
* feat(c/c++): resolve macro-built function-pointer command tables (#991) C/C++ commands dispatched through macro-built function-pointer tables were dead-ends in the graph: redis' `call` never showed up as a caller of any command (`c->cmd->proc(c)`), because the table is generated into a #included `.def`, the handler is buried inside `MAKE_CMD(...)`, the struct type is itself a macro alias, the `proc` field uses a function-TYPE typedef, and the receiver is a chained field access. #954 deferred exactly this shape. Six composable additions to c-fnptr-synthesizer.ts close it: - function-type typedefs (`typedef RET T(...)` + `T *f`) flag the field as a function pointer; - multi-declarator fields (`struct redisCommand *cmd, *last`) each count as a slot/type (needed for positional alignment and the chain walk); - chained/array receivers (`c->cmd->proc`) resolve through field types across all same-named struct layouts (redis has two unrelated `client` structs); - `#include "x"` directives are followed (from raw source) so a non-indexed `.def` is read as a registration unit with the includer's effective macro env; - function-like + object-like macros are expanded (params->args, type aliases) before positional/designated registration; - a macro that expands to a brace-wrapped element (sqlite `FUNCTION(...)`) has one outer brace layer peeled. Validated on two independent macro-table lineages at 100% target precision: redis (209 commands via redisCommand.proc, `call`->every command) and sqlite (69 FuncDef.xSFunc targets). No regression on the controls: git (cmd_struct.fn, 138 builtins), curl (Curl_cftype.*), lua (0). 0 non-function targets across all five; +3 synthetic fixtures; full suite green. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(c/c++): resolve conditional-compilation command tables (vim) (#991) Vim's `:ex` and normal-mode command tables are the hardest fn-pointer-table shape: the struct is defined INLINE with the array, the whole thing is behind `#ifdef DO_DECLARE_EXCMD`/`DO_DECLARE_NVCMD` (switched on by the includer), built by a macro the file conditionally redefines (`EXCMD`/`NVCMD` = the table element under the switch, a bare enum id otherwise), and dispatched by a parenthesized array subscript through a file-scope table: `(cmdnames[i].cmd_func)(&ea)`. Four more composable additions on top of the macro-table work: - a focused `#ifdef`/`#ifndef`/`#if defined`/`#else`/`#elif`/`#endif` evaluator drops inactive arms (unevaluable `#if EXPR` keeps its body); an indexed header is re-scanned in an includer's context only when that includer #defines a switch the header guards, with the include's macros re-read from the resolved text (the plain last-wins parse picks the wrong, enum, arm); - inline `struct TAG {…} var[] = {…}` tables whose struct never became a node are parsed in place and registered; - array-subscript receivers (`tbl[i].f`) strip the subscript and resolve the base through a global-var → struct-type map; - an optional `)` before the call covers the parenthesized `(….f)(args)` form. Validated on vim: 273 `:ex` commands (`do_one_cmd`→every command) + 67 normal-mode commands, 0 non-function targets, 0 cross-table misroute (registering both tables is what stops `normal_cmd`'s `nv_cmds[i].cmd_func` from falling back to the `cmdname` owner of the shared field name). Controls unchanged at 0 non-function (redis/sqlite/git/curl gain coverage from array/global dispatch, lua still 0); +1 synthetic fixture; full suite green. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(c/c++): resolve bare arrays of function pointers (#991) The C/C++ fn-pointer synthesizer keyed everything on (struct type, fn-pointer field), so a dispatch through a bare array of function pointers — no struct, no field — was unbridged: an opcode/handler table like `static op_t *opcodes[256] = {nop,…}` invoked `opcodes[op](…)` left every handler with zero callers. Closes the last #991 deferred item. Keyed by the array VARIABLE name (a new `arrayReg`, parallel to the struct `reg`). Registration detects an array whose element type is a function typedef — a function-TYPE typedef element (`opcode_t *ops[]`, the `*` making it an array of pointers) or a function-pointer typedef element (`zend_rc_dtor_func_t t[]`) — and reads its literal entries, whether positional (`fn`/`&fn`), designated by index (`[IDX]=fn`), or cast-wrapped (`(cast)fn`). Dispatch is `tbl[i](…)` / `(*tbl[i])(…)`, gated on `tbl` being a known fn-pointer array (the precision anchor); the fan-out reaches the whole set (a runtime subscript hits any entry), like a command table. The same-file table wins on a name collision, so two file-local `static opcodes[256]` (SameBoy's CPU + disassembler) never cross. The fn-pointer typedef/field regexes now also tolerate a calling-convention macro before the `*` (`(ZEND_FASTCALL *name)`), which hardens the existing struct-field path too. Validated on two independent lineages: SameBoy (GB emulator) — 147 edges via `opcodes[]`, 0 cross-file leak; php-src (Zend) — 54 edges across 7 tables in the designated+cast+CC-typedef form. Control: lua 0 — its `lua_CFunction searchers[]` is pushed into the VM, never C-dispatched, so the call-gate fires nothing. No regression on the #991 corpus: redis (835) / sqlite (683) struct edges byte-identical, git +3 / curl +20 legitimate new bare-array edges, vim 433 with all guards holding; 0 non-function targets across all. + 4 fixtures. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
ba209d9489 |
feat(c/c++): resolve function-pointer dispatch (#932) (#954)
C/C++ polymorphism is the function pointer: a struct fn-pointer field, concrete
functions registered into it through a table (`{"add", cmd_add}`), a designated
initializer (`.handler = on_open`), or an assignment, then dispatched indirectly
(`p->fn(argv)`). Static extraction captures neither the registration→field
binding nor the indirect call, so the dispatcher→handler edge was missing — git's
run_builtin looked like it called nothing, a vtable's implementations had no
callers, and the hook_demo.c in the issue was unreachable.
Add a resolution-layer synthesizer keyed by (struct type, fn-pointer field). It
reads source (the established Celery/Sidekiq/Spring pattern — C extraction has no
struct fields or indirect-call edges to build on) in passes: collect fn-pointer
typedefs, parse struct field layouts, collect registrations (positional matched
by field index, designated, and assignment), propagate field←field assignments
(so a generic hook slot reassigned from a registry — the hook_demo.c
`h->func = found->fn` shape — inherits the registry field's handlers), then link
each indirect dispatch site to the registered handlers. Receiver type resolves
from the enclosing function's params/locals, falling back to a field name unique
to one struct. Covers both the command-table idiom (git, redis) and the
ops-struct/vtable idiom (curl content-encoders, protocol handlers).
Pure edge synthesis (no node growth); high precision via the (struct, field) key.
Validated: git 502 edges (run_builtin→cmd_* plus git_hash_algo/archiver/reftable
vtables), redis 357 (dictType.hashFunction, connection + reply-object vtables),
curl 478 (Curl_cwtype.do_init → deflate/gzip/brotli/zstd); 0 non-function targets
on all three; node-stable; 0 on the lua control (its {name,fn} tables register
into the Lua VM, with no C indirect call to bridge). Full suite 1665 pass.
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
||
|
|
b5090cbad5 |
docs(dispatch-backlog): shelve trezor barrel-registry as single-lineage/overfit
Discovery across 15 independent diverse repos + GitHub-wide code search found the strict barrel-namespace shape (`import * as M from './api'` -> `M[runtimeKey]` -> `new` -> `.run()`) in exactly 2 repos: trezor-suite and OneKey hardware-js-sdk. But OneKey is a @trezor/connect fork (same findMethod/MethodConstructor skeleton), so it's 2 indexable repos but one design lineage = effectively n=1. Every independent registry-by-runtime-key found is a different shape the trezor-tuned synth wouldn't catch (n8n dynamic-import+DI, polkadot array-of-constructors, ccxt object-literal [already covered], typeorm/xrpl switch). The synth is the hard tier (cross-file barrel re-export enumeration + computed index + camel/Pascal transform + entry-method fan-out) -- meaningful complexity for a single-lineage win, which the overfit discipline says not to build. Feasibility was fine (the import resolver already chases re-export barrels); the blocker is corpus thinness. Reopen only if an independent (non-trezor-lineage) repo appears. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
feb2f641de |
feat(resolution): bridge Laravel event(new X) to its listener handles
Laravel decouples an event dispatch from its listener(s), linked by the event class: event(new OrderShipped($order)) has no static edge to the handle(OrderShipped $event) that runs it (usually a separate app/Listeners/ class). laravelEventEdges bridges each event(new X(...)) site -> every listener's handle for X. Two registration mechanisms, both real and both needed (built together): - (A) auto-discovery: a typed handle(EventType $e) first param, read from the method declaration source (PHP method nodes carry no signature, like C#); a handle(A|B $e) union is split into two events. - (B) the `protected $listen = [XEvent::class => [Listener::class, ...]]` map in an EventServiceProvider, parsed from comment-stripped source (so a fully-commented map on an auto-discovery app contributes nothing). This is the only way to link a listener whose handle() is untyped. Job exclusion is free: queued jobs dispatch via ::dispatch()/dispatch() (not matched) and their handle() takes an injected service, never an event type, so matching only event(new X) excludes them by construction. `use Dispatchable` is not keyed on (unreliable in real apps). Surfaces as `dynamic: laravel event` via the generic synth-edge fallback. Validated 100% precision on two grep-confirmed repos exercising both mechanisms: koel (small, populated $listen map, 9 edges incl. the untyped-handle case and a fan-out) and firefly-iii (large, pure auto-discovery / empty $listen, 141 edges, 0 source/target false positives, 0 namespace mismatch, union split verified); 0 on the guzzle control. Namespace-agnostic (FireflyIII\ not hardcoded). Node-stable (pure edge synth). Suite 1623 green. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
2c522c6254 |
feat(resolution): bridge Sidekiq Worker.perform_async to #perform
Sidekiq decouples a job's enqueue site from the worker's perform method, linked by the worker class NAME: DestroyUserWorker.perform_async(id) has no static edge to DestroyUserWorker#perform (usually in app/workers/, away from the controller/model that enqueues it). sidekiqDispatchEdges bridges each Worker.perform_async/_in/_at(...) site -> that worker's instance perform. Name-keyed, like Celery: the receiver class must be a Sidekiq worker, gated by reading `include Sidekiq::Job|Worker` from the class body (the mixin is an external gem module that forms no resolvable edge). ActiveJob's perform_later/ _now is a different shape and deliberately not matched. Namespace disambiguation was the n>1 validation payoff: loomio's flat workers hid a collision bug that forem exposed (four SendEmailNotificationWorker classes across modules; simple-name resolution mis-targeted 7/143 edges to the wrong namespace). Fixed by resolving a namespaced receiver via exact qualified-name lookup first, falling back to the simple name only for a unique worker — an ambiguous unqualified collision bails (precision over recall). Surfaces as `dynamic: sidekiq dispatch` via the generic synth-edge fallback. Validated 100% precision on two grep-confirmed repos: loomio (medium, Sidekiq::Worker, 47 edges) and forem (large, both include aliases — 131 Sidekiq::Job + 11 Sidekiq::Worker, 142 edges, 0 worker/source false positives, 0 namespace mismatch); 0 on the jekyll control. Node-stable (pure edge synth). Suite 1621 green. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
d1381e11f6 |
feat(resolution): bridge MediatR Send/Publish to its IRequestHandler.Handle
MediatR decouples a _mediator.Send(x)/.Publish(x) call from the Handle method that runs it, linked by the request/notification TYPE (the IRequestHandler<X,…> generic), usually across files in a Clean Architecture layout — so flows dead-end at the mediator call and the agent reads to find the handler. mediatrDispatchEdges bridges each dispatch -> the matching handler's Handle. Same two-pass, type-keyed shape as the Spring synthesizer, with two C#-specific twists found by probing: - C# method nodes carry NO signature (csharp.ts defines no getSignature), so Pass 1 reads the request type from the handler CLASS base-list source (`: IRequestHandler<X,…>` first generic arg) and binds the class's Handle. - The dominant .NET idiom is VARIABLE-passed, not inline `Send(new X)` — eShop has zero genuine inline MediatR sends. So Pass 2 resolves the sent type from the argument three ways within the enclosing method: inline `new X(…)`, a local `var v = new X(…)` (backward scan), or a parameter/local declared `X v`. Two precision gates: the receiver must be mediator-ish (mediator/sender/ publisher — excludes MAUI MessagingCenter.Send, HttpClient.Send) AND the resolved type must have a handler (so a same-named non-request DTO is never bridged). Handles the IdentifiedCommand<T,R> wrapper and void IRequestHandler<T>. Surfaces as `dynamic: mediatr dispatch` via the generic synth-edge fallback. Validated 100% precision on two grep-confirmed repos: jasontaylordev/ CleanArchitecture (small, 9 edges, inline + param forms) and dotnet/eShop (medium, 9 edges, 0 false positives, variable-passed + IdentifiedCommand + the CancelOrderCommand DTO-collision correctly avoided); 0 on the Newtonsoft.Json control. Node-stable (pure edge synth). Suite 1619 green. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
9b7ca2e394 |
feat(resolution): bridge Spring publishEvent() to its @EventListener handlers
Spring decouples an event publisher from its listener(s) through the application event bus, linked by the event TYPE: publishEvent(new XEvent(...)) has no static edge to the @EventListener void on(XEvent e) that handles it (usually a different class), so flows dead-end at the publish and the agent reads to find the handlers. springEventEdges bridges each publishEvent(new X) site -> every listener of X. Two-pass, type-keyed (no name resolution, so precision is structural): - Pass 1 builds Map<eventType, listenerMethod[]> from @EventListener / @TransactionalEventListener methods (event type = first param type off the node signature, or the @EventListener(X.class) value form) and the older `implements ApplicationListener<X>` onApplicationEvent methods. - Pass 2 links each publishEvent(new XEvent(...))'s enclosing method to every listener of XEvent; multi-line `publishEvent(\n new X(...))` handled. Key Java fact (probed): a method node's range INCLUDES its leading annotations (startLine is the first @-line, not the `public void` decl), so the annotation gate scans DOWNWARD from startLine bounded to consecutive @-lines, which can't bleed into an adjacent method. Surfaces as `dynamic: spring event` via the generic synth-edge fallback. Validated 100% precision on two grep-confirmed repos exercising all listener forms: halo (medium, 1254 java, 33 edges across 24 events, 0 publisher/listener false positives, param-typed + (X.class) + ApplicationListener + fan-out) and thombergs/code-examples (4 edges, adds @TransactionalEventListener); 0 on the gson control (no Spring). Node-stable (pure edge synth). Suite 1617 green. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
6e5c3a9336 |
feat(resolution): bridge Celery .delay()/.apply_async() dispatch to the task body
Celery decouples a task's call site from its body: a @shared_task / @app.task decorated def is invoked via task.delay(...) / task.apply_async(...), a dynamic hop with no static edge, so flows dead-end at the dispatch and the agent reads tasks.py to reconstruct them. celeryDispatchEdges links the enclosing function at each .delay/.apply_async site -> the task function body. Precision rests on a DECORATOR gate: the dispatched name must resolve to a Python function carrying a task decorator, read from the source lines ABOVE its def (the def's startLine excludes the decorator, and no decorates edge exists since @shared_task is an unresolved external import). The kind==='function' filter drops same-named test-method collisions; canvas forms (group(t).delay(), t.s()/.si()) have no single identifier before .delay so they're skipped, not mis-bridged; cross-module name collisions prefer a same-file task else bail. Surfaces as `dynamic: celery dispatch` via the generic synth-edge fallback. Validated 100% precision on two grep-confirmed repos exercising both decorator dialects: paperless-ngx (small, @shared_task, 31 edges, 31/31 real) and pretix (medium, @app.task, 63 edges across 21 tasks, 0/21 false positives); 0 on the httpie control (no Celery). Node-stable (pure edge synth). Suite 1615 green. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
80a1044d3d |
feat(resolution): bridge Vuex string dispatch/commit to actions and mutations
Completes the Vue store dispatch family (the Pinia bridge was
|
||
|
|
8ea32059b6 |
feat(resolution): bridge Pinia useStore().action() calls to the action
The dispatch bridge for Pinia, on top of the store-action extraction foundation
(
|
||
|
|
cc9c2f7420 |
feat(extraction): index Vuex/Pinia store actions, mutations, and getters
A Vue store's callable surface — Vuex `actions`/`mutations`/`getters` and Pinia
store actions — lived only as object-literal properties, so the symbols an agent
looks for (`login`, `getSessionList`, `getAuthMenuList`) were never nodes:
`codegraph search`/`codegraph_node` returned "not found" and the agent had to
read the store by hand. This extracts them as function nodes (with their real
bodies + callees), the foundation under any later dispatch-bridge synthesis.
A corpus probe (vue-element-admin, vue2-elm, Geeker-Admin, MallChatWeb) showed
Vue store dispatch is NOT one clean string-keyed shape but ~5; extraction here
covers the three dominant definition forms:
- Vuex MODULE: non-exported `const actions/mutations = {…}` collections
(gated by a ≥2-signal looksLikeVueStoreFile + the object-of-functions shape,
so a Redux file's stray `const actions` is a 0-node no-op).
- Pinia OPTIONS: `defineStore({ actions: {…}, getters: {…} })` — methods of
the actions/mutations/getters properties of a store-factory config.
- Pinia SETUP: `defineStore('id', () => { const foo = …; return {…} })` — the
body-local function consts (findPiniaSetupFn + extractPiniaSetupBody; the
generic body walk doesn't reach nested function scopes). Distinguished from
an inline action map via objectHasInlineFunctions so zustand/SvelteKit
extraction is unchanged.
Validated findable on element-admin (50 fns), Geeker (21), MallChat (68);
0-node no-op on a non-Vue control (uwave-web, unchanged at 4496 nodes). Deferred
(documented in the backlog): vue2-elm's `export default {…}` split-file +
computed-key `commit(CONST)` form (n=1), and the dispatch BRIDGE synthesis
(Vuex string-key + Pinia useStore().action()). Suite green (1610); new
__tests__/vue-store-extraction.test.ts.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
||
|
|
e9f7422223 |
feat(resolution): synthesize RTK Query hook→endpoint dispatch edges
Adds the RTK Query member of the dispatch-through-indirection family
(synthesizedBy:'rtk-query'). An RTK Query endpoint defined inside
`createApi({ endpoints })` and the `useGetXQuery`/`useUpdateYMutation` hook it
generates were both invisible to static extraction, so a `component →
useGetXQuery → getX → queryFn` flow had nothing to connect and explore
dead-ended on the API slice.
Extraction (tree-sitter.ts): mint a function node per endpoint — named by its
key, spanning the queryFn/query handler so its calls attribute — handling both
the `endpoints: build => ({...})` arrow and `endpoints(builder){ return {...} }`
method forms, with a bare-node fallback for factory handlers
(`queryFn: makeFn(url)`); and a function node per generated-hook binding from
`export const {...} = api`, carrying a sentinel signature.
Resolution (callback-synthesizer.ts): rtkQueryEdges bridges each generated-hook
node to its same-file endpoint by the naming convention (strip use + optional
Lazy + Query|Mutation, lowercase head). Component→hook is normal import/call
resolution; the hook→endpoint hop surfaces in explore as `dynamic: rtk query`.
Validated 100% precision (hooks == synth edges, 0 cross-file) on basetool (54),
minusx-metabase (11), shapeshift (13); 0 on the uwave-web control (no createApi
→ a complete no-op). The sentinel gate correctly ignores hand-written
look-alikes (shapeshift's useFoxyQuery is a real custom hook, never bridged).
Full suite green (1608); new __tests__/rtk-query-synthesizer.test.ts.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
||
|
|
7f970296cf |
feat(resolution): synthesize object-literal registry dispatch edges
Adds `objectRegistryEdges` — a dynamic-dispatch synthesizer for the command/handler
registry pattern: an object literal maps string keys → handler classes/functions, then
dispatches by a RUNTIME key static parsing can't follow:
this.commands = { [Cmd.ADD]: AddObjectCommand, ... } // registration
new this.commands[command](args).execute() // dynamic dispatch
It links each dispatching function → each registered handler's callable entry (a class's
execute/run/handle method — preferring the method chained at the dispatch site — or the
function value), like the gin-middleware-chain fan-out. Same-file registry+dispatch only.
Validated precise on 3 real repos (the discipline that caught redux-thunk's n=1 overfit):
EtherealEngine's CommandManager (64 edges, class registry → .execute), Prebid.js (7:
builder/consent/message dispatch, function registry), warp-drive (1). Zero false positives
after several precision gates found during validation:
- skip minified/generated bundles (avg line length > 200) — draco/three.min were a
false-positive minefield of `h[x](...)` calls + `{a:b}` literals;
- DEPTH-AWARE entry parsing (top-level `key: Identifier` only) so method-shorthand bodies
and nested objects don't leak their inner `k: v` pairs as bogus handlers;
- callable-only targets (drop data `constant`s — a `{x: URL}` entry resolving to the global);
- dynamic-dispatch gate (a statically-accessed look-alike object yields nothing).
Handles constructor and field-initializer registry forms (this. normalized). Surfaces in
codegraph_explore via the existing Dynamic-dispatch-links section.
Deferred (recall, documented in dispatch-synthesizer-backlog.md): assign-then-call dispatch,
augmentation registration (reg[k]=H), and the cross-file barrel-namespace variant
(trezor getMethod) — the hard tier.
Full suite green (1606); new __tests__/object-registry-synthesizer.test.ts.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
||
|
|
270e50655a |
fix(explore): surface synth constant-endpoint edges + precise redux-thunk dispatch resolution
Two fixes hardening the redux-thunk dynamic-dispatch synthesizer, found by validating it on real RTK repos beyond its trezor origin (uwave-web, session-desktop, octo-call): - Surfacing: buildFlowFromNamedSymbols filtered its named set to CALLABLE kinds, so synthesized edges between `constant` nodes (RTK thunks are `const X = createAsyncThunk(...)`) never entered the Flow / Dynamic-dispatch links scan — invisible at every tier, while the kind-agnostic Relationships section is off below 500 files. Add a `dynNamed` set (named constant/variable/ field nodes with a heuristic edge) feeding a shared collectSynthLinks into the "## Dynamic-dispatch links" section, threaded through the named.size<2 early-out (both-endpoints-constant hit return EMPTY first) and the main path. Main call-chain stays callable-only; the <500 budget tiers are untouched. No-op for callable flows. Plus a generic synthEdgeNote fallback so any synth hop reads "dynamic: <kind> @site", not a bare "[calls]". - Precision: reduxThunkEdges resolved a dispatched name by first-match-by-kind, so a thunk name colliding with a same-named service function linked to the wrong node (octo-call `leaveCall`). Prefer thunk-signature const > other const > same-file callable > first match. Tests: new explore-synth-constant-endpoints.test.ts (surfacing on a small repo) + a collision case in redux-thunk-synthesizer.test.ts. Full suite green (1605). Rationale + coverage backlog in docs/design/dispatch-synthesizer-backlog.md. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> |