Skip to content

Commit 4ddbbf7

Browse files
authored
Break-glass merge PR #2626
2 parents 5f701fb + b87d067 commit 4ddbbf7

3 files changed

Lines changed: 132 additions & 13 deletions

File tree

‎internal/cbm/preprocessor.cpp‎

Lines changed: 51 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -381,6 +381,56 @@ static bool parse_line_directive(const char *line, size_t len, uint32_t *out_lin
381381
return true;
382382
}
383383

384+
// True when `prev` and `tok` touched in the source text: same file and line,
385+
// `prev` ending exactly where `tok` starts (simplecpp's own adjacency test),
386+
// and neither produced by a macro expansion (expansion tokens carry the call
387+
// site's location, so their columns prove nothing).
388+
static bool tokens_source_adjacent(const simplecpp::Token *prev, const simplecpp::Token *tok) {
389+
if (!prev->macro.empty() || !tok->macro.empty() || !prev->location.sameline(tok->location)) {
390+
return false;
391+
}
392+
const std::string &s = prev->str();
393+
if (s.find_first_of("\r\n") != std::string::npos) {
394+
return false;
395+
}
396+
return prev->location.col + s.size() == tok->location.col;
397+
}
398+
399+
// The expanded buffer in the source's own spelling. Mirrors
400+
// simplecpp::TokenList::stringify() line for line — the same newlines and
401+
// #line directives, so build_line_map and every line-based remap are unchanged —
402+
// except that tokens which touched in the source stay touching. stringify()
403+
// joins every same-line pair with one space, so the second pass spelled a
404+
// multi-token name differently from the raw pass: the explicit specialization
405+
// `template<> struct bitonic<int64_t, AVX2>` became `bitonic < int64_t , AVX2 >`,
406+
// and every QN built from that text (defs adopted by the #961 rescue, call
407+
// scopes, C-LSP class scopes) minted a second node for the same definition, the
408+
// QN dedup (strcmp) missing it. A gap that held whitespace or a comment is still
409+
// one space, as before.
410+
static std::string stringify_source_spacing(const simplecpp::TokenList &tokens) {
411+
std::ostringstream ret;
412+
simplecpp::Location loc;
413+
loc.line = 1;
414+
for (const simplecpp::Token *tok = tokens.cfront(); tok; tok = tok->next) {
415+
if (tok->location.line < loc.line || tok->location.fileIndex != loc.fileIndex) {
416+
ret << "\n#line " << tok->location.line << " \"" << tokens.file(tok->location)
417+
<< "\"\n";
418+
loc = tok->location;
419+
}
420+
while (tok->location.line > loc.line) {
421+
ret << '\n';
422+
loc.line++;
423+
}
424+
const simplecpp::Token *prev = tok->previous;
425+
if (prev && prev->location.sameline(tok->location) && !tokens_source_adjacent(prev, tok)) {
426+
ret << ' ';
427+
}
428+
ret << tok->str();
429+
loc.adjust(tok->str());
430+
}
431+
return ret.str();
432+
}
433+
384434
static bool build_line_map(const std::string &expanded, const std::string &main_file,
385435
uint32_t *original_line_by_expanded_line,
386436
uint8_t *belongs_to_main_file) {
@@ -476,7 +526,7 @@ CBMPreprocessedSource *cbm_preprocess_with_map(const char *source, int source_le
476526

477527
simplecpp::preprocess(output, rawtokens, files, filedata, dui);
478528

479-
std::string result = output.stringify();
529+
std::string result = stringify_source_spacing(output);
480530

481531
// Clean up loaded file data
482532
simplecpp::cleanup(filedata);

‎tests/test_c_lsp.c‎

Lines changed: 15 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -16278,21 +16278,23 @@ TEST(clsp_c_global_initializer_does_not_inherit_previous_function_caller) {
1627816278
}
1627916279

1628016280
static int assert_cpp_family_preprocessed_collision_isolated(CBMLanguage language) {
16281-
/* simplecpp removes the 41-byte comment payload and normalizes tokens. As
16282-
* verified by the non-vacuity assertions below, raw alpha.render and the
16283-
* macro-hidden expanded bravo.render both land at numeric span 182:200,
16284-
* even though those offsets refer to different source texts. */
16281+
/* The preprocessed buffer drops the 21-byte comment payload and the line
16282+
* indentation, keeps source spacing between source tokens and one space
16283+
* between macro-expansion tokens. As verified by the non-vacuity assertions
16284+
* below, raw alpha.render and the macro-hidden expanded bravo.render both
16285+
* land at numeric span 162:180, even though those offsets refer to
16286+
* different source texts. */
1628516287
static const char source[] =
1628616288
"struct Alpha { int render(){return 1;} };\n"
1628716289
"struct Bravo { int render(){return 2;} };\n"
1628816290
"int occurrence_probe(){\n"
1628916291
" Alpha alpha; Bravo bravo;\n"
16290-
" /*xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx*/ alpha . render ( );\n"
16292+
" /*xxxxxxxxxxxxxxxxxxxxx*/ alpha . render ( );\n"
1629116293
"#define HIDDEN_RENDER(x) x.render()\n"
1629216294
" return HIDDEN_RENDER(bravo);\n"
1629316295
"}\n";
16294-
const uint32_t collision_start = 182;
16295-
const uint32_t collision_end = 200;
16296+
const uint32_t collision_start = 162;
16297+
const uint32_t collision_end = 180;
1629616298
ASSERT_EQ((uint32_t)(strstr(source, "alpha . render ( )") - source), collision_start);
1629716299

1629816300
CBMFileResult *result = extract_c_family(source, language);
@@ -16354,19 +16356,20 @@ TEST(clsp_cuda_preprocessed_coordinate_collision_isolated) {
1635416356
}
1635516357

1635616358
TEST(clsp_c_preprocessed_coordinate_collision_isolated) {
16357-
/* The raw fp call and macro-hidden expanded fp call both occupy 206:212,
16358-
* but data flow resolves them to different targets. */
16359+
/* The raw fp call and macro-hidden expanded fp call both occupy 180:186
16360+
* (comment payload sized against the preprocessed buffer's spacing), but
16361+
* data flow resolves them to different targets. */
1635916362
static const char source[] =
1636016363
"static int alpha_target(void){return 1;}\n"
1636116364
"static int bravo_target(void){return 2;}\n"
1636216365
"int occurrence_probe(void){\n"
1636316366
" int (*fp)(void) = alpha_target;\n"
16364-
" /*xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx*/ fp ( );\n"
16367+
" /*xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx*/ fp ( );\n"
1636516368
"#define HIDDEN_FP_CALL() (fp = bravo_target, fp())\n"
1636616369
" return HIDDEN_FP_CALL();\n"
1636716370
"}\n";
16368-
const uint32_t collision_start = 206;
16369-
const uint32_t collision_end = 212;
16371+
const uint32_t collision_start = 180;
16372+
const uint32_t collision_end = 186;
1637016373
const char *raw_probe = strstr(source, "occurrence_probe");
1637116374
ASSERT_NOT_NULL(raw_probe);
1637216375
ASSERT_EQ((uint32_t)(strstr(raw_probe, "fp ( )") - source), collision_start);

‎tests/test_extraction.c‎

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7424,6 +7424,71 @@ TEST(extract_c_clean_file_no_recovery_duplicates_issue961) {
74247424
PASS();
74257425
}
74267426

7427+
/* True when `qn` carries the token-joined spelling of the `box<int, 2>`
7428+
* template-id that the preprocessed buffer used to produce. */
7429+
static bool qn_has_token_spaced_box(const char *qn) {
7430+
return qn && (strstr(qn, "box <") || strstr(qn, "< int") || strstr(qn, "int ,"));
7431+
}
7432+
7433+
/* dotnet/runtime vxsort shape (src/coreclr/gc/vxsort/smallsort/
7434+
* bitonic_sort.*.generated.h): methods of an explicit specialization
7435+
* `template<> struct bitonic<int64_t, AVX2>` carry an attribute macro defined in
7436+
* another header (INLINE / NOINLINE), so the raw parse has ERROR regions on the
7437+
* method lines and the #961 rescue re-extracts them from the preprocessed
7438+
* buffer. That buffer used to join every token with one space, spelling the
7439+
* scope `box < int , 2 >` where the raw pass spells `box<int, 2>`; the QN dedup
7440+
* (strcmp) missed, so every method got a second node and the CALLS split across
7441+
* both. One definition, one spelling, on every path. */
7442+
TEST(extract_cpp_template_specialization_preprocessed_qn_single_spelling) {
7443+
CBMFileResult *r = extract("#ifndef BOX_H\n"
7444+
"#define BOX_H\n"
7445+
"namespace ns {\n"
7446+
"template <typename T, int M> struct box {\n"
7447+
" static void sort(T *p);\n"
7448+
"};\n"
7449+
"template<> struct box<int, 2> {\n"
7450+
" static INLINE void first(int *p) { p[0] = 0; }\n"
7451+
" static INLINE void second(int *p) { first(p); }\n"
7452+
"};\n"
7453+
"}\n"
7454+
"#endif\n",
7455+
CBM_LANG_CPP, "t", "box.h");
7456+
ASSERT_NOT_NULL(r);
7457+
int first_count = 0;
7458+
int second_count = 0;
7459+
const char *first_qn = NULL;
7460+
for (int i = 0; i < r->defs.count; i++) {
7461+
const CBMDefinition *d = &r->defs.items[i];
7462+
if (qn_has_token_spaced_box(d->qualified_name)) {
7463+
fprintf(stderr, " [tmplqn] FAIL token-spaced def QN: %s\n", d->qualified_name);
7464+
}
7465+
ASSERT_FALSE(qn_has_token_spaced_box(d->qualified_name));
7466+
if (d->name && strcmp(d->name, "first") == 0) {
7467+
first_count++;
7468+
first_qn = d->qualified_name;
7469+
}
7470+
if (d->name && strcmp(d->name, "second") == 0) {
7471+
second_count++;
7472+
}
7473+
}
7474+
ASSERT_EQ(first_count, 1);
7475+
ASSERT_EQ(second_count, 1);
7476+
ASSERT_NOT_NULL(first_qn);
7477+
ASSERT_NOT_NULL(strstr(first_qn, "ns.box<int, 2>.first"));
7478+
/* Call scopes and LSP-resolved endpoints name the same single spelling, so
7479+
* no CALLS edge lands on (or starts from) a phantom second node. */
7480+
for (int i = 0; i < r->calls.count; i++) {
7481+
ASSERT_FALSE(qn_has_token_spaced_box(r->calls.items[i].enclosing_func_qn));
7482+
}
7483+
for (int i = 0; i < r->resolved_calls.count; i++) {
7484+
const CBMResolvedCall *rc = &r->resolved_calls.items[i];
7485+
ASSERT_FALSE(qn_has_token_spaced_box(rc->caller_qn));
7486+
ASSERT_FALSE(qn_has_token_spaced_box(rc->callee_qn));
7487+
}
7488+
cbm_free_result(r);
7489+
PASS();
7490+
}
7491+
74277492
/* #1989: build-system export macros (UE "<MOD>_API", CMake generate_export_header
74287493
* "<lib>_EXPORT") are empty on the real compile line but opaque to tree-sitter,
74297494
* so `class MOD_API Foo` misparses. The preprocessed second pass predefines the
@@ -12359,6 +12424,7 @@ SUITE(extraction) {
1235912424
RUN_TEST(extract_cpp_preproc_macro_generated_callable_skipped_issue949);
1236012425
RUN_TEST(extract_c_ifdef_split_brace_after_include_remapped_issue949);
1236112426
RUN_TEST(extract_c_clean_file_no_recovery_duplicates_issue961);
12427+
RUN_TEST(extract_cpp_template_specialization_preprocessed_qn_single_spelling);
1236212428
RUN_TEST(extract_cpp_export_macro_class_recovery_issue1989);
1236312429
RUN_TEST(extract_cpp_export_macro_struct_recovery_issue1989);
1236412430
RUN_TEST(extract_cpp_export_macro_enum_recovery_issue1989);

0 commit comments

Comments
 (0)