Skip to content

Commit c7488b0

Browse files
authored
Merge pull request #2005 from XIYBHK/fix/1989-export-macro-parsing
fix(parsing): recover export-macro-hidden type definitions (#1989)
2 parents 9b10337 + 7d01d3e commit c7488b0

4 files changed

Lines changed: 945 additions & 10 deletions

File tree

‎internal/cbm/cbm.c‎

Lines changed: 242 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1514,6 +1514,63 @@ static bool cbm_span_contains_callable_def(const char *src, int src_len, uint32_
15141514
return false;
15151515
}
15161516

1517+
/* #1989: type-def counterpart of cbm_span_contains_callable_def. A rescued
1518+
* class/struct/enum definition has its name followed by a base clause (':') or
1519+
* a body ('{'), not a parameter list, so the callable validator would reject
1520+
* every type def pulled from the expanded tree. */
1521+
static bool cbm_span_contains_type_def(const char *src, int src_len, uint32_t start_line,
1522+
uint32_t end_line, const char *name) {
1523+
if (!src || src_len <= 0 || !name || !name[0] || start_line == 0 || end_line < start_line) {
1524+
return false;
1525+
}
1526+
int span_start = 0;
1527+
uint32_t line = 1;
1528+
while (span_start < src_len && line < start_line) {
1529+
if (src[span_start++] == '\n') {
1530+
line++;
1531+
}
1532+
}
1533+
if (line != start_line) {
1534+
return false;
1535+
}
1536+
int span_end = span_start;
1537+
while (span_end < src_len && line <= end_line) {
1538+
if (src[span_end++] == '\n') {
1539+
line++;
1540+
}
1541+
}
1542+
size_t name_len = strlen(name);
1543+
for (int pos = span_start; pos + (int)name_len <= span_end; pos++) {
1544+
if (strncmp(src + pos, name, name_len) != 0 ||
1545+
(pos > 0 && cbm_identifier_char(src[pos - 1])) ||
1546+
(pos + (int)name_len < src_len && cbm_identifier_char(src[pos + name_len]))) {
1547+
continue;
1548+
}
1549+
int open = pos + (int)name_len;
1550+
while (open < span_end && isspace((unsigned char)src[open])) {
1551+
open++;
1552+
}
1553+
// Base clause (": public B"), body ("{"), or template args ("<...").
1554+
if (open < span_end && (src[open] == ':' || src[open] == '{' || src[open] == '<')) {
1555+
return true;
1556+
}
1557+
}
1558+
return false;
1559+
}
1560+
1561+
/* #1989: label buckets for the misparse-correction gates. A raw def extracted
1562+
* from a broken `class MOD_API Foo` shape lands under a callable label while
1563+
* the expanded tree yields a proper type label for the same name. */
1564+
static bool cbm_def_label_is_type(const char *label) {
1565+
return label != NULL && (strcmp(label, "Class") == 0 || strcmp(label, "Enum") == 0 ||
1566+
strcmp(label, "Interface") == 0 || strcmp(label, "Type") == 0 ||
1567+
strcmp(label, "Struct") == 0 || strcmp(label, "Union") == 0);
1568+
}
1569+
1570+
static bool cbm_def_label_is_callable(const char *label) {
1571+
return label != NULL && (strcmp(label, "Function") == 0 || strcmp(label, "Method") == 0);
1572+
}
1573+
15171574
/* Remap an expanded-source definition back to the original input file. Every
15181575
* line in the definition must be attributable to the main file; generated
15191576
* macro bodies, included headers, and ambiguous spans fail closed. */
@@ -2133,6 +2190,14 @@ static CBMFileResult *extract_file_ex_body(const char *source, int source_len, C
21332190
// Defs keep original-source line numbers; only CALLS are extracted from expanded source.
21342191
if (language == CBM_LANG_C || language == CBM_LANG_CPP || language == CBM_LANG_CUDA) {
21352192
uint64_t pp_start = now_ns();
2193+
/* #1989: collect build-system export-macro candidates from the raw
2194+
* source. They are predefined empty in the expanded buffer (see
2195+
* preprocessor.cpp) so `class MOD_API Foo` parses with its real name,
2196+
* and the rescue loop below can adopt corrected type defs. Bounded:
2197+
* at most CBM_EXPORT_MACRO_MAX names per file. */
2198+
char export_cands[CBM_EXPORT_MACRO_MAX][CBM_EXPORT_MACRO_NAME_MAX];
2199+
int export_cand_count =
2200+
cbm_export_macro_candidates(source, source_len, export_cands, CBM_EXPORT_MACRO_MAX);
21362201
CBMPreprocessedSource *preprocessed = cbm_preprocess_with_map(
21372202
source, source_len, rel_path, extra_defines, include_paths, language != CBM_LANG_C);
21382203
if (preprocessed && preprocessed->source) {
@@ -2213,44 +2278,212 @@ static CBMFileResult *extract_file_ex_body(const char *source, int source_len, C
22132278
* recover defs from it — adopting ONLY those that
22142279
* intersect a raw ERROR region, whose name is visible on
22152280
* the raw source line, and whose QN the raw pass did not
2216-
* already extract. */
2217-
if (ts_node_has_error(root)) {
2281+
* already extract.
2282+
*
2283+
* #1989: build-system export macros (<MOD>_API,
2284+
* <lib>_EXPORT, ...) are empty on the real compile line but
2285+
* opaque to tree-sitter, so `class MOD_API Foo` misparses
2286+
* with the macro as the type name — often with NO raw ERROR
2287+
* region at all (the class_specifier "succeeds" with the
2288+
* wrong name; enums and free functions do error out). When
2289+
* export-macro candidates were collected for this file, also
2290+
* adopt expanded defs that replace a raw def literally named
2291+
* as a candidate, or that correct a raw misparse label (the
2292+
* raw pass extracted the same name as Function/Method from
2293+
* the broken function_definition shape; the expanded tree
2294+
* yields a proper type def). Superseded raw defs are
2295+
* dropped so the corrected definition is not shadowed by
2296+
* the qualified-name dedup. With no candidates the block
2297+
* reduces to the original #961 behavior. */
2298+
if (ts_node_has_error(root) || export_cand_count > 0) {
22182299
cbm_error_regions_t raw_regs = {{0}, {0}, 0, 0};
22192300
cbm_collect_error_regions(root, &raw_regs, source, source_len);
2220-
if (raw_regs.count > 0) {
2301+
if (raw_regs.count > 0 || export_cand_count > 0) {
22212302
int defs_before = result->defs.count;
22222303
cbm_extract_definitions(&pp_ctx);
22232304
int w = defs_before;
2305+
char *superseded = NULL;
2306+
if (export_cand_count > 0 && defs_before > 0) {
2307+
superseded =
2308+
(char *)cbm_calloc(CBM_MEM_CLASS_EXTRACT, (size_t)defs_before);
2309+
}
2310+
/* #1989: spans of defs already adopted from the
2311+
* expanded tree. A def nested inside one of these
2312+
* (an inline method of a rescued class) was dropped
2313+
* by the raw pass — the misparsed
2314+
* `class MOD_API Foo {...}` parsed as a
2315+
* function_definition, and nested defs in its body
2316+
* are never walked. Bounded: one span per adopted
2317+
* def, capped at 64. */
2318+
struct {
2319+
uint32_t start;
2320+
uint32_t end;
2321+
} rescued_spans[64];
2322+
int rescued_count = 0;
22242323
for (int i = defs_before; i < result->defs.count; i++) {
22252324
CBMDefinition *d = &result->defs.items[i];
22262325
bool adopt = false;
2326+
int supersede_j = -1;
22272327
if (cbm_remap_preprocessed_def(d, preprocessed)) {
22282328
for (int rj = 0; rj < raw_regs.count && !adopt; rj++) {
22292329
if (d->start_line <= raw_regs.ends[rj] &&
22302330
d->end_line >= raw_regs.starts[rj]) {
22312331
adopt = true;
22322332
}
22332333
}
2334+
if (!adopt && export_cand_count > 0 && d->name) {
2335+
for (int ci = 0; ci < export_cand_count && !adopt; ci++) {
2336+
for (int j = 0; j < defs_before && !adopt; j++) {
2337+
CBMDefinition *r = &result->defs.items[j];
2338+
if (!r->name ||
2339+
strcmp(r->name, export_cands[ci]) != 0) {
2340+
continue;
2341+
}
2342+
if (r->start_line <= d->end_line &&
2343+
d->start_line <= r->end_line &&
2344+
cbm_span_contains_type_def(
2345+
source, source_len, d->start_line,
2346+
d->end_line, d->name)) {
2347+
adopt = true;
2348+
supersede_j = j;
2349+
}
2350+
}
2351+
}
2352+
if (!adopt && d->label && cbm_def_label_is_type(d->label)) {
2353+
for (int j = 0; j < defs_before && !adopt; j++) {
2354+
CBMDefinition *r = &result->defs.items[j];
2355+
/* Widened past callables (#1989): the
2356+
* misparse also mints value-label raw
2357+
* defs for types (`class X_API FEmpty {}`
2358+
* -> Variable FEmpty). Same name,
2359+
* overlapping span, raw label is NOT a
2360+
* type => raw is the misparse artifact. */
2361+
if (!r->name || !r->label || !d->name ||
2362+
strcmp(r->name, d->name) != 0 ||
2363+
cbm_def_label_is_type(r->label)) {
2364+
continue;
2365+
}
2366+
if (r->start_line <= d->end_line &&
2367+
d->start_line <= r->end_line) {
2368+
adopt = true;
2369+
supersede_j = j;
2370+
}
2371+
}
2372+
}
2373+
/* Nested rescue (#1989): a def nested inside
2374+
* an already-adopted def's span — the inline
2375+
* method of a rescued class, dropped by the
2376+
* raw pass because the class parsed as a
2377+
* function body. The same validation gates
2378+
* (name-on-line, shape, QN dedup) still run
2379+
* below before it lands. */
2380+
if (!adopt && rescued_count > 0) {
2381+
for (int k = 0; k < rescued_count && !adopt; k++) {
2382+
if (d->start_line >= rescued_spans[k].start &&
2383+
d->end_line <= rescued_spans[k].end) {
2384+
adopt = true;
2385+
}
2386+
}
2387+
}
2388+
}
22342389
}
2235-
if (adopt && (!d->name ||
2236-
!cbm_line_contains(source, source_len, d->start_line,
2237-
d->name) ||
2238-
!cbm_span_contains_callable_def(
2239-
source, source_len, d->start_line, d->end_line,
2240-
d->name))) {
2390+
if (adopt &&
2391+
(!d->name ||
2392+
!cbm_line_contains(source, source_len, d->start_line,
2393+
d->name) ||
2394+
(!cbm_span_contains_callable_def(source, source_len,
2395+
d->start_line, d->end_line,
2396+
d->name) &&
2397+
!cbm_span_contains_type_def(source, source_len, d->start_line,
2398+
d->end_line, d->name)))) {
22412399
adopt = false;
22422400
}
22432401
for (int j = 0; j < defs_before && adopt; j++) {
2402+
if (j == supersede_j || (superseded && superseded[j])) {
2403+
continue; // being replaced by this very def
2404+
}
22442405
const char *q = result->defs.items[j].qualified_name;
22452406
if (q && d->qualified_name &&
22462407
strcmp(q, d->qualified_name) == 0) {
2408+
/* #1989: a raw def holding the same QN is
2409+
* usually a reject — unless it is the
2410+
* misparse artifact this very def came to
2411+
* replace (raw "Function FCalc" from the
2412+
* broken `class MOD_API FCalc` shape vs the
2413+
* rescued "Class FCalc"). Same name, same
2414+
* span, raw label is not a type: supersede
2415+
* instead of dropping the correction. */
2416+
CBMDefinition *r = &result->defs.items[j];
2417+
if (d->label && cbm_def_label_is_type(d->label) &&
2418+
r->label && r->name && d->name &&
2419+
strcmp(r->name, d->name) == 0 &&
2420+
!cbm_def_label_is_type(r->label) &&
2421+
r->start_line == d->start_line &&
2422+
r->end_line == d->end_line && superseded) {
2423+
superseded[j] = 1;
2424+
continue;
2425+
}
22472426
adopt = false;
22482427
}
22492428
}
22502429
if (adopt) {
2430+
if (supersede_j >= 0 && supersede_j < defs_before &&
2431+
superseded) {
2432+
superseded[supersede_j] = 1;
2433+
}
2434+
/* Phantom-artifact suppression (#1989): on the
2435+
* raw tree `class MOD_API Foo : public Base
2436+
* {...}` parses as a function_definition whose
2437+
* declarator is the BASE-CLASS name, so the raw
2438+
* pass mints a bogus Function def named after
2439+
* the base spanning the whole class. Fingerprint:
2440+
* a raw callable def with an IDENTICAL span to
2441+
* the adopted type def whose name is one of the
2442+
* adopted def's base classes. A real method is
2443+
* a strict subset of the class span and never
2444+
* carries a base-class name. */
2445+
if (d->label && cbm_def_label_is_type(d->label) && superseded &&
2446+
d->base_classes) {
2447+
for (int j = 0; j < defs_before; j++) {
2448+
CBMDefinition *r = &result->defs.items[j];
2449+
if (!r->label || !r->name ||
2450+
!cbm_def_label_is_callable(r->label) ||
2451+
r->start_line != d->start_line ||
2452+
r->end_line != d->end_line) {
2453+
continue;
2454+
}
2455+
for (const char **b = d->base_classes; *b; b++) {
2456+
if (strcmp(*b, r->name) == 0) {
2457+
superseded[j] = 1;
2458+
break;
2459+
}
2460+
}
2461+
}
2462+
}
2463+
if (rescued_count < 64) {
2464+
rescued_spans[rescued_count].start = d->start_line;
2465+
rescued_spans[rescued_count].end = d->end_line;
2466+
rescued_count++;
2467+
}
22512468
result->defs.items[w++] = *d;
22522469
}
22532470
}
2471+
if (superseded) {
2472+
int rw = 0;
2473+
for (int j = 0; j < defs_before; j++) {
2474+
if (!superseded[j]) {
2475+
result->defs.items[rw++] = result->defs.items[j];
2476+
}
2477+
}
2478+
int shift = defs_before - rw;
2479+
if (shift > 0 && w > defs_before) {
2480+
memmove(&result->defs.items[rw],
2481+
&result->defs.items[defs_before],
2482+
(size_t)(w - defs_before) * sizeof(CBMDefinition));
2483+
}
2484+
w -= shift;
2485+
cbm_free(CBM_MEM_CLASS_EXTRACT, superseded);
2486+
}
22542487
result->defs.count = w;
22552488
}
22562489
}

0 commit comments

Comments
 (0)