mirror of
https://github.com/DeusData/codebase-memory-mcp.git
synced 2026-11-04 10:48:20 +00:00
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:
1 parent
17096867bb
commit
8ee198fc4b
10 files changed
+1897
-179
No files matched your search
+126
-2
@@ -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++;
|
||||
|
||||
@@ -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
@@ -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 = ⊤
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -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
@@ -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, ¶ms);
|
||||
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"))
|
||||
|
||||
@@ -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. */
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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
@@ -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
File diff suppressed because it is too large.
Load diff
Reference in new issue
Block a user