Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
130 changes: 119 additions & 11 deletions internal/cbm/cbm.c
Original file line number Diff line number Diff line change
Expand Up @@ -849,6 +849,106 @@ static bool cbm_line_contains(const char *src, int src_len, uint32_t line, const
return false;
}

static bool cbm_identifier_char(char c) {
return isalnum((unsigned char)c) || c == '_';
}

/* Verify that the mapped original span contains callable-definition syntax,
* not merely the name at a macro invocation or call site. */
static bool cbm_span_contains_callable_def(const char *src, int src_len, uint32_t start_line,
uint32_t end_line, const char *name) {
if (!src || src_len <= 0 || !name || !name[0] || start_line == 0 || end_line < start_line) {
return false;
}
int span_start = 0;
uint32_t line = 1;
while (span_start < src_len && line < start_line) {
if (src[span_start++] == '\n') {
line++;
}
}
if (line != start_line) {
return false;
}
int span_end = span_start;
while (span_end < src_len && line <= end_line) {
if (src[span_end++] == '\n') {
line++;
}
}
size_t name_len = strlen(name);
for (int pos = span_start; pos + (int)name_len <= span_end; pos++) {
if (strncmp(src + pos, name, name_len) != 0 ||
(pos > 0 && cbm_identifier_char(src[pos - 1])) ||
(pos + (int)name_len < src_len && cbm_identifier_char(src[pos + name_len]))) {
continue;
}
int before = pos;
while (before > span_start && isspace((unsigned char)src[before - 1])) {
before--;
}
if (before > span_start && strchr("(,=!?[.", src[before - 1])) {
continue;
}
int open = pos + (int)name_len;
while (open < span_end && isspace((unsigned char)src[open])) {
open++;
}
if (open >= span_end || src[open] != '(') {
continue;
}
int depth = 0;
int close = -1;
for (int i = open; i < span_end; i++) {
if (src[i] == '(') {
depth++;
} else if (src[i] == ')' && --depth == 0) {
close = i;
break;
}
}
if (close < 0) {
continue;
}
for (int i = close + 1; i < span_end; i++) {
if (src[i] == '{') {
return true;
}
if (src[i] == ';') {
break;
}
}
}
return false;
}

/* Remap an expanded-source definition back to the original input file. Every
* line in the definition must be attributable to the main file; generated
* macro bodies, included headers, and ambiguous spans fail closed. */
static bool cbm_remap_preprocessed_def(CBMDefinition *def, const CBMPreprocessedSource *pp) {
if (!def || !pp || !pp->original_line_by_expanded_line || !pp->belongs_to_main_file ||
def->start_line == 0 || def->end_line < def->start_line ||
def->end_line > (uint32_t)pp->expanded_line_count) {
return false;
}

uint32_t original_start = pp->original_line_by_expanded_line[def->start_line];
uint32_t original_end = pp->original_line_by_expanded_line[def->end_line];
if (!original_start || !original_end || original_end < original_start) {
return false;
}
for (uint32_t line = def->start_line; line <= def->end_line; line++) {
if (!pp->belongs_to_main_file[line] || !pp->original_line_by_expanded_line[line]) {
return false;
}
}

def->start_line = original_start;
def->end_line = original_end;
def->lines = (int)(original_end - original_start + 1);
return true;
}

