Lists a file or a class can outgrow grow; a file's size decides nothing

Bases and mixins of Dart, PHP, Swift, Scala, Ruby and Perl classes, a PHP variable's writes, a Python function's returns, the files a shell process sources, the C# using-directive walk and the headers a C translation unit reaches were fixed arrays: past them a declaration was dropped or its class went open. They grow on the walk's arena now. A shell command asks the files that declare its name instead of every file of the process.

The binding walks, the source adapters and the proof passes each pass over a file a bounded number of times, and their node and visit counts voided a large generated file whole. Only their nesting guards stay. A Rust trait's method names are no longer cut at 63.

Tests: a class of 70 traits, a variable of 12 writes, a class of 40 bases, a process of 300 sourced files, the seen set's growth and depth guard, and the extension-named imports of item 4, which bind nothing. The chain test of item 1 now measures the walk alone, and the barrel's class is no substring of another needle.
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
This commit is contained in:
Martin Vogel committed 2026-11-03 04:03:09 +01:00
1 parent aecf66b116
commit 3b823ccf2f
23 files changed
+576 -194

No files matched your search

+28 -16
View File
@@ -2160,26 +2160,38 @@ static bool c_proof_signature_equal(const CBMType *left, const CBMType *right) {
static bool c_proof_file_reaches(CLSPContext *ctx, const char *from, const char *to) {
if (!strcmp(from, to))
return true;
enum { REACH_LIMIT = 4096 };
const char **seen = cbm_arena_alloc(ctx->arena, REACH_LIMIT * sizeof(*seen));
if (!seen)
CBMHashTable *known = cbm_ht_create(64);
if (!known)
return false;
int head = 0, tail = 0;
seen[tail++] = from;
while (head < tail) {
const CBMCFileFacts *facts = cbm_c_file_facts(ctx->c_decls, seen[head++]);
for (int n = 0; facts && n < facts->include_count; n++) {
/* A file enters once; each entry lives on the arena, so the walk needs no second list. */
typedef struct c_reach_file {
const char *file;
struct c_reach_file *next;
} c_reach_file_t;
c_reach_file_t *head = cbm_arena_alloc(ctx->arena, sizeof(*head));
c_reach_file_t *tail = head;
if (head)
*head = (c_reach_file_t){.file = from};
cbm_ht_set(known, from, (void *)from);
bool reached = false;
for (; head && !reached; head = head->next) {
const CBMCFileFacts *facts = cbm_c_file_facts(ctx->c_decls, head->file);
for (int n = 0; facts && n < facts->include_count && !reached; n++) {
const char *next = facts->includes[n];
if (!strcmp(next, to))
return true;
bool known = false;
for (int k = 0; k < tail && !known; k++)
known = !strcmp(seen[k], next);
if (!known && tail < REACH_LIMIT)
seen[tail++] = next;
reached = !strcmp(next, to);
if (reached || cbm_ht_get(known, next))
continue;
c_reach_file_t *entry = cbm_arena_alloc(ctx->arena, sizeof(*entry));
if (!entry)
continue;
*entry = (c_reach_file_t){.file = next};
tail->next = entry;
tail = entry;
cbm_ht_set(known, next, (void *)next);
}
}
return false;
cbm_ht_free(known);
return reached;
}
static const char *c_proof_func_file(CLSPContext *ctx, const CBMRegisteredFunc *candidate,
+2 -6
View File
@@ -50,7 +50,7 @@ static const char *csp_text(csp_context_t *ctx, TSNode node) {
: NULL;
}
static bool csp_budget(csp_context_t *ctx, unsigned depth) {
if (++ctx->visited > 1000000 || depth > 128)
if (depth > 128)
ctx->failed = true;
return !ctx->failed;
}
@@ -169,15 +169,11 @@ static bool csp_builtin_word(const char *text, uint32_t length) {
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;
/* One pass over the file, so its size decides nothing. */
for (;;) {
TSNode node = ts_tree_cursor_current_node(&cursor);
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) &&
+22 -11
View File
@@ -2859,15 +2859,18 @@ static void cs_collect_imports(CSLSPContext *ctx, TSNode root) {
* whether it sits inside a namespace declaration: a block namespace's
* body, or the compilation-unit siblings after a file-scoped namespace
* (the grammar may attach those either way). */
enum { CS_IMPORT_WALK_MAX = 256 };
TSNode stack[CS_IMPORT_WALK_MAX];
bool in_ns[CS_IMPORT_WALK_MAX];
int top = 0;
in_ns[top] = false;
stack[top++] = root;
typedef struct {
TSNode node;
bool in_ns;
} cs_import_walk_t;
enum { CS_IMPORT_WALK_INLINE = 256 };
cs_import_walk_t held[CS_IMPORT_WALK_INLINE];
cs_import_walk_t *stack = held;
int top = 0, capacity = CS_IMPORT_WALK_INLINE;
stack[top++] = (cs_import_walk_t){.node = root, .in_ns = false};
while (top > 0) {
TSNode n = stack[--top];
bool n_in_ns = in_ns[top];
TSNode n = stack[--top].node;
bool n_in_ns = stack[top].in_ns;
if (ts_node_is_null(n))
continue;
const char *k = ts_node_type(n);
@@ -2967,7 +2970,7 @@ static void cs_collect_imports(CSLSPContext *ctx, TSNode root) {
bool child_in_ns = n_in_ns || strcmp(k, "namespace_declaration") == 0 ||
strcmp(k, "file_scoped_namespace_declaration") == 0;
uint32_t cnc = ts_node_child_count(n);
for (uint32_t i = 0; i < cnc && top < CS_IMPORT_WALK_MAX; i++) {
for (uint32_t i = 0; i < cnc; i++) {
TSNode c = ts_node_child(n, i);
if (ts_node_is_null(c))
continue;
@@ -2976,8 +2979,16 @@ static void cs_collect_imports(CSLSPContext *ctx, TSNode root) {
strcmp(ck, "constructor_declaration") == 0 || strcmp(ck, "block") == 0) {
continue;
}
in_ns[top] = child_in_ns;
stack[top++] = c;
if (top == capacity) {
cs_import_walk_t *grown =
cbm_arena_alloc(ctx->arena, (size_t)capacity * 2 * sizeof(*grown));
if (!grown)
return;
memcpy(grown, stack, (size_t)top * sizeof(*grown));
stack = grown;
capacity *= 2;
}
stack[top++] = (cs_import_walk_t){.node = c, .in_ns = child_in_ns};
/* A file-scoped namespace covers the siblings that follow it. */
if (strcmp(ck, "file_scoped_namespace_declaration") == 0)
child_in_ns = true;
+1 -1
View File
@@ -53,7 +53,7 @@ static const char *csp_text(cs_proof_source_t *source, TSNode node) {
: NULL;
}
static bool csp_budget(cs_proof_source_t *source, unsigned depth) {
if (++source->visits > 1000000 || depth > 128)
if (depth > 128)
source->failed = true;
return !source->failed;
}
+1 -1
View File
@@ -54,7 +54,7 @@ static const char *kt_proof_text(kt_proof_source_t *source, TSNode node) {
: NULL;
}
static bool kt_proof_budget(kt_proof_source_t *source, unsigned depth) {
if (++source->visits > 1000000 || depth > 128)
if (depth > 128)
source->failed = true;
return !source->failed;
}
View File
Whitespace-only changes.
+2 -4
View File
@@ -2514,10 +2514,8 @@ void oo_walk_dispose(oo_walk_t *w) {
}
bool oo_budget(oo_walk_t *w) {
/* Each source node is visited a bounded number of times. Exhaustion
* discards the file's staged rows; it never publishes a partial walk. */
if (++w->visits > 64u * 1024u * 1024u)
w->failed = true;
/* Each source node is visited a bounded number of times, so a file's size
* decides nothing. */
return !w->failed;
}
+1 -1
View File
@@ -41,7 +41,7 @@ static const char *php_proof_text(php_source_t *source, TSNode node) {
: NULL;
}
static bool php_proof_budget(php_source_t *source, unsigned depth) {
if (++source->visits > 1000000 || depth > 128)
if (depth > 128)
source->failed = true;
return !source->failed;
}
+1 -1
View File
@@ -47,7 +47,7 @@ static const char *pyp_text(pyp_source_t *source, TSNode node) {
: NULL;
}
static bool pyp_budget(pyp_source_t *source, unsigned depth) {
if (++source->visits > 1000000 || depth > 128) {
if (depth > 128) {
source->failed = true;
source->exhausted = true;
}
+4 -3
View File
@@ -5775,10 +5775,11 @@ void cbm_rust_build_local_registry(CBMArena *arena, CBMTypeRegistry *reg, CBMFil
if (ti >= 0) {
reg->types[ti].is_interface = true;
if (!ts_node_is_null(body)) {
const char *methods[64];
int mc = 0;
uint32_t bc = ts_node_named_child_count(body);
for (uint32_t j = 0; j < bc && mc < 63; j++) {
const char **methods =
(const char **)cbm_arena_alloc(arena, (bc + 1) * sizeof(const char *));
int mc = 0;
for (uint32_t j = 0; methods && j < bc; j++) {
TSNode item = ts_node_named_child(body, j);
const char *ik = ts_node_type(item);
if (strcmp(ik, "function_item") != 0 &&
+1 -1
View File
@@ -39,7 +39,7 @@ static const char *rsp_text(rsp_source_t *source, TSNode node) {
: NULL;
}
static bool rsp_budget(rsp_source_t *source, unsigned depth) {
if (++source->visits > 1000000 || depth > 128)
if (depth > 128)
source->failed = true;
return !source->failed;
}
+9 -15
View File
@@ -460,9 +460,7 @@ static const char *dart_base_entry(cbm_source_walk_t *walk, char tag, TSNode typ
/* Each base is recorded with its role, since the roles are searched in order. */
static void dart_note_bases(cbm_source_walk_t *walk, TSNode node, cbm_source_decl_t *record) {
enum { MAX_BASES = 32 };
const char *found[MAX_BASES];
size_t used = 0;
cbm_source_names_t found = {0};
bool open = false;
uint32_t count = ts_node_named_child_count(node);
for (uint32_t i = 0; i < count; i++) {
@@ -474,8 +472,8 @@ static void dart_note_bases(cbm_source_walk_t *walk, TSNode node, cbm_source_dec
/* `mixin M on Base` and `extension E on Base` see Base's members as their own. */
if (cbm_source_is(child, "type_identifier")) {
const char *entry = dart_base_entry(walk, 'S', child);
if (entry && used < MAX_BASES)
found[used++] = entry;
if (entry)
open |= !cbm_source_names_push(walk, &found, entry);
continue;
}
if (!tag)
@@ -490,21 +488,17 @@ static void dart_note_bases(cbm_source_walk_t *walk, TSNode node, cbm_source_dec
if (!cbm_source_is(type, "type_identifier"))
continue;
const char *entry = dart_base_entry(walk, nested ? 'M' : tag, type);
if (!entry)
continue;
if (used == MAX_BASES)
open = true;
else
found[used++] = entry;
if (entry)
open |= !cbm_source_names_push(walk, &found, entry);
}
}
}
const char **copy = cbm_source_table_alloc(walk, (used + 1) * sizeof(*copy));
const char **copy = cbm_source_table_alloc(walk, (found.count + 1) * sizeof(*copy));
if (!copy)
return;
for (size_t i = 0; i < used; i++)
copy[i] = found[i] ? cbm_source_table_intern(walk, found[i]) : NULL;
copy[used] = NULL;
for (size_t i = 0; i < found.count; i++)
copy[i] = found.items[i] ? cbm_source_table_intern(walk, found.items[i]) : NULL;
copy[found.count] = NULL;
record->bases = copy;
if (open)
record->flags |= CBM_SOURCE_DECL_F_OPEN;
+16 -24
View File
@@ -42,7 +42,6 @@
#include <stdlib.h>
#include "syntax_index.h"
enum { PERL_MAX_BASES = 32 };
#define PERL_EXPORTER_KEY "&exporter"
#define PERL_EXPORT_PREFIX "&export:"
@@ -165,16 +164,9 @@ static bool perl_literal_words(perl_ctx_t *ctx, TSNode node, perl_word_fn fn, vo
return false;
}
typedef struct {
const char *words[PERL_MAX_BASES];
size_t count;
} perl_words_t;
static void perl_gather_word(perl_ctx_t *ctx, const char *word, void *user) {
(void)ctx;
perl_words_t *list = user;
if (list->count < PERL_MAX_BASES)
list->words[list->count++] = word;
if (!cbm_source_names_push(ctx->walk, user, word))
cbm_source_fail(ctx->walk, "perl_word_allocation");
}
static TSNode perl_use_arguments(TSNode use) {
@@ -241,29 +233,29 @@ static void perl_collect_use(perl_ctx_t *ctx, TSNode use) {
(void)perl_literal_words(ctx, arguments, perl_declare_imported, (void *)module);
return;
}
perl_words_t words = {0};
cbm_source_names_t words = {0};
bool literal =
ts_node_is_null(arguments) || perl_literal_words(ctx, arguments, perl_gather_word, &words);
if (mojo) {
/* -base means the Mojo::Base class itself, -strict and -signatures add no
* parent, and -role composes methods this walk does not model. */
perl_words_t parents = {0};
cbm_source_names_t parents = {0};
bool open = !literal;
for (size_t i = 0; i < words.count; i++) {
if (!strcmp(words.words[i], "-base"))
if (!strcmp(words.items[i], "-base"))
perl_gather_word(ctx, "Mojo::Base", &parents);
else if (!strcmp(words.words[i], "-role"))
else if (!strcmp(words.items[i], "-role"))
open = true;
else if (words.words[i][0] != '-')
perl_gather_word(ctx, words.words[i], &parents);
else if (words.items[i][0] != '-')
perl_gather_word(ctx, words.items[i], &parents);
}
perl_set_bases(ctx, ctx->package, parents.words, parents.count, line, open);
perl_set_bases(ctx, ctx->package, parents.items, parents.count, line, open);
return;
}
for (size_t i = 0; i < words.count; i++)
if (!strcmp(words.words[i], "Exporter"))
if (!strcmp(words.items[i], "Exporter"))
perl_note_exporter(ctx, ctx->package, line);
perl_set_bases(ctx, ctx->package, words.words, words.count, line, !literal);
perl_set_bases(ctx, ctx->package, words.items, words.count, line, !literal);
}
// cppcheck-suppress constParameterCallback
@@ -279,12 +271,12 @@ static void perl_collect_array(perl_ctx_t *ctx, const char *array, TSNode right,
if (!array)
return;
if (!strcmp(array, "ISA")) {
perl_words_t words = {0};
cbm_source_names_t words = {0};
bool literal = perl_literal_words(ctx, right, perl_gather_word, &words);
for (size_t i = 0; i < words.count; i++)
if (!strcmp(words.words[i], "Exporter"))
if (!strcmp(words.items[i], "Exporter"))
perl_note_exporter(ctx, ctx->package, line);
perl_set_bases(ctx, ctx->package, words.words, words.count, line, !literal);
perl_set_bases(ctx, ctx->package, words.items, words.count, line, !literal);
} else if (!strcmp(array, "EXPORT") || !strcmp(array, "EXPORT_OK")) {
(void)perl_literal_words(ctx, right, perl_declare_export, (void *)ctx->package);
}
@@ -416,13 +408,13 @@ static void perl_collect_node(perl_ctx_t *ctx, TSNode node, unsigned depth) {
const char *array =
cbm_source_text(ctx->walk, ts_node_named_child(arguments, 0));
if (array && !strcmp(array, "@ISA")) {
perl_words_t words = {0};
cbm_source_names_t words = {0};
uint32_t count = ts_node_named_child_count(arguments);
bool literal = true;
for (uint32_t i = 1; i < count; i++)
literal &= perl_literal_words(ctx, ts_node_named_child(arguments, i),
perl_gather_word, &words);
perl_set_bases(ctx, ctx->package, words.words, words.count,
perl_set_bases(ctx, ctx->package, words.items, words.count,
ts_node_start_point(child).row + 1, !literal);
}
}
+33 -19
View File
@@ -26,7 +26,7 @@
#include <stdlib.h>
#include "syntax_index.h"
enum { PHP_MAX_WRITES = 8, PHP_MAX_EXPR_DEPTH = 24 };
enum { PHP_MAX_EXPR_DEPTH = 24 };
typedef enum { PHP_USE_CLASS, PHP_USE_FUNCTION, PHP_USE_CONST } php_use_kind_t;
@@ -52,8 +52,8 @@ typedef struct php_var {
const char *type; /* proven canonical type */
bool param, blocked, computed, computing;
uint32_t available_from, escaped_from;
TSNode rhs[PHP_MAX_WRITES];
int writes;
TSNode *rhs; /* every write's value, in source order */
int writes, write_capacity;
struct php_var *next;
} php_var_t;
@@ -359,10 +359,13 @@ static void php_declare_method(php_ctx_t *ctx, const char *owner, TSNode method)
}
}
static void php_push_base(php_ctx_t *ctx, cbm_source_names_t *found, const char *entry) {
if (!cbm_source_names_push(ctx->walk, found, entry))
cbm_source_fail(ctx->walk, "php_base_allocation");
}
static const char *const *php_bases(php_ctx_t *ctx, TSNode node, const char **parent) {
enum { MAX_BASES = 64 };
const char *found[MAX_BASES];
size_t used = 0;
cbm_source_names_t found = {0};
*parent = NULL;
uint32_t count = ts_node_named_child_count(node);
bool interface = cbm_source_is(node, "interface_declaration");
@@ -373,7 +376,7 @@ static const char *const *php_bases(php_ctx_t *ctx, TSNode node, const char **pa
if (!base && !implements)
continue;
uint32_t members = ts_node_named_child_count(child);
for (uint32_t m = 0; m < members && used < MAX_BASES; m++) {
for (uint32_t m = 0; m < members; m++) {
TSNode member = ts_node_named_child(child, m);
if (!cbm_source_is(member, "name") && !cbm_source_is(member, "qualified_name"))
continue;
@@ -384,8 +387,9 @@ static const char *const *php_bases(php_ctx_t *ctx, TSNode node, const char **pa
bool is_parent = base && !interface;
if (is_parent)
*parent = canonical;
found[used++] =
cbm_arena_sprintf(ctx->walk->arena, "%c:%s", is_parent ? 'P' : 'I', canonical);
php_push_base(
ctx, &found,
cbm_arena_sprintf(ctx->walk->arena, "%c:%s", is_parent ? 'P' : 'I', canonical));
}
}
TSNode body = cbm_source_field(node, "body");
@@ -395,21 +399,21 @@ static const char *const *php_bases(php_ctx_t *ctx, TSNode node, const char **pa
if (!cbm_source_is(child, "use_declaration"))
continue;
uint32_t members = ts_node_named_child_count(child);
for (uint32_t m = 0; m < members && used < MAX_BASES; m++) {
for (uint32_t m = 0; m < members; m++) {
TSNode member = ts_node_named_child(child, m);
if (!cbm_source_is(member, "name") && !cbm_source_is(member, "qualified_name"))
continue;
const char *canonical = php_class_name(ctx, cbm_source_text(ctx->walk, member));
if (canonical)
found[used++] = cbm_arena_sprintf(ctx->walk->arena, "T:%s", canonical);
php_push_base(ctx, &found, cbm_arena_sprintf(ctx->walk->arena, "T:%s", canonical));
}
}
const char **copy = cbm_source_table_alloc(ctx->walk, (used + 1) * sizeof(*copy));
const char **copy = cbm_source_table_alloc(ctx->walk, (found.count + 1) * sizeof(*copy));
if (!copy)
return NULL;
for (size_t i = 0; i < used; i++)
copy[i] = cbm_source_table_intern(ctx->walk, found[i]);
copy[used] = NULL;
for (size_t i = 0; i < found.count; i++)
copy[i] = cbm_source_table_intern(ctx->walk, found.items[i]);
copy[found.count] = NULL;
return copy;
}
@@ -785,11 +789,21 @@ static void php_audit_scope(php_ctx_t *ctx, php_scope_t *scope, TSNode node, uns
const char *name = cbm_source_text(ctx->walk, left);
php_var_t *var = name ? php_var(ctx, scope, name) : NULL;
if (var) {
if (var->writes < PHP_MAX_WRITES)
var->rhs[var->writes] = right;
if (var->writes == var->write_capacity) {
int capacity = var->write_capacity ? var->write_capacity * 2 : 4;
TSNode *rhs =
cbm_arena_alloc(ctx->walk->arena, (size_t)capacity * sizeof(*rhs));
if (rhs) {
if (var->writes)
memcpy(rhs, var->rhs, (size_t)var->writes * sizeof(*rhs));
var->rhs = rhs;
var->write_capacity = capacity;
}
}
if (var->writes < var->write_capacity)
var->rhs[var->writes++] = right;
else
var->blocked = true;
var->writes++;
uint32_t end = ts_node_end_byte(node);
if (!var->available_from || end < var->available_from)
var->available_from = end;
@@ -950,7 +964,7 @@ static const char *php_var_class(php_ctx_t *ctx, php_scope_t *scope, const char
var->computing = true;
const char *type = var->param ? var->declared : NULL;
bool consistent = !var->param || var->declared;
for (int w = 0; consistent && w < var->writes && w < PHP_MAX_WRITES; w++) {
for (int w = 0; consistent && w < var->writes; w++) {
const char *written =
php_expr_class(ctx, scope, var->rhs[w], ts_node_start_byte(var->rhs[w]), depth + 1);
consistent = written && (!type || !strcmp(type, written));
+16 -13
View File
@@ -53,7 +53,7 @@
#include <stdlib.h>
#include "syntax_index.h"
enum { RUBY_MAX_NEST = 32, RUBY_MAX_BASES = 64 };
enum { RUBY_MAX_NEST = 32 };
/* Declaration facts of this adapter, above the shared flags. */
enum {
@@ -393,7 +393,7 @@ static void ruby_note_body_call(ruby_ctx_t *ctx, TSNode call, bool conditional)
/* Each mixin keeps the lexical path that its constant is read under. */
static void ruby_gather_mixin(ruby_ctx_t *ctx, TSNode call, bool in_singleton_class,
const char **bases, size_t *used) {
cbm_source_names_t *bases) {
const char *name = ruby_call_name(ctx->walk, call);
char tag = 0;
/* Inside `class << self`, include is what extend is outside it. */
@@ -407,15 +407,17 @@ static void ruby_gather_mixin(ruby_ctx_t *ctx, TSNode call, bool in_singleton_cl
return;
TSNode arguments = cbm_source_field(call, "arguments");
uint32_t count = ts_node_is_null(arguments) ? 0 : ts_node_named_child_count(arguments);
for (uint32_t i = 0; i < count && *used < RUBY_MAX_BASES; i++) {
for (uint32_t i = 0; i < count; i++) {
TSNode argument = ts_node_named_child(arguments, i);
const char *text = NULL;
if (cbm_source_is(argument, "constant") || cbm_source_is(argument, "scope_resolution") ||
cbm_source_is(argument, "self"))
text = cbm_source_text(ctx->walk, argument);
/* A computed mixin is an unknown ancestor; the '?' keeps that visible. */
bases[(*used)++] =
const char *entry =
cbm_arena_sprintf(ctx->walk->arena, "%c:%s|%s", tag, ruby_cref(ctx), text ? text : "?");
if (!entry || !cbm_source_names_push(ctx->walk, bases, entry))
cbm_source_fail(ctx->walk, "ruby_base_allocation");
}
}
@@ -459,8 +461,7 @@ static void ruby_collect_class(ruby_ctx_t *ctx, TSNode node, bool conditional, u
if (spelled)
qn = cbm_source_def_qn(ctx->walk, spelled, line);
cbm_source_decl_t *record = cbm_source_declare(ctx->walk, owner, "", qn, flags, line);
const char *bases[RUBY_MAX_BASES];
size_t used = 0;
cbm_source_names_t bases = {0};
TSNode superclass = cbm_source_field(node, "superclass");
if (!ts_node_is_null(superclass) && ts_node_named_child_count(superclass)) {
TSNode parent = ts_node_named_child(superclass, 0);
@@ -469,8 +470,10 @@ static void ruby_collect_class(ruby_ctx_t *ctx, TSNode node, bool conditional, u
? cbm_source_text(ctx->walk, parent)
: NULL;
/* Read in the enclosing nest: the class is not in scope for its own header. */
bases[used++] =
const char *entry =
cbm_arena_sprintf(ctx->walk->arena, "S:%s|%s", ruby_cref(ctx), text ? text : "?");
if (!entry || !cbm_source_names_push(ctx->walk, &bases, entry))
cbm_source_fail(ctx->walk, "ruby_base_allocation");
}
if (!ruby_push(ctx, owner)) {
cbm_source_fail(ctx->walk, "ruby_nesting_limit");
@@ -487,7 +490,7 @@ static void ruby_collect_class(ruby_ctx_t *ctx, TSNode node, bool conditional, u
do {
TSNode child = ts_tree_cursor_current_node(&cursor);
if (cbm_source_is(child, "call")) {
ruby_gather_mixin(ctx, child, false, bases, &used);
ruby_gather_mixin(ctx, child, false, &bases);
} else if (cbm_source_is(child, "singleton_class") &&
cbm_source_is(cbm_source_field(child, "value"), "self")) {
TSNode inner = cbm_source_field(child, "body");
@@ -498,7 +501,7 @@ static void ruby_collect_class(ruby_ctx_t *ctx, TSNode node, bool conditional, u
do {
TSNode statement = ts_tree_cursor_current_node(&walker);
if (cbm_source_is(statement, "call"))
ruby_gather_mixin(ctx, statement, true, bases, &used);
ruby_gather_mixin(ctx, statement, true, &bases);
} while (ts_tree_cursor_goto_next_sibling(&walker));
}
ts_tree_cursor_delete(&walker);
@@ -508,11 +511,11 @@ static void ruby_collect_class(ruby_ctx_t *ctx, TSNode node, bool conditional, u
ts_tree_cursor_delete(&cursor);
}
if (record) {
const char **copy = cbm_source_table_alloc(ctx->walk, (used + 1) * sizeof(*copy));
const char **copy = cbm_source_table_alloc(ctx->walk, (bases.count + 1) * sizeof(*copy));
if (copy) {
for (size_t i = 0; i < used; i++)
copy[i] = cbm_source_table_intern(ctx->walk, bases[i]);
copy[used] = NULL;
for (size_t i = 0; i < bases.count; i++)
copy[i] = cbm_source_table_intern(ctx->walk, bases.items[i]);
copy[bases.count] = NULL;
record->bases = copy;
}
}
+7 -11
View File
@@ -45,7 +45,7 @@
#define SCALA_TYPE_NAME "type:"
#define SCALA_OBJECT_NAME "object:"
enum { SCALA_MAX_PACKAGES = 8, SCALA_MAX_BASES = 32 };
enum { SCALA_MAX_PACKAGES = 8 };
typedef struct scala_local {
const char *name;
@@ -286,8 +286,7 @@ static void scala_collect_members(cbm_source_walk_t *walk, const char *package,
static void scala_note_bases(cbm_source_walk_t *walk, TSNode node, cbm_source_decl_t *record,
const char *type_params) {
const char *found[SCALA_MAX_BASES];
size_t used = 0;
cbm_source_names_t found = {0};
bool open = false;
TSNode extend = cbm_source_field(node, "extend");
uint32_t count = ts_node_is_null(extend) ? 0 : ts_node_named_child_count(extend);
@@ -296,17 +295,14 @@ static void scala_note_bases(cbm_source_walk_t *walk, TSNode node, cbm_source_de
if (cbm_source_is(child, "arguments"))
continue;
const char *text = scala_type_text(walk, child, type_params);
if (!text || used == SCALA_MAX_BASES)
open = true;
else
found[used++] = text;
open |= !text || !cbm_source_names_push(walk, &found, text);
}
const char **copy = cbm_source_table_alloc(walk, (used + 1) * sizeof(*copy));
const char **copy = cbm_source_table_alloc(walk, (found.count + 1) * sizeof(*copy));
if (!copy)
return;
for (size_t i = 0; i < used; i++)
copy[i] = cbm_source_table_intern(walk, found[i]);
copy[used] = NULL;
for (size_t i = 0; i < found.count; i++)
copy[i] = cbm_source_table_intern(walk, found.items[i]);
copy[found.count] = NULL;
record->bases = copy;
if (open)
record->flags |= CBM_SOURCE_DECL_F_OPEN;
+38 -19
View File
@@ -24,14 +24,16 @@
#define SHELL_VARIABLES "V:"
#define SHELL_LOCALS "W:"
#define SHELL_FILES "P:"
#define SHELL_DECLARERS "D:" /* a row per function: its name, and the file declaring it */
enum { SHELL_PATH_BUF = 1024, SHELL_CLOSURE_MAX = 256 };
enum { SHELL_PATH_BUF = 1024 };
typedef struct {
cbm_source_walk_t *walk;
const char *functions, *sources, *unset, *variables, *locals;
const char **closure; /* files this script's process consists of, itself first */
size_t closure_count;
cbm_source_chain_t closure_seen;
unsigned function_depth;
} shell_ctx_t;
@@ -221,6 +223,9 @@ static void shell_collect_walk(shell_ctx_t *ctx, TSNode node, unsigned depth) {
uint32_t line = ts_node_start_point(node).row + 1;
(void)cbm_source_declare(walk, ctx->functions, name,
cbm_source_def_qn_at(walk, name, line), 0, line);
cbm_source_decl_t *row = cbm_source_declare(walk, SHELL_DECLARERS, name, NULL, 0, line);
if (row)
row->type = cbm_source_table_intern(walk, walk->file);
}
} else if (cbm_source_is(node, "command")) {
const char *word = shell_command_word(walk, node);
@@ -273,33 +278,44 @@ static void shell_collect(cbm_source_walk_t *walk) {
shell_collect_walk(&ctx, walk->root, 0);
}
static bool shell_in_closure(const shell_ctx_t *ctx, const char *file) {
for (size_t i = 0; i < ctx->closure_count; i++)
if (!strcmp(ctx->closure[i], file))
return true;
return false;
static bool shell_push_closure(shell_ctx_t *ctx, size_t *capacity, const char *file) {
if (ctx->closure_count == *capacity) {
size_t grown = *capacity ? *capacity * 2 : 16;
const char **items = cbm_arena_alloc(ctx->walk->arena, grown * sizeof(*items));
if (!items)
return false;
if (ctx->closure_count)
memcpy(items, ctx->closure, ctx->closure_count * sizeof(*items));
ctx->closure = items;
*capacity = grown;
}
ctx->closure[ctx->closure_count++] = file;
return true;
}
/* Breadth first through literal `source` statements; each file enters once. Past the bound the
* process is not fully known, and a name no file so far declares stays unknown. */
/* Breadth first through literal `source` statements; each file enters once. */
static bool shell_build_closure(shell_ctx_t *ctx) {
cbm_source_walk_t *walk = ctx->walk;
ctx->closure = cbm_arena_calloc(walk->arena, SHELL_CLOSURE_MAX * sizeof(*ctx->closure));
if (!ctx->closure)
cbm_source_chain_t *seen = &ctx->closure_seen;
size_t capacity = 0;
if (!cbm_source_chain_first_visit(walk, seen, walk->file) ||
!shell_push_closure(ctx, &capacity, walk->file))
return false;
ctx->closure[ctx->closure_count++] = walk->file;
for (size_t at = 0; at < ctx->closure_count; at++) {
const char *owner = cbm_arena_sprintf(walk->arena, SHELL_SOURCES "%s", ctx->closure[at]);
if (!owner)
return false;
for (const cbm_source_decl_t *entry = cbm_source_lookup(walk, owner, ""); entry;
entry = entry->next) {
if (!entry->type || !cbm_source_lookup(walk, SHELL_FILES, entry->type) ||
shell_in_closure(ctx, entry->type))
if (!entry->type || !cbm_source_lookup(walk, SHELL_FILES, entry->type))
continue;
if (ctx->closure_count == SHELL_CLOSURE_MAX)
return true;
ctx->closure[ctx->closure_count++] = entry->type;
if (!cbm_source_chain_first_visit(walk, seen, entry->type)) {
if (seen->overflow)
return false;
continue;
}
if (!shell_push_closure(ctx, &capacity, entry->type))
return false;
}
}
return true;
@@ -332,10 +348,13 @@ static void shell_visit_command(shell_ctx_t *ctx, TSNode command) {
return;
}
const cbm_source_decl_t *found = NULL;
for (size_t i = 1; i < ctx->closure_count; i++) {
const char *owner = cbm_arena_sprintf(walk->arena, SHELL_FUNCTIONS "%s", ctx->closure[i]);
for (const cbm_source_decl_t *row = cbm_source_lookup(walk, SHELL_DECLARERS, word); row;
row = row->next) {
if (!strcmp(row->type, walk->file) || !cbm_source_chain_has(&ctx->closure_seen, row->type))
continue;
const char *owner = cbm_arena_sprintf(walk->arena, SHELL_FUNCTIONS "%s", row->type);
const cbm_source_decl_t *decl = owner ? cbm_source_lookup(walk, owner, word) : NULL;
if (!decl)
if (!decl || decl == found)
continue;
/* The later `source` wins at runtime, and order across files is not modelled. */
if (found && (!decl->qn || !found->qn || strcmp(decl->qn, found->qn))) {
+7 -11
View File
@@ -56,7 +56,7 @@
#define SWIFT_TARGET_KEY "@cbm:raw:swift-target:"
#define SWIFT_AMBIGUOUS_PREFIX "@cbm:"
enum { SWIFT_MAX_LABELS = 32, SWIFT_MAX_BASES = 32, SWIFT_MAX_INITIALIZER_CHAIN = 64 };
enum { SWIFT_MAX_LABELS = 32, SWIFT_MAX_INITIALIZER_CHAIN = 64 };
/* A type record of a struct: with no initializer anywhere it has one for its stored properties. */
enum { SWIFT_RECORD_STRUCT = 1u << 16 };
/* A parameter whose written type is a function: a trailing closure goes to one of these first.
@@ -599,8 +599,7 @@ static TSNode swift_body_of(TSNode node) {
}
static void swift_note_bases(cbm_source_walk_t *walk, TSNode node, cbm_source_decl_t *record) {
const char *found[SWIFT_MAX_BASES];
size_t used = 0;
cbm_source_names_t found = {0};
bool open = false;
uint32_t count = ts_node_named_child_count(node);
for (uint32_t i = 0; i < count; i++) {
@@ -608,17 +607,14 @@ static void swift_note_bases(cbm_source_walk_t *walk, TSNode node, cbm_source_de
if (!cbm_source_is(child, "inheritance_specifier"))
continue;
const char *text = swift_type_text(walk, cbm_source_field(child, "inherits_from"), 0);
if (!text || used == SWIFT_MAX_BASES)
open = true;
else
found[used++] = text;
open |= !text || !cbm_source_names_push(walk, &found, text);
}
const char **copy = cbm_source_table_alloc(walk, (used + 1) * sizeof(*copy));
const char **copy = cbm_source_table_alloc(walk, (found.count + 1) * sizeof(*copy));
if (!copy)
return;
for (size_t i = 0; i < used; i++)
copy[i] = cbm_source_table_intern(walk, found[i]);
copy[used] = NULL;
for (size_t i = 0; i < found.count; i++)
copy[i] = cbm_source_table_intern(walk, found.items[i]);
copy[found.count] = NULL;
record->bases = copy;
if (open)
record->flags |= CBM_SOURCE_DECL_F_OPEN;
+23 -17
View File
@@ -8,7 +8,7 @@
#include <string.h>
#include "syntax_index.h"
enum { BINDING_MAX_DEPTH = 512, BINDING_MAX_NODES = 1000000, BINDING_INITIAL_CAP = 32 };
enum { BINDING_MAX_DEPTH = 512, BINDING_INITIAL_CAP = 32 };
enum {
BINDING_DECLARATION = 1,
BINDING_IMPORT = 2,
@@ -1606,8 +1606,9 @@ static bool python_block_always_exits(TSNode block, unsigned depth) {
/* Every value this function returns, outside nested scopes. A bare return and
* None say nothing about the class of the others. Returns false when one is not
* an access path, or the function yields. */
static bool python_gather_returns(cbm_resolve_binding_index_t *index, TSNode node, const char **out,
uint32_t *count, uint32_t room, unsigned depth) {
static bool python_gather_returns(cbm_resolve_binding_index_t *index, TSNode node,
const char ***out, uint32_t *count, uint32_t *room,
unsigned depth) {
if (depth > 48)
return false;
for (uint32_t i = 0; i < ts_node_named_child_count(node); i++) {
@@ -1624,9 +1625,19 @@ static bool python_gather_returns(cbm_resolve_binding_index_t *index, TSNode nod
if (kind_is(value, "none"))
continue;
const char *chain = python_chain(index, value, 0);
if (!chain || *count >= room)
if (!chain)
return false;
out[(*count)++] = chain;
if (*count == *room) {
uint32_t capacity = *room ? *room * 2 : 4;
const char **items = cbm_arena_alloc(index->arena, capacity * sizeof(*items));
if (!items)
return false;
if (*count)
memcpy(items, *out, *count * sizeof(*items));
*out = items;
*room = capacity;
}
(*out)[(*count)++] = chain;
continue;
}
if (!python_gather_returns(index, child, out, count, room, depth + 1))
@@ -1638,9 +1649,8 @@ static bool python_gather_returns(cbm_resolve_binding_index_t *index, TSNode nod
/* Called once the function's own scope exists: its locals resolve the returns. */
static void python_note_returns(cbm_resolve_binding_index_t *index, TSNode function, size_t member,
uint32_t scope) {
enum { RETURNS_MAX = 16 };
const char *chains[RETURNS_MAX];
uint32_t count = 0;
const char **chains = NULL;
uint32_t count = 0, room = 0;
/* `-> Self` and `def f(self: T) -> T` return the receiver, whichever class that is. */
TSNode declared_return = field(function, "return_type");
binding_scope_t *body_scope = scope < index->scope_count ? &index->scopes[scope] : NULL;
@@ -1679,7 +1689,7 @@ static void python_note_returns(cbm_resolve_binding_index_t *index, TSNode funct
!strcmp(name, "__class_getitem__"));
if (member >= index->python_member_count ||
(!special && !ts_node_is_null(field(function, "return_type"))) ||
!python_gather_returns(index, field(function, "body"), chains, &count, RETURNS_MAX, 0) ||
!python_gather_returns(index, field(function, "body"), &chains, &count, &room, 0) ||
!count)
return;
const char **kept = cbm_arena_alloc(index->arena, (count + 1) * sizeof(*kept));
@@ -4059,10 +4069,9 @@ static void note_dart_import(cbm_resolve_binding_index_t *index, TSNode node) {
cbm_resolve_source_import_t import = {0};
TSTreeCursor cursor = ts_tree_cursor_new(node);
unsigned depth = 0;
uint32_t visited = 0;
for (;;) {
TSNode child = ts_tree_cursor_current_node(&cursor);
if (++visited > BINDING_MAX_NODES || depth >= BINDING_MAX_DEPTH) {
if (depth >= BINDING_MAX_DEPTH) {
fail_index(index, CBM_RESOLVE_ANALYSIS_LIMITED, "dart_import_walk_limit");
break;
}
@@ -5507,13 +5516,11 @@ static void walk_tree(cbm_resolve_binding_index_t *index, TSNode root) {
const char *kinds[BINDING_MAX_DEPTH] = {0};
TSTreeCursor cursor = ts_tree_cursor_new(root);
unsigned depth = 0;
uint32_t visited = 0;
for (;;) {
TSNode node = ts_tree_cursor_current_node(&cursor);
/* This walk is linear in the file. A node count would void the largest
* ECMAScript sources for their size alone; only nesting has a real bound. */
if ((++visited > BINDING_MAX_NODES && !javascript_language(index->language)) ||
depth >= BINDING_MAX_DEPTH) {
* sources for their size alone; only nesting has a real bound. */
if (depth >= BINDING_MAX_DEPTH) {
fail_index(index, CBM_RESOLVE_ANALYSIS_LIMITED, "binding_walk_limit");
break;
}
@@ -5716,11 +5723,10 @@ static void python_follow_write_aliases(cbm_resolve_binding_index_t *index) {
static void python_namespace_effects(cbm_resolve_binding_index_t *index, TSNode root) {
TSTreeCursor cursor = ts_tree_cursor_new(root);
unsigned depth = 0;
size_t visited = 0;
index->python_effects_complete = true;
for (;;) {
TSNode node = ts_tree_cursor_current_node(&cursor);
if (++visited > BINDING_MAX_NODES || depth > BINDING_MAX_DEPTH) {
if (depth > BINDING_MAX_DEPTH) {
index->python_effects_complete = false;
index->python_writes_unseen = true;
break;
+26 -2
View File
@@ -8,7 +8,7 @@
#include "stats.h"
#include "syntax_index.h"
enum { SOURCE_MAX_VISITS = 4000000, SOURCE_MAX_DEPTH = 400 };
enum { SOURCE_MAX_DEPTH = 400 };
struct cbm_source_table {
CBMArena arena;
@@ -196,7 +196,7 @@ void cbm_source_fail(cbm_source_walk_t *walk, const char *reason) {
bool cbm_source_step(cbm_source_walk_t *walk, unsigned depth) {
if (walk->failure)
return false;
if (++walk->visits > SOURCE_MAX_VISITS || depth > SOURCE_MAX_DEPTH) {
if (depth > SOURCE_MAX_DEPTH) {
cbm_source_fail(walk, "source_adapter_walk_limit");
return false;
}
@@ -240,6 +240,15 @@ static bool chain_grow(cbm_source_walk_t *walk, cbm_source_chain_t *chain) {
return true;
}
bool cbm_source_chain_has(const cbm_source_chain_t *chain, const char *owner) {
if (chain->capacity)
return *chain_slot(chain->slots, chain->capacity, owner) != NULL;
for (size_t i = 0; i < chain->count; i++)
if (!strcmp(chain->seen[i], owner))
return true;
return false;
}
bool cbm_source_chain_first_visit(cbm_source_walk_t *walk, cbm_source_chain_t *chain,
const char *owner) {
if (!chain->capacity) {
@@ -264,6 +273,21 @@ bool cbm_source_chain_first_visit(cbm_source_walk_t *walk, cbm_source_chain_t *c
return true;
}
bool cbm_source_names_push(cbm_source_walk_t *walk, cbm_source_names_t *names, const char *name) {
if (names->count == names->capacity) {
size_t capacity = names->capacity ? names->capacity * 2 : 8;
const char **items = cbm_arena_alloc(walk->arena, capacity * sizeof(*items));
if (!items)
return false;
if (names->count)
memcpy(items, names->items, names->count * sizeof(*items));
names->items = items;
names->capacity = capacity;
}
names->items[names->count++] = name;
return true;
}
bool cbm_source_token(TSNode node, const char *token) {
uint32_t count = ts_node_child_count(node);
for (uint32_t i = 0; i < count; i++) {
+11 -2
View File
@@ -71,7 +71,6 @@ typedef struct cbm_source_walk {
const cbm_source_table_t *table; /* phase two only */
cbm_source_unit_t *unit; /* phase two only */
TSNode root;
uint64_t visits;
const char *failure;
const char *top_owner; /* collect: the owner a language files its top-level names under */
CBMHashTable *def_index; /* lazily built, owned by the walk */
@@ -143,6 +142,9 @@ typedef struct {
bool cbm_source_chain_first_visit(cbm_source_walk_t *walk, cbm_source_chain_t *chain,
const char *owner);
/* Whether this walk has visited the name. */
bool cbm_source_chain_has(const cbm_source_chain_t *chain, const char *owner);
/* A walker that recurses: first_visit, and one level deeper until the matching leave. */
static inline bool cbm_source_chain_enter(cbm_source_walk_t *walk, cbm_source_chain_t *chain,
const char *owner) {
@@ -159,6 +161,13 @@ static inline void cbm_source_chain_leave(cbm_source_chain_t *chain) {
chain->depth--;
}
/* A list that grows on the walk's arena, as a declaration's parent names are gathered. */
typedef struct {
const char **items;
size_t count, capacity;
} cbm_source_names_t;
bool cbm_source_names_push(cbm_source_walk_t *walk, cbm_source_names_t *names, const char *name);
/* Syntax helpers shared by the language files; none of them decides anything. */
static inline bool cbm_source_is(TSNode node, const char *kind) {
return !ts_node_is_null(node) && strcmp(ts_node_type(node), kind) == 0;
@@ -172,7 +181,7 @@ static inline TSNode cbm_source_field(TSNode node, const char *name) {
TSNode cbm_source_named_field(TSNode node, const char *name);
const char *cbm_source_text(cbm_source_walk_t *walk, TSNode node);
const char *cbm_source_lower(cbm_source_walk_t *walk, const char *text);
/* True while the file's walk stays inside its analysis budget. */
/* True while the file's walk is not nested past what its stack can take. */
bool cbm_source_step(cbm_source_walk_t *walk, unsigned depth);
void cbm_source_fail(cbm_source_walk_t *walk, const char *reason);
bool cbm_source_token(TSNode node, const char *token);
+110 -6
View File
@@ -58,6 +58,7 @@
#include "lsp/rust_proof.h"
#include "tree_sitter/api.h"
#include "pipeline/pass_lsp_cross.h"
#include "resolve/source_adapter_internal.h"
#include "pipeline/pipeline.h"
#include "pipeline/pipeline_internal.h"
#include "store/store.h"
@@ -3520,7 +3521,7 @@ TEST(complexity_dart_barrel_exports_are_near_linear) {
/* A type's ancestors, however many, are each read once. `deep` is a line of classes, one a level;
* otherwise one class implements them all, which recurses one level. The member is nobody's, so
* the walk reads them all. */
static uint64_t cx_dart_ancestors_work(int ancestors, bool deep) {
static uint64_t cx_dart_ancestors_work(int ancestors, bool deep, bool asked) {
size_t room = (size_t)ancestors * 64 + 256;
char *text = malloc(room);
if (!text)
@@ -3532,13 +3533,14 @@ static uint64_t cx_dart_ancestors_work(int ancestors, bool deep) {
i - 1)
: (size_t)snprintf(text + used, room - used, "class C%d {}\n", i);
if (deep) {
used += (size_t)snprintf(text + used, room - used, "void use(C%d c) { c.nobody(); }\n",
ancestors - 1);
used += (size_t)snprintf(text + used, room - used, "void use(C%d c) { %s }\n",
ancestors - 1, asked ? "c.nobody();" : "");
} else {
used += (size_t)snprintf(text + used, room - used, "class All implements C0");
for (int i = 1; i < ancestors; i++)
used += (size_t)snprintf(text + used, room - used, ", C%d", i);
used += (size_t)snprintf(text + used, room - used, " {}\nvoid use(All c) { c.nobody(); }\n");
used += (size_t)snprintf(text + used, room - used, " {}\nvoid use(All c) { %s }\n",
asked ? "c.nobody();" : "");
}
CxSourceFile file = {.text = text};
snprintf(file.path, sizeof(file.path), "lib/chain.dart");
@@ -3546,9 +3548,15 @@ static uint64_t cx_dart_ancestors_work(int ancestors, bool deep) {
free(text);
return work;
}
/* What asking one member costs, apart from reading the file. */
static uint64_t cx_dart_walk_work(int ancestors, bool deep) {
uint64_t asked = cx_dart_ancestors_work(ancestors, deep, true);
uint64_t silent = cx_dart_ancestors_work(ancestors, deep, false);
return asked > silent ? asked - silent : 0;
}
static bool cx_dart_ancestors_are_near_linear(int ancestors, bool deep) {
uint64_t first = cx_dart_ancestors_work(ancestors, deep);
uint64_t second = cx_dart_ancestors_work(ancestors * 2, deep);
uint64_t first = cx_dart_walk_work(ancestors, deep);
uint64_t second = cx_dart_walk_work(ancestors * 2, deep);
double ratio = cx_ratio((double)second, (double)first);
printf(" dart %s ancestors %d: probes %llu -> %llu (ratio %.2f)\n", deep ? "deep" : "wide",
ancestors, (unsigned long long)first, (unsigned long long)second, ratio);
@@ -3562,6 +3570,100 @@ TEST(complexity_source_chain_walk_reads_every_ancestor_once) {
PASS();
}
/* A process sourcing every library, and calling something of each. */
static uint64_t cx_shell_sources_work(int sourced) {
CxSourceFile *files = calloc((size_t)sourced + 1, sizeof(*files));
size_t room = (size_t)sourced * 48 + 64;
char *text = malloc(room);
if (!files || !text) {
free(files);
free(text);
return 0;
}
size_t used = 0;
bool ok = true;
for (int i = 0; ok && i < sourced; i++) {
used += (size_t)snprintf(text + used, room - used, ". ./s%d.sh\n", i);
snprintf(files[i].path, sizeof(files[i].path), "s%d.sh", i);
files[i].text = malloc(48);
ok = files[i].text != NULL;
if (ok)
snprintf(files[i].text, 48, "f%d() { :; }\n", i);
}
used += (size_t)snprintf(text + used, room - used, "run() {\n");
for (int i = 0; ok && i < sourced; i++)
used += (size_t)snprintf(text + used, room - used, " f%d\n", i);
used += (size_t)snprintf(text + used, room - used, "}\n");
snprintf(files[sourced].path, sizeof(files[sourced].path), "main.sh");
files[sourced].text = text;
uint64_t work = ok ? cx_source_work(CBM_LANG_BASH, files, sourced + 1) : 0;
for (int i = 0; i <= sourced; i++)
free(files[i].text);
free(files);
return work;
}
TEST(complexity_shell_commands_of_a_big_process_are_near_linear) {
uint64_t first = cx_shell_sources_work(500);
uint64_t second = cx_shell_sources_work(1000);
double ratio = cx_ratio((double)second, (double)first);
printf(" shell closure commands: probes %llu -> %llu (ratio %.2f)\n",
(unsigned long long)first, (unsigned long long)second, ratio);
ASSERT_GTE(first, (uint64_t)500);
ASSERT_TRUE(ratio >= CX_RATIO_LO && ratio <= CX_RATIO_HI);
PASS();
}
/* The seen set itself: names enter once, and a long walk stays cheap per name. */
static uint64_t cx_seen_set_work(int names, bool *held) {
CBMArena arena;
cbm_arena_init(&arena);
cbm_source_walk_t walk = {.arena = &arena};
cbm_source_chain_t chain = {0};
cbm_resolve_stats_t stats;
cbm_resolve_stats_reset(&stats);
cbm_resolve_stats_t *previous = cbm_resolve_stats_bind(&stats);
char **texts = malloc((size_t)names * sizeof(*texts));
*held = texts != NULL;
for (int i = 0; *held && i < names; i++) {
texts[i] = cbm_arena_sprintf(&arena, "lib/deep/n%d.dart", i);
*held &= cbm_source_chain_first_visit(&walk, &chain, texts[i]);
*held &= cbm_source_chain_has(&chain, texts[i]);
}
for (int i = 0; *held && i < names; i++)
*held &= !cbm_source_chain_first_visit(&walk, &chain, texts[i]);
*held = *held && !chain.overflow && chain.count == (size_t)names;
cbm_resolve_stats_bind(previous);
uint64_t work = cx_stat_work(&stats, CBM_RESOLVE_STAT_LOOKUP_PROBES);
free(texts);
cbm_arena_destroy(&arena);
return work;
}
TEST(complexity_source_chain_seen_set_grows) {
bool first_held = false, second_held = false;
uint64_t first = cx_seen_set_work(5000, &first_held);
uint64_t second = cx_seen_set_work(10000, &second_held);
double ratio = cx_ratio((double)second, (double)first);
printf(" seen set: probes %llu -> %llu (ratio %.2f)\n", (unsigned long long)first,
(unsigned long long)second, ratio);
ASSERT_TRUE(first_held && second_held);
ASSERT_TRUE(ratio >= CX_RATIO_LO && ratio <= CX_RATIO_HI);
/* The depth guard: one name past it stops the walk and says so. */
CBMArena arena;
cbm_arena_init(&arena);
cbm_source_walk_t walk = {.arena = &arena};
cbm_source_chain_t chain = {0};
bool entered = true;
for (int i = 0; i < CBM_SOURCE_CHAIN_DEPTH_MAX; i++)
entered &= cbm_source_chain_enter(&walk, &chain, cbm_arena_sprintf(&arena, "n%d", i));
bool past = cbm_source_chain_enter(&walk, &chain, "past");
bool flagged = chain.overflow;
cbm_arena_destroy(&arena);
ASSERT_TRUE(entered);
ASSERT_FALSE(past);
ASSERT_TRUE(flagged);
PASS();
}
/* A function of route registrations asks, for each, what its receiver was bound to. */
CX_LINE(cx_line_go_route, " r.GET(\"/p%d\", h)\n")
CX_LINE3(cx_line_go_group, " g%d := r.Group(\"/g%d\")\n g%d.GET(\"/x\", h)\n")
@@ -3720,6 +3822,8 @@ SUITE(complexity) {
RUN_TEST(complexity_swift_type_bases_are_near_linear);
RUN_TEST(complexity_dart_barrel_exports_are_near_linear);
RUN_TEST(complexity_source_chain_walk_reads_every_ancestor_once);
RUN_TEST(complexity_source_chain_seen_set_grows);
RUN_TEST(complexity_shell_commands_of_a_big_process_are_near_linear);
RUN_TEST(complexity_go_route_bindings_are_near_linear);
RUN_TEST(complexity_flat_preceding_label_is_linear);
RUN_TEST(complexity_import_map_is_linear_in_a_files_imports);
+217 -10
View File
@@ -6730,7 +6730,7 @@ TEST(resolver_source_dart_barrel_of_any_width_and_depth) {
const char *extra = i == 150 ? "void hiddenFar() {}\n"
: i == 151 ? "void shownFar() {}\nvoid notShownFar() {}\n"
: i == 160 || i == 170 ? "void twice() {}\n"
: i == 190 ? "class Far {\n void go() {}\n}\n"
: i == 190 ? "class Wide {\n void go() {}\n}\n"
: "";
sources[count++] = (RQ_Source){.path = sl_sprintf("lib/a%d.dart", i),
.text = sl_sprintf("void f%d() {}\n%s", i, extra)};
@@ -6754,8 +6754,8 @@ TEST(resolver_source_dart_barrel_of_any_width_and_depth) {
"void callsShown() { shownFar(); }\n"
"void callsNotShown() { notShownFar(); }\n"
"void callsTwice() { twice(); }\n"
"void callsClass() { Far(); }\n"
"void callsMember(Far far) { far.go(); }\n"
"void callsClass() { Wide(); }\n"
"void callsMember(Wide w) { w.go(); }\n"
"void callsDeep() { deepName(); }\n"
"void callsShadowed() { shadowed(); }\n"
"void callsNobody() { nobody(); }\n")};
@@ -6768,10 +6768,10 @@ TEST(resolver_source_dart_barrel_of_any_width_and_depth) {
"a199.f199", "Function", 1, "void f199() {}"),
SL_CALL_AT("barrel-show-passes", "lib/main.dart", "main.callsShown", "shownFar()", 0,
"lib/a151.dart", "a151.shownFar", "Function", 2, "void shownFar() {}"),
SL_CALL_AT("barrel-class", "lib/main.dart", "main.callsClass", "Far()", 0, "lib/a190.dart",
"a190.Far", "Class", 2, "class Far {"),
SL_CALL_AT("barrel-class-member", "lib/main.dart", "main.callsMember", "far.go", 0,
"lib/a190.dart", "Far.go", "Method", 3, " void go() {}"),
SL_CALL_AT("barrel-class", "lib/main.dart", "main.callsClass", "Wide()", 0,
"lib/a190.dart", "a190.Wide", "Class", 2, "class Wide {"),
SL_CALL_AT("barrel-class-member", "lib/main.dart", "main.callsMember", "w.go", 0,
"lib/a190.dart", "Wide.go", "Method", 3, " void go() {}"),
SL_CALL_AT("chain-end", "lib/main.dart", "main.callsDeep", "deepName()", 0,
"lib/deep199.dart", "deep199.deepName", "Function", 2, "void deepName() {}"),
SL_CALL_AT("chain-shallower-hides-deeper", "lib/main.dart", "main.callsShadowed",
@@ -6843,11 +6843,12 @@ TEST(resolver_source_dart_inheritance_of_any_depth) {
TEST(resolver_source_php_inheritance_of_any_depth) {
static const RQ_Expected expected[] = {
SL_CALL_AT("member-past-128-levels", "src/chain.php", "chain.useIt", "$c->rootM", 0,
SL_CALL_AT("member-past-128-levels", "src/chain.php", "chain.useIt", "$c->rootM()", 0,
"src/chain.php", "C0.rootM", "Method", 3, " public function rootM() {}"),
};
static const RQ_Forbidden forbidden[] = {
SL_DENY_AT("member-nobody-declares", "src/chain.php", "chain.useAbsent", "$c->neverM", 0),
SL_DENY_AT("member-nobody-declares", "src/chain.php", "chain.useAbsent", "$c->neverM()",
0),
};
char *text = sl_chain_text("<?php\nclass C0 {\n public function rootM() {}\n}\n",
"class C%d extends C%d {}\n",
@@ -6881,7 +6882,7 @@ TEST(resolver_source_swift_inheritance_of_any_depth) {
TEST(resolver_source_ruby_inheritance_of_any_depth) {
static const RQ_Expected expected[] = {
SL_CALL_AT("member-past-128-levels", "lib/chain.rb", "C199.use_it", "root_m", 0,
SL_CALL_AT("member-past-128-levels", "lib/chain.rb", "C199.use_it", "root_m", 1,
"lib/chain.rb", "C0.root_m", "Method", 2, " def root_m; end"),
};
char *text = sl_chain_text("class C0\n def root_m; end\nend\n", "class C%d < C%d; end\n",
@@ -6911,6 +6912,207 @@ TEST(resolver_source_scala_inheritance_of_any_depth) {
PASS();
}
/* ── How many of a thing there are decides nothing ─────────────────── */
/* A file of `lines` copies of one line, each @ its number, between a head and a tail. */
static char *sl_repeat_text(const char *head, const char *line, int lines, const char *tail) {
char *text = NULL;
size_t used = 0, capacity = 0;
sl_append(&text, &used, &capacity, head);
for (int i = 0; i < lines; i++) {
char number[16];
snprintf(number, sizeof(number), "%d", i);
for (const char *at = line; *at; at++) {
char one[2] = {*at, 0};
sl_append(&text, &used, &capacity, *at == '@' ? number : one);
}
}
sl_append(&text, &used, &capacity, tail);
return text;
}
TEST(resolver_source_php_a_variable_of_many_writes_keeps_its_type) {
static const RQ_Expected expected[] = {
SL_CALL_AT("twelve-writes-of-one-class", "src/writes.php", "writes.same", "$x->run()", 0,
"src/writes.php", "A.run", "Method", 2, "class A { public function run() {} }"),
};
static const RQ_Forbidden forbidden[] = {
SL_DENY_AT("a-late-write-of-another-class", "src/writes.php", "writes.mixed", "$y->run()",
0),
};
char *writes = sl_repeat_text("", " $x = new A();\n", 12, "");
char *other = sl_repeat_text("", " $y = new A();\n", 11, " $y = new B();\n");
char *text = sl_sprintf("<?php\n"
"class A { public function run() {} }\n"
"class B { public function run() {} }\n"
"function same() {\n%s $x->run();\n}\n"
"function mixed() {\n%s $y->run();\n}\n",
writes, other);
free(writes);
free(other);
RQ_Source sources[] = {{.path = "src/writes.php", .text = text}};
bool ok = sl_check("php-writes", ".php", "<?php\nfunction filler() {}\n", sources, 1, expected,
sizeof(expected) / sizeof(expected[0]), forbidden,
sizeof(forbidden) / sizeof(forbidden[0]));
free(text);
ASSERT(ok);
PASS();
}
TEST(resolver_source_php_a_class_of_many_traits_reads_them_all) {
static const RQ_Expected expected[] = {
SL_CALL_AT("the-seventieth-trait", "src/traits.php", "Big.go", "$this->inTrait69()", 0,
"src/traits.php", "T69.inTrait69", "Method", 71,
"trait T69 { public function inTrait69() {} }"),
};
static const RQ_Forbidden forbidden[] = {
SL_DENY_AT("nobody-declares-it", "src/traits.php", "Big.go", "$this->inNoTrait()", 0),
};
char *traits = sl_repeat_text("<?php\n", "trait T@ { public function inTrait@() {} }\n", 70,
"class Big {\n");
char *uses = sl_repeat_text("", " use T@;\n", 70,
" public function go() {\n $this->inTrait69();\n"
" $this->inNoTrait();\n }\n}\n");
char *text = sl_sprintf("%s%s", traits, uses);
free(traits);
free(uses);
RQ_Source sources[] = {{.path = "src/traits.php", .text = text}};
bool ok = sl_check("php-traits", ".php", "<?php\nfunction filler() {}\n", sources, 1, expected,
sizeof(expected) / sizeof(expected[0]), forbidden,
sizeof(forbidden) / sizeof(forbidden[0]));
free(text);
ASSERT(ok);
PASS();
}
TEST(resolver_source_dart_a_class_of_many_bases_reads_them_all) {
static const RQ_Expected expected[] = {
SL_CALL_AT("the-fortieth-interface", "lib/bases.dart", "bases.use", "b.inBase39", 0,
"lib/bases.dart", "B39.inBase39", "Method", 40,
"class B39 { void inBase39() {} }"),
};
static const RQ_Forbidden forbidden[] = {
SL_DENY_AT("nobody-declares-it", "lib/bases.dart", "bases.useAbsent", "b.inNoBase", 0),
};
char *classes = sl_repeat_text("", "class B@ { void inBase@() {} }\n", 40, "class Big implements Z");
char *list = sl_repeat_text("", ", B@", 40,
" {}\nclass Z {}\nvoid use(Big b) { b.inBase39(); }\n"
"void useAbsent(Big b) { b.inNoBase(); }\n");
char *text = sl_sprintf("%s%s", classes, list);
free(classes);
free(list);
RQ_Source sources[] = {{.path = "pubspec.yaml", .text = "name: bases\n"},
{.path = "lib/bases.dart", .text = text}};
bool ok = sl_check("dart-bases", ".dart", "void filler() {}\n", sources, 2, expected,
sizeof(expected) / sizeof(expected[0]), forbidden,
sizeof(forbidden) / sizeof(forbidden[0]));
free(text);
ASSERT(ok);
PASS();
}
TEST(resolver_source_shell_a_process_of_many_sourced_files) {
enum { SOURCED = 300 };
static const RQ_Expected expected[] = {
SL_CALL_AT("the-three-hundredth-file", "main.sh", "main.run", "in299", 0, "lib/s299.sh",
"s299.in299", "Function", 1, "in299() { :; }"),
SL_CALL_AT("through-a-later-file", "main.sh", "main.run", "further", 0, "lib/past.sh",
"past.further", "Function", 1, "further() { :; }"),
};
static const RQ_Forbidden forbidden[] = {
SL_DENY_AT("a-file-nothing-sources", "main.sh", "main.run", "notSourced", 0),
SL_DENY_AT("two-files-declare-it", "main.sh", "main.run", "inTwo", 0),
};
size_t count = 0;
RQ_Source *sources = calloc((size_t)SOURCED + 4, sizeof(*sources));
ASSERT_NOT_NULL(sources);
for (int i = 0; i < SOURCED; i++) {
const char *extra = i == 299 ? ". lib/past.sh\n"
: i == 280 || i == 290 ? "inTwo() { :; }\n"
: "";
sources[count++] = (RQ_Source){.path = sl_sprintf("lib/s%d.sh", i),
.text = sl_sprintf("in%d() { :; }\n%s", i, extra)};
}
sources[count++] =
(RQ_Source){.path = strdup("lib/past.sh"), .text = strdup("further() { :; }\n")};
sources[count++] =
(RQ_Source){.path = strdup("lib/alone.sh"), .text = strdup("notSourced() { :; }\n")};
sources[count++] = (RQ_Source){
.path = strdup("main.sh"),
.text = sl_repeat_text("", ". lib/s@.sh\n", SOURCED,
"run() {\n in299\n further\n notSourced\n inTwo\n}\n")};
bool ok = sl_check("shell-closure", ".sh", "filler() { :; }\n", sources, count, expected,
sizeof(expected) / sizeof(expected[0]), forbidden,
sizeof(forbidden) / sizeof(forbidden[0]));
sl_free_sources(sources, count);
ASSERT(ok);
PASS();
}
/* A file's own extension is what its import's local name comes to, and it is nobody's name: `sh`,
* `tcl` and `dart` called in such a file mean what they mean without it. */
TEST(resolver_source_an_extension_is_no_local_name) {
/* Nothing here declares sh, tcl or dart, and each file has two rows whose local name is one. */
static const RQ_Source plain[] = {
{.path = "pubspec.yaml", .text = "name: ext\n"},
{.path = "sh1/main.sh",
.text = "source ./a.sh\nsource ./b.sh\nrun() {\n sh ./x.sh\n helper\n}\n"},
{.path = "sh1/a.sh", .text = "helper() {\n echo a\n}\n"},
{.path = "sh1/b.sh", .text = "other() {\n echo b\n}\n"},
{.path = "tcl1/main.tcl",
.text = "source a.tcl\nsource b.tcl\nproc run {} {\n tcl foo\n helper\n}\n"},
{.path = "tcl1/a.tcl", .text = "proc helper {} { return 1 }\n"},
{.path = "tcl1/b.tcl", .text = "proc other {} { return 1 }\n"},
{.path = "lib/d1.dart",
.text = "part 'p1.dart';\npart 'p2.dart';\nvoid run() {\n dart();\n helper();\n}\n"},
{.path = "lib/p1.dart", .text = "part of 'd1.dart';\nvoid helper() {}\n"},
{.path = "lib/p2.dart", .text = "part of 'd1.dart';\nvoid other() {}\n"},
};
static const RQ_Expected plain_expected[] = {
SL_CALL_AT("sourced-function", "sh1/main.sh", "main.run", "helper", 0, "sh1/a.sh",
"a.helper", "Function", 1, "helper() {"),
SL_CALL_AT("sourced-proc", "tcl1/main.tcl", "main.run", "helper", 0, "tcl1/a.tcl",
"a.helper", "Function", 1, "proc helper {} { return 1 }"),
SL_CALL_AT("function-of-a-part", "lib/d1.dart", "d1.run", "helper()", 0, "lib/p1.dart",
"p1.helper", "Function", 2, "void helper() {}"),
};
static const RQ_Forbidden plain_forbidden[] = {
SL_DENY_AT("the-outside-command-sh", "sh1/main.sh", "main.run", "sh ./x.sh", 0),
SL_DENY_AT("the-outside-command-tcl", "tcl1/main.tcl", "main.run", "tcl foo", 0),
SL_DENY_AT("nobody-declares-dart", "lib/d1.dart", "d1.run", "dart()", 0),
};
ASSERT(sl_check("extension-names", ".dart", "void filler() {}\n", plain,
sizeof(plain) / sizeof(plain[0]), plain_expected,
sizeof(plain_expected) / sizeof(plain_expected[0]), plain_forbidden,
sizeof(plain_forbidden) / sizeof(plain_forbidden[0])));
/* Here a sourced file or a part does declare one, and the call means it. */
static const RQ_Source declared[] = {
{.path = "pubspec.yaml", .text = "name: ext2\n"},
{.path = "sh2/main.sh", .text = "source ./a.sh\nsource ./b.sh\nrun() {\n sh ./x.sh\n}\n"},
{.path = "sh2/a.sh", .text = "sh() {\n echo mine\n}\n"},
{.path = "sh2/b.sh", .text = "other() {\n echo b\n}\n"},
{.path = "tcl2/main.tcl", .text = "source a.tcl\nsource b.tcl\nproc run {} {\n tcl foo\n}\n"},
{.path = "tcl2/a.tcl", .text = "proc tcl {x} { return 1 }\n"},
{.path = "tcl2/b.tcl", .text = "proc other {} { return 1 }\n"},
{.path = "lib/d2.dart",
.text = "part 'p3.dart';\npart 'p4.dart';\nvoid run2() {\n dart();\n}\n"},
{.path = "lib/p3.dart", .text = "part of 'd2.dart';\nvoid dart() {}\n"},
{.path = "lib/p4.dart", .text = "part of 'd2.dart';\nvoid other4() {}\n"},
};
static const RQ_Expected declared_expected[] = {
SL_CALL_AT("a-function-really-called-sh", "sh2/main.sh", "main.run", "sh ./x.sh", 0,
"sh2/a.sh", "a.sh", "Function", 1, "sh() {"),
SL_CALL_AT("a-proc-really-called-tcl", "tcl2/main.tcl", "main.run", "tcl foo", 0,
"tcl2/a.tcl", "a.tcl", "Function", 1, "proc tcl {x} { return 1 }"),
SL_CALL_AT("a-function-really-called-dart", "lib/d2.dart", "d2.run2", "dart()", 0,
"lib/p3.dart", "p3.dart", "Function", 2, "void dart() {}"),
};
ASSERT(sl_check("extension-names-declared", ".dart", "void filler() {}\n", declared,
sizeof(declared) / sizeof(declared[0]), declared_expected,
sizeof(declared_expected) / sizeof(declared_expected[0]), NULL, 0));
PASS();
}
SUITE(resolver_source_langs) {
RUN_TEST(resolver_source_elixir_modules_aliases_imports_and_arity);
RUN_TEST(resolver_source_erlang_module_arity_export_and_import);
@@ -6956,6 +7158,11 @@ SUITE(resolver_source_langs) {
RUN_TEST(resolver_source_swift_inheritance_of_any_depth);
RUN_TEST(resolver_source_ruby_inheritance_of_any_depth);
RUN_TEST(resolver_source_scala_inheritance_of_any_depth);
RUN_TEST(resolver_source_php_a_variable_of_many_writes_keeps_its_type);
RUN_TEST(resolver_source_php_a_class_of_many_traits_reads_them_all);
RUN_TEST(resolver_source_dart_a_class_of_many_bases_reads_them_all);
RUN_TEST(resolver_source_shell_a_process_of_many_sourced_files);
RUN_TEST(resolver_source_an_extension_is_no_local_name);
RUN_TEST(resolver_source_scala_packages_precedence_and_receivers);
RUN_TEST(resolver_source_haskell_imports_and_scope);
RUN_TEST(resolver_source_ocaml_sequential_scope_and_modules);