Skip to content

Commit 99d3919

Browse files
committed
fix(cpp): remap preprocessed recovery lines
Signed-off-by: Blank_Answer <97771966+blankanswer@users.noreply.github.com>
1 parent 2469ecc commit 99d3919

4 files changed

Lines changed: 439 additions & 37 deletions

File tree

internal/cbm/cbm.c

Lines changed: 119 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -849,6 +849,106 @@ static bool cbm_line_contains(const char *src, int src_len, uint32_t line, const
849849
return false;
850850
}
851851

852+
static bool cbm_identifier_char(char c) {
853+
return isalnum((unsigned char)c) || c == '_';
854+
}
855+
856+
/* Verify that the mapped original span contains callable-definition syntax,
857+
* not merely the name at a macro invocation or call site. */
858+
static bool cbm_span_contains_callable_def(const char *src, int src_len, uint32_t start_line,
859+
uint32_t end_line, const char *name) {
860+
if (!src || src_len <= 0 || !name || !name[0] || start_line == 0 || end_line < start_line) {
861+
return false;
862+
}
863+
int span_start = 0;
864+
uint32_t line = 1;
865+
while (span_start < src_len && line < start_line) {
866+
if (src[span_start++] == '\n') {
867+
line++;
868+
}
869+
}
870+
if (line != start_line) {
871+
return false;
872+
}
873+
int span_end = span_start;
874+
while (span_end < src_len && line <= end_line) {
875+
if (src[span_end++] == '\n') {
876+
line++;
877+
}
878+
}
879+
size_t name_len = strlen(name);
880+
for (int pos = span_start; pos + (int)name_len <= span_end; pos++) {
881+
if (strncmp(src + pos, name, name_len) != 0 ||
882+
(pos > 0 && cbm_identifier_char(src[pos - 1])) ||
883+
(pos + (int)name_len < src_len && cbm_identifier_char(src[pos + name_len]))) {
884+
continue;
885+
}
886+
int before = pos;
887+
while (before > span_start && isspace((unsigned char)src[before - 1])) {
888+
before--;
889+
}
890+
if (before > span_start && strchr("(,=!?[.", src[before - 1])) {
891+
continue;
892+
}
893+
int open = pos + (int)name_len;
894+
while (open < span_end && isspace((unsigned char)src[open])) {
895+
open++;
896+
}
897+
if (open >= span_end || src[open] != '(') {
898+
continue;
899+
}
900+
int depth = 0;
901+
int close = -1;
902+
for (int i = open; i < span_end; i++) {
903+
if (src[i] == '(') {
904+
depth++;
905+
} else if (src[i] == ')' && --depth == 0) {
906+
close = i;
907+
break;
908+
}
909+
}
910+
if (close < 0) {
911+
continue;
912+
}
913+
for (int i = close + 1; i < span_end; i++) {
914+
if (src[i] == '{') {
915+
return true;
916+
}
917+
if (src[i] == ';') {
918+
break;
919+
}
920+
}
921+
}
922+
return false;
923+
}
924+
925+
/* Remap an expanded-source definition back to the original input file. Every
926+
* line in the definition must be attributable to the main file; generated
927+
* macro bodies, included headers, and ambiguous spans fail closed. */
928+
static bool cbm_remap_preprocessed_def(CBMDefinition *def, const CBMPreprocessedSource *pp) {
929+
if (!def || !pp || !pp->original_line_by_expanded_line || !pp->belongs_to_main_file ||
930+
def->start_line == 0 || def->end_line < def->start_line ||
931+
def->end_line > (uint32_t)pp->expanded_line_count) {
932+
return false;
933+
}
934+
935+
uint32_t original_start = pp->original_line_by_expanded_line[def->start_line];
936+
uint32_t original_end = pp->original_line_by_expanded_line[def->end_line];
937+
if (!original_start || !original_end || original_end < original_start) {
938+
return false;
939+
}
940+
for (uint32_t line = def->start_line; line <= def->end_line; line++) {
941+
if (!pp->belongs_to_main_file[line] || !pp->original_line_by_expanded_line[line]) {
942+
return false;
943+
}
944+
}
945+
946+
def->start_line = original_start;
947+
def->end_line = original_end;
948+
def->lines = (int)(original_end - original_start + 1);
949+
return true;
950+
}
951+
852952
static void cbm_subtract_recovered_regions(cbm_error_regions_t *regs, const CBMDefArray *defs) {
853953
int kept = 0;
854954
for (int i = 0; i < regs->count; i++) {
@@ -1088,9 +1188,10 @@ static CBMFileResult *cbm_extract_file_impl(const char *source, int source_len,
10881188
// Defs keep original-source line numbers; only CALLS are extracted from expanded source.
10891189
if (language == CBM_LANG_C || language == CBM_LANG_CPP || language == CBM_LANG_CUDA) {
10901190
uint64_t pp_start = now_ns();
1091-
char *expanded = cbm_preprocess(source, source_len, rel_path, extra_defines, include_paths,
1092-
language != CBM_LANG_C);
1093-
if (expanded) {
1191+
CBMPreprocessedSource *preprocessed = cbm_preprocess_with_map(
1192+
source, source_len, rel_path, extra_defines, include_paths, language != CBM_LANG_C);
1193+
if (preprocessed && preprocessed->source) {
1194+
char *expanded = preprocessed->source;
10941195
int expanded_len = (int)strlen(expanded);
10951196
// Record calls count before second pass
10961197
int calls_before = result->calls.count;
@@ -1155,15 +1256,20 @@ static CBMFileResult *cbm_extract_file_impl(const char *source, int source_len,
11551256
for (int i = defs_before; i < result->defs.count; i++) {
11561257
CBMDefinition *d = &result->defs.items[i];
11571258
bool adopt = false;
1158-
for (int rj = 0; rj < raw_regs.count && !adopt; rj++) {
1159-
if (d->start_line <= raw_regs.ends[rj] &&
1160-
d->end_line >= raw_regs.starts[rj]) {
1161-
adopt = true;
1259+
if (cbm_remap_preprocessed_def(d, preprocessed)) {
1260+
for (int rj = 0; rj < raw_regs.count && !adopt; rj++) {
1261+
if (d->start_line <= raw_regs.ends[rj] &&
1262+
d->end_line >= raw_regs.starts[rj]) {
1263+
adopt = true;
1264+
}
11621265
}
11631266
}
1164-
if (adopt &&
1165-
(!d->name || !cbm_line_contains(source, source_len,
1166-
d->start_line, d->name))) {
1267+
if (adopt && (!d->name ||
1268+
!cbm_line_contains(source, source_len, d->start_line,
1269+
d->name) ||
1270+
!cbm_span_contains_callable_def(
1271+
source, source_len, d->start_line, d->end_line,
1272+
d->name))) {
11671273
adopt = false;
11681274
}
11691275
for (int j = 0; j < defs_before && adopt; j++) {
@@ -1184,9 +1290,11 @@ static CBMFileResult *cbm_extract_file_impl(const char *source, int source_len,
11841290
ts_tree_delete(pp_tree);
11851291
}
11861292
}
1187-
cbm_preprocess_free(expanded);
1293+
cbm_preprocessed_source_free(preprocessed);
11881294
atomic_fetch_add(&total_files_preprocessed, 1);
11891295
(void)calls_before; // used for future logging
1296+
} else {
1297+
cbm_preprocessed_source_free(preprocessed);
11901298
}
11911299
atomic_fetch_add(&total_preprocess_ns, now_ns() - pp_start);
11921300
}

internal/cbm/preprocessor.cpp

Lines changed: 166 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -10,45 +10,145 @@
1010
#include <vector>
1111
#include <cstring>
1212
#include <cstdlib>
13+
#include <cstdint>
1314

1415
extern "C" {
1516

16-
char* cbm_preprocess(
17-
const char* source, int source_len,
18-
const char* filename,
19-
const char** extra_defines,
20-
const char** include_paths,
21-
int cpp_mode
22-
) {
23-
if (!source || source_len <= 0) return NULL;
24-
25-
// Fast-path: skip if no preprocessor directives worth expanding.
26-
bool has_macros = false;
17+
static bool has_preprocessor_work(const char *source, int source_len) {
18+
if (!source || source_len <= 0) {
19+
return false;
20+
}
2721
for (int i = 0; i < source_len - 1; i++) {
2822
if (source[i] == '#') {
2923
// Skip whitespace after #
3024
int j = i + 1;
31-
while (j < source_len && (source[j] == ' ' || source[j] == '\t')) j++;
25+
while (j < source_len && (source[j] == ' ' || source[j] == '\t'))
26+
j++;
3227
int remaining = source_len - j;
3328
if (remaining >= 6 && strncmp(source + j, "define", 6) == 0) {
34-
has_macros = true;
35-
break;
29+
return true;
3630
}
3731
if (remaining >= 5 && strncmp(source + j, "ifdef", 5) == 0) {
38-
has_macros = true;
39-
break;
32+
return true;
4033
}
4134
if (remaining >= 6 && strncmp(source + j, "ifndef", 6) == 0) {
42-
has_macros = true;
43-
break;
35+
return true;
4436
}
4537
if (remaining >= 3 && strncmp(source + j, "if ", 3) == 0) {
46-
has_macros = true;
47-
break;
38+
return true;
4839
}
4940
}
5041
}
51-
if (!has_macros) return NULL; // NULL = no expansion needed, use original
42+
return false;
43+
}
44+
45+
static int count_expanded_lines(const std::string &text) {
46+
int count = 1;
47+
for (char c : text) {
48+
if (c == '\n') {
49+
count++;
50+
}
51+
}
52+
return count;
53+
}
54+
55+
static bool parse_line_directive(const char *line, size_t len, uint32_t *out_line,
56+
std::string *out_file) {
57+
size_t i = 0;
58+
while (i < len && (line[i] == ' ' || line[i] == '\t')) {
59+
i++;
60+
}
61+
if (i >= len || line[i++] != '#') {
62+
return false;
63+
}
64+
while (i < len && (line[i] == ' ' || line[i] == '\t')) {
65+
i++;
66+
}
67+
static const char prefix[] = "line";
68+
if (i + sizeof(prefix) - 1 > len || strncmp(line + i, prefix, sizeof(prefix) - 1) != 0) {
69+
return false;
70+
}
71+
i += sizeof(prefix) - 1;
72+
if (i >= len || (line[i] != ' ' && line[i] != '\t')) {
73+
return false;
74+
}
75+
while (i < len && (line[i] == ' ' || line[i] == '\t')) {
76+
i++;
77+
}
78+
if (i >= len || line[i] < '0' || line[i] > '9') {
79+
return false;
80+
}
81+
uint64_t parsed_line = 0;
82+
while (i < len && line[i] >= '0' && line[i] <= '9') {
83+
parsed_line = parsed_line * 10u + (uint64_t)(line[i] - '0');
84+
if (parsed_line > UINT32_MAX) {
85+
return false;
86+
}
87+
i++;
88+
}
89+
while (i < len && (line[i] == ' ' || line[i] == '\t')) {
90+
i++;
91+
}
92+
if (i >= len || line[i++] != '"') {
93+
return false;
94+
}
95+
size_t file_start = i;
96+
while (i < len && line[i] != '"') {
97+
i++;
98+
}
99+
if (i >= len) {
100+
return false;
101+
}
102+
*out_line = (uint32_t)parsed_line;
103+
*out_file = std::string(line + file_start, i - file_start);
104+
return true;
105+
}
106+
107+
static bool build_line_map(const std::string &expanded, const std::string &main_file,
108+
uint32_t *original_line_by_expanded_line,
109+
uint8_t *belongs_to_main_file) {
110+
std::string current_file = main_file;
111+
uint32_t current_line = 1;
112+
int expanded_line = 1;
113+
size_t line_start = 0;
114+
115+
while (line_start <= expanded.size()) {
116+
size_t line_end = expanded.find('\n', line_start);
117+
if (line_end == std::string::npos) {
118+
line_end = expanded.size();
119+
}
120+
121+
uint32_t directive_line = 0;
122+
std::string directive_file;
123+
if (parse_line_directive(expanded.c_str() + line_start, line_end - line_start,
124+
&directive_line, &directive_file)) {
125+
current_file = directive_file;
126+
current_line = directive_line;
127+
original_line_by_expanded_line[expanded_line] = 0;
128+
belongs_to_main_file[expanded_line] = 0;
129+
} else {
130+
original_line_by_expanded_line[expanded_line] = current_line;
131+
belongs_to_main_file[expanded_line] = current_file == main_file ? 1 : 0;
132+
if (current_line < UINT32_MAX) {
133+
current_line++;
134+
}
135+
}
136+
137+
if (line_end == expanded.size()) {
138+
break;
139+
}
140+
line_start = line_end + 1;
141+
expanded_line++;
142+
}
143+
return true;
144+
}
145+
146+
CBMPreprocessedSource *cbm_preprocess_with_map(const char *source, int source_len,
147+
const char *filename, const char **extra_defines,
148+
const char **include_paths, int cpp_mode) {
149+
if (!has_preprocessor_work(source, source_len)) {
150+
return NULL; // NULL = no expansion needed, use original
151+
}
52152

53153
try {
54154
simplecpp::DUI dui;
@@ -78,18 +178,58 @@ char* cbm_preprocess(
78178
// Clean up loaded file data
79179
simplecpp::cleanup(filedata);
80180

81-
char* out = (char*)malloc(result.size() + 1);
82-
if (!out) return NULL;
83-
memcpy(out, result.c_str(), result.size() + 1);
84-
return out;
181+
CBMPreprocessedSource *pp = (CBMPreprocessedSource *)calloc(1, sizeof(*pp));
182+
if (!pp) {
183+
return NULL;
184+
}
185+
int line_count = count_expanded_lines(result);
186+
pp->source = (char *)malloc(result.size() + 1);
187+
pp->original_line_by_expanded_line =
188+
(uint32_t *)calloc((size_t)line_count + 1u, sizeof(uint32_t));
189+
pp->belongs_to_main_file = (uint8_t *)calloc((size_t)line_count + 1u, sizeof(uint8_t));
190+
pp->expanded_line_count = line_count;
191+
if (!pp->source || !pp->original_line_by_expanded_line || !pp->belongs_to_main_file) {
192+
cbm_preprocessed_source_free(pp);
193+
return NULL;
194+
}
195+
memcpy(pp->source, result.c_str(), result.size() + 1);
196+
if (!build_line_map(result, files[0], pp->original_line_by_expanded_line,
197+
pp->belongs_to_main_file)) {
198+
cbm_preprocessed_source_free(pp);
199+
return NULL;
200+
}
201+
return pp;
85202
} catch (...) {
86203
// Graceful fallback: return NULL = use original source
87204
return NULL;
88205
}
89206
}
90207

91-
void cbm_preprocess_free(char* expanded) {
208+
char *cbm_preprocess(const char *source, int source_len, const char *filename,
209+
const char **extra_defines, const char **include_paths, int cpp_mode) {
210+
CBMPreprocessedSource *pp = cbm_preprocess_with_map(source, source_len, filename, extra_defines,
211+
include_paths, cpp_mode);
212+
if (!pp) {
213+
return NULL;
214+
}
215+
char *out = pp->source;
216+
pp->source = NULL;
217+
cbm_preprocessed_source_free(pp);
218+
return out;
219+
}
220+
221+
void cbm_preprocess_free(char *expanded) {
92222
free(expanded);
93223
}
94224

225+
void cbm_preprocessed_source_free(CBMPreprocessedSource *pp) {
226+
if (!pp) {
227+
return;
228+
}
229+
free(pp->source);
230+
free(pp->original_line_by_expanded_line);
231+
free(pp->belongs_to_main_file);
232+
free(pp);
233+
}
234+
95235
} // extern "C"

0 commit comments

Comments
 (0)