static void cbm_subtract_recovered_regions(cbm_error_regions_t *regs, const CBMDefArray *defs) {
int kept = 0;
for (int i = 0; i < regs->count; i++) {
Expand Down Expand Up @@ -1088,9 +1188,10 @@ static CBMFileResult *cbm_extract_file_impl(const char *source, int source_len,
// Defs keep original-source line numbers; only CALLS are extracted from expanded source.
if (language == CBM_LANG_C || language == CBM_LANG_CPP || language == CBM_LANG_CUDA) {
uint64_t pp_start = now_ns();
char *expanded = cbm_preprocess(source, source_len, rel_path, extra_defines, include_paths,
language != CBM_LANG_C);
if (expanded) {
CBMPreprocessedSource *preprocessed = cbm_preprocess_with_map(
source, source_len, rel_path, extra_defines, include_paths, language != CBM_LANG_C);
if (preprocessed && preprocessed->source) {
char *expanded = preprocessed->source;
int expanded_len = (int)strlen(expanded);
// Record calls count before second pass
int calls_before = result->calls.count;
Expand Down Expand Up @@ -1155,15 +1256,20 @@ static CBMFileResult *cbm_extract_file_impl(const char *source, int source_len,
for (int i = defs_before; i < result->defs.count; i++) {
CBMDefinition *d = &result->defs.items[i];
bool adopt = false;
for (int rj = 0; rj < raw_regs.count && !adopt; rj++) {
if (d->start_line <= raw_regs.ends[rj] &&
d->end_line >= raw_regs.starts[rj]) {
adopt = true;
if (cbm_remap_preprocessed_def(d, preprocessed)) {
for (int rj = 0; rj < raw_regs.count && !adopt; rj++) {
if (d->start_line <= raw_regs.ends[rj] &&
d->end_line >= raw_regs.starts[rj]) {
adopt = true;
}
}
}
if (adopt &&
(!d->name || !cbm_line_contains(source, source_len,
d->start_line, d->name))) {
if (adopt && (!d->name ||
!cbm_line_contains(source, source_len, d->start_line,
d->name) ||
!cbm_span_contains_callable_def(
source, source_len, d->start_line, d->end_line,
d->name))) {
adopt = false;
}
for (int j = 0; j < defs_before && adopt; j++) {
Expand All @@ -1184,9 +1290,11 @@ static CBMFileResult *cbm_extract_file_impl(const char *source, int source_len,
ts_tree_delete(pp_tree);
}
}
cbm_preprocess_free(expanded);
cbm_preprocessed_source_free(preprocessed);
atomic_fetch_add(&total_files_preprocessed, 1);
(void)calls_before; // used for future logging
} else {
cbm_preprocessed_source_free(preprocessed);
}
atomic_fetch_add(&total_preprocess_ns, now_ns() - pp_start);
}
Expand Down
192 changes: 166 additions & 26 deletions internal/cbm/preprocessor.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -10,45 +10,145 @@
#include <vector>
#include <cstring>
#include <cstdlib>
#include <cstdint>

extern "C" {

char* cbm_preprocess(
const char* source, int source_len,
const char* filename,
const char** extra_defines,
const char** include_paths,
int cpp_mode
) {
if (!source || source_len <= 0) return NULL;

// Fast-path: skip if no preprocessor directives worth expanding.
bool has_macros = false;
static bool has_preprocessor_work(const char *source, int source_len) {
if (!source || source_len <= 0) {
return false;
}
for (int i = 0; i < source_len - 1; i++) {
if (source[i] == '#') {
// Skip whitespace after #
int j = i + 1;
while (j < source_len && (source[j] == ' ' || source[j] == '\t')) j++;
while (j < source_len && (source[j] == ' ' || source[j] == '\t'))
j++;
int remaining = source_len - j;
if (remaining >= 6 && strncmp(source + j, "define", 6) == 0) {
has_macros = true;
break;
return true;
}
if (remaining >= 5 && strncmp(source + j, "ifdef", 5) == 0) {
has_macros = true;
break;
return true;
}
if (remaining >= 6 && strncmp(source + j, "ifndef", 6) == 0) {
has_macros = true;
break;
return true;
}
if (remaining >= 3 && strncmp(source + j, "if ", 3) == 0) {
has_macros = true;
break;
return true;
}
}
}
if (!has_macros) return NULL; // NULL = no expansion needed, use original
return false;
}

static int count_expanded_lines(const std::string &text) {
int count = 1;
for (char c : text) {
if (c == '\n') {
count++;
}
}
return count;
}

static bool parse_line_directive(const char *line, size_t len, uint32_t *out_line,
std::string *out_file) {
size_t i = 0;
while (i < len && (line[i] == ' ' || line[i] == '\t')) {
i++;
}
if (i >= len || line[i++] != '#') {
return false;
}
while (i < len && (line[i] == ' ' || line[i] == '\t')) {
i++;
}
static const char prefix[] = "line";
if (i + sizeof(prefix) - 1 > len || strncmp(line + i, prefix, sizeof(prefix) - 1) != 0) {
return false;
}
i += sizeof(prefix) - 1;
if (i >= len || (line[i] != ' ' && line[i] != '\t')) {
return false;
}
while (i < len && (line[i] == ' ' || line[i] == '\t')) {
i++;
}
if (i >= len || line[i] < '0' || line[i] > '9') {
return false;
}
uint64_t parsed_line = 0;
while (i < len && line[i] >= '0' && line[i] <= '9') {
parsed_line = parsed_line * 10u + (uint64_t)(line[i] - '0');
if (parsed_line > UINT32_MAX) {
return false;
}
i++;
}
while (i < len && (line[i] == ' ' || line[i] == '\t')) {
i++;
}
if (i >= len || line[i++] != '"') {
return false;
}
size_t file_start = i;
while (i < len && line[i] != '"') {
i++;
}
if (i >= len) {
return false;
}
*out_line = (uint32_t)parsed_line;
*out_file = std::string(line + file_start, i - file_start);
return true;
}

static bool build_line_map(const std::string &expanded, const std::string &main_file,
uint32_t *original_line_by_expanded_line,
uint8_t *belongs_to_main_file) {
std::string current_file = main_file;
uint32_t current_line = 1;
int expanded_line = 1;
size_t line_start = 0;

while (line_start <= expanded.size()) {
size_t line_end = expanded.find('\n', line_start);
if (line_end == std::string::npos) {
line_end = expanded.size();
}

uint32_t directive_line = 0;
std::string directive_file;
if (parse_line_directive(expanded.c_str() + line_start, line_end - line_start,
&directive_line, &directive_file)) {
current_file = directive_file;
current_line = directive_line;
original_line_by_expanded_line[expanded_line] = 0;
belongs_to_main_file[expanded_line] = 0;
} else {
original_line_by_expanded_line[expanded_line] = current_line;
belongs_to_main_file[expanded_line] = current_file == main_file ? 1 : 0;
if (current_line < UINT32_MAX) {
current_line++;
}
}

if (line_end == expanded.size()) {
break;
}
line_start = line_end + 1;
expanded_line++;
}
return true;
}

CBMPreprocessedSource *cbm_preprocess_with_map(const char *source, int source_len,
const char *filename, const char **extra_defines,
const char **include_paths, int cpp_mode) {
if (!has_preprocessor_work(source, source_len)) {
return NULL; // NULL = no expansion needed, use original
}

try {
simplecpp::DUI dui;
Expand Down Expand Up @@ -78,18 +178,58 @@ char* cbm_preprocess(
// Clean up loaded file data
simplecpp::cleanup(filedata);

char* out = (char*)malloc(result.size() + 1);
if (!out) return NULL;
memcpy(out, result.c_str(), result.size() + 1);
return out;
CBMPreprocessedSource *pp = (CBMPreprocessedSource *)calloc(1, sizeof(*pp));
if (!pp) {
return NULL;
}
int line_count = count_expanded_lines(result);
pp->source = (char *)malloc(result.size() + 1);
pp->original_line_by_expanded_line =
(uint32_t *)calloc((size_t)line_count + 1u, sizeof(uint32_t));
pp->belongs_to_main_file = (uint8_t *)calloc((size_t)line_count + 1u, sizeof(uint8_t));
pp->expanded_line_count = line_count;
if (!pp->source || !pp->original_line_by_expanded_line || !pp->belongs_to_main_file) {
cbm_preprocessed_source_free(pp);
return NULL;
}
memcpy(pp->source, result.c_str(), result.size() + 1);
if (!build_line_map(result, files[0], pp->original_line_by_expanded_line,
pp->belongs_to_main_file)) {
cbm_preprocessed_source_free(pp);
return NULL;
}
return pp;
} catch (...) {
// Graceful fallback: return NULL = use original source
return NULL;
}
}

void cbm_preprocess_free(char* expanded) {
char *cbm_preprocess(const char *source, int source_len, const char *filename,
const char **extra_defines, const char **include_paths, int cpp_mode) {
CBMPreprocessedSource *pp = cbm_preprocess_with_map(source, source_len, filename, extra_defines,
include_paths, cpp_mode);
if (!pp) {
return NULL;
}
char *out = pp->source;
pp->source = NULL;
cbm_preprocessed_source_free(pp);
return out;
}

void cbm_preprocess_free(char *expanded) {
free(expanded);
}

void cbm_preprocessed_source_free(CBMPreprocessedSource *pp) {
if (!pp) {
return;
}
free(pp->source);
free(pp->original_line_by_expanded_line);
free(pp->belongs_to_main_file);
free(pp);
}

} // extern "C"
Loading
Loading