mirror of
https://github.com/DeusData/codebase-memory-mcp.git
synced 2026-11-03 08:03:38 +08:00
fix(pipeline): guard unsafe header overlay pairs
Keep the single-header overlay fast path for safe header-only additions, but fall back when a C-family header delta contains type-like nodes and a same-stem implementation file exists. This prevents active overlay from selecting header-owned type rows where a fresh full rebuild selects richer source-owned type rows, as seen in the store.h/store.c batch diagnostics. Adds a pipeline canary for header type/implementation pair fallback while preserving the single-header overlay and multi-file header safety canaries. Validation: focused header overlay tests passed; pipeline suite passed 326; source safety OK; diff check clean; production build passed; one_source_file benchmark stayed incremental_overlay at 29.677x. Full ASan target was also run and was not clean, with obsolete in-run and environment-sensitive failures recorded in the notes. Signed-off-by: Andrew Hundt <ATHundt@gmail.com>
This commit is contained in:
@@ -85,6 +85,54 @@ static bool incr_changed_contains_c_family_header(const cbm_file_info_t *changed
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool incr_file_delta_has_type_like_node(const cbm_pipeline_file_delta_t *delta) {
|
||||
if (!delta || delta->change_kind == CBM_PIPELINE_DELTA_CHANGE_DELETE) {
|
||||
return false;
|
||||
}
|
||||
for (int i = 0; i < delta->delta.node_count; i++) {
|
||||
const cbm_node_t *node = &delta->delta.nodes[i];
|
||||
if (cbm_label_is_type_like(node->label)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool incr_same_stem_impl_exists(const char *path) {
|
||||
static const char *const impl_exts[] = {".c", ".cc", ".cpp", ".cxx", ".m", ".mm", ".cu"};
|
||||
if (!path || !path[0]) {
|
||||
return false;
|
||||
}
|
||||
const char *dot = strrchr(path, '.');
|
||||
if (!dot || dot == path) {
|
||||
return false;
|
||||
}
|
||||
size_t stem_len = (size_t)(dot - path);
|
||||
if (stem_len >= CBM_PATH_MAX || stem_len > (size_t)INT_MAX) {
|
||||
return false;
|
||||
}
|
||||
for (size_t i = 0; i < sizeof(impl_exts) / sizeof(impl_exts[0]); i++) {
|
||||
char candidate[CBM_PATH_MAX];
|
||||
int n = snprintf(candidate, sizeof(candidate), "%.*s%s", (int)stem_len, path,
|
||||
impl_exts[i]);
|
||||
if (n < 0 || (size_t)n >= sizeof(candidate)) {
|
||||
continue;
|
||||
}
|
||||
if (cbm_file_exists(candidate)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool incr_header_overlay_has_type_impl_pair(const cbm_file_info_t *file,
|
||||
const cbm_pipeline_file_delta_t *delta) {
|
||||
if (!file || !incr_is_c_family_header(file->language, file->rel_path)) {
|
||||
return false;
|
||||
}
|
||||
return incr_file_delta_has_type_like_node(delta) && incr_same_stem_impl_exists(file->path);
|
||||
}
|
||||
|
||||
/* ── File classification ─────────────────────────────────────────── */
|
||||
|
||||
/* Classify discovered files against stored metadata.
|
||||
@@ -1563,6 +1611,12 @@ static int incr_try_overlay_upsert_route(cbm_pipeline_t *p, cbm_store_t *store,
|
||||
itoa_buf_incr(rc));
|
||||
goto cleanup;
|
||||
}
|
||||
if (incr_header_overlay_has_type_impl_pair(&changed_files[i], &deltas[i])) {
|
||||
cbm_pipeline_set_publish_reason(p, CBM_PIPELINE_DELTA_REASON_HEADER_TYPE_IMPL_PAIR);
|
||||
cbm_log_info("incremental.overlay.fallback", "reason",
|
||||
CBM_PIPELINE_DELTA_REASON_HEADER_TYPE_IMPL_PAIR);
|
||||
goto cleanup;
|
||||
}
|
||||
int preserved = 0;
|
||||
rc = cbm_pipeline_file_delta_add_preserved_inbound_edges(store, &deltas[i], &preserved);
|
||||
if (rc != CBM_STORE_OK) {
|
||||
|
||||
@@ -349,6 +349,7 @@ typedef struct {
|
||||
#define CBM_PIPELINE_DELTA_REASON_INBOUND_EDGES_REQUIRE_FULL "inbound_edges_require_full"
|
||||
#define CBM_PIPELINE_DELTA_REASON_PREFLIGHT_ERROR "preflight_error"
|
||||
#define CBM_PIPELINE_DELTA_REASON_CROSS_FILE_NODE_QN_COLLISION "cross_file_node_qn_collision"
|
||||
#define CBM_PIPELINE_DELTA_REASON_HEADER_TYPE_IMPL_PAIR "header_type_impl_pair"
|
||||
|
||||
/* Conservative default exact-delta caps. Larger affected sets fall back to the
|
||||
* containment path unless config opts into a benchmarked frontier size. */
|
||||
|
||||
@@ -10212,6 +10212,73 @@ TEST(incremental_overlay_publish_single_c_header_uses_active_overlay) {
|
||||
PASS();
|
||||
}
|
||||
|
||||
TEST(incremental_overlay_single_c_header_type_impl_pair_falls_back) {
|
||||
if (setup_incremental_repo() != 0) {
|
||||
FAIL("setup failed");
|
||||
}
|
||||
|
||||
char header_path[CBM_PATH_MAX];
|
||||
char source_path[CBM_PATH_MAX];
|
||||
int n = snprintf(header_path, sizeof(header_path), "%s/paired.h", g_incr_tmpdir);
|
||||
ASSERT(n >= 0 && (size_t)n < sizeof(header_path));
|
||||
n = snprintf(source_path, sizeof(source_path), "%s/paired.c", g_incr_tmpdir);
|
||||
ASSERT(n >= 0 && (size_t)n < sizeof(source_path));
|
||||
ASSERT_EQ(th_write_file(header_path,
|
||||
"#ifndef PAIRED_H\n"
|
||||
"#define PAIRED_H\n"
|
||||
"typedef struct Paired Paired;\n"
|
||||
"int paired_value(Paired *p);\n"
|
||||
"#endif\n"),
|
||||
0);
|
||||
ASSERT_EQ(th_write_file(source_path,
|
||||
"#include \"paired.h\"\n\n"
|
||||
"struct Paired {\n"
|
||||
" int value;\n"
|
||||
"};\n\n"
|
||||
"int paired_value(Paired *p) {\n"
|
||||
" return p ? p->value : 0;\n"
|
||||
"}\n"),
|
||||
0);
|
||||
|
||||
cbm_config_t *cfg = incremental_test_config(g_incr_tmpdir);
|
||||
ASSERT_NOT_NULL(cfg);
|
||||
ASSERT_EQ(cbm_config_set(cfg, CBM_CONFIG_OVERLAY_PUBLISH,
|
||||
CBM_CONFIG_OVERLAY_PUBLISH_SMALL_DELTAS),
|
||||
0);
|
||||
cbm_pipeline_t *p = cbm_pipeline_new(g_incr_tmpdir, g_incr_dbpath, CBM_MODE_FAST);
|
||||
ASSERT_NOT_NULL(p);
|
||||
cbm_pipeline_apply_config(p, cfg);
|
||||
ASSERT_EQ(cbm_pipeline_run(p), 0);
|
||||
cbm_pipeline_free(p);
|
||||
|
||||
ASSERT_EQ(th_write_file(header_path,
|
||||
"#ifndef PAIRED_H\n"
|
||||
"#define PAIRED_H\n"
|
||||
"typedef struct Paired Paired;\n"
|
||||
"int paired_value(Paired *p);\n"
|
||||
"static int paired_extra(void) {\n"
|
||||
" return 7;\n"
|
||||
"}\n"
|
||||
"#endif\n"),
|
||||
0);
|
||||
|
||||
p = cbm_pipeline_new(g_incr_tmpdir, g_incr_dbpath, CBM_MODE_FAST);
|
||||
ASSERT_NOT_NULL(p);
|
||||
cbm_pipeline_apply_config(p, cfg);
|
||||
pipeline_capture_logs_start();
|
||||
int run_rc = cbm_pipeline_run(p);
|
||||
const char *logs = pipeline_capture_logs_end();
|
||||
ASSERT_EQ(run_rc, 0);
|
||||
ASSERT(strstr(logs, "msg=incremental.overlay.fallback reason="
|
||||
CBM_PIPELINE_DELTA_REASON_HEADER_TYPE_IMPL_PAIR) != NULL);
|
||||
ASSERT_EQ(cbm_pipeline_publish_kind(p), CBM_PIPELINE_PUBLISH_INCREMENTAL_EXACT);
|
||||
cbm_pipeline_free(p);
|
||||
|
||||
cbm_config_close(cfg);
|
||||
cleanup_incremental_repo();
|
||||
PASS();
|
||||
}
|
||||
|
||||
TEST(incremental_c_header_uses_exact_not_overlay_until_active_edges_are_exact) {
|
||||
enum {
|
||||
PIPELINE_C_HEADER_OVERLAY_MAX_AFFECTED = CBM_SZ_16,
|
||||
@@ -13761,6 +13828,7 @@ SUITE(pipeline) {
|
||||
RUN_TEST(incremental_fast_falls_back_for_oversized_inbound_frontier_and_matches_full);
|
||||
RUN_TEST(incremental_fast_c_header_frontier_too_large_uses_full_rebuild);
|
||||
RUN_TEST(incremental_overlay_publish_single_c_header_uses_active_overlay);
|
||||
RUN_TEST(incremental_overlay_single_c_header_type_impl_pair_falls_back);
|
||||
RUN_TEST(incremental_c_header_uses_exact_not_overlay_until_active_edges_are_exact);
|
||||
RUN_TEST(incremental_fast_configured_frontier_cap_allows_bounded_exact);
|
||||
RUN_TEST(incremental_fast_mixed_unowned_edge_frontier_falls_back_before_exact_build);
|
||||
|
||||
Reference in New Issue
Block a user