mirror of
https://github.com/DeusData/codebase-memory-mcp.git
synced 2026-11-04 10:48:20 +00:00
fix(cpp): one spelling per template specialization in the preprocessed pass
The preprocessed second pass rebuilt the expanded source with simplecpp's TokenList::stringify(), which puts one space between every pair of tokens: `template<> struct bitonic<int64_t, AVX2>` became `bitonic < int64_t , AVX2 >`. Every QN builder takes the class segment from node text, so a definition the #961 rescue re-adopted from that buffer (vxsort's INLINE/NOINLINE macros break the raw parse on every method line) got a second, token-spaced QN, and the QN-equality dedup kept both. CALLS split across the two spellings (dotnet/runtime: 902 compact + 996 spaced into the bitonic sorters). The expanded buffer now keeps the source spacing: tokens that touched in the source (same file and line, no macro expansion between them) stay touching, every other gap is still one space, and the line structure and #line directives are unchanged, so every line remap holds. One fix point instead of a normaliser at each QN builder; no vendored code changes. Proof (macOS arm64, dotnet/runtime 60108ba6, two identical runs each): - duplicate definition groups 706 -> 217 (v0.11.0: 545); nodes with a token-spaced QN 495 -> 0; CALLS into bitonic 902 + 996 -> 998, one spelling. The remaining groups are coreclr gc.cpp's SVR/WKS builds (184) and 33 scope disagreements between the two parses. - Side effect: rapidjson document.h's GenericValue members now exist once, with the raw parse's scope (the token-spaced copy that carried the nested scope is gone); `operator[]` / `operator()` in the expanded pass now parse as single tokens. - New test (RED without the change): extraction extract_cpp_template_specialization_preprocessed_qn_single_spelling. Three c_lsp collision fixtures recalibrated to the new byte offsets (comment padding and offsets only, assertions unchanged). - extraction, c_lsp, pipeline, edge_types_probe, parse_coverage, conditional_variants, lsp_resolution_probe: 1852 passed. Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
This commit is contained in:
1 parent
bb636d1ff7
commit
b87d067637
3 files changed
+132
-13
No files matched your search
@@ -381,6 +381,56 @@ static bool parse_line_directive(const char *line, size_t len, uint32_t *out_lin
|
||||
return true;
|
||||
}
|
||||
|
||||
// True when `prev` and `tok` touched in the source text: same file and line,
|
||||
// `prev` ending exactly where `tok` starts (simplecpp's own adjacency test),
|
||||
// and neither produced by a macro expansion (expansion tokens carry the call
|
||||
// site's location, so their columns prove nothing).
|
||||
static bool tokens_source_adjacent(const simplecpp::Token *prev, const simplecpp::Token *tok) {
|
||||
if (!prev->macro.empty() || !tok->macro.empty() || !prev->location.sameline(tok->location)) {
|
||||
return false;
|
||||
}
|
||||
const std::string &s = prev->str();
|
||||
if (s.find_first_of("\r\n") != std::string::npos) {
|
||||
return false;
|
||||
}
|
||||
return prev->location.col + s.size() == tok->location.col;
|
||||
}
|
||||
|
||||
// The expanded buffer in the source's own spelling. Mirrors
|
||||
// simplecpp::TokenList::stringify() line for line — the same newlines and
|
||||
// #line directives, so build_line_map and every line-based remap are unchanged —
|
||||
// except that tokens which touched in the source stay touching. stringify()
|
||||
// joins every same-line pair with one space, so the second pass spelled a
|
||||
// multi-token name differently from the raw pass: the explicit specialization
|
||||
// `template<> struct bitonic<int64_t, AVX2>` became `bitonic < int64_t , AVX2 >`,
|
||||
// and every QN built from that text (defs adopted by the #961 rescue, call
|
||||
// scopes, C-LSP class scopes) minted a second node for the same definition, the
|
||||
// QN dedup (strcmp) missing it. A gap that held whitespace or a comment is still
|
||||
// one space, as before.
|
||||
static std::string stringify_source_spacing(const simplecpp::TokenList &tokens) {
|
||||
std::ostringstream ret;
|
||||
simplecpp::Location loc;
|
||||
loc.line = 1;
|
||||
for (const simplecpp::Token *tok = tokens.cfront(); tok; tok = tok->next) {
|
||||
if (tok->location.line < loc.line || tok->location.fileIndex != loc.fileIndex) {
|
||||
ret << "\n#line " << tok->location.line << " \"" << tokens.file(tok->location)
|
||||
<< "\"\n";
|
||||
loc = tok->location;
|
||||
}
|
||||
while (tok->location.line > loc.line) {
|
||||
ret << '\n';
|
||||
loc.line++;
|
||||
}
|
||||
const simplecpp::Token *prev = tok->previous;
|
||||
if (prev && prev->location.sameline(tok->location) && !tokens_source_adjacent(prev, tok)) {
|
||||
ret << ' ';
|
||||
}
|
||||
ret << tok->str();
|
||||
loc.adjust(tok->str());
|
||||
}
|
||||
return ret.str();
|
||||
}
|
||||
|
||||
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) {
|
||||
@@ -476,7 +526,7 @@ CBMPreprocessedSource *cbm_preprocess_with_map(const char *source, int source_le
|
||||
|
||||
simplecpp::preprocess(output, rawtokens, files, filedata, dui);
|
||||
|
||||
std::string result = output.stringify();
|
||||
std::string result = stringify_source_spacing(output);
|
||||
|
||||
// Clean up loaded file data
|
||||
simplecpp::cleanup(filedata);
|
||||
|
||||
+15
-12
@@ -16278,21 +16278,23 @@ TEST(clsp_c_global_initializer_does_not_inherit_previous_function_caller) {
|
||||
}
|
||||
|
||||
static int assert_cpp_family_preprocessed_collision_isolated(CBMLanguage language) {
|
||||
/* simplecpp removes the 41-byte comment payload and normalizes tokens. As
|
||||
* verified by the non-vacuity assertions below, raw alpha.render and the
|
||||
* macro-hidden expanded bravo.render both land at numeric span 182:200,
|
||||
* even though those offsets refer to different source texts. */
|
||||
/* The preprocessed buffer drops the 21-byte comment payload and the line
|
||||
* indentation, keeps source spacing between source tokens and one space
|
||||
* between macro-expansion tokens. As verified by the non-vacuity assertions
|
||||
* below, raw alpha.render and the macro-hidden expanded bravo.render both
|
||||
* land at numeric span 162:180, even though those offsets refer to
|
||||
* different source texts. */
|
||||
static const char source[] =
|
||||
"struct Alpha { int render(){return 1;} };\n"
|
||||
"struct Bravo { int render(){return 2;} };\n"
|
||||
"int occurrence_probe(){\n"
|
||||
" Alpha alpha; Bravo bravo;\n"
|
||||
" /*xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx*/ alpha . render ( );\n"
|
||||
" /*xxxxxxxxxxxxxxxxxxxxx*/ alpha . render ( );\n"
|
||||
"#define HIDDEN_RENDER(x) x.render()\n"
|
||||
" return HIDDEN_RENDER(bravo);\n"
|
||||
"}\n";
|
||||
const uint32_t collision_start = 182;
|
||||
const uint32_t collision_end = 200;
|
||||
const uint32_t collision_start = 162;
|
||||
const uint32_t collision_end = 180;
|
||||
ASSERT_EQ((uint32_t)(strstr(source, "alpha . render ( )") - source), collision_start);
|
||||
|
||||
CBMFileResult *result = extract_c_family(source, language);
|
||||
@@ -16354,19 +16356,20 @@ TEST(clsp_cuda_preprocessed_coordinate_collision_isolated) {
|
||||
}
|
||||
|
||||
TEST(clsp_c_preprocessed_coordinate_collision_isolated) {
|
||||
/* The raw fp call and macro-hidden expanded fp call both occupy 206:212,
|
||||
* but data flow resolves them to different targets. */
|
||||
/* The raw fp call and macro-hidden expanded fp call both occupy 180:186
|
||||
* (comment payload sized against the preprocessed buffer's spacing), but
|
||||
* data flow resolves them to different targets. */
|
||||
static const char source[] =
|
||||
"static int alpha_target(void){return 1;}\n"
|
||||
"static int bravo_target(void){return 2;}\n"
|
||||
"int occurrence_probe(void){\n"
|
||||
" int (*fp)(void) = alpha_target;\n"
|
||||
" /*xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx*/ fp ( );\n"
|
||||
" /*xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx*/ fp ( );\n"
|
||||
"#define HIDDEN_FP_CALL() (fp = bravo_target, fp())\n"
|
||||
" return HIDDEN_FP_CALL();\n"
|
||||
"}\n";
|
||||
const uint32_t collision_start = 206;
|
||||
const uint32_t collision_end = 212;
|
||||
const uint32_t collision_start = 180;
|
||||
const uint32_t collision_end = 186;
|
||||
const char *raw_probe = strstr(source, "occurrence_probe");
|
||||
ASSERT_NOT_NULL(raw_probe);
|
||||
ASSERT_EQ((uint32_t)(strstr(raw_probe, "fp ( )") - source), collision_start);
|
||||
|
||||
@@ -7424,6 +7424,71 @@ TEST(extract_c_clean_file_no_recovery_duplicates_issue961) {
|
||||
PASS();
|
||||
}
|
||||
|
||||
/* True when `qn` carries the token-joined spelling of the `box<int, 2>`
|
||||
* template-id that the preprocessed buffer used to produce. */
|
||||
static bool qn_has_token_spaced_box(const char *qn) {
|
||||
return qn && (strstr(qn, "box <") || strstr(qn, "< int") || strstr(qn, "int ,"));
|
||||
}
|
||||
|
||||
/* dotnet/runtime vxsort shape (src/coreclr/gc/vxsort/smallsort/
|
||||
* bitonic_sort.*.generated.h): methods of an explicit specialization
|
||||
* `template<> struct bitonic<int64_t, AVX2>` carry an attribute macro defined in
|
||||
* another header (INLINE / NOINLINE), so the raw parse has ERROR regions on the
|
||||
* method lines and the #961 rescue re-extracts them from the preprocessed
|
||||
* buffer. That buffer used to join every token with one space, spelling the
|
||||
* scope `box < int , 2 >` where the raw pass spells `box<int, 2>`; the QN dedup
|
||||
* (strcmp) missed, so every method got a second node and the CALLS split across
|
||||
* both. One definition, one spelling, on every path. */
|
||||
TEST(extract_cpp_template_specialization_preprocessed_qn_single_spelling) {
|
||||
CBMFileResult *r = extract("#ifndef BOX_H\n"
|
||||
"#define BOX_H\n"
|
||||
"namespace ns {\n"
|
||||
"template <typename T, int M> struct box {\n"
|
||||
" static void sort(T *p);\n"
|
||||
"};\n"
|
||||
"template<> struct box<int, 2> {\n"
|
||||
" static INLINE void first(int *p) { p[0] = 0; }\n"
|
||||
" static INLINE void second(int *p) { first(p); }\n"
|
||||
"};\n"
|
||||
"}\n"
|
||||
"#endif\n",
|
||||
CBM_LANG_CPP, "t", "box.h");
|
||||
ASSERT_NOT_NULL(r);
|
||||
int first_count = 0;
|
||||
int second_count = 0;
|
||||
const char *first_qn = NULL;
|
||||
for (int i = 0; i < r->defs.count; i++) {
|
||||
const CBMDefinition *d = &r->defs.items[i];
|
||||
if (qn_has_token_spaced_box(d->qualified_name)) {
|
||||
fprintf(stderr, " [tmplqn] FAIL token-spaced def QN: %s\n", d->qualified_name);
|
||||
}
|
||||
ASSERT_FALSE(qn_has_token_spaced_box(d->qualified_name));
|
||||
if (d->name && strcmp(d->name, "first") == 0) {
|
||||
first_count++;
|
||||
first_qn = d->qualified_name;
|
||||
}
|
||||
if (d->name && strcmp(d->name, "second") == 0) {
|
||||
second_count++;
|
||||
}
|
||||
}
|
||||
ASSERT_EQ(first_count, 1);
|
||||
ASSERT_EQ(second_count, 1);
|
||||
ASSERT_NOT_NULL(first_qn);
|
||||
ASSERT_NOT_NULL(strstr(first_qn, "ns.box<int, 2>.first"));
|
||||
/* Call scopes and LSP-resolved endpoints name the same single spelling, so
|
||||
* no CALLS edge lands on (or starts from) a phantom second node. */
|
||||
for (int i = 0; i < r->calls.count; i++) {
|
||||
ASSERT_FALSE(qn_has_token_spaced_box(r->calls.items[i].enclosing_func_qn));
|
||||
}
|
||||
for (int i = 0; i < r->resolved_calls.count; i++) {
|
||||
const CBMResolvedCall *rc = &r->resolved_calls.items[i];
|
||||
ASSERT_FALSE(qn_has_token_spaced_box(rc->caller_qn));
|
||||
ASSERT_FALSE(qn_has_token_spaced_box(rc->callee_qn));
|
||||
}
|
||||
cbm_free_result(r);
|
||||
PASS();
|
||||
}
|
||||
|
||||
/* #1989: build-system export macros (UE "<MOD>_API", CMake generate_export_header
|
||||
* "<lib>_EXPORT") are empty on the real compile line but opaque to tree-sitter,
|
||||
* so `class MOD_API Foo` misparses. The preprocessed second pass predefines the
|
||||
@@ -12359,6 +12424,7 @@ SUITE(extraction) {
|
||||
RUN_TEST(extract_cpp_preproc_macro_generated_callable_skipped_issue949);
|
||||
RUN_TEST(extract_c_ifdef_split_brace_after_include_remapped_issue949);
|
||||
RUN_TEST(extract_c_clean_file_no_recovery_duplicates_issue961);
|
||||
RUN_TEST(extract_cpp_template_specialization_preprocessed_qn_single_spelling);
|
||||
RUN_TEST(extract_cpp_export_macro_class_recovery_issue1989);
|
||||
RUN_TEST(extract_cpp_export_macro_struct_recovery_issue1989);
|
||||
RUN_TEST(extract_cpp_export_macro_enum_recovery_issue1989);
|
||||
|
||||
Reference in new issue
Block a user