Files
deusdata--codebase-memory-mcp/internal/cbm/extract_env_accesses.c
T
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

212 lines
7.1 KiB
C

#include "cbm.h"
#include "arena.h" // CBMArena, cbm_arena_strndup
#include "helpers.h"
#include "lang_specs.h"
#include "extract_unified.h"
#include "foundation/constants.h"
#include "extract_node_stack.h"
#include "tree_sitter/api.h" // TSNode, ts_node_*
#include <stdint.h> // uint32_t
#include <string.h>
#include <ctype.h>
/* Minimum length for an env var name (e.g., "DB"). */
enum { MIN_ENV_NAME_LEN = 2 };
// Unquote a string literal: "foo" -> foo, 'foo' -> foo
static const char *unquote(CBMArena *a, const char *s) {
if (!s || !s[0]) {
return NULL;
}
// Trim whitespace
while (*s == ' ' || *s == '\t') {
s++;
}
size_t len = strlen(s);
if (len >= CBM_QUOTE_PAIR) {
char first = s[0];
char last = s[len - CBM_QUOTE_OFFSET];
if ((first == '"' && last == '"') || (first == '\'' && last == '\'') ||
(first == '`' && last == '`')) {
return cbm_arena_strndup(a, s + CBM_QUOTE_OFFSET, len - CBM_QUOTE_PAIR);
}
}
return s;
}
// Extract env key from a function call like os.Getenv("KEY").
static const char *extract_env_key_from_call(CBMExtractCtx *ctx, TSNode node,
const CBMLangSpec *spec) {
if (!spec->env_access_functions || !spec->env_access_functions[0]) {
return NULL;
}
TSNode func_node = ts_node_child_by_field_name(node, TS_FIELD("function"));
if (ts_node_is_null(func_node)) {
return NULL;
}
char *callee = cbm_node_text(ctx->arena, func_node, ctx->source);
if (!callee) {
return NULL;
}
// Check if callee matches any env access function
bool match = false;
for (const char **ef = spec->env_access_functions; *ef; ef++) {
if (strcmp(callee, *ef) == 0) {
match = true;
break;
}
}
if (!match) {
return NULL;
}
// Get first argument (the env key)
TSNode args = ts_node_child_by_field_name(node, TS_FIELD("arguments"));
if (ts_node_is_null(args)) {
return NULL;
}
// Find first named child (skip parentheses)
for (uint32_t i = 0; i < ts_node_child_count(args); i++) {
TSNode child = ts_node_child(args, i);
const char *ck = ts_node_type(child);
if (strcmp(ck, "(") == 0 || strcmp(ck, ")") == 0 || strcmp(ck, ",") == 0) {
continue;
}
char *arg_text = cbm_node_text(ctx->arena, child, ctx->source);
return unquote(ctx->arena, arg_text);
}
return NULL;
}
// Extract env key from member access like process.env.KEY or os.environ["KEY"].
static const char *extract_env_key_from_member(CBMExtractCtx *ctx, TSNode node,
const CBMLangSpec *spec) {
if (!spec->env_access_member_patterns || !spec->env_access_member_patterns[0]) {
return NULL;
}
char *text = cbm_node_text(ctx->arena, node, ctx->source);
if (!text || !text[0]) {
return NULL;
}
for (const char **pat = spec->env_access_member_patterns; *pat; pat++) {
size_t plen = strlen(*pat);
// Dot access: pattern.KEY
if (strncmp(text, *pat, plen) == 0 && text[plen] == '.') {
const char *key = text + plen + CBM_QUOTE_OFFSET;
// Validate: no further dots/brackets
if (key[0] && !strchr(key, '.') && !strchr(key, '[')) {
return key;
}
}
// Subscript: pattern["KEY"]
if (strncmp(text, *pat, plen) == 0 && text[plen] == '[') {
const char *inner = text + plen + CBM_QUOTE_OFFSET;
size_t ilen = strlen(inner);
if (ilen > 0 && inner[ilen - CBM_QUOTE_OFFSET] == ']') {
char *bracket_content =
cbm_arena_strndup(ctx->arena, inner, ilen - CBM_QUOTE_OFFSET);
return unquote(ctx->arena, bracket_content);
}
}
}
return NULL;
}
// Check if an env key name looks like an environment variable (uppercase + underscores).
static bool is_env_var_name(const char *s) {
if (!s || strlen(s) < MIN_ENV_NAME_LEN) {
return false;
}
bool has_upper = false;
for (const char *p = s; *p; p++) {
if (*p >= 'A' && *p <= 'Z') {
{
has_upper = true;
}
} else if (*p == '_' || (*p >= '0' && *p <= '9')) { /* ok */
} else {
{ return false; }
}
}
return has_upper;
}
// Iterative env access walker — explicit stack
static void walk_env_accesses(CBMExtractCtx *ctx, TSNode root, const CBMLangSpec *spec) {
TSNodeStack stack;
ts_nstack_init(&stack, ctx->arena, 4096);
ts_nstack_push(&stack, ctx->arena, root);
while (stack.count > 0) {
TSNode node = ts_nstack_pop(&stack);
const char *kind = ts_node_type(node);
const char *env_key = NULL;
if (cbm_kind_in_set(node, spec->call_node_types)) {
env_key = extract_env_key_from_call(ctx, node, spec);
} else if (strcmp(kind, "member_expression") == 0 || strcmp(kind, "subscript") == 0 ||
strcmp(kind, "attribute") == 0) {
env_key = extract_env_key_from_member(ctx, node, spec);
}
if (env_key && env_key[0] && is_env_var_name(env_key)) {
CBMEnvAccess ea;
ea.env_key = env_key;
ea.enclosing_func_qn = cbm_enclosing_func_qn_cached(ctx, node);
cbm_envaccess_push(&ctx->result->env_accesses, ctx->arena, ea);
continue; // don't push children (avoid double-counting)
}
ts_nstack_push_children(&stack, ctx->arena, node);
}
}
void cbm_extract_env_accesses(CBMExtractCtx *ctx) {
const CBMLangSpec *spec = cbm_lang_spec(ctx->language);
if (!spec) {
return;
}
bool has_funcs = spec->env_access_functions && spec->env_access_functions[0];
bool has_members = spec->env_access_member_patterns && spec->env_access_member_patterns[0];
if (!has_funcs && !has_members) {
return;
}
walk_env_accesses(ctx, ctx->root, spec);
}
// --- Unified handler ---
void handle_env_accesses(CBMExtractCtx *ctx, TSNode node, const CBMLangSpec *spec,
WalkState *state) {
bool has_funcs = spec->env_access_functions && spec->env_access_functions[0];
bool has_members = spec->env_access_member_patterns && spec->env_access_member_patterns[0];
if (!has_funcs && !has_members) {
return;
}
const char *kind = ts_node_type(node);
const char *env_key = NULL;
if (has_funcs && spec->call_node_types && cbm_kind_in_set(node, spec->call_node_types)) {
env_key = extract_env_key_from_call(ctx, node, spec);
} else if (has_members && (strcmp(kind, "member_expression") == 0 ||
strcmp(kind, "subscript") == 0 || strcmp(kind, "attribute") == 0)) {
env_key = extract_env_key_from_member(ctx, node, spec);
}
if (env_key && env_key[0] && is_env_var_name(env_key)) {
CBMEnvAccess ea;
ea.env_key = env_key;
ea.enclosing_func_qn = state->enclosing_func_qn;
cbm_envaccess_push(&ctx->result->env_accesses, ctx->arena, ea);
}
}