/* * Copyright (C) 2020-2021 Jo-Philipp Wich * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include #include #include #include #include #include "ucode/vm.h" #include "ucode/compiler.h" #include "ucode/program.h" #include "ucode/lib.h" /* uc_error_context_format() */ #undef __insn #define __insn(_name) #_name, static const char *insn_names[__I_MAX] = { __insns }; static const int8_t insn_operand_bytes[__I_MAX] = { [I_LOAD] = 4, [I_LOAD8] = 1, [I_LOAD16] = 2, [I_LOAD32] = 4, [I_LREXP] = 4, [I_LLOC] = 4, [I_LVAR] = 4, [I_LUPV] = 4, [I_CLFN] = 4, [I_ARFN] = 4, [I_SLOC] = 4, [I_SUPV] = 4, [I_SVAR] = 4, [I_ULOC] = 4, [I_UUPV] = 4, [I_UVAR] = 4, [I_UVAL] = 1, [I_NARR] = 4, [I_PARR] = 4, [I_NOBJ] = 4, [I_SOBJ] = 4, [I_JMP] = -4, [I_JMPZ] = -4, [I_COPY] = 1, [I_CALL] = 4, [I_MCALL] = 4, [I_QCALL] = 4, [I_QMCALL] = 4, [I_IMPORT] = 4, [I_EXPORT] = 4, [I_DYNLOAD] = 4 }; static const char *exception_type_strings[] = { [EXCEPTION_SYNTAX] = "Syntax error", [EXCEPTION_RUNTIME] = "Runtime error", [EXCEPTION_TYPE] = "Type error", [EXCEPTION_REFERENCE] = "Reference error", [EXCEPTION_USER] = "Error", [EXCEPTION_EXIT] = "Exit" }; static void uc_vm_reset_stack(uc_vm_t *vm) { while (vm->stack.count > 0) { vm->stack.count--; ucv_put(vm->stack.entries[vm->stack.count]); vm->stack.entries[vm->stack.count] = NULL; } } static uc_value_t * uc_vm_callframe_pop(uc_vm_t *vm); static void uc_vm_reset_callframes(uc_vm_t *vm) { while (vm->callframes.count > 0) ucv_put(uc_vm_callframe_pop(vm)); } static uc_value_t * uc_vm_alloc_global_scope(uc_vm_t *vm) { uc_value_t *scope, *arr; size_t i; scope = ucv_object_new(vm); /* build default require() search path */ arr = ucv_array_new(vm); for (i = 0; i < vm->config->module_search_path.count; i++) ucv_array_push(arr, ucv_string_new(vm->config->module_search_path.entries[i])); /* register module related constants */ ucv_object_add(scope, "REQUIRE_SEARCH_PATH", arr); ucv_object_add(scope, "modules", ucv_object_new(vm)); /* register scope math constants */ ucv_object_add(scope, "NaN", ucv_double_new(NAN)); ucv_object_add(scope, "Infinity", ucv_double_new(INFINITY)); /* register global property */ ucv_object_add(scope, "global", ucv_get(scope)); uc_vm_scope_set(vm, scope); return scope; } static void uc_vm_output_exception(uc_vm_t *vm, uc_exception_t *ex); void uc_vm_init(uc_vm_t *vm, uc_parse_config_t *config) { vm->exception.type = EXCEPTION_NONE; vm->exception.message = NULL; vm->config = config ? config : &uc_default_parse_config; vm->open_upvals = NULL; vm->values.prev = &vm->values; vm->values.next = &vm->values; vm->strbuf = NULL; vm->output = stdout; uc_vm_reset_stack(vm); uc_vm_alloc_global_scope(vm); uc_vm_exception_handler_set(vm, uc_vm_output_exception); uc_vm_trace_set(vm, 0); } void uc_vm_free(uc_vm_t *vm) { uc_upvalref_t *ref; size_t i; ucv_put(vm->exception.stacktrace); free(vm->exception.message); while (vm->open_upvals) { ref = vm->open_upvals->next; ucv_put(&vm->open_upvals->header); vm->open_upvals = ref; } for (i = 0; i < vm->restypes.count; i++) ucv_put(vm->restypes.entries[i]->proto); for (i = 0; i < vm->exports.count; i++) ucv_put(&vm->exports.entries[i]->header); uc_vm_reset_callframes(vm); uc_vm_reset_stack(vm); uc_vector_clear(&vm->stack); uc_vector_clear(&vm->callframes); printbuf_free(vm->strbuf); ucv_freeall(vm); for (i = 0; i < vm->restypes.count; i++) free(vm->restypes.entries[i]); uc_vector_clear(&vm->restypes); uc_vector_clear(&vm->exports); } static uc_chunk_t * uc_vm_frame_chunk(uc_callframe_t *frame) { return frame->closure ? &frame->closure->function->chunk : NULL; } static uc_program_t * uc_vm_frame_program(uc_callframe_t *frame) { return frame->closure ? frame->closure->function->program : NULL; } static uc_source_t * uc_vm_frame_source(uc_callframe_t *frame) { return frame->closure ? uc_program_function_source(frame->closure->function) : NULL; } static uc_callframe_t * uc_vm_current_frame(uc_vm_t *vm) { return uc_vector_last(&vm->callframes); } static uc_program_t * uc_vm_current_program(uc_vm_t *vm) { return uc_vm_frame_program(uc_vm_current_frame(vm)); } static bool uc_vm_is_strict(uc_vm_t *vm) { return uc_vm_current_frame(vm)->strict; } static uc_vm_insn_t uc_vm_decode_insn(uc_vm_t *vm, uc_callframe_t *frame, uc_chunk_t *chunk) { uc_vm_insn_t insn; #ifndef NDEBUG uint8_t *end = chunk->entries + chunk->count; #endif assert(frame->ip < end); insn = frame->ip[0]; frame->ip++; assert(frame->ip + abs(insn_operand_bytes[insn]) <= end); switch (insn_operand_bytes[insn]) { case 0: break; case -4: vm->arg.s32 = ( frame->ip[0] * 0x1000000UL + frame->ip[1] * 0x10000UL + frame->ip[2] * 0x100UL + frame->ip[3] ) - 0x7fffffff; frame->ip += 4; break; case 1: vm->arg.u8 = frame->ip[0]; frame->ip++; break; case 2: vm->arg.u16 = ( frame->ip[0] * 0x100 + frame->ip[1] ); frame->ip += 2; break; case 4: vm->arg.u32 = ( frame->ip[0] * 0x1000000UL + frame->ip[1] * 0x10000UL + frame->ip[2] * 0x100UL + frame->ip[3] ); frame->ip += 4; break; default: fprintf(stderr, "Unhandled operand format: %" PRId8 "\n", insn_operand_bytes[insn]); abort(); } return insn; } static char * uc_vm_format_val(uc_vm_t *vm, uc_value_t *val) { if (!vm->strbuf) vm->strbuf = xprintbuf_new(); else printbuf_reset(vm->strbuf); ucv_to_stringbuf(NULL, vm->strbuf, val, true); if (printbuf_length(vm->strbuf) >= 64) { printbuf_memset(vm->strbuf, 60, '.', 3); printbuf_memset(vm->strbuf, 63, 0, 1); } return vm->strbuf->buf; } static void uc_vm_frame_dump(uc_vm_t *vm, uc_callframe_t *frame) { uc_chunk_t *chunk = uc_vm_frame_chunk(frame); uc_function_t *function; uc_closure_t *closure; uc_upvalref_t *ref; uc_value_t *v; size_t i; fprintf(stderr, " [*] CALLFRAME[%zx]\n", frame - vm->callframes.entries); fprintf(stderr, " |- stackframe %zu/%zu\n", frame->stackframe, vm->stack.count); fprintf(stderr, " |- ctx %s\n", uc_vm_format_val(vm, frame->ctx)); if (chunk) { closure = frame->closure; function = closure->function; fprintf(stderr, " `- %zu upvalues\n", function->nupvals); for (i = 0; i < function->nupvals; i++) { ref = closure->upvals[i]; v = uc_chunk_debug_get_variable(chunk, 0, i, true); fprintf(stderr, " [%zu] <%p> %s ", i, (void *)ref, uc_vm_format_val(vm, v)); if (!ref) { fprintf(stderr, "{unresolved}\n"); } else if (ref->closed) { fprintf(stderr, "{closed} %s\n", uc_vm_format_val(vm, ref->value)); } else { fprintf(stderr, "{open[%zu]} %s\n", ref->slot, uc_vm_format_val(vm, vm->stack.entries[ref->slot])); } ucv_put(v); } } } static uc_value_t * uc_vm_resolve_upval(uc_vm_t *vm, uc_value_t *value) { uc_upvalref_t *ref; #ifdef __clang_analyzer__ /* Clang static analyzer does not understand that ucv_type(NULL) can't * possibly yield UC_UPVALUE. Nudge it. */ if (value != NULL && ucv_type(value) == UC_UPVALUE) #else if (ucv_type(value) == UC_UPVALUE) #endif { ref = (uc_upvalref_t *)value; if (ref->closed) return ucv_get(ref->value); else return ucv_get(vm->stack.entries[ref->slot]); } return value; } void uc_vm_stack_push(uc_vm_t *vm, uc_value_t *value) { uc_vector_grow(&vm->stack); ucv_put(vm->stack.entries[vm->stack.count]); vm->stack.entries[vm->stack.count] = uc_vm_resolve_upval(vm, value); vm->stack.count++; if (vm->trace) { fprintf(stderr, " [+%zd] %s\n", vm->stack.count - 1, uc_vm_format_val(vm, vm->stack.entries[vm->stack.count - 1])); } } uc_value_t * uc_vm_stack_pop(uc_vm_t *vm) { uc_value_t *rv; vm->stack.count--; rv = vm->stack.entries[vm->stack.count]; vm->stack.entries[vm->stack.count] = NULL; if (vm->trace) { fprintf(stderr, " [-%zd] %s\n", vm->stack.count, uc_vm_format_val(vm, rv)); } return rv; } uc_value_t * uc_vm_stack_peek(uc_vm_t *vm, size_t offset) { return vm->stack.entries[vm->stack.count + (-1 - offset)]; } static void uc_vm_stack_set(uc_vm_t *vm, size_t offset, uc_value_t *value) { if (vm->trace) { fprintf(stderr, " [!%zu] %s\n", offset, uc_vm_format_val(vm, value)); } ucv_put(vm->stack.entries[offset]); vm->stack.entries[offset] = value; } static void uc_vm_call_native(uc_vm_t *vm, uc_value_t *ctx, uc_cfunction_t *fptr, bool mcall, size_t nargs) { uc_value_t *res = NULL; uc_callframe_t *frame; /* add new callframe */ uc_vector_grow(&vm->callframes); frame = &vm->callframes.entries[vm->callframes.count++]; frame->stackframe = vm->stack.count - nargs - 1; frame->cfunction = fptr; frame->closure = NULL; frame->ctx = ctx; frame->mcall = mcall; if (vm->trace) uc_vm_frame_dump(vm, frame); res = fptr->cfn(vm, nargs); /* Reset stack, check for callframe depth since an uncatched exception in managed * code executed by fptr->cfn() could've reset the callframe stack already. */ if (vm->callframes.count > 0) ucv_put(uc_vm_callframe_pop(vm)); /* push return value */ if (!vm->exception.type) uc_vm_stack_push(vm, res); else ucv_put(res); } static bool uc_vm_call_function(uc_vm_t *vm, uc_value_t *ctx, uc_value_t *fno, bool mcall, size_t argspec) { size_t i, j, stackoff, nargs = argspec & 0xffff, nspreads = argspec >> 16; uc_callframe_t *frame = NULL; uc_value_t *ellip, *arg; uc_function_t *function; uc_closure_t *closure; uint16_t slot, tmp; char *s; /* XXX: make dependent on stack size */ if (vm->callframes.count >= 1000) { uc_vm_raise_exception(vm, EXCEPTION_RUNTIME, "Too much recursion"); ucv_put(ctx); ucv_put(fno); return false; } stackoff = vm->stack.count - nargs - 1; /* argument list contains spread operations, we need to reshuffle the stack */ if (nspreads > 0) { frame = uc_vm_current_frame(vm); /* create temporary array */ ellip = ucv_array_new_length(vm, nargs); /* pop original stack values and push to temp array in reverse order */ for (i = 0; i < nargs; i++) ucv_array_push(ellip, uc_vm_stack_pop(vm)); /* for each spread value index ... */ for (i = 0, slot = nargs; i < nspreads; i++) { /* decode stack depth value */ tmp = frame->ip[0] * 0x100 + frame->ip[1]; frame->ip += 2; /* push each preceeding non-spread value to the stack */ for (j = slot; j > tmp + 1UL; j--) uc_vm_stack_push(vm, ucv_get(ucv_array_get(ellip, j - 1))); /* read spread value at index... */ slot = tmp; arg = ucv_get(ucv_array_get(ellip, slot)); /* ... ensure that it is an array type ... */ if (ucv_type(arg) != UC_ARRAY) { s = ucv_to_string(vm, arg); uc_vm_raise_exception(vm, EXCEPTION_TYPE, "(%s) is not iterable", s); free(s); ucv_put(fno); return false; } /* ... and push each spread array value as argument to the stack */ for (j = 0; j < ucv_array_length(arg); j++) uc_vm_stack_push(vm, ucv_get(ucv_array_get(arg, j))); ucv_put(arg); } /* push remaining non-spread arguments to the stack */ for (i = slot; i > 0; i--) uc_vm_stack_push(vm, ucv_get(ucv_array_get(ellip, i - 1))); /* free temp array */ ucv_put(ellip); /* update arg count */ nargs = vm->stack.count - stackoff - 1; } /* is a native function */ if (ucv_type(fno) == UC_CFUNCTION) { uc_vm_call_native(vm, ctx, (uc_cfunction_t *)fno, mcall, nargs); return true; } if (ucv_type(fno) != UC_CLOSURE) { uc_vm_raise_exception(vm, EXCEPTION_TYPE, "left-hand side is not a function"); ucv_put(ctx); ucv_put(fno); return false; } closure = (uc_closure_t *)fno; function = closure->function; /* fewer arguments on stack than function expects => pad */ if (nargs < function->nargs) { for (i = nargs; i < function->nargs; i++) { if (function->vararg && (i + 1) == function->nargs) uc_vm_stack_push(vm, ucv_array_new_length(vm, 0)); else uc_vm_stack_push(vm, NULL); } } /* more arguments on stack than function expects... */ else if (nargs > function->nargs - function->vararg) { /* is a vararg function => pass excess args as array */ if (function->vararg) { ellip = ucv_array_new_length(vm, nargs - (function->nargs - 1)); for (i = function->nargs; i <= nargs; i++) ucv_array_push(ellip, uc_vm_stack_peek(vm, nargs - i)); for (i = function->nargs; i <= nargs; i++) uc_vm_stack_pop(vm); uc_vm_stack_push(vm, ellip); } /* static amount of args => drop excess values */ else { for (i = function->nargs; i < nargs; i++) ucv_put(uc_vm_stack_pop(vm)); } } uc_vector_grow(&vm->callframes); frame = &vm->callframes.entries[vm->callframes.count++]; frame->stackframe = stackoff; frame->cfunction = NULL; frame->closure = closure; frame->ctx = ctx; frame->ip = function->chunk.entries; frame->mcall = mcall; frame->strict = function->strict; if (vm->trace) uc_vm_frame_dump(vm, frame); return true; } static uc_source_t *last_source = NULL; static size_t last_srcpos = 0; static void uc_dump_insn(uc_vm_t *vm, uint8_t *pos, uc_vm_insn_t insn) { uc_callframe_t *frame = uc_vm_current_frame(vm); uc_chunk_t *chunk = uc_vm_frame_chunk(frame); uc_stringbuf_t *buf = NULL; uc_value_t *cnst = NULL; uc_source_t *source; size_t srcpos; srcpos = uc_program_function_srcpos(frame->closure->function, pos - chunk->entries); source = uc_vm_frame_source(frame); if (last_srcpos == 0 || last_source != source || srcpos != last_srcpos) { buf = xprintbuf_new(); uc_source_context_format(buf, source, srcpos, true); fwrite(buf->buf, 1, printbuf_length(buf), stderr); printbuf_free(buf); last_source = source; last_srcpos = srcpos; } fprintf(stderr, "%08zx %s", pos - chunk->entries, insn_names[insn]); switch (insn_operand_bytes[insn]) { case 0: break; case -1: fprintf(stderr, " {%s%hhd}", vm->arg.s8 < 0 ? "" : "+", vm->arg.s8); break; case -2: fprintf(stderr, " {%c0x%hx}", vm->arg.s16 < 0 ? '-' : '+', (uint16_t)(vm->arg.s16 < 0 ? -vm->arg.s16 : vm->arg.s16)); break; case -4: fprintf(stderr, " {%c0x%x}", vm->arg.s32 < 0 ? '-' : '+', (uint32_t)(vm->arg.s32 < 0 ? -vm->arg.s32 : vm->arg.s32)); break; case 1: fprintf(stderr, " {%hhu}", vm->arg.u8); break; case 2: fprintf(stderr, " {0x%hx}", vm->arg.u16); break; case 4: fprintf(stderr, " {0x%x}", vm->arg.u32); break; default: fprintf(stderr, " (unknown operand format: %" PRId8 ")", insn_operand_bytes[insn]); break; } switch (insn) { case I_LOAD: case I_LVAR: case I_SVAR: cnst = uc_program_get_constant(uc_vm_frame_program(uc_vector_last(&vm->callframes)), vm->arg.u32); fprintf(stderr, "\t; %s", cnst ? uc_vm_format_val(vm, cnst) : "(?)"); ucv_put(cnst); break; case I_LLOC: case I_LUPV: case I_SLOC: case I_SUPV: cnst = uc_chunk_debug_get_variable(chunk, pos - chunk->entries, vm->arg.u32, (insn == I_LUPV || insn == I_SUPV)); fprintf(stderr, "\t; %s", cnst ? uc_vm_format_val(vm, cnst) : "(?)"); ucv_put(cnst); break; case I_ULOC: case I_UUPV: cnst = uc_chunk_debug_get_variable(chunk, pos - chunk->entries, vm->arg.u32 & 0x00ffffff, (insn == I_UUPV)); /* fall through */ case I_UVAR: if (!cnst) cnst = uc_program_get_constant(uc_vm_frame_program(uc_vector_last(&vm->callframes)), vm->arg.u32 & 0x00ffffff); fprintf(stderr, "\t; %s (%s)", cnst ? uc_vm_format_val(vm, cnst) : "(?)", insn_names[vm->arg.u32 >> 24]); ucv_put(cnst); break; case I_UVAL: fprintf(stderr, "\t; (%s)", insn_names[vm->arg.u32]); break; default: break; } fprintf(stderr, "\n"); } static uc_value_t * uc_vm_exception_tostring(uc_vm_t *vm, size_t nargs) { uc_callframe_t *frame = uc_vm_current_frame(vm); uc_value_t *message = ucv_object_get(frame->ctx, "message", NULL); return message ? ucv_get(message) : ucv_string_new("Exception"); } static uc_value_t *exception_prototype = NULL; static uc_value_t * uc_vm_exception_new(uc_vm_t *vm, uc_exception_type_t type, const char *message, uc_value_t *stacktrace) { uc_value_t *exo; if (exception_prototype == NULL) { exception_prototype = ucv_object_new(vm); ucv_object_add(exception_prototype, "tostring", ucv_cfunction_new("tostring", uc_vm_exception_tostring)); } exo = ucv_object_new(vm); ucv_object_add(exo, "type", ucv_string_new(exception_type_strings[type])); ucv_object_add(exo, "message", ucv_string_new(message)); ucv_object_add(exo, "stacktrace", ucv_get(stacktrace)); ucv_prototype_set(exo, ucv_get(exception_prototype)); return exo; } static void uc_vm_clear_exception(uc_vm_t *vm) { vm->exception.type = EXCEPTION_NONE; ucv_put(vm->exception.stacktrace); vm->exception.stacktrace = NULL; free(vm->exception.message); vm->exception.message = NULL; } static bool uc_vm_handle_exception(uc_vm_t *vm) { uc_callframe_t *frame = NULL; uc_chunk_t *chunk = NULL; uc_value_t *exo; size_t i, pos; if (vm->callframes.count) frame = uc_vm_current_frame(vm); if (!frame || !frame->closure) return false; chunk = uc_vm_frame_chunk(frame); pos = frame->ip - chunk->entries; /* iterate the known exception ranges, see if the current ip falls into any of them */ for (i = 0; i < chunk->ehranges.count; i++) { /* skip nonmatching ranges */ if (pos < chunk->ehranges.entries[i].from || pos >= chunk->ehranges.entries[i].to) continue; /* we found a matching range... first unwind stack */ while (vm->stack.count > frame->stackframe + chunk->ehranges.entries[i].slot) ucv_put(uc_vm_stack_pop(vm)); /* prepare exception object and expose it to user handler code */ exo = uc_vm_exception_new(vm, vm->exception.type, vm->exception.message, vm->exception.stacktrace); uc_vm_stack_push(vm, exo); /* reset exception information */ uc_vm_clear_exception(vm); /* jump to exception handler */ if (chunk->ehranges.entries[i].target >= chunk->count) { uc_vm_raise_exception(vm, EXCEPTION_RUNTIME, "jump target out of range"); return false; } #if 0 if (vm->trace && chunk->entries + chunk->ehranges.entries[i].target > frame->ip) { while (frame->ip < chunk->entries + chunk->ehranges.entries[i].target) { fprintf(stderr, "(eh:skip) [%p:%zu] ", chunk, frame->ip - chunk->entries); uc_dump_insn(vm, frame->ip, uc_vm_decode_insn(vm, frame, chunk)); } } #endif frame->ip = chunk->entries + chunk->ehranges.entries[i].target; return true; } return false; } static uc_value_t * uc_vm_capture_stacktrace(uc_vm_t *vm, size_t i) { uc_value_t *stacktrace, *entry, *last = NULL; uc_function_t *function; uc_callframe_t *frame; uc_source_t *source; size_t off, srcpos; char *name; stacktrace = ucv_array_new(vm); for (; i > 0; i--) { frame = &vm->callframes.entries[i - 1]; entry = ucv_object_new(vm); if (frame->closure) { function = frame->closure->function; source = uc_program_function_source(function); off = (frame->ip - uc_vm_frame_chunk(frame)->entries) - 1; srcpos = uc_program_function_srcpos(function, off); ucv_object_add(entry, "filename", ucv_string_new(source->filename)); ucv_object_add(entry, "line", ucv_int64_new(uc_source_get_line(source, &srcpos))); ucv_object_add(entry, "byte", ucv_int64_new(srcpos)); } if (i > 1) { if (frame->closure) { if (frame->closure->function->name[0]) name = frame->closure->function->name; else if (frame->closure->is_arrow) name = "[arrow function]"; else name = "[anonymous function]"; } else { name = frame->cfunction->name; } ucv_object_add(entry, "function", ucv_string_new(name)); } if (!ucv_is_equal(last, entry)) { ucv_array_push(stacktrace, entry); last = entry; } else { ucv_put(entry); } } return stacktrace; } static uc_value_t * uc_vm_get_error_context(uc_vm_t *vm) { size_t offset, i, byte, line; uc_value_t *stacktrace; uc_callframe_t *frame; uc_stringbuf_t *buf; uc_chunk_t *chunk; /* skip to first non-native function call frame */ for (i = vm->callframes.count; i > 1; i--) if (vm->callframes.entries[i - 1].closure) break; frame = &vm->callframes.entries[i - 1]; if (!frame->closure) return NULL; chunk = uc_vm_frame_chunk(frame); offset = uc_program_function_srcpos(frame->closure->function, (frame->ip - chunk->entries) - 1); stacktrace = uc_vm_capture_stacktrace(vm, i); buf = ucv_stringbuf_new(); byte = offset; line = uc_source_get_line(uc_program_function_source(frame->closure->function), &byte); if (line) uc_error_context_format(buf, uc_vm_frame_source(frame), stacktrace, offset); else if (frame->ip != chunk->entries) ucv_stringbuf_printf(buf, "At instruction %zu", (frame->ip - chunk->entries) - 1); else ucv_stringbuf_append(buf, "At start of program"); ucv_object_add(ucv_array_get(stacktrace, 0), "context", ucv_stringbuf_finish(buf)); return stacktrace; } void __attribute__((format(printf, 3, 0))) uc_vm_raise_exception(uc_vm_t *vm, uc_exception_type_t type, const char *fmt, ...) { va_list ap; vm->exception.type = type; free(vm->exception.message); va_start(ap, fmt); xvasprintf(&vm->exception.message, fmt, ap); va_end(ap); ucv_put(vm->exception.stacktrace); vm->exception.stacktrace = uc_vm_get_error_context(vm); } static void uc_vm_insn_load(uc_vm_t *vm, uc_vm_insn_t insn) { switch (insn) { case I_LOAD: uc_vm_stack_push(vm, uc_program_get_constant(uc_vm_current_program(vm), vm->arg.u32)); break; case I_LOAD8: uc_vm_stack_push(vm, ucv_uint64_new(vm->arg.u8)); break; case I_LOAD16: uc_vm_stack_push(vm, ucv_uint64_new(vm->arg.u16)); break; case I_LOAD32: uc_vm_stack_push(vm, ucv_uint64_new(vm->arg.u32)); break; default: break; } } static void uc_vm_insn_load_regexp(uc_vm_t *vm, uc_vm_insn_t insn) { uc_value_t *re, *jstr = uc_program_get_constant(uc_vm_current_program(vm), vm->arg.u32); bool icase = false, newline = false, global = false; char *str, *err = NULL; if (ucv_type(jstr) != UC_STRING || ucv_string_length(jstr) < 2) { uc_vm_stack_push(vm, NULL); ucv_put(jstr); return; } str = ucv_string_get(jstr); global = (*str & (1 << 0)); icase = (*str & (1 << 1)); newline = (*str & (1 << 2)); re = ucv_regexp_new(++str, icase, newline, global, &err); ucv_put(jstr); if (re) uc_vm_stack_push(vm, re); else uc_vm_raise_exception(vm, EXCEPTION_SYNTAX, "%s", err); free(err); } static void uc_vm_insn_load_null(uc_vm_t *vm, uc_vm_insn_t insn) { uc_vm_stack_push(vm, NULL); } static void uc_vm_insn_load_bool(uc_vm_t *vm, uc_vm_insn_t insn) { uc_vm_stack_push(vm, ucv_boolean_new(insn == I_LTRUE)); } static void uc_vm_insn_load_var(uc_vm_t *vm, uc_vm_insn_t insn) { uc_value_t *name, *val = NULL; uc_value_t *scope, *next; bool found; scope = vm->globals; name = uc_program_get_constant(uc_vm_current_program(vm), vm->arg.u32); while (ucv_type(name) == UC_STRING) { val = ucv_object_get(scope, ucv_string_get(name), &found); if (found) break; next = ucv_prototype_get(scope); if (!next) { if (uc_vm_is_strict(vm)) { uc_vm_raise_exception(vm, EXCEPTION_REFERENCE, "access to undeclared variable %s", ucv_string_get(name)); } break; } scope = next; } ucv_put(name); uc_vm_stack_push(vm, ucv_get(val)); } static void uc_vm_insn_load_val(uc_vm_t *vm, uc_vm_insn_t insn) { uc_value_t *k = uc_vm_stack_pop(vm); uc_value_t *v = uc_vm_stack_pop(vm); switch (ucv_type(v)) { case UC_RESOURCE: case UC_OBJECT: case UC_ARRAY: uc_vm_stack_push(vm, ucv_key_get(vm, v, k)); break; default: if (insn == I_QLVAL) uc_vm_stack_push(vm, NULL); else uc_vm_raise_exception(vm, EXCEPTION_REFERENCE, "left-hand side expression is %s", v ? "not an array or object" : "null"); break; } ucv_put(k); ucv_put(v); } static void uc_vm_insn_load_upval(uc_vm_t *vm, uc_vm_insn_t insn) { uc_callframe_t *frame = uc_vm_current_frame(vm); uc_upvalref_t *ref = frame->closure->upvals[vm->arg.u32]; if (ref->closed) uc_vm_stack_push(vm, ucv_get(ref->value)); else uc_vm_stack_push(vm, ucv_get(vm->stack.entries[ref->slot])); } static void uc_vm_insn_load_local(uc_vm_t *vm, uc_vm_insn_t insn) { uc_callframe_t *frame = uc_vm_current_frame(vm); uc_vm_stack_push(vm, ucv_get(vm->stack.entries[frame->stackframe + vm->arg.u32])); } static uc_upvalref_t * uc_vm_capture_upval(uc_vm_t *vm, size_t slot) { uc_upvalref_t *curr = vm->open_upvals; uc_upvalref_t *prev = NULL; uc_upvalref_t *created; char *s; while (curr && curr->slot > slot) { prev = curr; curr = curr->next; } if (curr && curr->slot == slot) { if (vm->trace) { s = ucv_to_string(NULL, vm->stack.entries[slot]); fprintf(stderr, " {+%zu} <%p> %s\n", slot, (void *)curr, s); free(s); } return curr; } created = (uc_upvalref_t *)ucv_upvalref_new(slot); created->next = curr; if (vm->trace) { s = ucv_to_string(NULL, vm->stack.entries[slot]); fprintf(stderr, " {*%zu} <%p> %s\n", slot, (void *)created, s); free(s); } if (prev) prev->next = created; else vm->open_upvals = created; return created; } static void uc_vm_close_upvals(uc_vm_t *vm, size_t slot) { uc_upvalref_t *ref; while (vm->open_upvals && vm->open_upvals->slot >= slot) { ref = vm->open_upvals; ref->value = ucv_get(vm->stack.entries[ref->slot]); ref->closed = true; if (vm->trace) { fprintf(stderr, " {!%zu} <%p> %s\n", ref->slot, (void *)ref, uc_vm_format_val(vm, ref->value)); } vm->open_upvals = ref->next; ucv_put(&ref->header); } } static void uc_vm_insn_load_closure(uc_vm_t *vm, uc_vm_insn_t insn) { uc_callframe_t *frame = uc_vm_current_frame(vm); uc_function_t *function = uc_program_function_load(uc_vm_current_program(vm), vm->arg.u32); uc_closure_t *closure = (uc_closure_t *)ucv_closure_new(vm, function, insn == I_ARFN); volatile int32_t uv; size_t i; uc_vm_stack_push(vm, &closure->header); if (function->module) return; for (i = 0; i < function->nupvals; i++) { uv = ( frame->ip[0] * 0x1000000 + frame->ip[1] * 0x10000 + frame->ip[2] * 0x100 + frame->ip[3] ) - 0x7fffffff; if (uv < 0) closure->upvals[i] = uc_vm_capture_upval(vm, frame->stackframe - (uv + 1)); else closure->upvals[i] = frame->closure->upvals[uv]; ucv_get(&closure->upvals[i]->header); frame->ip += 4; } } static void uc_vm_insn_store_var(uc_vm_t *vm, uc_vm_insn_t insn) { uc_value_t *name, *v = uc_vm_stack_pop(vm); uc_value_t *scope, *next; bool found; scope = vm->globals; name = uc_program_get_constant(uc_vm_current_program(vm), vm->arg.u32); while (ucv_type(name) == UC_STRING) { ucv_object_get(scope, ucv_string_get(name), &found); if (found) break; next = ucv_prototype_get(scope); if (!next) { if (uc_vm_is_strict(vm)) { uc_vm_raise_exception(vm, EXCEPTION_REFERENCE, "access to undeclared variable %s", ucv_string_get(name)); } break; } scope = next; } if (scope && ucv_type(name) == UC_STRING) ucv_object_add(scope, ucv_string_get(name), ucv_get(v)); ucv_put(name); uc_vm_stack_push(vm, v); } static bool assert_mutable_value(uc_vm_t *vm, uc_value_t *val) { if (ucv_is_constant(val)) { uc_vm_stack_push(vm, NULL); uc_vm_raise_exception(vm, EXCEPTION_TYPE, "%s value is immutable", ucv_typename(val)); return false; } return true; } static void uc_vm_insn_store_val(uc_vm_t *vm, uc_vm_insn_t insn) { uc_value_t *v = uc_vm_stack_pop(vm); uc_value_t *k = uc_vm_stack_pop(vm); uc_value_t *o = uc_vm_stack_pop(vm); switch (ucv_type(o)) { case UC_OBJECT: case UC_ARRAY: if (assert_mutable_value(vm, o)) uc_vm_stack_push(vm, ucv_key_set(vm, o, k, v)); break; default: uc_vm_raise_exception(vm, EXCEPTION_TYPE, "attempt to set property on %s value", ucv_typename(o)); } ucv_put(o); ucv_put(k); } static void uc_vm_insn_store_upval(uc_vm_t *vm, uc_vm_insn_t insn) { uc_callframe_t *frame = uc_vm_current_frame(vm); uc_upvalref_t *ref = frame->closure->upvals[vm->arg.u32]; uc_value_t *val = ucv_get(uc_vm_stack_peek(vm, 0)); if (ref->closed) { ucv_put(ref->value); ref->value = val; } else { uc_vm_stack_set(vm, ref->slot, val); } } static void uc_vm_insn_store_local(uc_vm_t *vm, uc_vm_insn_t insn) { uc_callframe_t *frame = uc_vm_current_frame(vm); uc_value_t *val = ucv_get(uc_vm_stack_peek(vm, 0)); uc_vm_stack_set(vm, frame->stackframe + vm->arg.u32, val); } static int64_t int64(uc_value_t *nv, uint64_t *u64) { int64_t n; n = ucv_int64_get(nv); *u64 = 0; if (errno == ERANGE) { n = INT64_MAX; *u64 = ucv_uint64_get(nv); } return n; } static uint64_t abs64(int64_t n) { if (n == INT64_MIN) return 0x8000000000000000ULL; if (n < 0) return -n; return n; } static uc_value_t * uc_vm_value_bitop(uc_vm_t *vm, uc_vm_insn_t operation, uc_value_t *value, uc_value_t *operand) { uc_value_t *nv1, *nv2, *rv = NULL; uint64_t u1, u2; int64_t n1, n2; nv1 = ucv_to_number(value); nv2 = ucv_to_number(operand); n1 = int64(nv1, &u1); n2 = int64(nv2, &u2); if (n1 < 0 || n2 < 0) { switch (operation) { case I_LSHIFT: rv = ucv_int64_new(n1 << n2); break; case I_RSHIFT: rv = ucv_int64_new(n1 >> n2); break; case I_BAND: rv = ucv_int64_new(n1 & n2); break; case I_BXOR: rv = ucv_int64_new(n1 ^ n2); break; case I_BOR: rv = ucv_int64_new(n1 | n2); break; default: break; } } else { if (!u1) u1 = (uint64_t)n1; if (!u2) u2 = (uint64_t)n2; switch (operation) { case I_LSHIFT: rv = ucv_uint64_new(u1 << (u2 % (sizeof(uint64_t) * CHAR_BIT))); break; case I_RSHIFT: rv = ucv_uint64_new(u1 >> (u2 % (sizeof(uint64_t) * CHAR_BIT))); break; case I_BAND: rv = ucv_uint64_new(u1 & u2); break; case I_BXOR: rv = ucv_uint64_new(u1 ^ u2); break; case I_BOR: rv = ucv_uint64_new(u1 | u2); break; default: break; } } ucv_put(nv1); ucv_put(nv2); return rv; } static uc_value_t * uc_vm_value_logical(uc_vm_t *vm, uc_vm_insn_t operation, uc_value_t *value, uc_value_t *operand) { uc_value_t *rv = NULL; switch (operation) { case I_LTRUE: rv = ucv_get(ucv_is_truish(value) ? operand : value); break; case I_LFALSE: rv = ucv_get(ucv_is_truish(value) ? value : operand); break; case I_LNULL: rv = ucv_get(value == NULL ? operand : value); break; default: break; } return rv; } static uc_value_t * uc_vm_string_concat(uc_vm_t *vm, uc_value_t *v1, uc_value_t *v2) { char buf[sizeof(void *)], *s1, *s2; uc_stringbuf_t *sbuf; size_t l1, l2; /* optimize cases for string+string concat... */ if (ucv_type(v1) == UC_STRING && ucv_type(v2) == UC_STRING) { s1 = ucv_string_get(v1); s2 = ucv_string_get(v2); l1 = ucv_string_length(v1); l2 = ucv_string_length(v2); /* ... result fits into a tagged pointer */ if (l1 + l2 + 1 < sizeof(buf)) { memcpy(&buf[0], s1, l1); memcpy(&buf[l1], s2, l2); return ucv_string_new_length(buf, l1 + l2); } } sbuf = ucv_stringbuf_new(); ucv_to_stringbuf(vm, sbuf, v1, false); ucv_to_stringbuf(vm, sbuf, v2, false); return ucv_stringbuf_finish(sbuf); } static uint64_t upow64(uint64_t base, uint64_t exponent) { uint64_t result = 1; while (exponent) { if (exponent & 1) result *= base; exponent >>= 1; base *= base; } return result; } static uc_value_t * uc_vm_value_arith(uc_vm_t *vm, uc_vm_insn_t operation, uc_value_t *value, uc_value_t *operand) { uc_value_t *nv1, *nv2, *rv = NULL; uint64_t u1, u2; int64_t n1, n2; double d1, d2; if (operation == I_LSHIFT || operation == I_RSHIFT || operation == I_BAND || operation == I_BXOR || operation == I_BOR) return uc_vm_value_bitop(vm, operation, value, operand); if (operation == I_LTRUE || operation == I_LFALSE || operation == I_LNULL) return uc_vm_value_logical(vm, operation, value, operand); if (operation == I_ADD && (ucv_type(value) == UC_STRING || ucv_type(operand) == UC_STRING)) return uc_vm_string_concat(vm, value, operand); nv1 = ucv_to_number(value); nv2 = ucv_to_number(operand); /* any operation involving NaN results in NaN */ if (!nv1 || !nv2) { ucv_put(nv1); ucv_put(nv2); return ucv_double_new(NAN); } if (ucv_type(nv1) == UC_DOUBLE || ucv_type(nv2) == UC_DOUBLE) { d1 = ucv_double_get(nv1); d2 = ucv_double_get(nv2); switch (operation) { case I_ADD: case I_PLUS: rv = ucv_double_new(d1 + d2); break; case I_SUB: case I_MINUS: rv = ucv_double_new(d1 - d2); break; case I_MUL: rv = ucv_double_new(d1 * d2); break; case I_DIV: if (d2 == 0.0) rv = ucv_double_new(INFINITY); else if (isnan(d2)) rv = ucv_double_new(NAN); else if (!isfinite(d2)) rv = ucv_double_new(isfinite(d1) ? 0.0 : NAN); else rv = ucv_double_new(d1 / d2); break; case I_MOD: rv = ucv_double_new(fmod(d1, d2)); break; case I_EXP: rv = ucv_double_new(pow(d1, d2)); break; default: uc_vm_raise_exception(vm, EXCEPTION_RUNTIME, "undefined arithmetic operation %d", operation); break; } } else { n1 = int64(nv1, &u1); n2 = int64(nv2, &u2); switch (operation) { case I_ADD: case I_PLUS: if (n1 < 0 || n2 < 0) { if (u1) rv = ucv_uint64_new(u1 - abs64(n2)); else if (u2) rv = ucv_uint64_new(u2 - abs64(n1)); else rv = ucv_int64_new(n1 + n2); } else { if (!u1) u1 = (uint64_t)n1; if (!u2) u2 = (uint64_t)n2; rv = ucv_uint64_new(u1 + u2); } break; case I_SUB: case I_MINUS: if (n1 < 0 && n2 < 0) { if (n1 > n2) rv = ucv_uint64_new(abs64(n2) - abs64(n1)); else rv = ucv_int64_new(n1 - n2); } else if (n1 >= 0 && n2 >= 0) { if (!u1) u1 = (uint64_t)n1; if (!u2) u2 = (uint64_t)n2; if (u2 > u1) rv = ucv_int64_new(-(u2 - u1)); else rv = ucv_uint64_new(u1 - u2); } else if (n1 >= 0) { if (!u1) u1 = (uint64_t)n1; rv = ucv_uint64_new(u1 + abs64(n2)); } else { rv = ucv_int64_new(n1 - n2); } break; case I_MUL: if (n1 < 0 && n2 < 0) { rv = ucv_uint64_new(abs64(n1) * abs64(n2)); } else if (n1 >= 0 && n2 >= 0) { if (!u1) u1 = (uint64_t)n1; if (!u2) u2 = (uint64_t)n2; rv = ucv_uint64_new(u1 * u2); } else { rv = ucv_int64_new(n1 * n2); } break; case I_DIV: if (n2 == 0) { rv = ucv_double_new(INFINITY); } else if (n1 < 0 || n2 < 0) { rv = ucv_int64_new(n1 / n2); } else { if (!u1) u1 = (uint64_t)n1; if (!u2) u2 = (uint64_t)n2; rv = ucv_uint64_new(u1 / u2); } break; case I_MOD: if (n1 < 0 || n2 < 0) { rv = ucv_int64_new(n1 % n2); } else { if (!u1) u1 = (uint64_t)n1; if (!u2) u2 = (uint64_t)n2; rv = ucv_uint64_new(u1 % u2); } break; case I_EXP: if (n1 < 0 || n2 < 0) { if (n1 < 0 && n2 < 0) rv = ucv_double_new(-(1.0 / (double)upow64(abs64(n1), abs64(n2)))); else if (n2 < 0) rv = ucv_double_new(1.0 / (double)upow64(abs64(n1), abs64(n2))); else rv = ucv_int64_new(-upow64(abs64(n1), abs64(n2))); } else { if (!u1) u1 = (uint64_t)n1; if (!u2) u2 = (uint64_t)n2; rv = ucv_uint64_new(upow64(u1, u2)); } break; default: uc_vm_raise_exception(vm, EXCEPTION_RUNTIME, "undefined arithmetic operation %d", operation); break; } } ucv_put(nv1); ucv_put(nv2); return rv; } static void uc_vm_insn_update_var(uc_vm_t *vm, uc_vm_insn_t insn) { uc_value_t *name, *val = NULL, *inc = uc_vm_stack_pop(vm); uc_value_t *scope, *next; bool found; scope = vm->globals; name = uc_program_get_constant(uc_vm_current_program(vm), vm->arg.u32 & 0x00FFFFFF); assert(ucv_type(name) == UC_STRING); while (true) { val = ucv_object_get(scope, ucv_string_get(name), &found); if (found) break; next = ucv_prototype_get(scope); if (!next) { if (uc_vm_is_strict(vm)) { uc_vm_raise_exception(vm, EXCEPTION_REFERENCE, "access to undeclared variable %s", ucv_string_get(name)); } break; } scope = next; } val = uc_vm_value_arith(vm, vm->arg.u32 >> 24, val, inc); ucv_object_add(scope, ucv_string_get(name), ucv_get(val)); uc_vm_stack_push(vm, val); ucv_put(name); ucv_put(inc); } static void uc_vm_insn_update_val(uc_vm_t *vm, uc_vm_insn_t insn) { uc_value_t *inc = uc_vm_stack_pop(vm); uc_value_t *k = uc_vm_stack_pop(vm); uc_value_t *v = uc_vm_stack_pop(vm); uc_value_t *val = NULL; switch (ucv_type(v)) { case UC_OBJECT: case UC_ARRAY: if (assert_mutable_value(vm, v)) { val = ucv_key_get(vm, v, k); uc_vm_stack_push(vm, ucv_key_set(vm, v, k, uc_vm_value_arith(vm, vm->arg.u8, val, inc))); } break; default: uc_vm_raise_exception(vm, EXCEPTION_REFERENCE, "left-hand side expression is %s", v ? "not an array or object" : "null"); break; } ucv_put(val); ucv_put(inc); ucv_put(v); ucv_put(k); } static void uc_vm_insn_update_upval(uc_vm_t *vm, uc_vm_insn_t insn) { uc_callframe_t *frame = uc_vm_current_frame(vm); size_t slot = vm->arg.u32 & 0x00FFFFFF; uc_upvalref_t *ref = frame->closure->upvals[slot]; uc_value_t *inc = uc_vm_stack_pop(vm); uc_value_t *val; if (ref->closed) val = ref->value; else val = vm->stack.entries[ref->slot]; val = uc_vm_value_arith(vm, vm->arg.u32 >> 24, val, inc); uc_vm_stack_push(vm, val); ucv_put(inc); if (ref->closed) { ucv_put(ref->value); ref->value = ucv_get(uc_vm_stack_peek(vm, 0)); } else { uc_vm_stack_set(vm, ref->slot, ucv_get(uc_vm_stack_peek(vm, 0))); } } static void uc_vm_insn_update_local(uc_vm_t *vm, uc_vm_insn_t insn) { uc_callframe_t *frame = uc_vm_current_frame(vm); size_t slot = vm->arg.u32 & 0x00FFFFFF; uc_value_t *inc = uc_vm_stack_pop(vm); uc_value_t *val; val = uc_vm_value_arith(vm, vm->arg.u32 >> 24, vm->stack.entries[frame->stackframe + slot], inc); uc_vm_stack_push(vm, val); ucv_put(inc); uc_vm_stack_set(vm, frame->stackframe + slot, ucv_get(uc_vm_stack_peek(vm, 0))); } static void uc_vm_insn_narr(uc_vm_t *vm, uc_vm_insn_t insn) { uc_value_t *arr = ucv_array_new_length(vm, vm->arg.u32); uc_vm_stack_push(vm, arr); } static void uc_vm_insn_parr(uc_vm_t *vm, uc_vm_insn_t insn) { uc_value_t *arr = uc_vm_stack_peek(vm, vm->arg.u32); size_t idx; for (idx = 0; idx < vm->arg.u32; idx++) ucv_array_push(arr, uc_vm_stack_peek(vm, vm->arg.u32 - idx - 1)); for (idx = 0; idx < vm->arg.u32; idx++) uc_vm_stack_pop(vm); //uc_vm_shrink(state, vm->arg.u32); } static void uc_vm_insn_marr(uc_vm_t *vm, uc_vm_insn_t insn) { uc_value_t *src = uc_vm_stack_pop(vm); uc_value_t *dst = uc_vm_stack_peek(vm, 0); size_t i; char *s; if (ucv_type(src) != UC_ARRAY) { s = ucv_to_string(vm, src); uc_vm_raise_exception(vm, EXCEPTION_TYPE, "(%s) is not iterable", s); ucv_put(src); free(s); return; } for (i = 0; i < ucv_array_length(src); i++) ucv_array_push(dst, ucv_get(ucv_array_get(src, i))); ucv_put(src); } static void uc_vm_insn_nobj(uc_vm_t *vm, uc_vm_insn_t insn) { uc_value_t *obj = ucv_object_new(vm); uc_vm_stack_push(vm, obj); } static void uc_vm_insn_sobj(uc_vm_t *vm, uc_vm_insn_t insn) { uc_value_t *obj = uc_vm_stack_peek(vm, vm->arg.u32); size_t idx; for (idx = 0; idx < vm->arg.u32; idx += 2) ucv_key_set(vm, obj, uc_vm_stack_peek(vm, vm->arg.u32 - idx - 1), uc_vm_stack_peek(vm, vm->arg.u32 - idx - 2)); for (idx = 0; idx < vm->arg.u32; idx++) ucv_put(uc_vm_stack_pop(vm)); } static void uc_vm_insn_mobj(uc_vm_t *vm, uc_vm_insn_t insn) { uc_value_t *src = uc_vm_stack_pop(vm); uc_value_t *dst = uc_vm_stack_peek(vm, 0); size_t i; char *s; switch (ucv_type(src)) { case UC_OBJECT: ; /* a label can only be part of a statement and a declaration is not a statement */ ucv_object_foreach(src, k, v) ucv_object_add(dst, k, ucv_get(v)); ucv_put(src); break; case json_type_array: for (i = 0; i < ucv_array_length(src); i++) { xasprintf(&s, "%zu", i); ucv_object_add(dst, s, ucv_get(ucv_array_get(src, i))); free(s); } ucv_put(src); break; default: s = ucv_to_string(vm, src); uc_vm_raise_exception(vm, EXCEPTION_TYPE, "Value (%s) is not iterable", s); free(s); break; } } static void uc_vm_insn_arith(uc_vm_t *vm, uc_vm_insn_t insn) { uc_value_t *r2 = uc_vm_stack_pop(vm); uc_value_t *r1 = uc_vm_stack_pop(vm); uc_value_t *rv; rv = uc_vm_value_arith(vm, insn, r1, r2); ucv_put(r1); ucv_put(r2); uc_vm_stack_push(vm, rv); } static void uc_vm_insn_plus_minus(uc_vm_t *vm, uc_vm_insn_t insn) { uc_value_t *v = uc_vm_stack_pop(vm), *nv; bool is_sub = (insn == I_MINUS); int64_t n; double d; if (ucv_type(v) == UC_STRING) { nv = uc_number_parse(ucv_string_get(v), NULL); if (nv) { ucv_put(v); v = nv; } } switch (ucv_type(v)) { case UC_INTEGER: n = ucv_int64_get(v); /* numeric value is in range 9223372036854775808..18446744073709551615 */ if (errno == ERANGE) { if (is_sub) /* make negation of large numeric value result in smallest negative value */ uc_vm_stack_push(vm, ucv_int64_new(INT64_MIN)); else /* for positive number coercion return value as-is */ uc_vm_stack_push(vm, ucv_get(v)); } /* numeric value is in range -9223372036854775808..9223372036854775807 */ else { if (is_sub) { if (n == INT64_MIN) /* make negation of minimum value result in maximum signed positive value */ uc_vm_stack_push(vm, ucv_int64_new(INT64_MAX)); else /* for all other values flip the sign */ uc_vm_stack_push(vm, ucv_int64_new(-n)); } else { /* for positive number coercion return value as-is */ uc_vm_stack_push(vm, ucv_get(v)); } } break; case UC_DOUBLE: d = ucv_double_get(v); uc_vm_stack_push(vm, ucv_double_new(is_sub ? -d : d)); break; case UC_BOOLEAN: n = (int64_t)ucv_boolean_get(v); uc_vm_stack_push(vm, ucv_int64_new(is_sub ? -n : n)); break; case UC_NULL: uc_vm_stack_push(vm, ucv_int64_new(0)); break; default: uc_vm_stack_push(vm, ucv_double_new(NAN)); } ucv_put(v); } static void uc_vm_insn_bitop(uc_vm_t *vm, uc_vm_insn_t insn) { uc_value_t *r2 = uc_vm_stack_pop(vm); uc_value_t *r1 = uc_vm_stack_pop(vm); uc_value_t *rv; rv = uc_vm_value_bitop(vm, insn, r1, r2); ucv_put(r1); ucv_put(r2); uc_vm_stack_push(vm, rv); } static void uc_vm_insn_complement(uc_vm_t *vm, uc_vm_insn_t insn) { uc_value_t *v = uc_vm_stack_pop(vm); uc_value_t *nv; uint64_t u; int64_t n; nv = ucv_to_number(v); n = int64(nv, &u); if (n < 0) { uc_vm_stack_push(vm, ucv_int64_new(~n)); } else { if (!u) u = (uint64_t)n; uc_vm_stack_push(vm, ucv_uint64_new(~u)); } ucv_put(nv); ucv_put(v); } static void uc_vm_insn_rel(uc_vm_t *vm, uc_vm_insn_t insn) { uc_value_t *r2 = uc_vm_stack_pop(vm); uc_value_t *r1 = uc_vm_stack_pop(vm); bool res = ucv_compare(insn, r1, r2, NULL); ucv_put(r1); ucv_put(r2); uc_vm_stack_push(vm, ucv_boolean_new(res)); } static void uc_vm_insn_in(uc_vm_t *vm, uc_vm_insn_t insn) { uc_value_t *r2 = uc_vm_stack_pop(vm); uc_value_t *r1 = uc_vm_stack_pop(vm); uc_value_t *item; size_t arrlen, arridx; bool found = false; char *key; switch (ucv_type(r2)) { case UC_ARRAY: for (arridx = 0, arrlen = ucv_array_length(r2); arridx < arrlen; arridx++) { item = ucv_array_get(r2, arridx); if (ucv_compare(I_EQ, r1, item, NULL)) { found = true; break; } } break; case UC_OBJECT: if (ucv_type(r1) == UC_STRING) { ucv_object_get(r2, ucv_string_get(r1), &found); } else { key = ucv_to_string(vm, r1); ucv_object_get(r2, key, &found); free(key); } break; default: found = false; } ucv_put(r1); ucv_put(r2); uc_vm_stack_push(vm, ucv_boolean_new(found)); } static void uc_vm_insn_equality(uc_vm_t *vm, uc_vm_insn_t insn) { uc_value_t *r2 = uc_vm_stack_pop(vm); uc_value_t *r1 = uc_vm_stack_pop(vm); bool equal; if (ucv_is_scalar(r1) && ucv_is_scalar(r2)) equal = ucv_is_equal(r1, r2); else equal = (r1 == r2); ucv_put(r1); ucv_put(r2); uc_vm_stack_push(vm, ucv_boolean_new((insn == I_EQS) ? equal : !equal)); } static void uc_vm_insn_not(uc_vm_t *vm, uc_vm_insn_t insn) { uc_value_t *r1 = uc_vm_stack_pop(vm); uc_vm_stack_push(vm, ucv_boolean_new(!ucv_is_truish(r1))); ucv_put(r1); } static void uc_vm_insn_jmp(uc_vm_t *vm, uc_vm_insn_t insn) { uc_callframe_t *frame = uc_vm_current_frame(vm); uc_chunk_t *chunk = uc_vm_frame_chunk(frame); int32_t addr = vm->arg.s32; /* ip already has been incremented */ addr -= 5; if (frame->ip + addr < chunk->entries || frame->ip + addr >= chunk->entries + chunk->count) { uc_vm_raise_exception(vm, EXCEPTION_RUNTIME, "jump target out of range"); return; } frame->ip += addr; } static void uc_vm_insn_jmpz(uc_vm_t *vm, uc_vm_insn_t insn) { uc_callframe_t *frame = uc_vm_current_frame(vm); uc_chunk_t *chunk = uc_vm_frame_chunk(frame); uc_value_t *v = uc_vm_stack_pop(vm); int32_t addr = vm->arg.s32; /* ip already has been incremented */ addr -= 5; if (frame->ip + addr < chunk->entries || frame->ip + addr >= chunk->entries + chunk->count) { uc_vm_raise_exception(vm, EXCEPTION_RUNTIME, "jump target out of range"); return; } if (!ucv_is_truish(v)) frame->ip += addr; ucv_put(v); } static void uc_vm_insn_next(uc_vm_t *vm, uc_vm_insn_t insn) { uc_value_t *k = uc_vm_stack_pop(vm); uc_value_t *v = uc_vm_stack_pop(vm); void *end = (void *)~(uintptr_t)0; uc_resource_t *iterk; struct lh_entry *curr; uint64_t n; if (k != NULL && ucv_type(k) != UC_RESOURCE) { fprintf(stderr, "Invalid iterator value\n"); abort(); } if (k == NULL) k = ucv_resource_new(NULL, NULL); iterk = (uc_resource_t *)k; switch (ucv_type(v)) { case UC_OBJECT: curr = iterk->data ? iterk->data : ((uc_object_t *)v)->table->head; if (curr != NULL && curr != end) { iterk->data = curr->next ? curr->next : end; uc_vm_stack_push(vm, ucv_string_new(curr->k)); if (insn == I_NEXTKV) uc_vm_stack_push(vm, ucv_get((uc_value_t *)curr->v)); uc_vm_stack_push(vm, k); ucv_put(v); return; } break; case UC_ARRAY: n = (uintptr_t)iterk->data; if (n < ucv_array_length(v)) { iterk->data = (void *)(uintptr_t)(n + 1); if (insn == I_NEXTKV) uc_vm_stack_push(vm, ucv_uint64_new(n)); uc_vm_stack_push(vm, ucv_get(ucv_array_get(v, n))); uc_vm_stack_push(vm, k); ucv_put(v); return; } break; default: break; } uc_vm_stack_push(vm, NULL); uc_vm_stack_push(vm, NULL); if (insn == I_NEXTKV) uc_vm_stack_push(vm, NULL); ucv_put(k); ucv_put(v); } static void uc_vm_insn_close_upval(uc_vm_t *vm, uc_vm_insn_t insn) { uc_vm_close_upvals(vm, vm->stack.count - 1); ucv_put(uc_vm_stack_pop(vm)); } static void uc_vm_skip_call(uc_vm_t *vm, bool mcall) { uc_callframe_t *frame = uc_vm_current_frame(vm); size_t i; /* pop all function arguments, the function itself and the associated * function context off the stack */ for (i = 0; i < 1 + mcall + (vm->arg.u32 & 0xffff); i++) ucv_put(uc_vm_stack_pop(vm)); /* skip all encoded spread value indexes */ for (i = 0; i < (vm->arg.u32 >> 16); i++) frame->ip += 2; uc_vm_stack_push(vm, NULL); } static void uc_vm_insn_call(uc_vm_t *vm, uc_vm_insn_t insn) { uc_value_t *fno = ucv_get(uc_vm_stack_peek(vm, vm->arg.u32 & 0xffff)); uc_value_t *ctx = NULL; if (!ucv_is_callable(fno) && insn == I_QCALL) return uc_vm_skip_call(vm, false); if (!ucv_is_arrowfn(fno)) ctx = NULL; else if (vm->callframes.count > 0) ctx = uc_vm_current_frame(vm)->ctx; uc_vm_call_function(vm, ucv_get(ctx), fno, false, vm->arg.u32); } static void uc_vm_insn_mcall(uc_vm_t *vm, uc_vm_insn_t insn) { size_t key_slot = vm->stack.count - (vm->arg.u32 & 0xffff) - 1; uc_value_t *ctx = vm->stack.entries[key_slot - 1]; uc_value_t *key = vm->stack.entries[key_slot]; uc_value_t *fno = uc_vm_resolve_upval(vm, ucv_key_get(vm, ctx, key)); if (!ucv_is_callable(fno) && insn == I_QMCALL) return uc_vm_skip_call(vm, true); uc_vm_stack_set(vm, key_slot, fno); /* arrow functions as method calls inherit the parent ctx */ if (ucv_is_arrowfn(fno)) ctx = uc_vm_current_frame(vm)->ctx; uc_vm_call_function(vm, ucv_get(ctx), ucv_get(fno), true, vm->arg.u32); } static void uc_vm_insn_print(uc_vm_t *vm, uc_vm_insn_t insn) { uc_value_t *v = uc_vm_stack_pop(vm); char *p; switch (ucv_type(v)) { case UC_OBJECT: case UC_ARRAY: p = ucv_to_jsonstring(vm, v); fwrite(p, 1, strlen(p), vm->output); free(p); break; case UC_STRING: fwrite(ucv_string_get(v), 1, ucv_string_length(v), vm->output); break; case UC_NULL: break; default: p = ucv_to_string(vm, v); fwrite(p, 1, strlen(p), vm->output); free(p); } ucv_put(v); } static void uc_vm_insn_delete(uc_vm_t *vm, uc_vm_insn_t insn) { uc_value_t *k = uc_vm_stack_pop(vm); uc_value_t *v = uc_vm_stack_pop(vm); bool rv; switch (ucv_type(v)) { case UC_OBJECT: if (assert_mutable_value(vm, v)) { rv = ucv_key_delete(vm, v, k); uc_vm_stack_push(vm, ucv_boolean_new(rv)); } break; default: uc_vm_raise_exception(vm, EXCEPTION_REFERENCE, "left-hand side expression is %s", v ? "not an object" : "null"); break; } ucv_put(k); ucv_put(v); } static void uc_vm_insn_import(uc_vm_t *vm, uc_vm_insn_t insn) { uc_callframe_t *frame = uc_vm_current_frame(vm); uint16_t from = vm->arg.u32 & 0xffff; uint16_t to = vm->arg.u32 >> 16; uc_value_t *name, *modobj; uint32_t cidx; /* is a wildcard import * from ... */ if (to == 0xffff) { to = from; modobj = ucv_object_new(vm); /* instruction is followed by u16 containing the offset of the * first module export and `from` times u32 values containing * the constant indexes of the names */ for (from = frame->ip[0] * 0x100 + frame->ip[1], frame->ip += 2; from < to && from < vm->exports.count; from++) { cidx = ( frame->ip[0] * 0x1000000UL + frame->ip[1] * 0x10000UL + frame->ip[2] * 0x100UL + frame->ip[3] ); frame->ip += 4; name = uc_program_get_constant(uc_vm_current_program(vm), cidx); if (ucv_type(name) == UC_STRING && vm->exports.entries[from]) ucv_object_add(modobj, ucv_string_get(name), ucv_get(&vm->exports.entries[from]->header)); ucv_put(name); } ucv_set_constant(modobj, true); uc_vm_stack_push(vm, modobj); } /* module export available, patch into upvalue */ else if (from < vm->exports.count && vm->exports.entries[from]) { frame->closure->upvals[to] = vm->exports.entries[from]; ucv_get(&vm->exports.entries[from]->header); } /* module export missing, e.g. due to premature return in module, * patch up dummy upvalue ref with `null` value */ else { frame->closure->upvals[to] = (uc_upvalref_t *)ucv_upvalref_new(0); frame->closure->upvals[to]->closed = true; } } static void uc_vm_insn_export(uc_vm_t *vm, uc_vm_insn_t insn) { uc_callframe_t *frame = uc_vm_current_frame(vm); uc_upvalref_t *ref = uc_vm_capture_upval(vm, frame->stackframe + vm->arg.u32); uc_vector_push(&vm->exports, ref); ucv_get(&ref->header); } static void uc_vm_insn_dynload(uc_vm_t *vm, uc_vm_insn_t insn) { uc_callframe_t *frame = uc_vm_current_frame(vm); uc_value_t *name, *export, *modscope, *modobj; uint16_t count = vm->arg.u32 & 0xffff; uint16_t to = vm->arg.u32 >> 16; uint32_t cidx; bool found; /* instruction is followed by u32 containing the constant index of the * module name string to import and `count` times u32 values containing * the import name constant indexes */ cidx = ( frame->ip[0] * 0x1000000UL + frame->ip[1] * 0x10000UL + frame->ip[2] * 0x100UL + frame->ip[3] ); frame->ip += 4; /* push module name onto stack, then attempt to load module and pop * name value again. Will raise exception on error */ name = uc_program_get_constant(uc_vm_current_program(vm), cidx); modscope = uc_require_library(vm, name, true); ucv_put(name); if (!modscope) return; /* If count is zero, we're doing a wildcard import. Shallow copy module * object, mark it constant and patch into the target upvalue. */ if (count == 0) { modobj = ucv_object_new(vm); ucv_object_foreach(modscope, k, v) ucv_object_add(modobj, k, ucv_get(v)); ucv_set_constant(modobj, true); uc_vm_stack_push(vm, modobj); } /* ... otherwise we're importing a specific list of names */ else { while (count > 0) { cidx = ( frame->ip[0] * 0x1000000UL + frame->ip[1] * 0x10000UL + frame->ip[2] * 0x100UL + frame->ip[3] ); frame->ip += 4; name = uc_program_get_constant(uc_vm_current_program(vm), cidx); export = ucv_object_get(modscope, ucv_string_get(name), &found); if (!found) { uc_vm_raise_exception(vm, EXCEPTION_REFERENCE, "Module does not export %s", ucv_string_get(name)); ucv_put(name); return; } ucv_put(name); frame->closure->upvals[to] = (uc_upvalref_t *)ucv_upvalref_new(0); frame->closure->upvals[to]->closed = true; frame->closure->upvals[to]->value = ucv_get(export); count--; to++; } } } static void uc_vm_gc_step(uc_vm_t *vm) { size_t curr_count = 0, prev_count = 0; uc_weakref_t *ref; if (!(vm->gc_flags & GC_ENABLED)) return; if (vm->alloc_refs >= vm->gc_interval) { if (vm->trace) { for (ref = vm->values.next; ref != &vm->values; ref = ref->next) prev_count++; ucv_gc(vm); for (ref = vm->values.next; ref != &vm->values; ref = ref->next) curr_count++; fprintf(stderr, "! GC reclaimed %zu object(s)\n", prev_count - curr_count); } else { ucv_gc(vm); } vm->alloc_refs = 0; } } static uc_value_t * uc_vm_callframe_pop(uc_vm_t *vm) { uc_callframe_t *frame = uc_vm_current_frame(vm); uc_value_t *retval; /* close upvalues */ uc_vm_close_upvals(vm, frame->stackframe); if (vm->stack.count > frame->stackframe) retval = uc_vm_stack_pop(vm); else retval = NULL; /* reset function stack frame */ while (vm->stack.count > frame->stackframe) ucv_put(uc_vm_stack_pop(vm)); /* for method calls, release context as well */ if (frame->mcall) ucv_put(uc_vm_stack_pop(vm)); /* release function */ if (frame->closure) ucv_put(&frame->closure->header); if (frame->cfunction) ucv_put(&frame->cfunction->header); /* release context */ ucv_put(frame->ctx); vm->callframes.count--; return retval; } static void uc_vm_output_exception(uc_vm_t *vm, uc_exception_t *ex) { uc_value_t *ctx; if (ex->type == EXCEPTION_USER) fprintf(stderr, "%s\n", ex->message); else fprintf(stderr, "%s: %s\n", exception_type_strings[ex->type] ? exception_type_strings[ex->type] : "Error", ex->message); ctx = ucv_object_get(ucv_array_get(ex->stacktrace, 0), "context", NULL); if (ctx) fprintf(stderr, "%s\n", ucv_string_get(ctx)); fprintf(stderr, "\n"); } static uc_vm_status_t uc_vm_execute_chunk(uc_vm_t *vm) { uc_callframe_t *frame = uc_vm_current_frame(vm); uc_chunk_t *chunk = uc_vm_frame_chunk(frame); size_t caller = vm->callframes.count - 1; uc_value_t *retval; uc_vm_insn_t insn; uint8_t *ip; while (chunk && vm->callframes.count > caller) { if (vm->trace) { ip = frame->ip; insn = uc_vm_decode_insn(vm, frame, chunk); uc_dump_insn(vm, ip, insn); } else { insn = uc_vm_decode_insn(vm, frame, chunk); } switch (insn) { case I_LOAD: case I_LOAD8: case I_LOAD16: case I_LOAD32: uc_vm_insn_load(vm, insn); break; case I_LREXP: uc_vm_insn_load_regexp(vm, insn); break; case I_LNULL: uc_vm_insn_load_null(vm, insn); break; case I_LTRUE: case I_LFALSE: uc_vm_insn_load_bool(vm, insn); break; case I_LTHIS: uc_vm_stack_push(vm, ucv_get(frame->ctx)); break; case I_LVAR: uc_vm_insn_load_var(vm, insn); break; case I_LVAL: case I_QLVAL: uc_vm_insn_load_val(vm, insn); break; case I_LUPV: uc_vm_insn_load_upval(vm, insn); break; case I_LLOC: uc_vm_insn_load_local(vm, insn); break; case I_CLFN: case I_ARFN: uc_vm_insn_load_closure(vm, insn); break; case I_NARR: uc_vm_insn_narr(vm, insn); break; case I_PARR: uc_vm_insn_parr(vm, insn); break; case I_MARR: uc_vm_insn_marr(vm, insn); break; case I_NOBJ: uc_vm_insn_nobj(vm, insn); break; case I_SOBJ: uc_vm_insn_sobj(vm, insn); break; case I_MOBJ: uc_vm_insn_mobj(vm, insn); break; case I_SVAR: uc_vm_insn_store_var(vm, insn); break; case I_SVAL: uc_vm_insn_store_val(vm, insn); break; case I_SUPV: uc_vm_insn_store_upval(vm, insn); break; case I_SLOC: uc_vm_insn_store_local(vm, insn); break; case I_UVAR: uc_vm_insn_update_var(vm, insn); break; case I_UVAL: uc_vm_insn_update_val(vm, insn); break; case I_UUPV: uc_vm_insn_update_upval(vm, insn); break; case I_ULOC: uc_vm_insn_update_local(vm, insn); break; case I_ADD: case I_SUB: case I_MUL: case I_DIV: case I_MOD: case I_EXP: uc_vm_insn_arith(vm, insn); break; case I_PLUS: case I_MINUS: uc_vm_insn_plus_minus(vm, insn); break; case I_LSHIFT: case I_RSHIFT: case I_BAND: case I_BXOR: case I_BOR: uc_vm_insn_bitop(vm, insn); break; case I_COMPL: uc_vm_insn_complement(vm, insn); break; case I_EQS: case I_NES: uc_vm_insn_equality(vm, insn); break; case I_EQ: case I_NE: case I_LT: case I_LE: case I_GT: case I_GE: uc_vm_insn_rel(vm, insn); break; case I_IN: uc_vm_insn_in(vm, insn); break; case I_NOT: uc_vm_insn_not(vm, insn); break; case I_JMP: uc_vm_insn_jmp(vm, insn); break; case I_JMPZ: uc_vm_insn_jmpz(vm, insn); break; case I_NEXTK: case I_NEXTKV: uc_vm_insn_next(vm, insn); break; case I_COPY: uc_vm_stack_push(vm, ucv_get(uc_vm_stack_peek(vm, vm->arg.u8))); break; case I_POP: ucv_put(uc_vm_stack_pop(vm)); uc_vm_gc_step(vm); break; case I_CUPV: uc_vm_insn_close_upval(vm, insn); break; case I_CALL: case I_QCALL: uc_vm_insn_call(vm, insn); frame = uc_vm_current_frame(vm); chunk = frame->closure ? uc_vm_frame_chunk(frame) : NULL; break; case I_MCALL: case I_QMCALL: uc_vm_insn_mcall(vm, insn); frame = uc_vm_current_frame(vm); chunk = frame->closure ? uc_vm_frame_chunk(frame) : NULL; break; case I_RETURN: retval = uc_vm_callframe_pop(vm); uc_vm_stack_push(vm, retval); if (vm->callframes.count == 0) return STATUS_OK; frame = uc_vector_last(&vm->callframes); chunk = uc_vm_frame_chunk(frame); break; case I_PRINT: uc_vm_insn_print(vm, insn); break; case I_DELETE: uc_vm_insn_delete(vm, insn); break; case I_IMPORT: uc_vm_insn_import(vm, insn); break; case I_EXPORT: uc_vm_insn_export(vm, insn); break; case I_DYNLOAD: uc_vm_insn_dynload(vm, insn); break; default: uc_vm_raise_exception(vm, EXCEPTION_RUNTIME, "unknown opcode %d", insn); break; } /* previous instruction raised exception */ if (vm->exception.type != EXCEPTION_NONE) { /* VM termination was requested */ if (vm->exception.type == EXCEPTION_EXIT) { uc_vm_reset_callframes(vm); return STATUS_EXIT; } /* walk up callframes until something handles the exception or the original caller is reached */ while (!uc_vm_handle_exception(vm)) { /* if VM returned into native function, don't bubble up */ if (!chunk) return ERROR_RUNTIME; /* no exception handler in current function, pop callframe */ if (vm->callframes.count > 0) ucv_put(uc_vm_callframe_pop(vm)); /* no further callframe, report unhandled exception and terminate */ if (vm->callframes.count == 0 || vm->callframes.count <= caller) return ERROR_RUNTIME; /* resume execution in next remaining callframe */ frame = uc_vector_last(&vm->callframes); chunk = uc_vm_frame_chunk(frame); } } } return STATUS_OK; } uc_vm_status_t uc_vm_execute(uc_vm_t *vm, uc_program_t *program, uc_value_t **retval) { uc_function_t *fn = uc_program_entry(program); uc_closure_t *closure = (uc_closure_t *)ucv_closure_new(vm, fn, false); uc_vm_status_t status; uc_callframe_t *frame; uc_stringbuf_t *buf; uc_value_t *val; uc_vector_grow(&vm->callframes); frame = &vm->callframes.entries[vm->callframes.count++]; frame->closure = closure; frame->stackframe = 0; frame->ip = uc_vm_frame_chunk(frame)->entries; frame->strict = fn->strict; if (vm->trace) { buf = xprintbuf_new(); uc_source_context_format(buf, uc_vm_frame_source(frame), 0, true); fwrite(buf->buf, 1, printbuf_length(buf), stderr); printbuf_free(buf); uc_vm_frame_dump(vm, frame); } //uc_vm_stack_push(vm, closure->header.jso); uc_vm_stack_push(vm, NULL); status = uc_vm_execute_chunk(vm); switch (status) { case STATUS_OK: val = uc_vm_stack_pop(vm); if (retval) *retval = val; else ucv_put(val); break; case STATUS_EXIT: if (retval) *retval = ucv_int64_new(vm->arg.s32); break; default: if (vm->exhandler) vm->exhandler(vm, &vm->exception); if (retval) *retval = NULL; break; } return status; } uc_exception_type_t uc_vm_call(uc_vm_t *vm, bool mcall, size_t nargs) { uc_value_t *ctx = mcall ? ucv_get(uc_vm_stack_peek(vm, nargs + 1)) : NULL; uc_value_t *fno = ucv_get(uc_vm_stack_peek(vm, nargs)); uc_vm_clear_exception(vm); if (uc_vm_call_function(vm, ctx, fno, mcall, nargs & 0xffff)) { if (ucv_type(fno) != UC_CFUNCTION) uc_vm_execute_chunk(vm); } return vm->exception.type; } uc_value_t * uc_vm_scope_get(uc_vm_t *vm) { return vm->globals; } void uc_vm_scope_set(uc_vm_t *vm, uc_value_t *ctx) { ucv_put(vm->globals); vm->globals = ctx; } uc_value_t * uc_vm_invoke(uc_vm_t *vm, const char *fname, size_t nargs, ...) { uc_exception_type_t ex; uc_value_t *fno, *arg; va_list ap; size_t i; fno = ucv_property_get(vm->globals, fname); if (!ucv_is_callable(fno)) return NULL; uc_vm_stack_push(vm, ucv_get(fno)); va_start(ap, nargs); for (i = 0; i < nargs; i++) { arg = va_arg(ap, uc_value_t *); uc_vm_stack_push(vm, ucv_get(arg)); } va_end(ap); ex = uc_vm_call(vm, false, nargs); if (ex) { if (vm->exhandler) vm->exhandler(vm, &vm->exception); return NULL; } return uc_vm_stack_pop(vm); } uc_exception_handler_t * uc_vm_exception_handler_get(uc_vm_t *vm) { return vm->exhandler; } void uc_vm_exception_handler_set(uc_vm_t *vm, uc_exception_handler_t *exhandler) { vm->exhandler = exhandler; } uint32_t uc_vm_trace_get(uc_vm_t *vm) { return vm->trace; } void uc_vm_trace_set(uc_vm_t *vm, uint32_t level) { vm->trace = level; } bool uc_vm_registry_exists(uc_vm_t *vm, const char *key) { bool exists; ucv_object_get(vm->registry, key, &exists); return exists; } uc_value_t * uc_vm_registry_get(uc_vm_t *vm, const char *key) { return ucv_object_get(vm->registry, key, NULL); } void uc_vm_registry_set(uc_vm_t *vm, const char *key, uc_value_t *value) { if (!vm->registry) vm->registry = ucv_object_new(vm); ucv_object_add(vm->registry, key, value); } bool uc_vm_registry_delete(uc_vm_t *vm, const char *key) { return ucv_object_delete(vm->registry, key); } bool uc_vm_gc_start(uc_vm_t *vm, uint16_t interval) { bool changed = false; if (vm->gc_interval != interval) { vm->gc_interval = interval; changed = true; } if (!(vm->gc_flags & GC_ENABLED)) { vm->gc_flags |= GC_ENABLED; changed = true; } return changed; } bool uc_vm_gc_stop(uc_vm_t *vm) { if (!(vm->gc_flags & GC_ENABLED)) return false; vm->gc_flags &= ~GC_ENABLED; return true; }