From 8356cee5cfd73179371541ca7f9d1340673c741e Mon Sep 17 00:00:00 2001 From: Blank_Answer <97771966+blankanswer@users.noreply.github.com> Date: Wed, 8 Jul 2026 04:20:55 +0000 Subject: [PATCH 1/6] fix(cpp): recover callables after preprocessor signatures Signed-off-by: Blank_Answer <97771966+blankanswer@users.noreply.github.com> --- internal/cbm/cbm.c | 56 +++++++++++++++++++++++++++++++++++-- internal/cbm/cbm.h | 2 ++ internal/cbm/extract_defs.c | 8 ++++++ tests/test_extraction.c | 47 +++++++++++++++++++++++++++++++ 4 files changed, 111 insertions(+), 2 deletions(-) diff --git a/internal/cbm/cbm.c b/internal/cbm/cbm.c index 8de94b941..2fa615409 100644 --- a/internal/cbm/cbm.c +++ b/internal/cbm/cbm.c @@ -148,6 +148,48 @@ void cbm_rw_push(CBMRWArray *arr, CBMArena *a, CBMReadWrite rw) { arr->items[arr->count++] = rw; } +static bool def_label_is_preprocessed_callable(const char *label) { + return label && + (strcmp(label, "Function") == 0 || strcmp(label, "Method") == 0); +} + +static bool result_has_same_def_identity(const CBMFileResult *result, const CBMDefinition *def) { + if (!result || !def || !def->label || !def->qualified_name) { + return false; + } + for (int i = 0; i < result->defs.count; i++) { + const CBMDefinition *cur = &result->defs.items[i]; + if (!cur->label || !cur->qualified_name) { + continue; + } + if (strcmp(cur->label, def->label) == 0 && + strcmp(cur->qualified_name, def->qualified_name) == 0) { + return true; + } + } + return false; +} + +static void merge_missing_preprocessed_callables(CBMFileResult *dst, const CBMFileResult *src, + CBMArena *arena) { + if (!dst || !src) { + return; + } + for (int i = 0; i < src->defs.count; i++) { + const CBMDefinition *def = &src->defs.items[i]; + if (!def->qualified_name || !def->name) { + continue; + } + if (!def_label_is_preprocessed_callable(def->label)) { + continue; + } + if (result_has_same_def_identity(dst, def)) { + continue; + } + cbm_defs_push(&dst->defs, arena, *def); + } +} + void cbm_typerefs_push(CBMTypeRefArray *arr, CBMArena *a, CBMTypeRef tr) { GROW_ARRAY(arr, a); arr->items[arr->count++] = tr; @@ -1052,8 +1094,10 @@ static CBMFileResult *cbm_extract_file_impl(const char *source, int source_len, // metrics. Remember the boundary. int orig_calls_count = result->calls.count; - // Second pass: preprocess C/C++/CUDA and extract additional macro-hidden calls. - // Defs keep original-source line numbers; only CALLS are extracted from expanded source. + // Second pass: preprocess C/C++/CUDA and extract additional macro-hidden + // callables/calls. Existing defs keep original-source line numbers; only + // callables missing from the raw tree-sitter pass are filled from expanded + // source so #ifdef/#else signature blocks cannot swallow later methods. if (language == CBM_LANG_C || language == CBM_LANG_CPP || language == CBM_LANG_CUDA) { uint64_t pp_start = now_ns(); char *expanded = cbm_preprocess(source, source_len, rel_path, extra_defines, include_paths, @@ -1092,6 +1136,14 @@ static CBMFileResult *cbm_extract_file_impl(const char *source, int source_len, .module_qn = result->module_qn, .root = pp_root, }; + CBMFileResult pp_defs_result = {0}; + pp_defs_result.module_qn = result->module_qn; + pp_defs_result.is_test_file = result->is_test_file; + CBMExtractCtx pp_defs_ctx = pp_ctx; + pp_defs_ctx.result = &pp_defs_result; + cbm_extract_definition_nodes(&pp_defs_ctx); + merge_missing_preprocessed_callables(result, &pp_defs_result, a); + // Re-run unified extraction on expanded source. // This adds macro-expanded calls; duplicates with original calls are // harmless (pipeline deduplicates by caller+callee). diff --git a/internal/cbm/cbm.h b/internal/cbm/cbm.h index c8645f555..4fe3632ac 100644 --- a/internal/cbm/cbm.h +++ b/internal/cbm/cbm.h @@ -620,6 +620,8 @@ void cbm_channels_push(CBMChannelArray *arr, CBMArena *a, CBMChannel ch); // --- Sub-extractor entry points --- void cbm_extract_definitions(CBMExtractCtx *ctx); +// Run only the definition walker: no Module node and no variable extraction. +void cbm_extract_definition_nodes(CBMExtractCtx *ctx); void cbm_extract_imports(CBMExtractCtx *ctx); void cbm_extract_usages(CBMExtractCtx *ctx); void cbm_extract_semantic(CBMExtractCtx *ctx); diff --git a/internal/cbm/extract_defs.c b/internal/cbm/extract_defs.c index 2de63ffbb..8b725351f 100644 --- a/internal/cbm/extract_defs.c +++ b/internal/cbm/extract_defs.c @@ -6396,3 +6396,11 @@ void cbm_extract_definitions(CBMExtractCtx *ctx) { // Extract module-level variables extract_variables(ctx, ctx->root, spec); } + +void cbm_extract_definition_nodes(CBMExtractCtx *ctx) { + const CBMLangSpec *spec = cbm_lang_spec(ctx->language); + if (!spec) { + return; + } + walk_defs(ctx, ctx->root, spec, 0); +} diff --git a/tests/test_extraction.c b/tests/test_extraction.c index f8228e46a..90fa420a8 100644 --- a/tests/test_extraction.c +++ b/tests/test_extraction.c @@ -1237,6 +1237,52 @@ TEST(cpp_out_of_line_method_issue428) { PASS(); } +/* #946: C++ code commonly gates alternate out-of-line method signatures with + * #ifdef/#else/#endif. Raw tree-sitter can mis-balance the duplicate opening + * braces and lose methods that follow the conditional block; extraction must + * still recover later methods from the translation unit. */ +TEST(cpp_preproc_signature_gap_issue946) { + CBMFileResult *r = + extract("struct Rect {};\n" + "struct IBinder {};\n" + "struct IRegionSamplingListener {};\n" + "typedef int status_t;\n" + "\n" + "class SurfaceFlinger {\n" + "public:\n" + " status_t addRegionSamplingListener(const Rect&, const IBinder&,\n" + " const IRegionSamplingListener&, bool);\n" + " status_t addRegionSamplingListener(const Rect&, const IBinder&,\n" + " const IRegionSamplingListener&);\n" + " void commit();\n" + " void composite();\n" + "};\n" + "\n" + "#ifdef FLYME_GRAPHICS_EXTEND_LUMARGB\n" + "status_t SurfaceFlinger::addRegionSamplingListener(const Rect& samplingArea,\n" + " const IBinder& stopLayerHandle,\n" + " const IRegionSamplingListener& listener,\n" + " const bool rgbSample) {\n" + "#else\n" + "status_t SurfaceFlinger::addRegionSamplingListener(const Rect& samplingArea,\n" + " const IBinder& stopLayerHandle,\n" + " const IRegionSamplingListener& listener) {\n" + "#endif\n" + " return 0;\n" + "}\n" + "\n" + "void SurfaceFlinger::commit() {}\n" + "\n" + "void SurfaceFlinger::composite() {}\n", + CBM_LANG_CPP, "t", "SurfaceFlinger.cpp"); + ASSERT_NOT_NULL(r); + ASSERT_FALSE(r->has_error); + ASSERT(has_def(r, "Method", "commit")); + ASSERT(has_def(r, "Method", "composite")); + cbm_free_result(r); + PASS(); +} + /* --- COBOL paragraph --- */ TEST(cobol_paragraph) { CBMFileResult *r = @@ -3601,6 +3647,7 @@ SUITE(extraction) { RUN_TEST(zig_struct); RUN_TEST(cpp_function); RUN_TEST(cpp_out_of_line_method_issue428); + RUN_TEST(cpp_preproc_signature_gap_issue946); RUN_TEST(cobol_paragraph); RUN_TEST(verilog_module); RUN_TEST(cuda_kernel); From 7caa52e01724010471b12d22ed1cf9d166c318a4 Mon Sep 17 00:00:00 2001 From: Blank_Answer <97771966+blankanswer@users.noreply.github.com> Date: Wed, 8 Jul 2026 04:46:46 +0000 Subject: [PATCH 2/6] style: format preprocessed callable helper Signed-off-by: Blank_Answer <97771966+blankanswer@users.noreply.github.com> --- internal/cbm/cbm.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/internal/cbm/cbm.c b/internal/cbm/cbm.c index 2fa615409..a8b1126a5 100644 --- a/internal/cbm/cbm.c +++ b/internal/cbm/cbm.c @@ -149,8 +149,7 @@ void cbm_rw_push(CBMRWArray *arr, CBMArena *a, CBMReadWrite rw) { } static bool def_label_is_preprocessed_callable(const char *label) { - return label && - (strcmp(label, "Function") == 0 || strcmp(label, "Method") == 0); + return label && (strcmp(label, "Function") == 0 || strcmp(label, "Method") == 0); } static bool result_has_same_def_identity(const CBMFileResult *result, const CBMDefinition *def) { From eee5a31ec53e09ad6913cf0fe770bcfa7969bf4e Mon Sep 17 00:00:00 2001 From: Blank_Answer <97771966+blankanswer@users.noreply.github.com> Date: Fri, 10 Jul 2026 05:44:42 +0000 Subject: [PATCH 3/6] fix(cpp): remap preprocessed recovery coverage Signed-off-by: Blank_Answer <97771966+blankanswer@users.noreply.github.com> --- internal/cbm/cbm.c | 356 +++++++++++++++++++++++++++++++++++++++- tests/test_extraction.c | 18 ++ 2 files changed, 368 insertions(+), 6 deletions(-) diff --git a/internal/cbm/cbm.c b/internal/cbm/cbm.c index a8b1126a5..cca5ee8cc 100644 --- a/internal/cbm/cbm.c +++ b/internal/cbm/cbm.c @@ -152,6 +152,190 @@ static bool def_label_is_preprocessed_callable(const char *label) { return label && (strcmp(label, "Function") == 0 || strcmp(label, "Method") == 0); } +static bool cbm_ident_char(char c) { + return isalnum((unsigned char)c) || c == '_'; +} + +static bool cbm_plain_identifier(const char *s) { + if (!s || (!isalpha((unsigned char)s[0]) && s[0] != '_')) { + return false; + } + for (const char *p = s + 1; *p; p++) { + if (!cbm_ident_char(*p)) { + return false; + } + } + return true; +} + +static const char *cbm_last_qn_segment(const char *qn) { + if (!qn) { + return NULL; + } + const char *dot = strrchr(qn, '.'); + return dot ? dot + 1 : qn; +} + +static bool cbm_ident_at(const char *source, int source_len, int pos, const char *ident, + int ident_len) { + if (pos < 0 || ident_len <= 0 || pos + ident_len > source_len) { + return false; + } + if (pos > 0 && cbm_ident_char(source[pos - 1])) { + return false; + } + if (pos + ident_len < source_len && cbm_ident_char(source[pos + ident_len])) { + return false; + } + return strncmp(source + pos, ident, (size_t)ident_len) == 0; +} + +static bool cbm_scope_before_name_matches(const char *source, int name_pos, const char *scope) { + if (!scope || !scope[0]) { + return true; + } + const char *begin = source; + const char *p = source + name_pos; + while (p > begin && isspace((unsigned char)p[-1])) { + p--; + } + if (p - begin < 2 || p[-1] != ':' || p[-2] != ':') { + return false; + } + p -= 2; + while (p > begin && isspace((unsigned char)p[-1])) { + p--; + } + const char *end = p; + while (p > begin && cbm_ident_char(p[-1])) { + p--; + } + size_t len = (size_t)(end - p); + return len == strlen(scope) && strncmp(p, scope, len) == 0; +} + +static int cbm_find_matching_paren(const char *source, int source_len, int open_pos) { + int depth = 0; + for (int i = open_pos; i < source_len; i++) { + if (source[i] == '(') { + depth++; + } else if (source[i] == ')') { + if (--depth == 0) { + return i; + } + } + } + return -1; +} + +static bool cbm_span_ws_equal(const char *a, int alen, const char *b) { + int ai = 0; + int bi = 0; + int blen = b ? (int)strlen(b) : 0; + while (ai < alen || bi < blen) { + while (ai < alen && isspace((unsigned char)a[ai])) { + ai++; + } + while (bi < blen && isspace((unsigned char)b[bi])) { + bi++; + } + if (ai >= alen || bi >= blen) { + break; + } + if (a[ai++] != b[bi++]) { + return false; + } + } + while (ai < alen && isspace((unsigned char)a[ai])) { + ai++; + } + while (bi < blen && isspace((unsigned char)b[bi])) { + bi++; + } + return ai == alen && bi == blen; +} + +static int cbm_find_definition_open_brace(const char *source, int source_len, int after_paren) { + for (int i = after_paren + 1; i < source_len; i++) { + if (source[i] == '{') { + return i; + } + if (source[i] == ';') { + return -1; /* declaration, not definition */ + } + } + return -1; +} + +static int cbm_find_definition_close_brace(const char *source, int source_len, int open_pos) { + int depth = 0; + for (int i = open_pos; i < source_len; i++) { + if (source[i] == '{') { + depth++; + } else if (source[i] == '}') { + if (--depth == 0) { + return i; + } + } + } + return open_pos; +} + +static uint32_t cbm_line_for_byte(const char *source, int byte_pos) { + uint32_t line = 1; + for (int i = 0; i < byte_pos; i++) { + if (source[i] == '\n') { + line++; + } + } + return line; +} + +static bool cbm_remap_preprocessed_callable_lines(CBMDefinition *def, const char *source, + int source_len) { + if (!def || !source || source_len <= 0 || !cbm_plain_identifier(def->name)) { + return false; + } + const char *scope = NULL; + if (def->label && strcmp(def->label, "Method") == 0) { + scope = cbm_last_qn_segment(def->parent_class); + } + int name_len = (int)strlen(def->name); + for (int i = 0; i + name_len < source_len; i++) { + if (!cbm_ident_at(source, source_len, i, def->name, name_len)) { + continue; + } + if (!cbm_scope_before_name_matches(source, i, scope)) { + continue; + } + int p = i + name_len; + while (p < source_len && isspace((unsigned char)source[p])) { + p++; + } + if (p >= source_len || source[p] != '(') { + continue; + } + int close_paren = cbm_find_matching_paren(source, source_len, p); + if (close_paren < 0) { + continue; + } + if (def->signature && !cbm_span_ws_equal(source + p, close_paren - p + 1, + def->signature)) { + continue; + } + int open_brace = cbm_find_definition_open_brace(source, source_len, close_paren); + if (open_brace < 0) { + continue; + } + int close_brace = cbm_find_definition_close_brace(source, source_len, open_brace); + def->start_line = cbm_line_for_byte(source, i); + def->end_line = cbm_line_for_byte(source, close_brace); + def->lines = (int)(def->end_line - def->start_line + 1); + return true; + } + return false; +} + static bool result_has_same_def_identity(const CBMFileResult *result, const CBMDefinition *def) { if (!result || !def || !def->label || !def->qualified_name) { return false; @@ -170,7 +354,8 @@ static bool result_has_same_def_identity(const CBMFileResult *result, const CBMD } static void merge_missing_preprocessed_callables(CBMFileResult *dst, const CBMFileResult *src, - CBMArena *arena) { + CBMArena *arena, const char *original_source, + int original_source_len) { if (!dst || !src) { return; } @@ -185,7 +370,10 @@ static void merge_missing_preprocessed_callables(CBMFileResult *dst, const CBMFi if (result_has_same_def_identity(dst, def)) { continue; } - cbm_defs_push(&dst->defs, arena, *def); + CBMDefinition remapped = *def; + (void)cbm_remap_preprocessed_callable_lines(&remapped, original_source, + original_source_len); + cbm_defs_push(&dst->defs, arena, remapped); } } @@ -858,10 +1046,165 @@ static bool cbm_region_is_recovered(uint32_t rs, uint32_t re, const CBMDefArray return covered_to >= re; } -static void cbm_subtract_recovered_regions(cbm_error_regions_t *regs, const CBMDefArray *defs) { +typedef struct { + uint32_t starts[256]; + uint32_t ends[256]; + int count; +} cbm_line_intervals_t; + +static bool cbm_lang_uses_c_preprocessor(CBMLanguage language) { + return language == CBM_LANG_C || language == CBM_LANG_CPP || language == CBM_LANG_CUDA; +} + +static bool cbm_def_is_recovery_evidence(const CBMDefinition *d, uint32_t rs, uint32_t re) { + return d && d->label && strcmp(d->label, "Module") != 0 && strcmp(d->label, "Package") != 0 && + d->start_line >= rs && d->start_line <= re; +} + +static void cbm_line_intervals_push(cbm_line_intervals_t *ivals, uint32_t start, uint32_t end) { + if (!ivals || ivals->count >= (int)(sizeof(ivals->starts) / sizeof(ivals->starts[0]))) { + return; + } + ivals->starts[ivals->count] = start; + ivals->ends[ivals->count] = end < start ? start : end; + ivals->count++; +} + +static bool cbm_line_intervals_cover(const cbm_line_intervals_t *ivals, uint32_t line) { + if (!ivals) { + return false; + } + for (int i = 0; i < ivals->count; i++) { + if (line >= ivals->starts[i] && line <= ivals->ends[i]) { + return true; + } + } + return false; +} + +static bool cbm_line_blank_or_preproc(const char *source, int start, int end, bool *is_preproc) { + int p = start; + while (p < end && isspace((unsigned char)source[p])) { + p++; + } + if (is_preproc) { + *is_preproc = p < end && source[p] == '#'; + } + return p >= end || (p < end && source[p] == '#'); +} + +static void cbm_collect_cpp_preproc_alt_signatures(const char *source, int source_len, uint32_t rs, + uint32_t re, const CBMDefArray *defs, + cbm_line_intervals_t *alts) { + int line_start = 0; + uint32_t line = 1; + for (int p = 0; p <= source_len; p++) { + if (p < source_len && source[p] != '\n') { + continue; + } + int line_end = p; + if (line >= rs && line <= re) { + for (int di = 0; di < defs->count; di++) { + const CBMDefinition *d = &defs->items[di]; + if (!cbm_def_is_recovery_evidence(d, rs, re) || + !def_label_is_preprocessed_callable(d->label) || !cbm_plain_identifier(d->name)) { + continue; + } + const char *scope = NULL; + if (strcmp(d->label, "Method") == 0) { + scope = cbm_last_qn_segment(d->parent_class); + } + int name_len = (int)strlen(d->name); + for (int pos = line_start; pos + name_len <= line_end; pos++) { + if (!cbm_ident_at(source, source_len, pos, d->name, name_len) || + !cbm_scope_before_name_matches(source, pos, scope)) { + continue; + } + int sig_start = pos + name_len; + while (sig_start < source_len && isspace((unsigned char)source[sig_start])) { + sig_start++; + } + if (sig_start >= source_len || source[sig_start] != '(') { + continue; + } + int close_paren = cbm_find_matching_paren(source, source_len, sig_start); + if (close_paren < 0) { + continue; + } + int open_brace = cbm_find_definition_open_brace(source, source_len, close_paren); + if (open_brace < 0) { + continue; + } + uint32_t open_line = cbm_line_for_byte(source, open_brace); + if (open_line >= line && open_line <= re) { + cbm_line_intervals_push(alts, line, open_line); + } + } + } + } + line++; + line_start = p + 1; + } +} + +/* C-family preprocessor recovery: the raw tree can report one ERROR region + * spanning an entire #ifdef/#else signature block plus later definitions. Once + * the preprocessed pass recovers the selected callable and the trailing defs, + * unselected same-name signature lines are not a graph miss for that selected + * configuration. Different-name branches still stay flagged. */ +static bool cbm_region_is_recovered_with_cpp_preproc(uint32_t rs, uint32_t re, + const CBMDefArray *defs, + const char *source, int source_len, + CBMLanguage language) { + if (!cbm_lang_uses_c_preprocessor(language) || !source || source_len <= 0) { + return false; + } + + cbm_line_intervals_t defs_cover = {{0}, {0}, 0}; + for (int i = 0; i < defs->count; i++) { + const CBMDefinition *d = &defs->items[i]; + if (cbm_def_is_recovery_evidence(d, rs, re)) { + cbm_line_intervals_push(&defs_cover, d->start_line, d->end_line); + } + } + if (defs_cover.count == 0) { + return false; + } + + cbm_line_intervals_t alt_sigs = {{0}, {0}, 0}; + cbm_collect_cpp_preproc_alt_signatures(source, source_len, rs, re, defs, &alt_sigs); + + bool saw_preproc = false; + int line_start = 0; + uint32_t line = 1; + for (int p = 0; p <= source_len; p++) { + if (p < source_len && source[p] != '\n') { + continue; + } + int line_end = p; + if (line >= rs && line <= re) { + bool is_preproc = false; + if (cbm_line_blank_or_preproc(source, line_start, line_end, &is_preproc)) { + saw_preproc = saw_preproc || is_preproc; + } else if (!cbm_line_intervals_cover(&defs_cover, line) && + !cbm_line_intervals_cover(&alt_sigs, line)) { + return false; + } + } + line++; + line_start = p + 1; + } + return saw_preproc; +} + +static void cbm_subtract_recovered_regions(cbm_error_regions_t *regs, const CBMDefArray *defs, + const char *source, int source_len, + CBMLanguage language) { int kept = 0; for (int i = 0; i < regs->count; i++) { - if (!cbm_region_is_recovered(regs->starts[i], regs->ends[i], defs)) { + if (!cbm_region_is_recovered(regs->starts[i], regs->ends[i], defs) && + !cbm_region_is_recovered_with_cpp_preproc(regs->starts[i], regs->ends[i], defs, + source, source_len, language)) { regs->starts[kept] = regs->starts[i]; regs->ends[kept] = regs->ends[i]; kept++; @@ -1141,7 +1484,8 @@ static CBMFileResult *cbm_extract_file_impl(const char *source, int source_len, CBMExtractCtx pp_defs_ctx = pp_ctx; pp_defs_ctx.result = &pp_defs_result; cbm_extract_definition_nodes(&pp_defs_ctx); - merge_missing_preprocessed_callables(result, &pp_defs_result, a); + merge_missing_preprocessed_callables(result, &pp_defs_result, a, source, + source_len); // Re-run unified extraction on expanded source. // This adds macro-expanded calls; duplicates with original calls are @@ -1273,7 +1617,7 @@ static CBMFileResult *cbm_extract_file_impl(const char *source, int source_len, } else { cbm_collect_error_regions(root, ®s); } - cbm_subtract_recovered_regions(®s, &result->defs); + cbm_subtract_recovered_regions(®s, &result->defs, source, source_len, language); if (regs.count > 0) { result->parse_incomplete = true; result->error_region_count = regs.count; diff --git a/tests/test_extraction.c b/tests/test_extraction.c index 90fa420a8..5e6bfd883 100644 --- a/tests/test_extraction.c +++ b/tests/test_extraction.c @@ -21,6 +21,17 @@ static int has_def(CBMFileResult *r, const char *label, const char *name) { return 0; } +static const CBMDefinition *find_def_by_label_name(CBMFileResult *r, const char *label, + const char *name) { + for (int i = 0; i < r->defs.count; i++) { + if (r->defs.items[i].label && strcmp(r->defs.items[i].label, label) == 0 && + r->defs.items[i].name && strcmp(r->defs.items[i].name, name) == 0) { + return &r->defs.items[i]; + } + } + return NULL; +} + /* Check if any definition has the given name (any label). */ static int has_def_any(CBMFileResult *r, const char *name) { for (int i = 0; i < r->defs.count; i++) { @@ -1279,6 +1290,13 @@ TEST(cpp_preproc_signature_gap_issue946) { ASSERT_FALSE(r->has_error); ASSERT(has_def(r, "Method", "commit")); ASSERT(has_def(r, "Method", "composite")); + const CBMDefinition *composite = find_def_by_label_name(r, "Method", "composite"); + ASSERT_NOT_NULL(composite); + ASSERT_EQ(composite->start_line, 31u); + ASSERT_EQ(composite->end_line, 31u); + ASSERT_FALSE(r->parse_incomplete); + ASSERT_EQ(r->error_region_count, 0); + ASSERT_NULL(r->error_ranges); cbm_free_result(r); PASS(); } From 4a7a72d9e6b83635e0d1f6f76817d91b12c50270 Mon Sep 17 00:00:00 2001 From: Blank_Answer <97771966+blankanswer@users.noreply.github.com> Date: Fri, 10 Jul 2026 06:03:18 +0000 Subject: [PATCH 4/6] style: satisfy lint for preprocessor recovery Signed-off-by: Blank_Answer <97771966+blankanswer@users.noreply.github.com> --- internal/cbm/cbm.c | 42 +++++++++++++++++++++--------------------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/internal/cbm/cbm.c b/internal/cbm/cbm.c index cca5ee8cc..d9b78a21f 100644 --- a/internal/cbm/cbm.c +++ b/internal/cbm/cbm.c @@ -194,24 +194,23 @@ static bool cbm_scope_before_name_matches(const char *source, int name_pos, cons if (!scope || !scope[0]) { return true; } - const char *begin = source; - const char *p = source + name_pos; - while (p > begin && isspace((unsigned char)p[-1])) { + int p = name_pos; + while (p > 0 && isspace((unsigned char)source[p - 1])) { p--; } - if (p - begin < 2 || p[-1] != ':' || p[-2] != ':') { + if (p < 2 || source[p - 1] != ':' || source[p - 2] != ':') { return false; } p -= 2; - while (p > begin && isspace((unsigned char)p[-1])) { + while (p > 0 && isspace((unsigned char)source[p - 1])) { p--; } - const char *end = p; - while (p > begin && cbm_ident_char(p[-1])) { + int end = p; + while (p > 0 && cbm_ident_char(source[p - 1])) { p--; } size_t len = (size_t)(end - p); - return len == strlen(scope) && strncmp(p, scope, len) == 0; + return len == strlen(scope) && strncmp(source + p, scope, len) == 0; } static int cbm_find_matching_paren(const char *source, int source_len, int open_pos) { @@ -319,8 +318,7 @@ static bool cbm_remap_preprocessed_callable_lines(CBMDefinition *def, const char if (close_paren < 0) { continue; } - if (def->signature && !cbm_span_ws_equal(source + p, close_paren - p + 1, - def->signature)) { + if (def->signature && !cbm_span_ws_equal(source + p, close_paren - p + 1, def->signature)) { continue; } int open_brace = cbm_find_definition_open_brace(source, source_len, close_paren); @@ -1087,10 +1085,11 @@ static bool cbm_line_blank_or_preproc(const char *source, int start, int end, bo while (p < end && isspace((unsigned char)source[p])) { p++; } + bool preproc = p < end && source[p] == '#'; if (is_preproc) { - *is_preproc = p < end && source[p] == '#'; + *is_preproc = preproc; } - return p >= end || (p < end && source[p] == '#'); + return p >= end || preproc; } static void cbm_collect_cpp_preproc_alt_signatures(const char *source, int source_len, uint32_t rs, @@ -1102,12 +1101,13 @@ static void cbm_collect_cpp_preproc_alt_signatures(const char *source, int sourc if (p < source_len && source[p] != '\n') { continue; } - int line_end = p; if (line >= rs && line <= re) { + int line_end = p; for (int di = 0; di < defs->count; di++) { const CBMDefinition *d = &defs->items[di]; if (!cbm_def_is_recovery_evidence(d, rs, re) || - !def_label_is_preprocessed_callable(d->label) || !cbm_plain_identifier(d->name)) { + !def_label_is_preprocessed_callable(d->label) || + !cbm_plain_identifier(d->name)) { continue; } const char *scope = NULL; @@ -1131,7 +1131,8 @@ static void cbm_collect_cpp_preproc_alt_signatures(const char *source, int sourc if (close_paren < 0) { continue; } - int open_brace = cbm_find_definition_open_brace(source, source_len, close_paren); + int open_brace = + cbm_find_definition_open_brace(source, source_len, close_paren); if (open_brace < 0) { continue; } @@ -1153,9 +1154,8 @@ static void cbm_collect_cpp_preproc_alt_signatures(const char *source, int sourc * unselected same-name signature lines are not a graph miss for that selected * configuration. Different-name branches still stay flagged. */ static bool cbm_region_is_recovered_with_cpp_preproc(uint32_t rs, uint32_t re, - const CBMDefArray *defs, - const char *source, int source_len, - CBMLanguage language) { + const CBMDefArray *defs, const char *source, + int source_len, CBMLanguage language) { if (!cbm_lang_uses_c_preprocessor(language) || !source || source_len <= 0) { return false; } @@ -1181,8 +1181,8 @@ static bool cbm_region_is_recovered_with_cpp_preproc(uint32_t rs, uint32_t re, if (p < source_len && source[p] != '\n') { continue; } - int line_end = p; if (line >= rs && line <= re) { + int line_end = p; bool is_preproc = false; if (cbm_line_blank_or_preproc(source, line_start, line_end, &is_preproc)) { saw_preproc = saw_preproc || is_preproc; @@ -1203,8 +1203,8 @@ static void cbm_subtract_recovered_regions(cbm_error_regions_t *regs, const CBMD int kept = 0; for (int i = 0; i < regs->count; i++) { if (!cbm_region_is_recovered(regs->starts[i], regs->ends[i], defs) && - !cbm_region_is_recovered_with_cpp_preproc(regs->starts[i], regs->ends[i], defs, - source, source_len, language)) { + !cbm_region_is_recovered_with_cpp_preproc(regs->starts[i], regs->ends[i], defs, source, + source_len, language)) { regs->starts[kept] = regs->starts[i]; regs->ends[kept] = regs->ends[i]; kept++; From 0a22907fddc33d3ebd255a4c74e86f812880af7f Mon Sep 17 00:00:00 2001 From: Blank_Answer <97771966+blankanswer@users.noreply.github.com> Date: Fri, 10 Jul 2026 07:15:05 +0000 Subject: [PATCH 5/6] fix(cpp): remap preprocessed recovery fail-closed Signed-off-by: Blank_Answer <97771966+blankanswer@users.noreply.github.com> --- internal/cbm/cbm.c | 565 +++++++++++++++++++++------------- internal/cbm/preprocessor.cpp | 195 ++++++++++-- internal/cbm/preprocessor.h | 18 ++ tests/test_extraction.c | 271 +++++++++++++--- 4 files changed, 770 insertions(+), 279 deletions(-) diff --git a/internal/cbm/cbm.c b/internal/cbm/cbm.c index d9b78a21f..92d7f35be 100644 --- a/internal/cbm/cbm.c +++ b/internal/cbm/cbm.c @@ -227,33 +227,6 @@ static int cbm_find_matching_paren(const char *source, int source_len, int open_ return -1; } -static bool cbm_span_ws_equal(const char *a, int alen, const char *b) { - int ai = 0; - int bi = 0; - int blen = b ? (int)strlen(b) : 0; - while (ai < alen || bi < blen) { - while (ai < alen && isspace((unsigned char)a[ai])) { - ai++; - } - while (bi < blen && isspace((unsigned char)b[bi])) { - bi++; - } - if (ai >= alen || bi >= blen) { - break; - } - if (a[ai++] != b[bi++]) { - return false; - } - } - while (ai < alen && isspace((unsigned char)a[ai])) { - ai++; - } - while (bi < blen && isspace((unsigned char)b[bi])) { - bi++; - } - return ai == alen && bi == blen; -} - static int cbm_find_definition_open_brace(const char *source, int source_len, int after_paren) { for (int i = after_paren + 1; i < source_len; i++) { if (source[i] == '{') { @@ -266,20 +239,6 @@ static int cbm_find_definition_open_brace(const char *source, int source_len, in return -1; } -static int cbm_find_definition_close_brace(const char *source, int source_len, int open_pos) { - int depth = 0; - for (int i = open_pos; i < source_len; i++) { - if (source[i] == '{') { - depth++; - } else if (source[i] == '}') { - if (--depth == 0) { - return i; - } - } - } - return open_pos; -} - static uint32_t cbm_line_for_byte(const char *source, int byte_pos) { uint32_t line = 1; for (int i = 0; i < byte_pos; i++) { @@ -290,53 +249,172 @@ static uint32_t cbm_line_for_byte(const char *source, int byte_pos) { return line; } -static bool cbm_remap_preprocessed_callable_lines(CBMDefinition *def, const char *source, - int source_len) { +static bool cbm_source_line_bounds(const char *source, int source_len, uint32_t start_line, + uint32_t end_line, int *out_start, int *out_end) { + if (!source || source_len < 0 || start_line == 0 || end_line < start_line || !out_start || + !out_end) { + return false; + } + uint32_t line = 1; + int line_start = 0; + int span_start = -1; + int span_end = -1; + for (int i = 0; i <= source_len; i++) { + if (i < source_len && source[i] != '\n') { + continue; + } + if (line == start_line) { + span_start = line_start; + } + if (line == end_line) { + span_end = i; + break; + } + line++; + line_start = i + 1; + } + if (span_start < 0 || span_end < span_start) { + return false; + } + *out_start = span_start; + *out_end = span_end; + return true; +} + +static bool cbm_name_context_allows_definition(const char *source, int line_start, int name_pos) { + int p = name_pos; + while (p > line_start && isspace((unsigned char)source[p - 1])) { + p--; + } + if (p == line_start) { + return true; + } + switch (source[p - 1]) { + case '(': + case ',': + case '=': + case '!': + case '?': + case '[': + case '.': + return false; + default: + return true; + } +} + +static bool cbm_original_span_contains_callable_def(const CBMDefinition *def, const char *source, + int source_len) { if (!def || !source || source_len <= 0 || !cbm_plain_identifier(def->name)) { return false; } + int span_start = 0; + int span_end = 0; + if (!cbm_source_line_bounds(source, source_len, def->start_line, def->end_line, &span_start, + &span_end)) { + return false; + } const char *scope = NULL; if (def->label && strcmp(def->label, "Method") == 0) { scope = cbm_last_qn_segment(def->parent_class); } int name_len = (int)strlen(def->name); - for (int i = 0; i + name_len < source_len; i++) { + for (int i = span_start; i + name_len <= span_end; i++) { if (!cbm_ident_at(source, source_len, i, def->name, name_len)) { continue; } if (!cbm_scope_before_name_matches(source, i, scope)) { continue; } + if (!cbm_name_context_allows_definition(source, span_start, i)) { + continue; + } int p = i + name_len; - while (p < source_len && isspace((unsigned char)source[p])) { + while (p < span_end && isspace((unsigned char)source[p])) { p++; } - if (p >= source_len || source[p] != '(') { + if (p >= span_end || source[p] != '(') { continue; } - int close_paren = cbm_find_matching_paren(source, source_len, p); + int close_paren = cbm_find_matching_paren(source, span_end, p); if (close_paren < 0) { continue; } - if (def->signature && !cbm_span_ws_equal(source + p, close_paren - p + 1, def->signature)) { - continue; + return cbm_find_definition_open_brace(source, span_end, close_paren) >= 0; + } + return false; +} + +static bool cbm_remap_preprocessed_callable_lines(CBMDefinition *def, + const CBMPreprocessedSource *pp, + const char *original_source, + int original_source_len) { + if (!def || !pp || !pp->original_line_by_expanded_line || !pp->belongs_to_main_file || + def->start_line == 0 || def->end_line < def->start_line || + def->end_line > (uint32_t)pp->expanded_line_count) { + return false; + } + uint32_t original_start = pp->original_line_by_expanded_line[def->start_line]; + uint32_t original_end = pp->original_line_by_expanded_line[def->end_line]; + if (!original_start || !original_end || original_end < original_start) { + return false; + } + for (uint32_t line = def->start_line; line <= def->end_line; line++) { + if (!pp->belongs_to_main_file[line] || !pp->original_line_by_expanded_line[line]) { + return false; } - int open_brace = cbm_find_definition_open_brace(source, source_len, close_paren); - if (open_brace < 0) { - continue; + } + def->start_line = original_start; + def->end_line = original_end; + def->lines = (int)(def->end_line - def->start_line + 1); + return cbm_original_span_contains_callable_def(def, original_source, original_source_len); +} + +typedef struct { + uint32_t start_line; + uint32_t end_line; + const char *label; + const char *name; + const char *parent_class; +} CBMRecoveredCallable; + +typedef struct { + CBMRecoveredCallable *items; + int count; + int cap; +} CBMRecoveredCallableArray; + +static bool cbm_recovered_callables_push(CBMArena *arena, CBMRecoveredCallableArray *arr, + const CBMDefinition *def) { + if (!arena || !arr || !def) { + return false; + } + if (arr->count >= arr->cap) { + int new_cap = arr->cap ? arr->cap * 2 : 16; + CBMRecoveredCallable *items = + (CBMRecoveredCallable *)cbm_arena_alloc(arena, sizeof(*items) * (size_t)new_cap); + if (!items) { + return false; } - int close_brace = cbm_find_definition_close_brace(source, source_len, open_brace); - def->start_line = cbm_line_for_byte(source, i); - def->end_line = cbm_line_for_byte(source, close_brace); - def->lines = (int)(def->end_line - def->start_line + 1); - return true; + if (arr->items && arr->count > 0) { + memcpy(items, arr->items, sizeof(*items) * (size_t)arr->count); + } + arr->items = items; + arr->cap = new_cap; } - return false; + arr->items[arr->count++] = (CBMRecoveredCallable){ + .start_line = def->start_line, + .end_line = def->end_line < def->start_line ? def->start_line : def->end_line, + .label = def->label, + .name = def->name, + .parent_class = def->parent_class, + }; + return true; } -static bool result_has_same_def_identity(const CBMFileResult *result, const CBMDefinition *def) { +static int result_find_same_def_identity(const CBMFileResult *result, const CBMDefinition *def) { if (!result || !def || !def->label || !def->qualified_name) { - return false; + return -1; } for (int i = 0; i < result->defs.count; i++) { const CBMDefinition *cur = &result->defs.items[i]; @@ -345,15 +423,87 @@ static bool result_has_same_def_identity(const CBMFileResult *result, const CBMD } if (strcmp(cur->label, def->label) == 0 && strcmp(cur->qualified_name, def->qualified_name) == 0) { + return i; + } + } + return -1; +} + +static bool cbm_pp_directive_matches(const char *p, int len, const char *word) { + int word_len = (int)strlen(word); + return len >= word_len && strncmp(p, word, (size_t)word_len) == 0 && + (len == word_len || !cbm_ident_char(p[word_len])); +} + +static bool cbm_line_is_preprocessor_branch_directive(const char *line, int len) { + int p = 0; + while (p < len && isspace((unsigned char)line[p])) { + p++; + } + if (p >= len || line[p++] != '#') { + return false; + } + while (p < len && isspace((unsigned char)line[p])) { + p++; + } + int word_len = len - p; + return cbm_pp_directive_matches(line + p, word_len, "if") || + cbm_pp_directive_matches(line + p, word_len, "ifdef") || + cbm_pp_directive_matches(line + p, word_len, "ifndef") || + cbm_pp_directive_matches(line + p, word_len, "elif") || + cbm_pp_directive_matches(line + p, word_len, "else") || + cbm_pp_directive_matches(line + p, word_len, "endif"); +} + +static bool cbm_span_contains_preprocessor_branch(const char *source, int source_len, + uint32_t start_line, uint32_t end_line) { + int span_start = 0; + int span_end = 0; + if (!cbm_source_line_bounds(source, source_len, start_line, end_line, &span_start, &span_end)) { + return false; + } + int line_start = span_start; + while (line_start <= span_end) { + int line_end = line_start; + while (line_end < span_end && source[line_end] != '\n') { + line_end++; + } + if (cbm_line_is_preprocessor_branch_directive(source + line_start, line_end - line_start)) { return true; } + if (line_end >= span_end) { + break; + } + line_start = line_end + 1; } return false; } +static bool cbm_should_replace_preprocessed_duplicate(const CBMDefinition *existing, + const CBMDefinition *remapped, + const char *source, int source_len) { + if (!existing || !remapped || existing->start_line == 0 || remapped->start_line == 0) { + return false; + } + if (existing->start_line == remapped->start_line && existing->end_line == remapped->end_line) { + return false; + } + uint32_t start = + existing->start_line < remapped->start_line ? existing->start_line : remapped->start_line; + uint32_t existing_end = + existing->end_line < existing->start_line ? existing->start_line : existing->end_line; + uint32_t remapped_end = + remapped->end_line < remapped->start_line ? remapped->start_line : remapped->end_line; + uint32_t end = existing_end > remapped_end ? existing_end : remapped_end; + return cbm_span_contains_preprocessor_branch(source, source_len, start, end); +} + static void merge_missing_preprocessed_callables(CBMFileResult *dst, const CBMFileResult *src, - CBMArena *arena, const char *original_source, - int original_source_len) { + CBMArena *arena, + const CBMPreprocessedSource *preprocessed, + const char *original_source, + int original_source_len, + CBMRecoveredCallableArray *recovered) { if (!dst || !src) { return; } @@ -365,13 +515,22 @@ static void merge_missing_preprocessed_callables(CBMFileResult *dst, const CBMFi if (!def_label_is_preprocessed_callable(def->label)) { continue; } - if (result_has_same_def_identity(dst, def)) { + CBMDefinition remapped = *def; + if (!cbm_remap_preprocessed_callable_lines(&remapped, preprocessed, original_source, + original_source_len)) { continue; } - CBMDefinition remapped = *def; - (void)cbm_remap_preprocessed_callable_lines(&remapped, original_source, - original_source_len); - cbm_defs_push(&dst->defs, arena, remapped); + if (!cbm_recovered_callables_push(arena, recovered, &remapped)) { + continue; + } + int existing_idx = result_find_same_def_identity(dst, &remapped); + if (existing_idx < 0) { + cbm_defs_push(&dst->defs, arena, remapped); + } else if (cbm_should_replace_preprocessed_duplicate(&dst->defs.items[existing_idx], + &remapped, original_source, + original_source_len)) { + dst->defs.items[existing_idx] = remapped; + } } } @@ -1000,181 +1159,151 @@ static void cbm_collect_error_regions(TSNode n, cbm_error_regions_t *acc) { * not evidence the region's constructs survived. Conservative: partially * covered regions stay flagged. */ static bool cbm_region_is_recovered(uint32_t rs, uint32_t re, const CBMDefArray *defs) { - enum { MAX_COVER_DEFS = 256 }; - uint32_t starts[MAX_COVER_DEFS]; - uint32_t ends[MAX_COVER_DEFS]; - int n = 0; - for (int i = 0; i < defs->count && n < MAX_COVER_DEFS; i++) { - const CBMDefinition *d = &defs->items[i]; - if (!d->label || strcmp(d->label, "Module") == 0 || strcmp(d->label, "Package") == 0) { - continue; - } - if (d->start_line < rs || d->start_line > re) { - continue; /* recovery evidence must originate inside the region */ - } - starts[n] = d->start_line; - ends[n] = d->end_line < d->start_line ? d->start_line : d->end_line; - n++; - } - if (n == 0) { + if (!defs || defs->count <= 0) { return false; } - /* Insertion-sort by start, then sweep for gaps in [rs, re]. */ - for (int i = 1; i < n; i++) { - uint32_t s = starts[i]; - uint32_t e = ends[i]; - int j = i - 1; - while (j >= 0 && starts[j] > s) { - starts[j + 1] = starts[j]; - ends[j + 1] = ends[j]; - j--; - } - starts[j + 1] = s; - ends[j + 1] = e; - } uint32_t covered_to = rs - 1; - for (int i = 0; i < n; i++) { - if (starts[i] > covered_to + 1) { - return false; /* uncovered gap */ + while (covered_to < re) { + uint32_t next_line = covered_to + 1; + uint32_t best_end = covered_to; + for (int i = 0; i < defs->count; i++) { + const CBMDefinition *d = &defs->items[i]; + if (!d->label || strcmp(d->label, "Module") == 0 || strcmp(d->label, "Package") == 0) { + continue; + } + if (d->start_line < rs || d->start_line > re) { + continue; /* recovery evidence must originate inside the region */ + } + uint32_t end = d->end_line < d->start_line ? d->start_line : d->end_line; + if (d->start_line <= next_line && end > best_end) { + best_end = end; + } } - if (ends[i] > covered_to) { - covered_to = ends[i]; + if (best_end < next_line) { + return false; } + covered_to = best_end; } - return covered_to >= re; + return true; } -typedef struct { - uint32_t starts[256]; - uint32_t ends[256]; - int count; -} cbm_line_intervals_t; - static bool cbm_lang_uses_c_preprocessor(CBMLanguage language) { return language == CBM_LANG_C || language == CBM_LANG_CPP || language == CBM_LANG_CUDA; } -static bool cbm_def_is_recovery_evidence(const CBMDefinition *d, uint32_t rs, uint32_t re) { - return d && d->label && strcmp(d->label, "Module") != 0 && strcmp(d->label, "Package") != 0 && - d->start_line >= rs && d->start_line <= re; +static bool cbm_line_blank_or_preproc(const char *source, int start, int end, bool *is_preproc) { + int p = start; + while (p < end && isspace((unsigned char)source[p])) { + p++; + } + bool preproc = p < end && source[p] == '#'; + if (is_preproc) { + *is_preproc = preproc; + } + return p >= end || preproc; } -static void cbm_line_intervals_push(cbm_line_intervals_t *ivals, uint32_t start, uint32_t end) { - if (!ivals || ivals->count >= (int)(sizeof(ivals->starts) / sizeof(ivals->starts[0]))) { - return; +static int cbm_line_start_for_byte(const char *source, int byte_pos) { + while (byte_pos > 0 && source[byte_pos - 1] != '\n') { + byte_pos--; } - ivals->starts[ivals->count] = start; - ivals->ends[ivals->count] = end < start ? start : end; - ivals->count++; + return byte_pos; +} + +static bool cbm_recovered_callable_in_region(const CBMRecoveredCallable *rec, uint32_t rs, + uint32_t re) { + return rec && rec->name && rec->label && rec->start_line >= rs && rec->start_line <= re; } -static bool cbm_line_intervals_cover(const cbm_line_intervals_t *ivals, uint32_t line) { - if (!ivals) { +static bool cbm_line_in_recovered_callables(uint32_t line, + const CBMRecoveredCallableArray *recovered, uint32_t rs, + uint32_t re) { + if (!recovered) { return false; } - for (int i = 0; i < ivals->count; i++) { - if (line >= ivals->starts[i] && line <= ivals->ends[i]) { + for (int i = 0; i < recovered->count; i++) { + const CBMRecoveredCallable *rec = &recovered->items[i]; + if (!cbm_recovered_callable_in_region(rec, rs, re)) { + continue; + } + uint32_t end = rec->end_line < rec->start_line ? rec->start_line : rec->end_line; + if (line >= rec->start_line && line <= end) { return true; } } return false; } -static bool cbm_line_blank_or_preproc(const char *source, int start, int end, bool *is_preproc) { - int p = start; - while (p < end && isspace((unsigned char)source[p])) { - p++; +static bool cbm_recovered_alt_signature_covers_line(const char *source, int source_len, + uint32_t line, uint32_t rs, uint32_t re, + const CBMRecoveredCallableArray *recovered) { + if (!source || source_len <= 0 || !recovered) { + return false; } - bool preproc = p < end && source[p] == '#'; - if (is_preproc) { - *is_preproc = preproc; + int region_start = 0; + int region_end = 0; + if (!cbm_source_line_bounds(source, source_len, rs, re, ®ion_start, ®ion_end)) { + return false; } - return p >= end || preproc; -} - -static void cbm_collect_cpp_preproc_alt_signatures(const char *source, int source_len, uint32_t rs, - uint32_t re, const CBMDefArray *defs, - cbm_line_intervals_t *alts) { - int line_start = 0; - uint32_t line = 1; - for (int p = 0; p <= source_len; p++) { - if (p < source_len && source[p] != '\n') { + for (int i = 0; i < recovered->count; i++) { + const CBMRecoveredCallable *rec = &recovered->items[i]; + if (!cbm_recovered_callable_in_region(rec, rs, re) || !cbm_plain_identifier(rec->name)) { continue; } - if (line >= rs && line <= re) { - int line_end = p; - for (int di = 0; di < defs->count; di++) { - const CBMDefinition *d = &defs->items[di]; - if (!cbm_def_is_recovery_evidence(d, rs, re) || - !def_label_is_preprocessed_callable(d->label) || - !cbm_plain_identifier(d->name)) { - continue; - } - const char *scope = NULL; - if (strcmp(d->label, "Method") == 0) { - scope = cbm_last_qn_segment(d->parent_class); - } - int name_len = (int)strlen(d->name); - for (int pos = line_start; pos + name_len <= line_end; pos++) { - if (!cbm_ident_at(source, source_len, pos, d->name, name_len) || - !cbm_scope_before_name_matches(source, pos, scope)) { - continue; - } - int sig_start = pos + name_len; - while (sig_start < source_len && isspace((unsigned char)source[sig_start])) { - sig_start++; - } - if (sig_start >= source_len || source[sig_start] != '(') { - continue; - } - int close_paren = cbm_find_matching_paren(source, source_len, sig_start); - if (close_paren < 0) { - continue; - } - int open_brace = - cbm_find_definition_open_brace(source, source_len, close_paren); - if (open_brace < 0) { - continue; - } - uint32_t open_line = cbm_line_for_byte(source, open_brace); - if (open_line >= line && open_line <= re) { - cbm_line_intervals_push(alts, line, open_line); - } - } + const char *scope = NULL; + if (strcmp(rec->label, "Method") == 0) { + scope = cbm_last_qn_segment(rec->parent_class); + } + int name_len = (int)strlen(rec->name); + for (int pos = region_start; pos + name_len <= region_end; pos++) { + if (!cbm_ident_at(source, source_len, pos, rec->name, name_len) || + !cbm_scope_before_name_matches(source, pos, scope)) { + continue; + } + int sig_line_start = cbm_line_start_for_byte(source, pos); + if (!cbm_name_context_allows_definition(source, sig_line_start, pos)) { + continue; + } + int p = pos + name_len; + while (p < region_end && isspace((unsigned char)source[p])) { + p++; + } + if (p >= region_end || source[p] != '(') { + continue; + } + int close_paren = cbm_find_matching_paren(source, region_end, p); + if (close_paren < 0) { + continue; + } + int open_brace = cbm_find_definition_open_brace(source, region_end, close_paren); + if (open_brace < 0) { + continue; + } + uint32_t sig_start_line = cbm_line_for_byte(source, pos); + uint32_t sig_open_line = cbm_line_for_byte(source, open_brace); + if (line >= sig_start_line && line <= sig_open_line) { + return true; } } - line++; - line_start = p + 1; } + return false; } -/* C-family preprocessor recovery: the raw tree can report one ERROR region - * spanning an entire #ifdef/#else signature block plus later definitions. Once - * the preprocessed pass recovers the selected callable and the trailing defs, - * unselected same-name signature lines are not a graph miss for that selected - * configuration. Different-name branches still stay flagged. */ +/* C-family preprocessor recovery: only successfully remapped expanded-AST + * callables can justify clearing raw ERROR lines. Raw definitions still + * participate in the generic recovery subtraction above, but this preprocessor + * branch must not treat arbitrary raw defs as evidence. */ static bool cbm_region_is_recovered_with_cpp_preproc(uint32_t rs, uint32_t re, - const CBMDefArray *defs, const char *source, - int source_len, CBMLanguage language) { - if (!cbm_lang_uses_c_preprocessor(language) || !source || source_len <= 0) { + const CBMRecoveredCallableArray *recovered, + const char *source, int source_len, + CBMLanguage language) { + if (!cbm_lang_uses_c_preprocessor(language) || !source || source_len <= 0 || !recovered || + recovered->count <= 0) { return false; } - cbm_line_intervals_t defs_cover = {{0}, {0}, 0}; - for (int i = 0; i < defs->count; i++) { - const CBMDefinition *d = &defs->items[i]; - if (cbm_def_is_recovery_evidence(d, rs, re)) { - cbm_line_intervals_push(&defs_cover, d->start_line, d->end_line); - } - } - if (defs_cover.count == 0) { - return false; - } - - cbm_line_intervals_t alt_sigs = {{0}, {0}, 0}; - cbm_collect_cpp_preproc_alt_signatures(source, source_len, rs, re, defs, &alt_sigs); - bool saw_preproc = false; + bool saw_recovered = false; int line_start = 0; uint32_t line = 1; for (int p = 0; p <= source_len; p++) { @@ -1186,25 +1315,28 @@ static bool cbm_region_is_recovered_with_cpp_preproc(uint32_t rs, uint32_t re, bool is_preproc = false; if (cbm_line_blank_or_preproc(source, line_start, line_end, &is_preproc)) { saw_preproc = saw_preproc || is_preproc; - } else if (!cbm_line_intervals_cover(&defs_cover, line) && - !cbm_line_intervals_cover(&alt_sigs, line)) { + } else if (cbm_line_in_recovered_callables(line, recovered, rs, re)) { + saw_recovered = true; + } else if (!cbm_recovered_alt_signature_covers_line(source, source_len, line, rs, re, + recovered)) { return false; } } line++; line_start = p + 1; } - return saw_preproc; + return saw_preproc && saw_recovered; } static void cbm_subtract_recovered_regions(cbm_error_regions_t *regs, const CBMDefArray *defs, + const CBMRecoveredCallableArray *recovered, const char *source, int source_len, CBMLanguage language) { int kept = 0; for (int i = 0; i < regs->count; i++) { if (!cbm_region_is_recovered(regs->starts[i], regs->ends[i], defs) && - !cbm_region_is_recovered_with_cpp_preproc(regs->starts[i], regs->ends[i], defs, source, - source_len, language)) { + !cbm_region_is_recovered_with_cpp_preproc(regs->starts[i], regs->ends[i], recovered, + source, source_len, language)) { regs->starts[kept] = regs->starts[i]; regs->ends[kept] = regs->ends[i]; kept++; @@ -1435,6 +1567,7 @@ static CBMFileResult *cbm_extract_file_impl(const char *source, int source_len, // which must not be used for the def line-range attribution of the bottleneck // metrics. Remember the boundary. int orig_calls_count = result->calls.count; + CBMRecoveredCallableArray recovered_callables = {0}; // Second pass: preprocess C/C++/CUDA and extract additional macro-hidden // callables/calls. Existing defs keep original-source line numbers; only @@ -1442,9 +1575,10 @@ static CBMFileResult *cbm_extract_file_impl(const char *source, int source_len, // source so #ifdef/#else signature blocks cannot swallow later methods. if (language == CBM_LANG_C || language == CBM_LANG_CPP || language == CBM_LANG_CUDA) { uint64_t pp_start = now_ns(); - char *expanded = cbm_preprocess(source, source_len, rel_path, extra_defines, include_paths, - language != CBM_LANG_C); - if (expanded) { + CBMPreprocessedSource *preprocessed = cbm_preprocess_with_map( + source, source_len, rel_path, extra_defines, include_paths, language != CBM_LANG_C); + if (preprocessed && preprocessed->source) { + char *expanded = preprocessed->source; int expanded_len = (int)strlen(expanded); // Record calls count before second pass int calls_before = result->calls.count; @@ -1484,8 +1618,8 @@ static CBMFileResult *cbm_extract_file_impl(const char *source, int source_len, CBMExtractCtx pp_defs_ctx = pp_ctx; pp_defs_ctx.result = &pp_defs_result; cbm_extract_definition_nodes(&pp_defs_ctx); - merge_missing_preprocessed_callables(result, &pp_defs_result, a, source, - source_len); + merge_missing_preprocessed_callables(result, &pp_defs_result, a, preprocessed, + source, source_len, &recovered_callables); // Re-run unified extraction on expanded source. // This adds macro-expanded calls; duplicates with original calls are @@ -1501,7 +1635,7 @@ static CBMFileResult *cbm_extract_file_impl(const char *source, int source_len, ts_tree_delete(pp_tree); } } - cbm_preprocess_free(expanded); + cbm_preprocessed_source_free(preprocessed); atomic_fetch_add(&total_files_preprocessed, 1); (void)calls_before; // used for future logging } @@ -1617,7 +1751,8 @@ static CBMFileResult *cbm_extract_file_impl(const char *source, int source_len, } else { cbm_collect_error_regions(root, ®s); } - cbm_subtract_recovered_regions(®s, &result->defs, source, source_len, language); + cbm_subtract_recovered_regions(®s, &result->defs, &recovered_callables, source, + source_len, language); if (regs.count > 0) { result->parse_incomplete = true; result->error_region_count = regs.count; diff --git a/internal/cbm/preprocessor.cpp b/internal/cbm/preprocessor.cpp index 69f91c35a..e3b676340 100644 --- a/internal/cbm/preprocessor.cpp +++ b/internal/cbm/preprocessor.cpp @@ -10,45 +10,148 @@ #include #include #include +#include extern "C" { -char* cbm_preprocess( - const char* source, int source_len, - const char* filename, - const char** extra_defines, - const char** include_paths, - int cpp_mode -) { - if (!source || source_len <= 0) return NULL; - - // Fast-path: skip if no preprocessor directives worth expanding. - bool has_macros = false; +static bool has_preprocessor_work(const char *source, int source_len) { + if (!source || source_len <= 0) { + return false; + } for (int i = 0; i < source_len - 1; i++) { if (source[i] == '#') { // Skip whitespace after # int j = i + 1; - while (j < source_len && (source[j] == ' ' || source[j] == '\t')) j++; + while (j < source_len && (source[j] == ' ' || source[j] == '\t')) + j++; int remaining = source_len - j; if (remaining >= 6 && strncmp(source + j, "define", 6) == 0) { - has_macros = true; - break; + return true; } if (remaining >= 5 && strncmp(source + j, "ifdef", 5) == 0) { - has_macros = true; - break; + return true; } if (remaining >= 6 && strncmp(source + j, "ifndef", 6) == 0) { - has_macros = true; - break; + return true; + } + if (remaining >= 7 && strncmp(source + j, "include", 7) == 0) { + return true; } if (remaining >= 3 && strncmp(source + j, "if ", 3) == 0) { - has_macros = true; - break; + return true; + } + } + } + return false; +} + +static int count_expanded_lines(const std::string &text) { + int count = 1; + for (char c : text) { + if (c == '\n') { + count++; + } + } + return count; +} + +static bool parse_line_directive(const char *line, size_t len, uint32_t *out_line, + std::string *out_file) { + size_t i = 0; + while (i < len && (line[i] == ' ' || line[i] == '\t')) { + i++; + } + if (i >= len || line[i++] != '#') { + return false; + } + while (i < len && (line[i] == ' ' || line[i] == '\t')) { + i++; + } + static const char prefix[] = "line"; + if (i + sizeof(prefix) - 1 > len || strncmp(line + i, prefix, sizeof(prefix) - 1) != 0) { + return false; + } + i += sizeof(prefix) - 1; + if (i >= len || (line[i] != ' ' && line[i] != '\t')) { + return false; + } + while (i < len && (line[i] == ' ' || line[i] == '\t')) { + i++; + } + if (i >= len || line[i] < '0' || line[i] > '9') { + return false; + } + uint64_t parsed_line = 0; + while (i < len && line[i] >= '0' && line[i] <= '9') { + parsed_line = parsed_line * 10u + (uint64_t)(line[i] - '0'); + if (parsed_line > UINT32_MAX) { + return false; + } + i++; + } + while (i < len && (line[i] == ' ' || line[i] == '\t')) { + i++; + } + if (i >= len || line[i++] != '"') { + return false; + } + size_t file_start = i; + while (i < len && line[i] != '"') { + i++; + } + if (i >= len) { + return false; + } + *out_line = (uint32_t)parsed_line; + *out_file = std::string(line + file_start, i - file_start); + return true; +} + +static bool build_line_map(const std::string &expanded, const std::string &main_file, + uint32_t *original_line_by_expanded_line, + uint8_t *belongs_to_main_file) { + std::string current_file = main_file; + uint32_t current_line = 1; + int expanded_line = 1; + size_t line_start = 0; + + while (line_start <= expanded.size()) { + size_t line_end = expanded.find('\n', line_start); + if (line_end == std::string::npos) { + line_end = expanded.size(); + } + + uint32_t directive_line = 0; + std::string directive_file; + if (parse_line_directive(expanded.c_str() + line_start, line_end - line_start, + &directive_line, &directive_file)) { + current_file = directive_file; + current_line = directive_line; + original_line_by_expanded_line[expanded_line] = 0; + belongs_to_main_file[expanded_line] = 0; + } else { + original_line_by_expanded_line[expanded_line] = current_line; + belongs_to_main_file[expanded_line] = current_file == main_file ? 1 : 0; + if (current_line < UINT32_MAX) { + current_line++; } } + + if (line_end == expanded.size()) { + break; + } + line_start = line_end + 1; + expanded_line++; + } + return true; +} + +CBMPreprocessedSource *cbm_preprocess_with_map(const char *source, int source_len, + const char *filename, const char **extra_defines, + const char **include_paths, int cpp_mode) { + if (!has_preprocessor_work(source, source_len)) { + return NULL; // NULL = no expansion needed, use original } - if (!has_macros) return NULL; // NULL = no expansion needed, use original try { simplecpp::DUI dui; @@ -78,18 +181,58 @@ char* cbm_preprocess( // Clean up loaded file data simplecpp::cleanup(filedata); - char* out = (char*)malloc(result.size() + 1); - if (!out) return NULL; - memcpy(out, result.c_str(), result.size() + 1); - return out; + CBMPreprocessedSource *pp = (CBMPreprocessedSource *)calloc(1, sizeof(*pp)); + if (!pp) { + return NULL; + } + int line_count = count_expanded_lines(result); + pp->source = (char *)malloc(result.size() + 1); + pp->original_line_by_expanded_line = + (uint32_t *)calloc((size_t)line_count + 1u, sizeof(uint32_t)); + pp->belongs_to_main_file = (uint8_t *)calloc((size_t)line_count + 1u, sizeof(uint8_t)); + pp->expanded_line_count = line_count; + if (!pp->source || !pp->original_line_by_expanded_line || !pp->belongs_to_main_file) { + cbm_preprocessed_source_free(pp); + return NULL; + } + memcpy(pp->source, result.c_str(), result.size() + 1); + if (!build_line_map(result, files[0], pp->original_line_by_expanded_line, + pp->belongs_to_main_file)) { + cbm_preprocessed_source_free(pp); + return NULL; + } + return pp; } catch (...) { // Graceful fallback: return NULL = use original source return NULL; } } -void cbm_preprocess_free(char* expanded) { +char *cbm_preprocess(const char *source, int source_len, const char *filename, + const char **extra_defines, const char **include_paths, int cpp_mode) { + CBMPreprocessedSource *pp = cbm_preprocess_with_map(source, source_len, filename, extra_defines, + include_paths, cpp_mode); + if (!pp) { + return NULL; + } + char *out = pp->source; + pp->source = NULL; + cbm_preprocessed_source_free(pp); + return out; +} + +void cbm_preprocess_free(char *expanded) { free(expanded); } +void cbm_preprocessed_source_free(CBMPreprocessedSource *pp) { + if (!pp) { + return; + } + free(pp->source); + free(pp->original_line_by_expanded_line); + free(pp->belongs_to_main_file); + free(pp); +} + } // extern "C" diff --git a/internal/cbm/preprocessor.h b/internal/cbm/preprocessor.h index 06edbc087..c4e7f7465 100644 --- a/internal/cbm/preprocessor.h +++ b/internal/cbm/preprocessor.h @@ -2,11 +2,19 @@ #define CBM_PREPROCESSOR_H #include +#include #ifdef __cplusplus extern "C" { #endif +typedef struct { + char *source; + uint32_t *original_line_by_expanded_line; // 1-based; 0 means generated/unknown. + uint8_t *belongs_to_main_file; // 1-based; true only for the original input file. + int expanded_line_count; +} CBMPreprocessedSource; + // Preprocess C/C++ source: expand macros, evaluate #ifdef, resolve #include. // Returns malloc-allocated expanded source, or NULL if no expansion needed/on failure. // extra_defines: NULL-terminated array of "NAME=VALUE" strings (can be NULL). @@ -15,9 +23,19 @@ extern "C" { char *cbm_preprocess(const char *source, int source_len, const char *filename, const char **extra_defines, const char **include_paths, int cpp_mode); +// Preprocess and return source plus expanded-line -> original-line ownership map. +// Returns NULL if no expansion is needed or preprocessing fails. +// Free with cbm_preprocessed_source_free(). +CBMPreprocessedSource *cbm_preprocess_with_map(const char *source, int source_len, + const char *filename, const char **extra_defines, + const char **include_paths, int cpp_mode); + // Free preprocessed source returned by cbm_preprocess. void cbm_preprocess_free(char *expanded); +// Free preprocessed source and line maps returned by cbm_preprocess_with_map. +void cbm_preprocessed_source_free(CBMPreprocessedSource *pp); + #ifdef __cplusplus } #endif diff --git a/tests/test_extraction.c b/tests/test_extraction.c index 5e6bfd883..f9987da7b 100644 --- a/tests/test_extraction.c +++ b/tests/test_extraction.c @@ -8,6 +8,7 @@ #include "test_framework.h" #include "cbm.h" #include "../src/foundation/compat.h" /* cbm_clock_gettime (wide-flat scaling guard) */ +#include "../src/foundation/compat_fs.h" #include /* ── Helpers ───────────────────────────────────────────────────── */ @@ -77,6 +78,39 @@ static CBMFileResult *extract(const char *src, CBMLanguage lang, const char *pro return r; } +static CBMFileResult *extract_with_preproc_options(const char *src, CBMLanguage lang, + const char *proj, const char *path, + const char **extra_defines, + const char **include_paths) { + return cbm_extract_file(src, (int)strlen(src), lang, proj, path, 0, extra_defines, + include_paths); +} + +static int source_line_contains(const char *src, uint32_t line, const char *needle) { + if (!src || line == 0 || !needle) { + return 0; + } + const char *line_start = src; + for (uint32_t current = 1; current < line; current++) { + const char *nl = strchr(line_start, '\n'); + if (!nl) { + return 0; + } + line_start = nl + 1; + } + const char *line_end = strchr(line_start, '\n'); + if (!line_end) { + line_end = src + strlen(src); + } + size_t needle_len = strlen(needle); + for (const char *p = line_start; p + needle_len <= line_end; p++) { + if (strncmp(p, needle, needle_len) == 0) { + return 1; + } + } + return 0; +} + /* ═══════════════════════════════════════════════════════════════════ * Group A: OOP Languages * ═══════════════════════════════════════════════════════════════════ */ @@ -1248,56 +1282,212 @@ TEST(cpp_out_of_line_method_issue428) { PASS(); } +static const char *CPP_PREPROC_SIGNATURE_GAP_SRC = + "struct Rect {};\n" + "struct IBinder {};\n" + "struct IRegionSamplingListener {};\n" + "typedef int status_t;\n" + "\n" + "class SurfaceFlinger {\n" + "public:\n" + " status_t addRegionSamplingListener(const Rect&, const IBinder&,\n" + " const IRegionSamplingListener&, bool);\n" + " status_t addRegionSamplingListener(const Rect&, const IBinder&,\n" + " const IRegionSamplingListener&);\n" + " void commit();\n" + " void composite();\n" + "};\n" + "\n" + "#ifdef FLYME_GRAPHICS_EXTEND_LUMARGB\n" + "status_t SurfaceFlinger::addRegionSamplingListener(const Rect& samplingArea,\n" + " const IBinder& stopLayerHandle,\n" + " const IRegionSamplingListener& listener,\n" + " const bool rgbSample) {\n" + "#else\n" + "status_t SurfaceFlinger::addRegionSamplingListener(const Rect& samplingArea,\n" + " const IBinder& stopLayerHandle,\n" + " const IRegionSamplingListener& listener) " + "{\n" + "#endif\n" + " return 0;\n" + "}\n" + "\n" + "void SurfaceFlinger::commit() {}\n" + "\n" + "void SurfaceFlinger::composite() {}\n"; + /* #946: C++ code commonly gates alternate out-of-line method signatures with * #ifdef/#else/#endif. Raw tree-sitter can mis-balance the duplicate opening * braces and lose methods that follow the conditional block; extraction must - * still recover later methods from the translation unit. */ + * recover later methods and report ORIGINAL-source coordinates. */ TEST(cpp_preproc_signature_gap_issue946) { - CBMFileResult *r = - extract("struct Rect {};\n" - "struct IBinder {};\n" - "struct IRegionSamplingListener {};\n" - "typedef int status_t;\n" - "\n" - "class SurfaceFlinger {\n" - "public:\n" - " status_t addRegionSamplingListener(const Rect&, const IBinder&,\n" - " const IRegionSamplingListener&, bool);\n" - " status_t addRegionSamplingListener(const Rect&, const IBinder&,\n" - " const IRegionSamplingListener&);\n" - " void commit();\n" - " void composite();\n" - "};\n" - "\n" - "#ifdef FLYME_GRAPHICS_EXTEND_LUMARGB\n" - "status_t SurfaceFlinger::addRegionSamplingListener(const Rect& samplingArea,\n" - " const IBinder& stopLayerHandle,\n" - " const IRegionSamplingListener& listener,\n" - " const bool rgbSample) {\n" - "#else\n" - "status_t SurfaceFlinger::addRegionSamplingListener(const Rect& samplingArea,\n" - " const IBinder& stopLayerHandle,\n" - " const IRegionSamplingListener& listener) {\n" - "#endif\n" - " return 0;\n" - "}\n" - "\n" - "void SurfaceFlinger::commit() {}\n" - "\n" - "void SurfaceFlinger::composite() {}\n", - CBM_LANG_CPP, "t", "SurfaceFlinger.cpp"); - ASSERT_NOT_NULL(r); - ASSERT_FALSE(r->has_error); - ASSERT(has_def(r, "Method", "commit")); - ASSERT(has_def(r, "Method", "composite")); + const char *src = CPP_PREPROC_SIGNATURE_GAP_SRC; + CBMFileResult *r = extract(src, CBM_LANG_CPP, "t", "SurfaceFlinger.cpp"); + ASSERT_NOT_NULL(r); + ASSERT_FALSE(r->has_error); + const CBMDefinition *add = find_def_by_label_name(r, "Method", "addRegionSamplingListener"); + ASSERT_NOT_NULL(add); + ASSERT_EQ(add->start_line, 22u); + ASSERT_EQ(add->end_line, 27u); + ASSERT(source_line_contains(src, add->start_line, "addRegionSamplingListener")); + ASSERT(source_line_contains(src, add->end_line, "}")); + const CBMDefinition *commit = find_def_by_label_name(r, "Method", "commit"); + ASSERT_NOT_NULL(commit); + ASSERT_EQ(commit->start_line, 29u); + ASSERT_EQ(commit->end_line, 29u); + ASSERT(source_line_contains(src, commit->start_line, "commit()")); const CBMDefinition *composite = find_def_by_label_name(r, "Method", "composite"); ASSERT_NOT_NULL(composite); ASSERT_EQ(composite->start_line, 31u); ASSERT_EQ(composite->end_line, 31u); + ASSERT(source_line_contains(src, composite->start_line, "composite()")); + ASSERT_FALSE(r->parse_incomplete); + ASSERT_EQ(r->error_region_count, 0); + ASSERT_NULL(r->error_ranges); + cbm_free_result(r); + PASS(); +} + +TEST(cpp_preproc_signature_gap_first_branch_define) { + const char *src = CPP_PREPROC_SIGNATURE_GAP_SRC; + const char *defines[] = {"FLYME_GRAPHICS_EXTEND_LUMARGB", NULL}; + CBMFileResult *r = + extract_with_preproc_options(src, CBM_LANG_CPP, "t", "SurfaceFlinger.cpp", defines, NULL); + ASSERT_NOT_NULL(r); + ASSERT_FALSE(r->has_error); + const CBMDefinition *add = find_def_by_label_name(r, "Method", "addRegionSamplingListener"); + ASSERT_NOT_NULL(add); + ASSERT_EQ(add->start_line, 17u); + ASSERT_EQ(add->end_line, 27u); + ASSERT_NEQ(add->end_line, 20u); + ASSERT(source_line_contains(src, add->start_line, "addRegionSamplingListener")); + ASSERT(source_line_contains(src, add->end_line, "}")); + const CBMDefinition *commit = find_def_by_label_name(r, "Method", "commit"); + const CBMDefinition *composite = find_def_by_label_name(r, "Method", "composite"); + ASSERT_NOT_NULL(commit); + ASSERT_NOT_NULL(composite); + ASSERT_EQ(commit->start_line, 29u); + ASSERT_EQ(composite->start_line, 31u); + ASSERT_FALSE(r->parse_incomplete); + ASSERT_EQ(r->error_region_count, 0); + ASSERT_NULL(r->error_ranges); + cbm_free_result(r); + PASS(); +} + +TEST(cpp_preproc_remap_failure_skips_macro_generated_callable) { + const char *src = "#define MAKE_FN(name) int name() { return 1; }\n" + "#ifdef ENABLE_GENERATED\n" + "MAKE_FN(generated)\n" + "#endif\n" + "int visible() { return 0; }\n"; + const char *defines[] = {"ENABLE_GENERATED", NULL}; + CBMFileResult *r = + extract_with_preproc_options(src, CBM_LANG_CPP, "t", "macro.cpp", defines, NULL); + ASSERT_NOT_NULL(r); + ASSERT_FALSE(r->has_error); + ASSERT_FALSE(has_def(r, "Function", "generated")); + ASSERT(has_def(r, "Function", "visible")); + ASSERT_TRUE(r->parse_incomplete); + ASSERT_GTE(r->error_region_count, 1); + ASSERT_NOT_NULL(r->error_ranges); + cbm_free_result(r); + PASS(); +} + +TEST(cpp_preproc_include_header_defs_not_main_owned) { + char tmpdir[256] = "/tmp/cbm_header_XXXXXX"; + ASSERT_NOT_NULL(cbm_mkdtemp(tmpdir)); + + char header_path[512]; + snprintf(header_path, sizeof(header_path), "%s/helper.h", tmpdir); + FILE *header = cbm_fopen(header_path, "wb"); + ASSERT_NOT_NULL(header); + ASSERT_GTE(fputs("int header_owned() { return 1; }\n", header), 0); + ASSERT_EQ(fclose(header), 0); + + const char *includes[] = {tmpdir, NULL}; + const char *src = "#include \"helper.h\"\n" + "int main_owned() { return 0; }\n"; + CBMFileResult *r = + extract_with_preproc_options(src, CBM_LANG_CPP, "t", "main.cpp", NULL, includes); + cbm_unlink(header_path); + cbm_rmdir(tmpdir); + + ASSERT_NOT_NULL(r); + ASSERT_FALSE(r->has_error); + ASSERT(has_def(r, "Function", "main_owned")); + ASSERT_FALSE(has_def(r, "Function", "header_owned")); + cbm_free_result(r); + PASS(); +} + +TEST(cpp_preproc_remap_does_not_match_call_site) { + const char *src = "int main() {\n" + " if (foo()) {\n" + " return 1;\n" + " }\n" + " return 0;\n" + "}\n" + "\n" + "#ifdef ENABLE_GUARDED\n" + "int guarded(int x) {\n" + "#else\n" + "int guarded(int x) {\n" + "#endif\n" + " return x;\n" + "}\n" + "\n" + "int foo() {\n" + " return 1;\n" + "}\n"; + CBMFileResult *r = extract(src, CBM_LANG_CPP, "t", "callsite.cpp"); + ASSERT_NOT_NULL(r); + ASSERT_FALSE(r->has_error); + const CBMDefinition *foo = find_def_by_label_name(r, "Function", "foo"); + ASSERT_NOT_NULL(foo); + ASSERT_EQ(foo->start_line, 16u); + ASSERT_EQ(foo->end_line, 18u); + ASSERT(source_line_contains(src, 2u, "foo()")); + ASSERT(source_line_contains(src, foo->start_line, "int foo()")); + ASSERT_FALSE(r->parse_incomplete); + cbm_free_result(r); + PASS(); +} + +TEST(cpp_preproc_recovered_ranges_over_256) { + enum { N = 300 }; + size_t cap = (size_t)N * 64u + 512u; + char *src = (char *)malloc(cap); + ASSERT_NOT_NULL(src); + size_t off = 0; + off += (size_t)snprintf(src + off, cap - off, + "#ifdef ENABLE_BIG\n" + "int guarded(int x) {\n" + "#else\n" + "int guarded(int x) {\n" + "#endif\n" + " return x;\n" + "}\n"); + for (int i = 0; i < N; i++) { + off += (size_t)snprintf(src + off, cap - off, "int recovered_%03d(void) { return %d; }\n", + i, i); + } + + CBMFileResult *r = + cbm_extract_file(src, (int)off, CBM_LANG_C, "t", "many_recovered.c", 0, NULL, NULL); + ASSERT_NOT_NULL(r); + ASSERT_FALSE(r->has_error); ASSERT_FALSE(r->parse_incomplete); ASSERT_EQ(r->error_region_count, 0); ASSERT_NULL(r->error_ranges); + ASSERT(has_def(r, "Function", "recovered_000")); + ASSERT(has_def(r, "Function", "recovered_299")); + const CBMDefinition *last = find_def_by_label_name(r, "Function", "recovered_299"); + ASSERT_NOT_NULL(last); + ASSERT(source_line_contains(src, last->start_line, "recovered_299")); cbm_free_result(r); + free(src); PASS(); } @@ -3666,6 +3856,11 @@ SUITE(extraction) { RUN_TEST(cpp_function); RUN_TEST(cpp_out_of_line_method_issue428); RUN_TEST(cpp_preproc_signature_gap_issue946); + RUN_TEST(cpp_preproc_signature_gap_first_branch_define); + RUN_TEST(cpp_preproc_remap_failure_skips_macro_generated_callable); + RUN_TEST(cpp_preproc_include_header_defs_not_main_owned); + RUN_TEST(cpp_preproc_remap_does_not_match_call_site); + RUN_TEST(cpp_preproc_recovered_ranges_over_256); RUN_TEST(cobol_paragraph); RUN_TEST(verilog_module); RUN_TEST(cuda_kernel); From 5293205df1bda2469ff34c16430949d214371a0e Mon Sep 17 00:00:00 2001 From: Blank_Answer <97771966+blankanswer@users.noreply.github.com> Date: Fri, 10 Jul 2026 07:48:13 +0000 Subject: [PATCH 6/6] fix(cpp): keep preprocessor recovery fill-only Signed-off-by: Blank_Answer <97771966+blankanswer@users.noreply.github.com> --- internal/cbm/cbm.c | 78 ++--------------------------------- internal/cbm/preprocessor.cpp | 3 -- tests/test_extraction.c | 12 ++++-- 3 files changed, 12 insertions(+), 81 deletions(-) diff --git a/internal/cbm/cbm.c b/internal/cbm/cbm.c index 92d7f35be..dadcee31e 100644 --- a/internal/cbm/cbm.c +++ b/internal/cbm/cbm.c @@ -429,75 +429,6 @@ static int result_find_same_def_identity(const CBMFileResult *result, const CBMD return -1; } -static bool cbm_pp_directive_matches(const char *p, int len, const char *word) { - int word_len = (int)strlen(word); - return len >= word_len && strncmp(p, word, (size_t)word_len) == 0 && - (len == word_len || !cbm_ident_char(p[word_len])); -} - -static bool cbm_line_is_preprocessor_branch_directive(const char *line, int len) { - int p = 0; - while (p < len && isspace((unsigned char)line[p])) { - p++; - } - if (p >= len || line[p++] != '#') { - return false; - } - while (p < len && isspace((unsigned char)line[p])) { - p++; - } - int word_len = len - p; - return cbm_pp_directive_matches(line + p, word_len, "if") || - cbm_pp_directive_matches(line + p, word_len, "ifdef") || - cbm_pp_directive_matches(line + p, word_len, "ifndef") || - cbm_pp_directive_matches(line + p, word_len, "elif") || - cbm_pp_directive_matches(line + p, word_len, "else") || - cbm_pp_directive_matches(line + p, word_len, "endif"); -} - -static bool cbm_span_contains_preprocessor_branch(const char *source, int source_len, - uint32_t start_line, uint32_t end_line) { - int span_start = 0; - int span_end = 0; - if (!cbm_source_line_bounds(source, source_len, start_line, end_line, &span_start, &span_end)) { - return false; - } - int line_start = span_start; - while (line_start <= span_end) { - int line_end = line_start; - while (line_end < span_end && source[line_end] != '\n') { - line_end++; - } - if (cbm_line_is_preprocessor_branch_directive(source + line_start, line_end - line_start)) { - return true; - } - if (line_end >= span_end) { - break; - } - line_start = line_end + 1; - } - return false; -} - -static bool cbm_should_replace_preprocessed_duplicate(const CBMDefinition *existing, - const CBMDefinition *remapped, - const char *source, int source_len) { - if (!existing || !remapped || existing->start_line == 0 || remapped->start_line == 0) { - return false; - } - if (existing->start_line == remapped->start_line && existing->end_line == remapped->end_line) { - return false; - } - uint32_t start = - existing->start_line < remapped->start_line ? existing->start_line : remapped->start_line; - uint32_t existing_end = - existing->end_line < existing->start_line ? existing->start_line : existing->end_line; - uint32_t remapped_end = - remapped->end_line < remapped->start_line ? remapped->start_line : remapped->end_line; - uint32_t end = existing_end > remapped_end ? existing_end : remapped_end; - return cbm_span_contains_preprocessor_branch(source, source_len, start, end); -} - static void merge_missing_preprocessed_callables(CBMFileResult *dst, const CBMFileResult *src, CBMArena *arena, const CBMPreprocessedSource *preprocessed, @@ -520,16 +451,13 @@ static void merge_missing_preprocessed_callables(CBMFileResult *dst, const CBMFi original_source_len)) { continue; } + /* Successful expanded-source remaps are parse-coverage evidence. Raw + * definitions remain primary; the final defs array is fill-only below. */ if (!cbm_recovered_callables_push(arena, recovered, &remapped)) { continue; } - int existing_idx = result_find_same_def_identity(dst, &remapped); - if (existing_idx < 0) { + if (result_find_same_def_identity(dst, &remapped) < 0) { cbm_defs_push(&dst->defs, arena, remapped); - } else if (cbm_should_replace_preprocessed_duplicate(&dst->defs.items[existing_idx], - &remapped, original_source, - original_source_len)) { - dst->defs.items[existing_idx] = remapped; } } } diff --git a/internal/cbm/preprocessor.cpp b/internal/cbm/preprocessor.cpp index e3b676340..54cb073fe 100644 --- a/internal/cbm/preprocessor.cpp +++ b/internal/cbm/preprocessor.cpp @@ -34,9 +34,6 @@ static bool has_preprocessor_work(const char *source, int source_len) { if (remaining >= 6 && strncmp(source + j, "ifndef", 6) == 0) { return true; } - if (remaining >= 7 && strncmp(source + j, "include", 7) == 0) { - return true; - } if (remaining >= 3 && strncmp(source + j, "if ", 3) == 0) { return true; } diff --git a/tests/test_extraction.c b/tests/test_extraction.c index f9987da7b..950ba6d9b 100644 --- a/tests/test_extraction.c +++ b/tests/test_extraction.c @@ -1357,9 +1357,11 @@ TEST(cpp_preproc_signature_gap_first_branch_define) { ASSERT_FALSE(r->has_error); const CBMDefinition *add = find_def_by_label_name(r, "Method", "addRegionSamplingListener"); ASSERT_NOT_NULL(add); - ASSERT_EQ(add->start_line, 17u); + /* Raw AST remains primary: the expanded active branch is coverage evidence, + * not a replacement for an existing raw definition. */ + ASSERT_EQ(add->start_line, 22u); ASSERT_EQ(add->end_line, 27u); - ASSERT_NEQ(add->end_line, 20u); + ASSERT_NEQ(add->start_line, 17u); ASSERT(source_line_contains(src, add->start_line, "addRegionSamplingListener")); ASSERT(source_line_contains(src, add->end_line, "}")); const CBMDefinition *commit = find_def_by_label_name(r, "Method", "commit"); @@ -1396,7 +1398,8 @@ TEST(cpp_preproc_remap_failure_skips_macro_generated_callable) { } TEST(cpp_preproc_include_header_defs_not_main_owned) { - char tmpdir[256] = "/tmp/cbm_header_XXXXXX"; + char tmpdir[512]; + snprintf(tmpdir, sizeof(tmpdir), "%s/cbm_header_XXXXXX", cbm_tmpdir()); ASSERT_NOT_NULL(cbm_mkdtemp(tmpdir)); char header_path[512]; @@ -1408,6 +1411,9 @@ TEST(cpp_preproc_include_header_defs_not_main_owned) { const char *includes[] = {tmpdir, NULL}; const char *src = "#include \"helper.h\"\n" + "#ifdef ENABLE_SECOND_PASS\n" + "int branch_value = 1;\n" + "#endif\n" "int main_owned() { return 0; }\n"; CBMFileResult *r = extract_with_preproc_options(src, CBM_LANG_CPP, "t", "main.cpp", NULL, includes);