From 6ac4e93c9a794efa76bdba3137d5d3e71281d19f Mon Sep 17 00:00:00 2001 From: Thakee Nathees Date: Mon, 2 May 2022 14:00:43 +0530 Subject: [PATCH] stack overflow, and realloc bug fixed stack realloc wasn't update the next callframe's base pointer which caused a crash which is fixed infinit recursions are now handled properly. now it'll dump the stack frames and exit properly --- src/pk_debug.c | 89 +++++++++++++++++++++++++++++++++-------------- src/pk_internal.h | 5 +++ src/pk_vm.c | 29 ++++++++++----- 3 files changed, 88 insertions(+), 35 deletions(-) diff --git a/src/pk_debug.c b/src/pk_debug.c index 689d315..83e4d75 100644 --- a/src/pk_debug.c +++ b/src/pk_debug.c @@ -170,6 +170,43 @@ void reportCompileTimeError(PKVM* vm, const char* path, int line, pkByteBufferClear(&buff, vm); } +static void _reportStackFrame(PKVM* vm, CallFrame* frame) { + pkWriteFn writefn = vm->config.stderr_write; + const Function* fn = frame->closure->fn; + ASSERT(!fn->is_native, OOPS); + + // After fetching the instruction the ip will be inceased so we're + // reducing it by 1. But stack overflows are occure before executing + // any instruction of that function, so the instruction_index possibly + // be -1 (set it to zero in that case). + int instruction_index = frame->ip - fn->fn->opcodes.data - 1; + if (instruction_index == -1) instruction_index++; + + int line = fn->fn->oplines.data[instruction_index]; + + if (fn->owner->path == NULL) { + + writefn(vm, " [at:"); + char buff[STR_INT_BUFF_SIZE]; + sprintf(buff, "%2d", line); + writefn(vm, buff); + writefn(vm, "] "); + writefn(vm, fn->name); + writefn(vm, "()\n"); + + } else { + writefn(vm, " "); + writefn(vm, fn->name); + writefn(vm, "() ["); + writefn(vm, fn->owner->path->data); + writefn(vm, ":"); + char buff[STR_INT_BUFF_SIZE]; + sprintf(buff, "%d", line); + writefn(vm, buff); + writefn(vm, "]\n"); + } +} + void reportRuntimeError(PKVM* vm, Fiber* fiber) { pkWriteFn writefn = vm->config.stderr_write; @@ -180,36 +217,36 @@ void reportRuntimeError(PKVM* vm, Fiber* fiber) { writefn(vm, fiber->error->data); writefn(vm, "\n"); - // Stack trace. - for (int i = fiber->frame_count - 1; i >= 0; i--) { - CallFrame* frame = &fiber->frames[i]; - const Function* fn = frame->closure->fn; - ASSERT(!fn->is_native, OOPS); - int line = fn->fn->oplines.data[frame->ip - fn->fn->opcodes.data - 1]; + // If the stack frames are greater than 2 * max_dump_frames + 1, + // we're only print the first [max_dump_frames] and last [max_dump_frames] + // lines. + int max_dump_frames = 10; - if (fn->owner->path == NULL) { - - writefn(vm, " [at:"); - char buff[STR_INT_BUFF_SIZE]; - sprintf(buff, "%2d", line); - writefn(vm, buff); - writefn(vm, "] "); - writefn(vm, fn->name); - writefn(vm, "()\n"); - - } else { - writefn(vm, " "); - writefn(vm, fn->name); - writefn(vm, "() ["); - writefn(vm, fn->owner->path->data); - writefn(vm, ":"); - char buff[STR_INT_BUFF_SIZE]; - sprintf(buff, "%d", line); - writefn(vm, buff); - writefn(vm, "]\n"); + if (fiber->frame_count > 2 * max_dump_frames) { + for (int i = 0; i < max_dump_frames; i++) { + CallFrame* frame = &fiber->frames[fiber->frame_count - 1 - i]; + _reportStackFrame(vm, frame); } + int skipped_count = fiber->frame_count - max_dump_frames * 2; + writefn(vm, " ... skipping "); + char buff[STR_INT_BUFF_SIZE]; + sprintf(buff, "%d", skipped_count); + writefn(vm, buff); + writefn(vm, " stack frames\n"); + + for (int i = max_dump_frames; i >= 0; i--) { + CallFrame* frame = &fiber->frames[i]; + _reportStackFrame(vm, frame); + } + + } else { + for (int i = fiber->frame_count - 1; i >= 0; i--) { + CallFrame* frame = &fiber->frames[i]; + _reportStackFrame(vm, frame); + } } + } // Opcode names array. diff --git a/src/pk_internal.h b/src/pk_internal.h index 56d2cf6..5283488 100644 --- a/src/pk_internal.h +++ b/src/pk_internal.h @@ -54,6 +54,11 @@ // header for more information on Nan-tagging. #define VAR_NAN_TAGGING 1 +// The maximum size of the pocketlang stack. This value is arbitrary. currently +// it's 800 KB. Change this to any value upto 2147483647 (signed integer max) +// if you want. +#define MAX_STACK_SIZE 1024 * 800 + // The maximum number of argument a pocketlang function supported to call. This // value is arbitrary and feel free to change it. (Just used this limit for an // internal buffer to store values before calling a new fiber). diff --git a/src/pk_vm.c b/src/pk_vm.c index 9acb6c1..01c6778 100644 --- a/src/pk_vm.c +++ b/src/pk_vm.c @@ -333,8 +333,13 @@ static inline Var importModule(PKVM* vm, String* key) { void vmEnsureStackSize(PKVM* vm, int size) { + if (size >= (MAX_STACK_SIZE / sizeof(Var))) { + VM_SET_ERROR(vm, newString(vm, "Maximum stack limit reached.")); + return; + } + Fiber* fiber = vm->fiber; - if (fiber->stack_size > size) return; + if (fiber->stack_size >= size) return; int new_size = utilPowerOf2Ceil(size); @@ -378,8 +383,11 @@ void vmEnsureStackSize(PKVM* vm, int size) { } } -static inline void pushCallFrame(PKVM* vm, const Closure* closure, Var* rbp) { +// The return address for the next call frame (rbp) has to be set to the +// fiber's ret (fiber->ret == next rbp). +static inline void pushCallFrame(PKVM* vm, const Closure* closure) { ASSERT(!closure->fn->is_native, OOPS); + ASSERT(vm->fiber->ret != NULL, OOPS); // Grow the stack frame if needed. if (vm->fiber->frame_count + 1 > vm->fiber->frame_capacity) { @@ -391,12 +399,12 @@ static inline void pushCallFrame(PKVM* vm, const Closure* closure, Var* rbp) { } // Grow the stack if needed. - int needed = (closure->fn->fn->stack_size + - (int)(vm->fiber->sp - vm->fiber->stack)); + int current_stack_slots = (int)(vm->fiber->sp - vm->fiber->stack) + 1; + int needed = closure->fn->fn->stack_size + current_stack_slots; vmEnsureStackSize(vm, needed); CallFrame* frame = vm->fiber->frames + vm->fiber->frame_count++; - frame->rbp = rbp; + frame->rbp = vm->fiber->ret; frame->closure = closure; frame->ip = closure->fn->fn->opcodes.data; @@ -927,6 +935,8 @@ L_vm_main_loop: ASSERT(imported->body != NULL, OOPS); + UPDATE_FRAME(); //< Update the current frame's ip. + // Note that we're setting the main function's return address to the // module itself (for every other function we'll push a null at the rbp // before calling them and it'll be returned without modified if the @@ -934,11 +944,11 @@ L_vm_main_loop: // body of the module, so the main function will return what's at the // rbp without modifying it. So at the end of the main function the // stack top would be the module itself. - Var* module_ret = fiber->sp - 1; + fiber->ret = fiber->sp - 1; + pushCallFrame(vm, imported->body); - UPDATE_FRAME(); //< Update the current frame's ip. - pushCallFrame(vm, imported->body, module_ret); LOAD_FRAME(); //< Load the top frame to vm's execution variables. + CHECK_ERROR(); //< Stack overflow. } DISPATCH(); @@ -1076,8 +1086,9 @@ L_do_call: (instruction == OP_SUPER_CALL), OOPS); UPDATE_FRAME(); //< Update the current frame's ip. - pushCallFrame(vm, closure, fiber->ret); + pushCallFrame(vm, closure); LOAD_FRAME(); //< Load the top frame to vm's execution variables. + CHECK_ERROR(); //< Stack overflow. } }