Skip to content

Commit 256b3d3

Browse files
committed
fix(parsing): recover export-macro-hidden type definitions (#1989)
Collect a bounded set of conventional export macro candidates and inject empty definitions into the existing C/C++ preprocessing second pass without overriding explicit caller definitions. Conservatively reconcile remapped definitions to recover hidden classes, structs, enums, free functions, and inline methods while suppressing matching base-class phantom callables. Add focused regression coverage for supported suffixes, ordinary all-caps negative controls, candidate limits, comments, strings, raw strings, overlong names, explicit define priority, and C/C++ extraction. Local MinGW extraction tests pass (344/344). ASan/UBSan were not available in the local MinGW toolchain and remain covered by upstream CI. Signed-off-by: XIYBHK <xiybhk@163.com>
1 parent ae5de7f commit 256b3d3

4 files changed

Lines changed: 944 additions & 10 deletions

File tree

‎internal/cbm/cbm.c‎

Lines changed: 241 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1021,6 +1021,63 @@ static bool cbm_span_contains_callable_def(const char *src, int src_len, uint32_
10211021
return false;
10221022
}
10231023

1024+
/* #1989: type-def counterpart of cbm_span_contains_callable_def. A rescued
1025+
* class/struct/enum definition has its name followed by a base clause (':') or
1026+
* a body ('{'), not a parameter list, so the callable validator would reject
1027+
* every type def pulled from the expanded tree. */
1028+
static bool cbm_span_contains_type_def(const char *src, int src_len, uint32_t start_line,
1029+
uint32_t end_line, const char *name) {
1030+
if (!src || src_len <= 0 || !name || !name[0] || start_line == 0 || end_line < start_line) {
1031+
return false;
1032+
}
1033+
int span_start = 0;
1034+
uint32_t line = 1;
1035+
while (span_start < src_len && line < start_line) {
1036+
if (src[span_start++] == '\n') {
1037+
line++;
1038+
}
1039+
}
1040+
if (line != start_line) {
1041+
return false;
1042+
}
1043+
int span_end = span_start;
1044+
while (span_end < src_len && line <= end_line) {
1045+
if (src[span_end++] == '\n') {
1046+
line++;
1047+
}
1048+
}
1049+
size_t name_len = strlen(name);
1050+
for (int pos = span_start; pos + (int)name_len <= span_end; pos++) {
1051+
if (strncmp(src + pos, name, name_len) != 0 ||
1052+
(pos > 0 && cbm_identifier_char(src[pos - 1])) ||
1053+
(pos + (int)name_len < src_len && cbm_identifier_char(src[pos + name_len]))) {
1054+
continue;
1055+
}
1056+
int open = pos + (int)name_len;
1057+
while (open < span_end && isspace((unsigned char)src[open])) {
1058+
open++;
1059+
}
1060+
// Base clause (": public B"), body ("{"), or template args ("<...").
1061+
if (open < span_end && (src[open] == ':' || src[open] == '{' || src[open] == '<')) {
1062+
return true;
1063+
}
1064+
}
1065+
return false;
1066+
}
1067+
1068+
/* #1989: label buckets for the misparse-correction gates. A raw def extracted
1069+
* from a broken `class MOD_API Foo` shape lands under a callable label while
1070+
* the expanded tree yields a proper type label for the same name. */
1071+
static bool cbm_def_label_is_type(const char *label) {
1072+
return label != NULL && (strcmp(label, "Class") == 0 || strcmp(label, "Enum") == 0 ||
1073+
strcmp(label, "Interface") == 0 || strcmp(label, "Type") == 0 ||
1074+
strcmp(label, "Struct") == 0 || strcmp(label, "Union") == 0);
1075+
}
1076+
1077+
static bool cbm_def_label_is_callable(const char *label) {
1078+
return label != NULL && (strcmp(label, "Function") == 0 || strcmp(label, "Method") == 0);
1079+
}
1080+
10241081
/* Remap an expanded-source definition back to the original input file. Every
10251082
* line in the definition must be attributable to the main file; generated
10261083
* macro bodies, included headers, and ambiguous spans fail closed. */
@@ -1398,6 +1455,14 @@ CBMFileResult *cbm_extract_file_ex(const char *source, int source_len, CBMLangua
13981455
// Defs keep original-source line numbers; only CALLS are extracted from expanded source.
13991456
if (language == CBM_LANG_C || language == CBM_LANG_CPP || language == CBM_LANG_CUDA) {
14001457
uint64_t pp_start = now_ns();
1458+
/* #1989: collect build-system export-macro candidates from the raw
1459+
* source. They are predefined empty in the expanded buffer (see
1460+
* preprocessor.cpp) so `class MOD_API Foo` parses with its real name,
1461+
* and the rescue loop below can adopt corrected type defs. Bounded:
1462+
* at most CBM_EXPORT_MACRO_MAX names per file. */
1463+
char export_cands[CBM_EXPORT_MACRO_MAX][CBM_EXPORT_MACRO_NAME_MAX];
1464+
int export_cand_count =
1465+
cbm_export_macro_candidates(source, source_len, export_cands, CBM_EXPORT_MACRO_MAX);
14011466
CBMPreprocessedSource *preprocessed = cbm_preprocess_with_map(
14021467
source, source_len, rel_path, extra_defines, include_paths, language != CBM_LANG_C);
14031468
if (preprocessed && preprocessed->source) {
@@ -1477,44 +1542,211 @@ CBMFileResult *cbm_extract_file_ex(const char *source, int source_len, CBMLangua
14771542
* recover defs from it — adopting ONLY those that
14781543
* intersect a raw ERROR region, whose name is visible on
14791544
* the raw source line, and whose QN the raw pass did not
1480-
* already extract. */
1481-
if (ts_node_has_error(root)) {
1545+
* already extract.
1546+
*
1547+
* #1989: build-system export macros (<MOD>_API,
1548+
* <lib>_EXPORT, ...) are empty on the real compile line but
1549+
* opaque to tree-sitter, so `class MOD_API Foo` misparses
1550+
* with the macro as the type name — often with NO raw ERROR
1551+
* region at all (the class_specifier "succeeds" with the
1552+
* wrong name; enums and free functions do error out). When
1553+
* export-macro candidates were collected for this file, also
1554+
* adopt expanded defs that replace a raw def literally named
1555+
* as a candidate, or that correct a raw misparse label (the
1556+
* raw pass extracted the same name as Function/Method from
1557+
* the broken function_definition shape; the expanded tree
1558+
* yields a proper type def). Superseded raw defs are
1559+
* dropped so the corrected definition is not shadowed by
1560+
* the qualified-name dedup. With no candidates the block
1561+
* reduces to the original #961 behavior. */
1562+
if (ts_node_has_error(root) || export_cand_count > 0) {
14821563
cbm_error_regions_t raw_regs = {{0}, {0}, 0};
14831564
cbm_collect_error_regions(root, &raw_regs, source, source_len);
1484-
if (raw_regs.count > 0) {
1565+
if (raw_regs.count > 0 || export_cand_count > 0) {
14851566
int defs_before = result->defs.count;
14861567
cbm_extract_definitions(&pp_ctx);
14871568
int w = defs_before;
1569+
char *superseded = NULL;
1570+
if (export_cand_count > 0 && defs_before > 0) {
1571+
superseded = (char *)calloc((size_t)defs_before, 1);
1572+
}
1573+
/* #1989: spans of defs already adopted from the
1574+
* expanded tree. A def nested inside one of these
1575+
* (an inline method of a rescued class) was dropped
1576+
* by the raw pass — the misparsed
1577+
* `class MOD_API Foo {...}` parsed as a
1578+
* function_definition, and nested defs in its body
1579+
* are never walked. Bounded: one span per adopted
1580+
* def, capped at 64. */
1581+
struct {
1582+
uint32_t start;
1583+
uint32_t end;
1584+
} rescued_spans[64];
1585+
int rescued_count = 0;
14881586
for (int i = defs_before; i < result->defs.count; i++) {
14891587
CBMDefinition *d = &result->defs.items[i];
14901588
bool adopt = false;
1589+
int supersede_j = -1;
14911590
if (cbm_remap_preprocessed_def(d, preprocessed)) {
14921591
for (int rj = 0; rj < raw_regs.count && !adopt; rj++) {
14931592
if (d->start_line <= raw_regs.ends[rj] &&
14941593
d->end_line >= raw_regs.starts[rj]) {
14951594
adopt = true;
14961595
}
14971596
}
1597+
if (!adopt && export_cand_count > 0 && d->name) {
1598+
for (int ci = 0; ci < export_cand_count && !adopt; ci++) {
1599+
for (int j = 0; j < defs_before && !adopt; j++) {
1600+
CBMDefinition *r = &result->defs.items[j];
1601+
if (!r->name ||
1602+
strcmp(r->name, export_cands[ci]) != 0) {
1603+
continue;
1604+
}
1605+
if (r->start_line <= d->end_line &&
1606+
d->start_line <= r->end_line &&
1607+
cbm_span_contains_type_def(
1608+
source, source_len, d->start_line,
1609+
d->end_line, d->name)) {
1610+
adopt = true;
1611+
supersede_j = j;
1612+
}
1613+
}
1614+
}
1615+
if (!adopt && d->label && cbm_def_label_is_type(d->label)) {
1616+
for (int j = 0; j < defs_before && !adopt; j++) {
1617+
CBMDefinition *r = &result->defs.items[j];
1618+
/* Widened past callables (#1989): the
1619+
* misparse also mints value-label raw
1620+
* defs for types (`class X_API FEmpty {}`
1621+
* -> Variable FEmpty). Same name,
1622+
* overlapping span, raw label is NOT a
1623+
* type => raw is the misparse artifact. */
1624+
if (!r->name || !r->label || !d->name ||
1625+
strcmp(r->name, d->name) != 0 ||
1626+
cbm_def_label_is_type(r->label)) {
1627+
continue;
1628+
}
1629+
if (r->start_line <= d->end_line &&
1630+
d->start_line <= r->end_line) {
1631+
adopt = true;
1632+
supersede_j = j;
1633+
}
1634+
}
1635+
}
1636+
/* Nested rescue (#1989): a def nested inside
1637+
* an already-adopted def's span — the inline
1638+
* method of a rescued class, dropped by the
1639+
* raw pass because the class parsed as a
1640+
* function body. The same validation gates
1641+
* (name-on-line, shape, QN dedup) still run
1642+
* below before it lands. */
1643+
if (!adopt && rescued_count > 0) {
1644+
for (int k = 0; k < rescued_count && !adopt; k++) {
1645+
if (d->start_line >= rescued_spans[k].start &&
1646+
d->end_line <= rescued_spans[k].end) {
1647+
adopt = true;
1648+
}
1649+
}
1650+
}
1651+
}
14981652
}
1499-
if (adopt && (!d->name ||
1500-
!cbm_line_contains(source, source_len, d->start_line,
1501-
d->name) ||
1502-
!cbm_span_contains_callable_def(
1503-
source, source_len, d->start_line, d->end_line,
1504-
d->name))) {
1653+
if (adopt &&
1654+
(!d->name ||
1655+
!cbm_line_contains(source, source_len, d->start_line,
1656+
d->name) ||
1657+
(!cbm_span_contains_callable_def(source, source_len,
1658+
d->start_line, d->end_line,
1659+
d->name) &&
1660+
!cbm_span_contains_type_def(source, source_len, d->start_line,
1661+
d->end_line, d->name)))) {
15051662
adopt = false;
15061663
}
15071664
for (int j = 0; j < defs_before && adopt; j++) {
1665+
if (j == supersede_j || (superseded && superseded[j])) {
1666+
continue; // being replaced by this very def
1667+
}
15081668
const char *q = result->defs.items[j].qualified_name;
15091669
if (q && d->qualified_name &&
15101670
strcmp(q, d->qualified_name) == 0) {
1671+
/* #1989: a raw def holding the same QN is
1672+
* usually a reject — unless it is the
1673+
* misparse artifact this very def came to
1674+
* replace (raw "Function FCalc" from the
1675+
* broken `class MOD_API FCalc` shape vs the
1676+
* rescued "Class FCalc"). Same name, same
1677+
* span, raw label is not a type: supersede
1678+
* instead of dropping the correction. */
1679+
CBMDefinition *r = &result->defs.items[j];
1680+
if (d->label && cbm_def_label_is_type(d->label) &&
1681+
r->label && r->name && d->name &&
1682+
strcmp(r->name, d->name) == 0 &&
1683+
!cbm_def_label_is_type(r->label) &&
1684+
r->start_line == d->start_line &&
1685+
r->end_line == d->end_line && superseded) {
1686+
superseded[j] = 1;
1687+
continue;
1688+
}
15111689
adopt = false;
15121690
}
15131691
}
15141692
if (adopt) {
1693+
if (supersede_j >= 0 && supersede_j < defs_before &&
1694+
superseded) {
1695+
superseded[supersede_j] = 1;
1696+
}
1697+
/* Phantom-artifact suppression (#1989): on the
1698+
* raw tree `class MOD_API Foo : public Base
1699+
* {...}` parses as a function_definition whose
1700+
* declarator is the BASE-CLASS name, so the raw
1701+
* pass mints a bogus Function def named after
1702+
* the base spanning the whole class. Fingerprint:
1703+
* a raw callable def with an IDENTICAL span to
1704+
* the adopted type def whose name is one of the
1705+
* adopted def's base classes. A real method is
1706+
* a strict subset of the class span and never
1707+
* carries a base-class name. */
1708+
if (d->label && cbm_def_label_is_type(d->label) && superseded &&
1709+
d->base_classes) {
1710+
for (int j = 0; j < defs_before; j++) {
1711+
CBMDefinition *r = &result->defs.items[j];
1712+
if (!r->label || !r->name ||
1713+
!cbm_def_label_is_callable(r->label) ||
1714+
r->start_line != d->start_line ||
1715+
r->end_line != d->end_line) {
1716+
continue;
1717+
}
1718+
for (const char **b = d->base_classes; *b; b++) {
1719+
if (strcmp(*b, r->name) == 0) {
1720+
superseded[j] = 1;
1721+
break;
1722+
}
1723+
}
1724+
}
1725+
}
1726+
if (rescued_count < 64) {
1727+
rescued_spans[rescued_count].start = d->start_line;
1728+
rescued_spans[rescued_count].end = d->end_line;
1729+
rescued_count++;
1730+
}
15151731
result->defs.items[w++] = *d;
15161732
}
15171733
}
1734+
if (superseded) {
1735+
int rw = 0;
1736+
for (int j = 0; j < defs_before; j++) {
1737+
if (!superseded[j]) {
1738+
result->defs.items[rw++] = result->defs.items[j];
1739+
}
1740+
}
1741+
int shift = defs_before - rw;
1742+
if (shift > 0 && w > defs_before) {
1743+
memmove(&result->defs.items[rw],
1744+
&result->defs.items[defs_before],
1745+
(size_t)(w - defs_before) * sizeof(CBMDefinition));
1746+
}
1747+
w -= shift;
1748+
free(superseded);
1749+
}
15181750
result->defs.count = w;
15191751
}
15201752
}

0 commit comments

Comments
 (0)