fix(resolver): C tag uses, a stranded static's linkage, and bounded macro walks

A stranded static function lost its static flag and read as externally linked, so a call could bind across files: fixed with a decoy. Tag uses such as struct cfs_rq *cfs_rq now bind, kernel subtree USAGE 44,251 to 113,857, along with cast macros as receivers, C++ unions as classes, and two-headed function repair. Macro hand-off walks keep a seen set on macro and position: on doubling depth the work counter goes 35 to 47 where it was 155 to 8,225 without one.

Withdrawn as not provable: a rule binding a macro in one configuration branch and a function in another, because the call exists in only one, costing kernel subtree CALLS about 1,041; and a raw define beside a missing header, which that header may undefine, costing neovim about 5,500 edges that look true. Canonical lint passes.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
This commit is contained in:
Martin Vogel committed 2026-11-02 22:00:41 +01:00
1 parent 17096867bb
commit 8ee198fc4b
10 files changed
+1897 -179

No files matched your search

+126 -2
View File
@@ -944,9 +944,9 @@ static void cbm_attr_collect(TSNode node, const char *source, CBMAttrWords *word
return;
}
bool container = cbm_attr_is_container(ts_node_type(node));
bool unterminated = false;
TSTreeCursor cursor = ts_tree_cursor_new(node);
if (ts_tree_cursor_goto_first_child(&cursor)) {
bool unterminated = false;
do {
TSNode child = ts_tree_cursor_current_node(&cursor);
if (!ts_node_is_named(child)) {
@@ -1048,6 +1048,64 @@ typedef struct {
TSRange directive;
} CBMAttrConditional;
/* Past spaces and comments, on the same line only when `same_line` asks for it. */
static uint32_t cbm_attr_text_skip(const char *source, uint32_t length, uint32_t at,
bool same_line) {
while (at < length) {
char ch = source[at];
if (ch == ' ' || ch == '\t' || ch == '\r' || (!same_line && ch == '\n')) {
at++;
} else if (ch == '/' && at + 1 < length && source[at + 1] == '*') {
for (at += 2; at + 1 < length && !(source[at] == '*' && source[at + 1] == '/'); at++)
;
at = at + 2 <= length ? at + 2 : length;
} else if (ch == '/' && at + 1 < length && source[at + 1] == '/') {
while (at < length && source[at] != '\n')
at++;
} else {
break;
}
}
return at;
}
/* `void f(void)` with no `;` or brace of its own: a function's head, its body after the branch. */
static bool cbm_attr_text_is_head(const char *source, uint32_t from, uint32_t to) {
char last = 0;
for (uint32_t at = cbm_attr_text_skip(source, to, from, false); at < to;
at = cbm_attr_text_skip(source, to, at + 1, false)) {
char ch = source[at];
if (ch == ';' || ch == '{' || ch == '}' || ch == '"' || ch == '\'')
return false;
last = ch;
}
return last == ')';
}
/* The `#endif` that closes the conditional whose `#else` line ended at `from`, or `length`. */
static uint32_t cbm_attr_text_endif(const char *source, uint32_t length, uint32_t from) {
int nesting = 0;
for (uint32_t at = from; at < length; at++) {
if (source[at] != '\n')
continue;
uint32_t word = cbm_attr_text_skip(source, length, at + 1, true);
if (word >= length || source[word] != '#')
continue;
word = cbm_attr_text_skip(source, length, word + 1, true);
if (word + 2 < length && !memcmp(source + word, "if", 2)) {
nesting++;
} else if (word + 5 < length && !memcmp(source + word, "endif", 5)) {
if (!nesting)
return at + 1;
nesting--;
} else if (!nesting && word + 4 < length &&
(!memcmp(source + word, "else", 4) || !memcmp(source + word, "elif", 4))) {
return length; /* one alternative is all this reads */
}
}
return length;
}
static void cbm_attr_expression_branches(const char *source, uint32_t length, CBMAttrWords *words) {
CBMAttrConditional stack[CBM_ATTR_MAX_CONDITIONALS];
int top = 0, skipping = 0, depth = 0, overflow = 0;
@@ -1056,7 +1114,7 @@ static void cbm_attr_expression_branches(const char *source, uint32_t length, CB
/* Braces opened by `= {`. An initializer holds no `;`, so one closes the count whatever two
* branches of a conditional did to it. */
uint32_t initializer = 0;
bool after_assign = false;
bool after_assign = false, after_string = false;
while (at < length) {
char ch = source[at];
if (ch == '\n') {
@@ -1162,6 +1220,28 @@ static void cbm_attr_expression_branches(const char *source, uint32_t length, CB
frame->skip_start = at;
frame->skip_point = begin_point;
skipping++;
} else if (!frame->in_expression && !skipping && depth == frame->depth_at_open &&
cbm_attr_text_is_head(source, frame->directive.end_byte, at)) {
/* `#ifndef X / void f(void) / #else / void g(void) / #endif / {`: two heads and
* one body, and no grammar reads both. The first is read, as it is in a split
* expression, and the body is the same in either configuration. */
uint32_t close = cbm_attr_text_endif(source, length, end);
uint32_t after = close;
if (close < length) {
after = cbm_attr_text_skip(source, length, close, true);
while (after < length && source[after] != '\n')
after++;
after = cbm_attr_text_skip(source, length, after, false);
}
if (close < length && after < length && source[after] == '{' &&
cbm_attr_text_is_head(source, end, close)) {
frame->in_expression = true;
frame->skipping = true;
frame->skip_start = at;
frame->skip_point = begin_point;
skipping++;
cbm_attr_range_add(words, frame->directive);
}
} else if (!frame->in_expression && !skipping) {
/* `#if A / f(x, / #else / g(x, / #endif`: the branch leaves a parenthesis open
* that the code after the conditional closes, so each branch is half of a
@@ -1205,8 +1285,52 @@ static void cbm_attr_expression_branches(const char *source, uint32_t length, CB
}
if (at < length && source[at] == quote)
at++;
after_string = quote == '"';
continue;
}
if ((ch >= 'A' && ch <= 'Z') || (ch >= 'a' && ch <= 'z') || ch == '_') {
/* `"x" LUA_QL("=") " or "`: a macro call standing among string literals has to expand
* to a string, so the concatenation holds without it, and no grammar reads a call
* there. */
uint32_t word_end = at;
bool caps = true;
while (word_end < length && ((source[word_end] >= 'A' && source[word_end] <= 'Z') ||
(source[word_end] >= '0' && source[word_end] <= '9') ||
(source[word_end] >= 'a' && source[word_end] <= 'z') ||
source[word_end] == '_')) {
caps &= !(source[word_end] >= 'a' && source[word_end] <= 'z');
word_end++;
}
uint32_t open = cbm_attr_text_skip(source, length, word_end, true);
uint32_t quote = open < length && source[open] == '('
? cbm_attr_text_skip(source, length, open + 1, true)
: length;
if (!skipping && caps && word_end - at >= CBM_ATTR_MIN_CAPS && quote < length &&
source[quote] == '"') {
uint32_t close = quote + 1;
while (close < length && source[close] != '"' && source[close] != '\n')
close += source[close] == '\\' ? 2 : 1;
uint32_t paren = close < length && source[close] == '"'
? cbm_attr_text_skip(source, length, close + 1, true)
: length;
if (paren < length && source[paren] == ')') {
uint32_t next = cbm_attr_text_skip(source, length, paren + 1, false);
if (after_string || (next < length && source[next] == '"')) {
cbm_attr_range_add(words, (TSRange){{row, at - line_start},
{row, paren + 1 - line_start},
at,
paren + 1});
at = paren + 1;
continue;
}
}
}
after_string = false;
after_assign = false;
at = word_end;
continue;
}
after_string = false;
if (!skipping) {
if (ch == '(') {
depth++;
+29
View File
@@ -1154,6 +1154,7 @@ static bool is_binding_occurrence(CBMExtractCtx *ctx, TSNode node, const CBMLang
TSNode current = node;
TSTreeCursor *cursor = reset_occurrence_cursor(state, node);
bool bodyless_tag = false;
while (!ts_node_is_null(current)) {
TSNode parent;
const char *field;
@@ -1163,6 +1164,15 @@ static bool is_binding_occurrence(CBMExtractCtx *ctx, TSNode node, const CBMLang
if (is_value_field(field)) {
return false;
}
/* `struct X;` at file scope or in a body has no node of its own around the specifier. */
if (bodyless_tag && !field &&
(strcmp(ts_node_type(parent), "translation_unit") == 0 ||
strcmp(ts_node_type(parent), "compound_statement") == 0 ||
strcmp(ts_node_type(parent), "declaration_list") == 0 ||
strcmp(ts_node_type(parent), "field_declaration_list") == 0 ||
strcmp(ts_node_type(parent), "template_declaration") == 0)) {
return true;
}
/* A declaration container binds its name/pattern, not the symbols used
* by its type annotation. Both TypeScript (`cfg: Config`) and Go
* (`cfg Config`) expose that annotation through the exact `type` field;
@@ -1170,10 +1180,29 @@ static bool is_binding_occurrence(CBMExtractCtx *ctx, TSNode node, const CBMLang
* The emitted occurrence remains an ordinary USAGE, never a callable
* reference merely because the target happens to be a type. */
if (field && strcmp(field, "type") == 0) {
/* `struct X;` declares the tag: nothing here uses the type. */
if (bodyless_tag &&
(strcmp(ts_node_type(parent), "declaration") == 0 ||
strcmp(ts_node_type(parent), "field_declaration") == 0) &&
ts_node_is_null(ts_node_child_by_field_name(parent, TS_FIELD("declarator")))) {
return true;
}
return false;
}
const char *kind = ts_node_type(parent);
/* `struct X *p` names a tag declared elsewhere. Only a body or a lone `struct X;` makes
* this the tag's own declaration. */
if ((ctx->language == CBM_LANG_C || ctx->language == CBM_LANG_CPP ||
ctx->language == CBM_LANG_CUDA) &&
field && strcmp(field, "name") == 0 &&
(strcmp(kind, "struct_specifier") == 0 || strcmp(kind, "union_specifier") == 0 ||
strcmp(kind, "enum_specifier") == 0 || strcmp(kind, "class_specifier") == 0) &&
ts_node_is_null(ts_node_child_by_field_name(parent, TS_FIELD("body")))) {
bodyless_tag = true;
current = parent;
continue;
}
/* PL/SQL: `parameter` is a ref_call ARGUMENT wrapper (upstream grammar
* naming), not a declaration; definition-side bindings use the distinct
* parameter_declaration kind. Skip it so call arguments stay ordinary
+446 -116
View File
@@ -1264,6 +1264,28 @@ static int c_proof_include_compare(const void *left, const void *right) {
return order ? order : strcmp(a->module, b->module);
}
static int c_proof_offset_compare(const void *left, const void *right) {
uint32_t a = *(const uint32_t *)left, b = *(const uint32_t *)right;
return (a > b) - (a < b);
}
static void c_proof_add_unknown_include(CLSPContext *ctx, uint32_t at) {
if (ctx->proof_unknown_include_count == ctx->proof_unknown_include_cap) {
int cap = ctx->proof_unknown_include_cap ? ctx->proof_unknown_include_cap * 2 : 8;
uint32_t *next = cbm_arena_alloc(ctx->arena, (size_t)cap * sizeof(*next));
if (!next) {
ctx->binding_failed = true;
return;
}
if (ctx->proof_unknown_include_count)
memcpy(next, ctx->proof_unknown_includes,
(size_t)ctx->proof_unknown_include_count * sizeof(*next));
ctx->proof_unknown_includes = next;
ctx->proof_unknown_include_cap = cap;
}
ctx->proof_unknown_includes[ctx->proof_unknown_include_count++] = at;
}
static void c_proof_include_site(CLSPContext *ctx, TSNode node) {
TSNode path = ts_node_child_by_field_name(node, "path", 4);
if (ts_node_is_null(path))
@@ -1273,6 +1295,7 @@ static void c_proof_include_site(CLSPContext *ctx, TSNode node) {
if (len < 3 ||
!((text[0] == '"' && text[len - 1] == '"') || (text[0] == '<' && text[len - 1] == '>')))
return;
bool quoted = text[0] == '"';
text[len - 1] = '\0';
text++;
int lo = 0, hi = ctx->include_count;
@@ -1283,10 +1306,16 @@ static void c_proof_include_site(CLSPContext *ctx, TSNode node) {
else
hi = mid;
}
bool known = false;
for (int i = lo; i < ctx->include_count && !strcmp(ctx->proof_includes[i].path, text); i++) {
if (ts_node_start_byte(node) < ctx->proof_includes[i].at)
ctx->proof_includes[i].at = ts_node_start_byte(node);
known |= ctx->proof_includes[i].file_qn ||
(ctx->proof_includes[i].module[0] && ctx->c_decls &&
cbm_c_file_facts(ctx->c_decls, ctx->proof_includes[i].module));
}
if (quoted && !known)
c_proof_add_unknown_include(ctx, ts_node_start_byte(node));
}
static TSNode c_proof_function_name(TSNode declaration) {
@@ -1457,6 +1486,14 @@ static void c_proof_collect(CLSPContext *ctx, TSNode node, const char *owner, in
text ? cbm_arena_sprintf(ctx->arena, "$type:%s.%s", owner, text) : NULL;
if (qn)
c_proof_add_declaration(ctx, qn, ts_node_end_byte(alias));
/* `typedef struct {...} Name;` gives no tag of that name. */
if (text && !ts_node_is_null(type) &&
ts_node_is_null(ts_node_child_by_field_name(type, "name", 4)) &&
!ts_node_is_null(ts_node_child_by_field_name(type, "body", 4))) {
const char *key = cbm_arena_sprintf(ctx->arena, "$no-tag:%s", text);
if (key)
c_proof_add_declaration(ctx, key, ts_node_end_byte(alias));
}
break;
}
alias = ts_node_child_by_field_name(alias, "declarator", 10);
@@ -1756,15 +1793,8 @@ static bool c_tu_add_closure(CLSPContext *ctx, const char *file, const cbm_resol
* what that includer had in view ahead of the `#include` was in view for this file too. A source
* file with includes of its own is another matter: Lua's lbaselib.c also sits in one amalgamation
* and is compiled alone. Each hop goes up one file, and a file seen twice ends it. */
static void c_proof_add_includer_context(CLSPContext *ctx) {
const char *current = c_own_file_key(ctx);
if (!ctx->c_decls || !current)
return;
const CBMCFileFacts *own_facts = cbm_c_file_facts(ctx->c_decls, current);
if (!own_facts || (own_facts->source_file && own_facts->include_count > 0))
return;
static void c_proof_walk_includers(CLSPContext *ctx, const char *current) {
const cbm_resolve_proof_t *via = NULL;
ctx->tu_adding_includer_context = true;
for (uint32_t hop = 0; hop < 64; hop++) {
const CBMCFileFacts *includer = cbm_c_sole_includer(ctx->c_decls, current);
const char *key =
@@ -1800,6 +1830,17 @@ static void c_proof_add_includer_context(CLSPContext *ctx) {
}
current = key;
}
}
static void c_proof_add_includer_context(CLSPContext *ctx) {
const char *current = c_own_file_key(ctx);
if (!ctx->c_decls || !current)
return;
const CBMCFileFacts *own_facts = cbm_c_file_facts(ctx->c_decls, current);
if (!own_facts || (own_facts->source_file && own_facts->include_count > 0))
return;
ctx->tu_adding_includer_context = true;
c_proof_walk_includers(ctx, current);
ctx->tu_adding_includer_context = false;
}
@@ -1846,6 +1887,8 @@ static void c_proof_build_translation_unit(CLSPContext *ctx) {
facts = cbm_c_file_facts(ctx->c_decls, queue[head++]);
if (!facts)
continue;
if (facts->has_unknown_include)
c_proof_add_unknown_include(ctx, include->at);
for (int g = 0; g < facts->generated_count; g++) {
const char *source = facts->generated_for[g];
if (!c_tu_find(ctx, source, strlen(source)) && strcmp(source, own_key) &&
@@ -1943,6 +1986,9 @@ static void c_proof_prepare(CLSPContext *ctx, TSNode root) {
sizeof(*ctx->proof_declarations), c_proof_declaration_compare);
c_proof_build_translation_unit(ctx);
c_proof_add_includer_context(ctx);
if (ctx->proof_unknown_include_count > 1)
qsort(ctx->proof_unknown_includes, (size_t)ctx->proof_unknown_include_count,
sizeof(*ctx->proof_unknown_includes), c_proof_offset_compare);
}
static void c_proof_record_using(CLSPContext *ctx, TSNode node) {
@@ -2435,7 +2481,6 @@ typedef struct {
const CBMCDecl **items;
uint32_t *from; /* first byte of this file where the definition is in force */
int count;
int8_t beside_function; /* c_macro_only_beside_function, once known: 0 unknown, 1 yes, -1 no */
} CMacroDefs;
/* `#if defined(REDISMODULE_CORE) / #define RedisModuleString robj / #endif`: server.h defines the
@@ -2462,7 +2507,6 @@ static bool c_macro_branch_is_off(CLSPContext *ctx, const CBMCDecl *decl) {
}
static CMacroDefs *c_macro_defs(CLSPContext *ctx, const char *name) {
/* Written only through a real entry: a count of zero returns before beside_function is set. */
static CMacroDefs none = {0};
const CBMCDeclLink *first = cbm_c_decl_lookup(ctx->c_decls, name);
if (!first)
@@ -2522,34 +2566,46 @@ static bool c_proof_macro_visible(CLSPContext *ctx, const char *name, uint32_t a
return false;
}
/* `#if COMPAT / #define getn(L) objlen(L) / #else / int (getn)(State *L); / #endif`: a function
* stands in the branch beside every definition of the macro this unit sees, so the macro does not
* decide the name. Every branch of a conditional is read, and the function's is one of them. */
static bool c_macro_only_beside_function(CLSPContext *ctx, const char *name) {
CMacroDefs *defs = c_macro_defs(ctx, name);
if (!defs->count)
/* `#define CALL() f()`, `#include "gen.h"` with nothing in the index behind it, then the use: that
* text may `#undef` and redefine CALL, so the definition read here is not shown to be the one in
* force. An unknown header before an unconditional definition changes nothing, since the later
* definition wins. Before a conditional one it may have decided the branch. Inside an included file
* the order is not known here, so an unknown header anywhere under the same include counts. `<...>`
* is the toolchain, which defines its own names. */
static bool c_macro_definition_unproven(CLSPContext *ctx, const CBMCDecl *decl, uint32_t at) {
if (!ctx->proof_unknown_include_count)
return false;
if (defs->beside_function)
return defs->beside_function > 0;
const CBMCDeclLink *first = cbm_c_decl_lookup(ctx->c_decls, name);
defs->beside_function = -1;
for (int i = 0; i < defs->count; i++) {
const CBMCDecl *macro = defs->items[i];
if (!macro->branch_group)
return false;
bool beside = false;
for (const CBMCDeclLink *link = first; link && !beside; link = link->next) {
const CBMCDecl *other = link->decl;
beside = (other->kind == CBM_C_DECL_PROTOTYPE ||
other->kind == CBM_C_DECL_DEFINED_IN_BRANCH) &&
other->branch_group == macro->branch_group && other->branch != macro->branch &&
!strcmp(other->name, name) && !strcmp(other->file, macro->file);
uint32_t from = 0; /* the first offset where an unknown header could still matter */
if (!decl->branch_group) {
if (!strcmp(decl->file, c_own_file_key(ctx))) {
const char *key = cbm_arena_sprintf(ctx->arena, "$macro:%s", decl->name);
int low = 0, high = ctx->proof_declaration_count;
while (key && low < high) {
int mid = low + (high - low) / 2;
if (strcmp(ctx->proof_declarations[mid].qn, key) < 0)
low = mid + 1;
else
high = mid;
}
for (; key && low < ctx->proof_declaration_count &&
!strcmp(ctx->proof_declarations[low].qn, key) &&
ctx->proof_declarations[low].at < at;
low++)
from = ctx->proof_declarations[low].at + 1;
} else {
const CTUModule *entry = c_tu_find(ctx, decl->file, strlen(decl->file));
from = entry ? entry->at : 0;
}
if (!beside)
return false;
}
defs->beside_function = 1;
return true;
int low = 0, high = ctx->proof_unknown_include_count;
while (low < high) {
int mid = low + (high - low) / 2;
if (ctx->proof_unknown_includes[mid] < from)
low = mid + 1;
else
high = mid;
}
return low < ctx->proof_unknown_include_count && ctx->proof_unknown_includes[low] < at;
}
static bool c_proof_using_visible(CLSPContext *ctx, const CProofUsing *using, uint32_t at) {
@@ -2597,7 +2653,7 @@ static cbm_resolve_result_t c_proof_name_lookup(CLSPContext *ctx, const char *te
}
if (cbm_scope_contains(ctx->current_scope, name))
return c_proof_missing("c_name_shadowed");
if (c_proof_macro_visible(ctx, name, at) && !c_macro_only_beside_function(ctx, name))
if (c_proof_macro_visible(ctx, name, at))
return c_proof_missing("c_macro_may_rewrite_name");
cbm_resolve_result_t result = {.analysis = CBM_RESOLVE_ANALYSIS_COMPLETE};
const char *relative =
@@ -3217,8 +3273,9 @@ const CBMType *c_parse_type_node(CLSPContext *ctx, TSNode node) {
return cbm_type_func(ctx->arena, NULL, NULL, NULL);
}
// struct_specifier / class_specifier (inline struct/class type)
if (strcmp(kind, "struct_specifier") == 0 || strcmp(kind, "class_specifier") == 0) {
// struct_specifier / class_specifier / union_specifier (inline type)
if (strcmp(kind, "struct_specifier") == 0 || strcmp(kind, "class_specifier") == 0 ||
strcmp(kind, "union_specifier") == 0) {
TSNode name_node = ts_node_child_by_field_name(node, "name", 4);
if (!ts_node_is_null(name_node)) {
char *name = c_node_text(ctx, name_node);
@@ -5328,9 +5385,34 @@ static bool c_nested_type_in_scope(const CLSPContext *ctx, const char *scope) {
(klass->embedded_types && klass->embedded_types[0]);
}
/* A scope named from a module of `module_len` bytes: the top level of that file, a namespace around
* this code, or one a visible `using namespace` brings here. */
static bool c_scope_reached(CLSPContext *ctx, const char *written, const char *scope,
size_t module_len, uint32_t at) {
if (!scope[module_len])
return true;
if (written[0] == ':')
return false;
const char *from = scope + module_len + 1;
size_t from_len = strlen(from);
const char *around = ctx->current_namespace;
if (around && ctx->module_qn && !strncmp(around, ctx->module_qn, ctx->module_qn_len) &&
around[ctx->module_qn_len] == '.') {
around += ctx->module_qn_len + 1;
if (!strncmp(around, from, from_len) && (!around[from_len] || around[from_len] == '.'))
return true;
}
for (int i = 0; i < ctx->proof_using_count; i++) {
const CProofUsing *using = &ctx->proof_usings[i];
if (using->namespace && using->supported && !strcmp(using->target, from) &&
c_proof_using_visible(ctx, using, at))
return true;
}
return false;
}
/* `ns::Name`: what the first segment means depends on where lookup starts. The declaration's scope,
* with the written qualifier taken off, has to be a place that lookup reaches: the top level of
* its file, a namespace around this code, or one a visible `using namespace` brings here. */
* with the written qualifier taken off, has to be a place that lookup reaches. */
static bool c_qualifier_starts_here(CLSPContext *ctx, const char *written, const char *file_qn,
const char *scope, uint32_t at) {
/* `p.dir.t.hpp.__file__` is the file whose declarations are named `p.dir.t.Name`. */
@@ -5347,26 +5429,7 @@ static bool c_qualifier_starts_here(CLSPContext *ctx, const char *written, const
file_len--;
if (strncmp(scope, file_qn, file_len) || (scope[file_len] && scope[file_len] != '.'))
return false;
if (!scope[file_len])
return true;
if (written[0] == ':')
return false;
const char *from = scope + file_len + 1;
size_t from_len = strlen(from);
const char *around = ctx->current_namespace;
if (around && ctx->module_qn && !strncmp(around, ctx->module_qn, ctx->module_qn_len) &&
around[ctx->module_qn_len] == '.') {
around += ctx->module_qn_len + 1;
if (!strncmp(around, from, from_len) && (!around[from_len] || around[from_len] == '.'))
return true;
}
for (int i = 0; i < ctx->proof_using_count; i++) {
const CProofUsing *using = &ctx->proof_usings[i];
if (using->namespace && using->supported && !strcmp(using->target, from) &&
c_proof_using_visible(ctx, using, at))
return true;
}
return false;
return c_scope_reached(ctx, written, scope, file_len, at);
}
/* Every declaration of this type name the translation unit sees. A name may be
@@ -5501,6 +5564,60 @@ static bool c_base_may_depend(CLSPContext *ctx, const char *name) {
return ctx->in_template && name && c_name_is_declared_as(ctx, name, CBM_C_DECL_CLASS_TEMPLATE);
}
/* `struct Name` is looked up among tags, and where lookup finds none it declares a new one. So it
* is not a typedef of something else (`typedef struct impl Name;`), not the typedef of an unnamed
* aggregate (`typedef struct {...} Name;` gave no tag), and in C++ not a namesake in a namespace
* that lookup does not reach from here. A plain `Name` had to be declared to compile. This need
* not. */
static bool c_tag_is_other_than(CLSPContext *ctx, const CBMRegisteredType *type, const char *name,
bool qualified, uint32_t at) {
if (type->alias_of && strcmp(type->alias_of, name))
return true;
/* This file's own typedefs are known on every pass, an included file's only where facts are. */
const char *no_tag = cbm_arena_sprintf(ctx->arena, "$no-tag:%s", name);
if (no_tag && c_proof_declared(ctx, no_tag, at))
return true;
for (const CBMCDeclLink *link = cbm_c_decl_lookup(ctx->c_decls, name); link;
link = link->next) {
const CBMCDecl *decl = link->decl;
if (decl->kind == CBM_C_DECL_UNNAMED_TYPEDEF && !strcmp(decl->name, name) &&
c_tu_find(ctx, decl->file, strlen(decl->file)))
return true;
}
if (!ctx->cpp_mode || ctx->plain_header || qualified)
return false;
const char *qn = type->qualified_name;
const char *dot = qn ? strrchr(qn, '.') : NULL;
const char *scope = dot ? cbm_arena_strndup(ctx->arena, qn, (size_t)(dot - qn)) : NULL;
if (!scope)
return true;
/* A type nested in a class passed the scope check of that class on its way here. */
if (cbm_registry_lookup_type(ctx->proof_registry, scope))
return false;
if (type->source_file_qn)
return !c_qualifier_starts_here(ctx, name, type->source_file_qn, scope, at);
/* The pass over one file alone has no facts, and every project type in it is this file's own.
*/
size_t module_len = ctx->module_qn_len;
return strncmp(scope, ctx->module_qn, module_len) ||
(scope[module_len] && scope[module_len] != '.') ||
!c_scope_reached(ctx, name, scope, module_len, at);
}
/* `struct S { ... }; struct S x;` in a block: x has the block's S, whatever an outer S declares.
* Bound under a key of its own, because `struct config config;` is an ordinary variable that hides
* no tag: C keeps the two names apart. */
static bool c_name_bound_locally(CLSPContext *ctx, const char *name);
static const char *c_local_tag_key(CLSPContext *ctx, const char *name) {
return cbm_arena_sprintf(ctx->arena, "struct %s", name);
}
static bool c_local_tag_hides(CLSPContext *ctx, const char *name) {
if (!ctx->local_tag_declared || strstr(name, "::"))
return false;
const char *key = c_local_tag_key(ctx, name);
return key && c_name_bound_locally(ctx, key);
}
static const CBMRegisteredType *c_annotation_owner(CLSPContext *ctx, TSNode site) {
TSNode annotation = ts_node_child_by_field_name(site, "type", 4);
if (ts_node_is_null(annotation)) {
@@ -5508,6 +5625,7 @@ static const CBMRegisteredType *c_annotation_owner(CLSPContext *ctx, TSNode site
if (!ts_node_is_null(declaration))
annotation = ts_node_child_by_field_name(declaration, "type", 4);
}
bool elaborated = false;
for (int depth = 0; depth < 8 && !ts_node_is_null(annotation); depth++) {
const char *kind = ts_node_type(annotation);
if (!strcmp(kind, "type_descriptor")) {
@@ -5516,6 +5634,7 @@ static const CBMRegisteredType *c_annotation_owner(CLSPContext *ctx, TSNode site
}
if (!strcmp(kind, "struct_specifier") || !strcmp(kind, "union_specifier") ||
!strcmp(kind, "class_specifier")) {
elaborated = true;
annotation = ts_node_child_by_field_name(annotation, "name", 4);
continue;
}
@@ -5558,8 +5677,15 @@ static const CBMRegisteredType *c_annotation_owner(CLSPContext *ctx, TSNode site
return NULL;
}
}
return c_proof_follow_alias(
ctx, c_proof_type_by_name(ctx, name, ts_node_start_byte(annotation)));
if (c_local_tag_hides(ctx, name))
return NULL;
uint32_t at = ts_node_start_byte(annotation);
const CBMRegisteredType *named = c_proof_type_by_name(ctx, name, at);
/* That tag says nothing about the type its namesake is. */
if (elaborated && named &&
c_tag_is_other_than(ctx, named, name, strstr(name, "::") != NULL, at))
return NULL;
return c_proof_follow_alias(ctx, named);
}
return NULL;
}
@@ -7376,6 +7502,67 @@ static const CBMRegisteredType *c_proof_written_cast(CLSPContext *ctx, TSNode si
return owner;
}
static uint32_t c_macro_use_arguments(TSNode call, TSNode *out, uint32_t cap);
/* `hl_table[i].x` with `#define hl_table ((HlGroup *)(ga.ga_data))`, `SDS_HDR(8, s)->len` with
* `#define SDS_HDR(T, s) ((struct sdshdr##T *)...)`: whatever the operand is, the replacement
* writes the type. Every definition in force has to write the same one. */
static const CBMRegisteredType *c_macro_cast_owner(CLSPContext *ctx, const char *macro, TSNode site,
TSNode call, const cbm_resolve_proof_t **proof,
const CBMType **shape) {
uint32_t at = ts_node_start_byte(site);
if (!ctx->c_decls || !macro || c_name_bound_locally(ctx, macro) ||
!c_proof_macro_visible(ctx, macro, at))
return NULL;
const CMacroDefs *defs = c_macro_defs(ctx, macro);
const char *own = c_own_file_key(ctx);
const CBMCMacroShape *agreed = NULL;
for (int d = 0; d < defs->count; d++) {
const CBMCDecl *decl = defs->items[d];
if (defs->from[d] >= at && strcmp(decl->file, own))
continue;
const CBMCMacroShape *written = decl->macro;
if (c_macro_definition_unproven(ctx, decl, at) || !written || !written->cast_type ||
written->object_like != ts_node_is_null(call))
return NULL;
if (agreed && (strcmp(agreed->cast_type, written->cast_type) ||
agreed->cast_pointers != written->cast_pointers ||
agreed->cast_pastes != written->cast_pastes ||
agreed->cast_param != written->cast_param))
return NULL;
agreed = written;
}
if (!agreed)
return NULL;
const char *name = agreed->cast_type;
if (agreed->cast_pastes) {
TSNode arguments[32];
uint32_t count = c_macro_use_arguments(call, arguments, 32);
const char *argument =
agreed->cast_param < count ? c_node_text(ctx, arguments[agreed->cast_param]) : NULL;
for (const char *c = argument; c && *c; c++)
if (!isalnum((unsigned char)*c) && *c != '_')
argument = NULL;
name = argument ? cbm_arena_sprintf(ctx->arena, "%s%s", name, argument) : NULL;
}
const CBMRegisteredType *owner = name ? c_proof_named_class(ctx, name, at) : NULL;
if (!owner)
return NULL;
cbm_resolve_result_t typed = cbm_lsp_proof_type_lookup(
&ctx->field_types, macro, site, "c-macro-replacement-cast", owner->qualified_name,
owner->declaration_identity, owner->graph_qualified_name, owner->source_flags,
"cast-written-in-the-replacement");
typed = c_proof_type_import(ctx, owner->qualified_name, at, typed);
if (!cbm_resolve_admit(&typed))
return NULL;
const CBMType *type = cbm_type_named(ctx->arena, owner->qualified_name);
for (int i = 0; i < agreed->cast_pointers; i++)
type = cbm_type_pointer(ctx->arena, type);
*proof = typed.proof;
*shape = type;
return owner;
}
/* The struct an expression denotes, as the written declarations along it establish
* it: a typed name, an extern object, a member of a known struct, or any of those
* under `*`, `&`, `[]` and parentheses. No initializer is consulted and nothing
@@ -7390,6 +7577,20 @@ static const CBMRegisteredType *c_proof_expression_owner(CLSPContext *ctx, TSNod
uint32_t at = ts_node_start_byte(node);
if (!strcmp(kind, "parenthesized_expression") && ts_node_named_child_count(node) == 1)
return c_proof_expression_owner(ctx, ts_node_named_child(node, 0), depth + 1, proof, shape);
if (!strcmp(kind, "identifier")) {
const CBMRegisteredType *cast =
c_macro_cast_owner(ctx, c_node_text(ctx, node), node, (TSNode){0}, proof, shape);
if (cast)
return cast;
} else if (!strcmp(kind, "call_expression")) {
TSNode callee = ts_node_child_by_field_name(node, "function", 8);
const CBMRegisteredType *cast =
!ts_node_is_null(callee) && !strcmp(ts_node_type(callee), "identifier")
? c_macro_cast_owner(ctx, c_node_text(ctx, callee), node, node, proof, shape)
: NULL;
if (cast)
return cast;
}
if (!strcmp(kind, "subscript_expression") || !strcmp(kind, "pointer_expression")) {
const CBMRegisteredType *base = c_proof_expression_owner(
ctx, ts_node_child_by_field_name(node, "argument", 8), depth + 1, proof, shape);
@@ -7558,6 +7759,8 @@ static bool c_field_is_macro_path(CLSPContext *ctx, TSNode argument, TSNode fiel
const CBMCDecl *decl = defs->items[d];
if (defs->from[d] >= at && strcmp(decl->file, own))
continue;
if (c_macro_definition_unproven(ctx, decl, at))
return true;
const char *written = decl->macro ? decl->macro->field_path : NULL;
if (!written || (path && strcmp(path, written)))
return false;
@@ -8146,41 +8349,89 @@ static int c_macro_parameter(const CBMCMacroShape *shape, int position) {
return shape->variadic ? shape->param_count - 1 : -1;
}
/* A macro may hand an argument on several times, to macros that do the same, so following every
* hand-off doubles with each hop. A walk reads each (macro, position) once. Meeting one again adds
* nothing: its answer is already part of this one, and a macro is not expanded again inside its
* own expansion. */
typedef struct {
const char *macro;
int position;
} CMacroArgument;
static const char *c_macro_argument_key(CLSPContext *ctx, const char *macro, int position) {
return cbm_arena_sprintf(ctx->arena, "%d#%s", position, macro);
}
/* Does this written name, as argument `position` of the macro, reach the expansion as itself in
* every definition the unit sees? The name then means what it means at the use, because
* substitution puts it there. A definition that pastes it, reads it as a member, declares it or
* declares a local of that name says otherwise, and so does one this does not understand. */
* declares a local of that name says otherwise, and so does one this does not understand. One
* definition's answer does not depend on another's, so the order of the walk cannot change this. */
static bool c_macro_argument_is_plain(CLSPContext *ctx, const char *macro, int position,
const char *written, uint32_t at, int depth) {
if (depth > 16)
return false;
bool seen = false;
for (const CBMCDeclLink *link = cbm_c_decl_lookup(ctx->c_decls, macro); link;
link = link->next) {
const CBMCDecl *decl = link->decl;
if (decl->kind != CBM_C_DECL_MACRO || strcmp(decl->name, macro) ||
!c_macro_definition_visible(ctx, decl, at))
continue;
seen = true;
const CBMCMacroShape *shape = decl->macro;
int param = shape ? c_macro_parameter(shape, position) : -1;
if (param < 0 || !(shape->plain & (1u << param)))
return false;
for (int i = 0; i < shape->local_count; i++)
if (!strcmp(shape->locals[i], written))
return false;
for (int i = 0; i < shape->handoff_count; i++) {
const CBMCMacroHandoff *handoff = &shape->handoffs[i];
if (handoff->param != param || !c_proof_macro_visible(ctx, handoff->callee, at))
const char *written, uint32_t at) {
CMacroArgument top = {macro, position};
CMacroArgument *stack = &top;
int count = 1, cap = 1;
CBMHashTable *seen = NULL;
bool plain = true;
while (plain && count) {
CMacroArgument use = stack[--count];
bool defined = false;
for (const CBMCDeclLink *link = cbm_c_decl_lookup(ctx->c_decls, use.macro); link && plain;
link = link->next) {
const CBMCDecl *decl = link->decl;
if (decl->kind != CBM_C_DECL_MACRO || strcmp(decl->name, use.macro) ||
!c_macro_definition_visible(ctx, decl, at))
continue;
/* `__VA_ARGS__` handed on stands for several arguments at several positions. */
if ((shape->variadic && param == shape->param_count - 1) ||
!c_macro_argument_is_plain(ctx, handoff->callee, handoff->position, written, at,
depth + 1))
return false;
CBM_LSP_WORK(1);
defined = true;
const CBMCMacroShape *shape = decl->macro;
int param = shape ? c_macro_parameter(shape, use.position) : -1;
if (param < 0 || !(shape->plain & (1u << param))) {
plain = false;
break;
}
for (int i = 0; plain && i < shape->local_count; i++)
plain = strcmp(shape->locals[i], written) != 0;
for (int i = 0; plain && i < shape->handoff_count; i++) {
const CBMCMacroHandoff *handoff = &shape->handoffs[i];
if (handoff->param != param || !c_proof_macro_visible(ctx, handoff->callee, at))
continue;
/* `__VA_ARGS__` handed on stands for several arguments at several positions. */
if (shape->variadic && param == shape->param_count - 1) {
plain = false;
break;
}
const char *key = c_macro_argument_key(ctx, handoff->callee, handoff->position);
if (!seen)
seen = cbm_ht_create(8);
if (!key || !seen) {
plain = false;
break;
}
if (cbm_ht_get(seen, key))
continue;
cbm_ht_set(seen, key, (void *)key);
if (count == cap) {
int grown = cap * 2 + 6;
CMacroArgument *next =
cbm_arena_alloc(ctx->arena, (size_t)grown * sizeof(*next));
if (!next) {
plain = false;
break;
}
memcpy(next, stack, (size_t)count * sizeof(*next));
stack = next;
cap = grown;
}
stack[count++] = (CMacroArgument){handoff->callee, handoff->position};
}
}
plain = plain && defined;
}
return seen;
if (seen)
cbm_ht_free(seen);
return plain;
}
static void c_macro_args_load(CLSPContext *ctx, TSNode list) {
@@ -8211,7 +8462,7 @@ static void c_macro_args_load(CLSPContext *ctx, TSNode list) {
continue;
if (!strcmp(ts_node_type(argument), "identifier")) {
const char *written = c_node_text(ctx, argument);
if (written && c_macro_argument_is_plain(ctx, name, position, written, at, 0))
if (written && c_macro_argument_is_plain(ctx, name, position, written, at))
plain[count++] = ts_node_start_byte(argument);
}
position++;
@@ -8302,9 +8553,47 @@ static bool c_bare_argument_of_macro(CLSPContext *ctx, TSNode node) {
* variable in every definition the unit sees, directly or by handing it to a macro that does:
* `#define FOR_ALL_TABS(tp) for (tabpage_T *(tp) = first_tabpage; ...)`. */
static const char *c_macro_declared_type(CLSPContext *ctx, const char *macro, int position,
uint32_t at, int depth, int *pointers) {
if (depth > 16)
uint32_t at, int depth, int *pointers,
CBMHashTable **answers);
typedef struct {
const char *type;
int pointers;
} CMacroDeclared;
/* What the macro a hand-off reaches declares, read once per walk. Reached again while being read,
* in a cycle, the answer is still none: a macro is not expanded inside its own expansion. The depth
* only keeps a hostile chain off the stack: going to it from 16 changed no edge in redis, neovim,
* or the LLVM and kernel subtrees measured. */
#define C_MACRO_HANDOFF_MAX_DEPTH 64
static const char *c_macro_handed_type(CLSPContext *ctx, const CBMCMacroHandoff *handoff,
uint32_t at, int depth, int *pointers,
CBMHashTable **answers) {
if (depth > C_MACRO_HANDOFF_MAX_DEPTH)
return NULL;
const char *key = c_macro_argument_key(ctx, handoff->callee, handoff->position);
if (!*answers)
*answers = cbm_ht_create(8);
CMacroDeclared *answer = key && *answers ? cbm_ht_get(*answers, key) : NULL;
if (answer) {
*pointers = answer->pointers;
return answer->type;
}
answer = key && *answers ? cbm_arena_alloc(ctx->arena, sizeof(*answer)) : NULL;
if (!answer)
return NULL;
*answer = (CMacroDeclared){0};
cbm_ht_set(*answers, key, answer);
const char *type = c_macro_declared_type(ctx, handoff->callee, handoff->position, at, depth,
pointers, answers);
answer->type = type;
answer->pointers = *pointers;
return type;
}
static const char *c_macro_declared_type(CLSPContext *ctx, const char *macro, int position,
uint32_t at, int depth, int *pointers,
CBMHashTable **answers) {
const char *found = NULL;
for (const CBMCDeclLink *link = cbm_c_decl_lookup(ctx->c_decls, macro); link;
link = link->next) {
@@ -8312,6 +8601,9 @@ static const char *c_macro_declared_type(CLSPContext *ctx, const char *macro, in
if (decl->kind != CBM_C_DECL_MACRO || strcmp(decl->name, macro) ||
!c_macro_definition_visible(ctx, decl, at))
continue;
CBM_LSP_WORK(1);
if (c_macro_definition_unproven(ctx, decl, at))
return NULL;
const CBMCMacroShape *shape = decl->macro;
int param = shape ? c_macro_parameter(shape, position) : -1;
if (param < 0)
@@ -8325,8 +8617,7 @@ static const char *c_macro_declared_type(CLSPContext *ctx, const char *macro, in
for (int i = 0; i < shape->handoff_count && !type; i++) {
const CBMCMacroHandoff *handoff = &shape->handoffs[i];
if (handoff->param == param && c_proof_macro_visible(ctx, handoff->callee, at))
type = c_macro_declared_type(ctx, handoff->callee, handoff->position, at,
depth + 1, &depth_found);
type = c_macro_handed_type(ctx, handoff, at, depth + 1, &depth_found, answers);
}
}
if (!type || (found && (strcmp(found, type) || *pointers != depth_found)))
@@ -8343,9 +8634,13 @@ static void c_bind_macro_declared(CLSPContext *ctx, const char *macro, int posit
uint32_t at = ts_node_start_byte(name);
const char *written = c_node_text(ctx, name);
int pointers = 0;
const char *type_name = written && ctx->c_decls && c_proof_macro_visible(ctx, macro, at)
? c_macro_declared_type(ctx, macro, position, at, 0, &pointers)
: NULL;
CBMHashTable *answers = NULL;
const char *type_name =
written && ctx->c_decls && c_proof_macro_visible(ctx, macro, at)
? c_macro_declared_type(ctx, macro, position, at, 0, &pointers, &answers)
: NULL;
if (answers)
cbm_ht_free(answers);
if (!type_name)
return;
const CBMRegisteredType *owner =
@@ -8518,7 +8813,7 @@ static bool c_macro_word_only_handed_on(CLSPContext *ctx, const CBMCMacroShape *
if (strcmp(argument->name, name))
continue;
if (c_proof_macro_visible(ctx, argument->callee, at)) {
if (!c_macro_argument_is_plain(ctx, argument->callee, argument->position, name, at, 0))
if (!c_macro_argument_is_plain(ctx, argument->callee, argument->position, name, at))
return true;
continue;
}
@@ -8599,8 +8894,8 @@ static void c_macro_body_expand(CLSPContext *ctx, CMacroWalk *walk, const char *
in_force[d] = defs->from[d] < at || !strcmp(decl->file, own);
if (!in_force[d])
continue;
/* A definition this cannot read may say anything. */
if (!decl->macro)
/* A definition this cannot read may say anything, and so may a header it cannot read. */
if (!decl->macro || c_macro_definition_unproven(ctx, decl, at))
return;
first = first ? first : decl->macro;
visible++;
@@ -8626,9 +8921,6 @@ static void c_macro_body_expand(CLSPContext *ctx, CMacroWalk *walk, const char *
continue;
if (c_proof_macro_visible(ctx, name, at)) {
c_macro_walk_push(ctx, walk, name);
/* Lua: `#define error(S,s)` in one branch and `static void error(...)` in the other. */
if (c_macro_only_beside_function(ctx, name))
c_macro_body_name(ctx, walk, name, true);
} else {
c_macro_body_name(ctx, walk, name, true);
}
@@ -8748,10 +9040,25 @@ static bool c_is_sizeof_operand(TSNode node) {
}
static void c_emit_value_reference(CLSPContext *ctx, TSNode node) {
if (!ctx->c_decls || !ctx->enclosing_func_qn || c_bare_argument_of_macro(ctx, node))
if (!ctx->c_decls || !ctx->enclosing_func_qn)
return;
TSNode parent = ts_node_parent(node);
const char *parent_kind = ts_node_is_null(parent) ? "" : ts_node_type(parent);
/* `container_of(p, struct X, m)`: no grammar has a type among call arguments, and the keyword
* lands in an error just before the name. That name is a tag and no value. */
if (!strcmp(parent_kind, "argument_list") && ts_node_has_error(parent)) {
TSNode before = ts_node_prev_sibling(node);
const char *keyword = !ts_node_is_null(before) && !strcmp(ts_node_type(before), "ERROR")
? c_node_text(ctx, before)
: NULL;
if (keyword &&
(!strcmp(keyword, "struct") || !strcmp(keyword, "union") || !strcmp(keyword, "enum"))) {
c_emit_type_reference(ctx, node);
return;
}
}
if (c_bare_argument_of_macro(ctx, node))
return;
if (!strcmp(parent_kind, "qualified_identifier")) {
c_emit_scoped_enumerator(ctx, parent, node);
return;
@@ -8845,18 +9152,31 @@ static void c_emit_value_reference(CLSPContext *ctx, TSNode node) {
}
/* A function can declare its own `enum { LIMIT = 8 }` or `struct node { ... }`. Those
* names are in block scope and hide any file-scope namesake from here on. */
* names are in block scope and hide any file-scope namesake from here on. A lone `struct node;`
* does the same: it declares a new tag here, where `struct node *p;` would use the outer one. */
static void c_bind_local_tag_and_enumerators(CLSPContext *ctx, TSNode node, const char *kind) {
if (strcmp(kind, "struct_specifier") && strcmp(kind, "union_specifier") &&
strcmp(kind, "enum_specifier"))
strcmp(kind, "enum_specifier") && strcmp(kind, "class_specifier"))
return;
TSNode body = ts_node_child_by_field_name(node, "body", 4);
if (ts_node_is_null(body))
return;
if (ts_node_is_null(body)) {
TSNode parent = ts_node_parent(node);
const char *around = ts_node_is_null(parent) ? "" : ts_node_type(parent);
if (strcmp(around, "compound_statement") &&
(strcmp(around, "declaration") ||
!ts_node_is_null(ts_node_child_by_field_name(parent, "declarator", 10))))
return;
}
TSNode tag = ts_node_child_by_field_name(node, "name", 4);
const char *tag_name = ts_node_is_null(tag) ? NULL : c_node_text(ctx, tag);
if (tag_name)
if (tag_name) {
c_scope_bind(ctx, tag_name, cbm_type_unknown());
const char *tag_key = c_local_tag_key(ctx, tag_name);
if (tag_key) {
c_scope_bind(ctx, tag_key, cbm_type_unknown());
ctx->local_tag_declared = true;
}
}
if (strcmp(kind, "enum_specifier"))
return;
uint32_t count = ts_node_named_child_count(body);
@@ -8946,13 +9266,18 @@ static void c_emit_type_reference(CLSPContext *ctx, TSNode node) {
return;
}
/* `struct node {...}` defines a tag here rather than using one. */
if ((!strcmp(parent_kind, "struct_specifier") || !strcmp(parent_kind, "union_specifier") ||
!strcmp(parent_kind, "enum_specifier")) &&
!ts_node_is_null(ts_node_child_by_field_name(parent, "body", 4)))
bool in_specifier =
!strcmp(parent_kind, "struct_specifier") || !strcmp(parent_kind, "union_specifier") ||
!strcmp(parent_kind, "class_specifier") || !strcmp(parent_kind, "enum_specifier");
if (in_specifier && !ts_node_is_null(ts_node_child_by_field_name(parent, "body", 4)))
return;
/* `struct X` and `container_of(p, struct X, m)` ask among tags, which a variable named X does
* not hide. */
bool is_tag = in_specifier || !strcmp(parent_kind, "argument_list");
const char *name = c_node_text(ctx, node);
if (!name || !ctx->module_qn ||
(!qualified && (c_is_template_parameter(ctx, name) || c_name_bound_locally(ctx, name))))
(!qualified && (c_is_template_parameter(ctx, name) ||
(is_tag ? c_local_tag_hides(ctx, name) : c_name_bound_locally(ctx, name)))))
return;
/* `#define sds hisds`, `#define RedisModuleString robj`: the type is what the macro names. */
if (!qualified && c_proof_macro_visible(ctx, name, ts_node_start_byte(node))) {
@@ -8961,6 +9286,9 @@ static void c_emit_type_reference(CLSPContext *ctx, TSNode node) {
}
uint32_t at = ts_node_start_byte(node);
const CBMRegisteredType *type = c_proof_type_by_name(ctx, qualified ? qualified : name, at);
/* A tag, which may not be the type found for that spelling. */
if (type && is_tag && c_tag_is_other_than(ctx, type, name, qualified != NULL, at))
return;
/* A member type of the class around this code hides a namesake outside the class. */
if (type && !qualified && ctx->cpp_mode && c_enclosing_class_declares_other(ctx, name, type))
return;
@@ -10600,7 +10928,8 @@ static void c_process_body_child(CLSPContext *ctx, TSNode child) {
c_process_function(ctx, child);
} else if (strcmp(ck, "namespace_definition") == 0) {
c_process_namespace(ctx, child);
} else if (strcmp(ck, "class_specifier") == 0 || strcmp(ck, "struct_specifier") == 0) {
} else if (strcmp(ck, "class_specifier") == 0 || strcmp(ck, "struct_specifier") == 0 ||
strcmp(ck, "union_specifier") == 0) {
c_process_class(ctx, child);
} else if (strcmp(ck, "declaration") == 0) {
// declaration may contain class/struct specifier: class Foo { ... };
@@ -10610,7 +10939,8 @@ static void c_process_body_child(CLSPContext *ctx, TSNode child) {
for (uint32_t di = 0; di < dnc; di++) {
TSNode dch = ts_node_named_child(child, di);
const char *dk = ts_node_type(dch);
if (strcmp(dk, "class_specifier") == 0 || strcmp(dk, "struct_specifier") == 0) {
if (strcmp(dk, "class_specifier") == 0 || strcmp(dk, "struct_specifier") == 0 ||
strcmp(dk, "union_specifier") == 0) {
c_process_class(ctx, dch);
has_class = true;
}
+4
View File
@@ -24,11 +24,15 @@ typedef struct {
struct CProofDeclaration *proof_declarations;
int proof_declaration_count, proof_declaration_cap;
struct CProofInclude *proof_includes;
/* Where this file includes a quoted header the index holds nothing for, in file order. */
uint32_t *proof_unknown_includes;
int proof_unknown_include_count, proof_unknown_include_cap;
/* The translation unit: every file this one includes, through other files
* too, with the point in this file where it became visible. */
struct CTUModule *tu_modules;
uint32_t tu_cap, tu_count;
bool tu_adding_includer_context;
bool local_tag_declared; /* a block of this file declared a tag of its own */
const struct CBMCDeclIndex *c_decls;
const char *own_file_qn; /* NULL when only the module identifies this file */
/* C++: the class whose member function this is, inline or out of line. Its members
+183 -13
View File
@@ -698,8 +698,152 @@ static bool csp_append_member(CBMArena *arena, CBMCMacroMember **items, int *cou
return true;
}
/* The end of the `(` at `open`, or 0 when it does not close. Comments and literals have been left
* to the caller: a replacement with a string in its type part reads as no type at all. */
static uint32_t csp_macro_close(const char *text, uint32_t length, uint32_t open) {
int depth = 0;
for (uint32_t at = open; at < length; at++) {
if (text[at] == '"' || text[at] == '\'') {
char quote = text[at];
for (at++; at < length && text[at] != quote; at++)
if (text[at] == '\\')
at++;
} else if (text[at] == '(') {
depth++;
} else if (text[at] == ')' && --depth == 0) {
return at + 1;
}
}
return 0;
}
static uint32_t csp_macro_skip_space(const char *text, uint32_t length, uint32_t at) {
while (at < length) {
if (text[at] == ' ' || text[at] == '\t' || text[at] == '\r' || text[at] == '\n' ||
(text[at] == '\\' && at + 1 < length && text[at + 1] == '\n')) {
at++;
} else if (text[at] == '/' && at + 1 < length && text[at + 1] == '*') {
for (at += 2; at + 1 < length && !(text[at] == '*' && text[at + 1] == '/'); at++)
;
at = at + 2 <= length ? at + 2 : length;
} else if (text[at] == '/' && at + 1 < length && text[at + 1] == '/') {
return length;
} else {
break;
}
}
return at;
}
/* `((T *)operand)`. The parentheses around it have to close at its end: bare `(T *)p` used as
* `X->y` would cast p->y instead. The operand may be followed by nothing that takes the value out
* of the pointer type, which rules out comparisons, `?:` and `,`, and by no `+` or `-`, which would
* need the other operand read. A value the replacement's own parameter names as the type is not
* written. */
static void csp_macro_cast(csp_context_t *ctx, CBMCMacroShape *shape, const char *text,
uint32_t length, const csp_macro_params_t *params) {
uint32_t begin = csp_macro_skip_space(text, length, 0), end = length;
/* The end, past trailing space and comments. */
for (uint32_t at = begin; at < length;) {
uint32_t next = csp_macro_skip_space(text, length, at);
if (next >= length)
break;
at = next + 1;
end = at;
}
bool wrapped = false;
while (begin < end && text[begin] == '(' && csp_macro_close(text, end, begin) == end) {
wrapped = true;
begin = csp_macro_skip_space(text, end, begin + 1);
end--;
while (end > begin &&
(text[end - 1] == ' ' || text[end - 1] == '\t' || text[end - 1] == '\r' ||
text[end - 1] == '\n' || text[end - 1] == '\\'))
end--;
}
if (!wrapped || begin >= end || text[begin] != '(')
return;
uint32_t close = csp_macro_close(text, end, begin);
if (!close || close >= end)
return;
/* The type part: qualifiers and keywords, one name, `*`s. */
const char *word = NULL;
size_t word_length = 0;
uint32_t pointers = 0;
int paste_param = -1;
for (uint32_t at = csp_macro_skip_space(text, close - 1, begin + 1); at < close - 1;
at = csp_macro_skip_space(text, close - 1, at)) {
if (text[at] == '*') {
pointers++;
at++;
continue;
}
if (!isalpha((unsigned char)text[at]) && text[at] != '_')
return;
uint32_t start = at;
while (at < close - 1 && (isalnum((unsigned char)text[at]) || text[at] == '_'))
at++;
size_t size = at - start;
static const char *const skipped[] = {"const", "volatile", "struct", "union", "enum"};
bool keyword = false;
for (size_t k = 0; k < sizeof(skipped) / sizeof(skipped[0]); k++)
keyword |= strlen(skipped[k]) == size && !memcmp(text + start, skipped[k], size);
if (keyword)
continue;
if (word || pointers)
return;
word = text + start;
word_length = size;
if (at + 1 < close - 1 && text[at] == '#' && text[at + 1] == '#') {
at += 2;
uint32_t param_start = at;
while (at < close - 1 && (isalnum((unsigned char)text[at]) || text[at] == '_'))
at++;
for (int i = 0; i < params->count; i++)
if (strlen(params->names[i]) == at - param_start &&
!memcmp(params->names[i], text + param_start, at - param_start))
paste_param = i;
if (paste_param < 0)
return;
}
}
if (!word || pointers > 8)
return;
if (paste_param < 0)
for (int i = 0; i < params->count; i++)
if (strlen(params->names[i]) == word_length &&
!memcmp(params->names[i], word, word_length))
return;
/* The operand. */
uint32_t operand = csp_macro_skip_space(text, end, close);
if (operand >= end || text[operand] == '.' || text[operand] == '[' ||
(text[operand] == '-' && operand + 1 < end && text[operand + 1] == '>'))
return;
int depth = 0;
for (uint32_t at = operand; at < end; at++) {
char ch = text[at];
if (ch == '"' || ch == '\'') {
for (at++; at < end && text[at] != ch; at++)
if (text[at] == '\\')
at++;
} else if (ch == '(' || ch == '[') {
depth++;
} else if (ch == ')' || ch == ']') {
depth--;
} else if (depth == 0 && at > operand && strchr("+*/%&|^<>=!?:,", ch)) {
return;
} else if (depth == 0 && ch == '-' && !(at + 1 < end && text[at + 1] == '>')) {
return;
}
}
shape->cast_type = cbm_arena_strndup(ctx->arena, word, word_length);
shape->cast_pointers = (uint8_t)pointers;
shape->cast_pastes = paste_param >= 0;
shape->cast_param = paste_param >= 0 ? (uint8_t)paste_param : 0;
}
static const CBMCMacroShape *csp_macro_shape_keep(csp_context_t *ctx, const CBMCMacroShape *shape) {
if (shape->object_like && !shape->call_count && !shape->value_count)
if (shape->object_like && !shape->call_count && !shape->value_count && !shape->cast_type)
return NULL;
CBMCMacroShape *kept = cbm_arena_alloc(ctx->arena, sizeof(*kept));
if (!kept) {
@@ -733,6 +877,7 @@ static const CBMCMacroShape *csp_macro_shape(csp_context_t *ctx, TSNode node) {
return csp_macro_shape_keep(ctx, shape);
const char *text = ctx->source + begin;
uint32_t length = end - begin;
csp_macro_cast(ctx, shape, text, length, &params);
if (shape->object_like) {
/* Up to a comment or the end of the line: names joined by single dots, nothing else. */
uint32_t last = 0;
@@ -822,9 +967,8 @@ static const CBMCMacroShape *csp_macro_shape(csp_context_t *ctx, TSNode node) {
bool after_star = csp_token_is(&back1, "*") || parenthesized;
const csp_token_t *before_star = parenthesized ? &back3 : &back2;
csp_token_t following = next;
uint32_t peek = at;
if (parenthesized && csp_token_is(&following, ")"))
(void)csp_macro_token(text, length, peek, &following);
(void)csp_macro_token(text, length, at, &following);
bool star_declares = after_star && before_star->identifier &&
!csp_token_precedes_expression(before_star) &&
csp_token_ends_declarator(&following);
@@ -919,9 +1063,10 @@ static const CBMCMacroShape *csp_macro_shape(csp_context_t *ctx, TSNode node) {
* and stays unbound for it, which costs a value and proves nothing false. */
(after_star && before_star->identifier && csp_token_is(&next, ")") &&
!csp_token_precedes_expression(before_star))) {
const char *local = cbm_arena_strndup(ctx->arena, current.text, current.length);
if (!local ||
!csp_append_file(ctx->arena, &locals, &local_count, &local_cap, local)) {
const char *declared_name =
cbm_arena_strndup(ctx->arena, current.text, current.length);
if (!declared_name || !csp_append_file(ctx->arena, &locals, &local_count,
&local_cap, declared_name)) {
ctx->failed = true;
return NULL;
}
@@ -1041,6 +1186,24 @@ static void csp_note_macro(csp_context_t *ctx, TSNode node) {
}
}
static void csp_note_quoted_include(csp_context_t *ctx, TSNode node) {
TSNode path = csp_field(node, "path");
if (!ctx->facts || !csp_is(path, "string_literal"))
return;
const char *text = csp_text(ctx, path);
size_t length = text ? strlen(text) : 0;
const char *spelled = length >= 3 ? cbm_arena_strndup(ctx->arena, text + 1, length - 2) : NULL;
if (spelled && !csp_append_file(ctx->arena, &ctx->facts->quoted_includes,
&ctx->facts->quoted_include_count,
&ctx->facts->quoted_include_cap, spelled))
ctx->failed = true;
}
static bool csp_text_starts_with(const char *at, const char *end, const char *word) {
size_t length = strlen(word);
return (size_t)(end - at) >= length && !memcmp(at, word, length);
}
/* A `#define` inside a function body stays defined to the end of the file: malloc_vsnprintf's
* APPEND_C, do_sub's ADJUST_SUB_FIRSTLNUM. Most bodies hold none, and the text says so. */
bool cbm_c_text_defines_macro(const char *text, uint32_t length) {
@@ -1049,7 +1212,7 @@ bool cbm_c_text_defines_macro(const char *text, uint32_t length) {
const char *word = at + 1;
while (word < end && (*word == ' ' || *word == '\t'))
word++;
if (end - word >= 6 && !memcmp(word, "define", 6))
if (csp_text_starts_with(word, end, "define"))
return true;
at = word;
}
@@ -1091,8 +1254,6 @@ static void csp_free_function(csp_context_t *ctx, TSNode node, const char *owner
const char *qn = name ? cbm_arena_sprintf(ctx->arena, "%s.%s", owner, name) : NULL;
if (csp_is(node, "declaration") && name)
csp_note_prototype(ctx, node, decl, owner, name);
else if (name && ctx->branch_group)
(void)csp_add_decl(ctx, CBM_C_DECL_DEFINED_IN_BRANCH, csp_relative(ctx, owner, name), 0);
csp_entry_t *entry = csp_entry(ctx, qn, ts_node_start_point(node).row + 1);
/* A macro over a builtin type word (`#define int double`) falsifies what the
* signature below would say, so the definition's linkage facts stay unknown. */
@@ -1344,7 +1505,7 @@ static void csp_class(csp_context_t *ctx, TSNode node, const char *parent,
return;
bool complete = true, body_complete = true;
uint32_t access =
csp_is(node, "struct_specifier") ? CBM_SOURCE_DECL_PUBLIC : CBM_SOURCE_DECL_PRIVATE;
csp_is(node, "class_specifier") ? CBM_SOURCE_DECL_PRIVATE : CBM_SOURCE_DECL_PUBLIC;
TSTreeCursor header = ts_tree_cursor_new(node);
if (ts_tree_cursor_goto_first_child(&header))
do {
@@ -1399,10 +1560,10 @@ static void csp_class(csp_context_t *ctx, TSNode node, const char *parent,
* an ERROR. Definition extraction recovers that function from the expanded source, so it has an
* entry here to find: the signature standing in the ERROR says how it is linked. */
static void csp_note_stranded_definitions(csp_context_t *ctx, TSNode error, const char *owner) {
bool is_static = false;
uint32_t first_line = 0;
TSTreeCursor cursor = ts_tree_cursor_new(error);
if (ts_tree_cursor_goto_first_child(&cursor))
if (ts_tree_cursor_goto_first_child(&cursor)) {
bool is_static = false;
uint32_t first_line = 0;
do {
TSNode child = ts_tree_cursor_current_node(&cursor);
if (!ts_node_is_named(child) || csp_is(child, "comment"))
@@ -1410,6 +1571,7 @@ static void csp_note_stranded_definitions(csp_context_t *ctx, TSNode error, cons
if (csp_is(child, "storage_class_specifier")) {
const char *word = csp_text(ctx, child);
is_static |= word && !strcmp(word, "static");
continue;
} else if (csp_is(child, "primitive_type") || csp_is(child, "type_identifier") ||
csp_is(child, "sized_type_specifier") || csp_is(child, "type_qualifier") ||
csp_is(child, "struct_specifier")) {
@@ -1442,6 +1604,7 @@ static void csp_note_stranded_definitions(csp_context_t *ctx, TSNode error, cons
is_static = false;
first_line = 0;
} while (ts_tree_cursor_goto_next_sibling(&cursor));
}
ts_tree_cursor_delete(&cursor);
}
@@ -1528,6 +1691,10 @@ static void csp_walk(csp_context_t *ctx, TSNode node, const char *owner, unsigne
csp_note_macro(ctx, node);
return;
}
if (csp_is(node, "preproc_include")) {
csp_note_quoted_include(ctx, node);
return;
}
if (csp_preprocessor_branch(node)) {
bool saved = ctx->conditional;
uint32_t saved_group = ctx->branch_group, saved_branch = ctx->branch;
@@ -1559,6 +1726,9 @@ static void csp_walk(csp_context_t *ctx, TSNode node, const char *owner, unsigne
if (csp_is(node, "type_definition")) {
TSNode type = csp_field(node, "type"), declarator = csp_field(node, "declarator");
csp_note_enumerators(ctx, type, owner);
if (csp_is(declarator, "type_identifier") && !ts_node_is_null(csp_field(type, "body")) &&
ts_node_is_null(csp_field(type, "name")))
(void)csp_add_decl(ctx, CBM_C_DECL_UNNAMED_TYPEDEF, csp_text(ctx, declarator), 0);
/* An error among the members does not change which struct this is. */
if (csp_is(type, "class_specifier") || csp_is(type, "struct_specifier") ||
csp_is(type, "union_specifier"))
+10 -1
View File
@@ -23,7 +23,7 @@ typedef enum {
CBM_C_DECL_CLASS_TEMPLATE, /* `template <class T> class Name`: a base of this name can depend */
CBM_C_DECL_TEMPLATE_PARAMETER, /* `template <class Value> class Pair`: name is Value, type_name
* is Pair. What it means comes with each instantiation. */
CBM_C_DECL_DEFINED_IN_BRANCH, /* a function defined inside a conditional */
CBM_C_DECL_UNNAMED_TYPEDEF, /* `typedef struct {...} Name;`: no tag is called Name */
} CBMCDeclKind;
enum {
@@ -95,6 +95,12 @@ typedef struct CBMCMacroShape {
/* `#define w_p_list w_onebuf_opt.wo_list`: an object-like replacement that is nothing but
* dotted names, which can stand after `->` and name members there. */
const char *field_path;
/* `#define hl_table ((HlGroup *)(ga.ga_data))`, `#define SDS_HDR(T, s) ((struct sdshdr##T
* *)...)`: a replacement that is one cast as a whole writes the type of what it gives. */
const char *cast_type;
uint8_t cast_pointers;
bool cast_pastes; /* the type's name is cast_type, then the argument cast_param */
uint8_t cast_param;
} CBMCMacroShape;
typedef struct CBMCDecl {
@@ -135,6 +141,9 @@ typedef struct CBMCFileFacts {
int include_count, include_cap;
const char **generated_for; /* source files declared through a generated header */
int generated_count, generated_cap;
const char **quoted_includes; /* every `#include "x"`, as spelled */
int quoted_include_count, quoted_include_cap;
bool has_unknown_include; /* one of them has nothing in the index behind it */
} CBMCFileFacts;
/* As cbm_c_collect_source_facts, also filling facts when it is not NULL. */
+4 -1
View File
@@ -299,7 +299,10 @@ bool cbm_resolve_capabilities(CBMLanguage language, cbm_resolve_capabilities_t *
out->limitation = "Exact visibility and owner proofs; overloads of one parameter count "
"with separate nodes, a template's dependent names, specialized class "
"templates, ADL with a second visible function, callable-field "
"selection and what only some definitions of a macro name abstain.";
"selection, what only some definitions of a macro name, a name that is "
"a macro in one preprocessor branch and a function in another, and a "
"macro that an included header the index cannot read may redefine "
"abstain.";
break;
case CBM_LANG_JAVASCRIPT:
case CBM_LANG_TYPESCRIPT:
+11
View File
@@ -1205,6 +1205,17 @@ static void pxc_collect_c_file(const cbm_pipeline_ctx_t *ctx, CBMArena *arena, c
(void)cbm_c_facts_add_generated(arena, facts, cbm_arena_strdup(arena, generated));
free(generated);
}
/* A quoted include nothing proves is text this index cannot read. */
for (int q = 0; q < facts->quoted_include_count && !facts->has_unknown_include; q++) {
bool known = false;
for (size_t i = 0; snapshot && i < snapshot->count && !known; i++) {
const cbm_resolve_import_fact_t *fact = &snapshot->items[i];
known = fact->module_path && !strcmp(fact->module_path, facts->quoted_includes[q]) &&
cbm_resolve_admit(&fact->dependency) && cbm_resolve_admit(&fact->binding) &&
fact->dependency.target;
}
facts->has_unknown_include = !known;
}
CBMLSPDef *record = &defs[count];
memset(record, 0, sizeof(*record));
record->qualified_name = cbm_arena_sprintf(arena, "%s.@c-file-facts", module);
+70 -18
View File
@@ -16954,7 +16954,7 @@ static bool c_member_fields_quality(cbm_store_t *store, const char *project, con
.label = "Field",
.declaration_line = 5,
.declaration_text = " void (*open)(int fd);"},
.mode = RQ_KNOWN_GAP,
.mode = RQ_REQUIRED,
.gap_id = "C-FIELD-DESIGNATOR-OWNER",
.why = c_field_designator_why,
.tried = c_field_designator_tried},
@@ -16967,7 +16967,7 @@ static bool c_member_fields_quality(cbm_store_t *store, const char *project, con
.label = "Field",
.declaration_line = 6,
.declaration_text = " int (*shut)(int fd);"},
.mode = RQ_KNOWN_GAP,
.mode = RQ_REQUIRED,
.gap_id = "C-FIELD-DESIGNATOR-OWNER",
.why = c_field_designator_why,
.tried = c_field_designator_tried},
@@ -16981,7 +16981,7 @@ static bool c_member_fields_quality(cbm_store_t *store, const char *project, con
.label = "Field",
.declaration_line = 4,
.declaration_text = " void (*open)(int fd);"},
.mode = RQ_KNOWN_GAP,
.mode = RQ_REQUIRED,
.gap_id = "C-FIELD-DESIGNATOR-OWNER",
.why = c_field_designator_why,
.tried = c_field_designator_tried},
@@ -16994,7 +16994,7 @@ static bool c_member_fields_quality(cbm_store_t *store, const char *project, con
.label = "Field",
.declaration_line = 15,
.declaration_text = " struct Dev fixed[2];"},
.mode = RQ_KNOWN_GAP,
.mode = RQ_REQUIRED,
.gap_id = "C-FIELD-DESIGNATOR-OWNER",
.why = c_field_designator_why,
.tried = c_field_designator_tried},
@@ -17007,7 +17007,7 @@ static bool c_member_fields_quality(cbm_store_t *store, const char *project, con
.label = "Field",
.declaration_line = 10,
.declaration_text = " struct Ops *ops;"},
.mode = RQ_KNOWN_GAP,
.mode = RQ_REQUIRED,
.gap_id = "C-FIELD-DESIGNATOR-OWNER",
.why = c_field_designator_why,
.tried = c_field_designator_tried},
@@ -17020,7 +17020,7 @@ static bool c_member_fields_quality(cbm_store_t *store, const char *project, con
.label = "Field",
.declaration_line = 16,
.declaration_text = " int id;"},
.mode = RQ_KNOWN_GAP,
.mode = RQ_REQUIRED,
.gap_id = "C-FIELD-DESIGNATOR-OWNER",
.why = c_field_designator_why,
.tried = c_field_designator_tried},
@@ -17036,7 +17036,7 @@ static bool c_member_fields_quality(cbm_store_t *store, const char *project, con
.label = "Field",
.declaration_line = 11,
.declaration_text = " int id;"},
.mode = RQ_KNOWN_GAP,
.mode = RQ_REQUIRED,
.gap_id = "C-FIELD-DESIGNATOR-OWNER",
.why = c_field_designator_why,
.tried = c_field_designator_tried},
@@ -17052,7 +17052,7 @@ static bool c_member_fields_quality(cbm_store_t *store, const char *project, con
.label = "Field",
.declaration_line = 11,
.declaration_text = " int id;"},
.mode = RQ_KNOWN_GAP,
.mode = RQ_REQUIRED,
.gap_id = "C-FIELD-DESIGNATOR-OWNER",
.why = c_field_designator_why,
.tried = c_field_designator_tried},
@@ -17100,7 +17100,7 @@ static bool c_member_fields_quality(cbm_store_t *store, const char *project, con
.label = "Field",
.declaration_line = 6,
.declaration_text = " int id;"},
.mode = RQ_KNOWN_GAP,
.mode = RQ_REQUIRED,
.gap_id = "C-FIELD-DESIGNATOR-OWNER",
.why = c_field_designator_why,
.tried = c_field_designator_tried},
@@ -17116,7 +17116,7 @@ static bool c_member_fields_quality(cbm_store_t *store, const char *project, con
.label = "Field",
.declaration_line = 6,
.declaration_text = " int id;"},
.mode = RQ_KNOWN_GAP,
.mode = RQ_REQUIRED,
.gap_id = "C-FIELD-DESIGNATOR-OWNER",
.why = c_field_designator_why,
.tried = c_field_designator_tried},
@@ -17129,7 +17129,7 @@ static bool c_member_fields_quality(cbm_store_t *store, const char *project, con
.label = "Function",
.declaration_line = 3,
.declaration_text = "static void my_open(int fd) { (void)fd; }"},
.mode = RQ_KNOWN_GAP,
.mode = RQ_REQUIRED,
.gap_id = "C-FIELD-INITIALIZER-VALUE",
.why = c_field_global_value_why,
.tried = c_field_global_value_tried},
@@ -17142,7 +17142,7 @@ static bool c_member_fields_quality(cbm_store_t *store, const char *project, con
.label = "Function",
.declaration_line = 4,
.declaration_text = "static int my_shut(int fd) { return fd; }"},
.mode = RQ_KNOWN_GAP,
.mode = RQ_REQUIRED,
.gap_id = "C-FIELD-INITIALIZER-VALUE",
.why = c_field_global_value_why,
.tried = c_field_global_value_tried},
@@ -17156,10 +17156,56 @@ static bool c_member_fields_quality(cbm_store_t *store, const char *project, con
.label = "Function",
.declaration_line = 3,
.declaration_text = "static void their_open(int fd) { (void)fd; }"},
.mode = RQ_KNOWN_GAP,
.mode = RQ_REQUIRED,
.gap_id = "C-FIELD-INITIALIZER-VALUE",
.why = c_field_global_value_why,
.tried = c_field_global_value_tried},
#define C_FIELD_TAG(id, file_, caller_, begin, end, target_file, target_suffix, line, written) \
{.case_id = id, \
.site = {.file = file_, .caller = caller_, .start_byte = begin, .end_byte = end}, \
.relation = "USAGE", \
.target = {.file = target_file, \
.suffix = target_suffix, \
.label = "Class", \
.declaration_line = line, \
.declaration_text = written}, \
.mode = RQ_REQUIRED}
/* `struct Dev` in a signature, a body or a compound literal names a tag this file sees. */
C_FIELD_TAG("c.field.tag-use-alpha", "use_alpha.c", "use_alpha.use_alpha", 41, 46, "alpha.h",
"alpha.Alpha", 4, "struct Alpha {"),
C_FIELD_TAG("c.field.tag-guarded-alpha", "guarded.c", "guarded.guarded_alpha", 82, 87, "alpha.h",
"alpha.Alpha", 4, "struct Alpha {"),
C_FIELD_TAG("c.field.tag-hdr-alpha", "inline.h", "inline.hdr_alpha", 106, 111, "alpha.h",
"alpha.Alpha", 4, "struct Alpha {"),
C_FIELD_TAG("c.field.tag-hdr-own-struct", "inline.h", "inline.hdr_own_struct", 484, 491, "inline.h",
"inline.HdrNode", 17, "struct HdrNode {"),
C_FIELD_TAG("c.field.tag-arrow-ops", "use_ops.c", "use_ops.arrow_ops", 63, 66, "ops.h",
"ops.Ops", 4, "struct Ops {"),
C_FIELD_TAG("c.field.tag-arrow-other", "use_ops.c", "use_ops.arrow_other", 116, 124, "other_ops.h",
"other_ops.OtherOps", 3, "struct OtherOps {"),
C_FIELD_TAG("c.field.tag-arrow-chain", "use_ops.c", "use_ops.arrow_chain", 173, 176, "ops.h",
"ops.Dev", 9, "struct Dev {"),
C_FIELD_TAG("c.field.tag-arrow-ret", "use_ops.c", "use_ops.arrow_ret", 235, 238, "ops.h",
"ops.Ops", 4, "struct Ops {"),
C_FIELD_TAG("c.field.tag-index-ptr", "use_ops.c", "use_ops.index_ptr", 297, 301, "ops.h",
"ops.Bank", 13, "struct Bank {"),
C_FIELD_TAG("c.field.tag-index-arr", "use_ops.c", "use_ops.index_arr", 358, 362, "ops.h",
"ops.Bank", 13, "struct Bank {"),
C_FIELD_TAG("c.field.tag-second-decl", "use_ops.c", "use_ops.second_decl", 422, 426, "ops.h",
"ops.Bank", 13, "struct Bank {"),
C_FIELD_TAG("c.field.tag-init-block", "init_local.c", "init_local.init_block", 75, 78, "ops.h",
"ops.Dev", 9, "struct Dev {"),
C_FIELD_TAG("c.field.tag-init-literal-param", "init_local.c", "init_local.init_literal", 141, 145, "ops.h",
"ops.Bank", 13, "struct Bank {"),
C_FIELD_TAG("c.field.tag-init-literal-cast", "init_local.c", "init_local.init_literal", 175, 178, "ops.h",
"ops.Dev", 9, "struct Dev {"),
C_FIELD_TAG("c.field.tag-init-array", "init_local.c", "init_local.init_array", 230, 238, "other_ops.h",
"other_ops.OtherOps", 3, "struct OtherOps {"),
C_FIELD_TAG("c.field.tag-exact-local", "field_control.c", "field_control.exact_local_field", 91, 101, "field_control.c",
"field_control.ExactLocal", 2, "struct ExactLocal { int count; };"),
C_FIELD_TAG("c.field.tag-exact-external", "field_control.c", "field_control.exact_external_field", 159, 172, "field_external.h",
"field_external.ExactExternal", 1, "struct ExactExternal { int count; };"),
#undef C_FIELD_TAG
{.case_id = "c.field.initializer-table-address",
/* Literal source: &table_ops */
.site = {.file = "table_ops.c", .caller = "table_ops", .start_byte = 245, .end_byte = 254},
@@ -27608,17 +27654,14 @@ TEST(pipeline_cpp_enum_reference_reaches_flat_enumerator_c1) {
.tried = "The original qualified/unqualified source and declaration membership checks "
"remain; the method/type bridge cannot authorize an enum value."},
{.case_id = "cpp.enum.round",
.site = {.file = "use.cpp",
.caller = "use.pick",
.needle = "gfx::Brush::ROUND",
.occurrence_index = 0},
.site = {.file = "use.cpp", .caller = "use.pick", .needle = "ROUND", .occurrence_index = 0},
.relation = "USAGE",
.target = {.file = "shapes.hpp",
.suffix = "shapes.gfx.Brush.ROUND",
.label = "Variable",
.declaration_line = 6,
.declaration_text = " enum Shape { ROUND, SQUARE };"},
.mode = RQ_KNOWN_GAP,
.mode = RQ_REQUIRED,
.gap_id = "C-ENUM-LEXICAL-VALUE",
.why = "The enumerator has an exact declaration but no admitted enum-value lexical "
"binding proof.",
@@ -27708,6 +27751,15 @@ TEST(pipeline_c_enum_inside_struct_reference_resolves_c1) {
const RQ_Source sources[] = {{.path = files[0], .text = contents[0]}};
const RQ_Expected expected[] = {
{.case_id = "c.enum.tag-of-the-parameter",
.site = {.file = "link.c", .caller = "link.busy", .start_byte = 119, .end_byte = 123},
.relation = "USAGE",
.target = {.file = "link.c",
.suffix = "link.conn",
.label = "Class",
.declaration_line = 1,
.declaration_text = "struct conn {"},
.mode = RQ_REQUIRED},
{.case_id = "c.enum.named-field",
.site = {.file = "link.c", .caller = "link.busy", .start_byte = 144, .end_byte = 146},
.relation = "USAGE",
+1014 -28
View File
File diff suppressed because it is too large. Load diff