mirror of
https://github.com/DeusData/codebase-memory-mcp.git
synced 2026-11-04 10:48:20 +00:00
fix(go): a bare init never binds to a package init or an init method
Go gives `init` no binding in the package block: init functions cannot
be referred to from anywhere in a program, and a method or field named
init needs a selector. A bare `init` is therefore always a local name -
a closure (`init := func(ctx) {...}; init(ctx)`), a parameter, or the
`package init` clause of a directory named init.
Before #1915 same_module bound such a name to the flat package init.
With per-file init QNs it now falls through to the short-name fallback
and binds unrelated methods named init. On kubernetes that minted 24
CALLS (22 e2e storage DefineTests closures among them), 28 WRITES,
7 USAGE and one derived TESTS edge, all false.
cbm_go_suppress_bare_init_bind refuses, for a bare Go `init`, any
registry binding onto a Function (every Go Function named init is a
package init) and a weak short-name binding onto a Method or Field.
lsp_* answers and selector calls (`w.init()`) are untouched. Wired at
the CALLS, USAGE and READS/WRITES seams of both resolvers.
Kubernetes, full index, main vs branch: only those 60 edges removed,
nodes identical, no resolver edge added; the 14 LSP-typed selector
CALLS into init methods are unchanged.
Tests: pipeline_go_bare_init_binds_nothing (+ parallel twin) are RED
with the predicate disabled (closure CALLS, parameter CALLS, closure
WRITES, package-clause USAGE onto Watcher.init) and GREEN with it; the
selector control keeps Watcher.Start/Boot -> Watcher.init.
go_bare_init_never_binds_init_namesake pins the predicate.
Refs #1915
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
This commit is contained in:
1 parent
9faa3e5c06
commit
4a78fe6a6b
7 files changed
+327
No files matched your search
@@ -1344,6 +1344,14 @@ static int resolve_single_call(cbm_pipeline_ctx_t *ctx, CBMCall *call, const CBM
|
|||||||
if (cbm_java_suppress_call_to_data_member(lang == CBM_LANG_JAVA, target_node->label)) {
|
if (cbm_java_suppress_call_to_data_member(lang == CBM_LANG_JAVA, target_node->label)) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
/* A bare Go `init(...)` calls a local closure or parameter: never a
|
||||||
|
* package init (unreferable in Go) and never a same-named method found
|
||||||
|
* by name alone — see cbm_go_suppress_bare_init_bind. MUST match
|
||||||
|
* pass_parallel.c exactly. */
|
||||||
|
if (cbm_go_suppress_bare_init_bind(lang == CBM_LANG_GO, call->is_method, call->callee_name,
|
||||||
|
res.strategy, target_node->label)) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
evidence->candidate_qn = target_node->qualified_name;
|
evidence->candidate_qn = target_node->qualified_name;
|
||||||
evidence->calls_emitted |=
|
evidence->calls_emitted |=
|
||||||
emit_classified_edge(ctx, call, source_node, target_node, &res, module_qn, imp_keys,
|
emit_classified_edge(ctx, call, source_node, target_node, &res, module_qn, imp_keys,
|
||||||
|
|||||||
@@ -3629,6 +3629,13 @@ static void resolve_file_calls(resolve_ctx_t *rc, resolve_worker_state_t *ws, CB
|
|||||||
* Variable or a Field. */
|
* Variable or a Field. */
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
if (target_node && source_node->id != target_node->id &&
|
||||||
|
cbm_go_suppress_bare_init_bind(lang == CBM_LANG_GO, call->is_method, call->callee_name,
|
||||||
|
res.strategy, target_node->label)) {
|
||||||
|
/* Same guard as pass_calls.c — a bare Go `init` is a local name,
|
||||||
|
* never a package init or a method found by name alone. */
|
||||||
|
continue;
|
||||||
|
}
|
||||||
if (!target_node || source_node->id == target_node->id) {
|
if (!target_node || source_node->id == target_node->id) {
|
||||||
/* HTTP/ASYNC calls to an EXTERNAL client library (`requests.get(url)`)
|
/* HTTP/ASYNC calls to an EXTERNAL client library (`requests.get(url)`)
|
||||||
* resolve to an unindexed QN (target_node == NULL), but their edge
|
* resolve to an unindexed QN (target_node == NULL), but their edge
|
||||||
@@ -3805,6 +3812,12 @@ static void resolve_file_usages(resolve_ctx_t *rc, resolve_worker_state_t *ws,
|
|||||||
if (tgt && cbm_suppress_bare_field_ref(lang, usage->is_member_access, tgt->label)) {
|
if (tgt && cbm_suppress_bare_field_ref(lang, usage->is_member_access, tgt->label)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
/* A bare Go `init` names a local (cbm_go_suppress_bare_init_bind).
|
||||||
|
* Mirrors the sequential twin (pass_usages.c). */
|
||||||
|
if (tgt && cbm_go_suppress_bare_init_bind(lang == CBM_LANG_GO, usage->is_member_access,
|
||||||
|
usage->ref_name, res.strategy, tgt->label)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
if (usage->semantic_reference_blocked && (usage->semantic_reference_local_shadow ||
|
if (usage->semantic_reference_blocked && (usage->semantic_reference_local_shadow ||
|
||||||
cbm_pipeline_node_is_callable_target(tgt))) {
|
cbm_pipeline_node_is_callable_target(tgt))) {
|
||||||
continue;
|
continue;
|
||||||
@@ -3933,6 +3946,12 @@ static void resolve_file_rw(resolve_ctx_t *rc, resolve_worker_state_t *ws, CBMFi
|
|||||||
if (cbm_suppress_bare_field_ref(lang, rw->is_member_access, tgt->label)) {
|
if (cbm_suppress_bare_field_ref(lang, rw->is_member_access, tgt->label)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
/* A bare Go `init` names a local (cbm_go_suppress_bare_init_bind).
|
||||||
|
* Mirrors the sequential twin (pass_usages.c). */
|
||||||
|
if (cbm_go_suppress_bare_init_bind(lang == CBM_LANG_GO, rw->is_member_access, rw->var_name,
|
||||||
|
res.strategy, tgt->label)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
const char *etype = rw->is_write ? "WRITES" : "READS";
|
const char *etype = rw->is_write ? "WRITES" : "READS";
|
||||||
cbm_gbuf_insert_edge(ws->local_edge_buf, src->id, tgt->id, etype, "{}");
|
cbm_gbuf_insert_edge(ws->local_edge_buf, src->id, tgt->id, etype, "{}");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -288,6 +288,12 @@ static int resolve_usage_edges(cbm_pipeline_ctx_t *ctx, const CBMFileResult *res
|
|||||||
if (tgt && cbm_suppress_bare_field_ref(lang, usage->is_member_access, tgt->label)) {
|
if (tgt && cbm_suppress_bare_field_ref(lang, usage->is_member_access, tgt->label)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
/* A bare Go `init` names a local, never a package init or an init
|
||||||
|
* method (cbm_go_suppress_bare_init_bind). Mirrors pass_parallel.c. */
|
||||||
|
if (tgt && cbm_go_suppress_bare_init_bind(lang == CBM_LANG_GO, usage->is_member_access,
|
||||||
|
usage->ref_name, res.strategy, tgt->label)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
if (usage->semantic_reference_blocked && (usage->semantic_reference_local_shadow ||
|
if (usage->semantic_reference_blocked && (usage->semantic_reference_local_shadow ||
|
||||||
cbm_pipeline_node_is_callable_target(tgt))) {
|
cbm_pipeline_node_is_callable_target(tgt))) {
|
||||||
continue;
|
continue;
|
||||||
@@ -420,6 +426,13 @@ static int resolve_rw_edges(cbm_pipeline_ctx_t *ctx, const CBMFileResult *result
|
|||||||
if (cbm_suppress_bare_field_ref(lang, rw->is_member_access, tgt->label)) {
|
if (cbm_suppress_bare_field_ref(lang, rw->is_member_access, tgt->label)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
/* `init := func(...)`: a bare Go `init` names a local, never a package
|
||||||
|
* init or an init method (cbm_go_suppress_bare_init_bind). Mirrors
|
||||||
|
* pass_parallel.c. */
|
||||||
|
if (cbm_go_suppress_bare_init_bind(lang == CBM_LANG_GO, rw->is_member_access, rw->var_name,
|
||||||
|
res.strategy, tgt->label)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
const char *edge_type = rw->is_write ? "WRITES" : "READS";
|
const char *edge_type = rw->is_write ? "WRITES" : "READS";
|
||||||
cbm_gbuf_insert_edge(ctx->gbuf, src->id, tgt->id, edge_type, "{}");
|
cbm_gbuf_insert_edge(ctx->gbuf, src->id, tgt->id, edge_type, "{}");
|
||||||
|
|||||||
@@ -459,6 +459,16 @@ bool cbm_suppress_external_import_shadow(const char *callee_name, const char *st
|
|||||||
bool cbm_go_suppress_weak_method_match(bool is_go, bool is_method, const char *strategy,
|
bool cbm_go_suppress_weak_method_match(bool is_go, bool is_method, const char *strategy,
|
||||||
double confidence);
|
double confidence);
|
||||||
|
|
||||||
|
/* A bare Go `init` (a call or reference that is not the member half of a
|
||||||
|
* selector) is always a local name: Go forbids referring to a package init
|
||||||
|
* function, and a method or field needs a selector. True when such a name
|
||||||
|
* resolved onto a Function (every Go Function named init is a package init;
|
||||||
|
* any registry strategy) or onto a Method or Field found by a weak short-name
|
||||||
|
* strategy. lsp_* answers are kept. Go only. Pure; unit-tested in
|
||||||
|
* test_registry.c; wired in pass_calls.c, pass_usages.c and pass_parallel.c. */
|
||||||
|
bool cbm_go_suppress_bare_init_bind(bool is_go, bool is_member, const char *name,
|
||||||
|
const char *strategy, const char *target_label);
|
||||||
|
|
||||||
/* Import-binding counterpart (#2127). True when the callee's root identifier
|
/* Import-binding counterpart (#2127). True when the callee's root identifier
|
||||||
* is bound by an import of this file whose module chain contradicts the
|
* is bound by an import of this file whose module chain contradicts the
|
||||||
* resolved target (`from unittest.mock import patch; patch()` must not bind a
|
* resolved target (`from unittest.mock import patch; patch()` must not bind a
|
||||||
|
|||||||
@@ -1015,6 +1015,46 @@ bool cbm_go_suppress_weak_method_match(bool is_go, bool is_method, const char *s
|
|||||||
return confidence < CONF_UNIQUE_NAME;
|
return confidence < CONF_UNIQUE_NAME;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool cbm_go_suppress_bare_init_bind(bool is_go, bool is_member, const char *name,
|
||||||
|
const char *strategy, const char *target_label) {
|
||||||
|
/* The Go spec gives `init` no binding in the package block: the
|
||||||
|
* identifier declares init functions and nothing else, and "init
|
||||||
|
* functions cannot be referred to from anywhere in a program". A method
|
||||||
|
* or a field named init is reached only through a selector (`x.init()`,
|
||||||
|
* `e.init`), which arrives here as is_member and is left alone. So a
|
||||||
|
* BARE `init` is always a local name: a closure (`init :=
|
||||||
|
* func(ctx) {...}; init(ctx)`, 22 kubernetes e2e storage suites), a
|
||||||
|
* parameter (`func sum(init func() ref.Val)`), or the `package init`
|
||||||
|
* clause of a directory named init. None of them is a project node.
|
||||||
|
*
|
||||||
|
* Every Go Function named init is a package init (no other package-level
|
||||||
|
* `init` can be declared), so that target is refused for every registry
|
||||||
|
* strategy. Before #1915 the flat QN made same_module bind the bare name
|
||||||
|
* to its own package's init; after it, the per-file QN misses there and
|
||||||
|
* the name fallback binds unrelated init methods instead. A Method or
|
||||||
|
* Field namesake is refused when only the name found it (the shared weak
|
||||||
|
* drop-list), which is how every such edge was minted. lsp_* answers are
|
||||||
|
* untouched: the sequential resolver emits them before its registry
|
||||||
|
* guards run, so refusing one here would split the two resolvers.
|
||||||
|
*
|
||||||
|
* Go-gated: elsewhere `init` is an ordinary name. A bare `init()` in a
|
||||||
|
* Java, C#, C++, Kotlin or Ruby method calls the class's own method, and
|
||||||
|
* a C, Python or JS `init` function is called by its bare name. */
|
||||||
|
if (!is_go || is_member || !name || strcmp(name, "init") != 0 || !target_label) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (strategy && strncmp(strategy, "lsp_", strlen("lsp_")) == 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (strcmp(target_label, "Function") == 0) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (strcmp(target_label, "Method") == 0 || strcmp(target_label, "Field") == 0) {
|
||||||
|
return weak_short_name_strategy(strategy);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
/* Import-binding counterpart of the parameter guard above (#2127). An import
|
/* Import-binding counterpart of the parameter guard above (#2127). An import
|
||||||
* binds the identifier for the whole module; when that import is external it
|
* binds the identifier for the whole module; when that import is external it
|
||||||
* never materializes, and the chain falls through to project-wide short-name
|
* never materializes, and the chain falls through to project-wide short-name
|
||||||
|
|||||||
@@ -11071,6 +11071,201 @@ TEST(pipeline_go_init_lsp_edges_per_file_parallel) {
|
|||||||
PASS();
|
PASS();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Fixture for the bare-`init` probes (sequential and parallel twins), the
|
||||||
|
* shapes the kubernetes census found after #1915: a local closure `init :=
|
||||||
|
* func(ctx)` that is called, written and passed by value (the e2e storage
|
||||||
|
* suites), a parameter named init (`func sum(init func() ref.Val)`), and a
|
||||||
|
* `package init` directory. The decoys are a package init in the caller's
|
||||||
|
* own package (what same_module bound before #1915) and an unrelated
|
||||||
|
* Watcher.init method (what the name fallback binds after it). Watcher.Start
|
||||||
|
* and Boot call that method through a selector, the control that must keep
|
||||||
|
* resolving. pad_files > 0 adds filler Go files past MIN_FILES_FOR_PARALLEL. */
|
||||||
|
static void write_go_bare_init_fixture(const char *tmp, int pad_files) {
|
||||||
|
write_temp_file(tmp, "go.mod", "module example.com/fxbareinit\n\ngo 1.22\n");
|
||||||
|
write_temp_file(tmp, "suites/suite.go",
|
||||||
|
"package suites\n"
|
||||||
|
"\n"
|
||||||
|
"import \"context\"\n"
|
||||||
|
"\n"
|
||||||
|
"var registered []string\n"
|
||||||
|
"\n"
|
||||||
|
"func init() { registered = append(registered, \"volumes\") }\n"
|
||||||
|
"\n"
|
||||||
|
"func wrap(fn func(context.Context)) { _ = fn }\n"
|
||||||
|
"\n"
|
||||||
|
"func DefineTests(ctx context.Context) {\n"
|
||||||
|
"\tinit := func(ctx context.Context) {\n"
|
||||||
|
"\t\t_ = ctx\n"
|
||||||
|
"\t}\n"
|
||||||
|
"\tinit(ctx)\n"
|
||||||
|
"\twrap(init)\n"
|
||||||
|
"}\n"
|
||||||
|
"\n"
|
||||||
|
"func Sum(init func() int) int {\n"
|
||||||
|
"\treturn init()\n"
|
||||||
|
"}\n");
|
||||||
|
write_temp_file(tmp, "watcher/watcher.go",
|
||||||
|
"package watcher\n"
|
||||||
|
"\n"
|
||||||
|
"type Watcher struct{ ready bool }\n"
|
||||||
|
"\n"
|
||||||
|
"func NewWatcher() *Watcher { return &Watcher{} }\n"
|
||||||
|
"\n"
|
||||||
|
"func (w *Watcher) init() { w.ready = true }\n"
|
||||||
|
"\n"
|
||||||
|
"func (w *Watcher) Start() {\n"
|
||||||
|
"\tw.init()\n"
|
||||||
|
"}\n"
|
||||||
|
"\n"
|
||||||
|
"func Boot() {\n"
|
||||||
|
"\tw := NewWatcher()\n"
|
||||||
|
"\tw.init()\n"
|
||||||
|
"}\n");
|
||||||
|
write_temp_file(tmp, "boot/init/init.go",
|
||||||
|
"// Package init installs defaults for its importers.\n"
|
||||||
|
"package init\n"
|
||||||
|
"\n"
|
||||||
|
"func init() { setup() }\n"
|
||||||
|
"\n"
|
||||||
|
"func setup() {}\n");
|
||||||
|
for (int i = 0; i < pad_files; i++) {
|
||||||
|
char name[64];
|
||||||
|
char body[128];
|
||||||
|
snprintf(name, sizeof(name), "pad/filler%d.go", i);
|
||||||
|
snprintf(body, sizeof(body), "package pad\n\nfunc filler%d() int { return %d }\n", i, i);
|
||||||
|
write_temp_file(tmp, name, body);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool bare_init_qn_ends_with(const char *qn, const char *suffix) {
|
||||||
|
size_t ql = strlen(qn);
|
||||||
|
size_t sl = strlen(suffix);
|
||||||
|
return ql >= sl && strcmp(qn + ql - sl, suffix) == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Resolver edges into any node named `init` whose source is not one of the
|
||||||
|
* two selector controls. Every such edge in the fixture binds a bare `init`,
|
||||||
|
* which in Go is always a local name. */
|
||||||
|
static int go_bare_init_false_edges(cbm_store_t *s, const char *project) {
|
||||||
|
static const char *const types[] = {"CALLS", "USAGE", "WRITES", "READS", "CALL_REFERENCE"};
|
||||||
|
int hits = 0;
|
||||||
|
for (size_t t = 0; t < sizeof(types) / sizeof(types[0]); t++) {
|
||||||
|
cbm_edge_t *edges = NULL;
|
||||||
|
int edge_count = 0;
|
||||||
|
cbm_store_find_edges_by_type(s, project, types[t], &edges, &edge_count);
|
||||||
|
for (int i = 0; i < edge_count; i++) {
|
||||||
|
cbm_node_t src = {0};
|
||||||
|
cbm_node_t tgt = {0};
|
||||||
|
if (cbm_store_find_node_by_id(s, edges[i].source_id, &src) == CBM_STORE_OK &&
|
||||||
|
cbm_store_find_node_by_id(s, edges[i].target_id, &tgt) == CBM_STORE_OK &&
|
||||||
|
src.qualified_name && tgt.name && strcmp(tgt.name, "init") == 0 &&
|
||||||
|
!bare_init_qn_ends_with(src.qualified_name, ".watcher.Watcher.Start") &&
|
||||||
|
!bare_init_qn_ends_with(src.qualified_name, ".watcher.Boot")) {
|
||||||
|
printf(" bare init bound: %s %s -> %s\n", types[t], src.qualified_name,
|
||||||
|
tgt.qualified_name ? tgt.qualified_name : "?");
|
||||||
|
hits++;
|
||||||
|
}
|
||||||
|
cbm_node_free_fields(&src);
|
||||||
|
cbm_node_free_fields(&tgt);
|
||||||
|
}
|
||||||
|
if (edges) {
|
||||||
|
cbm_store_free_edges(edges, edge_count);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return hits;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int assert_go_bare_init_edges(cbm_store_t *s, const char *project) {
|
||||||
|
/* The decoys exist, so a zero below is a refused binding and not an
|
||||||
|
* absent target: two package inits and the Watcher method. */
|
||||||
|
cbm_node_t *inits = NULL;
|
||||||
|
int ic = 0;
|
||||||
|
cbm_store_find_nodes_by_name(s, project, "init", &inits, &ic);
|
||||||
|
int functions = 0;
|
||||||
|
int methods = 0;
|
||||||
|
for (int i = 0; i < ic; i++) {
|
||||||
|
functions += inits[i].label && strcmp(inits[i].label, "Function") == 0;
|
||||||
|
methods += inits[i].label && strcmp(inits[i].label, "Method") == 0;
|
||||||
|
}
|
||||||
|
cbm_store_free_nodes(inits, ic);
|
||||||
|
ASSERT_EQ(functions, 2);
|
||||||
|
ASSERT_EQ(methods, 1);
|
||||||
|
/* NEGATIVE: no bare `init` (closure call, closure write, closure passed
|
||||||
|
* by value, parameter call, `package init`) binds a package init or the
|
||||||
|
* unrelated method. */
|
||||||
|
ASSERT_EQ(go_bare_init_false_edges(s, project), 0);
|
||||||
|
/* CONTROL: a selector call `w.init()` is not bare and still resolves to
|
||||||
|
* the method, from a method body and from a free function. */
|
||||||
|
ASSERT_GTE(
|
||||||
|
go_calls_between(s, project, ".watcher.Watcher.Start", ".watcher.Watcher.init", NULL), 1);
|
||||||
|
ASSERT_GTE(go_calls_between(s, project, ".watcher.Boot", ".watcher.Watcher.init", NULL), 1);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* R45 (follow-up to #1915): a bare Go `init` never binds a package init or,
|
||||||
|
* by name alone, an init method (cbm_go_suppress_bare_init_bind). */
|
||||||
|
TEST(pipeline_go_bare_init_binds_nothing) {
|
||||||
|
char tmp[256];
|
||||||
|
snprintf(tmp, sizeof(tmp), "/tmp/cbm_go_bare_init_XXXXXX");
|
||||||
|
if (!cbm_mkdtemp(tmp)) {
|
||||||
|
FAIL("tmpdir");
|
||||||
|
}
|
||||||
|
write_go_bare_init_fixture(tmp, 0);
|
||||||
|
|
||||||
|
char db_path[512];
|
||||||
|
snprintf(db_path, sizeof(db_path), "%s/go_bare_init.db", tmp);
|
||||||
|
cbm_pipeline_t *p = cbm_pipeline_new(tmp, db_path, CBM_MODE_FULL);
|
||||||
|
ASSERT_NOT_NULL(p);
|
||||||
|
ASSERT_EQ(cbm_pipeline_run(p), 0);
|
||||||
|
const char *project = cbm_pipeline_project_name(p);
|
||||||
|
|
||||||
|
cbm_store_t *s = cbm_store_open_path(db_path);
|
||||||
|
ASSERT_NOT_NULL(s);
|
||||||
|
ASSERT_EQ(assert_go_bare_init_edges(s, project), 0);
|
||||||
|
|
||||||
|
cbm_store_close(s);
|
||||||
|
cbm_pipeline_free(p);
|
||||||
|
th_rmtree(tmp);
|
||||||
|
PASS();
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Parallel twin: >= MIN_FILES_FOR_PARALLEL (50) files plus CBM_WORKERS=4
|
||||||
|
* take the parallel resolver, which carries its own copy of each guard. */
|
||||||
|
TEST(pipeline_go_bare_init_binds_nothing_parallel) {
|
||||||
|
char tmp[256];
|
||||||
|
snprintf(tmp, sizeof(tmp), "/tmp/cbm_go_bare_init_par_XXXXXX");
|
||||||
|
if (!cbm_mkdtemp(tmp)) {
|
||||||
|
FAIL("tmpdir");
|
||||||
|
}
|
||||||
|
write_go_bare_init_fixture(tmp, 52);
|
||||||
|
|
||||||
|
char *old_workers = getenv("CBM_WORKERS");
|
||||||
|
char *saved = old_workers ? strdup(old_workers) : NULL;
|
||||||
|
cbm_setenv("CBM_WORKERS", "4", 1);
|
||||||
|
|
||||||
|
char db_path[512];
|
||||||
|
snprintf(db_path, sizeof(db_path), "%s/go_bare_init_par.db", tmp);
|
||||||
|
cbm_pipeline_t *p = cbm_pipeline_new(tmp, db_path, CBM_MODE_FULL);
|
||||||
|
ASSERT_NOT_NULL(p);
|
||||||
|
ASSERT_EQ(cbm_pipeline_run(p), 0);
|
||||||
|
const char *project = cbm_pipeline_project_name(p);
|
||||||
|
|
||||||
|
cbm_store_t *s = cbm_store_open_path(db_path);
|
||||||
|
ASSERT_NOT_NULL(s);
|
||||||
|
ASSERT_EQ(assert_go_bare_init_edges(s, project), 0);
|
||||||
|
|
||||||
|
cbm_store_close(s);
|
||||||
|
cbm_pipeline_free(p);
|
||||||
|
if (saved) {
|
||||||
|
cbm_setenv("CBM_WORKERS", saved, 1);
|
||||||
|
free(saved);
|
||||||
|
} else {
|
||||||
|
cbm_unsetenv("CBM_WORKERS");
|
||||||
|
}
|
||||||
|
th_rmtree(tmp);
|
||||||
|
PASS();
|
||||||
|
}
|
||||||
|
|
||||||
/* Fixture for the #1928 cross-language reference-guard probes (sequential and
|
/* Fixture for the #1928 cross-language reference-guard probes (sequential and
|
||||||
* parallel twins). pad_files > 0 adds filler files to push the index over the
|
* parallel twins). pad_files > 0 adds filler files to push the index over the
|
||||||
* parallel-pipeline threshold, since USAGE/WRITES/READS have one resolver per
|
* parallel-pipeline threshold, since USAGE/WRITES/READS have one resolver per
|
||||||
@@ -29621,6 +29816,8 @@ SUITE(pipeline) {
|
|||||||
RUN_TEST(pipeline_go_multi_init_nodes_survive);
|
RUN_TEST(pipeline_go_multi_init_nodes_survive);
|
||||||
RUN_TEST(pipeline_go_init_lsp_edges_per_file);
|
RUN_TEST(pipeline_go_init_lsp_edges_per_file);
|
||||||
RUN_TEST(pipeline_go_init_lsp_edges_per_file_parallel);
|
RUN_TEST(pipeline_go_init_lsp_edges_per_file_parallel);
|
||||||
|
RUN_TEST(pipeline_go_bare_init_binds_nothing);
|
||||||
|
RUN_TEST(pipeline_go_bare_init_binds_nothing_parallel);
|
||||||
RUN_TEST(pipeline_go_crossfile_type_alias_method);
|
RUN_TEST(pipeline_go_crossfile_type_alias_method);
|
||||||
RUN_TEST(pipeline_go_crossfile_type_alias_method_parallel);
|
RUN_TEST(pipeline_go_crossfile_type_alias_method_parallel);
|
||||||
RUN_TEST(pipeline_go_rw_usage_never_cross_into_c);
|
RUN_TEST(pipeline_go_rw_usage_never_cross_into_c);
|
||||||
|
|||||||
@@ -1260,6 +1260,45 @@ TEST(go_bare_ref_never_binds_field) {
|
|||||||
PASS();
|
PASS();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST(go_bare_init_never_binds_init_namesake) {
|
||||||
|
/* Go cannot refer to a package init function, and a method or field
|
||||||
|
* named init needs a selector, so a bare `init` (a closure, a parameter,
|
||||||
|
* a `package init` clause) is always a local name. */
|
||||||
|
/* Every Go Function named init is a package init: refused for every
|
||||||
|
* registry strategy, same_module included (the pre-#1915 binding). */
|
||||||
|
ASSERT_TRUE(cbm_go_suppress_bare_init_bind(true, false, "init", "same_module", "Function"));
|
||||||
|
ASSERT_TRUE(cbm_go_suppress_bare_init_bind(true, false, "init", "suffix_match", "Function"));
|
||||||
|
ASSERT_TRUE(cbm_go_suppress_bare_init_bind(true, false, "init", "import_map", "Function"));
|
||||||
|
ASSERT_TRUE(cbm_go_suppress_bare_init_bind(true, false, "init", NULL, "Function"));
|
||||||
|
/* A Method or Field namesake found by the name alone is refused. */
|
||||||
|
ASSERT_TRUE(cbm_go_suppress_bare_init_bind(true, false, "init", "suffix_match", "Method"));
|
||||||
|
ASSERT_TRUE(cbm_go_suppress_bare_init_bind(true, false, "init", "unique_name", "Method"));
|
||||||
|
ASSERT_TRUE(cbm_go_suppress_bare_init_bind(true, false, "init", "suffix_match", "Field"));
|
||||||
|
/* ... but not one that an import- or module-aware strategy produced. */
|
||||||
|
ASSERT_FALSE(cbm_go_suppress_bare_init_bind(true, false, "init", "same_module", "Method"));
|
||||||
|
ASSERT_FALSE(cbm_go_suppress_bare_init_bind(true, false, "init", "import_map", "Method"));
|
||||||
|
/* lsp_* answers are kept: the sequential resolver emits them before its
|
||||||
|
* registry guards run, so refusing one here would split the resolvers. */
|
||||||
|
ASSERT_FALSE(cbm_go_suppress_bare_init_bind(true, false, "init", "lsp_direct", "Function"));
|
||||||
|
ASSERT_FALSE(cbm_go_suppress_bare_init_bind(true, false, "init", "lsp_direct", "Method"));
|
||||||
|
/* The member half of a selector (`w.init()`, `e.init`) is not bare. */
|
||||||
|
ASSERT_FALSE(cbm_go_suppress_bare_init_bind(true, true, "init", "suffix_match", "Method"));
|
||||||
|
ASSERT_FALSE(cbm_go_suppress_bare_init_bind(true, true, "init", "suffix_match", "Field"));
|
||||||
|
/* Other names and other target kinds are untouched. */
|
||||||
|
ASSERT_FALSE(cbm_go_suppress_bare_init_bind(true, false, "initAll", "suffix_match", "Method"));
|
||||||
|
ASSERT_FALSE(cbm_go_suppress_bare_init_bind(true, false, "Init", "suffix_match", "Function"));
|
||||||
|
ASSERT_FALSE(cbm_go_suppress_bare_init_bind(true, false, "init", "suffix_match", "Variable"));
|
||||||
|
ASSERT_FALSE(cbm_go_suppress_bare_init_bind(true, false, "init", "suffix_match", "Module"));
|
||||||
|
/* Go only: elsewhere a bare `init()` is an ordinary call (a Java or C++
|
||||||
|
* method body calls its own init, a C or Python init is a function). */
|
||||||
|
ASSERT_FALSE(cbm_go_suppress_bare_init_bind(false, false, "init", "suffix_match", "Method"));
|
||||||
|
ASSERT_FALSE(cbm_go_suppress_bare_init_bind(false, false, "init", "same_module", "Function"));
|
||||||
|
/* Degenerate input → nothing to judge. */
|
||||||
|
ASSERT_FALSE(cbm_go_suppress_bare_init_bind(true, false, NULL, "suffix_match", "Function"));
|
||||||
|
ASSERT_FALSE(cbm_go_suppress_bare_init_bind(true, false, "init", "suffix_match", NULL));
|
||||||
|
PASS();
|
||||||
|
}
|
||||||
|
|
||||||
TEST(c_bare_ref_never_binds_field) {
|
TEST(c_bare_ref_never_binds_field) {
|
||||||
/* #2666: a C struct member is named only as the member half of `a.b`,
|
/* #2666: a C struct member is named only as the member half of `a.b`,
|
||||||
* `a->b` or a designator `.b` (field_identifier → is_member_access). A
|
* `a->b` or a designator `.b` (field_identifier → is_member_access). A
|
||||||
@@ -2656,6 +2695,7 @@ SUITE(registry) {
|
|||||||
RUN_TEST(registry_tie_prefers_csharp_implementation_over_ref_stub);
|
RUN_TEST(registry_tie_prefers_csharp_implementation_over_ref_stub);
|
||||||
RUN_TEST(cross_language_ref_drops_go_vs_c);
|
RUN_TEST(cross_language_ref_drops_go_vs_c);
|
||||||
RUN_TEST(go_bare_ref_never_binds_field);
|
RUN_TEST(go_bare_ref_never_binds_field);
|
||||||
|
RUN_TEST(go_bare_init_never_binds_init_namesake);
|
||||||
RUN_TEST(c_bare_ref_never_binds_field);
|
RUN_TEST(c_bare_ref_never_binds_field);
|
||||||
RUN_TEST(call_onto_field_follows_language_and_shape);
|
RUN_TEST(call_onto_field_follows_language_and_shape);
|
||||||
RUN_TEST(c_bare_call_does_not_weigh_field_namesakes);
|
RUN_TEST(c_bare_call_does_not_weigh_field_namesakes);
|
||||||
|
|||||||
Reference in new issue
Block a user