gen_server:call/cast/send_request now connects to the TARGET module's
handle_call/handle_cast for every statically-named target, not just self:
a bare atom reaches the module of that name (OTP's {local, ?MODULE}
convention names a server after its module), and a macro defined as a bare
atom (-define(STORE, kv_store)) resolves the same way, alongside the
existing ?MODULE / -define(SERVER, ?MODULE) self paths. A registered name
that matches no module emits a qualified ref that never resolves — silent,
never guessed. Pid, var, and tuple targets ({global, Name}, {Name, Node})
stay unlinked.
Validated on emqx: 53 new edges, 53/53 precise (each source line is a real
registered-name gen_server request; each target module self-registers under
that name, macro-indirected registrations included). Nearly all are
test-suite → handler links — production code goes through API wrappers the
self path already covers — which is exactly the tests-exercising-this-
handler linkage blast-radius and test-gap reporting consume. ejabberd
yields zero (it always wraps): no false positives invented.
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5
parent
2217a35943
commit
7e3d44fa96
@@ -8991,14 +8991,35 @@ handle_cast({put, K, V}, S) -> {noreply, maps:put(K, V, S)}.
|
||||
expect(calls).toContain('kv_store::handle_cast');
|
||||
});
|
||||
|
||||
it('should not connect gen_server calls to other processes', () => {
|
||||
it('should connect gen_server calls to a registered-name module, directly or via an atom macro', () => {
|
||||
const code = `-module(kv_client).
|
||||
-export([fetch/1, evict/1]).
|
||||
|
||||
-define(STORE, kv_store).
|
||||
|
||||
fetch(Key) ->
|
||||
gen_server:call(kv_store, {get, Key}).
|
||||
|
||||
evict(Key) ->
|
||||
gen_server:cast(?STORE, {evict, Key}).
|
||||
`;
|
||||
const result = extractFromSource('src/kv_client.erl', code);
|
||||
const calls = result.unresolvedReferences.filter((r) => r.referenceKind === 'calls').map((r) => r.referenceName);
|
||||
// OTP's {local, ?MODULE} convention names a server after its module —
|
||||
// a cross-module registered name targets that module's handlers. A name
|
||||
// matching no module simply never resolves downstream.
|
||||
expect(calls).toContain('kv_store::handle_call');
|
||||
expect(calls).toContain('kv_store::handle_cast');
|
||||
});
|
||||
|
||||
it('should not connect gen_server calls with dynamic targets', () => {
|
||||
const code = `-module(m).
|
||||
-export([go/2]).
|
||||
|
||||
go(Pid, Msg) ->
|
||||
gen_server:call(Pid, Msg),
|
||||
gen_server:call(other_registered_name, Msg),
|
||||
gen_server:cast({global, some_name}, Msg).
|
||||
gen_server:cast({global, some_name}, Msg),
|
||||
gen_server:call({some_name, node()}, Msg).
|
||||
`;
|
||||
const result = extractFromSource('src/m.erl', code);
|
||||
const calls = result.unresolvedReferences.filter((r) => r.referenceKind === 'calls').map((r) => r.referenceName);
|
||||
|
||||
Reference in New Issue
Block a user