// I think this file is too big for GCC to handle properly, there are curious false-positive analyzer warnings // that didn't appear before adding preproc_eval #include "preproc.h" #include #include #include "cstring.h" #include "khash.h" #include "machine.h" #include "prepare.h" //#define LOG_OPEN //#define LOG_INCLUDE //#define LOG_CLOSE typedef struct mtoken_s { enum mtoken_e { MTOK_TOKEN, MTOK_ARG, MTOK_STRINGIFY, MTOK_CONCAT, } typ; union { preproc_token_t tok; unsigned argid; struct { struct mtoken_s *l, *r; } concat; } val; } mtoken_t; static void mtoken_del(mtoken_t *tok) { switch (tok->typ) { case MTOK_TOKEN: preproc_token_del(&tok->val.tok); free(tok); return; case MTOK_CONCAT: mtoken_del(tok->val.concat.l); mtoken_del(tok->val.concat.r); free(tok); return; case MTOK_ARG: case MTOK_STRINGIFY: free(tok); return; } } KHASH_MAP_INIT_STR(argid_map, unsigned) static void argid_map_del(khash_t(argid_map) *args) { kh_cstr_t str; #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wcast-qual" kh_foreach_key(args, str, free((void*)str)) #pragma GCC diagnostic pop kh_destroy(argid_map, args); } static mtoken_t *mtoken_new_token(preproc_token_t tok) { mtoken_t *ret = malloc(sizeof *ret); if (!ret) return NULL; ret->typ = MTOK_TOKEN; ret->val.tok = tok; return ret; } static mtoken_t *mtoken_new_arg(unsigned argid, int as_string) { mtoken_t *ret = malloc(sizeof *ret); if (!ret) return NULL; ret->typ = as_string ? MTOK_STRINGIFY : MTOK_ARG; ret->val.argid = argid; return ret; } static mtoken_t *mtoken_new_concat(mtoken_t *l, mtoken_t *r) { // Takes ownership of l and r mtoken_t *ret = malloc(sizeof *ret); if (!ret) { mtoken_del(l); mtoken_del(r); return NULL; } ret->typ = MTOK_CONCAT; ret->val.concat.l = l; ret->val.concat.r = r; return ret; } static inline void macro_tok_print(mtoken_t *m) { switch (m->typ) { case MTOK_TOKEN: printf("token type %u", m->val.tok.tokt); if (m->val.tok.tokt == PPTOK_IDENT) printf(" IDENT '%s'", string_content(m->val.tok.tokv.str)); if (m->val.tok.tokt == PPTOK_NUM ) printf(" NUM %s", string_content(m->val.tok.tokv.str)); if (m->val.tok.tokt == PPTOK_SYM) printf(" SYM %s", sym2str[m->val.tok.tokv.sym]); return; case MTOK_ARG: printf("argument %u", m->val.argid); return; case MTOK_CONCAT: printf("concat {"); macro_tok_print(m->val.concat.l); printf("} {"); macro_tok_print(m->val.concat.r); printf("}"); return; case MTOK_STRINGIFY: printf("string argument %u", m->val.argid); return; } } VECTOR_DECLARE_STATIC(mtoken, mtoken_t*) #define mtoken_ptr_del(m) mtoken_del(*(m)) VECTOR_IMPL_STATIC(mtoken, mtoken_ptr_del) #undef mtoken_ptr_del typedef struct macro_s { int is_funlike; int has_varargs; unsigned nargs; VECTOR(mtoken) *toks; } macro_t; KHASH_MAP_INIT_STR(macros_map, macro_t) KHASH_SET_INIT_STR(string_set) static void macro_del(macro_t *m) { vector_del(mtoken, m->toks); } static void macros_map_del(khash_t(macros_map) *args) { kh_cstr_t str; macro_t *it; #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wcast-qual" kh_foreach_key_value_ref(args, str, it, free((void*)str); macro_del(it)) #pragma GCC diagnostic pop kh_destroy(macros_map, args); } static void macros_set_del(khash_t(string_set) *strset) { kh_cstr_t str; #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wcast-qual" kh_foreach_key(strset, str, free((void*)str)) #pragma GCC diagnostic pop kh_destroy(string_set, strset); } typedef struct ppsource_s { enum ppsrc_e { PPSRC_PREPARE = 0, PPSRC_PPTOKEN, PPSRC_PPTOKENS, // Currently, only pushed after macro expansion when the expansion is not empty PPSRC_PTOKEN, } srct; union { struct { prepare_t *st; char *old_dirname; char *old_filename; _Bool was_sys; size_t old_pathno; // Note: char is used for the depths, which gives a max depth of 256 #ifs before the program silently bugs unsigned char entered_next_ok_cond; unsigned char ok_depth, cond_depth; } prep; preproc_token_t pptok; struct { VECTOR(preproc) *toks; // Cannot contain a #if, #else, ... statement, they *must* come from a prepare_t size_t idx; } pptoks; proc_token_t ptok; // Added by proc_unget_token } srcv; } ppsource_t; static void ppsource_del(ppsource_t *src) { switch (src->srct) { case PPSRC_PREPARE: // st may be NULL if we failed to create the prepare_t* in preproc_new_file() if (src->srcv.prep.st) prepare_del(src->srcv.prep.st); if (src->srcv.prep.old_dirname) free(src->srcv.prep.old_dirname); if (src->srcv.prep.old_filename) free(src->srcv.prep.old_filename); return; case PPSRC_PPTOKEN: preproc_token_del(&src->srcv.pptok); return; case PPSRC_PPTOKENS: // Do not delete the tokens that escaped, only the remaining tokens and the vector vector_del_free_from(preproc, src->srcv.pptoks.toks, src->srcv.pptoks.idx); return; case PPSRC_PTOKEN: proc_token_del(&src->srcv.ptok); return; } } #define PREPARE_NEW_FILE(f, fn, old_fn, old_dn, was_sys_, old_no) (ppsource_t){ .srct = PPSRC_PREPARE, \ .srcv.prep = { .st = prepare_new_file((f), (fn)), .old_dirname = (old_dn), .old_filename = (old_fn), .was_sys = was_sys_, \ .old_pathno = old_no, .entered_next_ok_cond = 0, .ok_depth = 0, .cond_depth = 0 } } VECTOR_DECLARE_STATIC(ppsource, ppsource_t) VECTOR_IMPL_STATIC(ppsource, ppsource_del) struct preproc_s { machine_t *target; VECTOR(ppsource) *prep; VECTOR(ccharp) *pragma_once; enum preproc_state_e { PPST_NONE, PPST_NL, PPST_PRAGMA_EXPLICIT, } st; khash_t(macros_map) *macros_map; khash_t(string_set) *macros_defined, *macros_used; char *dirname; char *cur_file; _Bool is_sys; size_t cur_pathno; }; void preproc_del(preproc_t *src) { vector_del(ppsource, src->prep); macros_set_del(src->macros_used); macros_set_del(src->macros_defined); macros_map_del(src->macros_map); if (src->dirname) free(src->dirname); if (src->cur_file) free(src->cur_file); vector_del(ccharp, src->pragma_once); free(src); } static preproc_token_t ppsrc_next_token(preproc_t *src) { switch (vector_last(ppsource, src->prep).srct) { case PPSRC_PREPARE: return pre_next_token(vector_last(ppsource, src->prep).srcv.prep.st, 0); case PPSRC_PPTOKEN: { preproc_token_t ret = vector_last(ppsource, src->prep).srcv.pptok; vector_pop_nodel(ppsource, src->prep); return ret; } case PPSRC_PPTOKENS: { ppsource_t *tmp = &vector_last(ppsource, src->prep); preproc_token_t ret = vector_content(preproc, tmp->srcv.pptoks.toks)[tmp->srcv.pptoks.idx++]; if (tmp->srcv.pptoks.idx >= vector_size(preproc, tmp->srcv.pptoks.toks)) { vector_del_freed(preproc, tmp->srcv.pptoks.toks); vector_pop_nodel(ppsource, src->prep); } return ret; } case PPSRC_PTOKEN: default: return (preproc_token_t){ .tokt = PPTOK_INVALID, .tokv.c = 0 }; } } static int try_open_dir(preproc_t *src, string_t *filename) { size_t fnlen = string_len(filename); size_t incl_len = src->dirname ? strlen(src->dirname) : 1; char *fn = malloc(incl_len + fnlen + 2); if (!fn) return 0; if (src->dirname) { memcpy(fn, src->dirname, incl_len); fn[incl_len] = '/'; } else { fn[0] = '.'; fn[1] = '/'; } strcpy(fn + incl_len + 1, string_content(filename)); int has_once = 0; vector_for(ccharp, fname, src->pragma_once) { if (!strcmp(*fname, fn)) { has_once = 1; break; } } if (has_once) { #ifdef LOG_INCLUDE printf("Skipping opening %s as cur header: '%s' in pragma_once\n", string_content(filename), fn); #endif free(fn); return 1; } else { FILE *f = fopen(fn, "r"); #ifdef LOG_OPEN printf("Trying %s: %p\n", fn, f); #endif if (f) { char *new_dirname = strchr(fn, '/') ? strndup(fn, (size_t)(strrchr(fn, '/') - fn)) : NULL; int ret = vector_push(ppsource, src->prep, PREPARE_NEW_FILE(f, fn, src->cur_file, src->dirname, src->is_sys, src->cur_pathno)); if (ret) { src->is_sys = 0; src->cur_file = fn; src->dirname = new_dirname; src->cur_pathno = 0; } return ret; } else { free(fn); return 0; } } } static int try_open_sys(preproc_t *src, string_t *filename, size_t array_off) { size_t fnlen = string_len(filename); for (; array_off < src->target->npaths; ++array_off) { size_t incl_len = strlen(src->target->include_path[array_off]); char *fn = malloc(incl_len + fnlen + 2); if (!fn) return 0; memcpy(fn, src->target->include_path[array_off], incl_len); fn[incl_len] = '/'; strcpy(fn + incl_len + 1, string_content(filename)); int has_once = 0; vector_for(ccharp, fname, src->pragma_once) { if (!strcmp(*fname, fn)) { has_once = 1; break; } } if (has_once) { #ifdef LOG_INCLUDE printf("Skipping opening %s as system header: '%s' in pragma_once\n", string_content(filename), fn); #endif free(fn); return 1; } else { FILE *f = fopen(fn, "r"); #ifdef LOG_OPEN printf("Trying %s: %p\n", fn, f); #endif if (f) { char *new_dirname = strchr(fn, '/') ? strndup(fn, (size_t)(strrchr(fn, '/') - fn)) : NULL; int ret = vector_push(ppsource, src->prep, PREPARE_NEW_FILE(f, fn, src->cur_file, src->dirname, src->is_sys, src->cur_pathno)); if (ret) { src->is_sys = 1; src->cur_file = fn; src->dirname = new_dirname; src->cur_pathno = array_off + 1; } return ret; } free(fn); } } return 0; } preproc_t *preproc_new_file(machine_t *target, FILE *f, char *dirname, const char *filename) { preproc_t *ret = malloc(sizeof *ret); if (!ret) { fclose(f); return NULL; } ret->target = target; ret->macros_map = kh_init(macros_map); if (!ret->macros_map) { fclose(f); free(ret); return NULL; } ret->macros_defined = kh_init(string_set); if (!ret->macros_defined) { kh_destroy(macros_map, ret->macros_map); fclose(f); free(ret); return NULL; } ret->macros_used = kh_init(string_set); if (!ret->macros_used) { kh_destroy(macros_map, ret->macros_map); kh_destroy(string_set, ret->macros_defined); fclose(f); free(ret); return NULL; } ret->prep = vector_new_cap(ppsource, 1); if (!ret->prep) { kh_destroy(macros_map, ret->macros_map); kh_destroy(string_set, ret->macros_defined); kh_destroy(string_set, ret->macros_used); fclose(f); free(ret); return NULL; } ret->pragma_once = vector_new(ccharp); if (!ret->pragma_once) { vector_del(ppsource, ret->prep); kh_destroy(macros_map, ret->macros_map); kh_destroy(string_set, ret->macros_defined); kh_destroy(string_set, ret->macros_used); fclose(f); free(ret); return NULL; } ret->dirname = NULL; ret->cur_file = NULL; // ret can now be deleted by preproc_del // Include the first file if (!vector_push(ppsource, ret->prep, PREPARE_NEW_FILE(f, filename, NULL, NULL, 0, 0))) { preproc_del(ret); return NULL; } if (!vector_last(ppsource, ret->prep).srcv.prep.st) { preproc_del(ret); return NULL; } // Next finish setting up ret ret->st = PPST_NL; ret->is_sys = 0; ret->dirname = dirname; ret->cur_file = strdup(filename); ret->cur_pathno = 0; // Also include 'stdc-predef.h' (it will be parsed before the requested file) string_t *stdc_predef = string_new_cstr("stdc-predef.h"); if (!stdc_predef) { log_memory("failed to create new string 'stdc-predef.h'\n"); preproc_del(ret); return NULL; } if (!try_open_sys(ret, stdc_predef, 0)) { log_error_nopos("failed to open file 'stdc-predef.h'\n"); string_del(stdc_predef); preproc_del(ret); return NULL; } string_del(stdc_predef); return ret; } static void preprocs_del(VECTOR(preproc) **p) { if (!*p) return; vector_del(preproc, *p); } VECTOR_DECLARE_STATIC(preprocs, VECTOR(preproc)*) VECTOR_IMPL_STATIC(preprocs, preprocs_del) static VECTOR(preproc) *preproc_do_expand(loginfo_t *li, const khash_t(macros_map) *macros, const VECTOR(preproc) *toks, khash_t(string_set) *solved_macros, khash_t(string_set) *opt_used_macros); // Does not take any ownership, returns a vector with independent ownerships // opt_used_macros is NULL in regular expansion, non-NULL in #if-expansions static VECTOR(preproc) *preproc_solve_macro(loginfo_t *li, const khash_t(macros_map) *macros, char *mname, const macro_t *m, VECTOR(preprocs) *margs, khash_t(string_set) *solved_macros, khash_t(string_set) *opt_used_macros); // Moves mname to solved_macros or frees mname, returns a vector with independent ownerships // margs may be NULL if m->is_funlike is false // May change margs if m->has_varargs, but takes no ownership // opt_used_macros is NULL in regular expansion, non-NULL in #if-expansions static VECTOR(preproc) *preproc_solve_macro(loginfo_t *li, const khash_t(macros_map) *macros, char *mname, const macro_t *m, VECTOR(preprocs) *margs, khash_t(string_set) *solved_macros, khash_t(string_set) *opt_used_macros) { #define LOG_MEMORY(fmt, ...) log_memory(fmt " while expanding %s\n" __VA_ARGS__, mname) if (m->is_funlike && !margs) { log_internal(li, "m->is_funlike && !margs in preproc_solve_macro(... %s ...)\n", mname); free(mname); return NULL; } if (m->is_funlike && (m->nargs != vector_size(preprocs, margs)) // General case && (!m->has_varargs || (m->nargs > vector_size(preprocs, margs))) // Variadics && (m->nargs || ((vector_size(preprocs, margs) == 1) && (vector_size(preproc, vector_last(preprocs, margs)) == 0)))) { // Zero argument log_error(li, "invalid argument count for macro %s\n", mname); free(mname); return NULL; } if (m->has_varargs) { // Change margs to have the correct __VA_ARGS__ (argument m->nargs) if (m->nargs == vector_size(preprocs, margs)) { // No varargs, so add an empty one VECTOR(preproc) *marg = vector_new(preproc); if (!marg) { LOG_MEMORY("failed to create __VA_ARGS__"); free(mname); return NULL; } if (!vector_push(preprocs, margs, marg)) { LOG_MEMORY("failed to add __VA_ARGS__"); vector_del(preproc, marg); free(mname); return NULL; } } else if (m->nargs < vector_size(preprocs, margs) - 1) { // Too many arguments, merge them with commas VECTOR(preproc) *mvarg = vector_content(preprocs, margs)[m->nargs]; size_t size0 = vector_size(preproc, mvarg); vector_for_from(preprocs, it, margs, m->nargs + 1) { if (!vector_push(preproc, mvarg, ((preproc_token_t){.tokt = PPTOK_SYM, .tokv.sym = SYM_COMMA}))) { LOG_MEMORY("failed to add comma to __VA_ARGS__"); vector_pop_nodel_slice(preproc, mvarg, vector_size(preproc, mvarg) - size0); free(mname); return NULL; } if (!vector_push_vec(preproc, mvarg, *it)) { LOG_MEMORY("failed to add extra argument to __VA_ARGS__"); vector_pop_nodel_slice(preproc, mvarg, vector_size(preproc, mvarg) - size0); free(mname); return NULL; } } vector_pop_nodel_slice(preprocs, margs, vector_size(preprocs, margs) - m->nargs - 1); } } // Avoid 0-allocations VECTOR(preproc) **margs2 = calloc(margs ? (vector_size(preprocs, margs) ? vector_size(preprocs, margs) : 1) : 1, sizeof *margs2); if (!margs2) { LOG_MEMORY("failed to allocate expanded arguments array"); free(mname); return NULL; } VECTOR(preproc) *ret = vector_new(preproc); if (!ret) { LOG_MEMORY("failed to allocate return vector"); free(margs2); free(mname); return NULL; } VECTOR(mtoken) *st = vector_new_cap(mtoken, vector_size(mtoken, m->toks)); if (!st) { LOG_MEMORY("failed to allocate auxiliary vector"); vector_del(preproc, ret); free(margs2); free(mname); return NULL; } vector_for_rev(mtoken, mtok, m->toks) { vector_push(mtoken, st, *mtok); } int need_concat = 0, concat_cur = 0; while (vector_size(mtoken, st)) { mtoken_t *mtok = vector_last(mtoken, st); switch (mtok->typ) { case MTOK_CONCAT: { vector_last(mtoken, st) = mtok->val.concat.r; if (!vector_push(mtoken, st, mtok->val.concat.l)) { LOG_MEMORY("failed to add concatenation left m-token"); vector_del(preproc, ret); ret = NULL; goto solve_done; } ++need_concat; break; } case MTOK_TOKEN: { int do_add = 1; if (concat_cur == 2) { preproc_token_t *tok1 = &vector_last(preproc, ret); // Guaranteed to exist preproc_token_t *tok2 = &mtok->val.tok; #define ISIDENT(tt) (((tt) == PPTOK_IDENT) || ((tt) == PPTOK_IDENT_UNEXP)) #define FIND(s, c) strchr(string_content((s)), (c)) #define ONLYDIGS(s) (!FIND((s), '.') && !FIND((s), '+') && !FIND((s), '-')) if (ISIDENT(tok1->tokt) && (ISIDENT(tok2->tokt) || ((tok2->tokt == PPTOK_NUM) && ONLYDIGS(tok2->tokv.str)))) { do_add = 0; tok1->tokt = PPTOK_IDENT; if (!string_add_string(tok1->tokv.str, tok2->tokv.str)) { LOG_MEMORY("failed to concatenate identifiers"); vector_del(preproc, ret); ret = NULL; goto solve_done; } } else if ((tok1->tokt == PPTOK_NUM) && (ISIDENT(tok2->tokt) || (tok2->tokt == PPTOK_NUM))) { do_add = 0; if (!string_add_string(tok1->tokv.str, tok2->tokv.str)) { LOG_MEMORY("failed to concatenate identifiers"); vector_del(preproc, ret); ret = NULL; goto solve_done; } } else { log_warning(li, "unsupported concatenation between token type %u and %u while expanding %s\n", tok1->tokt, tok2->tokt, mname); } #undef ISIDENT #undef FIND #undef ONLYDIGS } if (do_add) { preproc_token_t *tok1 = &mtok->val.tok; if (tok1->tokt != PPTOK_NEWLINE) { preproc_token_t tok2; switch (tok1->tokt) { case PPTOK_INVALID: case PPTOK_NEWLINE: case PPTOK_BLANK: case PPTOK_START_LINE_COMMENT: case PPTOK_EOF: tok2 = (preproc_token_t){.tokt = tok1->tokt, .loginfo = *li, .tokv.c = tok1->tokv.c}; break; case PPTOK_SYM: tok2 = (preproc_token_t){.tokt = tok1->tokt, .loginfo = *li, .tokv.sym = tok1->tokv.sym}; break; case PPTOK_IDENT: case PPTOK_IDENT_UNEXP: case PPTOK_NUM: { string_t *dup = string_dup(tok1->tokv.str); if (!dup) { LOG_MEMORY("failed to duplicate string"); vector_del(preproc, ret); ret = NULL; goto solve_done; } tok2 = (preproc_token_t){.tokt = tok1->tokt, .loginfo = *li, .tokv.str = dup}; break; } case PPTOK_INCL: case PPTOK_STRING: { string_t *dup = string_dup(tok1->tokv.sstr); if (!dup) { LOG_MEMORY("failed to duplicate string"); vector_del(preproc, ret); ret = NULL; goto solve_done; } tok2 = (preproc_token_t){.tokt = tok1->tokt, .loginfo = *li, .tokv.sstr = dup, .tokv.sisstr = tok1->tokv.sisstr}; break; } } if (!vector_push(preproc, ret, tok2)) { LOG_MEMORY("failed to add token to output vector"); preproc_token_del(&tok2); vector_del(preproc, ret); ret = NULL; goto solve_done; } } } vector_pop_nodel(mtoken, st); if (need_concat) { concat_cur = 2; --need_concat; } else concat_cur = 0; break; } case MTOK_ARG: { VECTOR(preproc) *toks_to_add; if (!need_concat && !concat_cur) { if (!margs2[mtok->val.argid]) { margs2[mtok->val.argid] = preproc_do_expand(li, macros, vector_content(preprocs, margs)[mtok->val.argid], solved_macros, opt_used_macros); if (!margs2[mtok->val.argid]) { vector_del(preproc, ret); ret = NULL; goto solve_done; } } toks_to_add = margs2[mtok->val.argid]; } else { toks_to_add = vector_content(preprocs, margs)[mtok->val.argid]; } size_t tta_start = 0, len = vector_size(preproc, toks_to_add); if (len && (concat_cur == 2)) { preproc_token_t *tok1 = &vector_last(preproc, ret); // Guaranteed to exist preproc_token_t *tok2 = vector_begin(preproc, toks_to_add); #define ISIDENT(tt) (((tt) == PPTOK_IDENT) || ((tt) == PPTOK_IDENT_UNEXP)) #define FIND(s, c) strchr(string_content((s)), (c)) #define ONLYDIGS(s) (!FIND((s), '.') && !FIND((s), '+') && !FIND((s), '-')) if (ISIDENT(tok1->tokt) && (ISIDENT(tok2->tokt) || ((tok2->tokt == PPTOK_NUM) && ONLYDIGS(tok2->tokv.str)))) { tta_start = 1; --len; tok1->tokt = PPTOK_IDENT; if (!string_add_string(tok1->tokv.str, tok2->tokv.str)) { LOG_MEMORY("failed to concatenate identifiers"); vector_del(preproc, ret); ret = NULL; goto solve_done; } } else if ((tok1->tokt == PPTOK_NUM) && (ISIDENT(tok2->tokt) || (tok2->tokt == PPTOK_NUM))) { tta_start = 1; --len; if (!string_add_string(tok1->tokv.str, tok2->tokv.str)) { LOG_MEMORY("failed to concatenate identifiers"); vector_del(preproc, ret); ret = NULL; goto solve_done; } } else { log_warning(li, "unsupported concatenation between token type %u and %u while expanding %s\n", tok1->tokt, tok2->tokt, mname); } #undef ISIDENT #undef FIND #undef ONLYDIGS } if (len) { preproc_token_t tok2; for (preproc_token_t *tok1 = vector_begin(preproc, toks_to_add) + tta_start; tok1 != vector_end(preproc, toks_to_add); ++tok1) { if (tok1->tokt == PPTOK_NEWLINE) continue; switch (tok1->tokt) { case PPTOK_INVALID: case PPTOK_NEWLINE: case PPTOK_BLANK: case PPTOK_START_LINE_COMMENT: case PPTOK_EOF: tok2 = (preproc_token_t){.tokt = tok1->tokt, .loginfo = *li, .tokv.c = tok1->tokv.c}; break; case PPTOK_SYM: tok2 = (preproc_token_t){.tokt = tok1->tokt, .loginfo = *li, .tokv.sym = tok1->tokv.sym}; break; case PPTOK_IDENT: case PPTOK_IDENT_UNEXP: case PPTOK_NUM: { string_t *dup = string_dup(tok1->tokv.str); if (!dup) { LOG_MEMORY("failed to duplicate string"); vector_del(preproc, ret); ret = NULL; goto solve_done; } tok2 = (preproc_token_t){.tokt = tok1->tokt, .loginfo = *li, .tokv.str = dup}; break; } case PPTOK_INCL: case PPTOK_STRING: { string_t *dup = string_dup(tok1->tokv.sstr); if (!dup) { LOG_MEMORY("failed to duplicate string"); vector_del(preproc, ret); ret = NULL; goto solve_done; } tok2 = (preproc_token_t){.tokt = tok1->tokt, .loginfo = *li, .tokv.sstr = dup, .tokv.sisstr = tok1->tokv.sisstr}; break; } } if (!vector_push(preproc, ret, tok2)) { LOG_MEMORY("failed to add token to output vector"); preproc_token_del(&tok2); vector_del(preproc, ret); ret = NULL; goto solve_done; } } } vector_pop_nodel(mtoken, st); if (need_concat) { concat_cur = (vector_size(preproc, toks_to_add) || (concat_cur == 2)) ? 2 : 1; --need_concat; } else concat_cur = 0; break; } case MTOK_STRINGIFY: if (concat_cur == 2) { log_warning(li, "invalid concatenation with string while expanding %s\n", mname); } // TODO: better stringifier preproc_token_t tok2; { string_t *dup = string_new_cap(15); if (!dup) { LOG_MEMORY("failed to allocate stringify string"); vector_del(preproc, ret); ret = NULL; goto solve_done; } string_add_char(dup, '<'); string_add_char(dup, 'S'); string_add_char(dup, 't'); string_add_char(dup, 'r'); string_add_char(dup, 'i'); string_add_char(dup, 'n'); string_add_char(dup, 'g'); string_add_char(dup, 'i'); string_add_char(dup, 'f'); string_add_char(dup, 'y'); string_add_char(dup, ' '); string_add_char(dup, 'a'); string_add_char(dup, 'r'); string_add_char(dup, 'g'); string_add_char(dup, '>'); tok2 = (preproc_token_t){.tokt = PPTOK_STRING, .loginfo = *li, .tokv.sstr = dup, .tokv.sisstr = 1}; } if (!vector_push(preproc, ret, tok2)) { LOG_MEMORY("failed to add token to output vector"); preproc_token_del(&tok2); vector_del(preproc, ret); ret = NULL; goto solve_done; } vector_pop_nodel(mtoken, st); if (need_concat) { concat_cur = 2; --need_concat; } else concat_cur = 0; break; } } solve_done: // Don't call the destructors vector_del_freed(mtoken, st); if (margs) { for (size_t i = 0; i < vector_size(preprocs, margs); ++i) { if (margs2[i]) vector_del(preproc, margs2[i]); } } free(margs2); if (ret) { // Success, now expand the result // First add mname to the set of expanded macros int iret; kh_put(string_set, solved_macros, mname, &iret); if (iret < 0) { LOG_MEMORY("failed to add current macro to the set of expanded macros"); vector_del(preproc, ret); free(mname); return NULL; } // Next expand every remaining macros vector_trim(preproc, ret); VECTOR(preproc) *ret2 = preproc_do_expand(li, macros, ret, solved_macros, opt_used_macros); vector_del(preproc, ret); ret = ret2; if (!ret) return NULL; // There was an error, abort // Finally pop mname (in case we are expanding an argument) khiter_t it = kh_get(string_set, solved_macros, mname); if (it == kh_end(solved_macros)) { log_internal(li, "failed to find current macro in the set of expanded macros while expanding %s\n" , mname); vector_del(preproc, ret); return NULL; } #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wcast-qual" free((void*)kh_key(solved_macros, it)); #pragma GCC diagnostic pop kh_del(string_set, solved_macros, it); } #undef LOG_MEMORY return ret; } static VECTOR(preproc) *preproc_do_expand(loginfo_t *li, const khash_t(macros_map) *macros, const VECTOR(preproc) *toks, khash_t(string_set) *solved_macros, khash_t(string_set) *opt_used_macros) { VECTOR(preproc) *toks2 = vector_new_cap(preproc, vector_size(preproc, toks)); if (!toks2) { log_memory("failed to allocate full macro expansion auxiliary vector\n"); return NULL; } vector_for(preproc, tok, toks) { if (tok->tokt == PPTOK_NEWLINE) continue; preproc_token_t tok2; switch (tok->tokt) { case PPTOK_INVALID: case PPTOK_NEWLINE: case PPTOK_BLANK: case PPTOK_START_LINE_COMMENT: case PPTOK_EOF: tok2 = (preproc_token_t){.tokt = tok->tokt, .loginfo = tok->loginfo, .tokv.c = tok->tokv.c}; break; case PPTOK_SYM: tok2 = (preproc_token_t){.tokt = tok->tokt, .loginfo = tok->loginfo, .tokv.sym = tok->tokv.sym}; break; case PPTOK_IDENT: case PPTOK_IDENT_UNEXP: case PPTOK_NUM: { string_t *dup = string_dup(tok->tokv.str); if (!dup) { log_memory("failed to duplicate string during full macro expansion\n"); vector_del(preproc, toks2); return NULL; } tok2 = (preproc_token_t){.tokt = tok->tokt, .loginfo = tok->loginfo, .tokv.str = dup}; break; } case PPTOK_INCL: case PPTOK_STRING: { string_t *dup = string_dup(tok->tokv.sstr); if (!dup) { log_memory("failed to duplicate string during full macro expansion\n"); vector_del(preproc, toks2); return NULL; } tok2 = (preproc_token_t){.tokt = tok->tokt, .loginfo = tok->loginfo, .tokv.sstr = dup, .tokv.sisstr = tok->tokv.sisstr}; break; } } vector_push(preproc, toks2, tok2); // cap > size, thus this always succeed } VECTOR(preproc) *ret = vector_new_cap(preproc, vector_size(preproc, toks)); if (!ret) { log_memory("failed to allocate full macro expansion result vector\n"); return NULL; } vector_for(preproc, tok, toks2) { switch (tok->tokt) { case PPTOK_IDENT: { if (opt_used_macros && !strcmp(string_content(tok->tokv.str), "defined") && (vector_end(preproc, toks2) - tok >= 2)) { // We need to support "defined ident" and "defined(ident)" string_t *mname = NULL; preproc_token_t *tok2; // tok2 is the last used token, mname is the ident string if ((tok[1].tokt == PPTOK_SYM) && (tok[1].tokv.sym == SYM_LPAREN)) { if ((vector_end(preproc, toks2) - tok >= 4) && (tok[3].tokt == PPTOK_SYM) && (tok[3].tokv.sym == SYM_RPAREN)) { if ((tok[2].tokt == PPTOK_IDENT) || (tok[2].tokt == PPTOK_IDENT_UNEXP)) { tok2 = tok + 3; mname = tok[2].tokv.str; } } } else if ((tok[1].tokt == PPTOK_IDENT) || (tok[1].tokt == PPTOK_IDENT_UNEXP)) { tok2 = tok + 1; mname = tok[1].tokv.str; } if (mname) { // Add the ident to the used_macros int iret; char *mname2 = strdup(string_content(mname)); if (!mname2) { // Abort log_memory("failed to add %s to the list of used macros (strdup returned NULL)\n", string_content(mname)); goto expand_done; } kh_put(string_set, opt_used_macros, mname2, &iret); if (iret < 0) { // Abort free(mname2); log_memory("failed to add %s to the list of used macros (kh_put ireturned %d)\n", string_content(mname), iret); goto expand_done; } else if (iret == 0) { // Just free mname2, it was already present free(mname2); } // Value is either 0 or 1 as a PPTOK_NUM, so we need a string with capacity 1 string_t *num_str = string_new_cap(1); if (!num_str) { // Abort log_memory("failed to create defined() output\n"); goto expand_done; } khiter_t it = kh_get(macros_map, macros, string_content(mname)); string_add_char(num_str, (it == kh_end(macros)) ? '0' : '1'); if (!vector_push(preproc, ret, ((preproc_token_t){.tokt = PPTOK_NUM, .loginfo = *li, .tokv.str = num_str}))) { log_memory("failed to add defined() result to the output\n"); string_del(num_str); goto expand_done; } // Done string_del(tok->tokv.str); string_del(mname); tok = tok2; break; } // If mname == NULL, we simply ignore it and check if there is a macro called 'defined' } khiter_t it = kh_get(string_set, solved_macros, string_content(tok->tokv.str)); if (it != kh_end(solved_macros)) { tok->tokt = PPTOK_IDENT_UNEXP; } else { it = kh_get(macros_map, macros, string_content(tok->tokv.str)); if (it != kh_end(macros)) { macro_t *m = &kh_val(macros, it); if (m->is_funlike) { preproc_token_t *tok2 = tok + 1; if ((tok2 < vector_end(preproc, toks2)) && (tok2->tokt == PPTOK_SYM) && (tok2->tokv.sym == SYM_LPAREN)) { unsigned depth = 1; VECTOR(preprocs) *margs = vector_new(preprocs); if (!margs) { log_memory("failed to allocate macro arguments for macro %s during full macro expansion\n", string_content(tok->tokv.str)); if (margs) vector_del(preprocs, margs); goto expand_done; } VECTOR(preproc) *marg = vector_new(preproc); if (!marg) goto gather_args_err_mem; while (depth && (tok2 < vector_end(preproc, toks2) - 1) && (tok2->tokt != PPTOK_EOF) && (tok2->tokt != PPTOK_INVALID)) { ++tok2; if ((depth == 1) && (tok2->tokt == PPTOK_SYM) && (tok2->tokv.sym == SYM_COMMA)) { // Possible optimization: emplace NULL if vector_size(marg) == 0 // This would avoid allocating a new vector, but needs support in preproc_solve_macro vector_trim(preproc, marg); if (!vector_push(preprocs, margs, marg)) goto gather_args_err_mem; marg = vector_new(preproc); if (!marg) goto gather_args_err_mem; } else { if (!vector_push(preproc, marg, *tok2)) goto gather_args_err_mem; } if ((tok2->tokt == PPTOK_SYM) && (tok2->tokv.sym == SYM_LPAREN)) ++depth; else if ((tok2->tokt == PPTOK_SYM) && (tok2->tokv.sym == SYM_RPAREN)) --depth; } if (depth) { // This may be OK ("Unfinished fun-like macro %s\n", string_content(tok->tokv.str)); vector_del(preprocs, margs); vector_del(preproc, marg); goto abort_macro; } // margs finishes with a SYM_RPAREN token vector_pop(preproc, marg); vector_trim(preproc, marg); if (!vector_push(preprocs, margs, marg)) goto gather_args_err_mem; marg = NULL; if (0) { gather_args_err_mem: log_memory("failed to gather macro arguments for macro %s during full macro expansion\n", string_content(tok->tokv.str)); if (marg) vector_del(preproc, marg); vector_del(preprocs, margs); goto expand_done; } // OK, now expand the macro char *mname = string_steal(tok->tokv.str); tok = tok2; VECTOR(preproc) *expanded = preproc_solve_macro(li, macros, mname, m, margs, solved_macros, opt_used_macros); vector_del(preprocs, margs); if (!expanded) { // Error expanding the macro goto expand_done; } if (!vector_push_vec(preproc, ret, expanded)) { log_memory("pushing expanded macro to full macro expansion\n"); vector_del(preproc, expanded); goto expand_done; } vector_del_freed(preproc, expanded); // Done break; } } else { VECTOR(preproc) *expanded = preproc_solve_macro(li, macros, string_steal(tok->tokv.str), m, NULL, solved_macros, opt_used_macros); if (!expanded) { ++tok; // Current token is already freed (string stolen) goto expand_done; } if (!vector_push_vec(preproc, ret, expanded)) { log_memory("failed to add macro expansion in output vector of full macro expansion\n"); ++tok; // Current token is already freed (string stolen) goto expand_done; } vector_del_freed(preproc, expanded); break; } } } } /* FALLTHROUGH */ abort_macro: case PPTOK_IDENT_UNEXP: case PPTOK_INVALID: case PPTOK_NUM: case PPTOK_STRING: case PPTOK_INCL: case PPTOK_SYM: case PPTOK_NEWLINE: case PPTOK_BLANK: case PPTOK_START_LINE_COMMENT: case PPTOK_EOF: if (!vector_push(preproc, ret, *tok)) { log_memory("failed to add token to output vector during full macro expansion\n"); goto expand_done; } break; } if (0) { expand_done: vector_del_free_from(preproc, toks2, (size_t)(tok - vector_begin(preproc, toks2))); vector_del(preproc, ret); return NULL; } } vector_del_freed(preproc, toks2); return ret; } #define OPLVL_MUL 2 #define OPLVL_DIV 2 #define OPLVL_MOD 2 #define OPLVL_ADD 3 #define OPLVL_SUB 3 #define OPLVL_LSL 4 #define OPLVL_LSR 4 #define OPLVL_LET 5 #define OPLVL_LEE 5 #define OPLVL_GRT 5 #define OPLVL_GRE 5 #define OPLVL_EQU 6 #define OPLVL_NEQ 6 #define OPLVL_AAN 7 // Bitwise (Arithmetic) #define OPLVL_XOR 8 #define OPLVL_AOR 9 // Bitwise (Arithmetic) #define OPLVL_BAN 10 // Boolean #define OPLVL_BOR 11 // Boolean #define OPLVL_ALL 12 // Evaluate the entire stack #define OPLVL_ALS 13 // Evaluate the entire stack, (don't update the states (ignored)) #define OPTYP_MUL 1 #define OPTYP_DIV 2 #define OPTYP_MOD 3 #define OPTYP_ADD 1 #define OPTYP_SUB 2 #define OPTYP_LSL 1 #define OPTYP_LSR 2 #define OPTYP_LET 1 #define OPTYP_LEE 2 #define OPTYP_GRT 3 #define OPTYP_GRE 4 #define OPTYP_EQU 1 #define OPTYP_NEQ 2 #define OPTYP_AAN 1 #define OPTYP_XOR 1 #define OPTYP_AOR 1 #define OPTYP_BAN 1 #define OPTYP_BOR 1 typedef struct preproc_eval_aux_s { struct { unsigned st0 : 1; unsigned st2 : 2; unsigned st3 : 2; unsigned st4 : 2; unsigned st5 : 3; unsigned st6 : 2; unsigned st7 : 1; unsigned st8 : 1; unsigned st9 : 1; unsigned st_bool : 1; }; int64_t v0; unsigned st1; const preproc_token_t *v1; int64_t v2; loginfo_t li2; int64_t v3; loginfo_t li3; int64_t v4; loginfo_t li4; int64_t v5; loginfo_t li5; int64_t v6; loginfo_t li6; int64_t v7; loginfo_t li7; int64_t v8; loginfo_t li8; int64_t v9; loginfo_t li9; loginfo_t libool; unsigned n_colons; // Number of ':' expected (needs to skip to the end), ie number of ternary where the truth-y branch is taken } preproc_eval_aux_t; VECTOR_DECLARE_STATIC(ppeaux, preproc_eval_aux_t) VECTOR_IMPL_STATIC(ppeaux, (void)) static int64_t eval_stack(preproc_eval_aux_t *st, loginfo_t li, int max_lv, int optype, _Bool is_unsigned, int *success) { if (!li.lineno_end) { li.lineno_end = li.lineno; li.colno_end = li.colno; } int64_t acc = st->v0; st->st0 = 0; #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wconversion" #pragma GCC diagnostic ignored "-Wduplicated-cond" // For the else if (st->stX) if (st->st2 == OPTYP_MUL) { acc = st->v2 * acc; st->st2 = 0; li.lineno = st->li2.lineno; li.colno = st->li2.colno; } else if (st->st2 == OPTYP_DIV) { if (!acc) { log_error(&li, "division by zero during #if evaluation\n"); *success = 0; return 0; } acc = st->v2 / acc; st->st2 = 0; li.lineno = st->li2.lineno; li.colno = st->li2.colno; } else if (st->st2 == OPTYP_MOD) { if (!acc) { log_error(&li, "division by zero during #if evaluation\n"); *success = 0; return 0; } acc = st->v2 % acc; st->st2 = 0; li.lineno = st->li2.lineno; li.colno = st->li2.colno; } else if (st->st2) { log_internal(&st->li2, "unknown st2 %d during #if evaluation\n", st->st2); *success = 0; return 0; } if (max_lv == 2) { st->st2 = optype; st->v2 = acc; st->li2 = li; return acc; } if (st->st3 == OPTYP_ADD) { acc = st->v3 + acc; st->st3 = 0; li.lineno = st->li3.lineno; li.colno = st->li3.colno; } else if (st->st3 == OPTYP_SUB) { acc = st->v3 - acc; st->st3 = 0; li.lineno = st->li3.lineno; li.colno = st->li3.colno; } else if (st->st3) { log_internal(&st->li3, "unknown st3 %d during #if evaluation\n", st->st3); *success = 0; return 0; } if (max_lv == 3) { st->st3 = optype; st->v3 = acc; st->li3 = li; return acc; } if (st->st4 == OPTYP_LSL) { acc = st->v4 << acc; st->st4 = 0; li.lineno = st->li4.lineno; li.colno = st->li4.colno; } else if (st->st4 == OPTYP_LSR) { acc = is_unsigned ? (int64_t)((uint64_t)st->v4 >> (uint64_t)acc) : (st->v4 >> acc); st->st4 = 0; li.lineno = st->li4.lineno; li.colno = st->li4.colno; } else if (st->st4) { log_internal(&st->li4, "unknown st4 %d during #if evaluation\n", st->st4); *success = 0; return 0; } if (max_lv == 4) { st->st4 = optype; st->v4 = acc; st->li4 = li; return acc; } if (st->st5 == OPTYP_LET) { acc = is_unsigned ? ((uint64_t)st->v5 < (uint64_t)acc) : (st->v5 < acc); is_unsigned = 0; st->st5 = 0; li.lineno = st->li5.lineno; li.colno = st->li5.colno; } else if (st->st5 == OPTYP_LEE) { acc = is_unsigned ? ((uint64_t)st->v5 <= (uint64_t)acc) : (st->v5 <= acc); is_unsigned = 0; st->st5 = 0; li.lineno = st->li5.lineno; li.colno = st->li5.colno; } else if (st->st5 == OPTYP_GRT) { acc = is_unsigned ? ((uint64_t)st->v5 > (uint64_t)acc) : (st->v5 > acc); is_unsigned = 0; st->st5 = 0; li.lineno = st->li5.lineno; li.colno = st->li5.colno; } else if (st->st5 == OPTYP_GRE) { acc = is_unsigned ? ((uint64_t)st->v5 >= (uint64_t)acc) : (st->v5 >= acc); is_unsigned = 0; st->st5 = 0; li.lineno = st->li5.lineno; li.colno = st->li5.colno; } else if (st->st5) { log_internal(&st->li5, "unknown st5 %d during #if evaluation\n", st->st5); *success = 0; return 0; } if (max_lv == 5) { st->st5 = optype; st->v5 = acc; st->li5 = li; return acc; } if (st->st6 == OPTYP_EQU) { acc = st->v6 == acc; st->st6 = 0; li.lineno = st->li6.lineno; li.colno = st->li6.colno; } else if (st->st6 == OPTYP_NEQ) { acc = st->v6 != acc; st->st6 = 0; li.lineno = st->li6.lineno; li.colno = st->li6.colno; } else if (st->st6) { log_internal(&st->li6, "unknown st6 %d during #if evaluation\n", st->st6); *success = 0; return 0; } if (max_lv == 6) { st->st6 = optype; st->v6 = acc; st->li6 = li; return acc; } if (st->st7 == OPTYP_AAN) { acc = st->v7 & acc; st->st7 = 0; li.lineno = st->li7.lineno; li.colno = st->li7.colno; } else if (st->st7) { log_internal(&st->li7, "unknown st7 %d during #if evaluation\n", st->st7); *success = 0; return 0; } if (max_lv == 7) { st->st7 = optype; st->v7 = acc; st->li7 = li; return acc; } if (st->st8 == OPTYP_XOR) { acc = st->v8 ^ acc; st->st8 = 0; li.lineno = st->li8.lineno; li.colno = st->li8.colno; } else if (st->st8) { log_internal(&st->li8, "unknown st8 %d during #if evaluation\n", st->st8); *success = 0; return 0; } if (max_lv == 8) { st->st8 = optype; st->v8 = acc; st->li8 = li; return acc; } if (st->st9 == OPTYP_AOR) { acc = st->v9 | acc; st->st9 = 0; li.lineno = st->li9.lineno; li.colno = st->li9.colno; } else if (st->st9) { log_internal(&st->li9, "unknown st9 %d during #if evaluation\n", st->st9); *success = 0; return 0; } if (max_lv == 9) { st->st9 = optype; st->v9 = acc; st->li9 = li; return acc; } if (max_lv == 10) { // OPLVL == OPLVL_BAN, OPTYP == OPTYP_BAN li.lineno = st->libool.lineno; li.colno = st->libool.colno; st->libool = li; return acc; } if (max_lv == 11) { // OPLVL == OPLVL_BOR, OPTYP == OPTYP_BOR li.lineno = st->libool.lineno; li.colno = st->libool.colno; st->libool = li; return acc; } if (st->st_bool) { acc = !!acc; st->st_bool = 0; } if ((max_lv == OPLVL_ALL) || (max_lv == OPLVL_ALS)) { return acc; } #pragma GCC diagnostic pop log_internal(&li, "invalid max level %d in preprocessor partial expression evaluation\n", max_lv); *success = 0; return acc; } static int64_t preproc_eval(const VECTOR(preproc) *cond, int *aux_ret, int ptr_is_32bits) { int success; VECTOR(ppeaux) *stack = vector_new_cap(ppeaux, 1); if (!stack) { log_memory("failed to allocate #if evaluation stack vector\n"); *aux_ret = 0; return 0; } if (0) { eval_fail: vector_del(ppeaux, stack); *aux_ret = 0; return 0; } vector_push(ppeaux, stack, (preproc_eval_aux_t){0}); // vector_cap >= 1 int64_t acc; loginfo_t li0; _Bool is_unsigned = 0; vector_for(preproc, tok, cond) { if (tok->tokt == PPTOK_NUM) { // Evaluate token as an integer if st0 == 0, error otherwise if (vector_last(ppeaux, stack).st0 == 0) { num_constant_t cst; if (!num_constant_convert(&tok->loginfo, tok->tokv.str, &cst, ptr_is_32bits)) { goto eval_fail; } switch (cst.typ) { case NCT_INT32: acc = cst.val.i32; break; case NCT_UINT32: is_unsigned = 1; acc = cst.val.u32; break; case NCT_INT64: acc = cst.val.i64; break; case NCT_UINT64: is_unsigned = 1; acc = (int64_t)cst.val.u64; break; case NCT_FLOAT: case NCT_DOUBLE: case NCT_LDOUBLE: default: log_error(&tok->loginfo, "number '%s' is not a valid integer (during #if evaluation)\n", string_content(tok->tokv.str)); goto eval_fail; } li0 = tok->loginfo; goto push_acc_to_st0; } else { log_error(&tok->loginfo, "unexpected number during #if evaluation\n"); goto eval_fail; } } else if ((tok->tokt == PPTOK_IDENT) || (tok->tokt == PPTOK_IDENT_UNEXP)) { // Evaluate token as 0 if st0 == 0, error otherwise if (vector_last(ppeaux, stack).st0 == 0) { acc = 0; li0 = tok->loginfo; goto push_acc_to_st0; } else { log_error(&tok->loginfo, "unexpected ident '%s' during #if evaluation\n", string_content(tok->tokv.str)); goto eval_fail; } } else if (tok->tokt == PPTOK_SYM) { int op_lvl; int op_typ; switch (tok->tokv.sym) { case SYM_TILDE: // Unary (st0 == 0) if (vector_last(ppeaux, stack).st0 != 0) { log_error(&tok->loginfo, "unexpected unary '~' in #if expression\n"); goto eval_fail; } if (!(vector_last(ppeaux, stack).st1++)) { // Also update v1, since this is the first level 1 operator vector_last(ppeaux, stack).v1 = tok; } goto done_partial_eval; case SYM_EXCL: // Unary (st0 == 0) if (vector_last(ppeaux, stack).st0 != 0) { log_error(&tok->loginfo, "unexpected unary '!' in #if expression\n"); goto eval_fail; } if (!(vector_last(ppeaux, stack).st1++)) { // Also update v1, since this is the first level 1 operator vector_last(ppeaux, stack).v1 = tok; } goto done_partial_eval; case SYM_PLUS: // May be unary (st0 == 0) or binary (st0 != 0) if (vector_last(ppeaux, stack).st0 == 0) { // Unary, prepare lv1 then continue to the next token if (!(vector_last(ppeaux, stack).st1++)) { // Also update v1, since this is the first level 1 operator vector_last(ppeaux, stack).v1 = tok; } goto done_partial_eval; } else { op_lvl = OPLVL_ADD; op_typ = OPTYP_ADD; goto add_binop; } case SYM_DASH: // May be unary (st0 == 0) or binary (st0 != 0) if (vector_last(ppeaux, stack).st0 == 0) { // Unary, prepare lv1 then continue to the next token if (!(vector_last(ppeaux, stack).st1++)) { // Also update v1, since this is the first level 1 operator vector_last(ppeaux, stack).v1 = tok; } goto done_partial_eval; } else { op_lvl = OPLVL_SUB; op_typ = OPTYP_SUB; goto add_binop; } case SYM_STAR: // Binary (st0 != 0) if (vector_last(ppeaux, stack).st0 == 0) goto invalid_binop; op_lvl = OPLVL_MUL; op_typ = OPTYP_MUL; goto add_binop; case SYM_SLASH: // Binary (st0 != 0) if (vector_last(ppeaux, stack).st0 == 0) goto invalid_binop; op_lvl = OPLVL_DIV; op_typ = OPTYP_DIV; goto add_binop; case SYM_PERCENT: // Binary (st0 != 0) if (vector_last(ppeaux, stack).st0 == 0) goto invalid_binop; op_lvl = OPLVL_MOD; op_typ = OPTYP_MOD; goto add_binop; case SYM_HAT: // Binary (st0 != 0) if (vector_last(ppeaux, stack).st0 == 0) goto invalid_binop; op_lvl = OPLVL_XOR; op_typ = OPTYP_XOR; goto add_binop; case SYM_AMP: // Binary (st0 != 0) if (vector_last(ppeaux, stack).st0 == 0) goto invalid_binop; op_lvl = OPLVL_AAN; op_typ = OPTYP_AAN; goto add_binop; case SYM_PIPE: // Binary (st0 != 0) if (vector_last(ppeaux, stack).st0 == 0) goto invalid_binop; op_lvl = OPLVL_AOR; op_typ = OPTYP_AOR; goto add_binop; case SYM_EQEQ: // Binary (st0 != 0) if (vector_last(ppeaux, stack).st0 == 0) goto invalid_binop; op_lvl = OPLVL_EQU; op_typ = OPTYP_EQU; goto add_binop; case SYM_EXCLEQ: // Binary (st0 != 0) if (vector_last(ppeaux, stack).st0 == 0) goto invalid_binop; op_lvl = OPLVL_NEQ; op_typ = OPTYP_NEQ; goto add_binop; case SYM_LT: // Binary (st0 != 0) if (vector_last(ppeaux, stack).st0 == 0) goto invalid_binop; op_lvl = OPLVL_LET; op_typ = OPTYP_LET; goto add_binop; case SYM_GT: // Binary (st0 != 0) if (vector_last(ppeaux, stack).st0 == 0) goto invalid_binop; op_lvl = OPLVL_GRT; op_typ = OPTYP_GRT; goto add_binop; case SYM_LTEQ: // Binary (st0 != 0) if (vector_last(ppeaux, stack).st0 == 0) goto invalid_binop; op_lvl = OPLVL_LEE; op_typ = OPTYP_LEE; goto add_binop; case SYM_GTEQ: // Binary (st0 != 0) if (vector_last(ppeaux, stack).st0 == 0) goto invalid_binop; op_lvl = OPLVL_GRE; op_typ = OPTYP_GRE; goto add_binop; case SYM_AMPAMP: // Binary (st0 != 0) if (vector_last(ppeaux, stack).st0 == 0) goto invalid_binop; op_lvl = OPLVL_BAN; op_typ = OPTYP_BAN; goto add_binop; case SYM_PIPEPIPE: // Binary (st0 != 0) if (vector_last(ppeaux, stack).st0 == 0) goto invalid_binop; op_lvl = OPLVL_BOR; op_typ = OPTYP_BOR; goto add_binop; case SYM_LTLT: // Binary (st0 != 0) if (vector_last(ppeaux, stack).st0 == 0) goto invalid_binop; op_lvl = OPLVL_LSL; op_typ = OPTYP_LSL; goto add_binop; case SYM_GTGT: // Binary (st0 != 0) if (vector_last(ppeaux, stack).st0 == 0) goto invalid_binop; op_lvl = OPLVL_LSR; op_typ = OPTYP_LSR; goto add_binop; case SYM_LPAREN: // May be placed anywhere a constant is expected (st0 == 0) and simply pushes a new stack if (vector_last(ppeaux, stack).st0 != 0) { log_error(&tok->loginfo, "unexpected opening parenthesis in #if expression\n"); goto eval_fail; } if (!vector_push(ppeaux, stack, (preproc_eval_aux_t){0})) { log_memory("failed to push a new stack during #if evaluation\n"); goto eval_fail; } goto done_partial_eval; case SYM_RPAREN: // May be placed anywhere after a constant (st0 != 0) if (vector_size(ppeaux, stack) == 1) { log_error(&tok->loginfo, "unexpected symbol ')' (parenthesis not opened) during #if evaluation\n"); goto eval_fail; } if (vector_last(ppeaux, stack).n_colons) { log_error(&tok->loginfo, "unexpected symbol ')', expected symbol ':' during #if evaluation\n"); goto eval_fail; } if (!vector_last(ppeaux, stack).st0) { log_error(&tok->loginfo, "unexpected symbol ')' during #if evaluation\n"); goto eval_fail; } eval_rparen: // Evaluate the top of the stack, then pop success = 1; acc = eval_stack(&vector_last(ppeaux, stack), li0, OPLVL_ALS, 0, is_unsigned, &success); if (!success) { goto eval_fail; } eval_rparen_acc: vector_pop(ppeaux, stack); goto push_acc_to_st0; case SYM_QUESTION: { // May be placed anywhere and may skip some tokens depending on acc if (!vector_last(ppeaux, stack).st0) { log_error(&tok->loginfo, "unexpected ternary operator during #if evaluation\n"); goto eval_fail; } // Evaluate the top of the stack, then increase n_colons or skip tokens success = 1; acc = eval_stack(&vector_last(ppeaux, stack), li0, OPLVL_ALL, 0, is_unsigned, &success); if (!success) { goto eval_fail; } eval_question_acc: is_unsigned = 0; if (acc) { // Increase n_colons ++vector_last(ppeaux, stack).n_colons; goto done_partial_eval; } else { // Skip to the corresponding colon unsigned nquestions = 0, nparens = 0; // Note that we don't really care about the content of the ignored part; it may be syntaxically incorrect for (++tok; tok < vector_end(preproc, cond); ++tok) { if ((tok->tokt == PPTOK_SYM) && (tok->tokv.sym == SYM_LPAREN)) { ++nparens; } else if ((tok->tokt == PPTOK_SYM) && (tok->tokv.sym == SYM_RPAREN)) { if (nparens) --nparens; else { log_error(&tok->loginfo, "unexpected symbol ')', expected ':' during #if evaluation\n"); goto eval_fail; } } else if (!nparens && (tok->tokt == PPTOK_SYM) && (tok->tokv.sym == SYM_QUESTION)) { ++nquestions; } else if (!nparens && (tok->tokt == PPTOK_SYM) && (tok->tokv.sym == SYM_COLON)) { if (nquestions) --nquestions; else break; } } if (tok == vector_end(preproc, cond)) { log_error(&tok->loginfo, "ternary operator never finished during #if evaluation\n"); goto eval_fail; } goto done_partial_eval; } } case SYM_COLON: { if (!vector_last(ppeaux, stack).n_colons) { log_error(&tok->loginfo, "unexpected symbol ':' during #if evaluation\n"); goto eval_fail; } if (!vector_last(ppeaux, stack).st0) { log_error(&tok->loginfo, "unexpected symbol ':' during #if evaluation\n"); goto eval_fail; } eval_colon_acc: // Skip to the end of expression or to the corresponding rparen if (vector_size(ppeaux, stack) == 1) { goto done_complete_stack; // No parenthesis opened, skip to the end of the vector } // Skip to the next rparen; also, we skip correctly counting of ':' since we don't care about those anymore unsigned nparens = 0; // Note that we don't really care about the content of the ignored part; it may be syntaxically incorrect for (++tok; tok < vector_end(preproc, cond); ++tok) { if ((tok->tokt == PPTOK_SYM) && (tok->tokv.sym == SYM_LPAREN)) { ++nparens; } else if ((tok->tokt == PPTOK_SYM) && (tok->tokv.sym == SYM_RPAREN)) { if (nparens) --nparens; else break; } } // We need to exit at a RPAREN if (tok == vector_end(preproc, cond)) { log_error(&vector_last(preproc, cond).loginfo, "missing closing parenthesis in #if expression\n"); goto eval_fail; } vector_last(ppeaux, stack).n_colons = 0; goto eval_rparen; } case SYM_LBRACKET: case SYM_RBRACKET: case SYM_LSQBRACKET: case SYM_RSQBRACKET: case SYM_HASH: case SYM_HASHHASH: case SYM_SEMICOLON: case SYM_COLONCOLON: case SYM_VARIADIC: case SYM_DOT: case SYM_DASHGT: case SYM_EQ: case SYM_PLUSEQ: case SYM_DASHEQ: case SYM_STAREQ: case SYM_SLASHEQ: case SYM_PERCENTEQ: case SYM_HATEQ: case SYM_AMPEQ: case SYM_PIPEEQ: case SYM_LTLTEQ: case SYM_GTGTEQ: case SYM_PLUSPLUS: case SYM_DASHDASH: case SYM_COMMA: default: log_error(&tok->loginfo, "invalid symbol ID %u (%s) during #if evaluation\n", tok->tokv.sym, (tok->tokv.sym <= LAST_SYM) ? sym2str[tok->tokv.sym] : "invalid"); goto eval_fail; } // invalid_binop: unexpected binary operation // add_binop: add a binary operation if (0) { invalid_binop: log_error(&tok->loginfo, "unexpected binary operator '%s' in #if expression\n", sym2str[tok->tokv.sym]); goto eval_fail; add_binop: if (op_lvl < 2) { log_internal(&tok->loginfo, "invalid op_lvl %d < 2 during #if evaluation\n", op_lvl); goto eval_fail; } if (op_lvl >= OPLVL_ALL) { log_internal(&tok->loginfo, "invalid op_lvl %d > OPLVL_ALL during #if evaluation\n", op_lvl); goto eval_fail; } success = 1; acc = eval_stack(&vector_last(ppeaux, stack), li0, op_lvl, op_typ, is_unsigned, &success); if (!success) { goto eval_fail; } if (op_lvl < OPLVL_BAN) { // No post-processing required goto done_partial_eval; } if (op_lvl == OPLVL_BAN) { // op_typ == OPLVL_BAN vector_last(ppeaux, stack).st_bool = 1; if (acc) goto done_partial_eval; // We have five possibilities: // [0] && x ~> 0 // [0] && x) ~> 0) // [0] && x ? y : z ~> z // [0] && x : y ~> 0 : y ~> 0 // [0] && x || y ~> y // We are at the '&&'; note that we know the top of the stack has no pending operation unsigned nparens = 0; for (++tok; tok < vector_end(preproc, cond); ++tok) { if ((tok->tokt == PPTOK_SYM) && (tok->tokv.sym == SYM_LPAREN)) { ++nparens; } else if ((tok->tokt == PPTOK_SYM) && (tok->tokv.sym == SYM_RPAREN)) { if (nparens) --nparens; else { if (vector_size(ppeaux, stack) == 1) { log_error(&tok->loginfo, "unexpected symbol ')' (parenthesis not opened) during #if evaluation\n"); goto eval_fail; } if (vector_last(ppeaux, stack).n_colons) { log_error(&tok->loginfo, "unexpected symbol ')', expected symbol ':' during #if evaluation\n"); goto eval_fail; } goto eval_rparen_acc; } } else if ((tok->tokt == PPTOK_SYM) && (tok->tokv.sym == SYM_QUESTION)) { if (!nparens) { goto eval_question_acc; } } else if ((tok->tokt == PPTOK_SYM) && (tok->tokv.sym == SYM_COLON)) { if (!nparens) { if (!vector_last(ppeaux, stack).n_colons) { log_error(&tok->loginfo, "unexpected symbol ':' during #if evaluation\n"); goto eval_fail; } goto eval_colon_acc; } } else if ((tok->tokt == PPTOK_SYM) && (tok->tokv.sym == SYM_PIPEPIPE)) { if (!nparens) { goto done_partial_eval; } } } if (nparens) { log_error(&vector_last(preproc, cond).loginfo, "expected symbol ')' during #if evaluation\n"); goto eval_fail; } if (vector_last(ppeaux, stack).n_colons) { log_error(&vector_last(preproc, cond).loginfo, "expected symbol ':' during #if evaluation\n"); goto eval_fail; } if (vector_size(ppeaux, stack) != 1) { log_error(&vector_last(preproc, cond).loginfo, "expected symbol ')' during #if evaluation\n"); goto eval_fail; } goto done_complete_acc; } if (op_lvl == OPLVL_BOR) { // op_typ == OPLVL_BOR vector_last(ppeaux, stack).st_bool = 1; if (!acc) goto done_partial_eval; // We have four possibilities: // [!0] || x ~> 1 // [!0] || x) ~> 1) // [!0] || x ? y : z ~> z // [!0] || x : y ~> 1 : y ~> 1 // We are at the '||'; note that we know the top of the stack has no pending operation unsigned nparens = 0; for (++tok; tok < vector_end(preproc, cond); ++tok) { if ((tok->tokt == PPTOK_SYM) && (tok->tokv.sym == SYM_LPAREN)) { ++nparens; } else if ((tok->tokt == PPTOK_SYM) && (tok->tokv.sym == SYM_RPAREN)) { if (nparens) --nparens; else { if (vector_size(ppeaux, stack) == 1) { log_error(&tok->loginfo, "unexpected symbol ')' (parenthesis not opened) during #if evaluation\n"); goto eval_fail; } if (vector_last(ppeaux, stack).n_colons) { log_error(&tok->loginfo, "unexpected symbol ')', expected symbol ':' during #if evaluation\n"); goto eval_fail; } acc = 1; goto eval_rparen_acc; } } else if ((tok->tokt == PPTOK_SYM) && (tok->tokv.sym == SYM_QUESTION)) { if (!nparens) { acc = 1; goto eval_question_acc; } } else if ((tok->tokt == PPTOK_SYM) && (tok->tokv.sym == SYM_COLON)) { if (!nparens) { if (!vector_last(ppeaux, stack).n_colons) { log_error(&tok->loginfo, "unexpected symbol ':' during #if evaluation\n"); goto eval_fail; } acc = 1; goto eval_colon_acc; } } } if (nparens) { log_error(&vector_last(preproc, cond).loginfo, "expected symbol ')' during #if evaluation\n"); goto eval_fail; } if (vector_last(ppeaux, stack).n_colons) { log_error(&vector_last(preproc, cond).loginfo, "expected symbol ':' during #if evaluation\n"); goto eval_fail; } if (vector_size(ppeaux, stack) != 1) { log_error(&vector_last(preproc, cond).loginfo, "expected symbol ')' during #if evaluation\n"); goto eval_fail; } acc = 1; goto done_complete_acc; } log_internal(&li0, "invalid op_lvl %d during #if evaluation\n", op_lvl); goto eval_fail; } done_partial_eval: } else { log_error(&tok->loginfo, "invalid token type %u during #if evaluation\n", tok->tokt); goto eval_fail; } // push_acc_to_st0: evaluate all held st1 operations on acc, then set st0 to 1 and v0 to acc if (0) { push_acc_to_st0: while (vector_last(ppeaux, stack).st1) { // Only symbols, so this should be safe enum token_sym_type_e sym = vector_last(ppeaux, stack).v1[--vector_last(ppeaux, stack).st1].tokv.sym; if (sym == SYM_PLUS) {} else if (sym == SYM_DASH) acc = -acc; else if (sym == SYM_EXCL) acc = !acc; else if (sym == SYM_TILDE) acc = ~acc; else { log_internal(&vector_last(ppeaux, stack).v1[vector_last(ppeaux, stack).st1].loginfo, "unknown level 1 unary operator sym ID %u\n", sym); } } vector_last(ppeaux, stack).v0 = acc; vector_last(ppeaux, stack).st0 = 1; } } if (vector_size(ppeaux, stack) != 1) { log_error(&vector_last(preproc, cond).loginfo, "expected symbol ')' during #if evaluation\n"); goto eval_fail; } done_complete_stack: success = 1; acc = eval_stack(&vector_last(ppeaux, stack), li0, OPLVL_ALS, 0, is_unsigned, &success); if (!success) { goto eval_fail; } done_complete_acc: vector_del(ppeaux, stack); *aux_ret = 1; return acc; } int proc_unget_token(preproc_t *src, proc_token_t *tok) { return vector_push(ppsource, src->prep, ((ppsource_t){ .srct = PPSRC_PTOKEN, .srcv.ptok = *tok })); } static proc_token_t proc_next_token_aux(preproc_t *src) { if (!vector_size(ppsource, src->prep)) { return (proc_token_t){ .tokt = PTOK_EOF, .loginfo = { 0 }, .tokv = {.c = (char)EOF} }; } if (vector_last(ppsource, src->prep).srct == PPSRC_PTOKEN) { proc_token_t ret = vector_last(ppsource, src->prep).srcv.ptok; vector_pop_nodel(ppsource, src->prep); return ret; } check_if_depth: { if (!vector_size(ppsource, src->prep)) { return (proc_token_t){ .tokt = PTOK_EOF, .loginfo = { 0 }, .tokv = {.c = (char)EOF} }; } ppsource_t *ppsrc = &vector_last(ppsource, src->prep); if ((ppsrc->srct == PPSRC_PREPARE) && (ppsrc->srcv.prep.ok_depth != ppsrc->srcv.prep.cond_depth)) { // Ignore all tokens from ppsrc until: // INVALID -------------- abort (return PTOK_INVALID) // EOF ------------------ print an error, then pop and goto check_if_depth // # if/elif/endif/... -- parse, execute, goto check_if_depth // Also, track newlines to keep src->st up-to-date while (1) { preproc_token_t tok = ppsrc_next_token(src); skip_cur_token: if (tok.tokt == PPTOK_INVALID) { src->st = PPST_NONE; proc_token_t ret; ret.tokt = PTOK_INVALID; ret.loginfo = tok.loginfo; ret.tokv.c = tok.tokv.c; return ret; } else if (tok.tokt == PPTOK_NEWLINE) { src->st = PPST_NL; } else if (tok.tokt == PPTOK_EOF) { log_warning(&tok.loginfo, "file ended before closing all conditionals (ignoring)\n"); vector_pop(ppsource, src->prep); goto check_if_depth; } else if ((tok.tokt == PPTOK_SYM) && (src->st == PPST_NL) && (tok.tokv.sym == SYM_HASH)) { src->st = PPST_NONE; tok = ppsrc_next_token(src); if ((tok.tokt == PPTOK_NEWLINE) || (tok.tokt == PPTOK_EOF)) { // Empty preprocessor command src->st = PPST_NL; continue; } if ((tok.tokt != PPTOK_IDENT) && (tok.tokt != PPTOK_IDENT_UNEXP)) goto preproc_ignore_remaining; if (!strcmp(string_content(tok.tokv.str), "include")) goto preproc_ignore_remaining; else if (!strcmp(string_content(tok.tokv.str), "include_next")) goto preproc_ignore_remaining; else if (!strcmp(string_content(tok.tokv.str), "define")) goto preproc_ignore_remaining; else if (!strcmp(string_content(tok.tokv.str), "undef")) goto preproc_ignore_remaining; else if (!strcmp(string_content(tok.tokv.str), "error")) goto preproc_ignore_remaining; else if (!strcmp(string_content(tok.tokv.str), "warning")) goto preproc_ignore_remaining; // C23/extension else if (!strcmp(string_content(tok.tokv.str), "pragma")) goto preproc_ignore_remaining; else if (!strcmp(string_content(tok.tokv.str), "line")) goto preproc_ignore_remaining; else if (!strcmp(string_content(tok.tokv.str), "if")) { // Increase cond_depth (we already know we are ignoring the content) ++ppsrc->srcv.prep.cond_depth; goto preproc_ignore_remaining; } else if (!strcmp(string_content(tok.tokv.str), "ifdef")) { // Increase cond_depth (we already know we are ignoring the content) ++ppsrc->srcv.prep.cond_depth; goto preproc_ignore_remaining; } else if (!strcmp(string_content(tok.tokv.str), "ifndef")) { // Increase cond_depth (we already know we are ignoring the content) ++ppsrc->srcv.prep.cond_depth; goto preproc_ignore_remaining; } else if (!strcmp(string_content(tok.tokv.str), "elif")) { if ((ppsrc->srcv.prep.ok_depth == ppsrc->srcv.prep.cond_depth - 1) && !ppsrc->srcv.prep.entered_next_ok_cond) { string_del(tok.tokv.str); VECTOR(preproc) *cond = vector_new(preproc); if (!cond) { log_memory("failed to allocate #elif condition vector\n"); src->st = PPST_NONE; return (proc_token_t){ .tokt = PTOK_INVALID, .loginfo = tok.loginfo, .tokv = {.c = '\0'} }; } tok = ppsrc_next_token(src); loginfo_t li = tok.loginfo; while ((tok.tokt != PPTOK_NEWLINE) && (tok.tokt != PPTOK_EOF) && (tok.tokt != PPTOK_INVALID)) { li.lineno_end = tok.loginfo.lineno_end ? tok.loginfo.lineno_end : tok.loginfo.lineno; li.colno_end = tok.loginfo.colno_end ? tok.loginfo.colno_end : tok.loginfo.colno; if (!vector_push(preproc, cond, tok)) { log_memory("failed to add token to #elif condition vector\n"); vector_del(preproc, cond); src->st = PPST_NONE; return (proc_token_t){ .tokt = PTOK_INVALID, .loginfo = tok.loginfo, .tokv = {.c = '\0'} }; } tok = ppsrc_next_token(src); } vector_trim(preproc, cond); khash_t(string_set) *solved_macros = kh_init(string_set); if (!solved_macros) { log_memory("failed to allocate #elif solved_macros set\n"); vector_del(preproc, cond); src->st = PPST_NONE; return (proc_token_t){ .tokt = PTOK_INVALID, .loginfo = tok.loginfo, .tokv = {.c = '\0'} }; } VECTOR(preproc) *expanded = preproc_do_expand(&li, src->macros_map, cond, solved_macros, src->macros_used); vector_del(preproc, cond); macros_set_del(solved_macros); if (!expanded) { // Not required as preproc_do_expand already prints an error, but this can be useful log_error(&li, "failed to expand #elif condition\n"); src->st = PPST_NONE; return (proc_token_t){ .tokt = PTOK_INVALID, .loginfo = tok.loginfo, .tokv = {.c = '\0'} }; } // Now we need to compute what is pointed by expanded, and increase cond_depth and ok_depth as needed int st; int64_t res = preproc_eval(expanded, &st, src->target->size_long == 4); vector_del(preproc, expanded); if (!st) { // Not required as preproc_eval already prints an error, but this can be useful log_error(&li, "failed to evaluate #elif condition\n"); src->st = PPST_NONE; return (proc_token_t){ .tokt = PTOK_INVALID, .tokv = {.c = '\0'} }; } if (res) { ++vector_last(ppsource, src->prep).srcv.prep.ok_depth; if (tok.tokt == PPTOK_NEWLINE) { src->st = PPST_NL; goto check_next_token; } } else { if (tok.tokt == PPTOK_NEWLINE) { src->st = PPST_NL; continue; } } if (tok.tokt == PPTOK_EOF) { log_warning(&tok.loginfo, "file ended before closing all conditionals (ignoring)\n"); vector_pop(ppsource, src->prep); src->st = PPST_NL; // Should be redundant since TOK_NEWLINE is added before TOK_EOF if required // EOF has an empty destructor // Note that since we have opened the file, the previous file had ok_depth == cond_depth goto check_next_token; } else /* if (tok.tokt == PPTOK_INVALID) */ { src->st = PPST_NONE; return (proc_token_t){ .tokt = PTOK_INVALID, .tokv.c = tok.tokv.c }; } } else goto preproc_ignore_remaining; } else if (!strcmp(string_content(tok.tokv.str), "elifdef")) { string_del(tok.tokv.str); tok = ppsrc_next_token(src); if ((tok.tokt != PPTOK_IDENT) && (tok.tokt != PPTOK_IDENT_UNEXP)) { log_error(&tok.loginfo, "invalid token type %u after '#elifdef' preprocessor command\n", tok.tokt); goto preproc_ignore_remaining; } if ((ppsrc->srcv.prep.ok_depth == ppsrc->srcv.prep.cond_depth - 1) && !ppsrc->srcv.prep.entered_next_ok_cond) { // We may enter the following block, check it khiter_t it = kh_get(macros_map, src->macros_map, string_content(tok.tokv.str)); if (it != kh_end(src->macros_map)) { ++ppsrc->srcv.prep.ok_depth; goto preproc_ignore_remaining_goto; } goto preproc_ignore_remaining; } } else if (!strcmp(string_content(tok.tokv.str), "elifndef")) { string_del(tok.tokv.str); tok = ppsrc_next_token(src); if ((tok.tokt != PPTOK_IDENT) && (tok.tokt != PPTOK_IDENT_UNEXP)) { log_error(&tok.loginfo, "invalid token type %u after '#elifdef' preprocessor command\n", tok.tokt); goto preproc_ignore_remaining; } if ((ppsrc->srcv.prep.ok_depth == ppsrc->srcv.prep.cond_depth - 1) && !ppsrc->srcv.prep.entered_next_ok_cond) { // We may enter the following block, check it khiter_t it = kh_get(macros_map, src->macros_map, string_content(tok.tokv.str)); if (it == kh_end(src->macros_map)) { ++ppsrc->srcv.prep.ok_depth; goto preproc_ignore_remaining_goto; } goto preproc_ignore_remaining; } } else if (!strcmp(string_content(tok.tokv.str), "else")) { // Maybe increase ok_depth if ok_depth = cond_depth - 1; also goto check_if_depth // Also, we only need to goto check_if_depth if we actually update ok_depth // Note that this (very naive) approach allows code such as: /* #ifdef M * ... // Not preprocessed *** Preprocessed * #else * ... // Preprocessed *** Not preprocessed * #else * ... // Not preprocessed due to unrelated code *** Not preprocessed * #else * ... // Preprocessed *** Not preprocessed * #endif */ // Forbidding this code would require a 256-bits bitfield, which is big, thus not implemented. if ((ppsrc->srcv.prep.ok_depth == ppsrc->srcv.prep.cond_depth - 1) && !ppsrc->srcv.prep.entered_next_ok_cond) { ++ppsrc->srcv.prep.ok_depth; goto preproc_ignore_remaining_goto; } else goto preproc_ignore_remaining; } else if (!strcmp(string_content(tok.tokv.str), "endif")) { // Decrease cond_depth; also goto check_if_depth // Note that 0 <= ppsrc->srcv.prep.ok_depth < ppsrc->srcv.prep.cond_depth, so this code is OK --ppsrc->srcv.prep.cond_depth; goto preproc_ignore_remaining_goto; } log_warning(&tok.loginfo, "Unknown ignored pp command %s, skipping until EOL\n", string_content(tok.tokv.str)); preproc_ignore_remaining: while ((tok.tokt != PPTOK_NEWLINE) && (tok.tokt != PPTOK_EOF) && (tok.tokt != PPTOK_INVALID)) { preproc_token_del(&tok); tok = ppsrc_next_token(src); } if (tok.tokt == PPTOK_NEWLINE) { src->st = PPST_NL; } goto skip_cur_token; preproc_ignore_remaining_goto: while ((tok.tokt != PPTOK_NEWLINE) && (tok.tokt != PPTOK_EOF) && (tok.tokt != PPTOK_INVALID)) { preproc_token_del(&tok); tok = ppsrc_next_token(src); } if (tok.tokt == PPTOK_NEWLINE) { src->st = PPST_NL; } goto check_if_depth; } else { src->st = PPST_NONE; preproc_token_del(&tok); } } } } proc_token_t ret; check_next_token: if (!vector_size(ppsource, src->prep)) { ret.tokt = PTOK_EOF; ret.loginfo = (loginfo_t){0}; ret.tokv.c = (char)EOF; return ret; } start_next_token: preproc_token_t tok = ppsrc_next_token(src); start_cur_token: switch (tok.tokt) { case PPTOK_INVALID: src->st = PPST_NONE; ret.tokt = PTOK_INVALID; ret.loginfo = tok.loginfo; ret.tokv.c = tok.tokv.c; return ret; case PPTOK_IDENT: src->st = (src->st == PPST_PRAGMA_EXPLICIT) ? PPST_PRAGMA_EXPLICIT : PPST_NONE; { khiter_t it = kh_get(macros_map, src->macros_map, string_content(tok.tokv.str)); if (it != kh_end(src->macros_map)) { macro_t *m = &kh_val(src->macros_map, it); int need_solve = !m->is_funlike; VECTOR(preprocs) *margs = NULL; loginfo_t li = tok.loginfo; if (m->is_funlike) { preproc_token_t tok2 = ppsrc_next_token(src); size_t nnls = 0; while (tok2.tokt == PPTOK_NEWLINE) { ++nnls; tok2 = ppsrc_next_token(src); } if ((tok2.tokt == PPTOK_SYM) && (tok2.tokv.sym == SYM_LPAREN)) { need_solve = 1; margs = vector_new(preprocs); if (!margs) goto solve_err_mem; VECTOR(preproc) *marg = vector_new(preproc); if (!marg) goto solve_err_mem; while (need_solve && (tok2.tokt != PPTOK_EOF) && (tok2.tokt != PPTOK_INVALID)) { tok2 = ppsrc_next_token(src); if ((need_solve == 1) && (tok2.tokt == PPTOK_SYM) && (tok2.tokv.sym == SYM_COMMA)) { // Possible optimization: emplace NULL if vector_size(marg) == 0 // This would avoid allocating a new vector vector_trim(preproc, marg); if (!vector_push(preprocs, margs, marg)) { vector_del(preproc, marg); goto solve_err_mem; } marg = vector_new(preproc); if (!marg) goto solve_err_mem; } else if (tok2.tokt != PPTOK_NEWLINE) { if (!vector_push(preproc, marg, tok2)) { vector_del(preproc, marg); goto solve_err_mem; } } if ((tok2.tokt == PPTOK_SYM) && (tok2.tokv.sym == SYM_LPAREN)) ++need_solve; else if ((tok2.tokt == PPTOK_SYM) && (tok2.tokv.sym == SYM_RPAREN)) --need_solve; } li.lineno_end = tok2.loginfo.lineno_end ? tok2.loginfo.lineno_end : tok2.loginfo.lineno; li.colno_end = tok2.loginfo.colno_end ? tok2.loginfo.colno_end : tok2.loginfo.colno; if (need_solve) { log_error(&li, "unfinished function-like macro %s\n", string_content(tok.tokv.str)); vector_del(preprocs, margs); vector_del(preproc, marg); string_del(tok.tokv.str); src->st = PPST_NONE; ret.tokt = PTOK_INVALID; ret.loginfo = tok2.loginfo; ret.tokv.c = tok.tokv.c; return ret; } // margs finishes with a SYM_RPAREN token vector_pop(preproc, marg); vector_trim(preproc, marg); if (!vector_push(preprocs, margs, marg)) { vector_del(preproc, marg); goto solve_err_mem; } need_solve = 1; } else { if (!vector_reserve(ppsource, src->prep, vector_size(ppsource, src->prep) + nnls + 1)) { log_memory("undoing lookahead for macro use %s\n", string_content(tok.tokv.str)); string_del(tok.tokv.str); preproc_token_del(&tok2); src->st = PPST_NONE; ret.tokt = PTOK_INVALID; ret.loginfo = li; ret.tokv.c = '\0'; return ret; } vector_push(ppsource, src->prep, ((ppsource_t){.srct = PPSRC_PPTOKEN, .srcv.pptok = tok2})); while (nnls--) { vector_push( ppsource, src->prep, ((ppsource_t){.srct = PPSRC_PPTOKEN, .srcv.pptok = {.tokt = PPTOK_NEWLINE, .tokv.c = '\n'}})); } } } if (need_solve) { khash_t(string_set) *solved_macros = kh_init(string_set); if (!solved_macros) goto solve_err_mem; char *mname = string_steal(tok.tokv.str); VECTOR(preproc) *solved = preproc_solve_macro(&li, src->macros_map, mname, m, margs, solved_macros, NULL); if (margs) vector_del(preprocs, margs); macros_set_del(solved_macros); if (!solved) { src->st = PPST_NONE; ret.tokt = PTOK_INVALID; ret.loginfo = li; ret.tokv.c = '\0'; return ret; } // If the expansion is empty, don't push it if (vector_size(preproc, solved)) { if (!vector_push(ppsource, src->prep, ((ppsource_t){.srct = PPSRC_PPTOKENS, .srcv.pptoks = {.idx = 0, .toks = solved}}))) { log_memory("pushing expanded macro\n"); vector_del(preproc, solved); src->st = PPST_NONE; ret.tokt = PTOK_INVALID; ret.loginfo = li; ret.tokv.c = '\0'; return ret; } } else { vector_del(preproc, solved); } // src->prep is not empty (either through the push or by not popping the top) goto start_next_token; } else { if (margs) vector_del(preprocs, margs); } if (0) { solve_err_mem: log_memory("parsing macro use %s\n", string_content(tok.tokv.str)); if (margs) vector_del(preprocs, margs); string_del(tok.tokv.str); src->st = PPST_NONE; ret.tokt = PTOK_INVALID; ret.loginfo = li; ret.tokv.c = '\0'; return ret; } } } /* FALLTHROUGH */ case PPTOK_IDENT_UNEXP: { khiter_t it = kh_get(str2kw, str2kw, string_content(tok.tokv.str)); if (it == kh_end(str2kw)) { ret.tokt = PTOK_IDENT; ret.tokv.str = tok.tokv.str; } else { string_del(tok.tokv.str); ret.tokt = PTOK_KEYWORD; ret.tokv.kw = kh_val(str2kw, it); } ret.loginfo = tok.loginfo; return ret; } case PPTOK_NUM: src->st = (src->st == PPST_PRAGMA_EXPLICIT) ? PPST_PRAGMA_EXPLICIT : PPST_NONE; ret.tokt = PTOK_NUM; ret.loginfo = tok.loginfo; ret.tokv.str = tok.tokv.str; return ret; case PPTOK_STRING: src->st = (src->st == PPST_PRAGMA_EXPLICIT) ? PPST_PRAGMA_EXPLICIT : PPST_NONE; ret.tokt = PTOK_STRING; ret.loginfo = tok.loginfo; ret.tokv = (union proc_token_val_u){.sstr = tok.tokv.sstr, .sisstr = tok.tokv.sisstr}; return ret; case PPTOK_INCL: src->st = PPST_NONE; ret.tokt = PTOK_INVALID; ret.loginfo = tok.loginfo; ret.tokv.c = tok.tokv.sisstr ? '<' : '"'; string_del(tok.tokv.sstr); return ret; case PPTOK_SYM: if ((src->st == PPST_NL) && (tok.tokv.sym == SYM_HASH)) { tok = ppsrc_next_token(src); if ((tok.tokt == PPTOK_NEWLINE) || (tok.tokt == PPTOK_EOF)) { // Empty preprocessor command goto start_cur_token; } if ((tok.tokt != PPTOK_IDENT) && (tok.tokt != PPTOK_IDENT_UNEXP)) goto preproc_hash_err; if (!strcmp(string_content(tok.tokv.str), "include") || !strcmp(string_content(tok.tokv.str), "include_next")) { int is_next = string_content(tok.tokv.str)[7] == '_'; string_del(tok.tokv.str); tok = ppsrc_next_token(src); loginfo_t li = tok.loginfo; string_t *incl_file; int is_sys; if (tok.tokt == PPTOK_INCL) { incl_file = tok.tokv.sstr; // Assume we only have one #include "..." path, so include_next is always a system include is_sys = is_next || !tok.tokv.sisstr; tok = ppsrc_next_token(src); // Token was moved loginfo_t ignored_infos = tok.loginfo; int has_ignored = 0; while ((tok.tokt != PPTOK_NEWLINE) && (tok.tokt != PPTOK_EOF) && (tok.tokt != PPTOK_INVALID)) { has_ignored = 1; ignored_infos.lineno_end = tok.loginfo.lineno_end ? tok.loginfo.lineno_end : tok.loginfo.lineno; ignored_infos.colno_end = tok.loginfo.colno_end ? tok.loginfo.colno_end : tok.loginfo.colno; preproc_token_del(&tok); tok = ppsrc_next_token(src); } if (has_ignored) { log_warning(&ignored_infos, "ignored tokens after #include%s directive\n", is_next ? "_next" : ""); } } else { // Expand macro, then try again VECTOR(preproc) *incl = vector_new(preproc); if (!incl) { log_memory("failed to allocate #include tokens vector\n"); src->st = PPST_NONE; ret.tokt = PTOK_INVALID; ret.loginfo = tok.loginfo; ret.tokv.c = '\0'; return ret; } while ((tok.tokt != PPTOK_NEWLINE) && (tok.tokt != PPTOK_EOF) && (tok.tokt != PPTOK_INVALID)) { if (!vector_push(preproc, incl, tok)) { log_memory("failed to add token to #include tokens vector\n"); vector_del(preproc, incl); src->st = PPST_NONE; ret.tokt = PTOK_INVALID; ret.loginfo = tok.loginfo; ret.tokv.c = '\0'; return ret; } li.lineno_end = tok.loginfo.lineno_end ? tok.loginfo.lineno_end : tok.loginfo.lineno; li.colno_end = tok.loginfo.colno_end ? tok.loginfo.colno_end : tok.loginfo.colno; tok = ppsrc_next_token(src); } vector_trim(preproc, incl); khash_t(string_set) *solved_macros = kh_init(string_set); if (!solved_macros) { log_memory("failed to allocate #include solved_macros set\n"); vector_del(preproc, incl); src->st = PPST_NONE; ret.tokt = PTOK_INVALID; ret.loginfo = tok.loginfo; ret.tokv.c = '\0'; return ret; } VECTOR(preproc) *expanded = preproc_do_expand(&li, src->macros_map, incl, solved_macros, src->macros_used); vector_del(preproc, incl); macros_set_del(solved_macros); if (!expanded) { log_error(&li, "failed to expand #include tokens\n"); src->st = PPST_NONE; ret.tokt = PTOK_INVALID; ret.tokv.c = '\0'; return ret; } // Now we need to check what is pointed by expanded if (!vector_size(preproc, expanded)) { log_error(&li, "missing #include name\n"); src->st = PPST_NONE; ret.tokt = PTOK_INVALID; ret.loginfo = tok.loginfo; ret.tokv.c = '\0'; return ret; } if (vector_content(preproc, expanded)[0].tokt == PPTOK_STRING) { is_sys = is_next || src->is_sys; preproc_token_t *exp = vector_content(preproc, expanded); log_error(&li, "TODO: #include \n", string_content(exp->tokv.sstr), exp->tokv.sisstr); vector_del(preproc, expanded); src->st = PPST_NONE; ret.tokt = PTOK_INVALID; ret.loginfo = tok.loginfo; ret.tokv.c = '\0'; return ret; } else if ((vector_content(preproc, expanded)[0].tokt == PPTOK_SYM) && (vector_content(preproc, expanded)[0].tokv.sym == SYM_LT) && (vector_last(preproc, expanded).tokt == PPTOK_SYM) && (vector_last(preproc, expanded).tokv.sym == SYM_GT) && (vector_size(preproc, expanded) >= 3)) { log_warning(&li, "#include command with macro expansion, assuming no space is present in the file name\n"); is_sys = 0; incl_file = string_new(); for (vector_preproc_elem *tok2 = expanded->content + 1; tok2 < expanded->content + expanded->vsize - 1; ++tok2) { switch (tok2->tokt) { case PPTOK_IDENT: case PPTOK_IDENT_UNEXP: if (!string_add_string(incl_file, tok2->tokv.str)) { log_memory("failed to add ident to include string\n"); vector_del(preproc, expanded); string_del(incl_file); src->st = PPST_NONE; ret.tokt = PTOK_INVALID; ret.loginfo = tok.loginfo; ret.tokv.c = '\0'; return ret; } break; case PPTOK_SYM: if (!string_add_cstr(incl_file, sym2str[tok2->tokv.sym])) { log_memory("failed to add symbol to include string\n"); vector_del(preproc, expanded); string_del(incl_file); src->st = PPST_NONE; ret.tokt = PTOK_INVALID; ret.loginfo = tok.loginfo; ret.tokv.c = '\0'; return ret; } break; case PPTOK_INVALID: case PPTOK_NUM: case PPTOK_STRING: case PPTOK_INCL: case PPTOK_NEWLINE: case PPTOK_BLANK: case PPTOK_START_LINE_COMMENT: case PPTOK_EOF: default: log_error(&li, "TODO: add token type %u to include string\n", tok2->tokt); vector_del(preproc, expanded); string_del(incl_file); src->st = PPST_NONE; ret.tokt = PTOK_INVALID; ret.loginfo = tok.loginfo; ret.tokv.c = '\0'; return ret; } } vector_del(preproc, expanded); } else { log_error(&li, "invalid #include command (macro expansion does not result in string or <...>)\n"); vector_del(preproc, expanded); src->st = PPST_NONE; ret.tokt = PTOK_INVALID; ret.loginfo = tok.loginfo; ret.tokv.c = '\0'; return ret; } } // cur_pathno == 0 if cur_file was from an #include "...", otherwise idx + 1 #ifdef LOG_INCLUDE printf("Opening %s as %s from system path %zu\n", string_content(incl_file), is_sys ? "system header" : "cur header", is_next ? src->cur_pathno : 0); #endif if ((is_sys || !try_open_dir(src, incl_file)) && !try_open_sys(src, incl_file, is_next ? src->cur_pathno : 0)) { log_error(&li, "failed to open %s\n", string_content(incl_file)); string_del(incl_file); src->st = PPST_NONE; ret.tokt = PTOK_INVALID; ret.loginfo = li; ret.tokv.c = is_sys ? '<' : '"'; return ret; } string_del(incl_file); if (tok.tokt == PPTOK_NEWLINE) goto check_next_token; else goto start_cur_token; } else if (!strcmp(string_content(tok.tokv.str), "define")) { string_del(tok.tokv.str); tok = ppsrc_next_token(src); if ((tok.tokt != PPTOK_IDENT) && (tok.tokt != PPTOK_IDENT_UNEXP)) { log_error(&tok.loginfo, "invalid token type %u after '#define' preprocessor command\n", tok.tokt); goto preproc_hash_err; } string_t *defname = tok.tokv.str; macro_t m = (macro_t){ .is_funlike = 0, .has_varargs = 0, .nargs = 0, .toks = vector_new(mtoken) }; if (!m.toks) { log_memory("failed to allocate token vector for macro %s\n", string_content(defname)); string_del(defname); // Token is now freed ret.tokt = PTOK_INVALID; ret.loginfo = tok.loginfo; ret.tokv.c = '\0'; return ret; } khash_t(argid_map) *args = NULL; tok = ppsrc_next_token(src); if ((tok.tokt == PPTOK_SYM) && (tok.tokv.sym == SYM_LPAREN)) { m.is_funlike = 1; args = kh_init(argid_map); if (!args) { log_memory("failed to allocate args map for macro %s\n", string_content(defname)); string_del(defname); // Token is now freed vector_del(mtoken, m.toks); ret.tokt = PTOK_INVALID; ret.loginfo = tok.loginfo; ret.tokv.c = '\0'; return ret; } m.nargs = 0; tok = ppsrc_next_token(src); int ok; if ((tok.tokt == PPTOK_SYM) && (tok.tokv.sym == SYM_RPAREN)) { ok = 1; } else { ok = 0; while ((tok.tokt != PPTOK_NEWLINE) && (tok.tokt != PPTOK_EOF) && (tok.tokt != PPTOK_INVALID)) { if ((tok.tokt == PPTOK_SYM) && (tok.tokv.sym == SYM_VARIADIC)) { m.has_varargs = 1; int kh_ret; char *tok_str = strdup("__VA_ARGS__"); khiter_t kh_k = kh_put(argid_map, args, tok_str, &kh_ret); // Moves the string content if (kh_ret < 0) { // Failed to move, needs to free here log_memory("failed to push arg %s for macro %s\n", tok_str, string_content(defname)); string_del(defname); free(tok_str); vector_del(mtoken, m.toks); argid_map_del(args); ret.tokt = PTOK_INVALID; ret.loginfo = tok.loginfo; ret.tokv.c = '\0'; return ret; } if (kh_ret == 0) { log_error(&tok.loginfo, "duplicate argument name %s defining macro %s\n", tok_str, string_content(defname)); string_del(defname); vector_del(mtoken, m.toks); argid_map_del(args); goto preproc_hash_err; } kh_val(args, kh_k) = m.nargs; // Empty token destructor tok = ppsrc_next_token(src); if ((tok.tokt != PPTOK_SYM) || (tok.tokv.sym != SYM_RPAREN)) { log_error(&tok.loginfo, "invalid token type %u after variadic macro arguments definition\n", tok.tokt); string_del(defname); vector_del(mtoken, m.toks); argid_map_del(args); goto preproc_hash_err; } ok = 1; break; } else if ((tok.tokt == PPTOK_IDENT) || (tok.tokt == PPTOK_IDENT_UNEXP)) { int kh_ret; char *tok_str = string_steal(tok.tokv.str); khiter_t kh_k = kh_put(argid_map, args, tok_str, &kh_ret); // Moves the string content if (kh_ret < 0) { // Failed to move, needs to free here log_memory("failed to push arg %s for macro %s\n", tok_str, string_content(defname)); string_del(defname); free(tok_str); vector_del(mtoken, m.toks); argid_map_del(args); ret.tokt = PTOK_INVALID; ret.loginfo = tok.loginfo; ret.tokv.c = '\0'; return ret; } if (kh_ret == 0) { log_error(&tok.loginfo, "duplicate argument name %s defining macro %s\n", tok_str, string_content(defname)); string_del(defname); vector_del(mtoken, m.toks); argid_map_del(args); // Token was already deleted, create a fake token tok.tokt = PPTOK_SYM; tok.tokv.sym = LAST_SYM; goto preproc_hash_err; } kh_val(args, kh_k) = m.nargs++; // Token content is the string, which is moved to the vector tok = ppsrc_next_token(src); if ((tok.tokt == PPTOK_SYM) && (tok.tokv.sym == SYM_COMMA)) { // Empty destructor tok = ppsrc_next_token(src); } else if ((tok.tokt == PPTOK_SYM) && (tok.tokv.sym == SYM_RPAREN)) { // Empty destructor ok = 1; break; } else if ((tok.tokt == PPTOK_SYM) && (tok.tokv.sym == SYM_VARIADIC)) { m.has_varargs = 1; --m.nargs; // Empty token destructor tok = ppsrc_next_token(src); if ((tok.tokt != PPTOK_SYM) || (tok.tokv.sym != SYM_RPAREN)) { log_error(&tok.loginfo, "invalid token type %u after variadic macro arguments definition\n", tok.tokt); string_del(defname); vector_del(mtoken, m.toks); argid_map_del(args); goto preproc_hash_err; } ok = 1; break; } else { log_error(&tok.loginfo, "invalid %s type %u in macro arguments definition\n", (tok.tokt == PPTOK_SYM) ? "symbol" : "token", (tok.tokt == PPTOK_SYM) ? tok.tokv.sym : tok.tokt); string_del(defname); vector_del(mtoken, m.toks); argid_map_del(args); goto preproc_hash_err; } } else { log_error(&tok.loginfo, "invalid %s type %u as macro arguments definition name\n", (tok.tokt == PPTOK_SYM) ? "symbol" : "token", (tok.tokt == PPTOK_SYM) ? tok.tokv.sym : tok.tokt); string_del(defname); vector_del(mtoken, m.toks); argid_map_del(args); goto preproc_hash_err; } } } if (!ok) { log_error(&tok.loginfo, "invalid macro definition for %s\n", string_content(defname)); string_del(defname); vector_del(mtoken, m.toks); argid_map_del(args); goto preproc_hash_err; } // tok is ')', empty destructor tok = ppsrc_next_token(src); } if (tok.tokt == PPTOK_BLANK) { // BLANK has no destructor tok = ppsrc_next_token(src); } // All tokens until INVALID/NL/EOF are macro content int state = 0; #define ST_CONCAT 1 #define ST_STR 2 while ((tok.tokt != PPTOK_NEWLINE) && (tok.tokt != PPTOK_EOF) && (tok.tokt != PPTOK_INVALID)) { if ((tok.tokt == PPTOK_SYM) && (tok.tokv.sym == SYM_HASH)) { if (state & ST_STR) { log_warning(&tok.loginfo, "duplicated stringify in macro definition (defining %s)\n", string_content(defname)); } else { state |= ST_STR; } // Empty destructor tok = ppsrc_next_token(src); continue; } if ((tok.tokt == PPTOK_SYM) && (tok.tokv.sym == SYM_HASHHASH)) { if (state & ST_CONCAT) { log_warning(&tok.loginfo, "duplicated concatenation in macro definition (defining %s)\n", string_content(defname)); } else if (!vector_size(mtoken, m.toks)) { log_warning(&tok.loginfo, "invalid concatenation at start of macro definition (defining %s)\n", string_content(defname)); } else { state |= ST_CONCAT; } // Empty destructor tok = ppsrc_next_token(src); continue; } mtoken_t *mtok; unsigned argid = -1u; if (m.is_funlike && ((tok.tokt == PPTOK_IDENT) || (tok.tokt == PPTOK_IDENT_UNEXP))) { khiter_t kh_k = kh_get(argid_map, args, string_content(tok.tokv.str)); if (kh_k != kh_end(args)) { string_del(tok.tokv.str); // Token freed argid = kh_val(args, kh_k); } } if (argid != -1u) { mtok = mtoken_new_arg(argid, state & ST_STR); if (!mtok) { log_memory("failed to allocate new m-token (defining %s)\n", string_content(defname)); string_del(defname); macro_del(&m); src->st = PPST_NONE; ret.tokt = PTOK_INVALID; ret.loginfo = tok.loginfo; ret.tokv.c = tok.tokv.c; return ret; } state &= ~ST_STR; } else { mtok = mtoken_new_token(tok); // Token moved if (!mtok) { log_memory("failed to allocate new m-token (defining %s)\n", string_content(defname)); string_del(defname); macro_del(&m); if (tok.tokt == PPTOK_NEWLINE) goto check_next_token; else goto start_cur_token; } if (state & ST_STR) { log_warning(&tok.loginfo, "invalid stringify before token (defining %s)\n", string_content(defname)); state &= ~ST_STR; } } if (state & ST_CONCAT) { mtoken_t *mtok2 = vector_last(mtoken, m.toks); // Guaranteed to exist before setting ST_CONCAT mtok = mtoken_new_concat(mtok2, mtok); if (!mtok) { log_memory("failed to allocate new m-token concatenation (defining %s)\n", string_content(defname)); string_del(defname); macro_del(&m); vector_pop_nodel(mtoken, m.toks); src->st = PPST_NONE; ret.tokt = PTOK_INVALID; ret.loginfo = tok.loginfo; ret.tokv.c = tok.tokv.c; return ret; } vector_last(mtoken, m.toks) = mtok; state &= ~ST_CONCAT; } else { if (!vector_push(mtoken, m.toks, mtok)) { log_memory("failed to add m-token (defining %s)\n", string_content(defname)); string_del(defname); macro_del(&m); src->st = PPST_NONE; ret.tokt = PTOK_INVALID; ret.loginfo = tok.loginfo; ret.tokv.c = tok.tokv.c; return ret; } } // mtok moved to the vector tok = ppsrc_next_token(src); } #undef ST_CONCAT #undef ST_STR if (args) argid_map_del(args); if (tok.tokt == PPTOK_INVALID) { // Abort log_error(&tok.loginfo, "unexpected invalid input token\n"); string_del(defname); macro_del(&m); src->st = PPST_NONE; ret.tokt = PTOK_INVALID; ret.loginfo = tok.loginfo; ret.tokv.c = tok.tokv.c; return ret; } else { // NL and EOF have empty destructors khiter_t kh_k; int iret; char *mname_dup = string_steal(defname); kh_k = kh_put(string_set, src->macros_defined, mname_dup, &iret); if (iret < 0) { // Abort log_memory("failed to remember macro %s as defined, aborting\n", mname_dup); free(mname_dup); macro_del(&m); src->st = PPST_NONE; ret.tokt = PTOK_INVALID; ret.loginfo = tok.loginfo; ret.tokv.c = tok.tokv.c; return ret; } else if (iret > 0) { mname_dup = strdup(mname_dup); } kh_k = kh_put(macros_map, src->macros_map, mname_dup, &iret); if (iret < 0) { // Abort log_memory("failed to remember macro %s, aborting\n", mname_dup); free(mname_dup); macro_del(&m); src->st = PPST_NONE; ret.tokt = PTOK_INVALID; ret.loginfo = tok.loginfo; ret.tokv.c = tok.tokv.c; return ret; } else if (iret == 0) { // Ignore // Too noisy log_warning(&tok.loginfo, "Duplicated macro %s\n", mname_dup); free(mname_dup); macro_del(&m); } else { kh_val(src->macros_map, kh_k) = m; } if (tok.tokt == PPTOK_NEWLINE) goto check_next_token; else goto start_cur_token; } } else if (!strcmp(string_content(tok.tokv.str), "undef")) { string_del(tok.tokv.str); tok = ppsrc_next_token(src); if ((tok.tokt != PPTOK_IDENT) && (tok.tokt != PPTOK_IDENT_UNEXP)) { log_error(&tok.loginfo, "invalid token type %u after '#undef' preprocessor command\n", tok.tokt); goto preproc_hash_err; } string_t *mname = tok.tokv.str; tok = ppsrc_next_token(src); // Token was moved while ((tok.tokt != PPTOK_NEWLINE) && (tok.tokt != PPTOK_EOF) && (tok.tokt != PPTOK_INVALID)) { // TODO: Print warning 'ignored token(s)' preproc_token_del(&tok); tok = ppsrc_next_token(src); } khiter_t it = kh_get(macros_map, src->macros_map, string_content(mname)); if (it != kh_end(src->macros_map)) { #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wcast-qual" free((void*)kh_key(src->macros_map, it)); #pragma GCC diagnostic pop macro_del(&kh_val(src->macros_map, it)); kh_del(macros_map, src->macros_map, it); } string_del(mname); if (tok.tokt == PPTOK_NEWLINE) goto check_next_token; else goto start_cur_token; } else if (!strcmp(string_content(tok.tokv.str), "error")) { log_error(&tok.loginfo, "#error command:"); string_del(tok.tokv.str); tok = ppsrc_next_token(src); while ((tok.tokt != PPTOK_NEWLINE) && (tok.tokt != PPTOK_EOF) && (tok.tokt != PPTOK_INVALID)) { switch (tok.tokt) { case PPTOK_IDENT: case PPTOK_IDENT_UNEXP: printf(" %s", string_content(tok.tokv.str)); break; case PPTOK_SYM: printf("%s%s", (tok.tokv.sym == SYM_COMMA) ? "" : " ", sym2str[tok.tokv.sym]); break; case PPTOK_STRING: printf(" %c%s%c", tok.tokv.sisstr ? '"' : '\'', string_content(tok.tokv.sstr), tok.tokv.sisstr ? '"' : '\''); break; case PPTOK_INVALID: case PPTOK_NUM: case PPTOK_INCL: case PPTOK_NEWLINE: case PPTOK_BLANK: case PPTOK_START_LINE_COMMENT: case PPTOK_EOF: default: printf(" ", tok.tokt); } preproc_token_del(&tok); tok = ppsrc_next_token(src); } printf("\n"); vector_clear(ppsource, src->prep); ret.tokt = PTOK_INVALID; ret.loginfo = tok.loginfo; ret.tokv.c = (char)EOF; return ret; } else if (!strcmp(string_content(tok.tokv.str), "warning")) { log_warning(&tok.loginfo, "#warning command:"); tok = ppsrc_next_token(src); while ((tok.tokt != PPTOK_NEWLINE) && (tok.tokt != PPTOK_EOF) && (tok.tokt != PPTOK_INVALID)) { switch (tok.tokt) { case PPTOK_IDENT: case PPTOK_IDENT_UNEXP: printf(" %s", string_content(tok.tokv.str)); break; case PPTOK_SYM: printf("%s%s", (tok.tokv.sym == SYM_COMMA) ? "" : " ", sym2str[tok.tokv.sym]); break; case PPTOK_STRING: printf(" %c%s%c", tok.tokv.sisstr ? '"' : '\'', string_content(tok.tokv.sstr), tok.tokv.sisstr ? '"' : '\''); break; case PPTOK_INVALID: case PPTOK_NUM: case PPTOK_INCL: case PPTOK_NEWLINE: case PPTOK_BLANK: case PPTOK_START_LINE_COMMENT: case PPTOK_EOF: default: printf(" ", tok.tokt); } preproc_token_del(&tok); tok = ppsrc_next_token(src); } printf("\n"); goto preproc_hash_err; } else if (!strcmp(string_content(tok.tokv.str), "pragma")) { string_del(tok.tokv.str); tok = ppsrc_next_token(src); if ((tok.tokt != PPTOK_IDENT) && (tok.tokt != PPTOK_IDENT_UNEXP)) { log_error(&tok.loginfo, "unknown pragma directive, skipping until EOL\n"); goto preproc_hash_err; } else if (!strcmp(string_content(tok.tokv.str), "once")) { const char *fname = tok.loginfo.filename; if (!vector_push(ccharp, src->pragma_once, fname)) { log_memory("failed to add filename to #pragma once list\n"); vector_clear(ppsource, src->prep); ret.tokt = PTOK_INVALID; ret.loginfo = tok.loginfo; ret.tokv.c = (char)EOF; return ret; } string_del(tok.tokv.str); tok = ppsrc_next_token(src); goto preproc_hash_err; } else if (!strcmp(string_content(tok.tokv.str), "wrappers")) { string_del(tok.tokv.str); tok = ppsrc_next_token(src); if ((tok.tokt != PPTOK_IDENT) && (tok.tokt != PPTOK_IDENT_UNEXP)) { log_error(&tok.loginfo, "unknown pragma wrappers directive, skipping until EOL\n"); goto preproc_hash_err; } else if (!strcmp(string_content(tok.tokv.str), "allow_ints_ext")) { ret.loginfo = tok.loginfo; while ((tok.tokt != PPTOK_NEWLINE) && (tok.tokt != PPTOK_EOF) && (tok.tokt != PPTOK_INVALID)) { preproc_token_del(&tok); tok = ppsrc_next_token(src); } if (tok.tokt == PPTOK_INVALID) goto start_cur_token; else { ret.tokt = PTOK_PRAGMA; ret.tokv.pragma.typ = PRAGMA_ALLOW_INTS; return ret; } } else if (!strcmp(string_content(tok.tokv.str), "mark_simple")) { string_del(tok.tokv.str); tok = ppsrc_next_token(src); if ((tok.tokt != PPTOK_IDENT) && (tok.tokt != PPTOK_IDENT_UNEXP)) { log_error(&tok.loginfo, "invalid pragma wrappers explicit_simple directive, skipping until EOL\n"); goto preproc_hash_err; } src->st = PPST_NL; ret.tokt = PTOK_PRAGMA; ret.loginfo = tok.loginfo; ret.tokv.pragma.typ = PRAGMA_SIMPLE_SU; ret.tokv.pragma.val = tok.tokv.str; tok = ppsrc_next_token(src); while ((tok.tokt != PPTOK_NEWLINE) && (tok.tokt != PPTOK_EOF) && (tok.tokt != PPTOK_INVALID)) { preproc_token_del(&tok); tok = ppsrc_next_token(src); } if (tok.tokt == PPTOK_INVALID) { string_del(ret.tokv.pragma.val); goto start_cur_token; } else { return ret; } } else if (!strcmp(string_content(tok.tokv.str), "type_letters")) { string_del(tok.tokv.str); tok = ppsrc_next_token(src); if ((tok.tokt != PPTOK_IDENT) && (tok.tokt != PPTOK_IDENT_UNEXP)) { log_error(&tok.loginfo, "invalid pragma wrappers explicit_simple directive, skipping until EOL\n"); goto preproc_hash_err; } src->st = PPST_PRAGMA_EXPLICIT; ret.tokt = PTOK_PRAGMA; ret.loginfo = tok.loginfo; ret.tokv.pragma.typ = PRAGMA_EXPLICIT_CONV; ret.tokv.pragma.val = tok.tokv.str; return ret; } else if (!strcmp(string_content(tok.tokv.str), "type_letters_exact")) { string_del(tok.tokv.str); tok = ppsrc_next_token(src); if ((tok.tokt != PPTOK_IDENT) && (tok.tokt != PPTOK_IDENT_UNEXP)) { log_error(&tok.loginfo, "invalid pragma wrappers explicit_simple directive, skipping until EOL\n"); goto preproc_hash_err; } src->st = PPST_PRAGMA_EXPLICIT; ret.tokt = PTOK_PRAGMA; ret.loginfo = tok.loginfo; ret.tokv.pragma.typ = PRAGMA_EXPLICIT_CONV_STRICT; ret.tokv.pragma.val = tok.tokv.str; return ret; } else { log_error(&tok.loginfo, "unknown pragma wrappers directive '%s', skipping until EOL\n", string_content(tok.tokv.str)); goto preproc_hash_err; } } else { log_error(&tok.loginfo, "unknown pragma directive '%s', skipping until EOL\n", string_content(tok.tokv.str)); goto preproc_hash_err; } } else if (!strcmp(string_content(tok.tokv.str), "if")) { if (vector_last(ppsource, src->prep).srct != PPSRC_PREPARE) { log_error(&tok.loginfo, "invalid #if source type %u\n", vector_last(ppsource, src->prep).srct); src->st = PPST_NONE; ret.tokt = PTOK_INVALID; ret.loginfo = tok.loginfo; ret.tokv.c = '\0'; return ret; } string_del(tok.tokv.str); VECTOR(preproc) *cond = vector_new(preproc); if (!cond) { log_memory("failed to allocate #if condition vector\n"); src->st = PPST_NONE; ret.tokt = PTOK_INVALID; ret.loginfo = tok.loginfo; ret.tokv.c = '\0'; return ret; } tok = ppsrc_next_token(src); loginfo_t li = tok.loginfo; while ((tok.tokt != PPTOK_NEWLINE) && (tok.tokt != PPTOK_EOF) && (tok.tokt != PPTOK_INVALID)) { if (!vector_push(preproc, cond, tok)) { log_memory("failed to add token to #if condition vector\n"); vector_del(preproc, cond); src->st = PPST_NONE; ret.tokt = PTOK_INVALID; ret.loginfo = tok.loginfo; ret.tokv.c = '\0'; return ret; } li.lineno_end = tok.loginfo.lineno_end ? tok.loginfo.lineno_end : tok.loginfo.lineno; li.colno_end = tok.loginfo.colno_end ? tok.loginfo.colno_end : tok.loginfo.colno; tok = ppsrc_next_token(src); } vector_trim(preproc, cond); khash_t(string_set) *solved_macros = kh_init(string_set); if (!solved_macros) { log_memory("failed to allocate #if solved_macros set\n"); vector_del(preproc, cond); src->st = PPST_NONE; ret.tokt = PTOK_INVALID; ret.loginfo = tok.loginfo; ret.tokv.c = '\0'; return ret; } VECTOR(preproc) *expanded = preproc_do_expand(&li, src->macros_map, cond, solved_macros, src->macros_used); vector_del(preproc, cond); macros_set_del(solved_macros); if (!expanded) { log_error(&li, "Error: failed to expand #if condition\n"); src->st = PPST_NONE; ret.tokt = PTOK_INVALID; ret.loginfo = tok.loginfo; ret.tokv.c = '\0'; return ret; } // Now we need to compute what is pointed by expanded, and increase cond_depth and ok_depth as needed int st; int64_t res = preproc_eval(expanded, &st, src->target->size_long == 4); vector_del(preproc, expanded); if (!st) { log_error(&li, "Error: failed to evaluate #if condition\n"); src->st = PPST_NONE; ret.tokt = PTOK_INVALID; ret.loginfo = tok.loginfo; ret.tokv.c = '\0'; return ret; } ++vector_last(ppsource, src->prep).srcv.prep.cond_depth; if (res) { ++vector_last(ppsource, src->prep).srcv.prep.ok_depth; if (tok.tokt == PPTOK_NEWLINE) goto check_next_token; else goto start_cur_token; } else { vector_last(ppsource, src->prep).srcv.prep.entered_next_ok_cond = 0; if (tok.tokt == PPTOK_NEWLINE) goto check_if_depth; else goto start_cur_token; // Returns immediately } } else if (!strcmp(string_content(tok.tokv.str), "ifdef")) { if (vector_last(ppsource, src->prep).srct != PPSRC_PREPARE) { log_error(&tok.loginfo, "invalid #ifdef source type %u\n", vector_last(ppsource, src->prep).srct); goto preproc_hash_err; } string_del(tok.tokv.str); tok = ppsrc_next_token(src); if ((tok.tokt != PPTOK_IDENT) && (tok.tokt != PPTOK_IDENT_UNEXP)) { log_error(&tok.loginfo, "invalid token type %u after '#ifdef' preprocessor command\n", tok.tokt); goto preproc_hash_err; } khiter_t it = kh_get(macros_map, src->macros_map, string_content(tok.tokv.str)); int iret; char *mname = string_steal(tok.tokv.str); kh_put(string_set, src->macros_used, mname, &iret); if (iret <= 0) { free(mname); } tok.tokt = PPTOK_SYM; tok.tokv.sym = LAST_SYM; // We don't care about iret(?) // TODO: check iret, error if needed ++vector_last(ppsource, src->prep).srcv.prep.cond_depth; if (it != kh_end(src->macros_map)) { ++vector_last(ppsource, src->prep).srcv.prep.ok_depth; goto preproc_hash_err; } else { vector_last(ppsource, src->prep).srcv.prep.entered_next_ok_cond = 0; goto preproc_hash_err_goto; } } else if (!strcmp(string_content(tok.tokv.str), "ifndef")) { if (vector_last(ppsource, src->prep).srct != PPSRC_PREPARE) { log_error(&tok.loginfo, "invalid #ifndef source type %u\n", vector_last(ppsource, src->prep).srct); goto preproc_hash_err; } string_del(tok.tokv.str); tok = ppsrc_next_token(src); if ((tok.tokt != PPTOK_IDENT) && (tok.tokt != PPTOK_IDENT_UNEXP)) { log_error(&tok.loginfo, "invalid token type %u after '#ifndef' preprocessor command\n", tok.tokt); goto preproc_hash_err; } khiter_t it = kh_get(macros_map, src->macros_map, string_content(tok.tokv.str)); int iret; char *mname = string_steal(tok.tokv.str); kh_put(string_set, src->macros_used, mname, &iret); if (iret <= 0) { free(mname); } tok.tokt = PPTOK_SYM; tok.tokv.sym = LAST_SYM; // We don't care about iret(?) // TODO: check iret, error if needed ++vector_last(ppsource, src->prep).srcv.prep.cond_depth; if (it == kh_end(src->macros_map)) { ++vector_last(ppsource, src->prep).srcv.prep.ok_depth; goto preproc_hash_err; } else { vector_last(ppsource, src->prep).srcv.prep.entered_next_ok_cond = 0; goto preproc_hash_err_goto; } } else if (!strcmp(string_content(tok.tokv.str), "elif")) { // We are already in an #if or #elif (or #else) with a match, so we need to leave it and go to the very top if (vector_last(ppsource, src->prep).srct != PPSRC_PREPARE) { log_error(&tok.loginfo, "invalid #elif source type %u\n", vector_last(ppsource, src->prep).srct); goto preproc_hash_err; } if (vector_last(ppsource, src->prep).srcv.prep.ok_depth) { vector_last(ppsource, src->prep).srcv.prep.entered_next_ok_cond = 1; --vector_last(ppsource, src->prep).srcv.prep.ok_depth; goto preproc_hash_err_goto; } else { log_warning(&tok.loginfo, "unexpected #elif preprocessor command\n"); goto preproc_hash_err; } } else if (!strcmp(string_content(tok.tokv.str), "elifdef")) { // We are already in an #if or #elif (or #else) with a match, so we need to leave it and go to the very top if (vector_last(ppsource, src->prep).srct != PPSRC_PREPARE) { log_error(&tok.loginfo, "invalid #elifdef source type %u\n", vector_last(ppsource, src->prep).srct); goto preproc_hash_err; } string_del(tok.tokv.str); tok = ppsrc_next_token(src); int iret; char *mname = string_steal(tok.tokv.str); kh_put(string_set, src->macros_used, mname, &iret); if (iret <= 0) { free(mname); } tok.tokt = PPTOK_SYM; tok.tokv.sym = LAST_SYM; // We don't care about iret(?) // TODO: check iret, error if needed if (vector_last(ppsource, src->prep).srcv.prep.ok_depth) { vector_last(ppsource, src->prep).srcv.prep.entered_next_ok_cond = 1; --vector_last(ppsource, src->prep).srcv.prep.ok_depth; goto preproc_hash_err_goto; } else { log_warning(&tok.loginfo, "unexpected #elifdef preprocessor command\n"); goto preproc_hash_err; } } else if (!strcmp(string_content(tok.tokv.str), "elifndef")) { // We are already in an #if or #elif (or #else) with a match, so we need to leave it and go to the very top if (vector_last(ppsource, src->prep).srct != PPSRC_PREPARE) { log_error(&tok.loginfo, "invalid #elifndef source type %u\n", vector_last(ppsource, src->prep).srct); goto preproc_hash_err; } string_del(tok.tokv.str); tok = ppsrc_next_token(src); int iret; char *mname = string_steal(tok.tokv.str); kh_put(string_set, src->macros_used, mname, &iret); if (iret <= 0) { free(mname); } tok.tokt = PPTOK_SYM; tok.tokv.sym = LAST_SYM; // We don't care about iret(?) // TODO: check iret, error if needed if (vector_last(ppsource, src->prep).srcv.prep.ok_depth) { vector_last(ppsource, src->prep).srcv.prep.entered_next_ok_cond = 1; --vector_last(ppsource, src->prep).srcv.prep.ok_depth; goto preproc_hash_err_goto; } else { log_warning(&tok.loginfo, "unexpected #elifndef preprocessor command\n"); goto preproc_hash_err; } } else if (!strcmp(string_content(tok.tokv.str), "else")) { // We are already in an #if or #elif (or #else) with a match, so we need to leave it and go to the very top if (vector_last(ppsource, src->prep).srct != PPSRC_PREPARE) { log_error(&tok.loginfo, "invalid #else source type %u\n", vector_last(ppsource, src->prep).srct); goto preproc_hash_err; } if (vector_last(ppsource, src->prep).srcv.prep.ok_depth) { // We can safely ignore setting entered_next_ok since two #else is illegal (though this parser doesn't actually detect this) --vector_last(ppsource, src->prep).srcv.prep.ok_depth; goto preproc_hash_err_goto; } else { log_warning(&tok.loginfo, "unexpected #else preprocessor command\n"); goto preproc_hash_err; } } else if (!strcmp(string_content(tok.tokv.str), "endif")) { if (vector_last(ppsource, src->prep).srct != PPSRC_PREPARE) { log_error(&tok.loginfo, "invalid #endif source type %u\n", vector_last(ppsource, src->prep).srct); goto preproc_hash_err; } if (vector_last(ppsource, src->prep).srcv.prep.ok_depth) { --vector_last(ppsource, src->prep).srcv.prep.ok_depth; --vector_last(ppsource, src->prep).srcv.prep.cond_depth; } else { log_warning(&tok.loginfo, "unexpected #endif preprocessor command\n"); } goto preproc_hash_err; } log_warning(&tok.loginfo, "Unknown preprocessor command %s, skipping until EOL\n", string_content(tok.tokv.str)); preproc_hash_err: while ((tok.tokt != PPTOK_NEWLINE) && (tok.tokt != PPTOK_EOF) && (tok.tokt != PPTOK_INVALID)) { preproc_token_del(&tok); tok = ppsrc_next_token(src); } if (tok.tokt == PPTOK_NEWLINE) goto check_next_token; else goto start_cur_token; preproc_hash_err_goto: while ((tok.tokt != PPTOK_NEWLINE) && (tok.tokt != PPTOK_EOF) && (tok.tokt != PPTOK_INVALID)) { preproc_token_del(&tok); tok = ppsrc_next_token(src); } if (tok.tokt == PPTOK_NEWLINE) goto check_if_depth; else goto start_cur_token; // Returns immediately } src->st = (src->st == PPST_PRAGMA_EXPLICIT) ? PPST_PRAGMA_EXPLICIT : PPST_NONE; ret.tokt = PTOK_SYM; ret.loginfo = tok.loginfo; ret.tokv.sym = tok.tokv.sym; return ret; case PPTOK_NEWLINE: if (src->st == PPST_PRAGMA_EXPLICIT) { src->st = PPST_NL; ret.tokt = PTOK_SYM; ret.loginfo = tok.loginfo; ret.tokv.sym = SYM_SEMICOLON; return ret; } src->st = PPST_NL; goto check_next_token; case PPTOK_BLANK: src->st = PPST_NONE; ret.tokt = PTOK_INVALID; ret.loginfo = tok.loginfo; ret.tokv.c = tok.tokv.c; return ret; case PPTOK_START_LINE_COMMENT: src->st = PPST_NONE; ret.tokt = PTOK_INVALID; ret.loginfo = tok.loginfo; ret.tokv.c = tok.tokv.c; return ret; case PPTOK_EOF: if ((vector_last(ppsource, src->prep).srct == PPSRC_PREPARE) && vector_last(ppsource, src->prep).srcv.prep.cond_depth) { log_warning(&tok.loginfo, "file ended before closing all conditionals (ignoring)\n"); } #ifdef LOG_CLOSE printf("Closing %s\n", src->cur_file); #endif if (vector_last(ppsource, src->prep).srct == PPSRC_PREPARE) { if (src->dirname) free(src->dirname); if (src->cur_file) free(src->cur_file); src->dirname = vector_last(ppsource, src->prep).srcv.prep.old_dirname; src->cur_file = vector_last(ppsource, src->prep).srcv.prep.old_filename; src->is_sys = vector_last(ppsource, src->prep).srcv.prep.was_sys; src->cur_pathno = vector_last(ppsource, src->prep).srcv.prep.old_pathno; vector_last(ppsource, src->prep).srcv.prep.old_dirname = NULL; vector_last(ppsource, src->prep).srcv.prep.old_filename = NULL; } vector_pop(ppsource, src->prep); if (src->st == PPST_PRAGMA_EXPLICIT) { src->st = PPST_NL; ret.tokt = PTOK_SYM; ret.loginfo = (loginfo_t){0}; // The token's loginfo may have been deleted by the vector_pop above ret.tokv.sym = SYM_SEMICOLON; // Empty destructor return ret; } src->st = PPST_NL; // Should be redundant since TOK_NEWLINE is added before TOK_EOF if required // EOF has an empty destructor // Note that since we have opened the file, the previous file also had ok_depth == cond_depth goto check_next_token; default: log_error(&tok.loginfo, "unknown preprocessor token type %u, sending INVALID\n", tok.tokt); ret.tokt = PTOK_INVALID; ret.loginfo = tok.loginfo; ret.tokv.c = '\0'; return ret; } } proc_token_t proc_next_token(preproc_t *src) { proc_token_t ret = proc_next_token_aux(src); if ((ret.tokt == PTOK_STRING) && ret.tokv.sisstr) { while (1) { proc_token_t ret2 = proc_next_token_aux(src); if ((ret2.tokt == PTOK_STRING) && ret2.tokv.sisstr) { if (!string_add_string(ret.tokv.sstr, ret2.tokv.sstr)) { log_memory("failed to concatenate adjacent strings\n"); string_del(ret.tokv.sstr); string_del(ret2.tokv.sstr); src->st = PPST_NONE; ret.tokt = PTOK_INVALID; ret.tokv.c = '\0'; return ret; } string_del(ret2.tokv.sstr); ret.loginfo.lineno_end = ret2.loginfo.lineno_end ? ret2.loginfo.lineno_end : ret2.loginfo.lineno; ret.loginfo.colno_end = ret2.loginfo.colno_end ? ret2.loginfo.colno_end : ret2.loginfo.colno; } else { if (!proc_unget_token(src, &ret2)) { log_memory("failed to unget token next to string token\n"); string_del(ret.tokv.sstr); proc_token_del(&ret2); src->st = PPST_NONE; ret.tokt = PTOK_INVALID; ret.tokv.c = '\0'; return ret; } return ret; } } } else return ret; }