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:
Martin Vogel authored and GitHub committed 2026-07-12 16:41:22 +02:00
commit db330bf3a0
2 files changed
+138

No files matched your search

+78
View File
@@ -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);
}
}
+60
View File
@@ -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);