Files
deusdata--codebase-memory-mcp/internal/cbm/service_patterns.h
Alexandros Pappas d288a21ec2 fix(pipeline): classify native fetch() as HTTP_CALLS, not local shadows
Closes #856: native `fetch()` calls (Node 18+ built-in, and the identical
browser/WHATWG API — no static way to tell them apart, and no need to:
both make a real outbound HTTP request) were invisible to cross-repo
intelligence. A bare `fetch(url)` has no import and typically no local
definition, so registry resolution comes back empty and the call was
silently dropped instead of becoming an HTTP_CALLS edge.

The obvious fix — add "fetch" to the http_libraries substring table in
service_patterns.c — is wrong: that table is consulted by an early,
unconditional check in both pass_calls.c and pass_parallel.c that runs
before/regardless of whether the callee resolves to a real local
definition (by design, so `axios.get(url)` still classifies even when the
registry mis-binds bare `get` to an unrelated local method). That's safe
for "axios"/"requests" because nobody names their own function that; it
is not safe for "fetch", which collides with a plausible local identifier
(`function fetch(){}` or `const fetch = () => {}`).

Instead, cbm_service_pattern_is_global_fetch() is a new, narrow, exact-
match check consulted only in the *empty-resolution* fallback in both
files — the existing #523 path for unindexed external libraries. By the
time that branch runs, the registry has already had its chance to
resolve "fetch" to a local/imported definition; only a genuine miss
reaches the new check. This also means a member call like `repo.fetch()`
is naturally excluded (its callee_name is "repo.fetch", not the bare
"fetch" this checks for).

pass_parallel.c's empty-resolution branch calls the low-level
emit_http_async_service_edge() directly rather than going through
emit_service_edge(), which re-derives its own classification from
res->qualified_name via cbm_service_pattern_match() — a call with
"fetch" would come back CBM_SVC_NONE there and silently fall through to
a plain CALLS edge.

Three new tests in test_pipeline.c:
- bare fetch() -> HTTP_CALLS, sequential path (< 50 files)
- bare fetch() -> HTTP_CALLS, forced through the parallel resolver
  (>= 50 files)
- a local `function fetch(){}` shadowing the global -> plain CALLS to
  the local definition, zero HTTP_CALLS (the false-positive this PR
  must not introduce), bundled with a `repo.fetch()` member-call check
  in the first test

Verified end-to-end, not just by inspection: prod build clean under
-Wall -Wextra -Werror; test build clean under ASan+UBSan; full suite
5936 passed, 1 skipped (pre-existing, unrelated), 0 failed; clang-format
clean on all touched files; lint-cppcheck's pre-existing failures
(extract_defs.c, compat_regex.c) reproduced identically on unmodified
main via stash/pop, confirming they predate this change.

Refs #592.

Signed-off-by: Alexandros Pappas <11921291+apappas1129@users.noreply.github.com>
2026-07-07 11:33:56 +08:00

71 lines
3.4 KiB
C
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/*
* service_patterns.h — Allowlists for HTTP clients, async dispatch, and config accessors.
*
* Used during call resolution to classify CALLS edges as:
* HTTP_CALLS — synchronous HTTP client calls
* ASYNC_CALLS — async message/task dispatch
* CONFIGURES — config/env access
*
* Lookup is O(1) via hash table initialized once at startup.
*/
#ifndef CBM_SERVICE_PATTERNS_H
#define CBM_SERVICE_PATTERNS_H
#include <stdbool.h>
/* Edge type returned by pattern match. */
typedef enum {
CBM_SVC_NONE = 0, /* Not a service pattern — use normal CALLS */
CBM_SVC_HTTP = 1, /* Synchronous HTTP client call */
CBM_SVC_ASYNC = 2, /* Async dispatch (message broker, task queue) */
CBM_SVC_CONFIG = 3, /* Config/env accessor */
CBM_SVC_ROUTE_REG = 4, /* Route registration (router.GET, app.get, Route::post) */
CBM_SVC_GRPC = 5, /* gRPC client call (protobuf stub invocation) */
CBM_SVC_GRAPHQL = 6, /* GraphQL client query/mutation */
CBM_SVC_TRPC = 7, /* tRPC client procedure call */
} cbm_svc_kind_t;
/* Initialize the pattern lookup tables. Call once at startup. Thread-safe after init. */
void cbm_service_patterns_init(void);
/* Check if a resolved QN contains a known service library identifier.
* Returns the pattern kind, or CBM_SVC_NONE if no match.
* Matches on library name substrings in the QN (e.g., "requests" in
* "project.venv.requests.api.get"). Import-alias transparent. */
cbm_svc_kind_t cbm_service_pattern_match(const char *resolved_qn);
/* True for a bare, unqualified call to the native `fetch()` API. Deliberately
* NOT part of the substring tables above: those are matched unconditionally
* against the raw callee name before/regardless of registry resolution
* (the #523 external-library bypass), and "fetch" — unlike "axios" or
* "requests" — collides with a plausible local identifier. Callers must
* only consult this after registry resolution has come back empty, so a
* locally resolvable `function fetch(){}` / `const fetch = () => {}` is
* classified via its real resolved QN instead and never reaches this check. */
bool cbm_service_pattern_is_global_fetch(const char *callee_name);
/* Per-worker TLS cache for cbm_service_pattern_match results. The
* pattern matcher runs once per resolved CALL edge in emit_service_
* edge — that's 6 pattern lists × ~30 patterns × strstr per call ≈
* ~180 strstrs per call. The same resolved QN repeats across most of
* the call edges in a project (e.g. "fmt.Errorf"), so caching turns
* a linear pattern-list scan into one hash lookup. Call _begin once
* per worker thread before the resolve loop and _end at the end. */
void cbm_service_pattern_cache_begin(void);
void cbm_service_pattern_cache_end(void);
/* Get the HTTP method from the callee name suffix (e.g., ".get" → "GET").
* Returns NULL if method cannot be inferred. */
const char *cbm_service_pattern_http_method(const char *callee_name);
/* Get the HTTP method from a route registration callee name suffix
* (e.g., "router.GET" → "GET", "app.post" → "POST").
* Returns NULL if not a known route registration method. */
const char *cbm_service_pattern_route_method(const char *callee_name);
/* Get the broker name for an async QN (e.g., "pubsub" from a Pub/Sub QN).
* Returns NULL if not an async pattern. */
const char *cbm_service_pattern_broker(const char *resolved_qn);
#endif /* CBM_SERVICE_PATTERNS_H */