599 lines
23 KiB
C
599 lines
23 KiB
C
/*
|
|
* extract_sysctl.c — FreeBSD/DragonFly sysctl OID collector (pass A).
|
|
*
|
|
* Collects SYSCTL_* macro invocations into CBMSysctl records. The runtime
|
|
* dotted path (kern.ipc.maxsockbuf) is NOT resolved here — it requires the
|
|
* cross-file parent walk done by the repo-level pass (pass_sysctl.c), because a
|
|
* leaf names a parent C-symbol whose defining SYSCTL_NODE often lives in
|
|
* another translation unit.
|
|
*
|
|
* Two parse shapes must both be handled (verified against vendored
|
|
* tree-sitter-c):
|
|
* - bare SYSCTL_INT(...) -> call_expression(identifier, argument_list)
|
|
* - static SYSCTL_NODE(...) -> ERROR node; the macro name is an identifier
|
|
* child and the args nest inside a second
|
|
* inner ERROR node.
|
|
* Arguments are therefore read by flattening the terminal tokens between the
|
|
* macro '(' and its matching ')' and splitting on top-level commas — robust to
|
|
* whichever container/nesting the grammar produced.
|
|
*
|
|
* Intra-procedural resolution (recovers the SYSCTL_ADD_* / SYSCTL_CHILDREN
|
|
* dynamic-node family, ~half of all leaves): within a translation unit we
|
|
* track two local-variable aliasings so a leaf whose parent is a local var can
|
|
* still be attributed:
|
|
* node = SYSCTL_ADD_NODE(ctx, parent, nbr, "seg", ...);
|
|
* children = SYSCTL_CHILDREN(node);
|
|
* SYSCTL_ADD_INT(ctx, children, ..., "leaf", ...);
|
|
* We synthesize a stable symbol "<parent_sym>_<seg>" for such runtime nodes so
|
|
* the resolver joins them into the static tree, yielding the TEMPLATE path
|
|
* (e.g. dev.igb.rx_bytes) — the per-instance index (dev.igb.0.…) only exists at
|
|
* runtime and is intentionally out of scope.
|
|
*
|
|
* See docs/sysctl-extractor-design.md.
|
|
*/
|
|
#include "cbm.h"
|
|
#include "arena.h"
|
|
#include "helpers.h"
|
|
#include "foundation/constants.h"
|
|
#include "extract_node_stack.h"
|
|
#include "tree_sitter/api.h"
|
|
#include <string.h>
|
|
#include <stdio.h>
|
|
|
|
enum {
|
|
SYSCTL_STACK_CAP = 8192,
|
|
SYSCTL_MAX_ARGS = 16,
|
|
SYSCTL_ALIAS_CAP = 512, /* local var aliases tracked per file */
|
|
SYSCTL_TOKBUF = 256, /* max length of one flattened arg */
|
|
};
|
|
|
|
/* ── local-variable alias table (intra-procedural) ─────────────────
|
|
* Maps a C local variable name to the sysctl node C-symbol it refers to.
|
|
* Populated by `x = SYSCTL_ADD_NODE(...)` and `x = SYSCTL_CHILDREN(y)`. */
|
|
typedef struct {
|
|
const char *var; /* borrowed — arena */
|
|
const char *symbol; /* node C-symbol this var aliases (borrowed) */
|
|
} sysctl_alias_t;
|
|
|
|
typedef struct {
|
|
sysctl_alias_t items[SYSCTL_ALIAS_CAP];
|
|
int count;
|
|
} sysctl_alias_table_t;
|
|
|
|
static void alias_put(sysctl_alias_table_t *t, const char *var, const char *symbol) {
|
|
if (!var || !symbol) {
|
|
return;
|
|
}
|
|
for (int i = 0; i < t->count; i++) {
|
|
if (strcmp(t->items[i].var, var) == 0) {
|
|
t->items[i].symbol = symbol; /* last assignment wins */
|
|
return;
|
|
}
|
|
}
|
|
if (t->count < SYSCTL_ALIAS_CAP) {
|
|
t->items[t->count].var = var;
|
|
t->items[t->count].symbol = symbol;
|
|
t->count++;
|
|
}
|
|
}
|
|
|
|
static const char *alias_get(const sysctl_alias_table_t *t, const char *var) {
|
|
if (!var) {
|
|
return NULL;
|
|
}
|
|
for (int i = 0; i < t->count; i++) {
|
|
if (strcmp(t->items[i].var, var) == 0) {
|
|
return t->items[i].symbol;
|
|
}
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
/* ── macro classification ──────────────────────────────────────────
|
|
* Returns 0 = ignore, 1 = node, 2 = leaf. Sets *is_add for SYSCTL_ADD_* (extra
|
|
* leading ctx arg) and *is_root for root nodes (name in a shifted slot, no
|
|
* parent). */
|
|
static int classify_macro(const char *m, bool *is_add, bool *is_root) {
|
|
*is_add = false;
|
|
*is_root = false;
|
|
if (strncmp(m, "SYSCTL_", 7) != 0) {
|
|
return 0;
|
|
}
|
|
const char *tail = m + 7;
|
|
if (strncmp(tail, "ADD_", 4) == 0) {
|
|
*is_add = true;
|
|
tail += 4;
|
|
}
|
|
static const char *ignore[] = {
|
|
"DECL", "CHILDREN", "PARENT", "STATIC_CHILDREN", "FOREACH",
|
|
"IN", "OUT", "OUT_STR", "HANDLER_ARGS", "SIZEOF",
|
|
"SIZEOF_STRUCT", "ENFORCE_FLAGS", "NODE_CHILDREN", NULL,
|
|
};
|
|
for (int i = 0; ignore[i]; i++) {
|
|
if (strcmp(tail, ignore[i]) == 0) {
|
|
return 0;
|
|
}
|
|
}
|
|
if (strncmp(tail, "ROOT_NODE", 9) == 0) {
|
|
*is_root = true;
|
|
return 1;
|
|
}
|
|
if (strncmp(tail, "NODE", 4) == 0) { /* NODE, NODE_WITH_LABEL */
|
|
return 1;
|
|
}
|
|
return 2; /* every remaining SYSCTL_* declares a leaf */
|
|
}
|
|
|
|
/* ── argument flattening ───────────────────────────────────────────
|
|
* Collect the comma-separated argument slots of the invocation rooted at
|
|
* `inv` into `out`. Walks terminal tokens, tracks paren depth, splits on
|
|
* top-level commas. Each slot is arena-allocated, surrounding spaces trimmed.
|
|
* Returns the number of args (>= 0). */
|
|
typedef struct {
|
|
CBMExtractCtx *ctx;
|
|
const char **out;
|
|
int max;
|
|
int n;
|
|
char cur[SYSCTL_TOKBUF];
|
|
int cl;
|
|
int depth;
|
|
} argflat_t;
|
|
|
|
static void af_flush(argflat_t *a) {
|
|
if (a->n >= a->max) {
|
|
a->cl = 0;
|
|
return;
|
|
}
|
|
/* trim trailing spaces */
|
|
while (a->cl > 0 && a->cur[a->cl - 1] == ' ') {
|
|
a->cl--;
|
|
}
|
|
a->cur[a->cl] = '\0';
|
|
const char *p = a->cur;
|
|
while (*p == ' ') {
|
|
p++;
|
|
}
|
|
a->out[a->n++] = cbm_arena_strndup(a->ctx->arena, p, strlen(p));
|
|
a->cl = 0;
|
|
}
|
|
|
|
/* Append a node's verbatim source span to the current slot, no added spaces. */
|
|
static void af_emit_span(argflat_t *a, TSNode n) {
|
|
uint32_t sb = ts_node_start_byte(n);
|
|
uint32_t eb = ts_node_end_byte(n);
|
|
for (uint32_t i = sb; i < eb && a->cl < SYSCTL_TOKBUF - 1; i++) {
|
|
a->cur[a->cl++] = a->ctx->source[i];
|
|
}
|
|
}
|
|
|
|
static void af_walk(argflat_t *a, TSNode n) {
|
|
const char *t = ts_node_type(n);
|
|
|
|
/* Treat string literals and nested calls (e.g. SYSCTL_CHILDREN(x)) as
|
|
* atomic argument tokens — recursing into them would inject spaces between
|
|
* their sub-tokens and corrupt the value. Only at arg depth. */
|
|
if (a->depth >= 1 &&
|
|
(strcmp(t, "string_literal") == 0 || strcmp(t, "call_expression") == 0 ||
|
|
strcmp(t, "concatenated_string") == 0)) {
|
|
af_emit_span(a, n);
|
|
return;
|
|
}
|
|
|
|
uint32_t nc = ts_node_child_count(n);
|
|
if (nc == 0) {
|
|
if (strcmp(t, ",") == 0) {
|
|
if (a->depth == 1) {
|
|
af_flush(a);
|
|
} else if (a->cl < SYSCTL_TOKBUF - 1) {
|
|
a->cur[a->cl++] = ',';
|
|
}
|
|
return;
|
|
}
|
|
if (strcmp(t, "(") == 0) {
|
|
a->depth++;
|
|
if (a->depth == 1) {
|
|
return; /* opening paren of the macro call */
|
|
}
|
|
}
|
|
if (strcmp(t, ")") == 0) {
|
|
a->depth--;
|
|
if (a->depth == 0) {
|
|
return; /* closing paren of the macro call */
|
|
}
|
|
}
|
|
if (a->depth >= 1) {
|
|
af_emit_span(a, n); /* verbatim, no space padding */
|
|
}
|
|
return;
|
|
}
|
|
for (uint32_t i = 0; i < nc; i++) {
|
|
af_walk(a, ts_node_child(n, i));
|
|
}
|
|
}
|
|
|
|
static int collect_args(CBMExtractCtx *ctx, TSNode inv, const char **out, int max) {
|
|
argflat_t a = {.ctx = ctx, .out = out, .max = max, .n = 0, .cl = 0, .depth = 0};
|
|
af_walk(&a, inv);
|
|
af_flush(&a); /* final slot */
|
|
return a.n;
|
|
}
|
|
|
|
/* ── string / identifier helpers ───────────────────────────────────── */
|
|
|
|
/* Strip surrounding double quotes from a string-literal arg; return the inner
|
|
* text (arena). Returns NULL if not a quoted literal. */
|
|
static const char *unquote(CBMExtractCtx *ctx, const char *s) {
|
|
if (!s) {
|
|
return NULL;
|
|
}
|
|
size_t len = strlen(s);
|
|
if (len >= 2 && s[0] == '"' && s[len - 1] == '"') {
|
|
return cbm_arena_strndup(ctx->arena, s + 1, len - 2);
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
/* Extract the inner symbol of a SYSCTL_CHILDREN(x) / SYSCTL_STATIC_CHILDREN(x)
|
|
* argument. Returns the "x" token (arena), or NULL if the arg is not such a
|
|
* wrapper. */
|
|
static const char *children_inner(CBMExtractCtx *ctx, const char *arg) {
|
|
if (!arg) {
|
|
return NULL;
|
|
}
|
|
const char *open = NULL;
|
|
if (strncmp(arg, "SYSCTL_CHILDREN(", 16) == 0) {
|
|
open = arg + 16;
|
|
} else if (strncmp(arg, "SYSCTL_STATIC_CHILDREN(", 23) == 0) {
|
|
open = arg + 23;
|
|
} else {
|
|
return NULL;
|
|
}
|
|
const char *close = strchr(open, ')');
|
|
size_t n = close ? (size_t)(close - open) : strlen(open);
|
|
/* trim spaces */
|
|
while (n > 0 && open[0] == ' ') {
|
|
open++;
|
|
n--;
|
|
}
|
|
while (n > 0 && open[n - 1] == ' ') {
|
|
n--;
|
|
}
|
|
return cbm_arena_strndup(ctx->arena, open, n);
|
|
}
|
|
|
|
/* Synthesize a stable node symbol "<parent>_<seg>" for a runtime-added node so
|
|
* the resolver can join it into the static tree as a template path. */
|
|
static const char *synth_symbol(CBMExtractCtx *ctx, const char *parent_sym, const char *seg) {
|
|
if (!parent_sym || !seg) {
|
|
return NULL;
|
|
}
|
|
size_t n = strlen(parent_sym) + 1 + strlen(seg) + 1;
|
|
char *buf = cbm_arena_alloc(ctx->arena, n);
|
|
snprintf(buf, n, "%s_%s", parent_sym, seg);
|
|
return buf;
|
|
}
|
|
|
|
/* Resolve an argument that should name a parent OID-list to a node C-symbol:
|
|
* - "SYSCTL_CHILDREN(v)" -> alias_get(v) (or "_<v>" static node symbol)
|
|
* - a known local alias -> its symbol
|
|
* - a bare "_foo" static -> itself
|
|
* Returns NULL if not resolvable. */
|
|
/* Derive the driver name for a device-tree sysctl from the source path, e.g.
|
|
* sys/dev/bnxt/bnxt_en/bnxt_sysctl.c -> "bnxt". Returns a synthetic pre-resolved
|
|
* root symbol "@dev.<driver>" (the resolver treats a leading '@' as a literal
|
|
* path prefix), or NULL if the path is not under sys/dev/. These sysctls live in
|
|
* the runtime dev.<driver>.<unit>.* namespace; we recover the driver-level
|
|
* TEMPLATE path (unit index is runtime-only and intentionally omitted). */
|
|
static const char *device_tree_root(CBMExtractCtx *ctx) {
|
|
const char *p = ctx->rel_path;
|
|
if (!p) {
|
|
return NULL;
|
|
}
|
|
const char *dev = strstr(p, "sys/dev/");
|
|
if (!dev) {
|
|
dev = (strncmp(p, "dev/", 4) == 0) ? p : NULL;
|
|
if (!dev) {
|
|
return NULL;
|
|
}
|
|
dev += 4;
|
|
} else {
|
|
dev += 8; /* strlen("sys/dev/") */
|
|
}
|
|
const char *slash = strchr(dev, '/');
|
|
size_t n = slash ? (size_t)(slash - dev) : strlen(dev);
|
|
if (n == 0 || n > 48) {
|
|
return NULL;
|
|
}
|
|
char *sym = cbm_arena_alloc(ctx->arena, 5 + n + 1); /* "@dev." + name */
|
|
snprintf(sym, 5 + n + 1, "@dev.%.*s", (int)n, dev);
|
|
return sym;
|
|
}
|
|
|
|
static const char *resolve_parent_arg(CBMExtractCtx *ctx, const sysctl_alias_table_t *aliases,
|
|
const char *arg) {
|
|
if (!arg) {
|
|
return NULL;
|
|
}
|
|
/* Device drivers register under SYSCTL_CHILDREN(device_get_sysctl_tree(dev)),
|
|
* a runtime node. Recover the dev.<driver> template from the file path. */
|
|
if (strstr(arg, "device_get_sysctl_tree") != NULL) {
|
|
return device_tree_root(ctx);
|
|
}
|
|
const char *inner = children_inner(ctx, arg);
|
|
if (inner) {
|
|
const char *sym = alias_get(aliases, inner);
|
|
if (sym) {
|
|
return sym;
|
|
}
|
|
if (strstr(inner, "device_get_sysctl_tree") != NULL) {
|
|
return device_tree_root(ctx);
|
|
}
|
|
/* SYSCTL_CHILDREN(&static_node) or SYSCTL_CHILDREN(static_sym) */
|
|
if (inner[0] == '&') {
|
|
inner++;
|
|
}
|
|
return inner; /* static node C-symbol */
|
|
}
|
|
const char *sym = alias_get(aliases, arg);
|
|
if (sym) {
|
|
return sym;
|
|
}
|
|
if (arg[0] == '_') {
|
|
return arg; /* bare static node symbol like _kern_ipc */
|
|
}
|
|
return NULL; /* unresolved local (e.g. a function param) */
|
|
}
|
|
|
|
/* Best-effort handler identifier: for PROC/OID the function pointer arg. Scan
|
|
* from the tail for the first token that is a plain identifier (not a string,
|
|
* address-of, number, or NULL). */
|
|
static const char *guess_handler(const char **args, int argc, int base) {
|
|
for (int i = argc - 1; i >= base; i--) {
|
|
const char *a = args[i];
|
|
if (a && a[0] != '"' && a[0] != '&' && a[0] != '0' && a[0] != '(' &&
|
|
strcmp(a, "NULL") != 0 && (a[0] == '_' || (a[0] >= 'a' && a[0] <= 'z') ||
|
|
(a[0] >= 'A' && a[0] <= 'Z'))) {
|
|
return a;
|
|
}
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
/* ── record emission ───────────────────────────────────────────────── */
|
|
|
|
/* Process one SYSCTL_* invocation. `assign_target` is the LHS var name when the
|
|
* invocation is the RHS of an assignment/declarator (for alias tracking), else
|
|
* NULL. */
|
|
/* When alias_only is true, update the alias table but do NOT push a record —
|
|
* used during the pass-1 alias-collection sweeps to avoid duplicate records. */
|
|
static void process_invocation_ex(CBMExtractCtx *ctx, sysctl_alias_table_t *aliases,
|
|
const char *macro, TSNode site, const char *assign_target,
|
|
bool alias_only) {
|
|
bool is_add = false, is_root = false;
|
|
int kind = classify_macro(macro, &is_add, &is_root);
|
|
if (kind == 0) {
|
|
return;
|
|
}
|
|
|
|
const char *args[SYSCTL_MAX_ARGS];
|
|
int argc = collect_args(ctx, site, args, SYSCTL_MAX_ARGS);
|
|
int base = is_add ? 1 : 0; /* skip leading ctx for SYSCTL_ADD_* */
|
|
|
|
const char *parent_arg = NULL;
|
|
const char *seg = NULL; /* leaf/segment name */
|
|
|
|
if (is_root) {
|
|
/* ROOT_NODE(nbr, name, ...) / ADD_ROOT_NODE(ctx, nbr, name, ...) */
|
|
if (argc > base + 1) {
|
|
seg = args[base + 1];
|
|
}
|
|
} else {
|
|
if (argc > base + 0) {
|
|
parent_arg = args[base + 0];
|
|
}
|
|
if (argc > base + 2) {
|
|
seg = args[base + 2];
|
|
}
|
|
}
|
|
if (!seg) {
|
|
return;
|
|
}
|
|
/* seg may be a quoted string (runtime ADD_) or a bare token (static). */
|
|
const char *seg_str = unquote(ctx, seg);
|
|
if (seg_str) {
|
|
seg = seg_str;
|
|
}
|
|
|
|
/* Resolve parent to a node C-symbol (static or via local alias). */
|
|
const char *parent_sym =
|
|
is_root ? NULL : resolve_parent_arg(ctx, aliases, parent_arg);
|
|
|
|
const char *self_sym = NULL;
|
|
if (kind == 1) {
|
|
/* A node: its own symbol is <parent>_<seg> (static) or synthesized. */
|
|
if (is_root) {
|
|
size_t n = 1 + strlen(seg) + 1;
|
|
char *buf = cbm_arena_alloc(ctx->arena, n);
|
|
snprintf(buf, n, "_%s", seg);
|
|
self_sym = buf;
|
|
} else if (parent_sym) {
|
|
self_sym = synth_symbol(ctx, parent_sym, seg);
|
|
}
|
|
/* Track alias: `x = SYSCTL_ADD_NODE(...)` binds x to this node. */
|
|
if (assign_target && self_sym) {
|
|
alias_put(aliases, assign_target, self_sym);
|
|
}
|
|
}
|
|
|
|
if (alias_only) {
|
|
return; /* pass-1 sweep: alias recorded above, emit nothing */
|
|
}
|
|
|
|
CBMSysctl s = {
|
|
.leaf_name = seg,
|
|
.parent_symbol = parent_sym,
|
|
.self_symbol = self_sym,
|
|
.handler = guess_handler(args, argc, base),
|
|
.enclosing_func_qn = cbm_enclosing_func_qn_cached(ctx, site),
|
|
.file_rel = ctx->rel_path,
|
|
.is_node = (kind == 1),
|
|
.line = (int)ts_node_start_point(site).row + 1,
|
|
};
|
|
cbm_sysctl_push(&ctx->result->sysctls, ctx->arena, s);
|
|
}
|
|
|
|
static void process_invocation(CBMExtractCtx *ctx, sysctl_alias_table_t *aliases,
|
|
const char *macro, TSNode site, const char *assign_target) {
|
|
process_invocation_ex(ctx, aliases, macro, site, assign_target, false);
|
|
}
|
|
|
|
/* Handle `lhs = SYSCTL_CHILDREN(x)` / `lhs = SYSCTL_STATIC_CHILDREN(x)`:
|
|
* bind lhs to the same node x aliases (no record emitted — CHILDREN is an
|
|
* accessor, not a declaration). Returns true if consumed. */
|
|
static bool try_children_assign(CBMExtractCtx *ctx, sysctl_alias_table_t *aliases,
|
|
const char *lhs, const char *rhs_text) {
|
|
const char *inner = children_inner(ctx, rhs_text);
|
|
if (!inner || !lhs) {
|
|
return false;
|
|
}
|
|
const char *sym = alias_get(aliases, inner);
|
|
if (!sym) {
|
|
if (inner[0] == '&') {
|
|
inner++;
|
|
}
|
|
sym = inner; /* static node symbol */
|
|
}
|
|
alias_put(aliases, lhs, sym);
|
|
return true;
|
|
}
|
|
|
|
/* ── AST walk ───────────────────────────────────────────────────────
|
|
* Single stack walk. For each node:
|
|
* - call_expression whose function is a SYSCTL_ identifier -> invocation
|
|
* - ERROR node whose first identifier child is SYSCTL_ -> invocation
|
|
* - init_declarator / assignment_expression -> capture LHS var for aliasing
|
|
* The assignment context is detected by inspecting the invocation's parent. */
|
|
|
|
/* Return the LHS variable name if `call` sits on the RHS of an assignment or
|
|
* an initialized declarator, else NULL. Also handles `lhs = SYSCTL_CHILDREN(x)`
|
|
* for alias-only binding (handled by caller via rhs text). */
|
|
static const char *assign_lhs_of(CBMExtractCtx *ctx, TSNode call) {
|
|
TSNode parent = ts_node_parent(call);
|
|
if (ts_node_is_null(parent)) {
|
|
return NULL;
|
|
}
|
|
const char *pt = ts_node_type(parent);
|
|
if (strcmp(pt, "init_declarator") == 0) {
|
|
TSNode decl = ts_node_child_by_field_name(parent, TS_FIELD("declarator"));
|
|
if (!ts_node_is_null(decl)) {
|
|
return cbm_node_text(ctx->arena, decl, ctx->source);
|
|
}
|
|
} else if (strcmp(pt, "assignment_expression") == 0) {
|
|
TSNode lhs = ts_node_child_by_field_name(parent, TS_FIELD("left"));
|
|
if (!ts_node_is_null(lhs)) {
|
|
return cbm_node_text(ctx->arena, lhs, ctx->source);
|
|
}
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
/* Pass 1: collect local-variable aliases regardless of source order. Kernel
|
|
* registration code frequently uses a node variable (SYSCTL_CHILDREN(x)) many
|
|
* lines BEFORE the `x = SYSCTL_ADD_NODE(...)` that defines it (helper split
|
|
* across functions), so a single source-order pass would resolve the uses
|
|
* before the def. We therefore seed every alias first, then emit. */
|
|
static void collect_aliases(CBMExtractCtx *ctx, sysctl_alias_table_t *aliases, TSNode root) {
|
|
TSNodeStack stack;
|
|
ts_nstack_init(&stack, ctx, SYSCTL_STACK_CAP);
|
|
ts_nstack_push(&stack, root);
|
|
while (stack.count > 0) {
|
|
TSNode node = ts_nstack_pop(&stack);
|
|
if (strcmp(ts_node_type(node), "call_expression") == 0) {
|
|
TSNode fn = ts_node_child_by_field_name(node, TS_FIELD("function"));
|
|
if (!ts_node_is_null(fn) && strcmp(ts_node_type(fn), "identifier") == 0) {
|
|
char *name = cbm_node_text(ctx->arena, fn, ctx->source);
|
|
if (name) {
|
|
const char *lhs = assign_lhs_of(ctx, node);
|
|
if (lhs && (strncmp(name, "SYSCTL_CHILDREN", 15) == 0 ||
|
|
strncmp(name, "SYSCTL_STATIC_CHILDREN", 22) == 0)) {
|
|
char *rhs = cbm_node_text(ctx->arena, node, ctx->source);
|
|
try_children_assign(ctx, aliases, lhs, rhs);
|
|
} else if (lhs && strncmp(name, "SYSCTL_ADD_NODE", 15) == 0) {
|
|
/* Synthesize the node symbol from its resolved parent so
|
|
* the var binds to it. alias_only: do not emit a record
|
|
* here (the main pass emits it). Two sweeps chain
|
|
* forward references. */
|
|
process_invocation_ex(ctx, aliases, name, node, lhs, true);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
ts_nstack_push_children(&stack, node);
|
|
}
|
|
}
|
|
|
|
static void extract_sysctl_c(CBMExtractCtx *ctx) {
|
|
sysctl_alias_table_t aliases = {.count = 0};
|
|
|
|
/* Two alias-collection sweeps: the second resolves forward-referenced node
|
|
* chains that the first could not (a var defined after its use, whose own
|
|
* parent was also a not-yet-seen var). Two passes suffice for the depths
|
|
* seen in practice (root -> group -> leaf); deeper chains degrade to
|
|
* unresolved rather than loop. */
|
|
collect_aliases(ctx, &aliases, ctx->root);
|
|
collect_aliases(ctx, &aliases, ctx->root);
|
|
|
|
TSNodeStack stack;
|
|
ts_nstack_init(&stack, ctx, SYSCTL_STACK_CAP);
|
|
ts_nstack_push(&stack, ctx->root);
|
|
|
|
while (stack.count > 0) {
|
|
TSNode node = ts_nstack_pop(&stack);
|
|
const char *nt = ts_node_type(node);
|
|
|
|
if (strcmp(nt, "call_expression") == 0) {
|
|
TSNode fn = ts_node_child_by_field_name(node, TS_FIELD("function"));
|
|
if (!ts_node_is_null(fn) &&
|
|
strcmp(ts_node_type(fn), "identifier") == 0) {
|
|
char *name = cbm_node_text(ctx->arena, fn, ctx->source);
|
|
if (name && strncmp(name, "SYSCTL_", 7) == 0) {
|
|
/* SYSCTL_CHILDREN assignments are alias-only (seeded in
|
|
* pass 1) — skip here. Everything else emits a record. */
|
|
if (strncmp(name, "SYSCTL_CHILDREN", 15) != 0 &&
|
|
strncmp(name, "SYSCTL_STATIC_CHILDREN", 22) != 0) {
|
|
process_invocation(ctx, &aliases, name, node, NULL);
|
|
}
|
|
}
|
|
}
|
|
} else if (strcmp(nt, "ERROR") == 0 || strcmp(nt, "macro_type_specifier") == 0) {
|
|
/* `static SYSCTL_NODE(...)` parses two ways depending on context:
|
|
* - as a bare ERROR node (isolated), or
|
|
* - as a `macro_type_specifier` inside a `declaration` (in situ,
|
|
* when the grammar treats the macro as a type). The SYSCTL_
|
|
* identifier is a direct child either way; args nest below. */
|
|
uint32_t nc = ts_node_child_count(node);
|
|
for (uint32_t i = 0; i < nc; i++) {
|
|
TSNode c = ts_node_child(node, i);
|
|
if (strcmp(ts_node_type(c), "identifier") != 0) {
|
|
continue;
|
|
}
|
|
char *name = cbm_node_text(ctx->arena, c, ctx->source);
|
|
if (!name || strncmp(name, "SYSCTL_", 7) != 0) {
|
|
continue;
|
|
}
|
|
/* Such a declaration is never an assignment RHS -> no alias. */
|
|
process_invocation(ctx, &aliases, name, node, NULL);
|
|
break; /* one macro per node */
|
|
}
|
|
}
|
|
ts_nstack_push_children(&stack, node);
|
|
}
|
|
}
|
|
|
|
void cbm_extract_sysctl(CBMExtractCtx *ctx) {
|
|
/* C only; C++ kernel modules are vanishingly rare and share the grammar. */
|
|
if (ctx->language == CBM_LANG_C || ctx->language == CBM_LANG_CPP) {
|
|
extract_sysctl_c(ctx);
|
|
}
|
|
}
|