mirror of
https://github.com/DeusData/codebase-memory-mcp.git
synced 2026-11-04 10:48:20 +00:00
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:
commit
cdc56c5f45
21 files changed
+5396
-395
No files matched your search
+395
-11
@@ -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,
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
File diff suppressed because it is too large.
Load diff
@@ -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
@@ -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;
|
||||
}
|
||||
@@ -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
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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
|
||||
@@ -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;
|
||||
|
||||
@@ -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
@@ -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) {
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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."
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
@@ -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
File diff suppressed because it is too large.
Load diff
Reference in new issue
Block a user