/****************************************************************************** Copyright (c) 1992, 1995, 1996 Xerox Corporation. All rights reserved. Portions of this code were written by Stephen White, aka ghond. Use and copying of this software and preparation of derivative works based upon this software are permitted. Any distribution of this software or derivative works must comply with all applicable United States export control laws. This software is made available AS IS, and Xerox Corporation makes no warranty about the software, its performance or its conformity to any specification. Any person obtaining a copy of this software is requested to send their name and post office or electronic mail address to: Pavel Curtis Xerox PARC 3333 Coyote Hill Rd. Palo Alto, CA 94304 Pavel@Xerox.Com *****************************************************************************/ #include "my-string.h" #include "config.h" #include "db.h" #include "db_io.h" #include "decompile.h" #include "eval_env.h" #include "eval_vm.h" #include "exceptions.h" #include "execute.h" #include "functions.h" #include "list.h" #include "log.h" #include "numbers.h" #include "opcode.h" #include "options.h" #include "parse_cmd.h" #include "server.h" #include "storage.h" #include "streams.h" #include "structures.h" #include "sym_table.h" #include "tasks.h" #include "timers.h" #include "utils.h" #include "version.h" /* the following globals are the guts of the virtual machine: */ static activation *activ_stack = 0; static int max_stack_size = 0; static unsigned top_activ_stack; /* points to top-of-stack (last-occupied-slot), not next-empty-slot */ static int root_activ_vector; /* root_activ_vector == MAIN_VECTOR iff root activation is main vector */ /* these globals are not part of the vm because they get re-initialized after a suspend */ static int ticks_remaining; int task_timed_out; static int interpreter_is_running = 0; static int task_killed; static Timer_ID task_alarm_id; static task_kind current_task_kind; static const char *handler_verb_name; /* For in-DB traceback handling */ static Var handler_verb_args; /* macros to ease indexing into activation stack */ #define RUN_ACTIV activ_stack[top_activ_stack] #define CALLER_ACTIV activ_stack[top_activ_stack - 1] /**** error handling ****/ typedef enum { /* Reasons for executing a FINALLY handler */ /* These constants are stored in the DB, so don't change the order... */ FIN_FALL_THRU, FIN_RAISE, FIN_UNCAUGHT, FIN_RETURN, FIN_ABORT, /* This doesn't actually get you into a FINALLY... */ FIN_EXIT } Finally_Reason; /* * Keep a pool of the common size rt_stacks around to avoid beating up on * malloc. This doesn't really need tuning. Most rt_stacks will be less * than size 10. I rounded up to a size which won't waste a lot of space * with a powers-of-two malloc (while leaving some room for mymalloc * overhead, if any). */ static Var *rt_stack_quick; #define RT_STACK_QUICKSIZE 15 static void alloc_rt_stack(activation * a, int size) { Var *res; if (size <= RT_STACK_QUICKSIZE && rt_stack_quick) { res = rt_stack_quick; rt_stack_quick = rt_stack_quick[0].v.list; } else { res = mymalloc(MAX(size, RT_STACK_QUICKSIZE) * sizeof(Var), M_RT_STACK); } a->base_rt_stack = a->top_rt_stack = res; a->rt_stack_size = size; } static void free_rt_stack(activation * a) { Var *stack = a->base_rt_stack; if (a->rt_stack_size <= RT_STACK_QUICKSIZE) { stack[0].v.list = rt_stack_quick; rt_stack_quick = stack; } else myfree(stack, M_RT_STACK); } void print_error_backtrace(const char *msg, void (*output) (const char *)) { int t; Stream *str; if (!interpreter_is_running) return; str = new_stream(100); for (t = top_activ_stack; t >= 0; t--) { if (t != top_activ_stack) stream_printf(str, "... called from "); stream_printf(str, "#%d:%s", activ_stack[t].vloc, activ_stack[t].verbname); if (activ_stack[t].vloc != activ_stack[t].this) stream_printf(str, " (this == #%d)", activ_stack[t].this); stream_printf(str, ", line %d", find_line_number(activ_stack[t].prog, (t == 0 ? root_activ_vector : MAIN_VECTOR), activ_stack[t].error_pc)); if (t == top_activ_stack) stream_printf(str, ": %s", msg); output(reset_stream(str)); if (t > 0 && activ_stack[t].bi_func_pc) { stream_printf(str, "... called from built-in function %s()", name_func_by_num(activ_stack[t].bi_func_id)); output(reset_stream(str)); } } output("(End of traceback)"); free_stream(str); } void output_to_log(const char *line) { oklog("%s\n", line); } static Var backtrace_list; static void output_to_list(const char *line) { Var str; str.type = TYPE_STR; str.v.str = str_dup(line); backtrace_list = listappend(backtrace_list, str); } static Var error_backtrace_list(const char *msg) { backtrace_list = new_list(0); print_error_backtrace(msg, output_to_list); return backtrace_list; } static enum error suspend_task(package p) { vm the_vm = new_vm(current_task_id, top_activ_stack + 1); int i; enum error e; the_vm->max_stack_size = max_stack_size; the_vm->top_activ_stack = top_activ_stack; the_vm->root_activ_vector = root_activ_vector; the_vm->func_id = 0; /* shouldn't need func_id; */ for (i = 0; i <= top_activ_stack; i++) the_vm->activ_stack[i] = activ_stack[i]; e = (*p.u.susp.proc) (the_vm, p.u.susp.data); if (e != E_NONE) free_vm(the_vm, 0); return e; } static int raise_error(package p, enum outcome *outcome); static int unwind_stack(Finally_Reason why, Var value, enum outcome *outcome) { /* Returns true iff the entire stack was unwound and the interpreter * should stop, in which case *outcome is the correct outcome to return. */ Var code = (why == FIN_RAISE ? value.v.list[1] : zero); for (;;) { /* loop over activations */ activation *a = &(activ_stack[top_activ_stack]); void *bi_func_data = 0; int bi_func_pc; unsigned bi_func_id = 0; Objid player; Var v, *goal = a->base_rt_stack; if (why == FIN_EXIT) goal += value.v.list[1].v.num; while (a->top_rt_stack > goal) { /* loop over rt stack */ a->top_rt_stack--; v = *(a->top_rt_stack); if (why != FIN_ABORT && v.type == TYPE_FINALLY) { /* FINALLY handler */ a->pc = v.v.num; v.type = TYPE_INT; v.v.num = why; *(a->top_rt_stack++) = v; *(a->top_rt_stack++) = value; return 0; } else if (why == FIN_RAISE && v.type == TYPE_CATCH) { /* TRY-EXCEPT or `expr ! ...' handler */ Var *new_top = a->top_rt_stack - 2 * v.v.num; Var *vv; int found = 0; for (vv = new_top; vv < a->top_rt_stack; vv += 2) { if (!found && (vv->type != TYPE_LIST || ismember(code, *vv, 0))) { found = 1; v = *(vv + 1); if (v.type != TYPE_INT) panic("Non-numeric PC value on stack!"); a->pc = v.v.num; } free_var(*vv); } a->top_rt_stack = new_top; if (found) { *(a->top_rt_stack++) = value; return 0; } } else free_var(v); } if (why == FIN_EXIT) { a->pc = value.v.list[2].v.num; free_var(value); return 0; } bi_func_pc = a->bi_func_pc; if (bi_func_pc) { bi_func_id = a->bi_func_id; bi_func_data = a->bi_func_data; } player = a->player; free_activation(*a, 0); /* 0 == don't free bi_func_data */ if (top_activ_stack == 0) { /* done */ if (outcome) *outcome = (why == FIN_RETURN ? OUTCOME_DONE : OUTCOME_ABORTED); return 1; } top_activ_stack--; if (bi_func_pc != 0) { /* Must unwind through a built-in function */ package p; if (why == FIN_RETURN) { a = &(activ_stack[top_activ_stack]); p = call_bi_func(bi_func_id, value, bi_func_pc, a->progr, bi_func_data); switch (p.kind) { case BI_RETURN: *(a->top_rt_stack++) = p.u.ret; return 0; case BI_RAISE: if (a->debug) return raise_error(p, outcome); else { *(a->top_rt_stack++) = p.u.raise.code; free_str(p.u.raise.msg); free_var(p.u.raise.value); return 0; } case BI_SUSPEND: { enum error e = suspend_task(p); if (e == E_NONE) { if (outcome) *outcome = OUTCOME_BLOCKED; return 1; } else { value.type = TYPE_ERR; value.v.err = e; return unwind_stack(FIN_RAISE, value, outcome); } } case BI_CALL: a = &(activ_stack[top_activ_stack]); /* TOS has changed */ a->bi_func_id = bi_func_id; a->bi_func_pc = p.u.call.pc; a->bi_func_data = p.u.call.data; return 0; } } else { /* Built-in functions receive zero as a `returned value' on * errors and aborts, all further calls they make are short- * circuited with an immediate return of zero, and any errors * they raise are squelched. This is compatible with older, * pre-error-handling versions of the server, and thus * acceptible for the existing built-ins. It is conceivable * that this model will have to be revisited at some point in * the future. */ do { p = call_bi_func(bi_func_id, zero, bi_func_pc, a->progr, bi_func_data); switch (p.kind) { case BI_RETURN: free_var(p.u.ret); break; case BI_RAISE: free_var(p.u.raise.code); free_str(p.u.raise.msg); free_var(p.u.raise.value); break; case BI_SUSPEND: break; case BI_CALL: free_activation(activ_stack[top_activ_stack--], 0); bi_func_pc = p.u.call.pc; bi_func_data = p.u.call.data; break; } } while (p.kind == BI_CALL && bi_func_pc != 0); /* !tailcall */ } } else if (why == FIN_RETURN) { /* Push the value on the stack & go */ a = &(activ_stack[top_activ_stack]); *(a->top_rt_stack++) = value; return 0; } } } static int find_handler_activ(Var code) { /* Returns the index of the hottest activation with an active exception * handler for the given code. */ int frame; for (frame = top_activ_stack; frame >= 0; frame--) { activation *a = &(activ_stack[frame]); Var *v, *vv; for (v = a->top_rt_stack - 1; v >= a->base_rt_stack; v--) if (v->type == TYPE_CATCH) { for (vv = v - 2 * v->v.num; vv < v; vv += 2) if (vv->type != TYPE_LIST || ismember(code, *vv, 0)) return frame; v -= 2 * v->v.num; } } return -1; } static Var make_stack_list(activation * stack, int start, int end, int include_end, int root_vector, int line_numbers_too) { Var r; int count = 0, i, j; for (i = end; i >= start; i--) { if (include_end || i != end) count++; if (i != start && stack[i].bi_func_pc) count++; } r = new_list(count); j = 1; for (i = end; i >= start; i--) { Var v; if (include_end || i != end) { v = r.v.list[j++] = new_list(line_numbers_too ? 6 : 5); v.v.list[1].type = TYPE_OBJ; v.v.list[1].v.obj = stack[i].this; v.v.list[2].type = TYPE_STR; v.v.list[2].v.str = str_ref(stack[i].verb); v.v.list[3].type = TYPE_OBJ; v.v.list[3].v.obj = stack[i].progr; v.v.list[4].type = TYPE_OBJ; v.v.list[4].v.obj = stack[i].vloc; v.v.list[5].type = TYPE_OBJ; v.v.list[5].v.obj = stack[i].player; if (line_numbers_too) { v.v.list[6].type = TYPE_INT; v.v.list[6].v.num = find_line_number(stack[i].prog, (i == 0 ? root_vector : MAIN_VECTOR), stack[i].error_pc); } } if (i != start && stack[i].bi_func_pc) { v = r.v.list[j++] = new_list(line_numbers_too ? 6 : 5); v.v.list[1].type = TYPE_OBJ; v.v.list[1].v.obj = NOTHING; v.v.list[2].type = TYPE_STR; v.v.list[2].v.str = str_dup(name_func_by_num(stack[i].bi_func_id)); v.v.list[3].type = TYPE_OBJ; v.v.list[3].v.obj = NOTHING; v.v.list[4].type = TYPE_OBJ; v.v.list[4].v.obj = NOTHING; v.v.list[5].type = TYPE_OBJ; v.v.list[5].v.obj = stack[i].player; if (line_numbers_too) { v.v.list[6].type = TYPE_INT; v.v.list[6].v.num = stack[i].bi_func_pc; } } } return r; } static void save_handler_info(const char *vname, Var args) { handler_verb_name = vname; free_var(handler_verb_args); handler_verb_args = args; } static int raise_error(package p, enum outcome *outcome) { /* ASSERT: p.kind == BI_RAISE */ int handler_activ = find_handler_activ(p.u.raise.code); Finally_Reason why; Var value; if (handler_activ >= 0) { /* handler found */ why = FIN_RAISE; value = new_list(4); } else { /* uncaught exception */ why = FIN_UNCAUGHT; value = new_list(5); value.v.list[5] = error_backtrace_list(p.u.raise.msg); handler_activ = 0; /* get entire stack in list */ } value.v.list[1] = p.u.raise.code; value.v.list[2].type = TYPE_STR; value.v.list[2].v.str = p.u.raise.msg; value.v.list[3] = p.u.raise.value; value.v.list[4] = make_stack_list(activ_stack, handler_activ, top_activ_stack, 1, root_activ_vector, 1); if (why == FIN_UNCAUGHT) { save_handler_info("handle_uncaught_error", value); value = zero; } return unwind_stack(why, value, outcome); } static void abort_task(int is_ticks) { Var value; const char *msg = (is_ticks ? "Task ran out of ticks" : "Task ran out of seconds"); value = new_list(3); value.v.list[1].type = TYPE_STR; value.v.list[1].v.str = str_dup(is_ticks ? "ticks" : "seconds"); value.v.list[2] = make_stack_list(activ_stack, 0, top_activ_stack, 1, root_activ_vector, 1); value.v.list[3] = error_backtrace_list(msg); save_handler_info("handle_task_timeout", value); unwind_stack(FIN_ABORT, zero, 0); } /**** activation manipulation ****/ static int push_activation(void) { if (top_activ_stack < max_stack_size - 1) { top_activ_stack++; return 1; } else return 0; } void free_activation(activation a, char data_too) { Var *i; free_rt_env(a.rt_env, a.prog->num_var_names); for (i = a.base_rt_stack; i < a.top_rt_stack; i++) free_var(*i); free_rt_stack(&a); free_var(a.temp); free_str(a.verb); free_str(a.verbname); free_program(a.prog); if (data_too && a.bi_func_pc && a.bi_func_data) free_data(a.bi_func_data); /* else bi_func_state will be later freed by bi_function */ } /** Set up another activation for calling a verb does not change the vm in case of any error **/ enum error call_verb(Objid this, const char *vname, Var args, int do_pass) { /* if call succeeds, args will be consumed. If call fails, args will NOT be consumed -- it must therefore be freed by caller */ /* vname will never be consumed */ /* will only return E_MAXREC, E_INVIND, E_VERBNF, or E_NONE */ /* returns an error if there is one, and does not change the vm in that case, else sets up the activ_stack for the verb call and then returns E_NONE */ Objid where; db_verb_handle h; Program *program; Var *env; Var v; if (do_pass) if (!valid(RUN_ACTIV.vloc)) return E_INVIND; else where = db_object_parent(RUN_ACTIV.vloc); else where = this; if (!valid(where)) return E_INVIND; h = db_find_callable_verb(where, vname); if (!h.ptr) return E_VERBNF; else if (!push_activation()) return E_MAXREC; program = db_verb_program(h); vname = str_dup(vname); /* ensure that vname is heap-allocated */ RUN_ACTIV.prog = program_ref(program); RUN_ACTIV.this = this; RUN_ACTIV.progr = db_verb_owner(h); RUN_ACTIV.vloc = db_verb_definer(h); RUN_ACTIV.verb = str_ref(vname); RUN_ACTIV.verbname = str_ref(db_verb_names(h)); RUN_ACTIV.debug = (db_verb_flags(h) & VF_DEBUG); alloc_rt_stack(&RUN_ACTIV, program->main_vector.max_stack); RUN_ACTIV.pc = 0; RUN_ACTIV.error_pc = 0; RUN_ACTIV.bi_func_pc = 0; RUN_ACTIV.temp.type = TYPE_NONE; RUN_ACTIV.rt_env = env = new_rt_env(RUN_ACTIV.prog->num_var_names); fill_in_rt_consts(env, program->version); set_rt_env_obj(env, SLOT_THIS, this); set_rt_env_obj(env, SLOT_CALLER, CALLER_ACTIV.this); #define ENV_COPY(slot) \ set_rt_env_var(env, slot, var_ref(CALLER_ACTIV.rt_env[slot])) ENV_COPY(SLOT_ARGSTR); ENV_COPY(SLOT_DOBJ); ENV_COPY(SLOT_DOBJSTR); ENV_COPY(SLOT_PREPSTR); ENV_COPY(SLOT_IOBJ); ENV_COPY(SLOT_IOBJSTR); if (is_wizard(CALLER_ACTIV.progr) && (CALLER_ACTIV.rt_env[SLOT_PLAYER].type == TYPE_OBJ)) ENV_COPY(SLOT_PLAYER); else set_rt_env_obj(env, SLOT_PLAYER, CALLER_ACTIV.player); RUN_ACTIV.player = env[SLOT_PLAYER].v.obj; #undef ENV_COPY v.type = TYPE_STR; v.v.str = vname; set_rt_env_var(env, SLOT_VERB, v); /* no var_dup */ set_rt_env_var(env, SLOT_ARGS, args); /* no var_dup */ return E_NONE; } static int rangeset_check(int end, int from, int to) { if (from > end + 1 || to < 0) return 1; return 0; } #ifdef IGNORE_PROP_PROTECTED #define bi_prop_protected(prop, progr) (0) #else static int bi_prop_protected(enum bi_prop prop, Objid progr) { const char *pname = 0; /* silence warning */ if (is_wizard(progr)) return 0; switch (prop) { case BP_NAME: pname = "protect_name"; break; case BP_OWNER: pname = "protect_owner"; break; case BP_PROGRAMMER: pname = "protect_programmer"; break; case BP_WIZARD: pname = "protect_wizard"; break; case BP_R: pname = "protect_r"; break; case BP_W: pname = "protect_w"; break; case BP_F: pname = "protect_f"; break; case BP_LOCATION: pname = "protect_location"; break; case BP_CONTENTS: pname = "protect_contents"; break; default: panic("Can't happen in BI_PROP_PROTECTED!"); } return server_flag_option(pname); } #endif /* IGNORE_PROP_PROTECTED */ /** the main interpreter -- run() everything is just an entry point to it **/ static enum outcome run(char raise, enum error resumption_error, Var * result) { /* runs the_vm */ /* If the returned value is OUTCOME_DONE and RESULT is non-NULL, then * *RESULT is the value returned by the top frame. */ /* bc, bv, rts are distinguished as the state variables of run() their value capture the state of the running between OP_ cases */ Bytecodes bc; Byte *bv, *error_bv; Var *rts; /* next empty slot */ enum Opcode op; Var error_var; enum outcome outcome; /** a bunch of macros that work *ONLY* inside run() **/ /* helping macros about the runtime_stack. */ #define POP() (*(--rts)) #define PUSH(v) (*(rts++) = v) #define PUSH_REF(v) PUSH(var_ref(v)) #define TOP_RT_VALUE (*(rts - 1)) #define NEXT_TOP_RT_VALUE (*(rts - 2)) #define READ_BYTES(bv, nb) \ ( bv += nb, \ (nb == 1 \ ? bv[-1] \ : (nb == 2 \ ? ((unsigned) bv[-2] << 8) + bv[-1] \ : (((unsigned) bv[-4] << 24) \ + ((unsigned) bv[-3] << 16) \ + ((unsigned) bv[-2] << 8) \ + bv[-1])))) #define LOAD_STATE_VARIABLES() \ do { \ bc = ( (top_activ_stack != 0 || root_activ_vector == MAIN_VECTOR) \ ? RUN_ACTIV.prog->main_vector \ : RUN_ACTIV.prog->fork_vectors[root_activ_vector]); \ bv = bc.vector + RUN_ACTIV.pc; \ error_bv = bc.vector + RUN_ACTIV.error_pc; \ rts = RUN_ACTIV.top_rt_stack; /* next empty slot */ \ } while (0) #define STORE_STATE_VARIABLES() \ do { \ RUN_ACTIV.pc = bv - bc.vector; \ RUN_ACTIV.error_pc = error_bv - bc.vector; \ RUN_ACTIV.top_rt_stack = rts; \ } while (0) #define RAISE_ERROR(the_err) \ do { \ if (RUN_ACTIV.debug) { \ STORE_STATE_VARIABLES(); \ if (raise_error(make_error_pack(the_err), 0)) \ return OUTCOME_ABORTED; \ else { \ LOAD_STATE_VARIABLES(); \ goto next_opcode; \ } \ } \ } while (0) #define PUSH_ERROR(the_err) \ do { \ RAISE_ERROR(the_err); /* may not return */ \ error_var.type = TYPE_ERR; \ error_var.v.err = the_err; \ PUSH(error_var); \ } while (0) #define JUMP(label) (bv = bc.vector + label) /* end of major run() macros */ LOAD_STATE_VARIABLES(); if (raise) { error_bv = bv; PUSH_ERROR(resumption_error); } for (;;) { next_opcode: error_bv = bv; op = *bv++; if (COUNT_TICK(op)) { if (--ticks_remaining <= 0) { STORE_STATE_VARIABLES(); abort_task(1); return OUTCOME_ABORTED; } if (task_timed_out) { STORE_STATE_VARIABLES(); abort_task(0); return OUTCOME_ABORTED; } } if (task_killed) { STORE_STATE_VARIABLES(); unwind_stack(FIN_ABORT, zero, 0); return OUTCOME_ABORTED; } switch (op) { case OP_IF_QUES: case OP_IF: case OP_WHILE: case OP_EIF: do_test: { Var cond; cond = POP(); if (!is_true(cond)) /* jump if false */ JUMP(READ_BYTES(bv, bc.numbytes_label)); else { bv += bc.numbytes_label; } free_var(cond); } break; case OP_JUMP: JUMP(READ_BYTES(bv, bc.numbytes_label)); break; case OP_FOR_LIST: { unsigned id = READ_BYTES(bv, bc.numbytes_var_name); unsigned lab = READ_BYTES(bv, bc.numbytes_label); Var count, list; count = TOP_RT_VALUE; /* will be a integer */ list = NEXT_TOP_RT_VALUE; /* should be a list */ if (list.type != TYPE_LIST) { RAISE_ERROR(E_TYPE); free_var(POP()); free_var(POP()); JUMP(lab); } else if (count.v.num > list.v.list[0].v.num /* size */ ) { free_var(POP()); free_var(POP()); JUMP(lab); } else { free_var(RUN_ACTIV.rt_env[id]); RUN_ACTIV.rt_env[id] = var_ref(list.v.list[count.v.num]); count.v.num++; /* increment count */ TOP_RT_VALUE = count; } } break; case OP_FOR_RANGE: { unsigned id = READ_BYTES(bv, bc.numbytes_var_name); unsigned lab = READ_BYTES(bv, bc.numbytes_label); Var from, to; to = TOP_RT_VALUE; from = NEXT_TOP_RT_VALUE; if ((to.type != TYPE_INT && to.type != TYPE_OBJ) || to.type != from.type) { RAISE_ERROR(E_TYPE); free_var(POP()); free_var(POP()); JUMP(lab); } else if (to.type == TYPE_INT ? from.v.num > to.v.num : from.v.obj > to.v.obj) { free_var(POP()); free_var(POP()); JUMP(lab); } else { free_var(RUN_ACTIV.rt_env[id]); RUN_ACTIV.rt_env[id] = var_ref(from); if (to.type == TYPE_INT) from.v.num++; else from.v.obj++; NEXT_TOP_RT_VALUE = from; } } break; case OP_POP: free_var(POP()); break; case OP_IMM: { int slot; /* If we'd just throw it away anyway (eg verbdocs), skip both OPs. This accounts for most executions of OP_IMM in my tests. */ if (bv[bc.numbytes_literal] == OP_POP) { bv += bc.numbytes_literal + 1; break; } slot = READ_BYTES(bv, bc.numbytes_literal); PUSH_REF(RUN_ACTIV.prog->literals[slot]); } break; case OP_MAKE_EMPTY_LIST: { Var list; list = new_list(0); PUSH(list); } break; case OP_LIST_ADD_TAIL: { Var tail, list; tail = POP(); /* whatever */ list = POP(); /* should be list */ if (list.type != TYPE_LIST) { free_var(list); free_var(tail); PUSH_ERROR(E_TYPE); } else PUSH(listappend(list, tail)); } break; case OP_LIST_APPEND: { Var tail, list; tail = POP(); /* second, should be list */ list = POP(); /* first, should be list */ if (tail.type != TYPE_LIST || list.type != TYPE_LIST) { free_var(tail); free_var(list); PUSH_ERROR(E_TYPE); } else PUSH(listconcat(list, tail)); } break; case OP_INDEXSET: { Var value, index, list; value = POP(); /* rhs value */ index = POP(); /* index, should be integer */ list = POP(); /* lhs except last index, should be list or str */ /* whole thing should mean list[index] = value */ if ((list.type != TYPE_LIST && list.type != TYPE_STR) || index.type != TYPE_INT || (list.type == TYPE_STR && value.type != TYPE_STR)) { free_var(value); free_var(index); free_var(list); PUSH_ERROR(E_TYPE); } else if (index.v.num < 1 || (list.type == TYPE_LIST && index.v.num > list.v.list[0].v.num /* size */ ) || (list.type == TYPE_STR && index.v.num > (int) strlen(list.v.str))) { free_var(value); free_var(index); free_var(list); PUSH_ERROR(E_RANGE); } else if (list.type == TYPE_STR && strlen(value.v.str) != 1) { free_var(value); free_var(index); free_var(list); PUSH_ERROR(E_INVARG); } else if (list.type == TYPE_LIST) { Var res; if (var_refcount(list) == 1) res = list; else { res = var_dup(list); free_var(list); } PUSH(listset(res, value, index.v.num)); } else { /* TYPE_STR */ char *tmp_str = str_dup(list.v.str); free_str(list.v.str); tmp_str[index.v.num - 1] = value.v.str[0]; list.v.str = tmp_str; free_var(value); PUSH(list); } } break; case OP_MAKE_SINGLETON_LIST: { Var list; list = new_list(1); list.v.list[1] = POP(); PUSH(list); } break; case OP_CHECK_LIST_FOR_SPLICE: if (TOP_RT_VALUE.type != TYPE_LIST) { free_var(POP()); PUSH_ERROR(E_TYPE); } /* no op if top-rt-stack is a list */ break; case OP_PUT_TEMP: RUN_ACTIV.temp = var_ref(TOP_RT_VALUE); break; case OP_PUSH_TEMP: PUSH(RUN_ACTIV.temp); RUN_ACTIV.temp.type = TYPE_NONE; break; case OP_EQ: case OP_NE: { Var rhs, lhs, ans; rhs = POP(); lhs = POP(); ans.type = TYPE_INT; ans.v.num = (op == OP_EQ ? equality(rhs, lhs, 0) : !equality(rhs, lhs, 0)); PUSH(ans); free_var(rhs); free_var(lhs); } break; case OP_GT: case OP_LT: case OP_GE: case OP_LE: { Var rhs, lhs, ans; int comparison; rhs = POP(); lhs = POP(); if ((lhs.type == TYPE_INT || lhs.type == TYPE_FLOAT) && (rhs.type == TYPE_INT || rhs.type == TYPE_FLOAT)) { ans = compare_numbers(lhs, rhs); if (ans.type == TYPE_ERR) { free_var(rhs); free_var(lhs); PUSH_ERROR(ans.v.err); } else { comparison = ans.v.num; goto finish_comparison; } } else if (rhs.type != lhs.type || rhs.type == TYPE_LIST) { free_var(rhs); free_var(lhs); PUSH_ERROR(E_TYPE); } else { switch (rhs.type) { case TYPE_INT: comparison = compare_integers(lhs.v.num, rhs.v.num); break; case TYPE_OBJ: comparison = compare_integers(lhs.v.obj, rhs.v.obj); break; case TYPE_ERR: comparison = ((int) lhs.v.err) - ((int) rhs.v.err); break; case TYPE_STR: comparison = mystrcasecmp(lhs.v.str, rhs.v.str); break; default: errlog("RUN: Impossible type in comparison: %d\n", rhs.type); comparison = 0; } finish_comparison: ans.type = TYPE_INT; switch (op) { case OP_LT: ans.v.num = (comparison < 0); break; case OP_LE: ans.v.num = (comparison <= 0); break; case OP_GT: ans.v.num = (comparison > 0); break; case OP_GE: ans.v.num = (comparison >= 0); break; default: errlog("RUN: Imposible opcode in comparison: %d\n", op); break; } PUSH(ans); free_var(rhs); free_var(lhs); } } break; case OP_IN: { Var lhs, rhs, ans; rhs = POP(); /* should be list */ lhs = POP(); /* lhs, any type */ if (rhs.type != TYPE_LIST) { free_var(rhs); free_var(lhs); PUSH_ERROR(E_TYPE); } else { ans.type = TYPE_INT; ans.v.num = ismember(lhs, rhs, 0); PUSH(ans); free_var(rhs); free_var(lhs); } } break; case OP_MULT: case OP_MINUS: case OP_DIV: case OP_MOD: { Var lhs, rhs, ans; rhs = POP(); /* should be number */ lhs = POP(); /* should be number */ if ((lhs.type == TYPE_INT || lhs.type == TYPE_FLOAT) && (rhs.type == TYPE_INT || rhs.type == TYPE_FLOAT)) { switch (op) { case OP_MULT: ans = do_multiply(lhs, rhs); break; case OP_MINUS: ans = do_subtract(lhs, rhs); break; case OP_DIV: ans = do_divide(lhs, rhs); break; case OP_MOD: ans = do_modulus(lhs, rhs); break; default: errlog("RUN: Impossible opcode in arith ops: %d\n", op); break; } } else { ans.type = TYPE_ERR; ans.v.err = E_TYPE; } free_var(rhs); free_var(lhs); if (ans.type == TYPE_ERR) PUSH_ERROR(ans.v.err); else PUSH(ans); } break; case OP_ADD: { Var rhs, lhs, ans; rhs = POP(); lhs = POP(); if ((lhs.type == TYPE_INT || lhs.type == TYPE_FLOAT) && (rhs.type == TYPE_INT || rhs.type == TYPE_FLOAT)) ans = do_add(lhs, rhs); else if (lhs.type == TYPE_STR && rhs.type == TYPE_STR) { char *str; str = mymalloc((strlen(rhs.v.str) + strlen(lhs.v.str) + 1) * sizeof(char), M_STRING); sprintf(str, "%s%s", lhs.v.str, rhs.v.str); ans.type = TYPE_STR; ans.v.str = str; } else { ans.type = TYPE_ERR; ans.v.err = E_TYPE; } free_var(rhs); free_var(lhs); if (ans.type == TYPE_ERR) PUSH_ERROR(ans.v.err); else PUSH(ans); } break; case OP_AND: case OP_OR: { Var lhs; unsigned lab = READ_BYTES(bv, bc.numbytes_label); lhs = TOP_RT_VALUE; if ((op == OP_AND && !is_true(lhs)) || (op == OP_OR && is_true(lhs))) /* short-circuit */ JUMP(lab); else { free_var(POP()); } } break; case OP_NOT: { Var arg, ans; arg = POP(); ans.type = TYPE_INT; ans.v.num = !is_true(arg); PUSH(ans); free_var(arg); } break; case OP_UNARY_MINUS: { Var arg, ans; arg = POP(); if (arg.type == TYPE_INT) { ans.type = TYPE_INT; ans.v.num = -arg.v.num; } else if (arg.type == TYPE_FLOAT) ans = new_float(-*arg.v.fnum); else { free_var(arg); PUSH_ERROR(E_TYPE); break; } PUSH(ans); free_var(arg); } break; case OP_REF: { Var index, list; index = POP(); /* should be integer */ list = POP(); /* should be list or string */ if (index.type != TYPE_INT || (list.type != TYPE_LIST && list.type != TYPE_STR)) { free_var(index); free_var(list); PUSH_ERROR(E_TYPE); } else if (list.type == TYPE_LIST) { if (index.v.num <= 0 || index.v.num > list.v.list[0].v.num) { free_var(index); free_var(list); PUSH_ERROR(E_RANGE); } else { PUSH(var_ref(list.v.list[index.v.num])); free_var(index); free_var(list); } } else { /* list.type == TYPE_STR */ if (index.v.num <= 0 || index.v.num > (int) strlen(list.v.str)) { free_var(index); free_var(list); PUSH_ERROR(E_RANGE); } else { PUSH(strget(list, index)); free_var(index); free_var(list); } } } break; case OP_PUSH_REF: { Var index, list; index = TOP_RT_VALUE; list = NEXT_TOP_RT_VALUE; if (index.type != TYPE_INT || list.type != TYPE_LIST) { PUSH_ERROR(E_TYPE); } else if (index.v.num <= 0 || index.v.num > list.v.list[0].v.num) { PUSH_ERROR(E_RANGE); } else PUSH(var_ref(list.v.list[index.v.num])); } break; case OP_RANGE_REF: { Var base, from, to; to = POP(); /* should be integer */ from = POP(); /* should be integer */ base = POP(); /* should be list or string */ if ((base.type != TYPE_LIST && base.type != TYPE_STR) || to.type != TYPE_INT || from.type != TYPE_INT) { free_var(to); free_var(from); PUSH_ERROR(E_TYPE); } else { int len = (base.type == TYPE_STR ? strlen(base.v.str) : base.v.list[0].v.num); if (from.v.num <= to.v.num && (from.v.num <= 0 || from.v.num > len || to.v.num <= 0 || to.v.num > len)) { free_var(to); free_var(from); free_var(base); PUSH_ERROR(E_RANGE); } else { PUSH((base.type == TYPE_STR ? substr(base, from.v.num, to.v.num) : sublist(base, from.v.num, to.v.num))); /* base freed by substr/sublist */ free_var(from); free_var(to); } } } break; case OP_G_PUT: { unsigned id = READ_BYTES(bv, bc.numbytes_var_name); free_var(RUN_ACTIV.rt_env[id]); RUN_ACTIV.rt_env[id] = var_ref(TOP_RT_VALUE); } break; case OP_G_PUSH: { Var value; value = RUN_ACTIV.rt_env[READ_BYTES(bv, bc.numbytes_var_name)]; if (value.type == TYPE_NONE) PUSH_ERROR(E_VARNF); else PUSH_REF(value); } break; case OP_GET_PROP: { Var propname, obj, prop; propname = POP(); /* should be string */ obj = POP(); /* should be objid */ if (propname.type != TYPE_STR || obj.type != TYPE_OBJ) { free_var(propname); free_var(obj); PUSH_ERROR(E_TYPE); } else if (!valid(obj.v.obj)) { free_var(propname); free_var(obj); PUSH_ERROR(E_INVIND); } else { db_prop_handle h; h = db_find_property(obj.v.obj, propname.v.str, &prop); free_var(propname); free_var(obj); if (!h.ptr) PUSH_ERROR(E_PROPNF); else if (h.built_in ? bi_prop_protected(h.built_in, RUN_ACTIV.progr) : !db_property_allows(h, RUN_ACTIV.progr, PF_READ)) PUSH_ERROR(E_PERM); else if (h.built_in) PUSH(prop); /* it's already freshly allocated */ else PUSH_REF(prop); } } break; case OP_PUSH_GET_PROP: { Var propname, obj, prop; propname = TOP_RT_VALUE; obj = NEXT_TOP_RT_VALUE; if (propname.type != TYPE_STR || obj.type != TYPE_OBJ) PUSH_ERROR(E_TYPE); else if (!valid(obj.v.obj)) PUSH_ERROR(E_INVIND); else { db_prop_handle h; h = db_find_property(obj.v.obj, propname.v.str, &prop); if (!h.ptr) PUSH_ERROR(E_PROPNF); else if (h.built_in ? bi_prop_protected(h.built_in, RUN_ACTIV.progr) : !db_property_allows(h, RUN_ACTIV.progr, PF_READ)) PUSH_ERROR(E_PERM); else if (h.built_in) PUSH(prop); else PUSH_REF(prop); } } break; case OP_PUT_PROP: { Var obj, propname, rhs; rhs = POP(); /* any type */ propname = POP(); /* should be string */ obj = POP(); /* should be objid */ if (obj.type != TYPE_OBJ || propname.type != TYPE_STR) { free_var(rhs); free_var(propname); free_var(obj); PUSH_ERROR(E_TYPE); } else if (!valid(obj.v.obj)) { free_var(rhs); free_var(propname); free_var(obj); PUSH_ERROR(E_INVIND); } else { db_prop_handle h; enum error err = E_NONE; Objid progr = RUN_ACTIV.progr; h = db_find_property(obj.v.obj, propname.v.str, 0); if (!h.ptr) err = E_PROPNF; else { switch (h.built_in) { case BP_NONE: /* Not a built-in property */ if (!db_property_allows(h, progr, PF_WRITE)) err = E_PERM; break; case BP_NAME: if (rhs.type != TYPE_STR) err = E_TYPE; else if (!is_wizard(progr) && (is_user(obj.v.obj) || progr != db_object_owner(obj.v.obj))) err = E_PERM; break; case BP_OWNER: if (rhs.type != TYPE_OBJ) err = E_TYPE; else if (!is_wizard(progr)) err = E_PERM; break; case BP_PROGRAMMER: case BP_WIZARD: if (!is_wizard(progr)) err = E_PERM; else if (h.built_in == BP_WIZARD && !is_true(rhs) != !is_wizard(obj.v.obj)) { /* Notify only on changes in state; the !'s above * serve to canonicalize the truth values. */ /* First make sure traceback will be accurate. */ STORE_STATE_VARIABLES(); oklog("%sWIZARDED: #%d by programmer #%d\n", is_wizard(obj.v.obj) ? "DE" : "", obj.v.obj, progr); print_error_backtrace(is_wizard(obj.v.obj) ? "Wizard bit unset." : "Wizard bit set.", output_to_log); } break; case BP_R: case BP_W: case BP_F: if (progr != db_object_owner(obj.v.obj) && !is_wizard(progr)) err = E_PERM; break; case BP_LOCATION: case BP_CONTENTS: err = E_PERM; break; default: panic("Unknown built-in property in OP_PUT_PROP!"); } } free_var(propname); free_var(obj); if (err == E_NONE) { db_set_property_value(h, var_ref(rhs)); PUSH(rhs); } else { free_var(rhs); PUSH_ERROR(err); } } } break; case OP_FORK: case OP_FORK_WITH_ID: { Var time; unsigned id = 0, f_index; time = POP(); f_index = READ_BYTES(bv, bc.numbytes_fork); if (op == OP_FORK_WITH_ID) id = READ_BYTES(bv, bc.numbytes_var_name); if (time.type != TYPE_INT) { free_var(time); RAISE_ERROR(E_TYPE); } else if (time.v.num < 0) { free_var(time); RAISE_ERROR(E_INVARG); } else { enum error e; e = enqueue_forked_task2(RUN_ACTIV, f_index, time.v.num, op == OP_FORK_WITH_ID ? id : -1); if (e != E_NONE) RAISE_ERROR(e); } } break; case OP_CALL_VERB: { enum error err; Var args, verb, obj; args = POP(); /* args, should be list */ verb = POP(); /* verbname, should be string */ obj = POP(); /* objid, should be obj */ if (args.type != TYPE_LIST || verb.type != TYPE_STR || obj.type != TYPE_OBJ) err = E_TYPE; else if (!valid(obj.v.obj)) err = E_INVIND; else { STORE_STATE_VARIABLES(); err = call_verb(obj.v.obj, verb.v.str, args, 0); /* if there is no error, RUN_ACTIV is now the CALLEE's. args will be consumed in the new rt_env */ /* if there is an error, then RUN_ACTIV is unchanged, and args is not consumed in this case */ LOAD_STATE_VARIABLES(); } free_var(obj); free_var(verb); if (err != E_NONE) { /* there is an error, RUN_ACTIV unchanged, args must be freed */ free_var(args); PUSH_ERROR(err); } } break; case OP_RETURN: case OP_RETURN0: case OP_DONE: { Var ret_val; if (op == OP_RETURN) ret_val = POP(); else ret_val = zero; STORE_STATE_VARIABLES(); if (unwind_stack(FIN_RETURN, ret_val, &outcome)) { if (result && outcome == OUTCOME_DONE) *result = ret_val; else free_var(ret_val); return outcome; } LOAD_STATE_VARIABLES(); } break; case OP_BI_FUNC_CALL: { unsigned func_id; Var args; func_id = READ_BYTES(bv, 1); /* 1 == numbytes of func_id */ args = POP(); /* should be list */ if (args.type != TYPE_LIST) { free_var(args); PUSH_ERROR(E_TYPE); } else { package p; STORE_STATE_VARIABLES(); p = call_bi_func(func_id, args, 1, RUN_ACTIV.progr, 0); LOAD_STATE_VARIABLES(); switch (p.kind) { case BI_RETURN: PUSH(p.u.ret); break; case BI_RAISE: if (RUN_ACTIV.debug) { if (raise_error(p, 0)) return OUTCOME_ABORTED; else LOAD_STATE_VARIABLES(); } else { PUSH(p.u.raise.code); free_str(p.u.raise.msg); free_var(p.u.raise.value); } break; case BI_CALL: /* another activ has been pushed onto activ_stack */ RUN_ACTIV.bi_func_id = func_id; RUN_ACTIV.bi_func_data = p.u.call.data; RUN_ACTIV.bi_func_pc = p.u.call.pc; break; case BI_SUSPEND: { enum error e = suspend_task(p); if (e == E_NONE) return OUTCOME_BLOCKED; else PUSH_ERROR(e); } break; } } } break; case OP_EXTENDED: { register enum Extended_Opcode eop = *bv; bv++; if (COUNT_EOP_TICK(eop)) ticks_remaining--; switch (eop) { case EOP_RANGESET: { Var base, from, to, value; value = POP(); /* rhs value (list or string) */ to = POP(); /* end of range (integer) */ from = POP(); /* start of range (integer) */ base = POP(); /* lhs (list or string) */ /* base[from..to] = value */ if (to.type != TYPE_INT || from.type != TYPE_INT || (base.type != TYPE_LIST && base.type != TYPE_STR) || (value.type != TYPE_LIST && value.type != TYPE_STR) || (base.type != value.type)) { free_var(base); free_var(to); free_var(from); free_var(value); PUSH_ERROR(E_TYPE); } else if (rangeset_check(base.type == TYPE_STR ? strlen(base.v.str) : base.v.list[0].v.num, from.v.num, to.v.num)) { free_var(base); free_var(to); free_var(from); free_var(value); PUSH_ERROR(E_RANGE); } else if (base.type == TYPE_LIST) PUSH(listrangeset(base, from.v.num, to.v.num, value)); else /* TYPE_STR */ PUSH(strrangeset(base, from.v.num, to.v.num, value)); } break; case EOP_LENGTH: { unsigned i = READ_BYTES(bv, bc.numbytes_stack); Var item, v; v.type = TYPE_INT; item = RUN_ACTIV.base_rt_stack[i]; if (item.type == TYPE_STR) { v.v.num = strlen(item.v.str); PUSH(v); } else if (item.type == TYPE_LIST) { v.v.num = item.v.list[0].v.num; PUSH(v); } else PUSH_ERROR(E_TYPE); } break; case EOP_EXP: { Var lhs, rhs, ans; rhs = POP(); lhs = POP(); ans = do_power(lhs, rhs); free_var(lhs); free_var(rhs); if (ans.type == TYPE_ERR) PUSH_ERROR(ans.v.err); else PUSH(ans); } break; /* Scattered FOR */ case EOP_FOR_SCATTER: { unsigned lab = READ_BYTES(bv, bc.numbytes_label); Var count, list; count = TOP_RT_VALUE; /* will be a integer */ list = NEXT_TOP_RT_VALUE; /* should be a list */ if (list.type != TYPE_LIST) { RAISE_ERROR(E_TYPE); free_var(POP()); free_var(POP()); JUMP(lab); break; } else if (count.v.num > list.v.list[0].v.num /* size */ ) { free_var(POP()); free_var(POP()); JUMP(lab); break; } { int nargs = READ_BYTES(bv, 1); int nreq = READ_BYTES(bv, 1); int rest = READ_BYTES(bv, 1); int have_rest = (rest > nargs ? 0 : 1); Var list; int len = 0, nopt_avail, nrest, i, offset; int done, where = 0; enum error e = E_NONE; /* Get list item AND increment index */ list = NEXT_TOP_RT_VALUE.v.list[TOP_RT_VALUE.v. num++]; if (list.type != TYPE_LIST) e = E_TYPE; else if ((len = list.v.list[0].v.num) < nreq || (!have_rest && len > nargs)) e = E_ARGS; if (e != E_NONE) { /* skip rest of operands */ free_var(POP()); /* replace list with error code */ PUSH_ERROR(e); for (i = 1; i <= nargs; i++) { READ_BYTES(bv, bc.numbytes_var_name); READ_BYTES(bv, bc.numbytes_label); } } else { nopt_avail = len - nreq; nrest = (have_rest && len >= nargs ? len - nargs + 1 : 0); for (offset = 0, i = 1; i <= nargs; i++) { int id = READ_BYTES(bv, bc.numbytes_var_name); int label = READ_BYTES(bv, bc.numbytes_label); if (i == rest) { /* rest */ free_var(RUN_ACTIV.rt_env[id]); RUN_ACTIV.rt_env[id] = sublist(var_ref(list), i, i + nrest - 1); offset += nrest - 1; } else if (label == 0) { /* required */ free_var(RUN_ACTIV.rt_env[id]); RUN_ACTIV.rt_env[id] = var_ref(list.v. list[i + offset]); } else { /* optional */ if (nopt_avail > 0) { nopt_avail--; free_var(RUN_ACTIV.rt_env[id]); RUN_ACTIV.rt_env[id] = var_ref(list.v.list[i + offset]); } else { offset--; if (where == 0 && label != 1) where = label; } } } } done = READ_BYTES(bv, bc.numbytes_label); if (where == 0) JUMP(done); else JUMP(where); } } break; /* /Scattered FOR */ case EOP_SCATTER: { int nargs = READ_BYTES(bv, 1); int nreq = READ_BYTES(bv, 1); int rest = READ_BYTES(bv, 1); int have_rest = (rest > nargs ? 0 : 1); Var list; int len = 0, nopt_avail, nrest, i, offset; int done, where = 0; enum error e = E_NONE; list = TOP_RT_VALUE; if (list.type != TYPE_LIST) e = E_TYPE; else if ((len = list.v.list[0].v.num) < nreq || (!have_rest && len > nargs)) e = E_ARGS; if (e != E_NONE) { /* skip rest of operands */ free_var(POP()); /* replace list with error code */ PUSH_ERROR(e); for (i = 1; i <= nargs; i++) { READ_BYTES(bv, bc.numbytes_var_name); READ_BYTES(bv, bc.numbytes_label); } } else { nopt_avail = len - nreq; nrest = (have_rest && len >= nargs ? len - nargs + 1 : 0); for (offset = 0, i = 1; i <= nargs; i++) { int id = READ_BYTES(bv, bc.numbytes_var_name); int label = READ_BYTES(bv, bc.numbytes_label); if (i == rest) { /* rest */ free_var(RUN_ACTIV.rt_env[id]); RUN_ACTIV.rt_env[id] = sublist(var_ref(list), i, i + nrest - 1); offset += nrest - 1; } else if (label == 0) { /* required */ free_var(RUN_ACTIV.rt_env[id]); RUN_ACTIV.rt_env[id] = var_ref(list.v.list[i + offset]); } else { /* optional */ if (nopt_avail > 0) { nopt_avail--; free_var(RUN_ACTIV.rt_env[id]); RUN_ACTIV.rt_env[id] = var_ref(list.v.list[i + offset]); } else { offset--; if (where == 0 && label != 1) where = label; } } } } done = READ_BYTES(bv, bc.numbytes_label); if (where == 0) JUMP(done); else JUMP(where); } break; case EOP_PUSH_LABEL: case EOP_TRY_FINALLY: { Var v; v.type = (eop == EOP_PUSH_LABEL ? TYPE_INT : TYPE_FINALLY); v.v.num = READ_BYTES(bv, bc.numbytes_label); PUSH(v); } break; case EOP_CATCH: case EOP_TRY_EXCEPT: { Var v; v.type = TYPE_CATCH; v.v.num = (eop == EOP_CATCH ? 1 : READ_BYTES(bv, 1)); PUSH(v); } break; case EOP_END_CATCH: case EOP_END_EXCEPT: { Var v, marker; int i; if (eop == EOP_END_CATCH) v = POP(); marker = POP(); if (marker.type != TYPE_CATCH) panic("Stack marker is not TYPE_CATCH!"); for (i = 0; i < marker.v.num; i++) { (void) POP(); /* handler PC */ free_var(POP()); /* code list */ } if (eop == EOP_END_CATCH) PUSH(v); JUMP(READ_BYTES(bv, bc.numbytes_label)); } break; case EOP_END_FINALLY: { Var v, why; v = POP(); if (v.type != TYPE_FINALLY) panic("Stack marker is not TYPE_FINALLY!"); why.type = TYPE_INT; why.v.num = FIN_FALL_THRU; PUSH(why); PUSH(zero); } break; case EOP_CONTINUE: { Var v, why; v = POP(); why = POP(); switch (why.type == TYPE_INT ? why.v.num : -1) { case FIN_FALL_THRU: /* Do nothing; normal case. */ break; case FIN_EXIT: case FIN_RAISE: case FIN_RETURN: case FIN_UNCAUGHT: STORE_STATE_VARIABLES(); if (unwind_stack(why.v.num, v, &outcome)) return outcome; LOAD_STATE_VARIABLES(); break; default: panic("Unknown FINALLY reason!"); } } break; case EOP_WHILE_ID: { unsigned id = READ_BYTES(bv, bc.numbytes_var_name); free_var(RUN_ACTIV.rt_env[id]); RUN_ACTIV.rt_env[id] = var_ref(TOP_RT_VALUE); } goto do_test; case EOP_EXIT_ID: READ_BYTES(bv, bc.numbytes_var_name); /* ignore id */ /* fall thru */ case EOP_EXIT: { Var v; v = new_list(2); v.v.list[1].type = TYPE_INT; v.v.list[1].v.num = READ_BYTES(bv, bc.numbytes_stack); v.v.list[2].type = TYPE_INT; v.v.list[2].v.num = READ_BYTES(bv, bc.numbytes_label); STORE_STATE_VARIABLES(); unwind_stack(FIN_EXIT, v, 0); LOAD_STATE_VARIABLES(); } break; default: panic("Unknown extended opcode!"); } } break; /* These opcodes account for about 20% of all opcodes executed, so let's split out the case stmt so the compiler can help us out. If you're here because the #error below got tripped, just change the set of case stmts below for OP_PUSH and OP_PUT to be 0..NUM_READY_VARS-1. */ #if NUM_READY_VARS != 32 #error NUM_READY_VARS expected to be 32 #endif case OP_PUSH: case OP_PUSH + 1: case OP_PUSH + 2: case OP_PUSH + 3: case OP_PUSH + 4: case OP_PUSH + 5: case OP_PUSH + 6: case OP_PUSH + 7: case OP_PUSH + 8: case OP_PUSH + 9: case OP_PUSH + 10: case OP_PUSH + 11: case OP_PUSH + 12: case OP_PUSH + 13: case OP_PUSH + 14: case OP_PUSH + 15: case OP_PUSH + 16: case OP_PUSH + 17: case OP_PUSH + 18: case OP_PUSH + 19: case OP_PUSH + 20: case OP_PUSH + 21: case OP_PUSH + 22: case OP_PUSH + 23: case OP_PUSH + 24: case OP_PUSH + 25: case OP_PUSH + 26: case OP_PUSH + 27: case OP_PUSH + 28: case OP_PUSH + 29: case OP_PUSH + 30: case OP_PUSH + 31: { Var value; value = RUN_ACTIV.rt_env[PUSH_n_INDEX(op)]; if (value.type == TYPE_NONE) { free_var(value); PUSH_ERROR(E_VARNF); } else PUSH_REF(value); } break; #ifdef BYTECODE_REDUCE_REF case OP_PUSH_CLEAR: case OP_PUSH_CLEAR + 1: case OP_PUSH_CLEAR + 2: case OP_PUSH_CLEAR + 3: case OP_PUSH_CLEAR + 4: case OP_PUSH_CLEAR + 5: case OP_PUSH_CLEAR + 6: case OP_PUSH_CLEAR + 7: case OP_PUSH_CLEAR + 8: case OP_PUSH_CLEAR + 9: case OP_PUSH_CLEAR + 10: case OP_PUSH_CLEAR + 11: case OP_PUSH_CLEAR + 12: case OP_PUSH_CLEAR + 13: case OP_PUSH_CLEAR + 14: case OP_PUSH_CLEAR + 15: case OP_PUSH_CLEAR + 16: case OP_PUSH_CLEAR + 17: case OP_PUSH_CLEAR + 18: case OP_PUSH_CLEAR + 19: case OP_PUSH_CLEAR + 20: case OP_PUSH_CLEAR + 21: case OP_PUSH_CLEAR + 22: case OP_PUSH_CLEAR + 23: case OP_PUSH_CLEAR + 24: case OP_PUSH_CLEAR + 25: case OP_PUSH_CLEAR + 26: case OP_PUSH_CLEAR + 27: case OP_PUSH_CLEAR + 28: case OP_PUSH_CLEAR + 29: case OP_PUSH_CLEAR + 30: case OP_PUSH_CLEAR + 31: { Var *vp; vp = &RUN_ACTIV.rt_env[PUSH_CLEAR_n_INDEX(op)]; if (vp->type == TYPE_NONE) { PUSH_ERROR(E_VARNF); } else { PUSH(*vp); vp->type = TYPE_NONE; } } break; #endif /* BYTECODE_REDUCE_REF */ case OP_PUT: case OP_PUT + 1: case OP_PUT + 2: case OP_PUT + 3: case OP_PUT + 4: case OP_PUT + 5: case OP_PUT + 6: case OP_PUT + 7: case OP_PUT + 8: case OP_PUT + 9: case OP_PUT + 10: case OP_PUT + 11: case OP_PUT + 12: case OP_PUT + 13: case OP_PUT + 14: case OP_PUT + 15: case OP_PUT + 16: case OP_PUT + 17: case OP_PUT + 18: case OP_PUT + 19: case OP_PUT + 20: case OP_PUT + 21: case OP_PUT + 22: case OP_PUT + 23: case OP_PUT + 24: case OP_PUT + 25: case OP_PUT + 26: case OP_PUT + 27: case OP_PUT + 28: case OP_PUT + 29: case OP_PUT + 30: case OP_PUT + 31: { Var *varp = &RUN_ACTIV.rt_env[PUT_n_INDEX(op)]; free_var(*varp); if (bv[0] == OP_POP) { *varp = POP(); ++bv; } else *varp = var_ref(TOP_RT_VALUE); } break; default: if (IS_OPTIM_NUM_OPCODE(op)) { Var value; value.type = TYPE_INT; value.v.num = OPCODE_TO_OPTIM_NUM(op); PUSH(value); } else panic("Unknown opcode!"); break; } } } /**** manipulating data of task ****/ static int timeouts_enabled = 1; /* set to 0 in debugger to disable timeouts */ static void task_timeout(Timer_ID id, Timer_Data data) { task_timed_out = timeouts_enabled; } static Timer_ID setup_task_execution_limits(int seconds, int ticks) { task_alarm_id = set_virtual_timer(seconds < 1 ? 1 : seconds, task_timeout, 0); task_timed_out = task_killed = 0; ticks_remaining = (ticks < 100 ? 100 : ticks); return task_alarm_id; } enum outcome run_interpreter(char raise, enum error e, Var * result, int is_fg, int do_db_tracebacks) /* raise is boolean, true iff an error should be raised. e is the specific error to be raised if so. (in earlier versions, an error was raised iff e != E_NONE, but now it's possible to raise E_NONE on resumption from suspend().) */ { enum outcome ret; setup_task_execution_limits(is_fg ? server_int_option("fg_seconds", DEFAULT_FG_SECONDS) : server_int_option("bg_seconds", DEFAULT_BG_SECONDS), is_fg ? server_int_option("fg_ticks", DEFAULT_FG_TICKS) : server_int_option("bg_ticks", DEFAULT_BG_TICKS)); handler_verb_args = zero; handler_verb_name = 0; interpreter_is_running = 1; ret = run(raise, e, result); interpreter_is_running = 0; task_timed_out = 0; cancel_timer(task_alarm_id); if (ret == OUTCOME_ABORTED && handler_verb_name) { db_verb_handle h; Var args, handled, traceback; int i; args = handler_verb_args; h = db_find_callable_verb(SYSTEM_OBJECT, handler_verb_name); if (do_db_tracebacks && h.ptr) { ret = do_server_verb_task(SYSTEM_OBJECT, handler_verb_name, var_ref(handler_verb_args), h, activ_stack[0].player, "", &handled, 0); if ((ret == OUTCOME_DONE && is_true(handled)) || ret == OUTCOME_BLOCKED) { /* Assume the in-DB code handled it */ free_var(args); return OUTCOME_ABORTED; /* original ret value */ } } i = args.v.list[0].v.num; traceback = args.v.list[i]; /* traceback is always the last argument */ for (i = 1; i <= traceback.v.list[0].v.num; i++) notify(activ_stack[0].player, traceback.v.list[i].v.str); free_var(args); } return ret; } Objid caller() { return RUN_ACTIV.this; } static void check_activ_stack_size(int max) { if (max_stack_size != max) { if (activ_stack) myfree(activ_stack, M_VM); activ_stack = mymalloc(sizeof(activation) * max, M_VM); max_stack_size = max; } } static int current_max_stack_size(void) { int max = server_int_option("max_stack_depth", DEFAULT_MAX_STACK_DEPTH); if (max < DEFAULT_MAX_STACK_DEPTH) max = DEFAULT_MAX_STACK_DEPTH; return max; } /**** There are two methods of starting a new task: (1) Create a new one (2) Resume an old one */ /* procedure to create a new task */ static enum outcome do_task(Program * prog, int which_vector, Var * result, int do_db_tracebacks) { /* which vector determines the vector for the root_activ. a forked task can also have which_vector == MAIN_VECTOR. this happens iff it is recovered from a read from disk, because in that case the forked statement is parsed as the main vector */ int forked = (current_task_kind == TASK_FORKED); RUN_ACTIV.prog = program_ref(prog); root_activ_vector = which_vector; /* main or which of the forked */ alloc_rt_stack(&RUN_ACTIV, (which_vector == MAIN_VECTOR ? prog->main_vector.max_stack : prog->fork_vectors[which_vector].max_stack)); RUN_ACTIV.pc = 0; RUN_ACTIV.error_pc = 0; RUN_ACTIV.bi_func_pc = 0; RUN_ACTIV.temp.type = TYPE_NONE; return run_interpreter(0, E_NONE, result, !forked, do_db_tracebacks); } /* procedure to resume an old task */ enum outcome resume_from_previous_vm(vm the_vm, Var v, task_kind kind, Var * result) { int i; current_task_kind = kind; check_activ_stack_size(the_vm->max_stack_size); top_activ_stack = the_vm->top_activ_stack; root_activ_vector = the_vm->root_activ_vector; for (i = 0; i <= top_activ_stack; i++) activ_stack[i] = the_vm->activ_stack[i]; free_vm(the_vm, 0); if (v.type == TYPE_ERR) return run_interpreter(1, v.v.err, result, 0, 1); else { /* PUSH_REF(v) */ *(RUN_ACTIV.top_rt_stack++) = var_ref(v); return run_interpreter(0, E_NONE, result, 0, 1); } } /*** external functions ***/ enum outcome do_server_verb_task(Objid this, const char *verb, Var args, db_verb_handle h, Objid player, const char *argstr, Var * result, int do_db_tracebacks) { return do_server_program_task(this, verb, args, db_verb_definer(h), db_verb_names(h), db_verb_program(h), db_verb_owner(h), db_verb_flags(h) & VF_DEBUG, player, argstr, result, do_db_tracebacks); } enum outcome do_server_program_task(Objid this, const char *verb, Var args, Objid vloc, const char *verbname, Program * program, Objid progr, int debug, Objid player, const char *argstr, Var * result, int do_db_tracebacks) { Var *env; current_task_kind = TASK_INPUT; check_activ_stack_size(current_max_stack_size()); top_activ_stack = 0; RUN_ACTIV.rt_env = env = new_rt_env(program->num_var_names); RUN_ACTIV.this = this; RUN_ACTIV.player = player; RUN_ACTIV.progr = progr; RUN_ACTIV.vloc = vloc; RUN_ACTIV.verb = str_dup(verb); RUN_ACTIV.verbname = str_dup(verbname); RUN_ACTIV.debug = debug; fill_in_rt_consts(env, program->version); set_rt_env_obj(env, SLOT_PLAYER, player); set_rt_env_obj(env, SLOT_CALLER, -1); set_rt_env_obj(env, SLOT_THIS, this); set_rt_env_obj(env, SLOT_DOBJ, NOTHING); set_rt_env_obj(env, SLOT_IOBJ, NOTHING); set_rt_env_str(env, SLOT_DOBJSTR, str_dup("")); set_rt_env_str(env, SLOT_IOBJSTR, str_dup("")); set_rt_env_str(env, SLOT_ARGSTR, str_dup(argstr)); set_rt_env_str(env, SLOT_PREPSTR, str_dup("")); set_rt_env_str(env, SLOT_VERB, str_ref(RUN_ACTIV.verb)); set_rt_env_var(env, SLOT_ARGS, args); return do_task(program, MAIN_VECTOR, result, do_db_tracebacks); } enum outcome do_input_task(Objid user, Parsed_Command * pc, Objid this, db_verb_handle vh) { Program *prog = db_verb_program(vh); Var *env; current_task_kind = TASK_INPUT; check_activ_stack_size(current_max_stack_size()); top_activ_stack = 0; RUN_ACTIV.rt_env = env = new_rt_env(prog->num_var_names); RUN_ACTIV.this = this; RUN_ACTIV.player = user; RUN_ACTIV.progr = db_verb_owner(vh); RUN_ACTIV.vloc = db_verb_definer(vh); RUN_ACTIV.verb = str_ref(pc->verb); RUN_ACTIV.verbname = str_ref(db_verb_names(vh)); RUN_ACTIV.debug = (db_verb_flags(vh) & VF_DEBUG); fill_in_rt_consts(env, prog->version); set_rt_env_obj(env, SLOT_PLAYER, user); set_rt_env_obj(env, SLOT_CALLER, user); set_rt_env_obj(env, SLOT_THIS, this); set_rt_env_obj(env, SLOT_DOBJ, pc->dobj); set_rt_env_obj(env, SLOT_IOBJ, pc->iobj); set_rt_env_str(env, SLOT_DOBJSTR, str_ref(pc->dobjstr)); set_rt_env_str(env, SLOT_IOBJSTR, str_ref(pc->iobjstr)); set_rt_env_str(env, SLOT_ARGSTR, str_ref(pc->argstr)); set_rt_env_str(env, SLOT_PREPSTR, str_ref(pc->prepstr)); set_rt_env_str(env, SLOT_VERB, str_ref(pc->verb)); set_rt_env_var(env, SLOT_ARGS, var_ref(pc->args)); return do_task(prog, MAIN_VECTOR, 0, 1); } enum outcome do_forked_task(Program * prog, Var * rt_env, activation a, int f_id, Var * result) { current_task_kind = TASK_FORKED; check_activ_stack_size(current_max_stack_size()); top_activ_stack = 0; RUN_ACTIV = a; RUN_ACTIV.rt_env = rt_env; return do_task(prog, f_id, result, 1); } /* this is called from bf_eval to set up stack for an eval call */ int setup_activ_for_eval(Program * prog) { Var *env; if (!push_activation()) return 0; RUN_ACTIV.prog = prog; RUN_ACTIV.rt_env = env = new_rt_env(prog->num_var_names); fill_in_rt_consts(env, prog->version); set_rt_env_obj(env, SLOT_PLAYER, CALLER_ACTIV.player); set_rt_env_obj(env, SLOT_CALLER, CALLER_ACTIV.this); set_rt_env_obj(env, SLOT_THIS, NOTHING); set_rt_env_obj(env, SLOT_DOBJ, NOTHING); set_rt_env_obj(env, SLOT_IOBJ, NOTHING); set_rt_env_str(env, SLOT_DOBJSTR, str_dup("")); set_rt_env_str(env, SLOT_IOBJSTR, str_dup("")); set_rt_env_str(env, SLOT_ARGSTR, str_dup("")); set_rt_env_str(env, SLOT_PREPSTR, str_dup("")); set_rt_env_str(env, SLOT_VERB, str_dup("")); set_rt_env_var(env, SLOT_ARGS, new_list(0)); RUN_ACTIV.this = NOTHING; RUN_ACTIV.player = CALLER_ACTIV.player; RUN_ACTIV.progr = CALLER_ACTIV.progr; RUN_ACTIV.vloc = NOTHING; RUN_ACTIV.verb = str_dup(""); RUN_ACTIV.verbname = str_dup("Input to EVAL"); RUN_ACTIV.debug = 1; alloc_rt_stack(&RUN_ACTIV, RUN_ACTIV.prog->main_vector.max_stack); RUN_ACTIV.pc = 0; RUN_ACTIV.error_pc = 0; RUN_ACTIV.temp.type = TYPE_NONE; return 1; } void abort_running_task(void) { task_killed = 1; } /**** built in functions ****/ struct cf_state { unsigned fnum; void *data; }; static package bf_call_function(Var arglist, Byte next, void *vdata, Objid progr) { package p; unsigned fnum; struct cf_state *s; if (next == 1) { /* first call */ const char *fname = arglist.v.list[1].v.str; fnum = number_func_by_name(fname); if (fnum == FUNC_NOT_FOUND) { p = make_raise_pack(E_INVARG, "Unknown built-in function", var_ref(arglist.v.list[1])); free_var(arglist); } else { arglist = listdelete(arglist, 1); p = call_bi_func(fnum, arglist, next, progr, vdata); } } else { /* return to function */ s = vdata; fnum = s->fnum; p = call_bi_func(fnum, arglist, next, progr, s->data); free_data(s); } if (p.kind == BI_CALL) { s = alloc_data(sizeof(struct cf_state)); s->fnum = fnum; s->data = p.u.call.data; p.u.call.data = s; } return p; } static void bf_call_function_write(void *data) { struct cf_state *s = data; dbio_printf("bf_call_function data: fname = %s\n", name_func_by_num(s->fnum)); write_bi_func_data(s->data, s->fnum); } static void * bf_call_function_read(void) { struct cf_state *s = alloc_data(sizeof(struct cf_state)); const char *line = dbio_read_string(); const char *hdr = "bf_call_function data: fname = "; int hlen = strlen(hdr); if (!strncmp(line, hdr, hlen)) { line += hlen; if ((s->fnum = number_func_by_name(line)) == FUNC_NOT_FOUND) errlog("CALL_FUNCTION: Unknown built-in function: %s\n", line); else if (read_bi_func_data(s->fnum, &s->data, pc_for_bi_func_data())) return s; } return 0; } static package bf_raise(Var arglist, Byte next, void *vdata, Objid progr) { package p; int nargs = arglist.v.list[0].v.num; Var code = var_ref(arglist.v.list[1]); const char *msg = (nargs >= 2 ? str_ref(arglist.v.list[2].v.str) : str_dup(value2str(code))); Var value; value = (nargs >= 3 ? var_ref(arglist.v.list[3]) : zero); free_var(arglist); p.kind = BI_RAISE; p.u.raise.code = code; p.u.raise.msg = msg; p.u.raise.value = value; return p; } static package bf_suspend(Var arglist, Byte next, void *vdata, Objid progr) { static int seconds; int nargs = arglist.v.list[0].v.num; if (nargs >= 1) seconds = arglist.v.list[1].v.num; else seconds = -1; free_var(arglist); if (nargs >= 1 && seconds < 0) return make_error_pack(E_INVARG); else return make_suspend_pack(enqueue_suspended_task, &seconds); } static package bf_read(Var arglist, Byte next, void *vdata, Objid progr) { int argc = arglist.v.list[0].v.num; static Objid connection; int non_blocking = (argc >= 2 && is_true(arglist.v.list[2])); if (argc >= 1) connection = arglist.v.list[1].v.obj; else connection = activ_stack[0].player; free_var(arglist); /* Permissions checking */ if (argc >= 1) { if (!is_wizard(progr) && (!valid(connection) || progr != db_object_owner(connection))) return make_error_pack(E_PERM); } else { if (!is_wizard(progr) || last_input_task_id(connection) != current_task_id) return make_error_pack(E_PERM); } if (non_blocking) { Var r; r = read_input_now(connection); if (r.type == TYPE_ERR) return make_error_pack(r.v.err); else return make_var_pack(r); } return make_suspend_pack(make_reading_task, &connection); } static package bf_seconds_left(Var arglist, Byte next, void *vdata, Objid progr) { Var r; r.type = TYPE_INT; r.v.num = timer_wakeup_interval(task_alarm_id); free_var(arglist); return make_var_pack(r); } static package bf_ticks_left(Var arglist, Byte next, void *vdata, Objid progr) { Var r; r.type = TYPE_INT; r.v.num = ticks_remaining; free_var(arglist); return make_var_pack(r); } static package bf_pass(Var arglist, Byte next, void *vdata, Objid progr) { enum error e = call_verb(RUN_ACTIV.this, RUN_ACTIV.verb, arglist, 1); if (e == E_NONE) return tail_call_pack(); free_var(arglist); return make_error_pack(e); } static package bf_set_task_perms(Var arglist, Byte next, void *vdata, Objid progr) { /* (player) */ /* warning!! modifies top activation */ Objid oid = arglist.v.list[1].v.obj; free_var(arglist); if (progr != oid && !is_wizard(progr)) return make_error_pack(E_PERM); RUN_ACTIV.progr = oid; return no_var_pack(); } static package bf_caller_perms(Var arglist, Byte next, void *vdata, Objid progr) { /* () */ Var r; r.type = TYPE_OBJ; if (top_activ_stack == 0) r.v.obj = NOTHING; else r.v.obj = activ_stack[top_activ_stack - 1].progr; free_var(arglist); return make_var_pack(r); } static package bf_callers(Var arglist, Byte next, void *vdata, Objid progr) { int line_numbers_too = 0; if (arglist.v.list[0].v.num >= 1) line_numbers_too = is_true(arglist.v.list[1]); free_var(arglist); return make_var_pack(make_stack_list(activ_stack, 0, top_activ_stack, 0, root_activ_vector, line_numbers_too)); } static package bf_task_stack(Var arglist, Byte next, void *vdata, Objid progr) { int nargs = arglist.v.list[0].v.num; int id = arglist.v.list[1].v.num; int line_numbers_too = (nargs >= 2 && is_true(arglist.v.list[2])); vm the_vm = find_suspended_task(id); Objid owner = (the_vm ? progr_of_cur_verb(the_vm) : NOTHING); free_var(arglist); if (!the_vm) return make_error_pack(E_INVARG); if (!is_wizard(progr) && progr != owner) return make_error_pack(E_PERM); return make_var_pack(make_stack_list(the_vm->activ_stack, 0, the_vm->top_activ_stack, 1, the_vm->root_activ_vector, line_numbers_too)); } void register_execute(void) { register_function_with_read_write("call_function", 1, -1, bf_call_function, bf_call_function_read, bf_call_function_write, TYPE_STR); register_function("raise", 1, 3, bf_raise, TYPE_ANY, TYPE_STR, TYPE_ANY); register_function("suspend", 0, 1, bf_suspend, TYPE_INT); register_function("read", 0, 2, bf_read, TYPE_OBJ, TYPE_ANY); register_function("seconds_left", 0, 0, bf_seconds_left); register_function("ticks_left", 0, 0, bf_ticks_left); register_function("pass", 0, -1, bf_pass); register_function("set_task_perms", 1, 1, bf_set_task_perms, TYPE_OBJ); register_function("caller_perms", 0, 0, bf_caller_perms); register_function("callers", 0, 1, bf_callers, TYPE_ANY); register_function("task_stack", 1, 2, bf_task_stack, TYPE_INT, TYPE_ANY); } /**** storing to/loading from database ****/ void write_activ_as_pi(activation a) { Var dummy; dummy.type = TYPE_INT; dummy.v.num = -111; dbio_write_var(dummy); dbio_printf("%d %d %d %d %d %d %d %d %d\n", a.this, -7, -8, a.player, -9, a.progr, a.vloc, -10, a.debug); dbio_write_string("No"); dbio_write_string("More"); dbio_write_string("Parse"); dbio_write_string("Infos"); dbio_write_string(a.verb); dbio_write_string(a.verbname); } int read_activ_as_pi(activation * a) { int dummy; char c; free_var(dbio_read_var()); /* I use a `dummy' variable here and elsewhere instead of the `*' * assignment-suppression syntax of `scanf' because it allows more * straightforward error checking; unfortunately, the standard says that * suppressed assignments are not counted in determining the returned value * of `scanf'... */ if (dbio_scanf("%d %d %d %d %d %d %d %d %d%c", &a->this, &dummy, &dummy, &a->player, &dummy, &a->progr, &a->vloc, &dummy, &a->debug, &c) != 10 || c != '\n') { errlog("READ_A: Bad numbers.\n"); return 0; } dbio_read_string(); /* was argstr */ dbio_read_string(); /* was dobjstr */ dbio_read_string(); /* was iobjstr */ dbio_read_string(); /* was prepstr */ a->verb = dbio_read_string_intern(); a->verbname = dbio_read_string_intern(); return 1; } void write_rt_env(const char **var_names, Var * rt_env, unsigned size) { unsigned i; dbio_printf("%d variables\n", size); for (i = 0; i < size; i++) { dbio_write_string(var_names[i]); dbio_write_var(rt_env[i]); } } int read_rt_env(const char ***old_names, Var ** rt_env, int *old_size) { unsigned i; if (dbio_scanf("%d variables\n", old_size) != 1) { errlog("READ_RT_ENV: Bad count.\n"); return 0; } *old_names = (const char **) mymalloc((*old_size) * sizeof(char *), M_NAMES); *rt_env = new_rt_env(*old_size); for (i = 0; i < *old_size; i++) { (*old_names)[i] = dbio_read_string_intern(); (*rt_env)[i] = dbio_read_var(); } return 1; } Var * reorder_rt_env(Var * old_rt_env, const char **old_names, int old_size, Program * prog) { /* reorder old_rt_env, which is aligned according to old_names, to align to prog->var_names -- return the new rt_env after freeing old_rt_env and old_names */ unsigned size = prog->num_var_names; Var *rt_env = new_rt_env(size); unsigned i; for (i = 0; i < size; i++) { int slot; for (slot = 0; slot < old_size; slot++) { if (mystrcasecmp(old_names[slot], prog->var_names[i]) == 0) break; } if (slot < old_size) rt_env[i] = var_ref(old_rt_env[slot]); } free_rt_env(old_rt_env, old_size); for (i = 0; i < old_size; i++) free_str(old_names[i]); myfree((void *) old_names, M_NAMES); return rt_env; } void write_activ(activation a) { register Var *v; dbio_printf("language version %u\n", a.prog->version); dbio_write_program(a.prog); write_rt_env(a.prog->var_names, a.rt_env, a.prog->num_var_names); dbio_printf("%d rt_stack slots in use\n", a.top_rt_stack - a.base_rt_stack); for (v = a.base_rt_stack; v != a.top_rt_stack; v++) dbio_write_var(*v); write_activ_as_pi(a); dbio_write_var(a.temp); dbio_printf("%u %u %u\n", a.pc, a.bi_func_pc, a.error_pc); if (a.bi_func_pc != 0) { dbio_write_string(name_func_by_num(a.bi_func_id)); write_bi_func_data(a.bi_func_data, a.bi_func_id); } } static int check_pc_validity(Program * prog, int which_vector, unsigned pc) { Bytecodes *bc = (which_vector == -1 ? &prog->main_vector : &prog->fork_vectors[which_vector]); /* Current insn must be call to verb or built-in function like eval(), * move(), pass(), or suspend(). */ return (pc < bc->size && (bc->vector[pc - 1] == OP_CALL_VERB || bc->vector[pc - 2] == OP_BI_FUNC_CALL)); } int read_activ(activation * a, int which_vector) { DB_Version version; Var *old_rt_env; const char **old_names; int old_size, stack_in_use; unsigned i; const char *func_name; int max_stack; char c; if (dbio_input_version < DBV_Float) version = dbio_input_version; else if (dbio_scanf("language version %u\n", &version) != 1) { errlog("READ_ACTIV: Malformed language version\n"); return 0; } else if (!check_version(version)) { errlog("READ_ACTIV: Unrecognized language version: %d\n", version); return 0; } if (!(a->prog = dbio_read_program(version, 0, (void *) "suspended task"))) { errlog("READ_ACTIV: Malformed program\n"); return 0; } if (!read_rt_env(&old_names, &old_rt_env, &old_size)) { errlog("READ_ACTIV: Malformed runtime environment\n"); return 0; } a->rt_env = reorder_rt_env(old_rt_env, old_names, old_size, a->prog); max_stack = (which_vector == MAIN_VECTOR ? a->prog->main_vector.max_stack : a->prog->fork_vectors[which_vector].max_stack); alloc_rt_stack(a, max_stack); if (dbio_scanf("%d rt_stack slots in use\n", &stack_in_use) != 1) { errlog("READ_ACTIV: Bad stack_in_use number\n"); return 0; } a->top_rt_stack = a->base_rt_stack; for (i = 0; i < stack_in_use; i++) *(a->top_rt_stack++) = dbio_read_var(); if (!read_activ_as_pi(a)) { errlog("READ_ACTIV: Bad activ.\n", stack_in_use); return 0; } a->temp = dbio_read_var(); if (dbio_scanf("%u %u%c", &a->pc, &i, &c) != 3) { errlog("READ_ACTIV: bad pc, next. stack_in_use = %d\n", stack_in_use); return 0; } a->bi_func_pc = i; if (c == '\n') a->error_pc = a->pc; else if (dbio_scanf("%u\n", &a->error_pc) != 1) { errlog("READ_ACTIV: no error pc.\n"); return 0; } if (!check_pc_validity(a->prog, which_vector, a->pc)) { errlog("READ_ACTIV: Bad PC for suspended task.\n"); return 0; } if (a->bi_func_pc != 0) { func_name = dbio_read_string(); a->bi_func_id = number_func_by_name(func_name); if (a->bi_func_id == FUNC_NOT_FOUND) { errlog("READ_ACTIV: Unknown built-in function `%s'\n", func_name); return 0; } if (!read_bi_func_data(a->bi_func_id, &a->bi_func_data, &a->bi_func_pc)) { errlog("READ_ACTIV: Bad saved state for built-in function `%s'\n", func_name); return 0; } } return 1; } char rcsid_execute[] = "$Id: execute.c,v 1.10 1998/12/14 13:17:50 nop Exp $"; /* * $Log: execute.c,v $ * Revision 1.10 1998/12/14 13:17:50 nop * Merge UNSAFE_OPTS (ref fixups); fix Log tag placement to fit CVS whims * * Revision 1.9 1998/02/19 07:36:17 nop * Initial string interning during db load. * * Revision 1.8 1997/07/07 03:24:54 nop * Merge UNSAFE_OPTS (r5) after extensive testing. * * Revision 1.7 1997/03/21 13:23:23 bjj * Reorganize the top of run() slightly to make it slightly more efficient * (we do execute it billions of times, after all). Later we'll want to * get rid of if (task_killed) by introducing BI_KILL or by moving it into * the BI_FUNC_CALL case, at least. * * Revision 1.6.2.4 1998/12/06 07:13:21 bjj * Rationalize enqueue_forked_task interface and fix program_ref leak in * the case where fork fails with E_QUOTA. Make .queued_task_limit=0 really * enforce a limit of zero tasks (for old behavior set it to 1, that's the * effect it used to have). * * Revision 1.6.2.3 1997/09/09 07:01:17 bjj * Change bytecode generation so that x=f(x) calls f() without holding a ref * to the value of x in the variable slot. See the options.h comment for * BYTECODE_REDUCE_REF for more details. * * This checkin also makes x[y]=z (OP_INDEXSET) take advantage of that (that * new code is not conditional and still works either way). * * Revision 1.6.2.2 1997/05/24 07:08:37 bjj * Cleanup of Jay's last checkin to avoid some code duplication. * * Revision 1.6.2.1 1997/05/23 07:03:44 nop * Failure during property lookups/stores sometimes fails to free the string * containing the property name. (PUSH_ERROR() may return immediately.) * * Revision 1.6 1997/03/08 06:25:39 nop * 1.8.0p6 merge by hand. * * Revision 1.5 1997/03/05 08:41:47 bjj * A few malloc-friendly changes: rt_stacks are now centrally allocated/freed * so that we can keep a pool of them handy. rt_envs are similarly pooled. * Both revert to malloc/free for large requests. * * Revision 1.4 1997/03/03 09:03:31 bjj * 3 opcode optimizations: * * 1) OP_IMM+OP_POP is "peephole optimized" away at runtime. This makes * verbdocs and other comments cheaper. * * 2) OP_PUT_n+OP_POP is similarly optimized (PUT doesn't consume the * top value on the stack but it is often used that way in statements like * `var = expr;'). OP_G_PUT could use the same change but is rarely * executed. * * 3) OP_PUT_n, OP_PUSH_n which used to be in an if/else in the default * case are split out into 32 cases each so the compiler can optimize it * for us. These ops account for a large percentage of those executed. * * Revision 1.3 1997/03/03 06:14:44 nop * Nobody actually uses protected properties. Make IGNORE_PROP_PROTECTED * the default. * * Revision 1.2 1997/03/03 04:18:38 nop * GNU Indent normalization * * Revision 1.1.1.1 1997/03/03 03:44:59 nop * LambdaMOO 1.8.0p5 * * Revision 2.11 1997/03/04 04:31:48 eostrom * Modified run() and run_interpreter() to take a separate argument * indicating whether to raise an exception, rather than assuming E_NONE * means no and anything else means yes. * * Revision 2.10 1996/04/19 01:24:40 pavel * Added support for built-in functions making tail calls to MOO verbs and * changed pass() to use the new feature. Added patches to allow generation * of the new warning in read_bi_func_data(). Release 1.8.0p4. * * Revision 2.9 1996/03/19 07:12:21 pavel * Fixed call_verb() to return E_INVIND when appropriate even when pass==0. * Removed is_user() test for setting .programmer and .wizard properties. * Reordered error tests in bf_task_stack() to check for an invalid argument * first. Release 1.8.0p2. * * Revision 2.8 1996/03/10 01:18:22 pavel * Added new `caller()' entry point, for use by built-in fns. Release 1.8.0. * * Revision 2.7 1996/02/11 00:44:01 pavel * Fixed handling of `kill_task(task_id())'. Release 1.8.0beta2. * * Revision 2.6 1996/02/08 07:11:17 pavel * Added support for in-DB traceback handling, named WHILE loop, BREAK and * CONTINUE statement, exponentiation expression, and floating-point * arithmetic. Fixed logging of wiz-bit setting to happen on any change in * value in either direction. Added tick-counting for EOPs. Fixed stack bug * in `$' expression. Fixed horrible bugs in scattering assignment. Added * version numbers on each frame of suspended tasks. Renamed err/logf() to * errlog/oklog() and TYPE_NUM to TYPE_INT. Updated copyright notice for * 1996. Release 1.8.0beta1. * * Revision 2.5 1996/01/16 07:19:34 pavel * Fixed bug concerning which frame built-in function return data is stored * into. Fixed tracebacks for wizarding to be accurate and only generated for * setting .wizard to true. Added support for EOP_SCATTER. * Release 1.8.0alpha6. * * Revision 2.4 1996/01/11 07:28:04 pavel * Fixed bug in value of `progr' when MOO code returns to a built-in. Added * support for getting the value of a resumed task. Release 1.8.0alpha5. * * Revision 2.3 1995/12/31 03:12:19 pavel * Implemented the EOP_LENGTH opcode. Release 1.8.0alpha4. * * Revision 2.2 1995/12/28 00:23:15 pavel * Fixed bug in unwinding through handlers that don't cover the current error. * Fixed bug in not squelching all errors raised by built-ins called from !d * verbs. Commented conventions for errors vs. built-ins. Fixed memory leaks * in built-in property reference, errors raised in !d verbs, and verbnames * read from suspended tasks in DB files. Added call_function() built-in. * Fixed bug in registration of suspend(). Added support for better error * messages during DB loading of suspended task programs. * Release 1.8.0alpha3. * * Revision 2.1 1995/12/11 07:51:30 pavel * Added built-in function entries to stack traceback lists. Accounted for * verb programs never being NULL any more. Added support for built-in * properties being wiz-only. Removed one more silly use of `unsigned'. * Added support for suspending forever. Fixed registration of `read()' to * once again allow non-blocking mode. Added `task_stack()' built-in * function. * * Release 1.8.0alpha2. * * Revision 2.0 1995/11/30 04:23:34 pavel * New baseline version, corresponding to release 1.8.0alpha1. * * Revision 1.27 1992/10/27 06:19:22 pavel * Fixed a memory leak that occurred when a read() call was rejected for * E_INVARG reasons. * * Revision 1.26 1992/10/23 23:03:47 pavel * Added copyright notice. * * Revision 1.25 1992/10/23 19:25:00 pavel * Eliminated all uses of the useless macro NULL.. * * Revision 1.24 1992/10/21 03:02:35 pavel * Converted to use new automatic configuration system. * * Revision 1.23 1992/10/17 20:28:48 pavel * Global rename of strdup->str_dup, strref->str_ref, vardup->var_dup, and * varref->var_ref. * Added some (int) casts to placate over-protective compilers. * Changed return-type of read_activ() from char to int, for systems that use * unsigned chars. * * Revision 1.22 1992/09/25 21:11:36 pjames * Made use of the error_pc field in an activation to report correct * error tracebacks from verbs called by builtin verbs. * * Revision 1.21 1992/09/24 16:43:10 pavel * Exported `task_timed_out' for use by long-running built-in functions that * should abort if the timer goes off. * * Revision 1.20 1992/09/14 18:40:02 pjames * Updated #includes. Moved rcsid to bottom. * * Revision 1.19 1992/09/14 17:45:17 pjames * Moved db_modification code to db modules. * * Revision 1.18 1992/09/08 22:07:06 pjames * Changed `register_bf_execute()' to `register_execute()'. * Changed range checking for subrange assigments. * * Revision 1.17 1992/09/04 22:42:05 pavel * Fixed some picky ANSI C problems with (const char *)'s. * * Revision 1.16 1992/09/03 16:29:44 pjames * Minor change. * * Revision 1.15 1992/09/02 18:41:17 pavel * Added mechanism for resuming tasks with errors. * * Revision 1.14 1992/09/01 06:24:01 pavel * Fixed minor storage leak just introduced. * * Revision 1.13 1992/09/01 05:30:57 pavel * Added a call to vardup() for first argument to listset(). * * Revision 1.12 1992/08/31 22:29:01 pjames * Changed some `char *'s to `const char *' and fixed code accordingly. * * Revision 1.11 1992/08/28 23:23:03 pjames * Added support for EXTENDED instructions. * Added OP_RANGESET interpreting. * Changed OP_LISTSET to OP_INDEXSET, and added code for indexed string * assigments. * * Revision 1.10 1992/08/28 16:30:45 pjames * Changed myfree(*, M_STRING) to free_str(*). * Changed vardup to varref. * Changed some strref's to strdup. * * Revision 1.9 1992/08/12 02:01:46 pjames * Gave a variable an initial value to get rid on a bogus compiler * warning. * * Revision 1.8 1992/08/12 01:48:03 pjames * Fixed string freeing/copying to/from activation/forked_tasks. * * Revision 1.7 1992/08/11 17:30:44 pjames * Removed last read/write of Parse_Info, fixed a bug in a macro * (commented back in a line), and fixed call_verb (to not run the * program if it doesn't exist). * * Revision 1.6 1992/08/10 17:47:00 pjames * Moved several functions to eval_env.c and eval_vm.c. Moved built in * functions which modified vm to this module. Move property functions * to property.c. Updated #includes. Modified run() to be cleaner. * Used new built in function registration methods. Removed uses of * Parse_Info field in activations. * * Revision 1.5 1992/08/01 01:14:52 pavel * Minor fixes... * * Revision 1.4 1992/07/30 21:18:48 pjames * Updated print_error_backtrace to print line numbers for each frame on * the stack. * * Revision 1.3 1992/07/27 18:14:17 pjames * Changed name of ct_env to var_names, const_env to literals, and * f_vectors to fork_vectors. * * Revision 1.2 1992/07/21 00:02:07 pavel * Added rcsid_ declaration to hold the RCS ident. string. * * Revision 1.1 1992/07/20 23:23:12 pavel * Initial RCS-controlled version. */