Merge lane/c-cpp: translation-unit visibility, include resolution and C++ members

C-family takes the attribute-macro parse, everything else the C# repairing parse. The Rust project model already replaced the walker in this lane's pkgmap hunk, so only build_include_path_context survives. c_variants joins the identity rule; the read-as-value-reference lookup is guarded to C-family because other lanes were measured without it.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
This commit is contained in:
Martin Vogel committed 2026-11-01 19:01:42 +01:00
commit cdc56c5f45
21 files changed
+5396 -395

No files matched your search

+395 -11
View File
@@ -578,6 +578,393 @@ TSTree *cbm_parse_source(TSParser *parser, const char *source, uint32_t source_l
return tree;
}
/* ── Attribute macros a C-family grammar cannot read past ────────────────────
*
* `class LLVM_ABI StringRef {` is how real C++ exports a class. Without the macro's
* definition the grammar reads a function named StringRef whose body holds the members
* as statements, so the class, its methods and its fields are all lost. The same goes
* for `bool f(int a) ATTR_A ATTR_B {` and for the return type of
* `LLVM_NODISCARD StringRef f()`.
*
* Each form below cannot be valid source unless one word expands to an attribute or to
* nothing. That word is left out of a second parse through included ranges, so every
* offset and position stays that of the real file, and a file with none of these forms
* keeps its first tree. Where nothing says which of two words is the macro, none is
* left out. */
#define CBM_ATTR_MAX_ROUNDS 6 /* a macro hides at most one nesting level per round */
#define CBM_ATTR_MAX_DEPTH 256
#define CBM_ATTR_MIN_CAPS 3
typedef struct {
TSRange *items;
uint32_t count;
uint32_t cap;
uint32_t source_len;
const char *source;
const char **defined; /* -D names the caller configured: those keep their meaning */
bool failed;
} CBMAttrWords;
static void cbm_attr_range_add(CBMAttrWords *words, TSRange range) {
if (words->failed) {
return;
}
if (words->count == words->cap) {
uint32_t cap = words->cap ? words->cap * 2 : 32;
TSRange *grown =
(TSRange *)cbm_realloc(CBM_MEM_CLASS_EXTRACT, words->items, cap * sizeof(TSRange));
if (!grown) {
words->failed = true;
return;
}
words->items = grown;
words->cap = cap;
}
words->items[words->count++] = range;
}
static void cbm_attr_word_add(CBMAttrWords *words, TSNode word) {
uint32_t word_start = ts_node_start_byte(word);
size_t word_len = ts_node_end_byte(word) - word_start;
for (const char **define = words->defined; define && *define; define++) {
size_t name_len = strcspn(*define, "=");
if (name_len == word_len && !strncmp(*define, words->source + word_start, word_len)) {
return;
}
}
cbm_attr_range_add(words, (TSRange){ts_node_start_point(word), ts_node_end_point(word),
ts_node_start_byte(word), ts_node_end_byte(word)});
}
static TSPoint cbm_attr_point_at(const char *source, TSNode from, uint32_t byte) {
TSPoint point = ts_node_start_point(from);
for (uint32_t i = ts_node_start_byte(from); i < byte; i++) {
if (source[i] == '\n') {
point.row++;
point.column = 0;
} else {
point.column++;
}
}
return point;
}
/* `win_T *curwin INIT( = NULL);` is how neovim declares every global. A parenthesis
* followed by `=` opens no parameter list and no argument list, so the word before it is a
* macro taking an initializer, and the word and its parentheses are left out together. */
static void cbm_attr_initializer_macro(TSNode declaration, const char *source,
CBMAttrWords *words) {
uint32_t begin = ts_node_start_byte(declaration);
uint32_t end = ts_node_end_byte(declaration);
for (uint32_t i = begin; i + 1 < end; i++) {
if (source[i] != '(') {
continue;
}
uint32_t next = i + 1;
while (next < end && (source[next] == ' ' || source[next] == '\t')) {
next++;
}
if (next + 1 >= words->source_len || source[next] != '=' || source[next + 1] == '=') {
continue;
}
uint32_t word = i;
while (word > begin &&
(isalnum((unsigned char)source[word - 1]) || source[word - 1] == '_')) {
word--;
}
int depth = 0;
uint32_t close = i;
for (; close < words->source_len && source[close] != ';'; close++) {
depth += source[close] == '(';
depth -= source[close] == ')';
if (depth == 0) {
break;
}
}
if (word == i || close == words->source_len || source[close] == ';') {
continue;
}
cbm_attr_range_add(words, (TSRange){cbm_attr_point_at(source, declaration, word),
cbm_attr_point_at(source, declaration, close + 1), word,
close + 1});
return;
}
}
/* A macro's spelling: capitals, digits and underscores, too long to be a template
* parameter. */
static bool cbm_attr_all_caps(const char *source, TSNode word) {
uint32_t start = ts_node_start_byte(word);
uint32_t end = ts_node_end_byte(word);
bool letter = false;
for (uint32_t i = start; i < end; i++) {
char c = source[i];
if (c >= 'A' && c <= 'Z') {
letter = true;
} else if (c != '_' && !(c >= '0' && c <= '9')) {
return false;
}
}
return letter && end - start >= CBM_ATTR_MIN_CAPS;
}
static bool cbm_attr_spells_builtin(const char *source, TSNode word) {
static const char *const builtins[] = {"void", "bool", "char", "short", "int",
"long", "float", "double", "signed", "unsigned",
"const", "constexpr", "auto", NULL};
uint32_t start = ts_node_start_byte(word);
size_t length = ts_node_end_byte(word) - start;
for (int i = 0; builtins[i]; i++) {
if (strlen(builtins[i]) == length && !strncmp(source + start, builtins[i], length)) {
return true;
}
}
return false;
}
/* The one word an ERROR node holds, or a null node. */
static TSNode cbm_attr_error_word(TSNode error) {
TSNode none = {0};
if (ts_node_named_child_count(error) != 1) {
return none;
}
TSNode word = ts_node_named_child(error, 0);
return strcmp(ts_node_type(word), "identifier") ? none : word;
}
static bool cbm_attr_is_tag_specifier(const char *kind) {
return !strcmp(kind, "class_specifier") || !strcmp(kind, "struct_specifier") ||
!strcmp(kind, "union_specifier");
}
/* `bool f(int a) ATTR_A ATTR_B {`: a prototype missing its semicolon, then a "definition"
* made of two bare words. */
static bool cbm_attr_follows_open_prototype(TSNode definition) {
TSNode previous = ts_node_prev_named_sibling(definition);
if (ts_node_is_null(previous) || strcmp(ts_node_type(previous), "declaration")) {
return false;
}
TSNode declarator = ts_node_child_by_field_name(previous, "declarator", 10);
uint32_t count = ts_node_child_count(previous);
return !ts_node_is_null(declarator) &&
!strcmp(ts_node_type(declarator), "function_declarator") && count > 0 &&
ts_node_is_missing(ts_node_child(previous, count - 1));
}
/* A word between the type and the declarator: `TYPE word f()`. */
static void cbm_attr_leading_word(CBMAttrWords *words, const char *source, TSNode type,
TSNode word) {
if (strcmp(ts_node_type(type), "type_identifier")) {
/* `int WINAPI f()`: a builtin type cannot be the macro. */
cbm_attr_word_add(words, word);
} else if (cbm_attr_spells_builtin(source, word)) {
/* `Q_INVOKABLE void f()`: the grammar took the macro for the type. */
cbm_attr_word_add(words, type);
} else if (cbm_attr_all_caps(source, type) != cbm_attr_all_caps(source, word)) {
cbm_attr_word_add(words, cbm_attr_all_caps(source, type) ? type : word);
}
}
static void cbm_attr_check_declaration(TSNode node, const char *source, CBMAttrWords *words) {
TSNode type = ts_node_child_by_field_name(node, "type", 4);
TSNode declarator = ts_node_child_by_field_name(node, "declarator", 10);
if (ts_node_is_null(declarator)) {
return;
}
const char *declarator_kind = ts_node_type(declarator);
bool definition = !strcmp(ts_node_type(node), "function_definition");
bool bare_declarator = !strcmp(declarator_kind, "identifier");
if (definition && bare_declarator && !ts_node_is_null(type)) {
/* A function definition needs a function declarator, so `class ATTR Name {` is the
* only reading: the specifier's name is the macro. */
if (cbm_attr_is_tag_specifier(ts_node_type(type))) {
TSNode name = ts_node_child_by_field_name(type, "name", 4);
if (!ts_node_is_null(name) &&
ts_node_is_null(ts_node_child_by_field_name(type, "body", 4))) {
cbm_attr_word_add(words, name);
}
return;
}
if (!strcmp(ts_node_type(type), "type_identifier") &&
cbm_attr_follows_open_prototype(node)) {
cbm_attr_word_add(words, type);
cbm_attr_word_add(words, declarator);
return;
}
}
/* `LUA_API int (lua_gettop) (lua_State *L);` reads as a function returning a function, which
* no valid declaration is, so the word the grammar took for the type is the macro. A macro
* that wraps the name, `STATUS DEFINE(foo)(int x)`, has the capitals on the inner word. */
if (!ts_node_is_null(type) && !strcmp(ts_node_type(type), "type_identifier") &&
!strcmp(declarator_kind, "function_declarator") && cbm_attr_all_caps(source, type)) {
TSNode inner = ts_node_child_by_field_name(declarator, "declarator", 10);
TSNode inner_name =
ts_node_is_null(inner) ? inner : ts_node_child_by_field_name(inner, "declarator", 10);
if (!ts_node_is_null(inner_name) && !strcmp(ts_node_type(inner), "function_declarator") &&
!cbm_attr_all_caps(source, inner_name)) {
cbm_attr_word_add(words, type);
return;
}
}
uint32_t declarator_start = ts_node_start_byte(declarator);
uint32_t count = ts_node_child_count(node);
for (uint32_t i = 0; i < count; i++) {
TSNode child = ts_node_child(node, i);
if (!ts_node_is_error(child)) {
continue;
}
/* `class ATTR Name : public Base { };`: with an empty body the grammar reads a
* variable named Base, and no declaration has a base clause but a class. */
if (!definition && !ts_node_is_null(type) &&
cbm_attr_is_tag_specifier(ts_node_type(type)) &&
ts_node_is_null(ts_node_child_by_field_name(type, "body", 4)) &&
ts_node_start_byte(child) < declarator_start &&
memchr(source + ts_node_start_byte(child), ':',
ts_node_end_byte(child) - ts_node_start_byte(child))) {
TSNode name = ts_node_child_by_field_name(type, "name", 4);
if (!ts_node_is_null(name)) {
cbm_attr_word_add(words, name);
}
return;
}
/* `void f(int a) ATTR {`: the function is in the error and the macro is what the
* grammar kept as the declarator. */
if (definition && bare_declarator && ts_node_named_child_count(child) == 1 &&
!strcmp(ts_node_type(ts_node_named_child(child, 0)), "function_declarator")) {
cbm_attr_word_add(words, declarator);
continue;
}
TSNode word = cbm_attr_error_word(child);
if (ts_node_is_null(word)) {
continue;
}
if (ts_node_start_byte(child) >= declarator_start) {
/* `int f() const ATTR {` and `void f() override ATTR;` */
if (!strcmp(declarator_kind, "function_declarator")) {
cbm_attr_word_add(words, word);
}
} else if (!ts_node_is_null(type)) {
cbm_attr_leading_word(words, source, type, word);
}
}
}
static bool cbm_attr_is_container(const char *kind) {
static const char *const containers[] = {"translation_unit",
"namespace_definition",
"declaration_list",
"field_declaration_list",
"linkage_specification",
"template_declaration",
"class_specifier",
"struct_specifier",
"union_specifier",
"preproc_if",
"preproc_ifdef",
"preproc_elif",
"preproc_elifdef",
"preproc_else",
NULL};
for (int i = 0; containers[i]; i++) {
if (!strcmp(kind, containers[i])) {
return true;
}
}
return false;
}
/* A subtree without an error can only hold the clean `class ATTR Name {` form, and that
* form never sits inside a clean function body of real source. */
static void cbm_attr_collect(TSNode node, const char *source, CBMAttrWords *words, int depth) {
if (depth > CBM_ATTR_MAX_DEPTH || words->failed) {
return;
}
uint32_t count = ts_node_named_child_count(node);
for (uint32_t i = 0; i < count; i++) {
TSNode child = ts_node_named_child(node, i);
const char *kind = ts_node_type(child);
bool declaration = !strcmp(kind, "function_definition") || !strcmp(kind, "declaration") ||
!strcmp(kind, "field_declaration");
if (declaration) {
cbm_attr_check_declaration(child, source, words);
}
if (!strcmp(kind, "declaration") && ts_node_has_error(child)) {
cbm_attr_initializer_macro(child, source, words);
}
if (ts_node_has_error(child) || cbm_attr_is_container(kind)) {
cbm_attr_collect(child, source, words, depth + 1);
}
}
}
static int cbm_attr_range_cmp(const void *a, const void *b) {
uint32_t left = ((const TSRange *)a)->start_byte;
uint32_t right = ((const TSRange *)b)->start_byte;
return (left > right) - (left < right);
}
/* Sets the parser to read everything except the words. The last range is open, so the
* virtual final newline of cbm_parse_source stays inside it. */
static bool cbm_attr_exclude_words(TSParser *parser, CBMAttrWords *words) {
qsort(words->items, words->count, sizeof(TSRange), cbm_attr_range_cmp);
uint32_t unique = 0;
for (uint32_t i = 0; i < words->count; i++) {
if (!unique || words->items[i].start_byte != words->items[unique - 1].start_byte) {
words->items[unique++] = words->items[i];
}
}
words->count = unique;
TSRange *kept =
(TSRange *)cbm_alloc(CBM_MEM_CLASS_EXTRACT, ((size_t)unique + 1) * sizeof(TSRange));
if (!kept) {
return false;
}
uint32_t from = 0;
uint32_t kept_count = 0;
TSPoint from_point = {0, 0};
for (uint32_t i = 0; i < unique; i++) {
/* A word at the start of the file leaves nothing before it. */
if (words->items[i].start_byte > from) {
kept[kept_count++] = (TSRange){from_point, words->items[i].start_point, from,
words->items[i].start_byte};
}
from = words->items[i].end_byte;
from_point = words->items[i].end_point;
}
kept[kept_count++] = (TSRange){from_point, {UINT32_MAX, UINT32_MAX}, from, UINT32_MAX};
bool set = ts_parser_set_included_ranges(parser, kept, kept_count);
cbm_free(CBM_MEM_CLASS_EXTRACT, kept);
return set;
}
static TSTree *cbm_parse_c_source_defined(TSParser *parser, const char *source, uint32_t source_len,
TSParseOptions opts, const char **defined) {
TSTree *tree = cbm_parse_source(parser, source, source_len, opts);
CBMAttrWords words = {.source_len = source_len, .source = source, .defined = defined};
for (int round = 0; tree && round < CBM_ATTR_MAX_ROUNDS; round++) {
uint32_t before = words.count;
cbm_attr_collect(ts_tree_root_node(tree), source, &words, 0);
if (words.failed || words.count == before || !cbm_attr_exclude_words(parser, &words)) {
break;
}
TSTree *next = cbm_parse_source(parser, source, source_len, opts);
(void)ts_parser_set_included_ranges(parser, NULL, 0);
if (!next) {
ts_parser_reset(parser);
break;
}
ts_tree_delete(tree);
tree = next;
}
cbm_free(CBM_MEM_CLASS_EXTRACT, words.items);
return tree;
}
TSTree *cbm_parse_c_source(TSParser *parser, const char *source, uint32_t source_len,
TSParseOptions opts) {
return cbm_parse_c_source_defined(parser, source, source_len, opts, NULL);
}
// --- Parse timeout callback ---
/* Budget for the tree-sitter progress callback. The PRIMARY gate is per-thread
@@ -3361,7 +3748,7 @@ static void cbm_rescue_defs_from_projection(CBMExtractCtx *raw_ctx, const TSLang
TSParseOptions opts = {0};
/* The projection keeps the raw source's length and line structure,
* so it is parsed under the same terminated-last-line rule (#2078). */
tree = cbm_parse_source(parser, projected, (uint32_t)raw_ctx->source_len, opts);
tree = cbm_parse_c_source(parser, projected, (uint32_t)raw_ctx->source_len, opts);
}
CBMHashTable *held = tree ? cbm_ht_create(CBM_SZ_256) : NULL;
if (held) {
@@ -3525,7 +3912,7 @@ static void extract_cpp_branch_views(const CBMExtractCtx *raw, const TSLanguage
}
/* A view keeps the raw source's length and line structure, so it is
* parsed under the same terminated-last-line rule (#2078). */
TSTree *tree = cbm_parse_source(parser, view, (uint32_t)raw->source_len, opts);
TSTree *tree = cbm_parse_c_source(parser, view, (uint32_t)raw->source_len, opts);
if (!tree) {
return;
}
@@ -3973,7 +4360,11 @@ static CBMFileResult *extract_file_ex_body(const char *source, int source_len, C
bool sql_ranged = language == CBM_LANG_SQL && cbm_sql_values_exclusion_on(rel_path) &&
cbm_sql_values_kept_ranges(source, (uint32_t)source_len, &sql_kept) &&
ts_parser_set_included_ranges(parser, sql_kept.items, sql_kept.count);
TSTree *tree = cbm_parse_source_repairing(parser, language, source, (uint32_t)source_len, opts);
bool c_family = language == CBM_LANG_C || language == CBM_LANG_CPP || language == CBM_LANG_CUDA;
TSTree *tree = c_family ? cbm_parse_c_source_defined(parser, source, (uint32_t)source_len, opts,
extra_defines)
: cbm_parse_source_repairing(parser, language, source,
(uint32_t)source_len, opts);
if (sql_ranged) {
(void)ts_parser_set_included_ranges(parser, NULL, 0);
}
@@ -4204,16 +4595,9 @@ static CBMFileResult *extract_file_ex_body(const char *source, int source_len, C
TSParser *pp_parser = get_thread_parser(ts_lang, language);
if (pp_parser) {
ts_parser_reset(pp_parser);
CBMStringInput pp_input = {expanded, (uint32_t)expanded_len, false};
TSInput pp_ts_input = {
&pp_input,
cbm_string_read,
TSInputEncodingUTF8,
NULL,
};
TSParseOptions pp_opts = {0};
TSTree *pp_tree =
ts_parser_parse_with_options(pp_parser, NULL, pp_ts_input, pp_opts);
cbm_parse_c_source(pp_parser, expanded, (uint32_t)expanded_len, pp_opts);
if (pp_tree) {
TSNode pp_root = ts_tree_root_node(pp_tree);
cbm_sha256_hex(expanded, (size_t)expanded_len,
+6
View File
@@ -1279,6 +1279,12 @@ TSTree *cbm_parse_source(TSParser *parser, const char *source, uint32_t source_l
* same-length view of it. Offsets and lines are the source's own. */
TSTree *cbm_parse_source_repairing(TSParser *parser, CBMLanguage language, const char *source,
uint32_t source_len, TSParseOptions opts);
/* As above for C-family source. A word that can only be an attribute macro is left out
* of the parse, so `class LLVM_ABI StringRef {` reads as the class. Offsets and positions
* stay those of the real file. Every C-family whole-file parse goes through here, so the
* extraction and the semantic passes read one tree shape. */
TSTree *cbm_parse_c_source(TSParser *parser, const char *source, uint32_t source_len,
TSParseOptions opts);
// Extract all data from one file. Caller must call cbm_free_result().
// source must remain valid for the duration of the call.
+7
View File
@@ -9080,6 +9080,13 @@ static void extract_c_typedef(CBMExtractCtx *ctx, TSNode node, const CBMLangSpec
def.lines = (int)(def.end_line - def.start_line + TS_LINE_OFFSET);
def.is_exported = cbm_is_exported(name, ctx->language);
def.docstring = extract_docstring(ctx, node, name);
/* What this name aliases, as written: a tag or another typedef name. A
* member access through the alias reaches that type's fields. */
if (tag_kind && !ts_node_is_null(tag_node)) {
def.alias_target = cbm_node_text(a, tag_node, ctx->source);
} else if (strcmp(tk, "type_identifier") == 0) {
def.alias_target = cbm_node_text(a, type, ctx->source);
}
cbm_defs_push(&ctx->result->defs, a, def);
}
ts_tree_cursor_delete(&cursor);
+2 -2
View File
@@ -365,7 +365,7 @@ static const char *cpp_branch_types[] = {"if_statement", "for_statement",
"while_statement", "switch_statement", "case_statement",
"try_statement", "catch_clause", NULL};
static const char *cpp_var_types[] = {"declaration", NULL};
static const char *cpp_assign_types[] = {"assignment_expression", NULL};
static const char *cpp_assign_types[] = {"assignment_expression", "update_expression", NULL};
static const char *cpp_throw_types[] = {"throw_statement", NULL};
// ==================== C# ====================
@@ -490,7 +490,7 @@ static const char *c_branch_types[] = {"if_statement",
"case_statement",
NULL};
static const char *c_var_types[] = {"declaration", NULL};
static const char *c_assign_types[] = {"assignment_expression", NULL};
static const char *c_assign_types[] = {"assignment_expression", "update_expression", NULL};
// ==================== BASH ====================
static const char *bash_func_types[] = {"function_definition", NULL};
+1782 -185
View File
File diff suppressed because it is too large. Load diff
+12 -1
View File
@@ -24,6 +24,17 @@ typedef struct {
struct CProofDeclaration *proof_declarations;
int proof_declaration_count, proof_declaration_cap;
struct CProofInclude *proof_includes;
/* 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;
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
* are found before anything at namespace scope. */
const CBMRegisteredType *proof_method_class;
bool proof_class_scope_checked; /* the current name was looked for in that class */
bool plain_header; /* this TU is a .h, so its own language is its includer's */
const cbm_resolve_import_snapshot_t *import_facts;
bool require_import_proofs;
struct CProofBuiltinBinding *proof_builtins;
@@ -173,7 +184,7 @@ struct CLSPEvalMemoSlot {
/* Conservative syntax audit shared with the source-fact collector. Only direct,
* fixed-arity free signatures with explicitly written builtin types qualify. */
bool cbm_c_preprocessor_domain_unsupported(TSNode root);
bool cbm_c_preprocessor_domain_unsupported(TSNode root, const char *source);
const CBMType *cbm_c_builtin_annotation(CBMArena *arena, const char *source, TSNode type);
const CBMType *cbm_c_free_signature(CBMArena *arena, const char *source, TSNode declaration,
bool cpp_mode);
+589 -45
View File
@@ -17,7 +17,14 @@ typedef struct {
bool failed;
bool extern_c;
bool preprocessor_unknown;
bool conditional;
/* C has one linkage for every name. A plain .h is parsed by the C++ grammar
* like everything here, but its language is its includer's. */
bool c_language;
/* `#ifdef __cplusplus extern "C" { #endif`: the brace the grammar cannot pair. */
bool file_extern_c;
const char *module;
CBMCFileFacts *facts;
} csp_context_t;
static bool csp_is(TSNode node, const char *kind) {
@@ -55,13 +62,20 @@ static void csp_mark(csp_context_t *ctx, TSNode node, const char *qn, uint32_t f
entry->definition->source_flags = flags;
if (arity >= 0 && entry->definition->signature_param_count == arity)
entry->definition->source_flags |= CBM_SOURCE_DECL_FIXED_ARITY;
entry->definition->source_arity = arity >= 0 ? arity + 1 : 0;
}
static TSNode csp_function_declarator(TSNode node) {
TSNode decl = csp_field(node, "declarator");
for (unsigned depth = 0; depth < 32 && !ts_node_is_null(decl); depth++) {
if (csp_is(decl, "function_declarator"))
return decl;
if (!csp_is(decl, "pointer_declarator") && !csp_is(decl, "reference_declarator"))
if (csp_is(decl, "reference_declarator")) {
/* `T &f()`: the grammar gives this declarator's inner part no field name. */
uint32_t count = ts_node_named_child_count(decl);
decl = count ? ts_node_named_child(decl, count - 1) : (TSNode){0};
continue;
}
if (!csp_is(decl, "pointer_declarator"))
break;
decl = csp_field(decl, "declarator");
}
@@ -92,18 +106,39 @@ static int csp_arity(csp_context_t *ctx, TSNode declarator) {
ts_tree_cursor_delete(&cursor);
return count;
}
/* Raw directives can rename either a callable or a builtin type. The adapter
* does not borrow authority from a different preprocessor configuration. */
bool cbm_c_preprocessor_domain_unsupported(TSNode root) {
/* A signature compares builtin spellings, so only a macro that respells a
* builtin type word can falsify one. Conditionals and ordinary macros change
* which declarations exist, which the declaration facts record per name. */
static bool csp_builtin_word(const char *text, uint32_t length) {
static const char *const words[] = {"void", "bool", "_Bool", "char", "short",
"int", "long", "float", "double", "signed",
"unsigned", "wchar_t", "char8_t", "char16_t", "char32_t"};
for (size_t i = 0; i < sizeof(words) / sizeof(words[0]); i++)
if (strlen(words[i]) == length && !memcmp(text, words[i], length))
return true;
return false;
}
bool cbm_c_preprocessor_domain_unsupported(TSNode root, const char *source) {
TSTreeCursor cursor = ts_tree_cursor_new(root);
uint32_t visited = 0;
bool unsupported = false;
for (;;) {
TSNode node = ts_tree_cursor_current_node(&cursor);
if (++visited > 1000000 || !strncmp(ts_node_type(node), "preproc_", 8)) {
const char *kind = ts_node_type(node);
if (++visited > 1000000) {
unsupported = true;
break;
}
if (!strcmp(kind, "preproc_def") || !strcmp(kind, "preproc_function_def")) {
TSNode name = ts_node_child_by_field_name(node, "name", 4);
if (!ts_node_is_null(name) &&
csp_builtin_word(source + ts_node_start_byte(name),
ts_node_end_byte(name) - ts_node_start_byte(name))) {
unsupported = true;
break;
}
}
if (ts_tree_cursor_goto_first_child(&cursor))
continue;
while (!ts_tree_cursor_goto_next_sibling(&cursor)) {
@@ -236,28 +271,193 @@ const CBMType *cbm_c_free_signature(CBMArena *arena, const char *source, TSNode
return cbm_type_func(arena, NULL, types, returns);
}
static CBMCDecl *csp_add_decl(csp_context_t *ctx, CBMCDeclKind kind, const char *name,
uint32_t flags) {
CBMCFileFacts *facts = ctx->facts;
if (!facts || !name)
return NULL;
if (facts->decl_count == facts->decl_cap) {
int cap = facts->decl_cap ? facts->decl_cap * 2 : 16;
CBMCDecl *next = cbm_arena_alloc(ctx->arena, (size_t)cap * sizeof(*next));
if (!next) {
ctx->failed = true;
return NULL;
}
if (facts->decl_count)
memcpy(next, facts->decls, (size_t)facts->decl_count * sizeof(*next));
facts->decls = next;
facts->decl_cap = cap;
}
CBMCDecl *decl = &facts->decls[facts->decl_count++];
*decl = (CBMCDecl){.file = facts->file_qn ? facts->file_qn : ctx->module,
.name = name,
.arity = -1,
.kind = kind,
.flags = flags | (ctx->conditional ? CBM_C_DECL_CONDITIONAL : 0)};
return decl;
}
static bool csp_storage_is(csp_context_t *ctx, TSNode node, const char *storage) {
for (uint32_t i = 0; i < ts_node_named_child_count(node); i++) {
TSNode child = ts_node_named_child(node, i);
const char *text = csp_is(child, "storage_class_specifier") ? csp_text(ctx, child) : NULL;
if (text && !strcmp(text, storage))
return true;
}
return false;
}
/* Name relative to the file: a namespace is part of a C++ linkage key, the file
* module never is. */
static const char *csp_relative(csp_context_t *ctx, const char *owner, const char *name) {
size_t module_len = strlen(ctx->module);
if (!strcmp(owner, ctx->module))
return name;
if (!strncmp(owner, ctx->module, module_len) && owner[module_len] == '.')
return cbm_arena_sprintf(ctx->arena, "%s.%s", owner + module_len + 1, name);
return NULL;
}
/* A parameter list ending in `...` is not a fixed count, and `()` in C is not a
* prototype at all: it says nothing of the parameters. */
static int csp_fixed_arity(csp_context_t *ctx, TSNode decl) {
TSNode params = csp_field(decl, "parameters");
if (ts_node_is_null(params) || !ts_node_named_child_count(params))
return -1;
uint32_t count = ts_node_child_count(params);
for (uint32_t i = 0; i < count; i++)
if (csp_is(ts_node_child(params, i), "variadic_parameter") ||
csp_is(ts_node_child(params, i), "..."))
return -1;
return csp_arity(ctx, decl);
}
static void csp_note_prototype(csp_context_t *ctx, TSNode node, TSNode decl, const char *owner,
const char *name) {
bool is_static = csp_storage_is(ctx, node, "static");
uint32_t flags = (is_static ? 0 : CBM_C_DECL_EXTERNAL) |
(ctx->extern_c || ctx->file_extern_c ? CBM_C_DECL_C_LINKAGE : 0);
CBMCDecl *fact = csp_add_decl(ctx, CBM_C_DECL_PROTOTYPE, csp_relative(ctx, owner, name), flags);
if (!fact)
return;
fact->arity = csp_fixed_arity(ctx, decl);
fact->signature = ctx->preprocessor_unknown
? NULL
: cbm_c_free_signature(ctx->arena, ctx->source, node, !ctx->c_language);
}
/* `struct Tag`, `Tag *`, `const Tag`: the name a member access needs. */
static const char *csp_base_type_name(csp_context_t *ctx, TSNode type) {
if (csp_is(type, "struct_specifier") || csp_is(type, "union_specifier") ||
csp_is(type, "class_specifier"))
type = csp_field(type, "name");
return csp_is(type, "type_identifier") ? csp_text(ctx, type) : NULL;
}
/* Enumerators sit in file scope whichever declaration wrote them, with no linkage. */
static void csp_note_enumerators(csp_context_t *ctx, TSNode type, const char *owner) {
if (!ctx->facts || !csp_is(type, "enum_specifier") || strcmp(owner, ctx->module))
return;
TSNode list = csp_field(type, "body");
uint32_t count = ts_node_is_null(list) ? 0 : ts_node_named_child_count(list);
for (uint32_t i = 0; i < count; i++) {
TSNode item = ts_node_named_child(list, i);
const char *name =
csp_is(item, "enumerator") ? csp_text(ctx, csp_field(item, "name")) : NULL;
CBMCDecl *fact = name ? csp_add_decl(ctx, CBM_C_DECL_VALUE, name, 0) : NULL;
if (fact)
fact->target = cbm_arena_sprintf(ctx->arena, "%s.%s", ctx->module, name);
}
}
/* Objects, not functions: `extern struct redisServer server;` has no graph node
* of its own, and a file-scope `static` or enumerator has no external linkage. */
static void csp_note_values(csp_context_t *ctx, TSNode node, const char *owner) {
if (!ctx->facts || strcmp(owner, ctx->module))
return;
csp_note_enumerators(ctx, csp_field(node, "type"), owner);
bool is_extern = csp_storage_is(ctx, node, "extern");
bool is_static = csp_storage_is(ctx, node, "static");
const char *type_name = csp_base_type_name(ctx, csp_field(node, "type"));
/* `EXTERN buf_T *curbuf;` reads as type EXTERN with buf_T stranded in an ERROR:
* a storage macro in front of the real type. */
for (uint32_t i = 0; i < ts_node_named_child_count(node); i++) {
TSNode child = ts_node_named_child(node, i);
if (csp_is(child, "ERROR") && ts_node_named_child_count(child) == 1 &&
csp_is(ts_node_named_child(child, 0), "identifier"))
type_name = csp_text(ctx, ts_node_named_child(child, 0));
}
TSTreeCursor cursor = ts_tree_cursor_new(node);
if (ts_tree_cursor_goto_first_child(&cursor))
do {
const char *field = ts_tree_cursor_current_field_name(&cursor);
if (!field || strcmp(field, "declarator"))
continue;
TSNode declarator = ts_tree_cursor_current_node(&cursor);
bool initialized = csp_is(declarator, "init_declarator");
for (unsigned depth = 0; depth < 16 && !ts_node_is_null(declarator); depth++) {
if (csp_is(declarator, "function_declarator"))
break;
if (csp_is(declarator, "identifier")) {
const char *name = csp_text(ctx, declarator);
CBMCDecl *fact =
is_extern && !initialized
? csp_add_decl(ctx, CBM_C_DECL_EXTERN_VALUE, name, CBM_C_DECL_EXTERNAL)
: csp_add_decl(ctx, CBM_C_DECL_VALUE, name,
is_static ? 0 : CBM_C_DECL_EXTERNAL);
if (fact) {
fact->type_name = type_name;
fact->target = cbm_arena_sprintf(ctx->arena, "%s.%s", ctx->module, name);
}
break;
}
declarator = csp_field(declarator, "declarator");
}
} while (ts_tree_cursor_goto_next_sibling(&cursor));
ts_tree_cursor_delete(&cursor);
}
static void csp_note_macro(csp_context_t *ctx, TSNode node) {
TSNode name = csp_field(node, "name");
if (!csp_is(name, "identifier"))
return;
const char *text = csp_text(ctx, name);
if (text && !(ctx->facts && ctx->facts->guard && !strcmp(ctx->facts->guard, text)))
(void)csp_add_decl(ctx, CBM_C_DECL_MACRO, text, 0);
}
static void csp_free_function(csp_context_t *ctx, TSNode node, const char *owner) {
TSNode decl = csp_function_declarator(node), name_node = csp_field(decl, "declarator");
if (!csp_is(name_node, "identifier"))
/* Lua declares its whole API as `LUA_API int (lua_gettop) (lua_State *L);`. */
while (csp_is(name_node, "parenthesized_declarator"))
name_node = ts_node_named_child(name_node, 0);
if (!csp_is(name_node, "identifier")) {
if (csp_is(node, "declaration"))
csp_note_values(ctx, node, owner);
return;
}
const char *name = csp_text(ctx, name_node);
const char *qn = name ? cbm_arena_sprintf(ctx->arena, "%s.%s", owner, name) : NULL;
if (csp_is(node, "declaration") && name && !ts_node_has_error(decl))
csp_note_prototype(ctx, node, decl, owner, name);
csp_entry_t *entry = csp_entry(ctx, qn, ts_node_start_point(node).row + 1);
if (!entry || entry->duplicate)
/* 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. */
if (!entry || entry->duplicate || ctx->preprocessor_unknown)
return;
CBMLSPDef *def = entry->definition;
bool cpp = def->lang == CBM_LANG_CPP || def->lang == CBM_LANG_CUDA;
def->source_flags = CBM_SOURCE_DECL_KNOWN | CBM_SOURCE_DECL_TOP_LEVEL;
if (ctx->conditional)
def->source_flags |= CBM_SOURCE_DECL_CONDITIONAL;
if (csp_is(node, "function_definition"))
def->source_flags |= CBM_SOURCE_FUNCTION_DEFINITION;
if (!cpp || ctx->extern_c)
if (!cpp || ctx->extern_c || ctx->file_extern_c)
def->source_flags |= CBM_SOURCE_DECL_EXTERN_C;
for (uint32_t i = 0; i < ts_node_named_child_count(node); i++) {
TSNode child = ts_node_named_child(node, i);
const char *text = csp_is(child, "storage_class_specifier") ? csp_text(ctx, child) : NULL;
if (text && !strcmp(text, "static"))
def->source_flags |= CBM_SOURCE_DECL_STATIC;
}
if (csp_storage_is(ctx, node, "static"))
def->source_flags |= CBM_SOURCE_DECL_STATIC;
int arity = csp_fixed_arity(ctx, decl);
def->source_arity = arity >= 0 ? arity + 1 : 0;
size_t module_len = strlen(ctx->module);
def->namespace_name = !strcmp(owner, ctx->module) ? ""
: !strncmp(owner, ctx->module, module_len) && owner[module_len] == '.'
@@ -269,27 +469,112 @@ static void csp_free_function(csp_context_t *ctx, TSNode node, const char *owner
def->source_flags |= CBM_SOURCE_DECL_FIXED_ARITY;
}
static void csp_method(csp_context_t *ctx, TSNode node, const char *owner, bool is_public) {
static void csp_method(csp_context_t *ctx, TSNode node, const char *owner, uint32_t access) {
TSNode decl = csp_function_declarator(node), name_node = csp_field(decl, "declarator");
if (!csp_is(name_node, "field_identifier") && !csp_is(name_node, "identifier"))
return; /* Pointer fields, operators and constructors need their own contracts. */
const char *name = csp_text(ctx, name_node);
const char *qn = name ? cbm_arena_sprintf(ctx->arena, "%s.%s", owner, name) : NULL;
uint32_t flags =
CBM_SOURCE_DECL_KNOWN | (is_public ? CBM_SOURCE_DECL_PUBLIC : CBM_SOURCE_DECL_PRIVATE);
csp_mark(ctx, node, qn, flags, csp_arity(ctx, decl));
csp_mark(ctx, node, qn, CBM_SOURCE_DECL_KNOWN | access, csp_arity(ctx, decl));
}
static void csp_class(csp_context_t *ctx, TSNode node, const char *parent) {
TSNode body = csp_field(node, "body"), name_node = csp_field(node, "name");
if (ts_node_is_null(body) || !csp_is(name_node, "type_identifier"))
/* A name a class body declares. Many have no graph node: a member function declared and
* not defined here, a pure virtual, a nested type, an enumerator, a using-declaration.
* Inside the class such a name still hides anything of that name outside it. */
static void csp_member_name(csp_context_t *ctx, const char *klass, TSNode name, uint32_t access) {
if (ts_node_is_null(name))
return;
const char *name = csp_text(ctx, name_node);
const char *text = csp_text(ctx, name);
const char *scope_end = text ? strrchr(text, ':') : NULL;
CBMCDecl *fact = csp_add_decl(ctx, CBM_C_DECL_MEMBER_NAME, scope_end ? scope_end + 1 : text,
access == CBM_SOURCE_DECL_PUBLIC ? 0
: access == CBM_SOURCE_DECL_PROTECTED
? CBM_C_DECL_NOT_PUBLIC | CBM_C_DECL_PROTECTED
: CBM_C_DECL_NOT_PUBLIC);
if (fact)
fact->type_name = klass;
}
static void csp_note_member(csp_context_t *ctx, TSNode node, const char *klass, uint32_t access) {
if (csp_is(node, "template_declaration")) {
uint32_t count = ts_node_named_child_count(node);
for (uint32_t i = 0; i < count; i++)
csp_note_member(ctx, ts_node_named_child(node, i), klass, access);
return;
}
if (csp_is(node, "using_declaration")) {
uint32_t count = ts_node_named_child_count(node);
if (count)
csp_member_name(ctx, klass, ts_node_named_child(node, count - 1), access);
return;
}
if (csp_is(node, "alias_declaration")) {
csp_member_name(ctx, klass, csp_field(node, "name"), access);
return;
}
if (csp_is(node, "type_definition")) {
csp_member_name(ctx, klass, csp_field(node, "declarator"), access);
return;
}
TSNode function = csp_function_declarator(node);
if (!ts_node_is_null(function)) {
TSNode name = csp_field(function, "declarator");
if (csp_is(name, "field_identifier") || csp_is(name, "identifier"))
csp_member_name(ctx, klass, name, access);
return;
}
TSNode type = csp_field(node, "type");
if (ts_node_is_null(type) ||
(!csp_is(type, "struct_specifier") && !csp_is(type, "class_specifier") &&
!csp_is(type, "union_specifier") && !csp_is(type, "enum_specifier")))
return;
csp_member_name(ctx, klass, csp_field(type, "name"), access);
if (!csp_is(type, "enum_specifier"))
return;
TSNode body = csp_field(type, "body");
uint32_t count = ts_node_is_null(body) ? 0 : ts_node_named_child_count(body);
for (uint32_t i = 0; i < count; i++) {
TSNode enumerator = ts_node_named_child(body, i);
if (csp_is(enumerator, "enumerator"))
csp_member_name(ctx, klass, csp_field(enumerator, "name"), access);
}
}
/* A struct inside a preprocessor branch stays unknown: unlike a function, whose
* callers cannot compile without it, a branch is how a project swaps one layout
* for another, and member lookup cannot tell which one the includer gets. */
static void csp_class(csp_context_t *ctx, TSNode node, const char *parent,
const char *typedef_name);
/* `union vval_union { ... } vval;` defines a type where it declares a member. Extraction
* names it under the outer class or directly under the outer's scope depending on the form,
* and only the name it used has an entry, so the other call finds nothing. */
static void csp_nested_class(csp_context_t *ctx, TSNode member, const char *outer,
const char *parent) {
TSNode type = csp_field(member, "type");
if ((!csp_is(type, "struct_specifier") && !csp_is(type, "class_specifier") &&
!csp_is(type, "union_specifier")) ||
ts_node_is_null(csp_field(type, "body")))
return;
csp_class(ctx, type, outer, NULL);
csp_class(ctx, type, parent, NULL);
}
static void csp_class(csp_context_t *ctx, TSNode node, const char *parent,
const char *typedef_name) {
TSNode body = csp_field(node, "body"), name_node = csp_field(node, "name");
if (ts_node_is_null(body) || (!csp_is(name_node, "type_identifier") && !typedef_name) ||
ctx->conditional)
return;
const char *name =
csp_is(name_node, "type_identifier") ? csp_text(ctx, name_node) : typedef_name;
const char *qn = name ? cbm_arena_sprintf(ctx->arena, "%s.%s", parent, name) : NULL;
if (!qn) {
ctx->failed = true;
return;
}
bool complete = true, is_public = csp_is(node, "struct_specifier");
bool complete = true, body_complete = true;
uint32_t access =
csp_is(node, "struct_specifier") ? CBM_SOURCE_DECL_PUBLIC : CBM_SOURCE_DECL_PRIVATE;
TSTreeCursor header = ts_tree_cursor_new(node);
if (ts_tree_cursor_goto_first_child(&header))
do {
@@ -305,27 +590,129 @@ static void csp_class(csp_context_t *ctx, TSNode node, const char *parent) {
TSNode child = ts_tree_cursor_current_node(&cursor);
if (!csp_budget(ctx, 0)) {
complete = false;
body_complete = false;
break;
}
if (!ts_node_is_named(child) || csp_is(child, "comment"))
continue;
if (csp_is(child, "access_specifier")) {
const char *access = csp_text(ctx, child);
is_public = access && !strcmp(access, "public");
const char *written = csp_text(ctx, child);
access = written && !strcmp(written, "public") ? CBM_SOURCE_DECL_PUBLIC
: written && !strcmp(written, "protected") ? CBM_SOURCE_DECL_PROTECTED
: CBM_SOURCE_DECL_PRIVATE;
} else if (csp_is(child, "function_definition") || csp_is(child, "field_declaration") ||
csp_is(child, "declaration")) {
csp_method(ctx, child, qn, is_public);
csp_method(ctx, child, qn, access);
csp_note_member(ctx, child, name, access);
csp_nested_class(ctx, child, qn, parent);
} else {
complete = false;
if (csp_is(child, "template_declaration") || csp_is(child, "using_declaration") ||
csp_is(child, "alias_declaration") || csp_is(child, "type_definition"))
csp_note_member(ctx, child, name, access);
else if (!csp_is(child, "friend_declaration") &&
!csp_is(child, "static_assert_declaration"))
body_complete = false;
}
} while (ts_tree_cursor_goto_next_sibling(&cursor));
ts_tree_cursor_delete(&cursor);
csp_mark(ctx, node, qn,
CBM_SOURCE_DECL_KNOWN | (complete ? CBM_SOURCE_CLASS_DOMAIN_COMPLETE : 0), -1);
CBM_SOURCE_DECL_KNOWN | (complete ? CBM_SOURCE_CLASS_DOMAIN_COMPLETE : 0) |
(body_complete ? CBM_SOURCE_CLASS_BODY_COMPLETE : 0),
-1);
}
static bool csp_preprocessor_branch(TSNode node) {
return csp_is(node, "preproc_if") || csp_is(node, "preproc_ifdef") ||
csp_is(node, "preproc_else") || csp_is(node, "preproc_elif") ||
csp_is(node, "preproc_elifdef");
}
static void csp_walk(csp_context_t *ctx, TSNode node, const char *owner, unsigned depth);
static void csp_walk_children(csp_context_t *ctx, TSNode node, const char *owner, unsigned depth) {
TSTreeCursor cursor = ts_tree_cursor_new(node);
if (ts_tree_cursor_goto_first_child(&cursor))
do {
TSNode child = ts_tree_cursor_current_node(&cursor);
if (ts_node_is_named(child))
csp_walk(ctx, child, owner, depth + 1);
} while (ts_tree_cursor_goto_next_sibling(&cursor));
ts_tree_cursor_delete(&cursor);
}
/* The only code a guard's condition can hide is a header the caller suppressed
* on purpose: declarations inside one exist in every ordinary configuration.
* Any other branch records that its declarations are conditional. */
static TSNode csp_include_guard(csp_context_t *ctx, TSNode root) {
TSNode guard = {0};
uint32_t count = ts_node_named_child_count(root);
for (uint32_t i = 0; i < count; i++) {
TSNode child = ts_node_named_child(root, i);
if (csp_is(child, "comment"))
continue;
if (!ts_node_is_null(guard) || !csp_is(child, "preproc_ifdef"))
return (TSNode){0};
guard = child;
}
if (ts_node_is_null(guard))
return guard;
TSNode keyword = ts_node_child(guard, 0);
TSNode name = csp_field(guard, "name");
if (ts_node_is_null(keyword) || !csp_is(name, "identifier") ||
ts_node_end_byte(keyword) - ts_node_start_byte(keyword) != 7 ||
memcmp(ctx->source + ts_node_start_byte(keyword), "#ifndef", 7) ||
!ts_node_is_null(csp_field(guard, "alternative")))
return (TSNode){0};
uint32_t kids = ts_node_named_child_count(guard);
for (uint32_t i = 0; i < kids; i++) {
TSNode child = ts_node_named_child(guard, i);
if (csp_is(child, "comment") || ts_node_eq(child, name))
continue;
const char *defined =
csp_is(child, "preproc_def") ? csp_text(ctx, csp_field(child, "name")) : NULL;
const char *guarded = csp_text(ctx, name);
return defined && guarded && !strcmp(defined, guarded) ? guard : (TSNode){0};
}
return (TSNode){0};
}
static void csp_walk(csp_context_t *ctx, TSNode node, const char *owner, unsigned depth) {
if (!csp_budget(ctx, depth))
return;
if (ctx->facts && (csp_is(node, "namespace_definition") || csp_is(node, "class_specifier") ||
csp_is(node, "template_declaration")))
ctx->facts->cpp_syntax = true;
if (csp_is(node, "preproc_def") || csp_is(node, "preproc_function_def")) {
csp_note_macro(ctx, node);
return;
}
if (csp_preprocessor_branch(node)) {
bool saved = ctx->conditional;
ctx->conditional = true;
csp_walk_children(ctx, node, owner, depth);
ctx->conditional = saved;
return;
}
/* Error recovery around a directive still leaves its neighbours as complete
* nodes. Each is judged by itself, and one with an error inside is skipped. */
if (csp_is(node, "ERROR")) {
csp_walk_children(ctx, node, owner, depth);
return;
}
if (csp_is(node, "enum_specifier")) {
csp_note_enumerators(ctx, node, owner);
return;
}
if (csp_is(node, "type_definition")) {
TSNode type = csp_field(node, "type"), declarator = csp_field(node, "declarator");
csp_note_enumerators(ctx, type, owner);
/* 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"))
csp_class(ctx, type, owner,
csp_is(declarator, "type_identifier") ? csp_text(ctx, declarator) : NULL);
return;
}
if (csp_is(node, "linkage_specification")) {
const char *value = csp_text(ctx, csp_field(node, "value"));
TSNode body = csp_field(node, "body");
@@ -338,12 +725,17 @@ static void csp_walk(csp_context_t *ctx, TSNode node, const char *owner, unsigne
return;
}
if (csp_is(node, "function_definition") || csp_is(node, "declaration")) {
csp_free_function(ctx, node, owner);
/* A definition with an error in its body still has this signature. */
TSNode declarator = csp_field(node, "declarator"), type = csp_field(node, "type");
if ((ts_node_is_null(declarator) || !ts_node_has_error(declarator)) &&
(ts_node_is_null(type) || !ts_node_has_error(type)))
csp_free_function(ctx, node, owner);
if (csp_is(node, "function_definition"))
return;
}
if (csp_is(node, "class_specifier") || csp_is(node, "struct_specifier")) {
csp_class(ctx, node, owner);
if (csp_is(node, "class_specifier") || csp_is(node, "struct_specifier") ||
csp_is(node, "union_specifier")) {
csp_class(ctx, node, owner, NULL);
return;
}
if (csp_is(node, "namespace_definition")) {
@@ -361,17 +753,17 @@ static void csp_walk(csp_context_t *ctx, TSNode node, const char *owner, unsigne
if (!csp_is(node, "translation_unit") && !csp_is(node, "declaration_list") &&
!csp_is(node, "declaration"))
return;
TSTreeCursor cursor = ts_tree_cursor_new(node);
if (ts_tree_cursor_goto_first_child(&cursor))
do {
TSNode child = ts_tree_cursor_current_node(&cursor);
if (ts_node_is_named(child))
csp_walk(ctx, child, owner, depth + 1);
} while (ts_tree_cursor_goto_next_sibling(&cursor));
ts_tree_cursor_delete(&cursor);
csp_walk_children(ctx, node, owner, depth);
}
bool cbm_c_collect_source_facts(CBMArena *arena, const char *source, int length, const char *module,
CBMLSPDef *defs, int count) {
return cbm_c_collect_file_facts(arena, source, length, module, CBM_LANG_CPP, defs, count, NULL);
}
bool cbm_c_collect_file_facts(CBMArena *arena, const char *source, int length, const char *module,
CBMLanguage language, CBMLSPDef *defs, int count,
CBMCFileFacts *facts) {
if (count < 0 || (count && !defs))
return false;
for (int i = 0; i < count; i++) {
@@ -381,11 +773,14 @@ bool cbm_c_collect_source_facts(CBMArena *arena, const char *source, int length,
if (!arena || !source || length <= 0 || !module)
return false;
extern const TSLanguage *tree_sitter_cpp(void);
extern const TSLanguage *tree_sitter_c(void);
TSParser *parser = ts_parser_new();
if (!parser)
return false;
ts_parser_set_language(parser, tree_sitter_cpp());
TSTree *tree = ts_parser_parse_string(parser, NULL, source, (uint32_t)length);
/* C source can name a variable `new` or `class`, and the C++ grammar then
* loses the declaration it sits in. */
ts_parser_set_language(parser, language == CBM_LANG_C ? tree_sitter_c() : tree_sitter_cpp());
TSTree *tree = cbm_parse_c_source(parser, source, (uint32_t)length, (TSParseOptions){0});
ts_parser_delete(parser);
if (!tree)
return false;
@@ -393,14 +788,23 @@ bool cbm_c_collect_source_facts(CBMArena *arena, const char *source, int length,
.source = source,
.length = (uint32_t)length,
.module = module,
.facts = facts,
.c_language = language == CBM_LANG_C,
.definitions = cbm_ht_create(32)};
ctx.failed = !ctx.definitions || ts_node_has_error(ts_tree_root_node(tree));
ctx.preprocessor_unknown = cbm_c_preprocessor_domain_unsupported(ts_tree_root_node(tree));
ctx.failed = !ctx.definitions;
ctx.preprocessor_unknown =
cbm_c_preprocessor_domain_unsupported(ts_tree_root_node(tree), source);
ctx.file_extern_c = strstr(source, "__cplusplus") && strstr(source, "extern \"C\"");
if (facts && !facts->module_qn)
facts->module_qn = module;
for (int i = 0; !ctx.failed && i < count; i++) {
CBMLSPDef *def = &defs[i];
const char *qn =
def->graph_qualified_name ? def->graph_qualified_name : def->qualified_name;
if (!qn || !def->source_start_line || !def->short_name)
/* `static struct config {...} config;` holds a Class and a Variable of one name
* on one line, and only the Class has source facts to receive. */
if (!qn || !def->source_start_line || !def->short_name ||
(def->label && !strcmp(def->label, "Variable")))
continue;
size_t base = cbm_qn_callable_base_len_named(qn, def->short_name);
const char *key =
@@ -420,8 +824,16 @@ bool cbm_c_collect_source_facts(CBMArena *arena, const char *source, int length,
if (cbm_ht_get(ctx.definitions, key) != entry)
ctx.failed = true;
}
if (!ctx.failed)
csp_walk(&ctx, ts_tree_root_node(tree), module, 0);
if (!ctx.failed) {
TSNode root = ts_tree_root_node(tree);
TSNode guard = csp_include_guard(&ctx, root);
if (facts && !ts_node_is_null(guard))
facts->guard = csp_text(&ctx, csp_field(guard, "name"));
if (ts_node_is_null(guard))
csp_walk(&ctx, root, module, 0);
else
csp_walk_children(&ctx, guard, module, 0);
}
if (ctx.failed)
for (int i = 0; i < count; i++)
defs[i].source_flags = 0;
@@ -429,3 +841,135 @@ bool cbm_c_collect_source_facts(CBMArena *arena, const char *source, int length,
ts_tree_delete(tree);
return !ctx.failed;
}
static bool csp_append_file(CBMArena *arena, const char ***items, int *count, int *cap,
const char *file) {
if (*count == *cap) {
int next_cap = *cap ? *cap * 2 : 8;
const char **next = cbm_arena_alloc(arena, (size_t)next_cap * sizeof(*next));
if (!next)
return false;
if (*count)
memcpy(next, *items, (size_t)*count * sizeof(*next));
*items = next;
*cap = next_cap;
}
(*items)[(*count)++] = file;
return true;
}
bool cbm_c_facts_add_include(CBMArena *arena, CBMCFileFacts *facts, const char *file) {
return csp_append_file(arena, &facts->includes, &facts->include_count, &facts->include_cap,
file);
}
bool cbm_c_facts_add_method(CBMArena *arena, CBMCFileFacts *facts, const char *name,
const char *target, const char *owner, int arity) {
csp_context_t ctx = {.arena = arena, .facts = facts, .module = facts->module_qn};
CBMCDecl *fact = csp_add_decl(&ctx, CBM_C_DECL_METHOD, name, 0);
if (!fact)
return false;
fact->target = target;
fact->type_name = owner;
fact->arity = arity;
return true;
}
bool cbm_c_facts_add_generated(CBMArena *arena, CBMCFileFacts *facts, const char *file) {
return csp_append_file(arena, &facts->generated_for, &facts->generated_count,
&facts->generated_cap, file);
}
typedef struct CDeclFileEntry {
const CBMCFileFacts *facts;
struct CDeclFileEntry *next;
} CDeclFileEntry;
struct CBMCDeclIndex {
CBMCDeclLink **names;
CDeclFileEntry **files;
uint32_t name_mask, file_mask;
};
static uint32_t csp_hash(const char *text) {
uint32_t hash = 2166136261u;
for (const unsigned char *p = (const unsigned char *)text; *p; p++)
hash = (hash ^ *p) * 16777619u;
return hash;
}
static uint32_t csp_bucket_mask(size_t items) {
uint32_t size = 16;
while (size < items * 2 && size < (1u << 28))
size <<= 1;
return size - 1;
}
static const char *csp_file_key(const CBMCFileFacts *facts) {
return facts->file_qn ? facts->file_qn : facts->module_qn;
}
const CBMCDeclIndex *cbm_c_decl_index_build(CBMArena *arena, const CBMLSPDef *defs, int count) {
size_t files = 0, decls = 0;
for (int i = 0; i < count; i++) {
if (!defs[i].c_file_facts)
continue;
files++;
decls += (size_t)defs[i].c_file_facts->decl_count;
}
if (!files)
return NULL;
CBMCDeclIndex *index = cbm_arena_calloc(arena, sizeof(*index));
if (!index)
return NULL;
index->name_mask = csp_bucket_mask(decls);
index->file_mask = csp_bucket_mask(files);
index->names = cbm_arena_calloc(arena, (size_t)(index->name_mask + 1) * sizeof(*index->names));
index->files = cbm_arena_calloc(arena, (size_t)(index->file_mask + 1) * sizeof(*index->files));
if (!index->names || !index->files)
return NULL;
for (int i = 0; i < count; i++) {
const CBMCFileFacts *facts = defs[i].c_file_facts;
if (!facts || !facts->module_qn)
continue;
CDeclFileEntry *entry = cbm_arena_alloc(arena, sizeof(*entry));
if (!entry)
return NULL;
uint32_t slot = csp_hash(csp_file_key(facts)) & index->file_mask;
*entry = (CDeclFileEntry){.facts = facts, .next = index->files[slot]};
index->files[slot] = entry;
for (int d = 0; d < facts->decl_count; d++) {
const CBMCDecl *decl = &facts->decls[d];
uint32_t bucket = csp_hash(decl->name) & index->name_mask;
CBMCDeclLink *link = cbm_arena_alloc(arena, sizeof(*link));
if (!link)
return NULL;
*link = (CBMCDeclLink){.decl = decl, .next = index->names[bucket]};
index->names[bucket] = link;
/* `ns.f` is also reachable by the name a call writes. */
const char *leaf = decl->kind == CBM_C_DECL_PROTOTYPE ? strrchr(decl->name, '.') : NULL;
if (leaf) {
bucket = csp_hash(leaf + 1) & index->name_mask;
link = cbm_arena_alloc(arena, sizeof(*link));
if (!link)
return NULL;
*link = (CBMCDeclLink){.decl = decl, .next = index->names[bucket]};
index->names[bucket] = link;
}
}
}
return index;
}
const CBMCDeclLink *cbm_c_decl_lookup(const CBMCDeclIndex *index, const char *name) {
return index && name ? index->names[csp_hash(name) & index->name_mask] : NULL;
}
const CBMCFileFacts *cbm_c_file_facts(const CBMCDeclIndex *index, const char *file) {
if (!index || !file)
return NULL;
for (const CDeclFileEntry *e = index->files[csp_hash(file) & index->file_mask]; e; e = e->next)
if (!strcmp(csp_file_key(e->facts), file))
return e->facts;
return NULL;
}
+73
View File
@@ -8,4 +8,77 @@
bool cbm_c_collect_source_facts(CBMArena *arena, const char *source, int length, const char *module,
CBMLSPDef *defs, int count);
/* What a file contributes to the translation units that include it, beyond its
* graph definitions. A prototype, an extern declaration or a macro has no graph
* node, yet a file's declarations decide what a name means in every includer.
* These are source facts: nothing here selects an edge target. */
typedef enum {
CBM_C_DECL_PROTOTYPE = 0, /* a function declared without a body */
CBM_C_DECL_MACRO, /* a #define of this name, in any branch */
CBM_C_DECL_EXTERN_VALUE, /* an extern object declaration */
CBM_C_DECL_VALUE, /* a graph Variable: object definition or enumerator */
CBM_C_DECL_METHOD, /* an out-of-line or inline member definition */
CBM_C_DECL_MEMBER_NAME, /* any name a class body declares; type_name is the class */
} CBMCDeclKind;
enum {
CBM_C_DECL_EXTERNAL = 1u << 0, /* external linkage: not static, not an enumerator */
CBM_C_DECL_C_LINKAGE = 1u << 1, /* written under extern "C" */
CBM_C_DECL_CONDITIONAL = 1u << 2, /* inside a preprocessor branch */
CBM_C_DECL_NOT_PUBLIC = 1u << 3, /* a class member under private: or protected: */
CBM_C_DECL_PROTECTED = 1u << 4, /* with NOT_PUBLIC: reachable from a derived class */
};
typedef struct CBMCDecl {
const char *file; /* the declaring file's identity (see CBMCFileFacts) */
const char *name; /* namespace-relative name, dotted */
const char *target; /* VALUE: exact graph QN. Otherwise NULL. */
const char *type_name; /* VALUE, EXTERN_VALUE: written base type. METHOD: owner's name */
const CBMType *signature; /* PROTOTYPE: builtin-only audited signature, else NULL */
int arity; /* PROTOTYPE: fixed parameter count, -1 when not fixed */
uint32_t flags;
CBMCDeclKind kind;
} CBMCDecl;
typedef struct CBMCDeclLink {
const CBMCDecl *decl;
struct CBMCDeclLink *next;
} CBMCDeclLink;
typedef struct CBMCFileFacts {
/* A file's identity is its File QN. A module QN will not do: `sds.h` and `sds.c`
* share one, and an includer of the header does not see the source's statics. */
const char *file_qn;
const char *module_qn;
const char *guard; /* whole-file include guard macro, or NULL */
bool plain_header; /* a .h: included by C and C++ alike, so linkage is the includer's */
bool cpp_syntax; /* a namespace, class or template: this .h is C++ whatever its name */
CBMCDecl *decls;
int decl_count, decl_cap;
const char **includes; /* proven direct includes, as file identities */
int include_count, include_cap;
const char **generated_for; /* source files declared through a generated header */
int generated_count, generated_cap;
} CBMCFileFacts;
/* As cbm_c_collect_source_facts, also filling facts when it is not NULL. */
bool cbm_c_collect_file_facts(CBMArena *arena, const char *source, int length, const char *module,
CBMLanguage language, CBMLSPDef *defs, int count,
CBMCFileFacts *facts);
/* Each adds to facts, copying nothing: strings must outlive the facts. */
bool cbm_c_facts_add_include(CBMArena *arena, CBMCFileFacts *facts, const char *file);
bool cbm_c_facts_add_generated(CBMArena *arena, CBMCFileFacts *facts, const char *file);
/* A member definition: `size_t StringRef::find(char C) const { ... }` has its class
* in another file, so the class alone cannot reach it. */
bool cbm_c_facts_add_method(CBMArena *arena, CBMCFileFacts *facts, const char *name,
const char *target, const char *owner, int arity);
/* Immutable index over every file's facts. Built with the shared registry. */
typedef struct CBMCDeclIndex CBMCDeclIndex;
const CBMCDeclIndex *cbm_c_decl_index_build(CBMArena *arena, const CBMLSPDef *defs, int count);
/* The bucket holding this name, which may also hold others: compare ->decl->name. */
const CBMCDeclLink *cbm_c_decl_lookup(const CBMCDeclIndex *index, const char *name);
const CBMCFileFacts *cbm_c_file_facts(const CBMCDeclIndex *index, const char *file);
#endif
+6
View File
@@ -125,12 +125,18 @@ typedef struct {
uint32_t source_flags;
const CBMType *source_return_type; /* Parsed, source-audited annotation only. */
const CBMType *source_signature; /* Audited syntax; type names may be unresolved. */
int source_arity; /* C family: fixed parameter count + 1; 0 when not fixed. */
/* Go only (#2033): raw aliased type text of `type A = B` ("B"), qualified
* by the registrar against def_module_qn like return_types. NULL = not an
* identity alias. */
const char *alias_target;
/* C family: one record per file, label CBM_C_FILE_FACTS_LABEL, carrying what
* the file declares for its includers. Registrars ignore the label. */
const struct CBMCFileFacts *c_file_facts;
} CBMLSPDef;
#define CBM_C_FILE_FACTS_LABEL "CFileFacts"
bool cbm_go_collect_source_facts(CBMArena *, const char *, int, const char *, CBMLSPDef *, int);
// Parse source, build registry from defs + stdlib, run LSP.
+12 -1
View File
@@ -48,7 +48,12 @@ enum {
CBM_SOURCE_DECL_EXTERN_C = 1u << 12,
/* Audited fixed parameters with no type, reference, default or unpacking.
* PHP may apply them to exact positional values without coercion rules. */
CBM_SOURCE_DECL_UNTYPED_VALUE_PARAMETERS = 1u << 13
CBM_SOURCE_DECL_UNTYPED_VALUE_PARAMETERS = 1u << 13,
/* C family: inside a preprocessor branch, so it exists in some configurations. */
CBM_SOURCE_DECL_CONDITIONAL = 1u << 14,
/* Every name the class body declares is recorded. Unlike DOMAIN_COMPLETE this says
* nothing about base classes, which a lookup searches itself. */
CBM_SOURCE_CLASS_BODY_COMPLETE = 1u << 15
};
// Registered function/method with full type signature.
@@ -80,6 +85,8 @@ typedef struct {
* Unqualified source names retain their spelling until the language
* adapter proves the declaration's type-name lookup domain. */
const CBMType *source_signature;
int source_arity; /* C family: fixed parameter count + 1; 0 when not fixed. */
const char *source_file_qn; /* C family: the File that holds this declaration. */
const char *source_namespace;
} CBMRegisteredFunc;
@@ -116,6 +123,7 @@ typedef struct {
bool export_binding_proven;
const char *declaration_identity;
const char *graph_qualified_name; /* original canonical source graph identity, if different */
const char *source_file_qn; /* C family: the File that holds this declaration */
uint32_t source_flags;
} CBMRegisteredType;
@@ -250,6 +258,9 @@ typedef struct CBMTypeRegistry {
const char **export_keys;
const char **export_targets;
int export_count;
/* C family: what each file declares for its includers (see c_proof.h). */
const struct CBMCDeclIndex *c_decls;
} CBMTypeRegistry;
/* Read-only iterator over every exact namespace/name candidate in every
+11 -4
View File
@@ -25,11 +25,17 @@ static const edge_row_t edge_rows[] = {
"Exact project package paths from the source's module, its go.mod replace directives and "
"the enclosing go.work; every other package abstains."},
{CBM_LANG_C, C_EDGES,
"Literal include paths and configured search roots only; unproved preprocessing abstains."},
"Literal include paths, configured search roots, and a spelling only one project "
"file can satisfy; translation-unit visibility through includes; unproved "
"preprocessing abstains."},
{CBM_LANG_CPP, C_EDGES,
"Literal include paths and configured search roots only; unproved preprocessing abstains."},
"Literal include paths, configured search roots, and a spelling only one project "
"file can satisfy; translation-unit visibility through includes; unproved "
"preprocessing abstains."},
{CBM_LANG_CUDA, C_EDGES,
"Literal include paths and configured search roots only; unproved preprocessing abstains."},
"Literal include paths, configured search roots, and a spelling only one project "
"file can satisfy; translation-unit visibility through includes; unproved "
"preprocessing abstains."},
{CBM_LANG_OBJC, EDGE(IMPORTS),
"Literal include paths and configured search roots only; no semantic proof bridge "
"advertised."},
@@ -248,7 +254,8 @@ bool cbm_resolve_capabilities(CBMLanguage language, cbm_resolve_capabilities_t *
out->proof_subsets = CBM_RESOLVE_CAP_SCOPE | CBM_RESOLVE_CAP_IMPORT_EXPORT |
CBM_RESOLVE_CAP_DECLARED_TYPE | CBM_RESOLVE_CAP_MEMBER |
CBM_RESOLVE_CAP_TYPE_FLOW | CBM_RESOLVE_CAP_CALLABLE;
out->limitation = "Exact visibility and owner proofs; unsupported overload, template, ADL "
out->limitation = "Exact visibility and owner proofs; overloads of one parameter count "
"with separate nodes, templates, ADL with a second visible function "
"and callable-field selection abstain.";
break;
case CBM_LANG_JAVASCRIPT:
+6
View File
@@ -72,6 +72,12 @@ int cbm_pipeline_rust_import_map(const cbm_gbuf_t *, const char *, const char *,
const CBMFileResult *, const char ***, const char ***, int *);
/* Exact source include path -> declaration namespace, from the admission
* snapshot; dependency File QNs keep their separate extension-bearing identity. */
/* The File QN of the project source file a generated declaration header is made
* from (`buffer.h.generated.h`, written by the build from `buffer.c`), or NULL.
* The source file sits beside the includer or is the one project file of that
* spelling, as for any include. Caller frees. */
char *cbm_pipeline_c_generated_source(const cbm_pipeline_ctx_t *ctx, const char *includer,
const char *spelling);
int cbm_pipeline_c_include_map(const cbm_gbuf_t *, const char *, const char *,
const CBMFileResult *, const char ***, const char ***, int *);
#endif
+80 -3
View File
@@ -671,7 +671,9 @@ static int pxc_build_lsp_def(CBMArena *arena, const CBMDefinition *src, const ch
}
}
dst->lang = lang;
dst->alias_target = (lang == CBM_LANG_GO && src->alias_target)
dst->alias_target = ((lang == CBM_LANG_GO || lang == CBM_LANG_C || lang == CBM_LANG_CPP ||
lang == CBM_LANG_CUDA) &&
src->alias_target)
? cbm_arena_strdup(arena, src->alias_target)
: NULL;
dst->decorators = NULL;
@@ -1131,6 +1133,75 @@ static int pxc_build_py_namespace_records(CBMArena *arena, const CBMFileResult *
return written;
}
static bool pxc_is_c_family(CBMLanguage language) {
return language == CBM_LANG_C || language == CBM_LANG_CPP || language == CBM_LANG_CUDA;
}
/* Source facts for one C-family file's definitions, and one more record: what the
* file declares for its includers, and which project files it includes. Those
* includes are the proven ones, so an include this index cannot place stops the
* chain there rather than guessing where it leads. */
static void pxc_collect_c_file(const cbm_pipeline_ctx_t *ctx, CBMArena *arena, const char *source,
int source_length, CBMFileResult *fr, const cbm_file_info_t *file,
CBMLSPDef *defs, int *idx, int room, int file_start) {
int count = *idx - file_start;
CBMCFileFacts *facts = ctx ? cbm_arena_calloc(arena, sizeof(*facts)) : NULL;
const char *module = cbm_arena_strdup(arena, fr->module_qn);
if (facts) {
size_t path_len = strlen(file->rel_path);
facts->plain_header = path_len > 2 && !strcmp(file->rel_path + path_len - 2, ".h");
facts->module_qn = module;
char *file_qn = cbm_pipeline_fqn_compute(ctx->project_name, file->rel_path, "__file__");
facts->file_qn = file_qn ? cbm_arena_strdup(arena, file_qn) : NULL;
free(file_qn);
}
(void)cbm_c_collect_file_facts(arena, source, source_length, module, file->language, defs,
count, facts);
if (!facts || count >= room)
return;
for (int i = 0; i < count; i++) {
const CBMLSPDef *def = &defs[i];
const char *target = def->graph_qualified_name;
if (!def->label || strcmp(def->label, "Method") || !def->short_name || !target)
continue;
/* The name written before the member: `K` of `M.ns.K.m`. */
size_t length = strlen(target), leaf = strlen(def->short_name);
const char *owner = NULL;
if (length > leaf + 1 && target[length - leaf - 1] == '.') {
size_t end = length - leaf - 1, start = end;
while (start > 0 && target[start - 1] != '.')
start--;
owner = cbm_arena_strndup(arena, target + start, end - start);
}
(void)cbm_c_facts_add_method(arena, facts, def->short_name, target, owner,
def->signature_param_types ? def->signature_param_count : -1);
}
const cbm_resolve_import_snapshot_t *snapshot =
cbm_pipeline_import_snapshot(ctx->gbuf, ctx->project_name, file->rel_path, fr);
for (size_t i = 0; snapshot && i < snapshot->count; i++) {
const cbm_resolve_import_fact_t *fact = &snapshot->items[i];
if (cbm_resolve_admit(&fact->dependency) && cbm_resolve_admit(&fact->binding) &&
fact->dependency.target) {
(void)cbm_c_facts_add_include(arena, facts,
cbm_arena_strdup(arena, fact->dependency.target));
continue;
}
char *generated = cbm_pipeline_c_generated_source(ctx, file->rel_path, fact->module_path);
if (generated)
(void)cbm_c_facts_add_generated(arena, facts, cbm_arena_strdup(arena, generated));
free(generated);
}
CBMLSPDef *record = &defs[count];
memset(record, 0, sizeof(*record));
record->qualified_name = cbm_arena_sprintf(arena, "%s.@c-file-facts", module);
record->short_name = "@c-file-facts";
record->label = CBM_C_FILE_FACTS_LABEL;
record->def_module_qn = module;
record->lang = file->language;
record->c_file_facts = facts;
(*idx)++;
}
/* Collect a project-wide CBMLSPDef[] from all cached results. Returns a
* malloc'd array (caller frees) of length *out_count. String fields are
* borrowed from cache[i]->arena and from def_modules[i] (also borrowed).
@@ -1163,6 +1234,9 @@ CBMLSPDef *cbm_pxc_collect_all_defs(const cbm_pipeline_ctx_t *ctx, CBMArena *are
}
}
total += defs;
if (ctx && pxc_is_c_family(files[i].language)) {
total += 1; /* the file's declaration facts */
}
if (files[i].language == CBM_LANG_RUST) {
total += impls;
}
@@ -1305,8 +1379,8 @@ CBMLSPDef *cbm_pxc_collect_all_defs(const cbm_pipeline_ctx_t *ctx, CBMArena *are
}
if (files[fi].language == CBM_LANG_C || files[fi].language == CBM_LANG_CPP ||
files[fi].language == CBM_LANG_CUDA)
(void)cbm_c_collect_source_facts(arena, source, source_length, fr->module_qn,
defs + file_start, idx - file_start);
pxc_collect_c_file(ctx, arena, source, source_length, fr, &files[fi],
defs + file_start, &idx, capacity - file_start, file_start);
else if (files[fi].language == CBM_LANG_GO)
(void)cbm_go_collect_source_facts(arena, source, source_length, def_modules[fi],
defs + file_start, idx - file_start);
@@ -1955,6 +2029,9 @@ static void pxc_append_results(CBMArena *dst_arena, CBMResolvedCallArray *dst_ca
if (!dst_calls || !src_out)
return;
/* Every proof of this file comes through here. Its size follows the file, and proof
* work counts hashed bytes, so a cap silently cost a large file its last rows, at a
* point that moved with the length of the checkout path. */
cbm_resolve_proof_pool_t proof_pool;
cbm_resolve_proof_pool_init_project(&proof_pool, dst_arena);
cbm_resolve_proof_clone_session_t proofs;
+7 -1
View File
@@ -4490,12 +4490,18 @@ static void resolve_worker_impl(int worker_id, void *ctx_ptr) {
int qualified_lsp_sites = pp_qualified_lsp_site_count(result);
bool pending_lsp_site = pp_has_pending_lsp_site(result);
bool unowned_c_member = pp_has_unowned_c_member(result, lang, rel);
/* C and C++ values, fields and written types bind in the translation unit as
* well, so a file with no call left to resolve can still hold those sites. The
* sequential route has no such filter and the two routes must agree. */
bool c_family_sites =
(lang == CBM_LANG_C || lang == CBM_LANG_CPP || lang == CBM_LANG_CUDA) &&
(result->usages.count > 0 || result->rw.count > 0 || result->type_refs.count > 0);
bool cross_lsp_eligible =
(rc->all_defs && rc->def_count > 0 && cbm_pxc_has_cross_lsp(lang) &&
(((semantic_sites > 0 || pending_lsp_site) &&
(jvm_cross_lsp || rust_workspace_cross_lsp || pending_lsp_site ||
qualified_lsp_sites < semantic_sites)) ||
unowned_c_member) &&
unowned_c_member || c_family_sites) &&
!is_generated) ||
result->has_test_definition_owners;
+275 -4
View File
@@ -2306,9 +2306,225 @@ static const cbm_gbuf_node_t *include_in_directory(const cbm_pipeline_ctx_t *ctx
return cbm_pipeline_file_node(ctx->gbuf, ctx->project_name, canonical + size + 1);
}
/* Headers the toolchain supplies. A project file spelled the same way is a
* compat shim or a vendored copy, and nothing here says the compiler would
* choose it over the toolchain's own, so these spellings never infer a target.
* Spellings under a system directory are matched by prefix in the caller. */
static bool include_toolchain_spelling(const char *spelling) {
static const char *const names[] = {
"algorithm",
"alloca.h",
"any",
"arpa/inet.h",
"array",
"assert.h",
"atomic",
"bit",
"bitset",
"cassert",
"cctype",
"cerrno",
"cfloat",
"charconv",
"chrono",
"cinttypes",
"climits",
"clocale",
"cmath",
"codecvt",
"compare",
"complex",
"complex.h",
"concepts",
"condition_variable",
"coroutine",
"csetjmp",
"csignal",
"cstdarg",
"cstddef",
"cstdint",
"cstdio",
"cstdlib",
"cstring",
"ctime",
"ctype.h",
"cuchar",
"cwchar",
"cwctype",
"deque",
"dirent.h",
"dlfcn.h",
"errno.h",
"exception",
"execinfo.h",
"execution",
"expected",
"fcntl.h",
"fenv.h",
"filesystem",
"float.h",
"fnmatch.h",
"format",
"forward_list",
"fstream",
"functional",
"future",
"getopt.h",
"glob.h",
"grp.h",
"initializer_list",
"inttypes.h",
"iomanip",
"ios",
"iosfwd",
"iostream",
"iso646.h",
"istream",
"iterator",
"langinfo.h",
"latch",
"libgen.h",
"limits",
"limits.h",
"list",
"locale",
"locale.h",
"malloc.h",
"map",
"math.h",
"memory",
"memory_resource",
"mutex",
"net/if.h",
"netdb.h",
"netinet/in.h",
"netinet/tcp.h",
"new",
"numbers",
"numeric",
"optional",
"ostream",
"paths.h",
"poll.h",
"pthread.h",
"pwd.h",
"queue",
"random",
"ranges",
"ratio",
"regex",
"regex.h",
"resolv.h",
"sched.h",
"scoped_allocator",
"search.h",
"semaphore",
"semaphore.h",
"set",
"setjmp.h",
"shared_mutex",
"signal.h",
"source_location",
"span",
"spawn.h",
"sstream",
"stack",
"stdalign.h",
"stdarg.h",
"stdatomic.h",
"stdbool.h",
"stddef.h",
"stdexcept",
"stdint.h",
"stdio.h",
"stdlib.h",
"stdnoreturn.h",
"stop_token",
"streambuf",
"string",
"string.h",
"string_view",
"strings.h",
"syncstream",
"syslog.h",
"system_error",
"termios.h",
"tgmath.h",
"thread",
"threads.h",
"time.h",
"tuple",
"type_traits",
"typeindex",
"typeinfo",
"uchar.h",
"unistd.h",
"unordered_map",
"unordered_set",
"utility",
"utime.h",
"valarray",
"variant",
"vector",
"version",
"wchar.h",
"wctype.h",
"windows.h",
"winsock2.h",
"ws2tcpip.h",
};
/* Autoconf's output, under the build tree of whichever component is compiling.
* Another component's own config.h is the classic false match for it. */
if (!strcmp(spelling, "config.h"))
return true;
if (!strncmp(spelling, "sys/", 4) || !strncmp(spelling, "bits/", 5))
return true;
for (size_t i = 0; i < sizeof(names) / sizeof(names[0]); i++)
if (!strcmp(spelling, names[i]))
return true;
return false;
}
/* With no build configuration for this file, the include's whole spelling is
* the remaining evidence: exactly one project file must end in that path, at a
* path-component boundary. That file is what a -I at its prefix selects, and no
* other project file could be selected by any -I. Several such files is a real
* collision, reported as ambiguous and never settled by nearness. */
static const cbm_gbuf_node_t *resolve_inferred_include(const cbm_pipeline_ctx_t *ctx,
const CBMImport *imp, CBMHashTable *map,
bool *ambiguous, const char **reason) {
const char *spelling = imp->module_path;
*reason = "include_search_roots_unknown";
if (!map || !spelling[0] || spelling[0] == '/' || strchr(spelling, '\\') ||
strstr(spelling, "//") || !strncmp(spelling, "./", 2) || !strncmp(spelling, "../", 3) ||
strstr(spelling, "/../") || strstr(spelling, "/./"))
return NULL;
if (include_toolchain_spelling(spelling)) {
*reason = "include_toolchain_header";
return NULL;
}
char key[PKGMAP_PATH_BUF];
int length = snprintf(key, sizeof(key), "@cbm:incpath:%s", spelling);
if (length <= 0 || (size_t)length >= sizeof(key))
return NULL;
const char *rel = cbm_ht_get(map, key);
if (!rel) {
*reason = "include_not_in_project";
return NULL;
}
if (!strcmp(rel, ambiguous_pkgmap_value)) {
*ambiguous = true;
*reason = "include_path_ambiguous";
return NULL;
}
*reason = "include_unique_project_path";
return cbm_pipeline_file_node(ctx->gbuf, ctx->project_name, rel);
}
static const cbm_gbuf_node_t *resolve_configured_include(const cbm_pipeline_ctx_t *ctx,
const char *source_rel,
const CBMImport *imp, CBMHashTable *map) {
const CBMImport *imp, CBMHashTable *map,
bool *ambiguous, const char **reason) {
if (!source_rel || (imp->kind != CBM_IMPORT_KIND_INCLUDE_QUOTED &&
imp->kind != CBM_IMPORT_KIND_INCLUDE_SYSTEM))
return NULL;
@@ -2338,7 +2554,12 @@ static const cbm_gbuf_node_t *resolve_configured_include(const cbm_pipeline_ctx_
int length = snprintf(key, sizeof(key), "@cbm:cc:%s", source_rel);
const char *roots =
map && length > 0 && (size_t)length < sizeof(key) ? cbm_ht_get(map, key) : NULL;
if (!roots || strcmp(roots, ambiguous_pkgmap_value) == 0)
/* A compile entry is authoritative for this file, including when it names
* no root that holds the header: nothing is inferred past configuration. */
if (!roots)
return resolve_inferred_include(ctx, imp, map, ambiguous, reason);
*reason = "configured_include_root";
if (strcmp(roots, ambiguous_pkgmap_value) == 0)
return NULL;
for (const char *p = roots; p && p[0];) {
const char *end = strchr(p, '\n');
@@ -2641,9 +2862,32 @@ static bool rust_project_build(cbm_pipeline_ctx_t *ctx, const cbm_file_info_t *f
return true;
}
/* Every component-boundary suffix of every C-family file path, for the inferred
* include rule. A suffix shared by two files becomes the ambiguous marker. */
static bool build_include_path_context(const cbm_file_info_t *files, int count, CBMHashTable *map) {
for (int i = 0; i < count; i++) {
CBMLanguage language = files[i].language;
if ((language != CBM_LANG_C && language != CBM_LANG_CPP && language != CBM_LANG_CUDA &&
language != CBM_LANG_OBJC) ||
!files[i].rel_path)
continue;
for (const char *suffix = files[i].rel_path; suffix;) {
char key[PKGMAP_PATH_BUF];
int length = snprintf(key, sizeof(key), "@cbm:incpath:%s", suffix);
if (length <= 0 || (size_t)length >= sizeof(key) ||
!import_fact_put(map, key, files[i].rel_path))
return false;
const char *slash = strchr(suffix, '/');
suffix = slash ? slash + 1 : NULL;
}
}
return true;
}
static bool import_context_build(cbm_pipeline_ctx_t *ctx, const cbm_file_info_t *files, int count,
CBMFileResult **cache, CBMHashTable *map) {
if (!ctx || !map || !build_compile_import_context(ctx, map))
if (!ctx || !map || !build_compile_import_context(ctx, map) ||
!build_include_path_context(files, count, map))
return false;
return rust_project_build(ctx, files, count, cache);
}
@@ -4104,7 +4348,7 @@ static const cbm_gbuf_node_t *resolve_import_target(const cbm_pipeline_ctx_t *ct
lang == CBM_LANG_OBJC) {
*reason = imp->kind == CBM_IMPORT_KIND_INCLUDE_QUOTED ? "quoted_include_path"
: "include_search_roots_unknown";
return resolve_configured_include(ctx, source_rel, imp, namespace_map);
return resolve_configured_include(ctx, source_rel, imp, namespace_map, ambiguous, reason);
}
if (lang == CBM_LANG_RUST) {
return resolve_rust_import(ctx, source_rel, imp, ambiguous, NULL, reason);
@@ -5683,6 +5927,33 @@ int cbm_pipeline_rust_import_map(const cbm_gbuf_t *graph, const char *project,
out_count);
}
char *cbm_pipeline_c_generated_source(const cbm_pipeline_ctx_t *ctx, const char *includer,
const char *spelling) {
static const char suffix[] = ".h.generated.h";
size_t length = spelling ? strlen(spelling) : 0, suffix_len = sizeof(suffix) - 1;
CBMHashTable *map = cbm_pipeline_get_nsmap();
if (!ctx || !map || !includer || length <= suffix_len ||
strcmp(spelling + length - suffix_len, suffix))
return NULL;
char stem[PKGMAP_PATH_BUF], beside[PKGMAP_PATH_BUF], key[PKGMAP_PATH_BUF];
int written = snprintf(stem, sizeof(stem), "%.*s.c", (int)(length - suffix_len), spelling);
if (written <= 0 || (size_t)written >= sizeof(stem))
return NULL;
char *directory = path_dirname(includer);
bool near = directory && cbm_repo_rel_join(directory, stem, beside, sizeof(beside)) &&
cbm_pipeline_file_node(ctx->gbuf, ctx->project_name, beside);
cbm_free(CBM_MEM_CLASS_SEMANTIC, directory);
if (near)
return cbm_pipeline_fqn_compute(ctx->project_name, beside, "__file__");
written = snprintf(key, sizeof(key), "@cbm:incpath:%s", stem);
if (written <= 0 || (size_t)written >= sizeof(key))
return NULL;
const char *rel = cbm_ht_get(map, key);
if (!rel || !strcmp(rel, ambiguous_pkgmap_value))
return NULL;
return cbm_pipeline_fqn_compute(ctx->project_name, rel, "__file__");
}
int cbm_pipeline_c_include_map(const cbm_gbuf_t *graph, const char *project, const char *source_rel,
const CBMFileResult *result, const char ***out_keys,
const char ***out_values, int *out_count) {
+23 -2
View File
@@ -100,6 +100,10 @@ typedef struct {
/* cfg twins and one type's repeated impl methods are one graph identity;
* the Rust adapter separately rejects a QN two scopes of a file claim. */
bool rust;
/* A C file's #if branches define one name more than once. The graph holds one
* node for them, with each branch's span as a variant, so an edge to or from
* that node is true of the configurations it was found in. */
bool c_variants;
} target_proof_t;
typedef struct {
@@ -394,6 +398,11 @@ static bool add_target(cbm_shared_resolver_t *resolver, const CBMDefinition *def
bool same_span = prior->start_line == definition->start_line &&
prior->end_line == definition->end_line;
prior->cross_file |= strcmp(prior->file, file) != 0;
if (same_owner &&
(language == CBM_LANG_C || language == CBM_LANG_CPP || language == CBM_LANG_CUDA)) {
prior->c_variants = true;
return true;
}
if (same_owner && !same_span && cbm_oo_language(language) &&
(strcmp(definition->label, "Class") != 0 || language == CBM_LANG_CSHARP))
prior->overload_set = true;
@@ -568,8 +577,9 @@ static bool target_materialized(const file_resolver_t *resolver, const cbm_gbuf_
return true;
if (strcmp(proof->label, node->label))
return false;
return proof->overload_set || (proof->start_line == (uint32_t)node->start_line &&
proof->end_line == (uint32_t)node->end_line);
return proof->overload_set || proof->c_variants ||
(proof->start_line == (uint32_t)node->start_line &&
proof->end_line == (uint32_t)node->end_line);
}
/* JavaScript nests functions freely and extraction names them flat, so two
@@ -3695,6 +3705,11 @@ static cbm_resolve_result_t generic_result(file_resolver_t *resolver, const char
: proof;
}
}
/* The C adapter's semantic rows are the whole translation-unit lookup. With no
* row there is no second, name-based way to arrive at a target. */
if (file->language == CBM_LANG_C || file->language == CBM_LANG_CPP ||
file->language == CBM_LANG_CUDA)
return unresolved("c_translation_unit_binding_unproven");
if (file->language == CBM_LANG_DART) {
cbm_resolve_result_t proof = dart_name_result(resolver, name);
return cbm_resolve_binding_validate(resolver->bindings, name, start, end, origin, proof);
@@ -4203,6 +4218,12 @@ static bool resolve_writes(file_resolver_t *resolver, CBMArena *arena) {
const semantic_site_t *site =
find_semantic(resolver, rw->enclosing_func_qn, rw->site_start_byte, rw->site_end_byte,
rw->source_origin, CBM_RESOLVED_FIELD_REFERENCE);
if (!site && !rw->is_member_access &&
(resolver->file->language == CBM_LANG_C || resolver->file->language == CBM_LANG_CPP ||
resolver->file->language == CBM_LANG_CUDA))
site =
find_semantic(resolver, rw->enclosing_func_qn, rw->site_start_byte,
rw->site_end_byte, rw->source_origin, CBM_RESOLVED_VALUE_REFERENCE);
cbm_resolve_result_t outcome;
if (site)
outcome = semantic_result(resolver, site, arena);
+21 -8
View File
@@ -212,25 +212,25 @@ static const cv_golden_t cv_goldens[] = {
{"cv_c_preprocessor", "cv_c_preprocessor-2", "pick.c", "pick", "beta()", 0, "pick.c", "beta", 2,
"void beta(void) {}"},
{"cv_c_preprocessor", "cv_c_preprocessor-3", "pick.c", "caller", "pick()", 0, "pick.c", "pick",
4, "void pick(void) { alpha(); }"},
6, "void pick(void) { beta(); }"},
{"cv_cpp_elif", "cv_cpp_elif-1", "pick.cpp", "pick", "alpha()", 1, "pick.cpp", "alpha", 1,
"void alpha() {}"},
{"cv_cpp_elif", "cv_cpp_elif-2", "pick.cpp", "pick", "beta()", 1, "pick.cpp", "beta", 2,
"void beta() {}"},
{"cv_cpp_elif", "cv_cpp_elif-3", "pick.cpp", "caller", "pick()", 2, "pick.cpp", "pick", 4,
"void pick() { alpha(); }"},
{"cv_cpp_elif", "cv_cpp_elif-3", "pick.cpp", "caller", "pick()", 2, "pick.cpp", "pick", 6,
"void pick() { beta(); }"},
{"cv_cpp_class_members", "cv_cpp_class_members-1", "pick.cpp", "pick", "alpha()", 1, "pick.cpp",
"alpha", 2, " void alpha() {}"},
{"cv_cpp_class_members", "cv_cpp_class_members-2", "pick.cpp", "pick", "beta()", 1, "pick.cpp",
"beta", 3, " void beta() {}"},
{"cv_cpp_class_members", "cv_cpp_class_members-3", "pick.cpp", "caller", "pick()", 2,
"pick.cpp", "pick", 5, " void pick() { alpha(); }"},
"pick.cpp", "pick", 7, " void pick() { beta(); }"},
{"cv_cuda", "cv_cuda-1", "pick.cu", "pick", "alpha()", 1, "pick.cu", "alpha", 1,
"__device__ void alpha() {}"},
{"cv_cuda", "cv_cuda-2", "pick.cu", "pick", "beta()", 1, "pick.cu", "beta", 2,
"__device__ void beta() {}"},
{"cv_cuda", "cv_cuda-3", "pick.cu", "caller", "pick()", 2, "pick.cu", "pick", 4,
"__device__ void pick() { alpha(); }"},
{"cv_cuda", "cv_cuda-3", "pick.cu", "caller", "pick()", 2, "pick.cu", "pick", 6,
"__device__ void pick() { beta(); }"},
{"cv_csharp_class_members", "cv_csharp_class_members-1", "Picker.cs", "Pick", "Alpha()", 1,
"Picker.cs", "Alpha", 2, " static void Alpha() {}"},
{"cv_csharp_class_members", "cv_csharp_class_members-2", "Picker.cs", "Pick", "Beta()", 1,
@@ -370,6 +370,20 @@ static const cv_recovered_t cv_recovered[] = {
{"cv_rust_cfg_methods-1", 6, 6, " fn pick(&self) { alpha(); }", NULL},
{"cv_rust_cfg_methods-2", 8, 8, " fn pick(&self) { beta(); }", NULL},
{"cv_rust_cfg_methods-3", 0, 0, NULL, "Method"},
/* The translation-unit rules bind a function's body in either branch, and a caller to the
* one node both branches share. That node's stored span is the last branch. */
{"cv_c_preprocessor-1", 4, 4, "void pick(void) { alpha(); }", NULL},
{"cv_c_preprocessor-2", 6, 6, "void pick(void) { beta(); }", NULL},
{"cv_c_preprocessor-3", 0, 0, NULL, NULL},
{"cv_cpp_elif-1", 4, 4, "void pick() { alpha(); }", NULL},
{"cv_cpp_elif-2", 6, 6, "void pick() { beta(); }", NULL},
{"cv_cpp_elif-3", 0, 0, NULL, NULL},
{"cv_cpp_class_members-1", 5, 5, " void pick() { alpha(); }", "Method"},
{"cv_cpp_class_members-2", 7, 7, " void pick() { beta(); }", "Method"},
{"cv_cpp_class_members-3", 0, 0, NULL, "Method"},
{"cv_cuda-1", 4, 4, "__device__ void pick() { alpha(); }", NULL},
{"cv_cuda-2", 6, 6, "__device__ void pick() { beta(); }", NULL},
{"cv_cuda-3", 0, 0, NULL, NULL},
};
static bool cv_quality(cv_proj_t *p, const char *test, const cv_file_t *files, int count) {
@@ -447,8 +461,7 @@ static bool cv_quality(cv_proj_t *p, const char *test, const cv_file_t *files, i
.why =
recovered
? "This source-authored body call is recovered and must retain its exact "
"callable target; merged Python and Rust callers also require their "
"authored variant."
"callable target; merged callers also require their authored variant."
: g->target
? "The strict resolver has no admitted binding for these conditional or "
"build-selected declarations; each source relationship remains recall debt."
+122
View File
@@ -3266,6 +3266,123 @@ TEST(ei_strict_rust_ambiguous_declared_module_is_not_ranked) {
PASS();
}
/* The IMPORTS targets of one file, as a count and whether target_file is among them. */
static int ei_import_count(cbm_store_t *store, const char *project, const char *source_file,
const char *target_file) {
int64_t source = ei_node_id_for_file_label(store, project, source_file, "File");
int64_t expected = target_file ? ei_node_id_for_file_label(store, project, target_file, "File")
: 0;
if (source <= 0 || (target_file && expected <= 0))
return -1;
cbm_edge_t *edges = NULL;
int count = 0;
if (cbm_store_find_edges_by_source_type(store, source, "IMPORTS", &edges, &count) !=
CBM_STORE_OK)
return -1;
int matched = 0;
for (int i = 0; i < count; i++)
matched += edges[i].target_id == expected;
int total = count;
cbm_store_free_edges(edges, count);
return target_file ? (matched == 1 && total == 1 ? 1 : -1) : total;
}
/* No build configuration: an include spelled so that exactly one project file ends
* in that path, at a component boundary, names that file. */
TEST(ei_c_unique_include_spelling_binds_its_one_project_file) {
static const EILangFile files[] = {
{"include/suite.h", "int suite(void);\n"},
{"src/state.c", "#include \"suite.h\"\n"},
{"src/nvim/api/buffer.h", "int api(void);\n"},
{"src/nvim/buffer.c", "#include \"nvim/api/buffer.h\"\n"},
{"include/linux/mm.h", "int mm(void);\n"},
{"drivers/net/rx.c", "#include <linux/mm.h>\n"},
};
EILangProj lp;
cbm_store_t *store = ei_index_files(&lp, files, 6);
ASSERT_NOT_NULL(store);
int quoted = ei_import_count(store, lp.project, "src/state.c", "include/suite.h");
int rooted = ei_import_count(store, lp.project, "src/nvim/buffer.c", "src/nvim/api/buffer.h");
int angle = ei_import_count(store, lp.project, "drivers/net/rx.c", "include/linux/mm.h");
ei_cleanup(&lp, store);
ASSERT_EQ(quoted, 1);
ASSERT_EQ(rooted, 1);
ASSERT_EQ(angle, 1);
PASS();
}
TEST(ei_c_include_spelling_collision_abstains_honestly) {
static const EILangFile files[] = {
{"src/sds.h", "int a(void);\n"},
{"deps/hiredis/sds.h", "int b(void);\n"},
{"src/sds.c", "#include \"sds.h\"\n"},
{"deps/hiredis/sds.c", "#include \"sds.h\"\n"},
{"modules/m.c", "#include \"sds.h\"\n"},
};
EILangProj lp;
cbm_store_t *store = ei_index_files(&lp, files, 5);
ASSERT_NOT_NULL(store);
/* Beside the includer the search is exact, whatever else shares the name. */
int beside = ei_import_count(store, lp.project, "src/sds.c", "src/sds.h");
int vendored = ei_import_count(store, lp.project, "deps/hiredis/sds.c", "deps/hiredis/sds.h");
/* Elsewhere nothing says which of the two a -I would select, however near. */
int elsewhere = ei_import_count(store, lp.project, "modules/m.c", NULL);
ei_cleanup(&lp, store);
ASSERT_EQ(beside, 1);
ASSERT_EQ(vendored, 1);
ASSERT_EQ(elsewhere, 0);
PASS();
}
TEST(ei_c_toolchain_header_never_binds_a_project_namesake) {
static const EILangFile files[] = {
{"compat/string.h", "int a(void);\n"},
{"compat/sys/types.h", "int b(void);\n"},
{"compat/stdio.h", "int c(void);\n"},
{"src/main.c",
"#include <string.h>\n#include <sys/types.h>\n#include \"stdio.h\"\nint main(void);\n"},
};
EILangProj lp;
cbm_store_t *store = ei_index_files(&lp, files, 4);
ASSERT_NOT_NULL(store);
int count = ei_import_count(store, lp.project, "src/main.c", NULL);
ei_cleanup(&lp, store);
ASSERT_EQ(count, 0);
PASS();
}
TEST(ei_c_partial_or_relative_spelling_never_matches_by_suffix) {
static const EILangFile files[] = {
{"src/buffer.h", "int a(void);\n"},
{"src/main.c", "#include \"uffer.h\"\n#include \"../other/buffer.h\"\n"
"#include \"src/../src/buffer.h\"\n"},
};
EILangProj lp;
cbm_store_t *store = ei_index_files(&lp, files, 2);
ASSERT_NOT_NULL(store);
int count = ei_import_count(store, lp.project, "src/main.c", NULL);
ei_cleanup(&lp, store);
ASSERT_EQ(count, 0);
PASS();
}
/* A compile entry says where this file searches. A project file those roots do not
* reach is not selected, even when its spelling would be unique. */
TEST(ei_c_configured_roots_stop_inference) {
static const EILangFile files[] = {
{"main.cpp", "#include <target.h>\nint main() { return 0; }\n"},
{"elsewhere/target.h", "int a();\n"},
{"user/unrelated.h", "int b();\n"},
};
EILangProj lp;
cbm_store_t *store = ei_index_files_configured(&lp, files, 3, "-I user");
ASSERT_NOT_NULL(store);
int count = ei_import_count(store, lp.project, "main.cpp", NULL);
ei_cleanup(&lp, store);
ASSERT_EQ(count, 0);
PASS();
}
TEST(ei_strict_configured_include_group_order) {
static const EILangFile files[] = {
{"main.cpp", "#include <target.h>\n#include \"quote.h\"\nint main() { return 0; }\n"},
@@ -3342,6 +3459,11 @@ SUITE(edge_imports) {
RUN_TEST(ei_strict_rust_custom_crate_root_and_decoy);
RUN_TEST(ei_strict_rust_ambiguous_declared_module_is_not_ranked);
RUN_TEST(ei_strict_configured_include_group_order);
RUN_TEST(ei_c_unique_include_spelling_binds_its_one_project_file);
RUN_TEST(ei_c_include_spelling_collision_abstains_honestly);
RUN_TEST(ei_c_toolchain_header_never_binds_a_project_namesake);
RUN_TEST(ei_c_partial_or_relative_spelling_never_matches_by_suffix);
RUN_TEST(ei_c_configured_roots_stop_inference);
RUN_TEST(ei_strict_quoted_include_exact_target_and_unmodeled_system_gap);
RUN_TEST(ei_strict_bare_es_package_does_not_bind_root_module);
+36 -66
View File
@@ -747,77 +747,47 @@ static const LRP_Golden LRP_GOLDEN[] = {
"The visible external prototype and the unique project definition have identical builtin "
"signatures and C linkage. The source-audited external-definition bridge supports this "
"cross-file call; prototype materialization is not required."},
{"lrp_c_s2_funcptr_in_struct", "main.c", "run", "make_ops()", 0, "main.c", "make_ops", "CALLS",
2, "Ops make_ops(void);", false, false,
"The visible prototype is a source declaration, but C extraction registers only function "
"definitions; no graph node or exact registry declaration exists for the prototype. Cross-TU "
"definition equivalence is a separate gap requiring an explicit linkage unit and compatible "
"effective linkage/signature.",
"Reviewed lang_specs.c c_func_types, extract_defs.c resolve_func_name_c_family, c_lsp.c "
"c_proof_collect/c_proof_domain; Source audit reported binding_not_found. Existing "
"resolver_c "
"source-prototype unit controls inject declaration identities and therefore do not test "
"materialization."},
{"lrp_c_s3_constructor", "main.c", "make_buf", "buf_new(n)", 0, "main.c", "buf_new", "CALLS", 2,
"Buf *buf_new(int cap);", false, false,
"The visible prototype is a source declaration, but C extraction registers only function "
"definitions; no graph node or exact registry declaration exists for the prototype. Cross-TU "
"definition equivalence is a separate gap requiring an explicit linkage unit and compatible "
"effective linkage/signature.",
"Reviewed lang_specs.c c_func_types, extract_defs.c resolve_func_name_c_family, c_lsp.c "
"c_proof_collect/c_proof_domain; Source audit reported binding_not_found. Existing "
"resolver_c "
"source-prototype unit controls inject declaration identities and therefore do not test "
"materialization."},
{"lrp_c_s2_funcptr_in_struct", "main.c", "run", "make_ops()", 0, "ops.c", "make_ops", "CALLS", 5,
"Ops make_ops(void) {", true, false,
"A prototype in the calling file declares the external name, and the one definition of "
"that name in the project is the target.",
"The earlier golden named the prototype, which has no graph node, and recorded a gap. The "
"translation-unit rules bind the definition, so the golden now names it."},
{"lrp_c_s3_constructor", "main.c", "make_buf", "buf_new(n)", 0, "buf.c", "buf_new", "CALLS", 4,
"Buf *buf_new(int cap) {", true, false,
"A prototype in the calling file declares the external name, and the one definition of "
"that name in the project is the target.",
"The earlier golden named the prototype, which has no graph node, and recorded a gap. The "
"translation-unit rules bind the definition, so the golden now names it."},
{"lrp_c_s4_static_local", "main.c", "run", "exported(n)", 0, "util.c", "exported", "CALLS", 3,
"int exported(int x) { return helper(x); }", true, false, NULL,
"The visible external prototype binds the unique compatible external definition in util.c. "
"The separate same-file helper call remains required, and main.run to the static helper "
"remains explicitly forbidden."},
{"lrp_c_s5_chained_deref", "main.c", "run", "node_val(head->next)", 0, "main.c", "node_val",
"CALLS", 2, "int node_val(const Node *n);", false, false,
"The visible prototype is a source declaration, but C extraction registers only function "
"definitions; no graph node or exact registry declaration exists for the prototype. Cross-TU "
"definition equivalence is a separate gap requiring an explicit linkage unit and compatible "
"effective linkage/signature.",
"Reviewed lang_specs.c c_func_types, extract_defs.c resolve_func_name_c_family, c_lsp.c "
"c_proof_collect/c_proof_domain; Source audit reported binding_not_found. Existing "
"resolver_c "
"source-prototype unit controls inject declaration identities and therefore do not test "
"materialization."},
{"lrp_c_s6_base_method", "circle.c", "run", "shape_color(&c->base)", 0, "circle.c",
"shape_color", "CALLS", 3, "int shape_color(const Shape *s);", false, false,
"The visible prototype is a source declaration, but C extraction registers only function "
"definitions; no graph node or exact registry declaration exists for the prototype. Cross-TU "
"definition equivalence is a separate gap requiring an explicit linkage unit and compatible "
"effective linkage/signature.",
"Reviewed lang_specs.c c_func_types, extract_defs.c resolve_func_name_c_family, c_lsp.c "
"c_proof_collect/c_proof_domain; Source audit reported binding_not_found. Existing "
"resolver_c "
"source-prototype unit controls inject declaration identities and therefore do not test "
"materialization."},
{"lrp_c_s7_generic_callback", "sort.c", "my_sort", "int_cmp(&arr[0], &arr[1])", 0, "sort.c",
"int_cmp", "CALLS", 2, "int int_cmp(const void *a, const void *b);", false, false,
"The visible prototype is a source declaration, but C extraction registers only function "
"definitions; no graph node or exact registry declaration exists for the prototype. Cross-TU "
"definition equivalence is a separate gap requiring an explicit linkage unit and compatible "
"effective linkage/signature.",
"Reviewed lang_specs.c c_func_types, extract_defs.c resolve_func_name_c_family, c_lsp.c "
"c_proof_collect/c_proof_domain; Source audit reported binding_not_found. Existing "
"resolver_c "
"source-prototype unit controls inject declaration identities and therefore do not test "
"materialization."},
{"lrp_c_s8_field_call", "service.c", "run", "log_msg(&svc->logger, m)", 0, "service.c",
"log_msg", "CALLS", 3, "void log_msg(const Logger *l, const char *msg);", false, false,
"The visible prototype is a source declaration, but C extraction registers only function "
"definitions; no graph node or exact registry declaration exists for the prototype. Cross-TU "
"definition equivalence is a separate gap requiring an explicit linkage unit and compatible "
"effective linkage/signature.",
"Reviewed lang_specs.c c_func_types, extract_defs.c resolve_func_name_c_family, c_lsp.c "
"c_proof_collect/c_proof_domain; Source audit reported binding_not_found. Existing "
"resolver_c "
"source-prototype unit controls inject declaration identities and therefore do not test "
"materialization."},
{"lrp_c_s5_chained_deref", "main.c", "run", "node_val(head->next)", 0, "node.c", "node_val",
"CALLS", 3, "int node_val(const Node *n) { return n->val; }", true, false,
"A prototype in the calling file declares the external name, and the one definition of "
"that name in the project is the target.",
"The earlier golden named the prototype, which has no graph node, and recorded a gap. The "
"translation-unit rules bind the definition, so the golden now names it."},
{"lrp_c_s6_base_method", "circle.c", "run", "shape_color(&c->base)", 0, "shape.c", "shape_color",
"CALLS", 3, "int shape_color(const Shape *s) { return s->color; }", true, false,
"A prototype in the calling file declares the external name, and the one definition of "
"that name in the project is the target.",
"The earlier golden named the prototype, which has no graph node, and recorded a gap. The "
"translation-unit rules bind the definition, so the golden now names it."},
{"lrp_c_s7_generic_callback", "sort.c", "my_sort", "int_cmp(&arr[0], &arr[1])", 0, "compare.c",
"int_cmp", "CALLS", 1, "int int_cmp(const void *a, const void *b) {", true, false,
"A prototype in the calling file declares the external name, and the one definition of "
"that name in the project is the target.",
"The earlier golden named the prototype, which has no graph node, and recorded a gap. The "
"translation-unit rules bind the definition, so the golden now names it."},
{"lrp_c_s8_field_call", "service.c", "run", "log_msg(&svc->logger, m)", 0, "logger.c", "log_msg",
"CALLS", 3, "void log_msg(const Logger *l, const char *msg) { (void)l; (void)msg; }", true, false,
"A prototype in the calling file declares the external name, and the one definition of "
"that name in the project is the target.",
"The earlier golden named the prototype, which has no graph node, and recorded a gap. The "
"translation-unit rules bind the definition, so the golden now names it."},
{"lrp_c_s2_funcptr_in_struct", "main.c", "run", "o.compute(n)", 0, "ops.c", "double_it",
"CALLS", 3, "static int double_it(int x) { return x * 2; }", false, false,
"Requires proving make_ops linkage to this implementation, its returned struct field "
+102 -29
View File
@@ -15550,8 +15550,8 @@ static void write_c_header_namesake_fixture(const char *tmp, int pad_files) {
"{\n"
"\tput_page(page);\n"
"}\n");
/* No include search roots configure this system spelling. A same-named
* project header is not evidence that the compiler would select it. */
/* No include search roots are configured, and exactly one project file ends in
* this spelling, so it is the file any -I could select: the real kernel shape. */
write_temp_file(tmp, "drivers/net/unconfigured.c",
"#include <linux/mm.h>\n"
"void unconfigured_free(struct page *page) { put_page(page); }\n");
@@ -15635,7 +15635,7 @@ TEST(pipeline_c_call_reaches_header_function_beside_field) {
ASSERT_EQ(labelled_call_count(s, project, "mte_dump_tag_range", "put_page", "Field"), 0);
ASSERT_EQ(labelled_call_count(s, project, "recv_buffer_free", "put_page", "Field"), 0);
ASSERT_EQ(labelled_call_count(s, project, "unconfigured_free", "put_page", "Function"), 0);
ASSERT_EQ(labelled_call_count(s, project, "unconfigured_free", "put_page", "Function"), 1);
ASSERT_EQ(labelled_call_count(s, project, "unconfigured_free", "put_page", "Field"), 0);
cbm_store_close(s);
@@ -15673,7 +15673,7 @@ TEST(pipeline_c_call_reaches_header_function_beside_field_parallel) {
ASSERT_EQ(labelled_call_count(s, project, "mte_dump_tag_range", "put_page", "Field"), 0);
ASSERT_EQ(labelled_call_count(s, project, "recv_buffer_free", "put_page", "Field"), 0);
ASSERT_EQ(labelled_call_count(s, project, "unconfigured_free", "put_page", "Function"), 0);
ASSERT_EQ(labelled_call_count(s, project, "unconfigured_free", "put_page", "Function"), 1);
ASSERT_EQ(labelled_call_count(s, project, "unconfigured_free", "put_page", "Field"), 0);
cbm_store_close(s);
@@ -16125,6 +16125,79 @@ static bool c_member_fields_quality(cbm_store_t *store, const char *project, con
"conditional, parenthesized, return and binary expressions, not initializer_pair values; "
"no guessed name supplies proof.";
static const RQ_Expected expected[] = {
/* Parameter types and reads that the translation-unit rules now bind. */
{.case_id = "c.field.use-beta-param-type",
.site = {.file = "use_beta.c", .caller = "use_beta.use_beta", .start_byte = 32, .end_byte = 36},
.relation = "USAGE",
.target = {.file = "beta.h",
.suffix = "beta.Beta",
.label = "Class",
.declaration_line = 4,
.declaration_text = "typedef struct {"}},
{.case_id = "c.field.guarded-alpha-read",
.site = {.file = "guarded.c", .caller = "guarded.guarded_alpha", .start_byte = 108, .end_byte = 113},
.relation = "USAGE",
.target = {.file = "alpha.h",
.suffix = "alpha.Alpha.count",
.label = "Field",
.declaration_line = 5,
.declaration_text = " int count;"}},
{.case_id = "c.field.guarded-beta-param-type",
.site = {.file = "guarded.c", .caller = "guarded.guarded_beta", .start_byte = 140, .end_byte = 144},
.relation = "USAGE",
.target = {.file = "beta.h",
.suffix = "beta.Beta",
.label = "Class",
.declaration_line = 4,
.declaration_text = "typedef struct {"}},
{.case_id = "c.field.guarded-beta-read",
.site = {.file = "guarded.c", .caller = "guarded.guarded_beta", .start_byte = 165, .end_byte = 170},
.relation = "USAGE",
.target = {.file = "beta.h",
.suffix = "beta.Beta.count",
.label = "Field",
.declaration_line = 5,
.declaration_text = " int count;"}},
{.case_id = "c.field.hdr-beta-param-type",
.site = {.file = "inline.h", .caller = "inline.hdr_beta", .start_byte = 168, .end_byte = 172},
.relation = "USAGE",
.target = {.file = "beta.h",
.suffix = "beta.Beta",
.label = "Class",
.declaration_line = 4,
.declaration_text = "typedef struct {"}},
{.case_id = "c.field.hdr-own-typedef-param-type",
.site = {.file = "inline.h", .caller = "inline.hdr_own_typedef", .start_byte = 408, .end_byte = 414},
.relation = "USAGE",
.target = {.file = "inline.h",
.suffix = "inline.HdrBuf",
.label = "Class",
.declaration_line = 13,
.declaration_text = "typedef struct {"}},
{.case_id = "c.field.use-remote-param-type",
.site = {.file = "mix.c", .caller = "mix.use_remote", .start_byte = 120, .end_byte = 124},
.relation = "USAGE",
.target = {.file = "beta.h",
.suffix = "beta.Beta",
.label = "Class",
.declaration_line = 4,
.declaration_text = "typedef struct {"}},
{.case_id = "c.field.global-alpha-read",
.site = {.file = "globals.c", .caller = "globals.read_alpha_global", .start_byte = 163, .end_byte = 168},
.relation = "USAGE",
.target = {.file = "alpha.h",
.suffix = "alpha.Alpha.count",
.label = "Field",
.declaration_line = 5,
.declaration_text = " int count;"}},
{.case_id = "c.field.global-beta-read",
.site = {.file = "globals.c", .caller = "globals.read_beta_global", .start_byte = 220, .end_byte = 225},
.relation = "USAGE",
.target = {.file = "beta.h",
.suffix = "beta.Beta.count",
.label = "Field",
.declaration_line = 5,
.declaration_text = " int count;"}},
{.case_id = "c.field.alpha-count-0",
/* Literal source: a->count */
.site = {.file = "use_alpha.c",
@@ -16153,7 +16226,7 @@ static bool c_member_fields_quality(cbm_store_t *store, const char *project, con
.label = "Field",
.declaration_line = 5,
.declaration_text = " int count;"},
.mode = RQ_KNOWN_GAP,
.mode = RQ_REQUIRED,
.gap_id = "C-FIELD-GUARDED-OWNER",
.why = c_field_guard_why,
.tried = c_field_guard_tried},
@@ -16169,7 +16242,7 @@ static bool c_member_fields_quality(cbm_store_t *store, const char *project, con
.label = "Field",
.declaration_line = 5,
.declaration_text = " int count;"},
.mode = RQ_KNOWN_GAP,
.mode = RQ_REQUIRED,
.gap_id = "C-FIELD-WRITE-ROLE",
.why = c_field_write_why,
.tried = c_field_write_tried},
@@ -16201,7 +16274,7 @@ static bool c_member_fields_quality(cbm_store_t *store, const char *project, con
.label = "Field",
.declaration_line = 5,
.declaration_text = " int count;"},
.mode = RQ_KNOWN_GAP,
.mode = RQ_REQUIRED,
.gap_id = "C-FIELD-ANONYMOUS-TYPEDEF-OWNER",
.why = c_field_typedef_why,
.tried = c_field_typedef_tried},
@@ -16217,7 +16290,7 @@ static bool c_member_fields_quality(cbm_store_t *store, const char *project, con
.label = "Field",
.declaration_line = 5,
.declaration_text = " int count;"},
.mode = RQ_KNOWN_GAP,
.mode = RQ_REQUIRED,
.gap_id = "C-FIELD-WRITE-ROLE",
.why = c_field_write_why,
.tried = c_field_write_tried},
@@ -16233,7 +16306,7 @@ static bool c_member_fields_quality(cbm_store_t *store, const char *project, con
.label = "Field",
.declaration_line = 6,
.declaration_text = " int alpha_only;"},
.mode = RQ_KNOWN_GAP,
.mode = RQ_REQUIRED,
.gap_id = "C-FIELD-GUARDED-OWNER",
.why = c_field_guard_why,
.tried = c_field_guard_tried},
@@ -16249,7 +16322,7 @@ static bool c_member_fields_quality(cbm_store_t *store, const char *project, con
.label = "Field",
.declaration_line = 6,
.declaration_text = " int beta_only;"},
.mode = RQ_KNOWN_GAP,
.mode = RQ_REQUIRED,
.gap_id = "C-FIELD-ANONYMOUS-TYPEDEF-OWNER",
.why = c_field_typedef_why,
.tried = c_field_typedef_tried},
@@ -16308,7 +16381,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-GUARDED-OWNER",
.why = c_field_guard_why,
.tried = c_field_guard_tried},
@@ -16372,7 +16445,7 @@ static bool c_member_fields_quality(cbm_store_t *store, const char *project, con
.label = "Field",
.declaration_line = 14,
.declaration_text = " struct Dev *devs, *spare;"},
.mode = RQ_KNOWN_GAP,
.mode = RQ_REQUIRED,
.gap_id = "C-FIELD-GUARDED-OWNER",
.why = c_field_guard_why,
.tried = c_field_guard_tried},
@@ -16388,7 +16461,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-COMPOSITE-RECEIVER",
.why = c_field_chain_why,
.tried = c_field_chain_tried},
@@ -16404,7 +16477,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-GUARDED-OWNER",
.why = c_field_guard_why,
.tried = c_field_guard_tried},
@@ -16420,7 +16493,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-COMPOSITE-RECEIVER",
.why = c_field_chain_why,
.tried = c_field_chain_tried},
@@ -16436,7 +16509,7 @@ static bool c_member_fields_quality(cbm_store_t *store, const char *project, con
.label = "Field",
.declaration_line = 14,
.declaration_text = " struct Dev *devs, *spare;"},
.mode = RQ_KNOWN_GAP,
.mode = RQ_REQUIRED,
.gap_id = "C-FIELD-GUARDED-OWNER",
.why = c_field_guard_why,
.tried = c_field_guard_tried},
@@ -16452,7 +16525,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-COMPOSITE-RECEIVER",
.why = c_field_chain_why,
.tried = c_field_chain_tried},
@@ -16466,7 +16539,7 @@ static bool c_member_fields_quality(cbm_store_t *store, const char *project, con
.label = "Field",
.declaration_line = 5,
.declaration_text = " int count;"},
.mode = RQ_KNOWN_GAP,
.mode = RQ_REQUIRED,
.gap_id = "C-FIELD-GUARDED-OWNER",
.why = c_field_guard_why,
.tried = c_field_guard_tried},
@@ -16494,7 +16567,7 @@ static bool c_member_fields_quality(cbm_store_t *store, const char *project, con
.label = "Field",
.declaration_line = 5,
.declaration_text = " int count;"},
.mode = RQ_KNOWN_GAP,
.mode = RQ_REQUIRED,
.gap_id = "C-FIELD-ANONYMOUS-TYPEDEF-OWNER",
.why = c_field_typedef_why,
.tried = c_field_typedef_tried},
@@ -16510,7 +16583,7 @@ static bool c_member_fields_quality(cbm_store_t *store, const char *project, con
.label = "Field",
.declaration_line = 14,
.declaration_text = " int count;"},
.mode = RQ_KNOWN_GAP,
.mode = RQ_REQUIRED,
.gap_id = "C-FIELD-ANONYMOUS-TYPEDEF-OWNER",
.why = c_field_typedef_why,
.tried = c_field_typedef_tried},
@@ -16528,7 +16601,7 @@ static bool c_member_fields_quality(cbm_store_t *store, const char *project, con
.label = "Field",
.declaration_line = 18,
.declaration_text = " int count;"},
.mode = RQ_KNOWN_GAP,
.mode = RQ_REQUIRED,
.gap_id = "C-PP-HEADER-CERTIFICATE",
.why = "The genuine field access needs the included header dependency closure. "
"Expanded bytes, configuration and matching spans do not prove that "
@@ -16547,7 +16620,7 @@ static bool c_member_fields_quality(cbm_store_t *store, const char *project, con
.label = "Field",
.declaration_line = 5,
.declaration_text = " int count;"},
.mode = RQ_KNOWN_GAP,
.mode = RQ_REQUIRED,
.gap_id = "C-FIELD-WRITE-ROLE",
.why = c_field_write_why,
.tried = c_field_write_tried},
@@ -16674,7 +16747,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-GUARDED-OWNER",
.why = c_field_guard_why,
.tried = c_field_guard_tried},
@@ -16775,7 +16848,7 @@ static bool c_member_fields_quality(cbm_store_t *store, const char *project, con
.label = "Variable",
.declaration_line = 6,
.declaration_text = "static struct Ops table_ops = {"},
.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},
@@ -27367,7 +27440,7 @@ TEST(pipeline_cpp_enum_reference_reaches_flat_enumerator_c1) {
.label = "Variable",
.declaration_line = 1,
.declaration_text = "enum Color { RED, GREEN };"},
.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.",
@@ -36420,7 +36493,7 @@ static bool cpp_recv_quality(const CppRecvRun *r, unsigned scenario) {
.suffix = "SecdManager.SecdManager.getInstance",
.declaration_line = 3,
.declaration_text = "SecdManager *SecdManager::getInstance() {"},
.mode = RQ_KNOWN_GAP,
.mode = RQ_REQUIRED,
.gap_id = "CPP-FACTORY-VISIBILITY-PROOF",
.why = "The unconfigured include roots and header-to-definition binding are not a closed "
"declaration lookup; static class-member selection has no complete source proof "
@@ -36469,7 +36542,7 @@ static bool cpp_recv_quality(const CppRecvRun *r, unsigned scenario) {
.suffix = "SecdManager.Registry.instance",
.declaration_line = 14,
.declaration_text = "Registry &Registry::instance() {"},
.mode = RQ_KNOWN_GAP,
.mode = RQ_REQUIRED,
.gap_id = "CPP-FACTORY-VISIBILITY-PROOF",
.why = "The unconfigured include roots and header-to-definition binding are not a closed "
"declaration lookup; static class-member selection has no complete source proof "
@@ -36515,7 +36588,7 @@ static bool cpp_recv_quality(const CppRecvRun *r, unsigned scenario) {
.declaration_line = 21,
.declaration_text = "std::unique_ptr<Widget> Widget::create() { return "
"std::make_unique<Widget>(); }"},
.mode = RQ_KNOWN_GAP,
.mode = RQ_REQUIRED,
.gap_id = "CPP-FACTORY-VISIBILITY-PROOF",
.why = "The unconfigured include roots and header-to-definition binding are not a closed "
"declaration lookup; static class-member selection has no complete source proof "
@@ -36562,7 +36635,7 @@ static bool cpp_recv_quality(const CppRecvRun *r, unsigned scenario) {
.declaration_line = 25,
.declaration_text = "std::shared_ptr<Session> Session::open() { return "
"std::make_shared<Session>(); }"},
.mode = RQ_KNOWN_GAP,
.mode = RQ_REQUIRED,
.gap_id = "CPP-FACTORY-VISIBILITY-PROOF",
.why = "The unconfigured include roots and header-to-definition binding are not a closed "
"declaration lookup; static class-member selection has no complete source proof "
+1829 -33
View File
File diff suppressed because it is too large. Load diff