Skip to content

Commit eee5a31

Browse files
committed
fix(cpp): remap preprocessed recovery coverage
Signed-off-by: Blank_Answer <97771966+blankanswer@users.noreply.github.com>
1 parent 7caa52e commit eee5a31

2 files changed

Lines changed: 368 additions & 6 deletions

File tree

internal/cbm/cbm.c

Lines changed: 350 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,190 @@ static bool def_label_is_preprocessed_callable(const char *label) {
152152
return label && (strcmp(label, "Function") == 0 || strcmp(label, "Method") == 0);
153153
}
154154

155+
static bool cbm_ident_char(char c) {
156+
return isalnum((unsigned char)c) || c == '_';
157+
}
158+
159+
static bool cbm_plain_identifier(const char *s) {
160+
if (!s || (!isalpha((unsigned char)s[0]) && s[0] != '_')) {
161+
return false;
162+
}
163+
for (const char *p = s + 1; *p; p++) {
164+
if (!cbm_ident_char(*p)) {
165+
return false;
166+
}
167+
}
168+
return true;
169+
}
170+
171+
static const char *cbm_last_qn_segment(const char *qn) {
172+
if (!qn) {
173+
return NULL;
174+
}
175+
const char *dot = strrchr(qn, '.');
176+
return dot ? dot + 1 : qn;
177+
}
178+
179+
static bool cbm_ident_at(const char *source, int source_len, int pos, const char *ident,
180+
int ident_len) {
181+
if (pos < 0 || ident_len <= 0 || pos + ident_len > source_len) {
182+
return false;
183+
}
184+
if (pos > 0 && cbm_ident_char(source[pos - 1])) {
185+
return false;
186+
}
187+
if (pos + ident_len < source_len && cbm_ident_char(source[pos + ident_len])) {
188+
return false;
189+
}
190+
return strncmp(source + pos, ident, (size_t)ident_len) == 0;
191+
}
192+
193+
static bool cbm_scope_before_name_matches(const char *source, int name_pos, const char *scope) {
194+
if (!scope || !scope[0]) {
195+
return true;
196+
}
197+
const char *begin = source;
198+
const char *p = source + name_pos;
199+
while (p > begin && isspace((unsigned char)p[-1])) {
200+
p--;
201+
}
202+
if (p - begin < 2 || p[-1] != ':' || p[-2] != ':') {
203+
return false;
204+
}
205+
p -= 2;
206+
while (p > begin && isspace((unsigned char)p[-1])) {
207+
p--;
208+
}
209+
const char *end = p;
210+
while (p > begin && cbm_ident_char(p[-1])) {
211+
p--;
212+
}
213+
size_t len = (size_t)(end - p);
214+
return len == strlen(scope) && strncmp(p, scope, len) == 0;
215+
}
216+
217+
static int cbm_find_matching_paren(const char *source, int source_len, int open_pos) {
218+
int depth = 0;
219+
for (int i = open_pos; i < source_len; i++) {
220+
if (source[i] == '(') {
221+
depth++;
222+
} else if (source[i] == ')') {
223+
if (--depth == 0) {
224+
return i;
225+
}
226+
}
227+
}
228+
return -1;
229+
}
230+
231+
static bool cbm_span_ws_equal(const char *a, int alen, const char *b) {
232+
int ai = 0;
233+
int bi = 0;
234+
int blen = b ? (int)strlen(b) : 0;
235+
while (ai < alen || bi < blen) {
236+
while (ai < alen && isspace((unsigned char)a[ai])) {
237+
ai++;
238+
}
239+
while (bi < blen && isspace((unsigned char)b[bi])) {
240+
bi++;
241+
}
242+
if (ai >= alen || bi >= blen) {
243+
break;
244+
}
245+
if (a[ai++] != b[bi++]) {
246+
return false;
247+
}
248+
}
249+
while (ai < alen && isspace((unsigned char)a[ai])) {
250+
ai++;
251+
}
252+
while (bi < blen && isspace((unsigned char)b[bi])) {
253+
bi++;
254+
}
255+
return ai == alen && bi == blen;
256+
}
257+
258+
static int cbm_find_definition_open_brace(const char *source, int source_len, int after_paren) {
259+
for (int i = after_paren + 1; i < source_len; i++) {
260+
if (source[i] == '{') {
261+
return i;
262+
}
263+
if (source[i] == ';') {
264+
return -1; /* declaration, not definition */
265+
}
266+
}
267+
return -1;
268+
}
269+
270+
static int cbm_find_definition_close_brace(const char *source, int source_len, int open_pos) {
271+
int depth = 0;
272+
for (int i = open_pos; i < source_len; i++) {
273+
if (source[i] == '{') {
274+
depth++;
275+
} else if (source[i] == '}') {
276+
if (--depth == 0) {
277+
return i;
278+
}
279+
}
280+
}
281+
return open_pos;
282+
}
283+
284+
static uint32_t cbm_line_for_byte(const char *source, int byte_pos) {
285+
uint32_t line = 1;
286+
for (int i = 0; i < byte_pos; i++) {
287+
if (source[i] == '\n') {
288+
line++;
289+
}
290+
}
291+
return line;
292+
}
293+
294+
static bool cbm_remap_preprocessed_callable_lines(CBMDefinition *def, const char *source,
295+
int source_len) {
296+
if (!def || !source || source_len <= 0 || !cbm_plain_identifier(def->name)) {
297+
return false;
298+
}
299+
const char *scope = NULL;
300+
if (def->label && strcmp(def->label, "Method") == 0) {
301+
scope = cbm_last_qn_segment(def->parent_class);
302+
}
303+
int name_len = (int)strlen(def->name);
304+
for (int i = 0; i + name_len < source_len; i++) {
305+
if (!cbm_ident_at(source, source_len, i, def->name, name_len)) {
306+
continue;
307+
}
308+
if (!cbm_scope_before_name_matches(source, i, scope)) {
309+
continue;
310+
}
311+
int p = i + name_len;
312+
while (p < source_len && isspace((unsigned char)source[p])) {
313+
p++;
314+
}
315+
if (p >= source_len || source[p] != '(') {
316+
continue;
317+
}
318+
int close_paren = cbm_find_matching_paren(source, source_len, p);
319+
if (close_paren < 0) {
320+
continue;
321+
}
322+
if (def->signature && !cbm_span_ws_equal(source + p, close_paren - p + 1,
323+
def->signature)) {
324+
continue;
325+
}
326+
int open_brace = cbm_find_definition_open_brace(source, source_len, close_paren);
327+
if (open_brace < 0) {
328+
continue;
329+
}
330+
int close_brace = cbm_find_definition_close_brace(source, source_len, open_brace);
331+
def->start_line = cbm_line_for_byte(source, i);
332+
def->end_line = cbm_line_for_byte(source, close_brace);
333+
def->lines = (int)(def->end_line - def->start_line + 1);
334+
return true;
335+
}
336+
return false;
337+
}
338+
155339
static bool result_has_same_def_identity(const CBMFileResult *result, const CBMDefinition *def) {
156340
if (!result || !def || !def->label || !def->qualified_name) {
157341
return false;
@@ -170,7 +354,8 @@ static bool result_has_same_def_identity(const CBMFileResult *result, const CBMD
170354
}
171355

172356
static void merge_missing_preprocessed_callables(CBMFileResult *dst, const CBMFileResult *src,
173-
CBMArena *arena) {
357+
CBMArena *arena, const char *original_source,
358+
int original_source_len) {
174359
if (!dst || !src) {
175360
return;
176361
}
@@ -185,7 +370,10 @@ static void merge_missing_preprocessed_callables(CBMFileResult *dst, const CBMFi
185370
if (result_has_same_def_identity(dst, def)) {
186371
continue;
187372
}
188-
cbm_defs_push(&dst->defs, arena, *def);
373+
CBMDefinition remapped = *def;
374+
(void)cbm_remap_preprocessed_callable_lines(&remapped, original_source,
375+
original_source_len);
376+
cbm_defs_push(&dst->defs, arena, remapped);
189377
}
190378
}
191379

@@ -858,10 +1046,165 @@ static bool cbm_region_is_recovered(uint32_t rs, uint32_t re, const CBMDefArray
8581046
return covered_to >= re;
8591047
}
8601048

861-
static void cbm_subtract_recovered_regions(cbm_error_regions_t *regs, const CBMDefArray *defs) {
1049+
typedef struct {
1050+
uint32_t starts[256];
1051+
uint32_t ends[256];
1052+
int count;
1053+
} cbm_line_intervals_t;
1054+
1055+
static bool cbm_lang_uses_c_preprocessor(CBMLanguage language) {
1056+
return language == CBM_LANG_C || language == CBM_LANG_CPP || language == CBM_LANG_CUDA;
1057+
}
1058+
1059+
static bool cbm_def_is_recovery_evidence(const CBMDefinition *d, uint32_t rs, uint32_t re) {
1060+
return d && d->label && strcmp(d->label, "Module") != 0 && strcmp(d->label, "Package") != 0 &&
1061+
d->start_line >= rs && d->start_line <= re;
1062+
}
1063+
1064+
static void cbm_line_intervals_push(cbm_line_intervals_t *ivals, uint32_t start, uint32_t end) {
1065+
if (!ivals || ivals->count >= (int)(sizeof(ivals->starts) / sizeof(ivals->starts[0]))) {
1066+
return;
1067+
}
1068+
ivals->starts[ivals->count] = start;
1069+
ivals->ends[ivals->count] = end < start ? start : end;
1070+
ivals->count++;
1071+
}
1072+
1073+
static bool cbm_line_intervals_cover(const cbm_line_intervals_t *ivals, uint32_t line) {
1074+
if (!ivals) {
1075+
return false;
1076+
}
1077+
for (int i = 0; i < ivals->count; i++) {
1078+
if (line >= ivals->starts[i] && line <= ivals->ends[i]) {
1079+
return true;
1080+
}
1081+
}
1082+
return false;
1083+
}
1084+
1085+
static bool cbm_line_blank_or_preproc(const char *source, int start, int end, bool *is_preproc) {
1086+
int p = start;
1087+
while (p < end && isspace((unsigned char)source[p])) {
1088+
p++;
1089+
}
1090+
if (is_preproc) {
1091+
*is_preproc = p < end && source[p] == '#';
1092+
}
1093+
return p >= end || (p < end && source[p] == '#');
1094+
}
1095+
1096+
static void cbm_collect_cpp_preproc_alt_signatures(const char *source, int source_len, uint32_t rs,
1097+
uint32_t re, const CBMDefArray *defs,
1098+
cbm_line_intervals_t *alts) {
1099+
int line_start = 0;
1100+
uint32_t line = 1;
1101+
for (int p = 0; p <= source_len; p++) {
1102+
if (p < source_len && source[p] != '\n') {
1103+
continue;
1104+
}
1105+
int line_end = p;
1106+
if (line >= rs && line <= re) {
1107+
for (int di = 0; di < defs->count; di++) {
1108+
const CBMDefinition *d = &defs->items[di];
1109+
if (!cbm_def_is_recovery_evidence(d, rs, re) ||
1110+
!def_label_is_preprocessed_callable(d->label) || !cbm_plain_identifier(d->name)) {
1111+
continue;
1112+
}
1113+
const char *scope = NULL;
1114+
if (strcmp(d->label, "Method") == 0) {
1115+
scope = cbm_last_qn_segment(d->parent_class);
1116+
}
1117+
int name_len = (int)strlen(d->name);
1118+
for (int pos = line_start; pos + name_len <= line_end; pos++) {
1119+
if (!cbm_ident_at(source, source_len, pos, d->name, name_len) ||
1120+
!cbm_scope_before_name_matches(source, pos, scope)) {
1121+
continue;
1122+
}
1123+
int sig_start = pos + name_len;
1124+
while (sig_start < source_len && isspace((unsigned char)source[sig_start])) {
1125+
sig_start++;
1126+
}
1127+
if (sig_start >= source_len || source[sig_start] != '(') {
1128+
continue;
1129+
}
1130+
int close_paren = cbm_find_matching_paren(source, source_len, sig_start);
1131+
if (close_paren < 0) {
1132+
continue;
1133+
}
1134+
int open_brace = cbm_find_definition_open_brace(source, source_len, close_paren);
1135+
if (open_brace < 0) {
1136+
continue;
1137+
}
1138+
uint32_t open_line = cbm_line_for_byte(source, open_brace);
1139+
if (open_line >= line && open_line <= re) {
1140+
cbm_line_intervals_push(alts, line, open_line);
1141+
}
1142+
}
1143+
}
1144+
}
1145+
line++;
1146+
line_start = p + 1;
1147+
}
1148+
}
1149+
1150+
/* C-family preprocessor recovery: the raw tree can report one ERROR region
1151+
* spanning an entire #ifdef/#else signature block plus later definitions. Once
1152+
* the preprocessed pass recovers the selected callable and the trailing defs,
1153+
* unselected same-name signature lines are not a graph miss for that selected
1154+
* configuration. Different-name branches still stay flagged. */
1155+
static bool cbm_region_is_recovered_with_cpp_preproc(uint32_t rs, uint32_t re,
1156+
const CBMDefArray *defs,
1157+
const char *source, int source_len,
1158+
CBMLanguage language) {
1159+
if (!cbm_lang_uses_c_preprocessor(language) || !source || source_len <= 0) {
1160+
return false;
1161+
}
1162+
1163+
cbm_line_intervals_t defs_cover = {{0}, {0}, 0};
1164+
for (int i = 0; i < defs->count; i++) {
1165+
const CBMDefinition *d = &defs->items[i];
1166+
if (cbm_def_is_recovery_evidence(d, rs, re)) {
1167+
cbm_line_intervals_push(&defs_cover, d->start_line, d->end_line);
1168+
}
1169+
}
1170+
if (defs_cover.count == 0) {
1171+
return false;
1172+
}
1173+
1174+
cbm_line_intervals_t alt_sigs = {{0}, {0}, 0};
1175+
cbm_collect_cpp_preproc_alt_signatures(source, source_len, rs, re, defs, &alt_sigs);
1176+
1177+
bool saw_preproc = false;
1178+
int line_start = 0;
1179+
uint32_t line = 1;
1180+
for (int p = 0; p <= source_len; p++) {
1181+
if (p < source_len && source[p] != '\n') {
1182+
continue;
1183+
}
1184+
int line_end = p;
1185+
if (line >= rs && line <= re) {
1186+
bool is_preproc = false;
1187+
if (cbm_line_blank_or_preproc(source, line_start, line_end, &is_preproc)) {
1188+
saw_preproc = saw_preproc || is_preproc;
1189+
} else if (!cbm_line_intervals_cover(&defs_cover, line) &&
1190+
!cbm_line_intervals_cover(&alt_sigs, line)) {
1191+
return false;
1192+
}
1193+
}
1194+
line++;
1195+
line_start = p + 1;
1196+
}
1197+
return saw_preproc;
1198+
}
1199+
1200+
static void cbm_subtract_recovered_regions(cbm_error_regions_t *regs, const CBMDefArray *defs,
1201+
const char *source, int source_len,
1202+
CBMLanguage language) {
8621203
int kept = 0;
8631204
for (int i = 0; i < regs->count; i++) {
864-
if (!cbm_region_is_recovered(regs->starts[i], regs->ends[i], defs)) {
1205+
if (!cbm_region_is_recovered(regs->starts[i], regs->ends[i], defs) &&
1206+
!cbm_region_is_recovered_with_cpp_preproc(regs->starts[i], regs->ends[i], defs,
1207+
source, source_len, language)) {
8651208
regs->starts[kept] = regs->starts[i];
8661209
regs->ends[kept] = regs->ends[i];
8671210
kept++;
@@ -1141,7 +1484,8 @@ static CBMFileResult *cbm_extract_file_impl(const char *source, int source_len,
11411484
CBMExtractCtx pp_defs_ctx = pp_ctx;
11421485
pp_defs_ctx.result = &pp_defs_result;
11431486
cbm_extract_definition_nodes(&pp_defs_ctx);
1144-
merge_missing_preprocessed_callables(result, &pp_defs_result, a);
1487+
merge_missing_preprocessed_callables(result, &pp_defs_result, a, source,
1488+
source_len);
11451489

11461490
// Re-run unified extraction on expanded source.
11471491
// 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,
12731617
} else {
12741618
cbm_collect_error_regions(root, &regs);
12751619
}
1276-
cbm_subtract_recovered_regions(&regs, &result->defs);
1620+
cbm_subtract_recovered_regions(&regs, &result->defs, source, source_len, language);
12771621
if (regs.count > 0) {
12781622
result->parse_incomplete = true;
12791623
result->error_region_count = regs.count;

0 commit comments

Comments
 (0)