d288a21ec2
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>
71 lines
3.4 KiB
C
71 lines
3.4 KiB
C
/*
|
||
* 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 */
|