Files
wehub-resource-sync 41cb1c0170
OpenSSF Scorecard / scorecard (push) Failing after 0s
DCO / dco (push) Failing after 0s
CodeQL SAST / analyze (push) Failing after 1s
Deploy Pages / deploy (push) Failing after 1s
chore: import upstream snapshot with attribution
2026-07-13 12:28:05 +08:00

864 lines
28 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.c — Classify call edges by library identity in resolved QN.
*
* Instead of matching callee names (ambiguous: "get", "post", "send"),
* we match library identifiers in the RESOLVED qualified name. The QN
* contains the full module path, so import aliases are transparent:
* r.get("/api") → QN: project.venv.requests.api.get → match "requests" → HTTP_CALLS
*
* Two-level matching:
* 1. Library identifier in QN → determines edge type (HTTP/ASYNC/CONFIG)
* 2. Method suffix → determines HTTP method (get→GET, post→POST)
*/
#include "service_patterns.h"
#include <stdbool.h>
#include <stddef.h>
#include <stdlib.h>
#include <string.h>
/* ── Library identifier → edge type ────────────────────────────── */
typedef struct {
const char *library_id; /* substring to find in resolved QN */
cbm_svc_kind_t kind; /* HTTP_CALLS, ASYNC_CALLS, CONFIGURES */
const char *broker; /* for ASYNC: broker name (NULL otherwise) */
} lib_pattern_t;
/* HTTP client libraries — match these substrings in the resolved QN.
* Sources: github.com/easybase/awesome-http, official SDK docs, agent research */
static const lib_pattern_t http_libraries[] = {
/* Python */
{"requests", CBM_SVC_HTTP, NULL},
{"httpx", CBM_SVC_HTTP, NULL},
{"aiohttp", CBM_SVC_HTTP, NULL},
{"urllib", CBM_SVC_HTTP, NULL},
{"urllib3", CBM_SVC_HTTP, NULL},
{"httplib2", CBM_SVC_HTTP, NULL},
{"pycurl", CBM_SVC_HTTP, NULL},
{"treq", CBM_SVC_HTTP, NULL},
{"uplink", CBM_SVC_HTTP, NULL},
/* JavaScript / TypeScript */
{"axios", CBM_SVC_HTTP, NULL},
{"superagent", CBM_SVC_HTTP, NULL},
{"needle", CBM_SVC_HTTP, NULL},
{"node-fetch", CBM_SVC_HTTP, NULL},
{"undici", CBM_SVC_HTTP, NULL},
{"ofetch", CBM_SVC_HTTP, NULL},
{"wretch", CBM_SVC_HTTP, NULL},
{"sindresorhus/ky", CBM_SVC_HTTP, NULL},
{"phin", CBM_SVC_HTTP, NULL},
/* Go */
{"net/http", CBM_SVC_HTTP, NULL},
{"resty", CBM_SVC_HTTP, NULL},
{"sling", CBM_SVC_HTTP, NULL},
{"heimdall", CBM_SVC_HTTP, NULL},
{"gentleman", CBM_SVC_HTTP, NULL},
{"retryablehttp", CBM_SVC_HTTP, NULL},
/* Java / Kotlin */
{"HttpClient", CBM_SVC_HTTP, NULL},
{"OkHttp", CBM_SVC_HTTP, NULL},
{"okhttp3", CBM_SVC_HTTP, NULL},
{"RestTemplate", CBM_SVC_HTTP, NULL},
{"WebClient", CBM_SVC_HTTP, NULL},
{"Unirest", CBM_SVC_HTTP, NULL},
{"AsyncHttpClient", CBM_SVC_HTTP, NULL},
{"apache.http", CBM_SVC_HTTP, NULL},
{"Retrofit", CBM_SVC_HTTP, NULL},
{"Feign", CBM_SVC_HTTP, NULL},
{"ktor.client", CBM_SVC_HTTP, NULL},
{"kittinunf.fuel", CBM_SVC_HTTP, NULL},
/* Rust */
{"reqwest", CBM_SVC_HTTP, NULL},
{"hyper", CBM_SVC_HTTP, NULL},
{"surf", CBM_SVC_HTTP, NULL},
{"ureq", CBM_SVC_HTTP, NULL},
{"isahc", CBM_SVC_HTTP, NULL},
{"attohttpc", CBM_SVC_HTTP, NULL},
/* C# */
{"HttpClient", CBM_SVC_HTTP, NULL},
{"RestSharp", CBM_SVC_HTTP, NULL},
{"Flurl", CBM_SVC_HTTP, NULL},
{"Refit", CBM_SVC_HTTP, NULL},
/* Ruby */
{"HTTParty", CBM_SVC_HTTP, NULL},
{"Faraday", CBM_SVC_HTTP, NULL},
{"RestClient", CBM_SVC_HTTP, NULL},
{"Typhoeus", CBM_SVC_HTTP, NULL},
{"Excon", CBM_SVC_HTTP, NULL},
{"Net::HTTP", CBM_SVC_HTTP, NULL},
/* PHP */
{"Guzzle", CBM_SVC_HTTP, NULL},
{"guzzle", CBM_SVC_HTTP, NULL},
{"curl", CBM_SVC_HTTP, NULL},
{"Symfony\\HttpClient", CBM_SVC_HTTP, NULL},
/* C/C++ */
{"cpr", CBM_SVC_HTTP, NULL},
{"cpp-httplib", CBM_SVC_HTTP, NULL},
{"Poco.Net", CBM_SVC_HTTP, NULL},
{"Beast", CBM_SVC_HTTP, NULL},
/* Swift */
{"Alamofire", CBM_SVC_HTTP, NULL},
{"Moya", CBM_SVC_HTTP, NULL},
{"URLSession", CBM_SVC_HTTP, NULL},
/* Dart */
{"Dio", CBM_SVC_HTTP, NULL},
{"dio", CBM_SVC_HTTP, NULL},
{"package:http", CBM_SVC_HTTP, NULL},
{"Chopper", CBM_SVC_HTTP, NULL},
/* Elixir */
{"HTTPoison", CBM_SVC_HTTP, NULL},
{"Tesla", CBM_SVC_HTTP, NULL},
{"Finch", CBM_SVC_HTTP, NULL},
{"Mint.HTTP", CBM_SVC_HTTP, NULL},
/* Scala */
{"sttp", CBM_SVC_HTTP, NULL},
{"akka.http", CBM_SVC_HTTP, NULL},
{"http4s", CBM_SVC_HTTP, NULL},
{"scalaj", CBM_SVC_HTTP, NULL},
/* Haskell */
{"wreq", CBM_SVC_HTTP, NULL},
{"http-client", CBM_SVC_HTTP, NULL},
{"http-conduit", CBM_SVC_HTTP, NULL},
{"servant-client", CBM_SVC_HTTP, NULL},
{"Network.HTTP", CBM_SVC_HTTP, NULL},
/* Lua */
{"socket.http", CBM_SVC_HTTP, NULL},
{"resty.http", CBM_SVC_HTTP, NULL},
{NULL, CBM_SVC_NONE, NULL},
};
/* Async dispatch / message broker libraries */
static const lib_pattern_t async_libraries[] = {
/* GCP */
{"cloudtasks", CBM_SVC_ASYNC, "cloud_tasks"},
{"cloud_tasks", CBM_SVC_ASYNC, "cloud_tasks"},
{"cloud.tasks", CBM_SVC_ASYNC, "cloud_tasks"},
{"CloudTasks", CBM_SVC_ASYNC, "cloud_tasks"},
{"pubsub", CBM_SVC_ASYNC, "pubsub"},
{"cloud.pubsub", CBM_SVC_ASYNC, "pubsub"},
{"PubSub", CBM_SVC_ASYNC, "pubsub"},
/* AWS — use SDK module paths to avoid false positives. cbm_fqn_compute
* converts path slashes to '.', so a resolved local Go QN reads
* "aws-sdk-go.service.sqs..."; include both slash and dot forms so the
* substring match fires whether the id comes from an import path or a QN. */
{"aws-sdk-go/service/sqs", CBM_SVC_ASYNC, "sqs"},
{"aws-sdk-go.service.sqs", CBM_SVC_ASYNC, "sqs"},
{"aws_sdk_sqs", CBM_SVC_ASYNC, "sqs"},
{"Amazon.SQS", CBM_SVC_ASYNC, "sqs"},
{"@aws-sdk/client-sqs", CBM_SVC_ASYNC, "sqs"},
{"boto3.client.sqs", CBM_SVC_ASYNC, "sqs"},
{"aws-sdk-go/service/sns", CBM_SVC_ASYNC, "sns"},
{"aws-sdk-go.service.sns", CBM_SVC_ASYNC, "sns"},
{"aws_sdk_sns", CBM_SVC_ASYNC, "sns"},
{"Amazon.SNS", CBM_SVC_ASYNC, "sns"},
{"@aws-sdk/client-sns", CBM_SVC_ASYNC, "sns"},
{"eventbridge", CBM_SVC_ASYNC, "eventbridge"},
{"EventBridge", CBM_SVC_ASYNC, "eventbridge"},
{"aws-sdk-go/service/lambda", CBM_SVC_ASYNC, "lambda"},
{"aws-sdk-go.service.lambda", CBM_SVC_ASYNC, "lambda"},
{"aws_sdk_lambda", CBM_SVC_ASYNC, "lambda"},
{"@aws-sdk/client-lambda", CBM_SVC_ASYNC, "lambda"},
{"stepfunctions", CBM_SVC_ASYNC, "stepfunctions"},
/* Azure */
{"ServiceBus", CBM_SVC_ASYNC, "servicebus"},
{"Azure.Messaging", CBM_SVC_ASYNC, "servicebus"},
/* Kafka */
{"kafka", CBM_SVC_ASYNC, "kafka"},
{"Kafka", CBM_SVC_ASYNC, "kafka"},
{"kafkajs", CBM_SVC_ASYNC, "kafka"},
{"sarama", CBM_SVC_ASYNC, "kafka"},
{"rdkafka", CBM_SVC_ASYNC, "kafka"},
{"confluent", CBM_SVC_ASYNC, "kafka"},
{"Confluent.Kafka", CBM_SVC_ASYNC, "kafka"},
/* RabbitMQ */
{"amqp", CBM_SVC_ASYNC, "rabbitmq"},
{"AMQP", CBM_SVC_ASYNC, "rabbitmq"},
{"amqplib", CBM_SVC_ASYNC, "rabbitmq"},
{"RabbitMQ", CBM_SVC_ASYNC, "rabbitmq"},
{"lapin", CBM_SVC_ASYNC, "rabbitmq"},
{"MassTransit", CBM_SVC_ASYNC, "rabbitmq"},
/* NATS */
{"nats", CBM_SVC_ASYNC, "nats"},
{"NATS", CBM_SVC_ASYNC, "nats"},
/* Redis pub/sub */
{"ioredis", CBM_SVC_ASYNC, "redis"},
/* Task queues */
{"celery", CBM_SVC_ASYNC, "celery"},
{"Celery", CBM_SVC_ASYNC, "celery"},
{"dramatiq", CBM_SVC_ASYNC, "dramatiq"},
{"huey", CBM_SVC_ASYNC, "huey"},
{"python-rq", CBM_SVC_ASYNC, "rq"},
{"rq.Queue", CBM_SVC_ASYNC, "rq"},
{"bullmq", CBM_SVC_ASYNC, "bullmq"},
{"BullMQ", CBM_SVC_ASYNC, "bullmq"},
{"bull.Queue", CBM_SVC_ASYNC, "bull"},
{"Sidekiq", CBM_SVC_ASYNC, "sidekiq"},
{"sidekiq", CBM_SVC_ASYNC, "sidekiq"},
{"Resque", CBM_SVC_ASYNC, "resque"},
{"GoodJob", CBM_SVC_ASYNC, "goodjob"},
{"DelayedJob", CBM_SVC_ASYNC, "delayed_job"},
{"Hangfire", CBM_SVC_ASYNC, "hangfire"},
{"NServiceBus", CBM_SVC_ASYNC, "nservicebus"},
{"asynq", CBM_SVC_ASYNC, "asynq"},
{"RichardKnop/machinery", CBM_SVC_ASYNC, "machinery"},
/* Workflow engines — use specific module paths to avoid "Temporal" in Django etc. */
{"temporalio", CBM_SVC_ASYNC, "temporal"},
{"@temporalio", CBM_SVC_ASYNC, "temporal"},
{"temporal.client", CBM_SVC_ASYNC, "temporal"},
{"temporal.worker", CBM_SVC_ASYNC, "temporal"},
{"inngest", CBM_SVC_ASYNC, "inngest"},
/* Elixir */
{"Oban", CBM_SVC_ASYNC, "oban"},
{"Broadway", CBM_SVC_ASYNC, "broadway"},
{"GenStage", CBM_SVC_ASYNC, "genstage"},
{"Phoenix.PubSub", CBM_SVC_ASYNC, "phoenix_pubsub"},
/* Scala */
{"Alpakka", CBM_SVC_ASYNC, "alpakka"},
/* MQTT */
{"mqtt", CBM_SVC_ASYNC, "mqtt"},
{"paho.mqtt", CBM_SVC_ASYNC, "mqtt"},
{"MQTTClient", CBM_SVC_ASYNC, "mqtt"},
{"mosquitto", CBM_SVC_ASYNC, "mqtt"},
{"asyncio_mqtt", CBM_SVC_ASYNC, "mqtt"},
{"gmqtt", CBM_SVC_ASYNC, "mqtt"},
{"rumqttc", CBM_SVC_ASYNC, "mqtt"},
/* NATS */
{"nats.go", CBM_SVC_ASYNC, "nats"},
{"nats-py", CBM_SVC_ASYNC, "nats"},
{"nats.ws", CBM_SVC_ASYNC, "nats"},
{"nats.java", CBM_SVC_ASYNC, "nats"},
{"nats.net", CBM_SVC_ASYNC, "nats"},
{"async-nats", CBM_SVC_ASYNC, "nats"},
{"nats.rs", CBM_SVC_ASYNC, "nats"},
/* Dapr pub/sub */
{"dapr.clients.grpc", CBM_SVC_ASYNC, "dapr"},
{"DaprClient", CBM_SVC_ASYNC, "dapr"},
{NULL, CBM_SVC_NONE, NULL},
};
/* Config accessor libraries */
static const lib_pattern_t config_libraries[] = {
/* Universal */
{"getenv", CBM_SVC_CONFIG, NULL},
{"Getenv", CBM_SVC_CONFIG, NULL},
{"getEnv", CBM_SVC_CONFIG, NULL},
{"LookupEnv", CBM_SVC_CONFIG, NULL},
{"lookupEnv", CBM_SVC_CONFIG, NULL},
{"get_env", CBM_SVC_CONFIG, NULL},
{"fetch_env", CBM_SVC_CONFIG, NULL},
{"GetEnvironmentVariable", CBM_SVC_CONFIG, NULL},
{"getProperty", CBM_SVC_CONFIG, NULL},
{"getEnvironment", CBM_SVC_CONFIG, NULL},
/* Go */
{"viper", CBM_SVC_CONFIG, NULL},
{"envconfig", CBM_SVC_CONFIG, NULL},
{"godotenv", CBM_SVC_CONFIG, NULL},
/* Python */
{"decouple", CBM_SVC_CONFIG, NULL},
{"dynaconf", CBM_SVC_CONFIG, NULL},
{"dotenv", CBM_SVC_CONFIG, NULL},
/* JS/TS */
{"nconf", CBM_SVC_CONFIG, NULL},
{"convict", CBM_SVC_CONFIG, NULL},
{"envalid", CBM_SVC_CONFIG, NULL},
/* Rust */
{"dotenvy", CBM_SVC_CONFIG, NULL},
{"figment", CBM_SVC_CONFIG, NULL},
{"config-rs", CBM_SVC_CONFIG, NULL},
/* Java/Scala */
{"ConfigFactory", CBM_SVC_CONFIG, NULL},
{"ConfigurationProperties", CBM_SVC_CONFIG, NULL},
/* Elixir */
{"Application.get_env", CBM_SVC_CONFIG, NULL},
{"Application.fetch_env", CBM_SVC_CONFIG, NULL},
{NULL, CBM_SVC_NONE, NULL},
};
/* Route registration frameworks — callee resolves to one of these AND
* has an HTTP method suffix → CBM_SVC_ROUTE_REG.
* Distinguished from HTTP clients: "gin.GET" registers a handler,
* "requests.get" makes an outbound HTTP call. */
static const lib_pattern_t route_reg_libraries[] = {
/* Go */
{"gin-gonic/gin", CBM_SVC_ROUTE_REG, NULL},
{"gin.", CBM_SVC_ROUTE_REG, NULL},
{"go-chi/chi", CBM_SVC_ROUTE_REG, NULL},
{"chi.", CBM_SVC_ROUTE_REG, NULL},
{"gorilla/mux", CBM_SVC_ROUTE_REG, NULL},
{"labstack/echo", CBM_SVC_ROUTE_REG, NULL},
{"echo.", CBM_SVC_ROUTE_REG, NULL},
{"gofiber/fiber", CBM_SVC_ROUTE_REG, NULL},
{"fiber.", CBM_SVC_ROUTE_REG, NULL},
{"net/http.ServeMux", CBM_SVC_ROUTE_REG, NULL},
{"http.ServeMux", CBM_SVC_ROUTE_REG, NULL},
{"httprouter", CBM_SVC_ROUTE_REG, NULL},
/* JavaScript / TypeScript */
{"express", CBM_SVC_ROUTE_REG, NULL},
{"fastify", CBM_SVC_ROUTE_REG, NULL},
{"koa-router", CBM_SVC_ROUTE_REG, NULL},
{"hono", CBM_SVC_ROUTE_REG, NULL},
{"hapi", CBM_SVC_ROUTE_REG, NULL},
/* Python (non-decorator, e.g., Flask add_url_rule) */
{"flask", CBM_SVC_ROUTE_REG, NULL},
{"FastAPI", CBM_SVC_ROUTE_REG, NULL},
{"starlette", CBM_SVC_ROUTE_REG, NULL},
/* PHP */
{"Laravel", CBM_SVC_ROUTE_REG, NULL},
{"Illuminate.Routing", CBM_SVC_ROUTE_REG, NULL},
{"Symfony.Routing", CBM_SVC_ROUTE_REG, NULL},
/* Kotlin */
{"ktor.server", CBM_SVC_ROUTE_REG, NULL},
{"ktor.routing", CBM_SVC_ROUTE_REG, NULL},
/* Rust */
{"actix-web", CBM_SVC_ROUTE_REG, NULL},
{"actix_web", CBM_SVC_ROUTE_REG, NULL},
{"axum", CBM_SVC_ROUTE_REG, NULL},
{"rocket", CBM_SVC_ROUTE_REG, NULL},
/* Java */
{"Spring", CBM_SVC_ROUTE_REG, NULL},
{"jakarta.ws.rs", CBM_SVC_ROUTE_REG, NULL},
/* C# */
{"Microsoft.AspNetCore", CBM_SVC_ROUTE_REG, NULL},
{"MapGet", CBM_SVC_ROUTE_REG, NULL},
{"MapPost", CBM_SVC_ROUTE_REG, NULL},
/* Ruby */
{"ActionDispatch", CBM_SVC_ROUTE_REG, NULL},
{"Sinatra", CBM_SVC_ROUTE_REG, NULL},
/* Elixir */
{"Phoenix.Router", CBM_SVC_ROUTE_REG, NULL},
/* Scala */
{"akka.http.scaladsl.server", CBM_SVC_ROUTE_REG, NULL},
{"play.api.routing", CBM_SVC_ROUTE_REG, NULL},
{NULL, CBM_SVC_NONE, NULL},
};
/* gRPC client libraries — protobuf stub invocations */
static const lib_pattern_t grpc_libraries[] = {
/* Go */
{"google.golang.org/grpc", CBM_SVC_GRPC, NULL},
{"grpc.Dial", CBM_SVC_GRPC, NULL},
{"grpc.NewClient", CBM_SVC_GRPC, NULL},
{"grpc.DialContext", CBM_SVC_GRPC, NULL},
/* Python */
{"grpc.insecure_channel", CBM_SVC_GRPC, NULL},
{"grpc.secure_channel", CBM_SVC_GRPC, NULL},
{"grpcio", CBM_SVC_GRPC, NULL},
{"grpc.aio", CBM_SVC_GRPC, NULL},
/* Java/Kotlin */
{"io.grpc", CBM_SVC_GRPC, NULL},
{"ManagedChannelBuilder", CBM_SVC_GRPC, NULL},
{"ManagedChannel", CBM_SVC_GRPC, NULL},
{"newBlockingStub", CBM_SVC_GRPC, NULL},
{"newFutureStub", CBM_SVC_GRPC, NULL},
/* C# */
{"Grpc.Net.Client", CBM_SVC_GRPC, NULL},
{"GrpcChannel", CBM_SVC_GRPC, NULL},
{"Grpc.Core", CBM_SVC_GRPC, NULL},
/* JS/TS */
{"@grpc/grpc-js", CBM_SVC_GRPC, NULL},
{"grpc-web", CBM_SVC_GRPC, NULL},
/* Rust */
{"tonic", CBM_SVC_GRPC, NULL},
/* Dart/Flutter */
{"package:grpc", CBM_SVC_GRPC, NULL},
{NULL, CBM_SVC_NONE, NULL},
};
/* GraphQL client libraries */
static const lib_pattern_t graphql_libraries[] = {
/* JS/TS */
{"graphql-request", CBM_SVC_GRAPHQL, NULL},
{"@apollo/client", CBM_SVC_GRAPHQL, NULL},
{"apollo-client", CBM_SVC_GRAPHQL, NULL},
{"urql", CBM_SVC_GRAPHQL, NULL},
{"graphql-tag", CBM_SVC_GRAPHQL, NULL},
/* Python */
{"gql", CBM_SVC_GRAPHQL, NULL},
{"sgqlc", CBM_SVC_GRAPHQL, NULL},
{"graphene", CBM_SVC_GRAPHQL, NULL},
/* Java */
{"graphql-java", CBM_SVC_GRAPHQL, NULL},
{"DgsQueryExecutor", CBM_SVC_GRAPHQL, NULL},
/* Go */
{"graphql-go", CBM_SVC_GRAPHQL, NULL},
{"gqlgen", CBM_SVC_GRAPHQL, NULL},
/* Ruby */
{"graphql-ruby", CBM_SVC_GRAPHQL, NULL},
/* Rust */
{"async-graphql", CBM_SVC_GRAPHQL, NULL},
{"juniper", CBM_SVC_GRAPHQL, NULL},
{NULL, CBM_SVC_NONE, NULL},
};
/* tRPC libraries (TypeScript only) */
static const lib_pattern_t trpc_libraries[] = {
{"@trpc/server", CBM_SVC_TRPC, NULL},
{"@trpc/client", CBM_SVC_TRPC, NULL},
{"@trpc/react-query", CBM_SVC_TRPC, NULL},
{"createTRPCRouter", CBM_SVC_TRPC, NULL},
{"createTRPCProxyClient", CBM_SVC_TRPC, NULL},
{NULL, CBM_SVC_NONE, NULL},
};
/* Method suffix type (used by both route registration and HTTP client tables) */
typedef struct {
const char *suffix;
const char *method;
} method_suffix_t;
/* Route registration method suffixes — matched on callee name.
* These are methods on router objects that register handlers. */
static const method_suffix_t route_reg_suffixes[] = {
/* HTTP method registrations */
{".GET", "GET"},
{".Get", "GET"},
{".get", "GET"},
{".POST", "POST"},
{".Post", "POST"},
{".post", "POST"},
{".PUT", "PUT"},
{".Put", "PUT"},
{".put", "PUT"},
{".DELETE", "DELETE"},
{".Delete", "DELETE"},
{".delete", "DELETE"},
{".PATCH", "PATCH"},
{".Patch", "PATCH"},
{".patch", "PATCH"},
/* Handle/HandleFunc (Go stdlib, gorilla) */
{".Handle", "ANY"},
{".HandleFunc", "ANY"},
{".handle", "ANY"},
/* Framework-specific route registration */
{".Route", "ANY"},
{".route", "ANY"},
{"::get", "GET"},
{"::post", "POST"},
{"::put", "PUT"},
{"::delete", "DELETE"},
{"::patch", "PATCH"},
/* Minimal API (C# ASP.NET) */
{".MapGet", "GET"},
{".MapPost", "POST"},
{".MapPut", "PUT"},
{".MapDelete", "DELETE"},
/* Router mounting / prefix registration (any method) */
{".include_router", "ANY"},
{".mount", "ANY"},
{".add_url_rule", "ANY"},
{".register_blueprint", "ANY"},
{".use", "ANY"},
{".register", "ANY"},
{".add_route", "ANY"},
{".add_api_route", "ANY"},
{".add_api_websocket_route", "ANY"},
{NULL, NULL},
};
/* ── HTTP method inference from function/method name suffix ───── */
static const method_suffix_t method_suffixes[] = {
{".get", "GET"}, {".Get", "GET"}, {".GET", "GET"},
{".post", "POST"}, {".Post", "POST"}, {".POST", "POST"},
{".put", "PUT"}, {".Put", "PUT"}, {".PUT", "PUT"},
{".delete", "DELETE"}, {".Delete", "DELETE"}, {".DELETE", "DELETE"},
{".patch", "PATCH"}, {".Patch", "PATCH"}, {".PATCH", "PATCH"},
{".head", "HEAD"}, {".Head", "HEAD"}, {".HEAD", "HEAD"},
{".options", "OPTIONS"}, {".Options", "OPTIONS"}, {"GetAsync", "GET"},
{"PostAsync", "POST"}, {"PutAsync", "PUT"}, {"DeleteAsync", "DELETE"},
{"SendAsync", NULL}, {"getForObject", "GET"}, {"getForEntity", "GET"},
{"postForObject", "POST"}, {"postForEntity", "POST"}, {NULL, NULL},
};
/* ── Matching implementation ───────────────────────────────────── */
/* Check if any library identifier appears as a substring in the QN.
* Case-sensitive: "requests" matches "project.venv.requests.api.get"
* but not "Requests". Library names are specific enough to avoid
* false positives even with substring matching. */
static const lib_pattern_t *match_qn(const char *qn, const lib_pattern_t *patterns) {
if (!qn || !qn[0]) {
return NULL;
}
for (int i = 0; patterns[i].library_id != NULL; i++) {
if (strstr(qn, patterns[i].library_id) != NULL) {
return &patterns[i];
}
}
return NULL;
}
static bool starts_with_segment(const char *path, const char *segment) {
if (!path || path[0] != '/' || !segment) {
return false;
}
size_t seg_len = strlen(segment);
const char *p = path + 1;
return strncmp(p, segment, seg_len) == 0 && (p[seg_len] == '\0' || p[seg_len] == '/');
}
static bool contains_segment(const char *path, const char *segment) {
if (!path || !segment) {
return false;
}
size_t seg_len = strlen(segment);
const char *p = path;
while ((p = strchr(p, '/')) != NULL) {
p++;
if (strncmp(p, segment, seg_len) == 0 && (p[seg_len] == '\0' || p[seg_len] == '/')) {
return true;
}
}
return false;
}
static bool is_digit_char(char ch) {
return ch >= '0' && ch <= '9';
}
static bool has_http_route_marker(const char *path) {
if (starts_with_segment(path, "api") || starts_with_segment(path, "apis") ||
starts_with_segment(path, "graphql") || starts_with_segment(path, "health") ||
starts_with_segment(path, "metrics")) {
return true;
}
return path && path[0] == '/' && path[1] == 'v' && is_digit_char(path[2]) &&
(path[3] == '\0' || path[3] == '/');
}
static bool has_filesystem_root(const char *path) {
static const char *const roots[] = {"etc", "root", "var", "usr", "home", "tmp",
"private", "opt", "bin", "sbin", "dev", "proc",
"sys", "run", "lib", "lib64", "mnt", "media",
"boot", "srv", "Users", "Volumes", NULL};
for (int i = 0; roots[i]; i++) {
if (starts_with_segment(path, roots[i])) {
return true;
}
}
return false;
}
static bool has_hidden_config_segment(const char *path) {
static const char *const segments[] = {".aws", ".azure", ".config", ".docker", ".env",
".git", ".gnupg", ".kube", ".ssh", NULL};
for (int i = 0; segments[i]; i++) {
if (contains_segment(path, segments[i])) {
return true;
}
}
return false;
}
static bool path_ext_matches(const char *ext, const char *wanted) {
return ext && wanted && strcmp(ext, wanted) == 0;
}
static bool has_filesystem_extension(const char *path) {
if (!path) {
return false;
}
const char *end = strpbrk(path, "?#");
if (!end) {
end = path + strlen(path);
}
const char *last_slash = path;
for (const char *p = path; p < end; p++) {
if (*p == '/') {
last_slash = p;
}
}
const char *dot = NULL;
for (const char *p = last_slash + 1; p < end; p++) {
if (*p == '.') {
dot = p;
}
}
if (!dot || dot == end - 1) {
return false;
}
char ext[32];
size_t ext_len = (size_t)(end - dot);
if (ext_len >= sizeof(ext)) {
return false;
}
memcpy(ext, dot, ext_len);
ext[ext_len] = '\0';
static const char *const hard_file_exts[] = {
".cfg", ".conf", ".credentials", ".crt", ".db", ".env",
".ini", ".key", ".pem", ".pid", ".properties", ".service",
".sock", ".socket", ".sqlite", ".toml", NULL};
for (int i = 0; hard_file_exts[i]; i++) {
if (path_ext_matches(ext, hard_file_exts[i])) {
return true;
}
}
if ((path_ext_matches(ext, ".json") || path_ext_matches(ext, ".yaml") ||
path_ext_matches(ext, ".yml") || path_ext_matches(ext, ".xml")) &&
!has_http_route_marker(path)) {
return true;
}
return false;
}
static bool callee_is_delimiter_or_filesystem_builder(const char *callee_name) {
if (!callee_name) {
return false;
}
const char *last_dot = strrchr(callee_name, '.');
const char *last_colon = strstr(callee_name, "::");
const char *method = callee_name;
if (last_dot && last_dot[1]) {
method = last_dot + 1;
}
if (last_colon && last_colon[2]) {
method = last_colon + 2;
}
if (strcmp(method, "split") == 0 || strcmp(method, "rsplit") == 0 ||
strcmp(method, "partition") == 0 || strcmp(method, "join") == 0) {
return true;
}
return strstr(callee_name, "os.path.join") != NULL || strstr(callee_name, "path.join") != NULL;
}
static const char *strip_string_delimiters(const char *literal, char *buf, size_t buf_sz) {
if (!literal || !literal[0]) {
return NULL;
}
const char *start = literal;
while (*start == ' ' || *start == '\t' || *start == '\n' || *start == '\r') {
start++;
}
size_t len = strlen(start);
while (len > 0 && (start[len - 1] == ' ' || start[len - 1] == '\t' || start[len - 1] == '\n' ||
start[len - 1] == '\r')) {
len--;
}
if (len >= 2 && (start[0] == '"' || start[0] == '\'' || start[0] == '`') &&
start[len - 1] == start[0]) {
start++;
len -= 2;
}
if (len == 0 || len >= buf_sz) {
return NULL;
}
memcpy(buf, start, len);
buf[len] = '\0';
return buf;
}
bool cbm_service_pattern_is_http_route_literal(const char *literal, const char *callee_name) {
char path_buf[1024];
const char *path = strip_string_delimiters(literal, path_buf, sizeof(path_buf));
if (!path || !path[0]) {
return false;
}
if (strncmp(path, "http://", 7) == 0 || strncmp(path, "https://", 8) == 0) {
return true;
}
if (strstr(path, "://") != NULL) {
return false;
}
if (path[0] != '/') {
return false;
}
if (callee_is_delimiter_or_filesystem_builder(callee_name)) {
return false;
}
if (has_filesystem_root(path) || has_hidden_config_segment(path) ||
has_filesystem_extension(path)) {
return false;
}
return true;
}
/* ── Public API ────────────────────────────────────────────────── */
/* Per-worker TLS cache of cbm_service_pattern_match results.
* The hot path in resolve_file_calls invokes pattern matching for
* EVERY resolved CALL (via emit_service_edge) — that's 6 pattern-list
* scans × ~30 patterns × strstr per call. On kubernetes (~600k
* resolved call edges), the same resolved QN (e.g. "context.Context.
* Done", "fmt.Errorf", "errors.New") repeats hundreds of thousands of
* times. A simple TLS hash cache turns the linear scan into one
* lookup after the first miss for that QN. Lifetime is per-worker for
* the duration of the parallel_resolve phase. */
#include "foundation/hash_table.h"
#include "foundation/compat.h"
static CBM_TLS CBMHashTable *_svc_cache = NULL;
/* Encode the enum + 1 in the pointer so 0/NULL means "miss". */
static inline void *svc_enum_to_ptr(cbm_svc_kind_t k) {
return (void *)(uintptr_t)((unsigned)k + 1u);
}
static inline cbm_svc_kind_t svc_ptr_to_enum(void *p) {
return (cbm_svc_kind_t)((uintptr_t)p - 1u);
}
static void svc_cache_free_key(const char *key, void *val, void *ud) {
(void)val;
(void)ud;
free((char *)key);
}
void cbm_service_pattern_cache_begin(void) {
if (_svc_cache)
return; /* idempotent */
_svc_cache = cbm_ht_create(8192);
}
void cbm_service_pattern_cache_end(void) {
if (!_svc_cache)
return;
cbm_ht_foreach(_svc_cache, svc_cache_free_key, NULL);
cbm_ht_free(_svc_cache);
_svc_cache = NULL;
}
void cbm_service_patterns_init(void) {
/* No-op — tables are static const */
}
bool cbm_service_pattern_is_global_fetch(const char *callee_name) {
return callee_name != NULL && strcmp(callee_name, "fetch") == 0;
}
cbm_svc_kind_t cbm_service_pattern_match(const char *resolved_qn) {
if (!resolved_qn || !resolved_qn[0]) {
return CBM_SVC_NONE;
}
if (_svc_cache) {
void *cached = cbm_ht_get(_svc_cache, resolved_qn);
if (cached) {
return svc_ptr_to_enum(cached);
}
}
cbm_svc_kind_t result = CBM_SVC_NONE;
const lib_pattern_t *p;
/* Route registration checked first — prevents gin/echo from matching
* as HTTP clients (both have .get/.post suffixes). */
if ((p = match_qn(resolved_qn, route_reg_libraries)))
result = p->kind;
else if ((p = match_qn(resolved_qn, http_libraries)))
result = p->kind;
else if ((p = match_qn(resolved_qn, async_libraries)))
result = p->kind;
else if ((p = match_qn(resolved_qn, config_libraries)))
result = p->kind;
else if ((p = match_qn(resolved_qn, grpc_libraries)))
result = p->kind;
else if ((p = match_qn(resolved_qn, graphql_libraries)))
result = p->kind;
else if ((p = match_qn(resolved_qn, trpc_libraries)))
result = p->kind;
if (_svc_cache) {
char *kdup = strdup(resolved_qn);
if (kdup)
cbm_ht_set(_svc_cache, kdup, svc_enum_to_ptr(result));
}
return result;
}
const char *cbm_service_pattern_http_method(const char *callee_name) {
if (!callee_name) {
return NULL;
}
for (int i = 0; method_suffixes[i].suffix != NULL; i++) {
size_t slen = strlen(method_suffixes[i].suffix);
size_t clen = strlen(callee_name);
if (clen >= slen && strcmp(callee_name + clen - slen, method_suffixes[i].suffix) == 0) {
return method_suffixes[i].method;
}
}
return NULL;
}
const char *cbm_service_pattern_route_method(const char *callee_name) {
if (!callee_name) {
return NULL;
}
size_t clen = strlen(callee_name);
for (int i = 0; route_reg_suffixes[i].suffix != NULL; i++) {
size_t slen = strlen(route_reg_suffixes[i].suffix);
if (clen >= slen && strcmp(callee_name + clen - slen, route_reg_suffixes[i].suffix) == 0) {
return route_reg_suffixes[i].method;
}
}
return NULL;
}
const char *cbm_service_pattern_broker(const char *resolved_qn) {
if (!resolved_qn) {
return NULL;
}
const lib_pattern_t *p = match_qn(resolved_qn, async_libraries);
return p ? p->broker : NULL;
}