/* This file is part of Mailfromd. Copyright (C) 2006-2020 Sergey Poznyakoff This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifdef HAVE_CONFIG_H # include #endif #include #include #include #include #include #include #include #include #include #include #include "mailfromd.h" #include "prog.h" #include "msg.h" #include "optab.h" #include "builtin.h" #include "mflib/_register.h" #include "global.h" /* Code generation support */ #define CODE_INITIAL 128 #define CODE_INCREMENT 128 static prog_counter_t pc, pmax; mf_code_cell_t *prog; size_t stack_size = STACK_SIZE; size_t stack_max_size = 0; size_t stack_expand_incr = STACK_INCR; enum stack_expand_policy stack_expand_policy = stack_expand_add; /* Data segment */ STKVAL *dataseg; size_t datasize; size_t dvarsize; /* Table of relocatable entries in the data segment */ size_t *dataseg_reloc; size_t dataseg_reloc_count; /* Regular expressions */ struct rt_regex *regtab; size_t regcount; mu_opool_t regpool; void code_init() { pmax = CODE_INITIAL; prog = calloc(pmax, sizeof prog[0]); if (!prog) { mu_error(_("not enough memory")); exit(1); } } prog_counter_t code_get_counter() { return pc; } prog_counter_t code_reserve(size_t count) { prog_counter_t ret = pc; if (pc + count > pmax) { size_t incr = (pc + count - pmax + CODE_INCREMENT - 1) / CODE_INCREMENT; pmax += incr * CODE_INCREMENT; prog = realloc(prog, pmax*sizeof prog[0]); if (!prog) { mu_error(_("not enough memory")); exit(1); } } pc += count; return ret; } prog_counter_t code_cell(mf_code_cell_t cell) { if (pc >= pmax) { pmax += CODE_INCREMENT; prog = realloc(prog, pmax*sizeof prog[0]); if (!prog) { mu_error(_("not enough memory")); exit(1); } } prog[pc] = cell; return pc++; } prog_counter_t code_instr(const instr_t ptr) { return code_cell((mf_code_cell_t)ptr); } prog_counter_t code_op(unsigned code) { return code_cell((mf_code_cell_t)(STKVAL)code); } prog_counter_t code_immediate_stkval(STKVAL val) { return code_cell((mf_code_cell_t)val); } prog_counter_t code_exmask(struct exmask *exmask) { return code_immediate(exmask->off, size); } void code_put_stkval(prog_counter_t pos, STKVAL val) { assert(pos < pmax); prog[pos].c_value = val; } mf_code_cell_t code_peek(prog_counter_t pos) { assert(pos < pmax); return prog[pos]; } /* Regexps*/ void register_regex(struct sym_regex *rp) { struct rt_regex r; if (regcount == 0) mu_opool_create(®pool, MU_OPOOL_ENOMEMABRT); r.compiled = 0; /* Will be compiled later */ r.expr = rp->lit ? rp->lit->off : 0; r.regflags = rp->regflags; rp->index = regcount++; mu_opool_append(regpool, &r, sizeof r); } void finalize_regex() { if (regcount) regtab = mu_opool_finish(regpool, NULL); } #define PROG_TRACE_ENGINE (builtin_module_trace(BUILTIN_IDX_prog)) static void set_prog_trace(const char *modlist, int val) { while (1) { size_t len = strcspn(modlist, ","); if (len == 3 && memcmp(modlist, "all", 3) == 0) builtin_set_all_module_trace(val); else if (len == 4 && memcmp(modlist, "none", 4) == 0) builtin_set_all_module_trace(!val); else { if (len > 3 && memcmp (modlist, "no-", 3) == 0) builtin_set_module_trace(modlist+3, len-3, !val); else builtin_set_module_trace(modlist, len, val); } modlist += len; if (*modlist) modlist++; else break; } } void enable_prog_trace(const char *modlist) { set_prog_trace(modlist, 1); } void disable_prog_trace(const char *modlist) { set_prog_trace(modlist, 0); } /* ======================================================================== Drzewa w górę, rzeki w dół. Jacek Kaczmarski. "Upadek Ikara" ======================================================================== */ /* Run-time evaluation */ /* Max. number of C locals to save in struct eval_environ for eventual fixups. See comment to env_fixup_autos, below. */ #define MAX_AUTO_PTR 128 struct exception_context { prog_counter_t pc; prog_counter_t tos; prog_counter_t base; }; #define TOS_INVARIANT(env,t) (datasize + (env)->stack_size - (t)) struct environ_locus { unsigned long file; /* File name */ unsigned int line; /* Line number */ }; struct environ_cleanup_closure { void (*cleanup)(void *); void *data; }; struct eval_environ { prog_counter_t pc; /* Program counter */ prog_counter_t tos; /* Top of stack: toh <= tos < datasize + stack_size */ prog_counter_t toh; /* Top of heap: datasize <= toh <= tos */ prog_counter_t base; /* Base pointer */ STKVAL reg; /* General purpose register */ STKVAL *dataseg; /* Data space */ size_t stack_size; /* Size of allocated stack + heap */ /* Program locus corresponding to PC */ struct environ_locus locus; /* Temporary heap space */ size_t temp_start; size_t temp_size; STKVAL *auto_ptr[MAX_AUTO_PTR]; /* Pointers to C automatic variables referring to dataseg. */ size_t numautos; /* Number of entries in auto_ptr. */ /* Sendmail interaction data: */ SMFICTX *ctx; /* Milter Context */ void *data; /* MTA symbol table */ /* methods to access the latter */ const char *(*getsym)(void *data, const char *str); int (*setreply)(void *data, char *code, char *xcode, char *message); void (*msgmod)(void *data, struct msgmod_closure *c); /* Regular expression matching */ regmatch_t *matches; /* Match map */ size_t matchcount; /* Number of used entries in matches */ size_t matchsize; /* Total number of entries in matches */ prog_counter_t matchstr; /* Pointer to the last matched string in the dataseg. */ mu_stream_t stream; /* Capture stream */ size_t line_count; /* Number of lines in stream */ int reposition; /* When !0, stream must be repositioned to its end before writing. */ mu_header_t header; /* Headers from stream, converted to a MU object. */ /* Message modification queue */ mu_list_t mmq; /* Non-local exits */ struct exception_context *defcatch_ctx; struct exception_context *catch_ctx; /* Built-in private data */ void **bi_priv_array; /* Function clean-up sequence */ mu_list_t cleanup_list; /* Exit information */ sfsistat status; /* Program exit status */ jmp_buf x_jmp; /* Return point for runtime errors */ jmp_buf catch_jmp; /* Return point for throws */ }; #define ENV_LOC_FILE(env) \ ((const char*)((env)->dataseg + (size_t) (env)->locus.file)) #define ENV_LOC_LINE(env) ((env)->locus.line) void advance_pc(eval_environ_t env, long cnt) { env->pc += cnt; } void adjust_stack(eval_environ_t env, unsigned cnt) { env->tos += cnt; } void unroll_stack(eval_environ_t env, unsigned cnt) { env->tos -= cnt; } prog_counter_t env_register_read(eval_environ_t env, int what) { switch (what) { case REG_PC: return env->pc; case REG_TOS: return env->tos; case REG_TOH: return env->toh; case REG_BASE: return env->base; case REG_REG: return (prog_counter_t) mf_c_val(env->reg, ulong); case REG_MATCHSTR: return env->matchstr; } return -1; } size_t env_base(eval_environ_t env, size_t frame) { size_t base = env->base; for (; frame; frame--) base = mf_c_val(env->dataseg[base + 1], size) + base + 1; return base; } char * env_vaptr(eval_environ_t env, size_t off) { return (char*)(env->dataseg + mf_c_val(env->dataseg[off], size)); } void env_var_inc(eval_environ_t env, size_t off) { ++mf_c_val(*env_data_ref(env, off), long); } static void env_register_auto(eval_environ_t env, void *ptr); void env_get_locus(eval_environ_t env, struct mu_locus_range *locus) { mu_locus_range_init(locus); locus->beg.mu_file = (char*)(env->dataseg + env->locus.file); env_register_auto(env, (void*) &locus->beg.mu_file); locus->beg.mu_line = env->locus.line; #if 0 locus->beg.mu_col = env->locus.point; ... #endif } const char * env_get_macro(eval_environ_t env, const char *symbol) { return env->getsym(env->data, symbol); } int env_get_stream(eval_environ_t env, mu_stream_t *pstr) { return mu_streamref_create(pstr, env->stream); } STKVAL env_get_reg(eval_environ_t env) { return env->reg; } void env_reposition(eval_environ_t env) { env->reposition = 1; } static int _cleanup_compare(const void *a, const void *b) { struct environ_cleanup_closure const *closa = a; struct environ_cleanup_closure const *closb = b; return closa->data != closb->data; } static void _cleanup_destroy(void *item) { struct environ_cleanup_closure *clos = item; if (clos->cleanup) clos->cleanup(clos->data); } static void env_create_cleanup_list(eval_environ_t env) { mu_list_create(&env->cleanup_list); mu_list_set_destroy_item(env->cleanup_list, _cleanup_destroy); mu_list_set_comparator(env->cleanup_list, _cleanup_compare); } void env_function_cleanup_flush(eval_environ_t env, void *ptr) { if (ptr) { struct environ_cleanup_closure clos; clos.data = ptr; mu_list_remove(env->cleanup_list, &clos); } else { mu_list_clear(env->cleanup_list); } } void env_function_cleanup_del(eval_environ_t env, void *ptr) { int rc; struct environ_cleanup_closure clos, *cptr; clos.data = ptr; rc = mu_list_locate(env->cleanup_list, &clos, (void**)&cptr); if (rc == 0) { cptr->cleanup = NULL; mu_list_remove(env->cleanup_list, &clos); } } void env_function_cleanup_add(eval_environ_t env, void *data, void (*func)(void *)) { struct environ_cleanup_closure *clos = mu_zalloc(sizeof(*clos)); clos->cleanup = func ? func : free; clos->data = data; mu_list_append(env->cleanup_list, clos); } /* A call to expand_dataseg (see below) invalidates any C variables that pointed to the dataseg before the call. To avoid dereferencing invalid memory pointers, addresses of such C variables are stored in env->auto_ptr using env_register_auto (it is done by get_string_arg). When expand_dataseg is called, it calls env_fixup_autos and passes it the offset of the new dataseg from the old one. Env_fixup_autos adds this value to every address in auto_ptr, thereby fixing them. The auto_ptr array is cleared (by calling env_unregister_autos) after executing each instruction (see eval_environment). */ static void env_register_auto(eval_environ_t env, void *ptr) { char *addr = *(char**)ptr; if (env->numautos == MAX_AUTO_PTR) runtime_error(env, "INTERNAL ERROR at %s:%d, please report", __FILE__, __LINE__); /* Check if address is within the dataseg */ if (!(addr >= (char*) env->dataseg && (addr < (char*) (env->dataseg + datasize + env->stack_size)))) ptr = NULL; env->auto_ptr[env->numautos++] = ptr; } /* Pop the last registered auto variable */ static void env_pop_auto(eval_environ_t env) { env->numautos--; } static void env_unregister_autos(eval_environ_t env) { env->numautos = 0; } static void env_fixup_autos(eval_environ_t env, ptrdiff_t offset) { int i; for (i = 0; i < env->numautos; i++) { STKVAL *pptr = env->auto_ptr[i]; if (pptr) mf_c_val(*pptr,str) += offset; /*FIXME*/ } } int expand_dataseg(eval_environ_t env, size_t count, const char *errtext) { STKVAL *newds; ptrdiff_t offset; size_t new_stack_size; size_t diff; switch (stack_expand_policy) { case stack_expand_add: diff = ((count + stack_expand_incr - 1) / stack_expand_incr) * stack_expand_incr; new_stack_size = env->stack_size + diff; break; case stack_expand_twice: new_stack_size = 2 * env->stack_size; if (new_stack_size < env->stack_size) { if (errtext) runtime_error(env, "%s", errtext); else return 1; } diff = new_stack_size - env->stack_size; if (diff < count) { if (errtext) runtime_error(env, "%s", errtext); else return 1; } } if ((stack_max_size && new_stack_size > stack_max_size) || (newds = realloc(env->dataseg, (new_stack_size + datasize) * sizeof env->dataseg[0])) == NULL) { if (errtext) runtime_error(env, "%s", errtext); else return 1; } offset = (char*)newds - (char*)env->dataseg; env->dataseg = newds; env->stack_size = new_stack_size; env->tos += diff; env->base += diff; memmove(newds + env->tos, newds + env->tos - diff, (datasize + env->stack_size - env->tos) * sizeof newds[0]); env_fixup_autos(env, offset); mu_diag_output(MU_DIAG_WARNING, _("stack segment expanded, new size=%lu"), (unsigned long) env->stack_size); return 0; } void prog_trace(eval_environ_t env, const char *fmt, ...) { char buf[512]; va_list ap; va_start(ap, fmt); vsnprintf(buf, sizeof buf, fmt, ap); va_end(ap); logmsg(MU_LOG_DEBUG, "%4lu: %s:%u: %s", (unsigned long) env->pc, ENV_LOC_FILE(env), ENV_LOC_LINE(env), buf); } void instr_funcall(eval_environ_t env); void instr_locus(eval_environ_t env); void runtime_stack_trace(eval_environ_t env) { size_t base; mu_error(_("stack trace:")); for (base = env->base; base < datasize + env->stack_size - 4; base = mf_c_val(env->dataseg[base + 1], size) + base + 1) { int i; prog_counter_t pc = mf_c_val(env->dataseg[base + 2], ulong) - 1; char *name; struct mu_locus_point *ploc = NULL, loc; if (pc < 2) break; /*FIXME*/ if (mf_cell_instr(prog[pc-2]) == instr_funcall) { name = (char*)(env->dataseg + mf_cell_c_value(prog[pc-1], size)); pc -= 2; } else { name = "(in catch)"; pc -= 3; } for (i = 0; i < 10; i++) { if (pc > i + 3 && prog[pc - i - 3].c_instr == instr_locus) { loc.mu_file = (char*)(env->dataseg + mf_cell_c_value(prog[pc - i - 2], size)); loc.mu_line = mf_cell_c_value(prog[pc - i - 1], size); ploc = &loc; break; } } if (ploc) mu_error("%04lu: %s:%u: %s", (unsigned long) pc, ploc->mu_file, ploc->mu_line, name); else mu_error("%04lu: %s", (unsigned long) pc, name); } mu_error(_("stack trace finishes")); } void runtime_warning(eval_environ_t env, const char *fmt, ...) { va_list ap; mu_diag_printf(MU_DIAG_WARNING, _("RUNTIME WARNING near %s:%u: "), ENV_LOC_FILE(env), ENV_LOC_LINE(env)); va_start(ap, fmt); mu_diag_cont_printf(fmt, ap); va_end(ap); mu_diag_cont_printf("\n"); } void runtime_error(eval_environ_t env, const char *fmt, ...) { int n; va_list ap; char buf[512]; n = snprintf(buf, sizeof buf, _("RUNTIME ERROR near %s:%u: "), ENV_LOC_FILE(env), ENV_LOC_LINE(env)); va_start(ap, fmt); vsnprintf(buf + n, sizeof buf - n, fmt, ap); va_end(ap); mu_error("%s", buf); if (stack_trace_option) runtime_stack_trace(env); env->status = SMFIS_TEMPFAIL; /* FIXME */ longjmp(env->x_jmp, 1); } STKVAL get_immediate(eval_environ_t env, unsigned n) { return mf_cell_value(prog[env->pc + n + 1]); } static void get_literal(eval_environ_t env, unsigned n, const char **p) { *p = (char*)(env->dataseg + mf_c_val(get_immediate(env, n), size)); env_register_auto(env, p); } STKVAL get_arg(eval_environ_t env, unsigned n) { return env->dataseg[env->tos + n + 1]; } void get_string_arg(eval_environ_t env, unsigned n, char * MFL_DATASEG *p) { *p = (char*) (env->dataseg + mf_c_val(get_arg(env, n), size)); env_register_auto(env, (void*) p); } void get_numeric_arg(eval_environ_t env, unsigned n, long *np) { *np = mf_c_val(get_arg(env, n), long); } void get_pointer_arg(eval_environ_t env, unsigned n, void * MFL_DATASEG *p) { *p = mf_c_val(get_arg(env, n), ptr); } void push(eval_environ_t env, STKVAL val) { if (env->tos < env->toh) runtime_error(env, "INTERNAL ERROR at %s:%d, please report", __FILE__, __LINE__); if (env->tos == env->toh) { mu_debug(MF_SOURCE_PROG, MU_DEBUG_TRACE8, ("tos=%lu, toh=%lu", (unsigned long) env->tos, (unsigned long) env->toh)); expand_dataseg(env, 1, _("out of stack space; increase #pragma stacksize")); } env->dataseg[env->tos--] = val; } STKVAL pop(eval_environ_t env) { if (env->tos == datasize + env->stack_size - 1) runtime_error(env, _("stack underflow")); return env->dataseg[++env->tos]; } size_t heap_reserve_words(eval_environ_t env, size_t words) { size_t off = env->toh; if (env->toh + words > env->tos) expand_dataseg(env, words, _("heap overrun; increase #pragma stacksize")); env->toh += words; return off; } size_t heap_reserve(eval_environ_t env, size_t size) { return heap_reserve_words(env, B2STACK(size)); } STKVAL heap_tempspace(eval_environ_t env, size_t size) { size_t words = B2STACK(size); STKVAL ret; if (env->toh + words > env->tos) expand_dataseg(env, words, _("heap overrun; increase #pragma stacksize")); mf_c_val(ret, ptr) = env->dataseg + env->toh; return ret; } void heap_obstack_begin(eval_environ_t env) { env->temp_start = env->toh; env->temp_size = 0; } void heap_obstack_cancel(eval_environ_t env) { env->toh = env->temp_start; env->temp_start = 0; env->temp_size = 0; } STKVAL heap_obstack_finish(eval_environ_t env) { STKVAL ret; mf_c_val(ret, size) = env->temp_start; env->temp_start = 0; env->temp_size = 0; return ret; } void * heap_obstack_grow(eval_environ_t env, void * MFL_DATASEG ptr, size_t size) { size_t words = B2STACK(size); char *ret; env_register_auto(env, (void*) &ptr); if (env->tos - env->toh < words + B2STACK(env->temp_size)) expand_dataseg(env, words, _("memory chunk too big to fit into heap")); ret = (char*) env_data_ref(env, env->temp_start) + env->temp_size; if (ptr) memmove(ret, ptr, size); env->temp_size += size; env->toh += words; env_pop_auto(env); return ret; } void * heap_obstack_base(eval_environ_t env) { return (void*) env_data_ref(env, env->temp_start); } STKVAL * env_data_ref(eval_environ_t env, size_t off) { return env->dataseg + off; } void pushs(eval_environ_t env, const char * MFL_DATASEG s) { size_t off; env_register_auto(env, (void*) &s); off = heap_reserve(env, strlen(s) + 1); strcpy((char*) env_data_ref(env, off), s); env_pop_auto(env); push(env, (STKVAL) off); } void pushn(eval_environ_t env, long n) { push(env, (STKVAL) n); } /* Auxiliary instructions */ void instr_locus(eval_environ_t env) { env->locus.file = mf_c_val(get_immediate(env, 0), uint); env->locus.line = mf_c_val(get_immediate(env, 1), uint); if (PROG_TRACE_ENGINE) prog_trace(env, "LOCUS"); advance_pc(env, 2); } void dump_locus(prog_counter_t i) { printf("\"%s\" %lu", (char*) (dataseg + mf_cell_c_value(prog[i], size)), (unsigned long) mf_cell_c_value(prog[i+1], size)); } /* Stack manipulation instructions */ void instr_xchg(eval_environ_t env) { prog_counter_t p = env->tos + 1; STKVAL tmp = env->dataseg[p]; env->dataseg[p] = env->dataseg[p + 1]; env->dataseg[p + 1] = tmp; if (PROG_TRACE_ENGINE) prog_trace(env, "XCHG"); } void instr_dup(eval_environ_t env) { if (PROG_TRACE_ENGINE) prog_trace(env, "DUP"); push(env, get_arg(env, 0)); } void instr_pop(eval_environ_t env) { if (PROG_TRACE_ENGINE) prog_trace(env, "POP"); pop(env); } void instr_push(eval_environ_t env) { if (PROG_TRACE_ENGINE) prog_trace(env, "PUSH %p", mf_c_val(get_immediate(env, 0), ptr)); push(env, get_immediate(env, 0)); advance_pc(env, 1); } void dump_push(prog_counter_t i) { printf("%lx", mf_cell_c_value(prog[i], ulong)); } void instr_memstk(eval_environ_t env) { size_t frame = mf_c_val(get_immediate(env, 0), size); long off = mf_c_val(get_immediate(env, 1), long); size_t val; /* The naive approach would be to just push(env, env_base(env, frame) + off) However, push() itself might cause dataseg expansion, which would change tos/toh pointers and render the previously computed argument value invalid. The correct way is to push a placeholder value first, thereby occupying stack slot and eventually causing dataseg expansion, and then compute val using actual environment: */ push(env, (STKVAL)0L); val = env_base(env, frame) + off; env->dataseg[env->tos+1] = (STKVAL)val; if (PROG_TRACE_ENGINE) prog_trace(env, "MEMSTK %lu(%ld)=%lu", (unsigned long) frame, off, (unsigned long) val); advance_pc(env, 2); } void dump_memstk(prog_counter_t i) { printf("%lu(%ld)", mf_cell_c_value(prog[i], ulong), mf_cell_c_value(prog[i+1], long)); } void instr_xmemstk(eval_environ_t env) { size_t frame = mf_c_val(get_arg(env, 1), size); long off = mf_c_val(get_arg(env, 0), long); size_t val; adjust_stack(env, 2); /* See comment in instr_memstk, above */ push(env, (STKVAL)0L); val = env_base(env, frame) + off; env->dataseg[env->tos+1] = (STKVAL)val; if (PROG_TRACE_ENGINE) prog_trace(env, "XMEMSTK %lu(%ld)=%lu", (unsigned long) frame, off, (unsigned long) val); } void instr_deref(eval_environ_t env) { size_t off = mf_c_val(get_arg(env, 0), size); STKVAL val = env->dataseg[off]; if (PROG_TRACE_ENGINE) prog_trace(env, "DEREF %lu=%lu (%p)", (unsigned long) off, mf_c_val(val, ulong), mf_c_val(val, ptr)); adjust_stack(env, 1); push(env, val); } void instr_stkalloc(eval_environ_t env) { unsigned n = mf_c_val(get_immediate(env, 0), uint); if (PROG_TRACE_ENGINE) prog_trace(env, "STKALLOC %x", n); if (env->tos - n < env->toh) { mu_debug(MF_SOURCE_PROG, MU_DEBUG_TRACE8, ("tos=%lu, toh=%lu, delta=%u", (unsigned long) env->tos, (unsigned long) env->toh, n)); expand_dataseg(env, env->toh - (env->tos - n), _("Out of stack space; increase #pragma stacksize")); } env->tos -= n; advance_pc(env, 1); } void dump_stkalloc(prog_counter_t i) { printf("%u", mf_cell_c_value(prog[i], uint)); } void instr_backref(eval_environ_t env) { unsigned n = mf_c_val(get_immediate(env, 0), uint); size_t matchlen; if (PROG_TRACE_ENGINE) prog_trace(env, "BACKREF %u", mf_c_val(get_immediate(env, 0), uint)); advance_pc(env, 1); if (!env->matches || !env->matchstr) { /* FIXME: Try to throw exception first: env_throw(env, mf_no_regex); */ runtime_error(env, _("no previous regular expression")); } if (n > env->matchcount) { /* FIXME: See above */ runtime_error(env, _("invalid back-reference number")); } if (env->matches[n].rm_so == -1) { push(env, (STKVAL)0L); } else { char *s; size_t off; matchlen = env->matches[n].rm_eo - env->matches[n].rm_so; off = heap_reserve(env, matchlen + 1); s = (char*) env_data_ref(env, off); memcpy(s, (const char *) &env->dataseg[env->matchstr] + env->matches[n].rm_so, matchlen); s[matchlen] = 0; push(env, (STKVAL) off); } } void dump_backref(prog_counter_t i) { printf("%u", mf_cell_c_value(prog[i], uint)); } /* Type conversion instructions */ void instr_ston(eval_environ_t env) { char *s; char *p; long v; get_string_arg(env, 0, &s); v = strtol(s, &p, 0); if (PROG_TRACE_ENGINE) prog_trace(env, "STON %s", s); adjust_stack(env, 1); if (*p) env_throw(env, mfe_ston_conv, "Cannot convert stack value to number (stopped at %-.8s)", p); push(env, (STKVAL) v); } void instr_ntos(eval_environ_t env) { long v = mf_c_val(get_arg(env, 0), long); char buf[NUMERIC_BUFSIZE_BOUND]; if (PROG_TRACE_ENGINE) prog_trace(env, "NTOS %ld", v); adjust_stack(env, 1); snprintf(buf, sizeof buf, "%ld", v); pushs(env, buf); } /* Evaluation instructions */ void instr_cmp(eval_environ_t env) { long l = mf_c_val(get_arg(env, 1), long); long r = mf_c_val(get_arg(env, 0), long); if (PROG_TRACE_ENGINE) prog_trace(env, "CMP %ld %ld", l, r); adjust_stack(env, 2); pushn(env, l == r); } void instr_symbol(eval_environ_t env) { char *symbol; const char *s; get_literal(env, 0, (const char **)&symbol); s = env->getsym(env->data, symbol); if (PROG_TRACE_ENGINE) prog_trace(env, "SYMBOL %s", symbol); if (!s) env_throw(env, mfe_macroundef, _("macro not defined: %s"), symbol); if (PROG_TRACE_ENGINE) prog_trace(env, "%s dereferenced to %s", symbol, s); advance_pc(env, 1); pushs(env, s); } void dump_symbol(prog_counter_t i) { printf("%08lx %s", mf_cell_c_value(prog[i], ulong), (char *) (dataseg + mf_cell_c_value(prog[i], size))); } /* Comparation instructions */ void instr_eqn(eval_environ_t env) { long a = mf_c_val(get_arg(env, 1), long); long b = mf_c_val(get_arg(env, 0), long); if (PROG_TRACE_ENGINE) prog_trace(env, "EQN %ld %ld", a, b); adjust_stack(env, 2); pushn(env, a == b); } void instr_eqs(eval_environ_t env) { char *a, *b; get_string_arg(env, 1, &a); get_string_arg(env, 0, &b); if (PROG_TRACE_ENGINE) prog_trace(env, "EQS %s %s", a, b); adjust_stack(env, 2); pushn(env, strcmp(a, b) == 0); } void instr_nen(eval_environ_t env) { long a = mf_c_val(get_arg(env, 1), long); long b = mf_c_val(get_arg(env, 0), long); if (PROG_TRACE_ENGINE) prog_trace(env, "NEN %ld %ld", a, b); adjust_stack(env, 2); pushn(env, a != b); } void instr_nes(eval_environ_t env) { char *a, *b; get_string_arg(env, 1, &a); get_string_arg(env, 0, &b); if (PROG_TRACE_ENGINE) prog_trace(env, "NES %s %s", a, b); adjust_stack(env, 2); pushn(env, strcmp(a, b) != 0); } void instr_ltn(eval_environ_t env) { long a = mf_c_val(get_arg(env, 1), long); long b = mf_c_val(get_arg(env, 0), long); if (PROG_TRACE_ENGINE) prog_trace(env, "LTN %ld %ld", a, b); adjust_stack(env, 2); pushn(env, a < b); } void instr_lts(eval_environ_t env) { char *a, *b; get_string_arg(env, 1, &a); get_string_arg(env, 0, &b); if (PROG_TRACE_ENGINE) prog_trace(env, "LTS %s %s", a, b); adjust_stack(env, 2); pushn(env, strcmp(a, b) < 0); } void instr_len(eval_environ_t env) { long a = mf_c_val(get_arg(env, 1), long); long b = mf_c_val(get_arg(env, 0), long); if (PROG_TRACE_ENGINE) prog_trace(env, "LEN %ld %ld", a, b); adjust_stack(env, 2); pushn(env, a <= b); } void instr_les(eval_environ_t env) { char *a, *b; get_string_arg(env, 1, &a); get_string_arg(env, 0, &b); if (PROG_TRACE_ENGINE) prog_trace(env, "LES %s %s", a, b); adjust_stack(env, 2); pushn(env, strcmp(a, b) <= 0); } void instr_gtn(eval_environ_t env) { long a = mf_c_val(get_arg(env, 1), long); long b = mf_c_val(get_arg(env, 0), long); if (PROG_TRACE_ENGINE) prog_trace(env, "GTN %ld %ld", a, b); adjust_stack(env, 2); pushn(env, a > b); } void instr_gts(eval_environ_t env) { char *a, *b; get_string_arg(env, 1, &a); get_string_arg(env, 0, &b); if (PROG_TRACE_ENGINE) prog_trace(env, "GTS %s %s", a, b); adjust_stack(env, 2); pushn(env, strcmp(a, b) > 0); } void instr_gen(eval_environ_t env) { long a = mf_c_val(get_arg(env, 1), long); long b = mf_c_val(get_arg(env, 0), long); if (PROG_TRACE_ENGINE) prog_trace(env, "GEN %ld %ld", a, b); adjust_stack(env, 2); pushn(env, a >= b); } void instr_ges(eval_environ_t env) { char *a, *b; get_string_arg(env, 1, &a); get_string_arg(env, 0, &b); if (PROG_TRACE_ENGINE) prog_trace(env, "GES %s %s", a, b); adjust_stack(env, 2); pushn(env, strcmp(a, b) >= 0); } /* Jump instructions */ void instr_bz(eval_environ_t env) { long v = mf_c_val(get_arg(env, 0), long); long off = mf_c_val(get_immediate(env, 0), long); if (PROG_TRACE_ENGINE) prog_trace(env, "BZ %ld (%ld)", off, v); adjust_stack(env, 1); if (v == 0) advance_pc(env, off); advance_pc(env, 1); } void dump_branch (prog_counter_t i) { printf("%ld (%ld)", mf_cell_c_value(prog[i], long), i + mf_cell_c_value(prog[i], long) + 1); } void instr_bnz(eval_environ_t env) { long v = mf_c_val(get_arg(env, 0), long); long off = mf_c_val(get_immediate(env, 0), long); if (PROG_TRACE_ENGINE) prog_trace(env, "BNZ %ld (%ld)", off, v); adjust_stack(env, 1); if (v != 0) advance_pc(env, off); advance_pc(env, 1); } void instr_jmp(eval_environ_t env) { long off = mf_c_val(get_immediate(env, 0), long); if (PROG_TRACE_ENGINE) prog_trace(env, "JMP %ld", off); advance_pc(env, off+1); } /* Longean instructions */ void instr_not(eval_environ_t env) { long v = mf_c_val(get_arg(env, 0), long); if (PROG_TRACE_ENGINE) prog_trace(env, "NOT %ld", v); adjust_stack(env, 1); pushn(env, !v); } /* Bitwise arithmetics */ void instr_logand(eval_environ_t env) { unsigned long a = mf_c_val(get_arg(env, 1), ulong); unsigned long b = mf_c_val(get_arg(env, 0), ulong); if (PROG_TRACE_ENGINE) prog_trace(env, "LOGAND %lu %lu", a, b); adjust_stack(env, 2); pushn(env, a & b); } void instr_logor(eval_environ_t env) { unsigned long a = mf_c_val(get_arg(env, 1), ulong); unsigned long b = mf_c_val(get_arg(env, 0), ulong); if (PROG_TRACE_ENGINE) prog_trace(env, "LOGOR %lu %lu", a, b); adjust_stack(env, 2); pushn(env, a | b); } void instr_logxor(eval_environ_t env) { unsigned long a = mf_c_val(get_arg(env, 1), ulong); unsigned long b = mf_c_val(get_arg(env, 0), ulong); if (PROG_TRACE_ENGINE) prog_trace(env, "LOGXOR %lu %lu", a, b); adjust_stack(env, 2); pushn(env, a ^ b); } void instr_lognot(eval_environ_t env) { unsigned long v = mf_c_val(get_arg(env, 0), ulong); if (PROG_TRACE_ENGINE) prog_trace(env, "LOGNOT %ld", v); adjust_stack(env, 1); pushn(env, ~v); } /* Arithmetic instructions */ void instr_add(eval_environ_t env) { long a = mf_c_val(get_arg(env, 1), long); long b = mf_c_val(get_arg(env, 0), long); if (PROG_TRACE_ENGINE) prog_trace(env, "ADD %ld %ld", a, b); adjust_stack(env, 2); pushn(env, a + b); } void instr_sub(eval_environ_t env) { long a = mf_c_val(get_arg(env, 1), long); long b = mf_c_val(get_arg(env, 0), long); if (PROG_TRACE_ENGINE) prog_trace(env, "SUB %ld %ld", a, b); adjust_stack(env, 2); pushn(env, a - b); } void instr_mul(eval_environ_t env) { long a = mf_c_val(get_arg(env, 1), long); long b = mf_c_val(get_arg(env, 0), long); if (PROG_TRACE_ENGINE) prog_trace(env, "MUL %ld %ld", a, b); adjust_stack(env, 2); pushn(env, a * b); } void instr_div(eval_environ_t env) { long a = mf_c_val(get_arg(env, 1), long); long b = mf_c_val(get_arg(env, 0), long); if (PROG_TRACE_ENGINE) prog_trace(env, "DIV %ld %ld", a, b); adjust_stack(env, 2); if (b == 0) env_throw(env, mfe_divzero, "Division by zero at %08x", (unsigned int) env->pc); pushn(env, a / b); } void instr_mod(eval_environ_t env) { long a = mf_c_val(get_arg(env, 1), long); long b = mf_c_val(get_arg(env, 0), long); if (PROG_TRACE_ENGINE) prog_trace(env, "MOD %ld %ld", a, b); adjust_stack(env, 2); if (b == 0) env_throw(env, mfe_divzero, "Division by zero at %08x", (unsigned int) env->pc); pushn(env, a % b); } void instr_shl(eval_environ_t env) { long a = mf_c_val(get_arg(env, 1), long); long b = mf_c_val(get_arg(env, 0), long); if (PROG_TRACE_ENGINE) prog_trace(env, "SHL %ld %ld", a, b); adjust_stack(env, 2); pushn(env, a << (unsigned long) b); } void instr_shr(eval_environ_t env) { long a = mf_c_val(get_arg(env, 1), long); long b = mf_c_val(get_arg(env, 0), long); if (PROG_TRACE_ENGINE) prog_trace(env, "SHR %ld %ld", a, b); adjust_stack(env, 2); pushn(env, a >> (unsigned long) b); } void instr_neg(eval_environ_t env) { long v = mf_c_val(get_arg(env, 0), long); if (PROG_TRACE_ENGINE) prog_trace(env, "NEG %ld", v); adjust_stack(env, 1); pushn(env, -v); } /* Matching and Regular expression instructions */ /* REGEX: Basically it is useful only for debugging. Its effect is the same as that of instr_push */ void instr_regex(eval_environ_t env) { char buffer[REGEX_STRING_BUFSIZE]; size_t index = mf_c_val(get_immediate(env, 0), size); if (PROG_TRACE_ENGINE) prog_trace(env, "REGEX (%s) %s", regex_flags_to_string(regtab[index].regflags, buffer, sizeof buffer), (char*) env_data_ref(env, regtab[index].expr)); advance_pc(env, 1); push(env, (STKVAL) index); } void dump_regex(prog_counter_t i) { size_t index = mf_cell_c_value(prog[i], size); char buffer[REGEX_STRING_BUFSIZE]; printf("(%s) %s", regex_flags_to_string(regtab[index].regflags, buffer, sizeof buffer), (char*)(dataseg + regtab[index].expr)); } void instr_regmatch(eval_environ_t env) { int v; size_t index = mf_c_val(get_arg(env, 0), size); regex_t *re = ®tab[index].re; char *string; get_string_arg(env, 1, &string); env->matchstr = (STKVAL*)string - env->dataseg; adjust_stack(env, 2); if (PROG_TRACE_ENGINE) prog_trace(env, "REGMATCH %s %s", (char*)env_data_ref(env, regtab[index].expr), string); env->matchcount = re->re_nsub; if (env->matchsize < env->matchcount + 1) { void *p = realloc(env->matches, sizeof(env->matches[0]) * (env->matchcount + 1)); if (!p) runtime_error(env, _("not enough memory")); env->matches = p; env->matchsize = env->matchcount + 1; } v = regexec(re, string, env->matchcount + 1, env->matches, 0); pushn(env, v == 0); } void instr_regcomp(eval_environ_t env) { int v; char buffer[REGEX_STRING_BUFSIZE]; size_t expr_off = mf_c_val(get_arg(env, 0), size); char *expr; size_t index = mf_c_val(get_immediate(env, 0), size); struct rt_regex *rtx = ®tab[index]; get_string_arg(env, 0, &expr); if (PROG_TRACE_ENGINE) prog_trace(env, "REGCOMP %s %s", regex_flags_to_string(rtx->regflags, buffer, sizeof buffer), expr); advance_pc(env, 1); adjust_stack(env, 1); if (rtx->compiled) { regfree(&rtx->re); rtx->compiled = 0; } v = regcomp(&rtx->re, expr, rtx->regflags); if (v) { char errbuf[512]; regerror(v, &rtx->re, errbuf, sizeof(errbuf)); env_throw(env, mfe_regcomp, "compiling regex `%s': %s", expr, errbuf); } else { rtx->compiled = 1; rtx->expr = expr_off; } push(env, (STKVAL) index); } void dump_regcomp(prog_counter_t i) { size_t index = mf_cell_c_value(prog[i], size); struct rt_regex *rtx = ®tab[index]; char buffer[REGEX_STRING_BUFSIZE]; printf("%s", regex_flags_to_string(rtx->regflags, buffer, sizeof buffer)); } void instr_fnmatch(eval_environ_t env) { char *string, *pattern; get_string_arg(env, 1, &string); get_string_arg(env, 0, &pattern); adjust_stack(env, 2); if (PROG_TRACE_ENGINE) prog_trace(env, "FNMATCH %s %s", string, pattern); pushn(env, fnmatch(pattern, string, 0) == 0); } static int mx_match(eval_environ_t env, char *string, int (*matcher)(const char *name, void *data), void *data) { int rc = 0; struct dns_reply reply; mf_status mxstat; char *p = strchr(string, '@'); if (p) p++; else p = string; mxstat = dns_to_mf_status(mx_lookup(p, 0, &reply)); rc = 0; if (mxstat == mf_success) { int i; for (i = 0; i < reply.count; i++) { if (matcher(reply.data.str[i], data)) { rc = 1; break; } } } dns_reply_free(&reply); if (!mf_resolved(mxstat)) env_throw(env, mf_status_to_exception(mxstat), "cannot get MXs for %s", p); return rc; } static int fn_matcher(const char *string, void *data) { return fnmatch (data, string, 0) == 0; } void instr_fnmatch_mx(eval_environ_t env) { char *string, *pattern; get_string_arg(env, 1, &string); get_string_arg(env, 0, &pattern); adjust_stack(env, 2); if (PROG_TRACE_ENGINE) prog_trace(env, "FNMATCH,MX %s %s", string, pattern); pushn(env, mx_match(env, string, fn_matcher, pattern)); } static int regex_matcher(const char *string, void *data) { return regexec(data, string, 0, NULL, 0) == 0; } void instr_regmatch_mx(eval_environ_t env) { int rc; size_t index = mf_c_val(get_arg(env, 0), size); regex_t *re = ®tab[index].re; char *string; get_string_arg(env, 1, &string); adjust_stack(env, 2); if (PROG_TRACE_ENGINE) prog_trace(env, "REGMATCH,MX %s %s", (char*) env_data_ref(env, regtab[index].expr), string); rc = mx_match(env, string, regex_matcher, re); pushn(env, rc); } /* Mail filter specific instructions */ void instr_next(eval_environ_t env) { if (PROG_TRACE_ENGINE) prog_trace(env, "NEXT"); trace("%s%s:%u: next", mailfromd_msgid(env->ctx), ENV_LOC_FILE(env), ENV_LOC_LINE(env)); } void instr_result(eval_environ_t env) { sfsistat status = (sfsistat) mf_c_val(get_immediate(env, 0), int); char *code, *xcode; char *message; get_string_arg(env, 2, &message); get_string_arg(env, 1, &xcode); get_string_arg(env, 0, &code); if (PROG_TRACE_ENGINE) prog_trace(env, "RESULT %d %s %s %s", status, SP(code), SP(xcode), SP(message)); trace("%s%s:%u: %s %s %s %s", mailfromd_msgid(env->ctx), ENV_LOC_FILE(env), ENV_LOC_LINE(env), sfsistat_str(status), SP(code), SP(xcode), SP(message)); if (code[0] == 0) code = NULL; if (xcode[0] == 0) xcode = NULL; if (status == SMFIS_ACCEPT && env_msgmod_count(env)) runtime_warning(env, _("`accept' causes previous message " "modification commands to be ignored; " "call mmq_purge() prior to `accept', " "to suppress this warning")); env->status = status; env->setreply(env->data, code, xcode, message); advance_pc(env, 1); adjust_stack(env, 3); } void dump_result(prog_counter_t i) { printf("%s", sfsistat_str((sfsistat)mf_cell_c_value(prog[i], int))); } void instr_header(eval_environ_t env) { struct msgmod_closure *hdr = mu_alloc (sizeof(*hdr)); enum msgmod_opcode opcode = (enum msgmod_opcode) mf_c_val(get_immediate(env, 0), int); const char *name; char *value; get_string_arg(env, 0, &value); get_literal(env, 1, &name); advance_pc(env, 2); adjust_stack(env, 1); trace("%s%s:%u: %s %s %s", mailfromd_msgid(env->ctx), ENV_LOC_FILE(env), ENV_LOC_LINE(env), msgmod_opcode_str(opcode), name, SP(value)); env_msgmod_append(env, opcode, name, value, 1); } void dump_header(prog_counter_t i) { printf("%s %s", msgmod_opcode_str((enum msgmod_opcode) mf_cell_c_value(prog[i], int)), (char*)(dataseg + mf_cell_c_value(prog[i+1], size))); } void instr_builtin(eval_environ_t env) { const char *name; void (*handler)(eval_environ_t) = mf_c_val(get_immediate(env, 1), ptr); get_literal(env, 0, &name); if (PROG_TRACE_ENGINE) prog_trace(env, "BUILTIN %s", name); advance_pc(env, 2); handler(env); } void dump_builtin(prog_counter_t i) { printf("%s ", (char*)(dataseg + mf_cell_c_value(prog[i], size))); } void instr_concat(eval_environ_t env) { char * MFL_DATASEG left, * MFL_DATASEG right; size_t off; char *res; get_string_arg(env, 1, &left); get_string_arg(env, 0, &right); off = heap_reserve(env, strlen(left) + strlen(right) + 1); res = (char*) env_data_ref(env, off); strcat(strcpy(res, left), right); adjust_stack(env, 2); if (PROG_TRACE_ENGINE) prog_trace(env, "CONCAT ('%s','%s')='%s'", left, right, res); push(env, (STKVAL) off); } void dump_adjust(prog_counter_t i) { printf("%lu ", mf_cell_c_value(prog[i], ulong)); } void instr_asgn(eval_environ_t env) { STKVAL val = get_arg(env, 1); size_t dest = mf_c_val(get_arg(env, 0), size); adjust_stack(env, 2); if (PROG_TRACE_ENGINE) prog_trace(env, "ASGN %lu=%u", (unsigned long) dest, mf_c_val(val, uint)); env->dataseg[dest] = val; } void instr_catch(eval_environ_t env) { long off = mf_c_val(get_immediate(env, 0), long); unsigned toff = mf_c_val(get_immediate(env, 1), uint); size_t count = mf_c_val(env->dataseg[toff], size); STKVAL *tab = (STKVAL *) (env->dataseg + toff + 1); size_t i; prog_counter_t entry = env->pc + 3; if (PROG_TRACE_ENGINE) prog_trace(env, "CATCH %ld, %ld", entry, off); for (i = 0; i < count * NBMBITS; i++) if (((bitmask_bits_t)mf_c_val(tab[BIT_WORD(i)], uint)) & BIT_MASK(i)) { if (PROG_TRACE_ENGINE) prog_trace(env, "CATCH TARGET: %lu %s", (unsigned long) i, mf_exception_str(i)); env->catch_ctx[i].pc = entry; env->catch_ctx[i].tos = TOS_INVARIANT(env, env->tos); env->catch_ctx[i].base = TOS_INVARIANT(env, env->base); } advance_pc(env, off); } void dump_catch(prog_counter_t i) { size_t toff = mf_cell_c_value(prog[i+1], size); size_t count = mf_c_val(dataseg[toff], size); STKVAL *tab = (STKVAL *) (dataseg + toff + 1); printf("%ld (%ld)", mf_cell_c_value(prog[i], long), i + mf_cell_c_value(prog[i], long)); printf("; Targets:"); for (i = 0; i < count * NBMBITS; i++) if (((bitmask_bits_t)mf_c_val(tab[BIT_WORD(i)], uint)) & BIT_MASK(i)) printf(" %s(%lu)", mf_exception_str(i), (unsigned long) i); } void instr_throw(eval_environ_t env) { unsigned long n = mf_c_val(get_immediate(env, 0), ulong); size_t off = mf_c_val(get_arg(env, 0), size); advance_pc(env, 1); adjust_stack(env, 1); if (n > exception_count) runtime_error(env, _("invalid exception number: %lu"), n); if (PROG_TRACE_ENGINE) prog_trace(env, "THROW %s(%ld)", mf_exception_str(n), n); env_throw_0(env, (mf_exception) n, off); } void dump_throw(prog_counter_t i) { printf("%s (%u)", mf_exception_str(mf_cell_c_value(prog[i], uint)), mf_cell_c_value(prog[i], uint)); } void instr_echo(eval_environ_t env) { char *str = (char*) env_data_ref(env, mf_c_val(pop(env), size)); int rc = mu_stream_write(mf_strecho, str, strlen(str), NULL); if (rc == 0) rc = mu_stream_write(mf_strecho, "\n", 1, NULL); if (rc ) logmsg(MU_LOG_EMERG, "cannot write to echo stream: %s", mu_strerror (rc)); } void instr_return(eval_environ_t env) { if (PROG_TRACE_ENGINE) prog_trace(env, "RETURN"); env_leave_frame(env, 0); env->pc--; } void instr_retcatch(eval_environ_t env) { prog_counter_t pc = env->pc; if (PROG_TRACE_ENGINE) prog_trace(env, "RETCATCH"); env_leave_frame(env, 2); env->pc = pc; } void instr_saveex(eval_environ_t env) { unsigned off = mf_c_val(get_immediate(env, 0), uint); size_t count = mf_c_val(env->dataseg[off], size); STKVAL *tab = (STKVAL *) (env->dataseg + off + 1); size_t i; if (PROG_TRACE_ENGINE) prog_trace(env, "SAVEEX %x (%lu ex.)", off, (unsigned long) count); advance_pc(env, 1); for (i = 0; i < count * NBMBITS; i++) if (((bitmask_bits_t)mf_c_val(tab[BIT_WORD(i)], uint)) & BIT_MASK(i)) { mu_debug(MF_SOURCE_PROG, MU_DEBUG_TRACE9, ("Push Exception: %lu %lu <- pc=%lu, tos=%lu, base=%lu", (unsigned long) i, (unsigned long) TOS_INVARIANT(env,env->tos), (unsigned long) env->catch_ctx[i].pc, (unsigned long) env->catch_ctx[i].tos, (unsigned long) env->catch_ctx[i].base)); push(env, (STKVAL) env->catch_ctx[i].pc); push(env, (STKVAL) env->catch_ctx[i].tos); push(env, (STKVAL) env->catch_ctx[i].base); } push(env, (STKVAL) off); } void dump_saveex(prog_counter_t ctr) { size_t off = mf_cell_c_value(prog[ctr], size); size_t count = mf_c_val(dataseg[off], size); STKVAL *tab = (STKVAL *) (dataseg + off + 1); size_t i; printf("%lu:", (unsigned long) count); for (i = 0; i < count; i++) if (((bitmask_bits_t)mf_c_val(tab[BIT_WORD(i)], uint)) & BIT_MASK(i)) printf(" %lu", (unsigned long) i); } void instr_restex(eval_environ_t env) { unsigned off = mf_c_val(pop(env), uint); size_t count = mf_c_val(env->dataseg[off], size); STKVAL *tab = (STKVAL *) (env->dataseg + off + 1); size_t i; if (PROG_TRACE_ENGINE) prog_trace(env, "RESTEX %x (%lu ex.)", off, (unsigned long) count); if (!count) return; i = count * NBMBITS - 1; do { i--; if (((bitmask_bits_t)mf_c_val(tab[BIT_WORD(i)], uint)) & BIT_MASK(i)) { env->catch_ctx[i].base = (prog_counter_t) mf_c_val(pop(env), ulong); env->catch_ctx[i].tos = (prog_counter_t) mf_c_val(pop(env), ulong); env->catch_ctx[i].pc = (prog_counter_t) mf_c_val(pop(env), ulong); mu_debug(MF_SOURCE_PROG, MU_DEBUG_TRACE9, ("Pop Exception: %lu %lu -> pc=%lu, tos=%lu, base=%lu", (unsigned long) i, (unsigned long) TOS_INVARIANT(env,env->tos), (unsigned long) env->catch_ctx[i].pc, (unsigned long) env->catch_ctx[i].tos, (unsigned long) env->catch_ctx[i].base)); } } while (i > 0); } void instr_adjust(eval_environ_t env) { long nargs = mf_c_val(get_immediate(env, 0), long); if (PROG_TRACE_ENGINE) prog_trace(env, "ADJUST %ld", nargs); adjust_stack(env, nargs); advance_pc(env, 1); } void instr_popreg(eval_environ_t env) { env->reg = pop(env); if (PROG_TRACE_ENGINE) prog_trace(env, "POPREG %p", mf_c_val(env->reg, ptr)); } void instr_pushreg(eval_environ_t env) { if (PROG_TRACE_ENGINE) prog_trace(env, "PUSHREG %p", mf_c_val(env->reg, ptr)); push(env, env->reg); } void instr_funcall(eval_environ_t env) { const char *name; prog_counter_t pc = (prog_counter_t) mf_c_val(get_immediate(env, 1), ulong); get_literal(env, 0, &name); if (PROG_TRACE_ENGINE) prog_trace(env, "FUNCALL %s (%lu)", name, (unsigned long)pc); advance_pc(env, 2); env_make_frame(env); env->pc = pc-1; } void dump_funcall(prog_counter_t i) { printf("%s (%lu)", (char*)(dataseg + mf_cell_c_value(prog[i], size)), mf_cell_c_value(prog[i+1], ulong)); } /* Opcode: xlat N OFF Synopsis: Scan the table until xI==REG is found or the table is exhausted. If found, replace REG with yI and return 0. Otherwise, return 1 */ void instr_xlat(eval_environ_t env) { unsigned long i; unsigned long count = mf_c_val(get_immediate(env, 0), ulong); size_t off = mf_c_val(get_immediate(env, 1), size); STKVAL *tab = (STKVAL *) (env->dataseg + off); if (PROG_TRACE_ENGINE) prog_trace(env, "XLAT %lu %lu", count, (unsigned long) off); advance_pc(env, 2); for (i = 0; i < count; i += 2) { if (mf_c_val(tab[i], long) == mf_c_val(env->reg, long)) { env->reg = tab[i+1]; push(env, (STKVAL)0); return; } } push(env, (STKVAL)1); } void instr_xlats(eval_environ_t env) { unsigned long i; unsigned long count = mf_c_val(get_immediate(env, 0), uint); size_t off = mf_c_val(get_immediate(env, 1), size); STKVAL *tab = (STKVAL *) (env->dataseg + off); char *str = (char*) env_data_ref(env, mf_c_val(env->reg, size)); if (PROG_TRACE_ENGINE) prog_trace(env, "XLATS %lu %lu", count, (unsigned long) off); advance_pc(env, 2); for (i = 0; i < count; i += 2) { if (strcmp((char*)(env->dataseg + mf_c_val(tab[i], size)), str) == 0) { env->reg = tab[i+1]; push(env, (STKVAL)0); return; } } push(env, (STKVAL)1); } void dump_xlat(prog_counter_t i) { unsigned long j; unsigned long count = mf_cell_c_value(prog[i], ulong); size_t off = mf_cell_c_value(prog[i+1], size); STKVAL *tab = (STKVAL *) (dataseg + off); printf("%lu %lu ", count, (unsigned long) off); for (j = 0; j < count; j += 2) printf("(%ld %ld) ", mf_c_val(tab[j], long), mf_c_val(tab[j+1], long)); } void dump_xlats(prog_counter_t i) { unsigned long j; unsigned long count = mf_cell_c_value(prog[i], ulong); size_t off = mf_cell_c_value(prog[i+1], size); STKVAL *tab = (STKVAL *) (dataseg + off); printf("%lu %lu ", count, (unsigned long) off); for (j = 0; j < count; j += 2) printf("(%08lx %s %ld) ", mf_c_val(tab[j], ulong), (char*)(dataseg + mf_c_val(tab[j], size)), mf_c_val(tab[j+1], long)); } void instr_jreg(eval_environ_t env) { env->pc += (prog_counter_t)mf_c_val(env->reg, ulong) - 1; if (PROG_TRACE_ENGINE) prog_trace(env, "JREG %ld (%lu)", (long)((prog_counter_t)mf_c_val(env->reg, ulong) - 1), (unsigned long)env->pc); } static void _dumper(prog_counter_t pc, struct optab *op, void *data) { printf("%04lu: ", (unsigned long) pc); printf("%s ", op->name); if (op->dump) op->dump(pc + 1); putchar('\n'); } void dump_code(prog_counter_t start, prog_counter_t end) { if (end == 0) end = pc; scan_code(start, end, _dumper, NULL); } static void _fixup(prog_counter_t pc, struct optab *op, void *data) { struct mu_locus_range *locus = data; enum instr_opcode opcode = (enum instr_opcode) mf_cell_c_value(prog[pc], int); prog[pc].c_instr = op->instr; if (opcode == opcode_regex) { int rc; size_t index = mf_cell_c_value(prog[pc+1], size); if (index > regcount) { parse_error_locus(locus, "Invalid regexp index %lu", (unsigned long) index); return; } rc = regcomp(®tab[index].re, (char*) (dataseg + regtab[index].expr), regtab[index].regflags); if (rc) { char errbuf[512]; regerror(rc, ®tab[index].re, errbuf, sizeof(errbuf)); parse_error_locus(locus, "Cannot compile regex: %s", errbuf); } else regtab[index].compiled = 1; } else if (opcode == opcode_locus) { mu_locus_point_set_file(&locus->beg, (char*) (dataseg + mf_cell_c_value(prog[pc+1], size))); locus->beg.mu_line = mf_cell_c_value(prog[pc+2], size); } } void fixup_code() { struct mu_locus_range locus = MU_LOCUS_RANGE_INITIALIZER; scan_code(0, pc, _fixup, &locus); mu_locus_range_deinit(&locus); if (error_count) exit(EX_CONFIG); } void env_init(eval_environ_t env) { /* Initialize status and registers */ env->status = SMFIS_CONTINUE; env->tos = datasize + env->stack_size - 1; env->base = 0; mf_c_val(env->reg, long) = 0; //FIXME env->matches = NULL; env->matchsize = 0; env->matchcount = 0; env->matchstr = 0; env->numautos = 0; /* Initialize catch functions */ if (exception_count) memcpy(env->catch_ctx, env->defcatch_ctx, exception_count * sizeof(env->catch_ctx[0])); env_final_gc(env); } /* Initialize the data segment and relocate string variables */ static void init_dataseg(STKVAL *dseg, size_t count) { memcpy(dseg, dataseg, count * sizeof dataseg[0]); } void env_init_dataseg(eval_environ_t env) { init_dataseg(env->dataseg, dvarsize); } void env_make_frame0(eval_environ_t env) { push(env, (STKVAL) 0); push(env, (STKVAL) (env->base - env->tos)); env->base = env->tos; } void env_make_frame(eval_environ_t env) { push(env, (STKVAL) (env->pc + 1)); push(env, (STKVAL) (env->base - env->tos)); env->base = env->tos; } void env_leave_frame(eval_environ_t env, int nargs) { env->tos = env->base; env->base += (prog_counter_t) mf_c_val(pop(env), ulong) + 1; env->pc = (prog_counter_t) mf_c_val(pop(env), ulong); adjust_stack(env, nargs); } void env_push_string(eval_environ_t env, char *arg) { pushs(env, arg); } void env_push_number(eval_environ_t env, long arg) { push(env, (STKVAL) arg); } void env_push_pointer(eval_environ_t env, void *arg) { push(env, (STKVAL) arg); } int eval_environment(eval_environ_t env, prog_counter_t start) { if (setjmp(env->x_jmp)) return 1; for (env->pc = start; ; env->pc++) { if (env->pc >= pc) runtime_error(env, _("pc out of range")); if (!mf_cell_instr(prog[env->pc])) break; if (setjmp(env->catch_jmp) == 0) { (*mf_cell_instr(prog[env->pc]))(env); env_unregister_autos(env); } } return 0; } static void env_vsprintf_error(const char *fmt, va_list ap) { mu_error(_("out of memory while formatting error message:")); mu_verror(fmt, ap); } size_t env_vsprintf(eval_environ_t env, const char *biname, const char *fmt, va_list ap) { size_t n = 0, off; char *p, *s; while (1) { size_t k; size_t size; if (env->tos == env->toh) if (expand_dataseg(env, B2STACK(strlen(fmt)), NULL)) { env_vsprintf_error(fmt, ap); break; } size = (env->tos - env->toh - 1) * sizeof env->dataseg[0]; s = (char*) env_data_ref(env, env->toh); if (biname) { n = snprintf(s, size, "%s: ", biname); if (n >= size) { n += strlen(fmt); /* rough estimation */ if (expand_dataseg(env, B2STACK(n), NULL)) { env_vsprintf_error(fmt, ap); break; } continue; } } k = vsnprintf(s + n, size - n, fmt, ap); if (k >= size) { if (expand_dataseg(env, B2STACK(k), NULL)) { env_vsprintf_error(fmt, ap); break; } continue; } n += k; break; } p = (char*) env_data_ref(env, off = heap_reserve(env, n + 1)); memmove(p, s, n + 1); return off; } void env_throw_0(eval_environ_t env, mf_exception status, size_t off) { prog_counter_t pc; env_function_cleanup_flush(env, NULL); if (status > exception_count) runtime_error(env, _("unknown exception: %d: %s"), status, (char*) env_data_ref(env, off)); pc = env->catch_ctx[status].pc; if (pc) { /* Restore tos */ env->tos = TOS_INVARIANT(env, env->catch_ctx[status].tos); env->base = TOS_INVARIANT(env, env->catch_ctx[status].base); /* Reset the exception to avoid recursion. */ env->catch_ctx[status].pc = 0; /* Fixup the program counter */ env->pc = pc - 1; /* Generate normal entry frame */ push(env, (STKVAL) off); env_push_number(env, status); env_make_frame(env); longjmp(env->catch_jmp, 1); } runtime_error(env, _("uncaught exception %s: %s"), mf_exception_str(status), (char*) env_data_ref(env, off)); } void env_throw(eval_environ_t env, mf_exception status, const char *fmt, ...) { va_list ap; size_t off; va_start(ap, fmt); off = env_vsprintf(env, NULL, fmt, ap); va_end(ap); env_throw_0(env, status, off); } void env_throw_bi(eval_environ_t env, mf_exception status, const char *biname, const char *fmt, ...) { va_list ap; size_t off; va_start(ap, fmt); off = env_vsprintf(env, biname, fmt, ap); va_end(ap); env_throw_0(env, status, off); } sfsistat environment_get_status(eval_environ_t env) { return env->status; } SMFICTX * env_get_context(eval_environ_t env) { return env->ctx; } size_t env_get_line_count(eval_environ_t env) { return env->line_count; } /* Capturing support: Captured message is stored in env->stream. Before storing, any CRs (\r) are removed from the message. (FIXME: Actually, only those CRs should be removed that are followed by LF. However, that should not be a problem, since no CRs are allowed in RFC822 messages, unless followed by LF. Anyway, I'll fix that soon.) The number of lines in stream is stored in env->line_count. It is used to produce correct message size for functions that need it, e.g. bi_sa. */ int env_capture_start(eval_environ_t env) { int rc; env->line_count = 0; if (env->stream) { /* Drop any previously captured message data */ env_free_captured(env); mu_header_destroy(&env->header); /* Truncate existing stream and reposition to its beginning */ rc = mu_stream_truncate(env->stream, 0); if (rc == 0 && mu_stream_seek(env->stream, 0, SEEK_SET, NULL) == 0) return 0; /* If truncate fails, try to re-create the stream */ mu_stream_close(env->stream); mu_stream_destroy(&env->stream); } env->reposition = 0; rc = mu_temp_file_stream_create(&env->stream, NULL, 0); if (rc) { mu_error(_("%scannot create temporary stream: %s"), mailfromd_msgid(env->ctx), mu_strerror(rc)); return 1; } return 0; } static void env_capture_count_lines(eval_environ_t env, const char *buf, size_t size) { while (size) { size_t len; const char *p = memchr(buf, '\n', size); if (p) { env->line_count++; len = p - buf + 1; } else len = size; buf += len; size -= len; } } /* FIXME: Use CRLF filter */ int env_capture_write(eval_environ_t env, const char *buf, size_t size) { int rc; if (!env->stream) return 1; if (env->reposition) { rc = mu_stream_seek(env->stream, 0, SEEK_END, NULL); if (rc) { mu_error(_("%stemporary stream seek failed: %s"), mailfromd_msgid(env->ctx), mu_strerror(rc)); mu_stream_close(env->stream); mu_stream_destroy(&env->stream); return rc; } env->reposition = 0; } env_capture_count_lines(env, buf, size); while (size) { size_t len = mem_search(buf, '\r', size); rc = mu_stream_write(env->stream, buf, len, NULL); if (rc) { mu_error(_("%stemporary stream write failed: %s"), mailfromd_msgid(env->ctx), mu_strerror(rc)); mu_stream_close(env->stream); mu_stream_destroy(&env->stream); return rc; } if (buf[len] == '\r') len++; buf += len; size -= len; } return 0; } int env_capture_write_args(eval_environ_t env, ...) { va_list ap; char *arg; int rc = 0; if (!env->stream) return 1; va_start(ap, env); while (arg = va_arg(ap, char*)) { if (rc = env_capture_write(env, arg, strlen(arg))) break; } va_end(ap); return rc; } int env_get_header(eval_environ_t env, mu_header_t *hdr) { if (!env->header) { char *text; int rc; mu_off_t size; size_t total; size_t start; rc = mu_stream_size(env->stream, &size); if (rc) { mu_diag_funcall(MU_DIAG_ERROR, "mu_stream_size", NULL, rc); return rc; } text = mu_alloc(size + 1); rc = mu_stream_seek(env->stream, 0, SEEK_SET, NULL); if (rc) { mu_diag_funcall(MU_DIAG_ERROR, "mu_stream_seek", NULL, rc); free(text); return rc; } /* FIXME: Use "header" filter instead of this loop */ for (total = 0; total < size;) { size_t nrd; rc = mu_stream_read(env->stream, text + total, size - total, &nrd); if (rc) { mu_diag_funcall(MU_DIAG_ERROR, "mu_stream_read", NULL, rc); free(text); return rc; } if (nrd == 0) break; total += nrd; } text[total] = 0; /* FIXME: Reposition the stream at its end. This call may happen in the middle of capturing so I have to make sure to not disturb the capturing process. The same effect could have been achieved by using streamref, but this approach speeds up things a bit. */ env_reposition(env); if (memcmp (text, "From ", 5) == 0) start = strcspn (text, "\n") + 1; else start = 0; rc = mu_header_create(&env->header, text + start, total - start); free(text); if (rc) { mu_diag_funcall(MU_DIAG_ERROR, "mu_header_create", NULL, rc); return rc; } } *hdr = env->header; return 0; } /* MMQ */ void env_msgmod_clear(eval_environ_t env) { if (PROG_TRACE_ENGINE) prog_trace(env, "Clearing message modification queue"); if (env->msgmod) env->msgmod(env->data, NULL); mu_list_clear(env->mmq); } void destroy_msgmod_closure(void *item) { struct msgmod_closure *cmd = item; free(cmd->name); free(cmd->value); } void env_msgmod_append(eval_environ_t env, enum msgmod_opcode opcode, const char *name, const char *value, unsigned idx) { struct msgmod_closure *cp = mu_alloc(sizeof *cp); if (PROG_TRACE_ENGINE) prog_trace(env, "Registering %s \"%s\" \"%s\" %u", msgmod_opcode_str(opcode), SP(name), SP(value), idx); cp->opcode = opcode; cp->name = name ? mu_strdup(name) : NULL; cp->value = value ? mu_strdup(value) : NULL; cp->idx = idx; if (!env->mmq) { mu_list_create(&env->mmq); mu_list_set_destroy_item(env->mmq, destroy_msgmod_closure); } mu_debug(MF_SOURCE_ENGINE, MU_DEBUG_TRACE5, ("adding msgmod_closure: %s \"%s\" %s %u", msgmod_opcode_str(cp->opcode), SP(cp->name), SP(cp->value), cp->idx)); if (env->msgmod) env->msgmod(env->data, cp); mu_list_append(env->mmq, cp); } size_t env_msgmod_count(eval_environ_t env) { size_t n; if (!env->mmq) n = 0; else mu_list_count(env->mmq, &n); return n; } int env_msgmod_apply(eval_environ_t env, mu_list_action_t fun, void *data) { return mu_list_foreach(env->mmq, fun, data); } struct builtin_priv { /* Built-in private data structure */ void *(*init)(); void (*destroy)(void*); int (*free_capture)(void*); }; static size_t builtin_priv_size; static size_t builtin_priv_count; static struct builtin_priv *bi_priv; static void builtin_priv_destroy(void *ptr) { free(ptr); } int builtin_priv_register(void *(*init)(), void (*destroy)(void*), void (*free_capture)) { struct builtin_priv *p; int desc; if (!init) return -1; if (builtin_priv_count == builtin_priv_size) { if (builtin_priv_size == 0) builtin_priv_size = 4; bi_priv = mu_2nrealloc(bi_priv, &builtin_priv_size, sizeof bi_priv[0]); } desc = builtin_priv_count; p = bi_priv + builtin_priv_count++; p->init = init; p->destroy = destroy ? destroy : builtin_priv_destroy; p->free_capture = free_capture; return desc; } void env_free_captured(eval_environ_t env) { int i; if (!env->bi_priv_array) return; for (i = 0; i < builtin_priv_count; i++) if (bi_priv[i].free_capture && env->bi_priv_array[i]) bi_priv[i].free_capture(env->bi_priv_array[i]); } void * env_get_builtin_priv(eval_environ_t env, int id) { if (id < 0 || id >= builtin_priv_count) runtime_error(env, _("unknown built-in private data requested (%d)"), id); if (!env->bi_priv_array) { if (builtin_priv_count == 0) runtime_error(env, _("no built-in private data registered")); env->bi_priv_array = mu_calloc(builtin_priv_count, sizeof env->bi_priv_array[0]); } if (env->bi_priv_array[id] == NULL) { env->bi_priv_array[id] = bi_priv[id].init(); if (!env->bi_priv_array[id]) runtime_error(env, _("initial allocation for built-in " "private data #%d failed"), id); } return env->bi_priv_array[id]; } static void env_builtin_priv_destroy(eval_environ_t env) { if (env->bi_priv_array) { int i; for (i = 0; i < builtin_priv_count; i++) if (env->bi_priv_array[i]) bi_priv[i].destroy(env->bi_priv_array[i]); free(env->bi_priv_array); } } eval_environ_t create_environment(SMFICTX *ctx, const char *(*getsym)(void *data, const char *str), int (*setreply)(void *data, char *code, char *xcode, char *message), void (*msgmod)(void *data, struct msgmod_closure *cp), void *data) { struct eval_environ *env = calloc(1, sizeof *env); if (!env) { mu_error(_("not enough memory")); exit(1); } env->stack_size = stack_size; env->dataseg = calloc(stack_size + datasize, sizeof env->dataseg[0]); if (!env->dataseg) { mu_error(_("not enough memory")); exit(1); } init_dataseg(env->dataseg, datasize); env->ctx = ctx; env->data = data; env->getsym = getsym; env->setreply = setreply; env->msgmod = msgmod; env->status = SMFIS_CONTINUE; /* FIXME: The only registers that we initialize here. The rest is initialized in env_init. The top of heap should be retained across calls to handlers, since we store string variables there. This raises stack size requirements. */ env->toh = datasize; env->tos = datasize + env->stack_size - 1; env->bi_priv_array = NULL; env_create_cleanup_list(env); if (exception_count) { env->defcatch_ctx = mu_zalloc(exception_count * sizeof(env->defcatch_ctx[0])); env->catch_ctx = mu_zalloc(exception_count * sizeof(env->catch_ctx[0])); } return env; } void destroy_environment(eval_environ_t env) { free(env->catch_ctx); free(env->defcatch_ctx); free(env->dataseg); free(env->matches); mu_stream_destroy(&env->stream); mu_header_destroy(&env->header); mu_list_destroy(&env->cleanup_list); mu_list_destroy(&env->mmq); env_builtin_priv_destroy(env); free(env); } struct entry_point { int ishandler; prog_counter_t pc; union { enum smtp_state tag; const char *name; } v; }; struct enum_data { size_t i; struct module *mod; struct entry_point *base; }; static int function_counter(void *sym, void *data) { struct function *fp = (struct function *)sym; struct enum_data *d = data; if (fp->sym.module == d->mod && fp->sym.alias == NULL) d->i++; return 0; } static int function_lister(void *sym, void *data) { struct function *f = sym; struct enum_data *d = data; if (f->sym.module == d->mod && f->sym.alias == NULL) { d->base[d->i].ishandler = 0; d->base[d->i].v.name = f->sym.name; d->base[d->i].pc = f->entry; d->i++; } return 0; } static int comp_pc(const void *a, const void *b) { const struct entry_point *ap = a; const struct entry_point *bp = b; if (ap->pc < bp->pc) return -1; else if (ap->pc > bp->pc) return 1; return 0; } static void print_dataseg() { char *p = (char*) dataseg; size_t s = datasize * sizeof(STKVAL); size_t off; printf("Data segment:\n"); printf("-------------\n"); for (off = 0; off < s; ) { char vbuf[GACOPYZ_VBUFSIZE]; size_t rd = gacopyz_format_vbuf(vbuf, p + off, s - off); printf("%08lx: %s\n", (unsigned long) off, vbuf); off += rd; } } void print_code() { enum smtp_state tag; struct entry_point *ep; /* Entry points */ size_t epcount = 0; /* Number of entry points */ size_t i; struct enum_data d; struct module **modv; size_t modc; /* Get all modules */ collect_modules(&modv, &modc); /* Count all available entry points: */ d.i = 0; for (i = 0; i < modc; i++) { d.mod = modv[i]; symtab_enumerate(MODULE_SYMTAB(modv[i], namespace_function), function_counter, &d); } epcount = d.i; for (tag = smtp_state_first; tag < smtp_state_count; tag++) if (entry_point[tag]) epcount++; ep = mu_alloc((epcount+1)*sizeof *ep); /* Fill in entry points array */ i = 0; for (tag = smtp_state_first; tag < smtp_state_count; tag++) if (entry_point[tag]) { ep[i].ishandler = 1; ep[i].pc = entry_point[tag]; ep[i].v.tag = tag; i++; } d.base = ep; d.i = i; for (i = 0; i < modc; i++) { d.mod = modv[i]; symtab_enumerate(MODULE_SYMTAB(modv[i], namespace_function), function_lister, &d); } /* Dispose of module vector */ free (modv); /* Sort the array */ qsort(ep, epcount, sizeof ep[0], comp_pc); ep[epcount].pc = pc; /* Actually print the code */ for (i = 0; i < epcount; i++) { if (ep[i].ishandler) printf("HANDLER %s\n", state_to_string(ep[i].v.tag)); else printf("FUNCTION %s\n", ep[i].v.name); dump_code(ep[i].pc, ep[i+1].pc); } free(ep); print_dataseg(); } static eval_environ_t genv; static size_t *s_off; static size_t s_cnt; static int s_off_cmp(const void *a, const void *b) { char *pa = mf_c_val(genv->dataseg[*(const size_t *) a], str); char *pb = mf_c_val(genv->dataseg[*(const size_t *) b], str); if (pa < pb) return -1; else if (pa > pb) return 1; return 1; } void env_final_gc(eval_environ_t env) { size_t i; size_t top = datasize; size_t bot = env->toh; genv = env; /* Prepare s_off/s_count: remove any variables that are not in heap */ s_off = mu_calloc(dataseg_reloc_count, sizeof s_off[0]); for (i = 0, s_cnt = 0; i < dataseg_reloc_count; i++) { size_t p = mf_c_val(env->dataseg[dataseg_reloc[i]], size); if (top <= p && p < bot) s_off[s_cnt++] = dataseg_reloc[i]; } if (s_cnt) { qsort(s_off, s_cnt, sizeof s_off[0], s_off_cmp); /* Compact the variables */ env->toh = datasize; for (i = 0; i < s_cnt; i++) { size_t off = s_off[i]; #define S_PTR ((char*) env_data_ref(env, mf_c_val(env->dataseg[off], size))) size_t len = strlen(S_PTR) + 1; size_t q = heap_reserve(env, len); memmove(env_data_ref(env, q), S_PTR, len); env->dataseg[off] = (STKVAL) q; #undef S_PTR } free(s_off); } } void env_save_catches(eval_environ_t env) { memcpy(env->defcatch_ctx, env->catch_ctx, exception_count * sizeof env->defcatch_ctx[0]); }