mirror of
https://github.com/DeusData/codebase-memory-mcp.git
synced 2026-11-04 10:48:20 +00:00
Merge pull request #1050 from DeusData/fix/961-ifdef-split-defs
fix(extract): recover C/C++ defs lost to #ifdef-split brace ERROR regions
This commit is contained in:
2 files changed
+138
No files matched your search
@@ -817,6 +817,38 @@ static bool cbm_region_is_recovered(uint32_t rs, uint32_t re, const CBMDefArray
|
||||
return covered_to >= re;
|
||||
}
|
||||
|
||||
/* #961: true when 1-based `line` of `src` contains `name` (used to verify a
|
||||
* def recovered from EXPANDED source really lives on that ORIGINAL line —
|
||||
* rejects header-inlined defs whose physical expanded lines alias unrelated
|
||||
* raw lines when compile_commands include paths are present). */
|
||||
static bool cbm_line_contains(const char *src, int src_len, uint32_t line, const char *name) {
|
||||
if (!src || !name || !name[0] || line == 0) {
|
||||
return false;
|
||||
}
|
||||
uint32_t cur = 1;
|
||||
int i = 0;
|
||||
while (i < src_len && cur < line) {
|
||||
if (src[i] == '\n') {
|
||||
cur++;
|
||||
}
|
||||
i++;
|
||||
}
|
||||
if (cur != line) {
|
||||
return false;
|
||||
}
|
||||
int end = i;
|
||||
while (end < src_len && src[end] != '\n') {
|
||||
end++;
|
||||
}
|
||||
size_t nlen = strlen(name);
|
||||
for (int j = i; j + (int)nlen <= end; j++) {
|
||||
if (strncmp(src + j, name, nlen) == 0) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static void cbm_subtract_recovered_regions(cbm_error_regions_t *regs, const CBMDefArray *defs) {
|
||||
int kept = 0;
|
||||
for (int i = 0; i < regs->count; i++) {
|
||||
@@ -1103,6 +1135,52 @@ static CBMFileResult *cbm_extract_file_impl(const char *source, int source_len,
|
||||
cbm_run_c_lsp(a, result, expanded, expanded_len, pp_root,
|
||||
language != CBM_LANG_C);
|
||||
|
||||
/* #961: a def whose body braces are split across
|
||||
* #ifdef/#else branches parses as an ERROR region on the
|
||||
* RAW source (both branches present at once -> unbalanced
|
||||
* braces), so the raw defs walk silently dropped it. The
|
||||
* expanded tree parses clean (simplecpp picked one
|
||||
* branch) and same-file token lines stay aligned, so
|
||||
* recover defs from it — adopting ONLY those that
|
||||
* intersect a raw ERROR region, whose name is visible on
|
||||
* the raw source line, and whose QN the raw pass did not
|
||||
* already extract. */
|
||||
if (ts_node_has_error(root)) {
|
||||
cbm_error_regions_t raw_regs = {{0}, {0}, 0};
|
||||
cbm_collect_error_regions(root, &raw_regs);
|
||||
if (raw_regs.count > 0) {
|
||||
int defs_before = result->defs.count;
|
||||
cbm_extract_definitions(&pp_ctx);
|
||||
int w = defs_before;
|
||||
for (int i = defs_before; i < result->defs.count; i++) {
|
||||
CBMDefinition *d = &result->defs.items[i];
|
||||
bool adopt = false;
|
||||
for (int rj = 0; rj < raw_regs.count && !adopt; rj++) {
|
||||
if (d->start_line <= raw_regs.ends[rj] &&
|
||||
d->end_line >= raw_regs.starts[rj]) {
|
||||
adopt = true;
|
||||
}
|
||||
}
|
||||
if (adopt &&
|
||||
(!d->name || !cbm_line_contains(source, source_len,
|
||||
d->start_line, d->name))) {
|
||||
adopt = false;
|
||||
}
|
||||
for (int j = 0; j < defs_before && adopt; j++) {
|
||||
const char *q = result->defs.items[j].qualified_name;
|
||||
if (q && d->qualified_name &&
|
||||
strcmp(q, d->qualified_name) == 0) {
|
||||
adopt = false;
|
||||
}
|
||||
}
|
||||
if (adopt) {
|
||||
result->defs.items[w++] = *d;
|
||||
}
|
||||
}
|
||||
result->defs.count = w;
|
||||
}
|
||||
}
|
||||
|
||||
ts_tree_delete(pp_tree);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3389,6 +3389,64 @@ TEST(extract_js_member_call_flags_is_method) {
|
||||
PASS();
|
||||
}
|
||||
|
||||
/* #961: a C function whose body braces are split across #ifdef/#else
|
||||
* branches (one open brace per branch, a single shared close) parses with
|
||||
* an ERROR region on the raw source — both branches are present at once —
|
||||
* and the defs walk silently dropped the function while its callers stayed
|
||||
* (cbm_path_within_root, handle_process_kill). The preprocessed second
|
||||
* pass (simplecpp picks one branch, same-file token lines stay aligned)
|
||||
* must recover the definition with its original line. */
|
||||
TEST(extract_c_ifdef_split_brace_fn_recovered_issue961) {
|
||||
CBMFileResult *r = extract("static int a(void) { return 1; }\n"
|
||||
"static int b(void) { return 2; }\n"
|
||||
"int split_brace_fn(int x) {\n"
|
||||
"#ifdef _WIN32\n"
|
||||
" if (a() && b()) {\n"
|
||||
"#else\n"
|
||||
" if (a() || b()) {\n"
|
||||
"#endif\n"
|
||||
" x += 1;\n"
|
||||
" }\n"
|
||||
" return x;\n"
|
||||
"}\n"
|
||||
"int after_fn(int x) { return x; }\n",
|
||||
CBM_LANG_C, "t", "split.c");
|
||||
ASSERT_NOT_NULL(r);
|
||||
const CBMDefinition *d = find_def(r, "split_brace_fn");
|
||||
if (!d) {
|
||||
fprintf(stderr, " [961] FAIL split_brace_fn dropped (defs walk lost the "
|
||||
"#ifdef-split function)\n");
|
||||
}
|
||||
ASSERT_NOT_NULL(d);
|
||||
ASSERT_EQ((int)d->start_line, 3);
|
||||
/* Error recovery must stay localized: neighbours extract either way. */
|
||||
ASSERT_NOT_NULL(find_def(r, "a"));
|
||||
ASSERT_NOT_NULL(find_def(r, "after_fn"));
|
||||
cbm_free_result(r);
|
||||
PASS();
|
||||
}
|
||||
|
||||
/* #961 inverse guard: a clean C file must not gain duplicate or phantom
|
||||
* defs from the recovery path (it only engages on raw-parse ERROR regions). */
|
||||
TEST(extract_c_clean_file_no_recovery_duplicates_issue961) {
|
||||
CBMFileResult *r = extract("#ifdef _WIN32\n"
|
||||
"static int w(void) { return 1; }\n"
|
||||
"#else\n"
|
||||
"static int u(void) { return 2; }\n"
|
||||
"#endif\n"
|
||||
"int use(int x) { return x; }\n",
|
||||
CBM_LANG_C, "t", "clean.c");
|
||||
ASSERT_NOT_NULL(r);
|
||||
int use_count = 0;
|
||||
for (int i = 0; i < r->defs.count; i++) {
|
||||
if (r->defs.items[i].name && strcmp(r->defs.items[i].name, "use") == 0)
|
||||
use_count++;
|
||||
}
|
||||
ASSERT_EQ(use_count, 1);
|
||||
cbm_free_result(r);
|
||||
PASS();
|
||||
}
|
||||
|
||||
/* #668: walk_defs used a fixed `walk_defs_frame_t stack[4096]` — a ~160 KB
|
||||
* C-stack frame that overflowed small thread stacks (the reporter's crash was in
|
||||
* the "definitions pass" on a large SQL file), and whose `top < 4096` push guards
|
||||
@@ -3806,6 +3864,8 @@ SUITE(extraction) {
|
||||
RUN_TEST(complexity_go_method_receiver_self_recursion);
|
||||
RUN_TEST(complexity_delegation_receivers_not_recursive_issue876);
|
||||
RUN_TEST(complexity_access_depth_and_params);
|
||||
RUN_TEST(extract_c_ifdef_split_brace_fn_recovered_issue961);
|
||||
RUN_TEST(extract_c_clean_file_no_recovery_duplicates_issue961);
|
||||
RUN_TEST(walk_defs_no_truncation_over_4096_issue668);
|
||||
RUN_TEST(extract_rust_test_attr_marks_is_test_issue855);
|
||||
|
||||
|
||||
Reference in new issue
Block a user