Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Makefile.cbm
Original file line number Diff line number Diff line change
Expand Up @@ -437,6 +437,7 @@ TEST_REPRO_SRCS = \
tests/repro/repro_new_cypher_limit_zero.c \
tests/repro/repro_issue363.c \
tests/repro/repro_issue581.c \
tests/repro/repro_issue787.c \
tests/repro/repro_invariant_calls.c \
tests/repro/repro_invariant_graph.c \
tests/repro/repro_invariant_breadth.c \
Expand Down
65 changes: 43 additions & 22 deletions src/graph_buffer/graph_buffer.c
Original file line number Diff line number Diff line change
Expand Up @@ -587,25 +587,31 @@ int64_t cbm_gbuf_upsert_node(cbm_gbuf_t *gb, const char *label, const char *name
/* Check if node already exists */
cbm_gbuf_node_t *existing = cbm_ht_get(gb->node_by_qn, qualified_name);
if (existing) {
/* Update in-place. name/properties are strdup'd BEFORE freeing old ones
* (callers may pass existing->name as an argument). label/file_path are
* interned: gb_intern returns a stable pool pointer (idempotent even when
* label == existing->label), so the old value is replaced, never freed. */
char *new_name = heap_strdup(name);
char *new_props = properties_json ? heap_strdup(properties_json) : NULL;
/* Don't let a per-file "Module" def downgrade a structural directory node
/* Don't let a per-file "Module" def touch a structural directory node
* ("Project" root or "Folder"). In a directory-based-module language
* (Go/Java) a file's module_qn equals its directory QN: a root file →
* the project name (== the "Project" node's QN); a file in pkg/ →
* proj.pkg (== the "pkg/" Folder node's QN). Its always-emitted Module
* def collides here; the directory node is the package/module container
* and must keep its structural label. (Both the sequential upsert and the
* parallel local-gbuf merge route through this function.) */
if (!(existing->label && label && strcmp(label, "Module") == 0 &&
(strcmp(existing->label, "Project") == 0 ||
strcmp(existing->label, "Folder") == 0))) {
existing->label = (char *)gb_intern(gb, label);
* and must keep its structural label AND its own name/file_path/range.
* Updating those in place set the shared node's file_path to whichever
* same-package file happened to be processed LAST (worker-order
* dependent) — the nondeterministic file attribution behind #787 — and
* left the Folder node exposed to delete-nodes-by-file on incremental
* reindex of that file. Skip the update entirely. (Both the sequential
* upsert and the parallel local-gbuf merge route through this function.) */
if (existing->label && label && strcmp(label, "Module") == 0 &&
(strcmp(existing->label, "Project") == 0 ||
strcmp(existing->label, "Folder") == 0)) {
return existing->id;
}
/* Update in-place. name/properties are strdup'd BEFORE freeing old ones
* (callers may pass existing->name as an argument). label/file_path are
* interned: gb_intern returns a stable pool pointer (idempotent even when
* label == existing->label), so the old value is replaced, never freed. */
char *new_name = heap_strdup(name);
char *new_props = properties_json ? heap_strdup(properties_json) : NULL;
existing->label = (char *)gb_intern(gb, label);
free(existing->name);
existing->name = new_name;
existing->file_path = (char *)gb_intern(gb, file_path);
Expand Down Expand Up @@ -1063,15 +1069,30 @@ static void free_remap_entry(const char *key, void *val, void *ud) {
* label/file_path are re-interned into dst's pool (sn's pointers belong to src). */
static void merge_update_existing(cbm_gbuf_t *dst, cbm_gbuf_node_t *existing,
const cbm_gbuf_node_t *sn, CBMHashTable **remap) {
existing->label = (char *)gb_intern(dst, sn->label);
free(existing->name);
existing->name = heap_strdup(sn->name);
existing->file_path = (char *)gb_intern(dst, sn->file_path);
existing->start_line = sn->start_line;
existing->end_line = sn->end_line;
if (sn->properties_json) {
free(existing->properties_json);
existing->properties_json = heap_strdup(sn->properties_json);
/* Same guard as cbm_gbuf_upsert_node: a per-file "Module" def coming from a
* worker-local gbuf must not touch the structural directory node ("Project"
* root or "Folder") that shares its QN in a directory-based-module language
* (Java/Go). pass_structure seeds Folder/Project nodes on the MAIN gbuf
* before the parallel extract, so every worker's always-emitted Module def
* for that package collides here; unconditional "src wins" relabelled the
* directory node to Module and set its file_path to whichever worker merged
* LAST — the nondeterministic USAGE-source misattribution of #787. Keep the
* structural node intact; the ID remap below still redirects the worker's
* edges onto the canonical node. */
bool module_on_container =
existing->label && sn->label && strcmp(sn->label, "Module") == 0 &&
(strcmp(existing->label, "Project") == 0 || strcmp(existing->label, "Folder") == 0);
if (!module_on_container) {
existing->label = (char *)gb_intern(dst, sn->label);
free(existing->name);
existing->name = heap_strdup(sn->name);
existing->file_path = (char *)gb_intern(dst, sn->file_path);
existing->start_line = sn->start_line;
existing->end_line = sn->end_line;
if (sn->properties_json) {
free(existing->properties_json);
existing->properties_json = heap_strdup(sn->properties_json);
}
}

if (sn->id != existing->id) {
Expand Down
6 changes: 6 additions & 0 deletions src/pipeline/pass_calls.c
Original file line number Diff line number Diff line change
Expand Up @@ -408,6 +408,12 @@ static const cbm_gbuf_node_t *calls_find_source(cbm_pipeline_ctx_t *ctx, const c
const cbm_gbuf_node_t *src = NULL;
if (enclosing_qn) {
src = cbm_gbuf_find_by_qn(ctx->gbuf, enclosing_qn);
/* A class-level call in a directory-module language carries the
* DIRECTORY module QN, which hits the shared Folder/Project node —
* attribute to this file's File node instead (#787). */
if (cbm_pipeline_node_is_dir_container(src)) {
src = NULL;
}
}
if (!src) {
char *fqn = cbm_pipeline_fqn_compute(ctx->project_name, rel, "__file__");
Expand Down
6 changes: 6 additions & 0 deletions src/pipeline/pass_parallel.c
Original file line number Diff line number Diff line change
Expand Up @@ -1668,6 +1668,12 @@ static const cbm_gbuf_node_t *find_source_node(const cbm_gbuf_t *gbuf, const cha
const cbm_gbuf_node_t *src = NULL;
if (enclosing_qn) {
src = cbm_gbuf_find_by_qn(gbuf, enclosing_qn);
/* A class-level reference in a directory-module language carries the
* DIRECTORY module QN, which hits the shared Folder/Project node —
* attribute to this file's File node instead (#787). */
if (cbm_pipeline_node_is_dir_container(src)) {
src = NULL;
}
}
if (!src) {
char *file_qn = cbm_pipeline_fqn_compute(project, rel, "__file__");
Expand Down
6 changes: 6 additions & 0 deletions src/pipeline/pass_usages.c
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,12 @@ static const cbm_gbuf_node_t *find_enclosing_node(cbm_pipeline_ctx_t *ctx, const
const cbm_gbuf_node_t *node = NULL;
if (func_qn && func_qn[0]) {
node = cbm_gbuf_find_by_qn(ctx->gbuf, func_qn);
/* A class-level reference in a directory-module language carries the
* DIRECTORY module QN, which hits the shared Folder/Project node —
* attribute to this file's File node instead (#787). */
if (cbm_pipeline_node_is_dir_container(node)) {
node = NULL;
}
}
if (!node) {
char *file_qn = cbm_pipeline_fqn_compute(ctx->project_name, rel_path, "__file__");
Expand Down
14 changes: 14 additions & 0 deletions src/pipeline/pipeline_internal.h
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
#include "cbm.h"
#include "lsp/go_lsp.h" /* CBMLSPDef for cbm_parallel_resolve cross-LSP inputs */
#include <stdatomic.h>
#include <string.h>

/* ── Shared pipeline constants ─────────────────────────────────── */

Expand All @@ -33,6 +34,19 @@
* out_sz >= strlen(in) + 1 always suffices. Returns out. */
const char *cbm_route_canon_path(const char *in, char *out, size_t out_sz);

/* True when a graph node is a structural directory container (Folder/Project)
* rather than a code node. In a directory-based-module language (Java/Go, see
* cbm_lang_module_is_dir) a file's module QN equals its directory QN, so an
* enclosing-scope lookup for a CLASS-LEVEL usage/call (enclosing_func_qn ==
* module_qn) resolves to the ONE Folder/Project node shared by every file in
* that package. Sourcing an edge there conflates all same-package files into a
* single source node with an arbitrary file_path (#787). Source-node finders
* must treat such a hit as a miss and fall back to the per-file File node. */
static inline bool cbm_pipeline_node_is_dir_container(const cbm_gbuf_node_t *node) {
return node && node->label &&
(strcmp(node->label, "Folder") == 0 || strcmp(node->label, "Project") == 0);
}

/* Time unit conversions */
#define CBM_NS_PER_SEC 1000000000LL
#define CBM_US_PER_SEC 1000000LL
Expand Down
Loading
Loading