/******************************************************************** ** Copyright (c) 2018-2020 Guan Wenliang ** This file is part of the Berry default interpreter. ** skiars@qq.com, https://github.com/Skiars/berry ** See Copyright Notice in the LICENSE file or at ** https://github.com/Skiars/berry/blob/master/LICENSE ********************************************************************/ #include "be_exec.h" #include "be_parser.h" #include "be_vm.h" #include "be_vector.h" #include "be_mem.h" #include "be_sys.h" #include "be_debug.h" #include "be_bytecode.h" #include "be_decoder.h" #include #include #if !BE_USE_SCRIPT_COMPILER && !BE_USE_BYTECODE_LOADER #error no compiler or bytecode loader enabled. #endif #define FILE_BUFFER_SIZE 256 #define __STR(s) #s #define STR(s) __STR(s) #define STACK_OVER_MSG(n) \ "stack overflow (maximum stack size is " STR(n) ")" #ifdef BE_EXPLICIT_ABORT #define _os_abort BE_EXPLICIT_ABORT #else #define _os_abort abort #endif #ifdef BE_EXPLICIT_EXIT #define _os_exit BE_EXPLICIT_EXIT #else #define _os_exit exit #endif #define exec_try(j) if (be_setjmp((j)->b) == 0) #define exec_throw(j) be_longjmp((j)->b, 1) #define fixup_ptr(ptr, offset) ((ptr) = (void*)((bbyte*)(ptr) + (offset))) #define ptr_offset(ptr1, ptr2) ((bbyte*)(ptr1) - (bbyte*)(ptr2)) struct pparser { const char *fname; breader reader; void *data; bbyte islocal; }; struct pcall { bvalue *v; int argc; }; struct vmstate { int top, reg, depth; int refcount; }; struct strbuf { size_t len; const char *s; }; struct filebuf { void *fp; char buf[FILE_BUFFER_SIZE]; }; void be_throw(bvm *vm, int errorcode) { #if BE_USE_PERF_COUNTERS vm->counter_exc++; #endif /* if BE_MALLOC_FAIL then call */ if (errorcode == BE_MALLOC_FAIL) { if (vm->obshook != NULL) (*vm->obshook)(vm, BE_OBS_MALLOC_FAIL, vm->gc.usage); } if (vm->errjmp) { vm->errjmp->status = errorcode; exec_throw(vm->errjmp); } else { _os_abort(); } } /* Fatal error Exit */ /* Raise a BE_EXIT exception if within a try/catch block, or exit VM */ BERRY_API void be_exit(bvm *vm, int status) { if (vm->errjmp) { be_pushint(vm, status); be_pop(vm, 1); be_throw(vm, BE_EXIT); } else { _os_exit(status); } } void be_throw_message(bvm *vm, int errorcode, const char *msg) { be_pushstring(vm, msg); be_throw(vm, errorcode); } /* Exec protected: exec function and capture any exception and contain it within call */ /* Exceptions or fatal errors are not propagated */ int be_execprotected(bvm *vm, bpfunc f, void *data) { struct blongjmp jmp; jmp.status = 0; jmp.prev = vm->errjmp; /* save long jump position */ vm->errjmp = &jmp; exec_try(vm->errjmp) { f(vm, data); } vm->errjmp = jmp.prev; /* restore long jump position */ return jmp.status; } static void vm_state_save(bvm *vm, struct vmstate *state) { state->depth = be_stack_count(&vm->callstack); state->top = cast_int(vm->top - vm->stack); state->reg = cast_int(vm->reg - vm->stack); state->refcount = vm->refstack.count; } static void copy_exception(bvm *vm, int res, int dstindex) { bvalue *dst = vm->stack + dstindex; if (res == BE_EXCEPTION || res == BE_EXIT) { bvalue *src = vm->top; *dst++ = *src++; if (res == BE_EXCEPTION) { *dst++ = *src++; } } vm->top = dst; } static void vm_state_restore(bvm *vm, const struct vmstate *state, int res) { vm->reg = vm->stack + state->reg; be_vector_resize(vm, &vm->refstack, state->refcount); /* copy exception information to top */ copy_exception(vm, res, state->top); be_assert(be_stack_count(&vm->callstack) >= state->depth); if (be_stack_count(&vm->callstack) > state->depth) { be_vector_resize(vm, &vm->callstack, state->depth); vm->cf = be_stack_top(&vm->callstack); } } #if BE_USE_SCRIPT_COMPILER static void m_parser(bvm *vm, void *data) { struct pparser *p = cast(struct pparser*, data); bclosure *cl = be_parser_source(vm, p->fname, p->reader, p->data, p->islocal); var_setclosure(vm->top, cl); be_incrtop(vm); } int be_protectedparser(bvm *vm, const char *fname, breader reader, void *data, bbool islocal) { int res; struct pparser s; struct vmstate state; s.fname = fname; s.reader = reader; s.data = data; s.islocal = (bbyte)(islocal != 0); vm_state_save(vm, &state); res = be_execprotected(vm, m_parser, &s); if (res) { /* restore call stack */ vm_state_restore(vm, &state, res); } return res; } static const char* _sgets(struct blexer* lexer, void *data, size_t *size) { (void)lexer; struct strbuf *sb = data; *size = sb->len; if (sb->len) { sb->len = 0; return sb->s; } return NULL; } static const char* _fgets(struct blexer* lexer, void *data, size_t *size) { (void)lexer; struct filebuf *fb = data; *size = be_fread(fb->fp, fb->buf, sizeof(fb->buf)); if (*size) { return fb->buf; } return NULL; } BERRY_API int be_loadbuffer(bvm *vm, const char *name, const char *buffer, size_t length) { struct strbuf sbuf; sbuf.s = buffer; sbuf.len = length; return be_protectedparser(vm, name, _sgets, &sbuf, bfalse); } static int fileparser(bvm *vm, const char *name, bbool islocal) { int res = BE_IO_ERROR; struct filebuf *fbuf = be_malloc(vm, sizeof(struct filebuf)); fbuf->fp = be_fopen(name, "r"); if (fbuf->fp) { res = be_protectedparser(vm, name, _fgets, fbuf, islocal); be_fclose(fbuf->fp); } be_free(vm, fbuf, sizeof(struct filebuf)); return res; } #endif /* BE_USE_SCRIPT_COMPILER */ #if BE_USE_BYTECODE_LOADER static void bytecode_loader(bvm *vm, void *data) { bclosure *cl = be_bytecode_load(vm, (const char *)data); if (cl != NULL) { var_setclosure(vm->top, cl); } else { var_setnil(vm->top); } be_incrtop(vm); } /* load bytecode file */ static int load_bytecode(bvm *vm, const char *name) { int res = BE_SYNTAX_ERROR; if (be_bytecode_check(name)) { struct vmstate state; vm_state_save(vm, &state); res = be_execprotected(vm, bytecode_loader, (void*)name); if (res) { /* restore call stack */ vm_state_restore(vm, &state, res); } } return res; } #else #define load_bytecode(vm, name) BE_SYNTAX_ERROR #endif /* BE_USE_BYTECODE_LOADER */ BERRY_API int be_loadmode(bvm *vm, const char *name, bbool islocal) { int res = load_bytecode(vm, name); #if BE_USE_SCRIPT_COMPILER if (res && res != BE_IO_ERROR && res != BE_EXCEPTION) { res = fileparser(vm, name, islocal); } #else (void)islocal; #endif if (res == BE_IO_ERROR) { be_pushfstring(vm, "cannot open file '%s'.", name); } return res; } #if BE_USE_BYTECODE_SAVER static void _bytecode_save(bvm *vm, void *data) { if (be_top(vm) > 0 && var_isclosure(vm->top - 1)) { bclosure *cl = var_toobj(vm->top - 1); be_bytecode_save(vm, (const char *)data, cl->proto); } } /* save bytecode file */ BERRY_API int be_savecode(bvm *vm, const char *name) { int res; struct vmstate state; vm_state_save(vm, &state); res = be_execprotected(vm, _bytecode_save, (void *)name); if (res) { /* restore call stack */ vm_state_restore(vm, &state, res); } return res; } #endif static void m_pcall(bvm *vm, void *data) { struct pcall *p = cast(struct pcall*, data); be_dofunc(vm, p->v, p->argc); } /* Protected call: contain any exception of fatal error and restore context if something went wrong */ int be_protectedcall(bvm *vm, bvalue *v, int argc) { int res; struct pcall s; struct vmstate state; s.v = v; s.argc = argc; vm_state_save(vm, &state); res = be_execprotected(vm, m_pcall, &s); if (res) { /* restore call stack */ vm_state_restore(vm, &state, res); } return res; } #if BE_DEBUG && defined(be_assert) /* increase top register and return new top */ /* Does not expand the stack if there is not enough room, but may corrupt memory */ bvalue* be_incrtop(bvm *vm) { bvalue *top = vm->top++; be_assert(top < vm->stacktop); return top; } #endif /* TODO what is the difference with be_stack_push? */ void be_stackpush(bvm *vm) { /* make sure there is enough stack space */ be_stack_require(vm, 1 + BE_STACK_FREE_MIN); be_incrtop(vm); } /* check that the stack is able to store `count` items, and increase stack if needed */ BERRY_API void be_stack_require(bvm *vm, int count) { #if BE_USE_DEBUG_STACK == 0 if (vm->top + count >= vm->stacktop) { be_stack_expansion(vm, count); } #else be_stack_expansion(vm, vm->top - vm->stacktop + count); /* force exact resize each time */ #endif } /* Scan the entire callstack and adjust all pointer by `offset` */ static void update_callstack(bvm *vm, intptr_t offset) { bcallframe *cf = be_stack_top(&vm->callstack); bcallframe *base = be_stack_base(&vm->callstack); for (; cf >= base; --cf) { fixup_ptr(cf->func, offset); fixup_ptr(cf->top, offset); fixup_ptr(cf->reg, offset); } fixup_ptr(vm->top, offset); fixup_ptr(vm->reg, offset); } static void update_upvalues(bvm *vm, intptr_t offset) { bupval *node = vm->upvalist; /* update the value referenced by open upvalues */ for (; node != NULL; node = node->u.next) { fixup_ptr(node->value, offset); } } /* Resize the stack to new `size` as number of elements */ /* Then update all pointers in callstack and upvalues with the new stack address */ static void stack_resize(bvm *vm, size_t size) { intptr_t offset; bvalue *old = vm->stack; /* save original pointer of stack before resize */ size_t os = (vm->stacktop - old) * sizeof(bvalue); /* size of current stack allocated in bytes */ #if BE_USE_DEBUG_STACK == 0 vm->stack = be_realloc(vm, old, os, sizeof(bvalue) * size); /* reallocate with the new size */ #else /* force a reallocation */ size_t ns = sizeof(bvalue) * size; vm->stack = be_malloc(vm, ns); size_t transf = (os < ns) ? os : ns; /* min size */ memmove(vm->stack, old, transf); /* copy to new location */ memset(old, 0xFF, os); /* fill the structure with invalid pointers */ be_free(vm, old, os); #endif vm->stacktop = vm->stack + size; /* compute new stacktop */ offset = ptr_offset(vm->stack, old); /* compute the address difference between old and ne stack addresses */ /* update callframes */ update_callstack(vm, offset); /* update open upvalues */ update_upvalues(vm, offset); } /* Stack resize internal API */ /* Increases the stack by `n` elements, reallocate stack if needed and update all callstacks and upvals */ /* Check if we are above the max allowed stack */ void be_stack_expansion(bvm *vm, int n) { int size = vm->stacktop - vm->stack; /* with debug enabled, stack increase may be negative */ /* check new stack size */ if (size + n > BE_STACK_TOTAL_MAX) { /* ensure the stack is enough when generating error messages. */ stack_resize(vm, size + 1); be_raise(vm, "runtime_error", STACK_OVER_MSG(BE_STACK_TOTAL_MAX)); } if (vm->obshook != NULL) (*vm->obshook)(vm, BE_OBS_STACK_RESIZE_START, size * sizeof(bvalue), (size + n) * sizeof(bvalue)); stack_resize(vm, size + n); } static void fixup_exceptstack(bvm* vm, struct bexecptframe* lbase) { struct bexecptframe *base = be_stack_base(&vm->exceptstack); if (lbase != base) { /* the address has changed when the stack is expanded */ struct bexecptframe *top = be_stack_top(&vm->exceptstack); bbyte *begin = (bbyte*)&lbase->errjmp; bbyte *end = (bbyte*)&(lbase + (top - base))->errjmp; intptr_t offset = ptr_offset(base, lbase); struct blongjmp *errjmp = vm->errjmp; while (errjmp) { bbyte *prev = (bbyte*)errjmp->prev; if (prev >= begin && prev < end) { fixup_ptr(prev, offset); /* fixup the prev pointer */ errjmp->prev = (struct blongjmp*)prev; } errjmp = (struct blongjmp*)prev; } } } /* set an exception handling recovery point. To do this, we have to * push some VM states into the exception stack. */ void be_except_block_setup(bvm *vm) { struct bexecptframe *frame; struct bexecptframe *lbase = be_stack_base(&vm->exceptstack); be_stack_push(vm, &vm->exceptstack, NULL); frame = be_stack_top(&vm->exceptstack); frame->depth = be_stack_count(&vm->callstack); /* the call stack depth */ frame->ip = vm->ip; /* OP_EXBLK's next instruction pointer */ /* set longjmp() jump point */ frame->errjmp.status = 0; frame->errjmp.prev = vm->errjmp; /* save long jump list */ frame->refcount = vm->refstack.count; /* save reference pointer */ vm->errjmp = &frame->errjmp; fixup_exceptstack(vm, lbase); } /* resumes to the state of the previous frame when an exception occurs. */ void be_except_block_resume(bvm *vm) { int errorcode = vm->errjmp->status; struct bexecptframe *frame = be_stack_top(&vm->exceptstack); if (errorcode == BE_EXCEPTION) { vm->errjmp = vm->errjmp->prev; be_vector_resize(vm, &vm->refstack, frame->refcount); /* jump to except instruction */ vm->ip = frame->ip + IGET_sBx(frame->ip[-1]); if (be_stack_count(&vm->callstack) > frame->depth) { bvalue *top = vm->top; bcallframe *cf = be_vector_at(&vm->callstack, frame->depth); vm->top = cf->top; vm->reg = cf->reg; vm->cf = frame->depth ? cf - 1 : NULL; be_vector_resize(vm, &vm->callstack, frame->depth); /* copy the exception value and argument to the top of * the current function */ vm->top[0] = top[0]; /* exception value */ vm->top[1] = top[1]; /* exception argument */ } be_stack_pop(&vm->exceptstack); } else { /* other errors cannot be catch by the except block */ /* find the next error handling location */ while (vm->errjmp == &frame->errjmp) { vm->errjmp = vm->errjmp->prev; be_stack_pop(&vm->exceptstack); frame = be_stack_top(&vm->exceptstack); } be_throw(vm, errorcode); /* rethrow this exception */ } } /* only close the except block, no other operations */ void be_except_block_close(bvm *vm, int count) { struct bexecptframe *frame; int size = be_stack_count(&vm->exceptstack); be_assert(count > 0 && count <= size); frame = be_vector_at(&vm->exceptstack, size - count); vm->errjmp = frame->errjmp.prev; be_vector_resize(vm, &vm->exceptstack, size - count); } void be_save_stacktrace(bvm *vm) { bstack *stack = &vm->tracestack; be_stack_clear(stack); if (be_stack_count(&vm->callstack)) { bcallframe *cf; bcallframe *base = be_stack_base(&vm->callstack); bcallframe *top = be_stack_top(&vm->callstack); for (cf = base; cf <= top; ++cf) { bcallsnapshot *st; be_stack_push(vm, stack, NULL); st = be_stack_top(stack); st->func = *cf->func; st->ip = cf == top ? vm->ip : cf[1].ip; } } }