diff --git a/include/pocketpy/config.h b/include/pocketpy/config.h index 53109114..8d2e09b0 100644 --- a/include/pocketpy/config.h +++ b/include/pocketpy/config.h @@ -1,10 +1,10 @@ #pragma once // clang-format off -#define PK_VERSION "2.1.1" +#define PK_VERSION "2.1.2" #define PK_VERSION_MAJOR 2 #define PK_VERSION_MINOR 1 -#define PK_VERSION_PATCH 1 +#define PK_VERSION_PATCH 2 /*************** feature settings ***************/ #ifndef PK_ENABLE_OS // can be overridden by cmake diff --git a/include/pocketpy/interpreter/frame.h b/include/pocketpy/interpreter/frame.h index 15577be4..36163ee7 100644 --- a/include/pocketpy/interpreter/frame.h +++ b/include/pocketpy/interpreter/frame.h @@ -14,14 +14,11 @@ typedef struct ValueStack { py_TValue begin[PK_VM_STACK_SIZE + PK_MAX_CO_VARNAMES]; } ValueStack; -typedef struct UnwindTarget { - struct UnwindTarget* next; - int iblock; - int offset; -} UnwindTarget; - -UnwindTarget* UnwindTarget__new(UnwindTarget* next, int iblock, int offset); -void UnwindTarget__delete(UnwindTarget* self); +typedef struct FrameExcInfo { + int iblock; // try block index + int offset; // stack offset from p0 + py_TValue exc; // handled exception +} FrameExcInfo; typedef struct py_Frame { struct py_Frame* f_back; @@ -32,7 +29,7 @@ typedef struct py_Frame { py_Ref locals; bool is_locals_special; int ip; - UnwindTarget* uw_list; + c11_vector /*T=FrameExcInfo*/ exc_stack; } py_Frame; typedef struct SourceLocation { @@ -58,10 +55,9 @@ int Frame__delglobal(py_Frame* self, py_Name name) PY_RAISE; py_Ref Frame__getclosure(py_Frame* self, py_Name name); py_StackRef Frame__getlocal_noproxy(py_Frame* self, py_Name name); -int Frame__prepare_jump_exception_handler(py_Frame* self, ValueStack*); - -UnwindTarget* Frame__find_unwind_target(py_Frame* self, int iblock); -void Frame__set_unwind_target(py_Frame* self, py_TValue* sp); +int Frame__goto_exception_handler(py_Frame* self, ValueStack*, py_Ref); +void Frame__begin_try(py_Frame* self, py_TValue* sp); +FrameExcInfo* Frame__top_exc_info(py_Frame* self); void Frame__gc_mark(py_Frame* self, c11_vector* p_stack); SourceLocation Frame__source_location(py_Frame* self); \ No newline at end of file diff --git a/include/pocketpy/interpreter/vm.h b/include/pocketpy/interpreter/vm.h index d216e612..8cde83d4 100644 --- a/include/pocketpy/interpreter/vm.h +++ b/include/pocketpy/interpreter/vm.h @@ -53,13 +53,11 @@ typedef struct VM { py_Callbacks callbacks; py_TValue last_retval; - py_TValue curr_exception; + py_TValue unhandled_exc; int recursion_depth; int max_recursion_depth; - bool is_curr_exc_handled; // handled by try-except block but not cleared yet - py_TValue reg[8]; // users' registers void* ctx; // user-defined context diff --git a/include/pocketpy/objects/exception.h b/include/pocketpy/objects/exception.h index d6cb7cc3..140f989a 100644 --- a/include/pocketpy/objects/exception.h +++ b/include/pocketpy/objects/exception.h @@ -17,3 +17,5 @@ typedef struct BaseException { c11_vector /*T=BaseExceptionFrame*/ stacktrace; } BaseException; +char* safe_stringify_exception(py_Ref exc); +char* formatexc_internal(py_Ref exc); \ No newline at end of file diff --git a/include/pocketpy/pocketpy.h b/include/pocketpy/pocketpy.h index d9741ae5..ef57ef0d 100644 --- a/include/pocketpy/pocketpy.h +++ b/include/pocketpy/pocketpy.h @@ -600,20 +600,17 @@ PK_API int py_import(const char* path) PY_RAISE PY_RETURN; PK_API bool py_exception(py_Type type, const char* fmt, ...) PY_RAISE; /// Raise an exception object. Always return false. PK_API bool py_raise(py_Ref) PY_RAISE; -/// Print the current exception. -/// The exception will be set as handled. +/// Print the unhandled exception. PK_API void py_printexc(); -/// Format the current exception and return a null-terminated string. -/// The result should be freed by the caller. -/// The exception will be set as handled. +/// Format the unhandled exception and return a null-terminated string. +/// The returned string should be freed by the caller. PK_API char* py_formatexc(); -/// Check if an exception is raised. -PK_API bool py_checkexc(bool ignore_handled); -/// Check if the exception is an instance of the given type. -/// This function is roughly equivalent to python's `except as e:` block. -/// If match, the exception will be stored in `py_retval()` as handled. +/// Check if there is an unhandled exception. +PK_API bool py_checkexc(); +/// Check if the unhandled exception is an instance of the given type. +/// If match, the exception will be stored in `py_retval()`. PK_API bool py_matchexc(py_Type type) PY_RETURN; -/// Clear the current exception. +/// Clear the unhandled exception. /// @param p0 the unwinding point. Use `NULL` if not needed. PK_API void py_clearexc(py_StackRef p0); diff --git a/include/pocketpy/xmacros/opcodes.h b/include/pocketpy/xmacros/opcodes.h index f4cf9ac4..3253cfd7 100644 --- a/include/pocketpy/xmacros/opcodes.h +++ b/include/pocketpy/xmacros/opcodes.h @@ -116,14 +116,14 @@ OPCODE(ADD_CLASS_ANNOTATION) OPCODE(WITH_ENTER) OPCODE(WITH_EXIT) /**************************/ -OPCODE(TRY_ENTER) +OPCODE(BEGIN_TRY) +OPCODE(END_TRY) OPCODE(EXCEPTION_MATCH) +OPCODE(HANDLE_EXCEPTION) OPCODE(RAISE) OPCODE(RAISE_ASSERT) OPCODE(RE_RAISE) OPCODE(PUSH_EXCEPTION) -OPCODE(BEGIN_EXC_HANDLING) -OPCODE(END_EXC_HANDLING) /**************************/ OPCODE(FORMAT_STRING) /**************************/ diff --git a/src/compiler/compiler.c b/src/compiler/compiler.c index 1cd5106a..4dd0db17 100644 --- a/src/compiler/compiler.c +++ b/src/compiler/compiler.c @@ -1132,8 +1132,12 @@ static int Ctx__prepare_loop_divert(Ctx* self, int line, bool is_break) { Ctx__emit_(self, OP_POP_TOP, BC_NOARG, line); break; } + case CodeBlockType_TRY: { + Ctx__emit_(self, OP_END_TRY, BC_NOARG, line); + break; + } case CodeBlockType_EXCEPT: { - Ctx__emit_(self, OP_END_EXC_HANDLING, 1, line); + Ctx__emit_(self, OP_END_TRY, BC_NOARG, line); break; } default: break; @@ -1915,9 +1919,11 @@ static Error* exprCompileTimeCall(Compiler* self, py_ItemRef func, int line) { } while(match(TK_COMMA)); consume(TK_RPAREN); + py_StackRef p0 = py_peek(0); bool ok = py_vectorcall(argc, kwargc); if(!ok) { char* msg = py_formatexc(); + py_clearexc(p0); err = SyntaxError(self, "compile-time call error:\n%s", msg); PK_FREE(msg); return err; @@ -2623,8 +2629,9 @@ static Error* compile_try_except(Compiler* self) { int patches_length = 0; Ctx__enter_block(ctx(), CodeBlockType_TRY); - Ctx__emit_(ctx(), OP_TRY_ENTER, BC_NOARG, prev()->line); + Ctx__emit_(ctx(), OP_BEGIN_TRY, BC_NOARG, prev()->line); check(compile_block_body(self)); + Ctx__emit_(ctx(), OP_END_TRY, BC_NOARG, BC_KEEPLINE); // https://docs.python.org/3/reference/compound_stmts.html#finally-clause /* If finally is present, it specifies a ‘cleanup’ handler. The try clause is executed, @@ -2640,12 +2647,9 @@ static Error* compile_try_except(Compiler* self) { // A return, break, continue in try/except block will make the finally block not executed bool has_finally = curr()->type == TK_FINALLY; - if(!has_finally) { - patches[patches_length++] = Ctx__emit_(ctx(), OP_JUMP_FORWARD, BC_NOARG, BC_KEEPLINE); - } else { - return SyntaxError(self, "finally clause is not supported yet"); - } + if(has_finally) return SyntaxError(self, "finally clause is not supported yet"); + patches[patches_length++] = Ctx__emit_(ctx(), OP_JUMP_FORWARD, BC_NOARG, BC_KEEPLINE); Ctx__exit_block(ctx()); do { @@ -2670,7 +2674,7 @@ static Error* compile_try_except(Compiler* self) { } int patch = Ctx__emit_(ctx(), OP_POP_JUMP_IF_FALSE, BC_NOARG, BC_KEEPLINE); // on match - Ctx__emit_(ctx(), OP_BEGIN_EXC_HANDLING, BC_NOARG, BC_KEEPLINE); + Ctx__emit_(ctx(), OP_HANDLE_EXCEPTION, BC_NOARG, BC_KEEPLINE); if(as_name) { Ctx__emit_(ctx(), OP_PUSH_EXCEPTION, BC_NOARG, BC_KEEPLINE); Ctx__emit_store_name(ctx(), name_scope(self), as_name, BC_KEEPLINE); @@ -2678,23 +2682,20 @@ static Error* compile_try_except(Compiler* self) { Ctx__enter_block(ctx(), CodeBlockType_EXCEPT); check(compile_block_body(self)); Ctx__exit_block(ctx()); - Ctx__emit_(ctx(), OP_END_EXC_HANDLING, BC_NOARG, BC_KEEPLINE); + Ctx__emit_(ctx(), OP_END_TRY, BC_NOARG, BC_KEEPLINE); patches[patches_length++] = Ctx__emit_(ctx(), OP_JUMP_FORWARD, BC_NOARG, BC_KEEPLINE); Ctx__patch_jump(ctx(), patch); } while(curr()->type == TK_EXCEPT); // no match, re-raise - // ... + Ctx__emit_(ctx(), OP_RE_RAISE, BC_NOARG, BC_KEEPLINE); // match one & handled, jump to the end for(int i = 0; i < patches_length; i++) { Ctx__patch_jump(ctx(), patches[i]); } - if(match(TK_FINALLY)) { return SyntaxError(self, "finally clause is not supported yet"); } - - // re-raise if needed - Ctx__emit_(ctx(), OP_RE_RAISE, BC_NOARG, BC_KEEPLINE); + if(match(TK_FINALLY)) return SyntaxError(self, "finally clause is not supported yet"); return NULL; } @@ -2797,9 +2798,19 @@ static Error* compile_stmt(Compiler* self) { consume_end_stmt(); break; case TK_RAISE: { - check(EXPR(self)); - Ctx__s_emit_top(ctx()); - Ctx__emit_(ctx(), OP_RAISE, BC_NOARG, kw_line); + if(is_expression(self, false)) { + check(EXPR(self)); + Ctx__s_emit_top(ctx()); + Ctx__emit_(ctx(), OP_RAISE, BC_NOARG, kw_line); + } else { + int iblock = ctx()->curr_iblock; + CodeBlock* blocks = (CodeBlock*)ctx()->co->blocks.data; + if(blocks[iblock].type != CodeBlockType_EXCEPT) { + return SyntaxError(self, + "raise without exception is only allowed in except block"); + } + Ctx__emit_(ctx(), OP_RE_RAISE, BC_NOARG, kw_line); + } consume_end_stmt(); } break; case TK_DEL: { diff --git a/src/debugger/core.c b/src/debugger/core.c index 8d48b63c..56432400 100644 --- a/src/debugger/core.c +++ b/src/debugger/core.c @@ -108,22 +108,15 @@ void c11_debugger_exception_on_trace(py_Ref exc) { BaseException* ud = py_touserdata(exc); c11_vector* stacktrace = &ud->stacktrace; const char* name = py_tpname(exc->type); - const char* message; - bool ok = py_str(exc); - if(!ok || !py_isstr(py_retval())) { - message = ""; - } else { - message = c11_strdup(py_tostr(py_retval())); - } + const char* message = safe_stringify_exception(exc); debugger.exception_stacktrace = stacktrace; debugger.isexceptionmode = true; debugger.current_excname = name; debugger.current_excmessage = message; clear_structures(); - } -const char* c11_debugger_excinfo(const char ** message){ +const char* c11_debugger_excinfo(const char** message) { *message = debugger.current_excmessage; return debugger.current_excname; } @@ -183,7 +176,8 @@ bool c11_debugger_path_equal(const char* path1, const char* path2) { } C11_STOP_REASON c11_debugger_should_pause() { - if(debugger.current_event == TRACE_EVENT_POP && !debugger.isexceptionmode) return C11_DEBUGGER_NOSTOP; + if(debugger.current_event == TRACE_EVENT_POP && !debugger.isexceptionmode) + return C11_DEBUGGER_NOSTOP; C11_STOP_REASON pause_resaon = C11_DEBUGGER_NOSTOP; int is_out = debugger.curr_stack_depth <= debugger.pause_allowed_depth; int is_new_line = debugger.current_line != debugger.step_line; @@ -399,4 +393,4 @@ bool c11_debugger_unfold_var(int var_id, c11_sbuf* buffer) { #undef python_vars -#endif // PK_ENABLE_OS +#endif // PK_ENABLE_OS diff --git a/src/interpreter/ceval.c b/src/interpreter/ceval.c index b0734801..38e55d3d 100644 --- a/src/interpreter/ceval.c +++ b/src/interpreter/ceval.c @@ -13,9 +13,6 @@ static bool stack_format_object(VM* self, c11_sv spec); -#define CHECK_RETURN_FROM_EXCEPT_OR_FINALLY() \ - if(self->is_curr_exc_handled) py_clearexc(NULL) - #define DISPATCH() \ do { \ frame->ip++; \ @@ -787,7 +784,6 @@ __NEXT_STEP: DISPATCH(); } case OP_RETURN_VALUE: { - CHECK_RETURN_FROM_EXCEPT_OR_FINALLY(); if(byte.arg == BC_NOARG) { self->last_retval = POPX(); } else { @@ -804,7 +800,6 @@ __NEXT_STEP: DISPATCH(); } case OP_YIELD_VALUE: { - CHECK_RETURN_FROM_EXCEPT_OR_FINALLY(); if(byte.arg == 1) { py_newnone(py_retval()); } else { @@ -814,7 +809,6 @@ __NEXT_STEP: return RES_YIELD; } case OP_FOR_ITER_YIELD_VALUE: { - CHECK_RETURN_FROM_EXCEPT_OR_FINALLY(); int res = py_next(TOP()); if(res == -1) goto __ERROR; if(res) { @@ -1135,16 +1129,27 @@ __NEXT_STEP: DISPATCH(); } /////////// - case OP_TRY_ENTER: { - Frame__set_unwind_target(frame, SP()); + case OP_BEGIN_TRY: { + Frame__begin_try(frame, SP()); + DISPATCH(); + } + case OP_END_TRY: { + c11_vector__pop(&frame->exc_stack); DISPATCH(); } case OP_EXCEPTION_MATCH: { if(!py_checktype(TOP(), tp_type)) goto __ERROR; - bool ok = py_isinstance(&self->curr_exception, py_totype(TOP())); + bool ok = py_isinstance(&self->unhandled_exc, py_totype(TOP())); py_newbool(TOP(), ok); DISPATCH(); } + case OP_HANDLE_EXCEPTION: { + FrameExcInfo* info = Frame__top_exc_info(frame); + assert(info != NULL && py_isnil(&info->exc)); + info->exc = self->unhandled_exc; + py_newnil(&self->unhandled_exc); + DISPATCH(); + } case OP_RAISE: { // [exception] if(py_istype(TOP(), tp_type)) { @@ -1169,25 +1174,18 @@ __NEXT_STEP: goto __ERROR; } case OP_RE_RAISE: { - if(self->curr_exception.type) { - assert(!self->is_curr_exc_handled); - goto __ERROR_RE_RAISE; + if(py_isnil(&self->unhandled_exc)) { + FrameExcInfo* info = Frame__top_exc_info(frame); + assert(info != NULL && !py_isnil(&info->exc)); + self->unhandled_exc = info->exc; } - DISPATCH(); + c11_vector__pop(&frame->exc_stack); + goto __ERROR_RE_RAISE; } case OP_PUSH_EXCEPTION: { - assert(self->curr_exception.type); - PUSH(&self->curr_exception); - DISPATCH(); - } - case OP_BEGIN_EXC_HANDLING: { - assert(self->curr_exception.type); - self->is_curr_exc_handled = true; - DISPATCH(); - } - case OP_END_EXC_HANDLING: { - assert(self->curr_exception.type); - py_clearexc(NULL); + FrameExcInfo* info = Frame__top_exc_info(frame); + assert(info != NULL && !py_isnil(&info->exc)); + PUSH(&info->exc); DISPATCH(); } ////////////////// @@ -1203,16 +1201,19 @@ __NEXT_STEP: c11__unreachable(); __ERROR: + assert(!py_isnil(&self->unhandled_exc)); py_BaseException__stpush(frame, - &self->curr_exception, + &self->unhandled_exc, frame->co->src, Frame__lineno(frame), !frame->is_locals_special ? frame->co->name->data : NULL); __ERROR_RE_RAISE: do { + self->curr_class = NULL; + self->curr_decl_based_function = NULL; } while(0); - int target = Frame__prepare_jump_exception_handler(frame, &self->stack); + int target = Frame__goto_exception_handler(frame, &self->stack, &self->unhandled_exc); if(target >= 0) { // 1. Exception can be handled inside the current frame DISPATCH_JUMP_ABSOLUTE(target); diff --git a/src/interpreter/frame.c b/src/interpreter/frame.c index 6a70054c..a3bdb367 100644 --- a/src/interpreter/frame.c +++ b/src/interpreter/frame.c @@ -7,7 +7,6 @@ #include #include - void FastLocals__to_dict(py_TValue* locals, const CodeObject* co) { py_StackRef dict = py_pushtmp(); py_newdict(dict); @@ -32,16 +31,6 @@ NameDict* FastLocals__to_namedict(py_TValue* locals, const CodeObject* co) { return dict; } -UnwindTarget* UnwindTarget__new(UnwindTarget* next, int iblock, int offset) { - UnwindTarget* self = PK_MALLOC(sizeof(UnwindTarget)); - self->next = next; - self->iblock = iblock; - self->offset = offset; - return self; -} - -void UnwindTarget__delete(UnwindTarget* self) { PK_FREE(self); } - py_Frame* Frame__new(const CodeObject* co, py_StackRef p0, py_GlobalRef module, @@ -62,57 +51,41 @@ py_Frame* Frame__new(const CodeObject* co, self->locals = locals; self->is_locals_special = is_locals_special; self->ip = -1; - self->uw_list = NULL; + c11_vector__ctor(&self->exc_stack, sizeof(FrameExcInfo)); return self; } void Frame__delete(py_Frame* self) { - while(self->uw_list) { - UnwindTarget* p = self->uw_list; - self->uw_list = p->next; - UnwindTarget__delete(p); - } + c11_vector__dtor(&self->exc_stack); FixedMemoryPool__dealloc(&pk_current_vm->pool_frame, self); } -int Frame__prepare_jump_exception_handler(py_Frame* self, ValueStack* _s) { - // try to find a parent try block - int iblock = Frame__iblock(self); - while(iblock >= 0) { - CodeBlock* block = c11__at(CodeBlock, &self->co->blocks, iblock); - if(block->type == CodeBlockType_TRY) break; - iblock = block->parent; - } - if(iblock < 0) return -1; - UnwindTarget* uw = Frame__find_unwind_target(self, iblock); - _s->sp = (self->p0 + uw->offset); // unwind the stack - return c11__at(CodeBlock, &self->co->blocks, iblock)->end; +int Frame__goto_exception_handler(py_Frame* self, ValueStack* value_stack, py_Ref exc) { + if(self->exc_stack.length == 0) return -1; + FrameExcInfo* info = &c11_vector__back(FrameExcInfo, &self->exc_stack); + value_stack->sp = (self->p0 + info->offset); // unwind the stack + return c11__at(CodeBlock, &self->co->blocks, info->iblock)->end; } -UnwindTarget* Frame__find_unwind_target(py_Frame* self, int iblock) { - UnwindTarget* uw; - for(uw = self->uw_list; uw; uw = uw->next) { - if(uw->iblock == iblock) return uw; - } - return NULL; -} - -void Frame__set_unwind_target(py_Frame* self, py_TValue* sp) { +void Frame__begin_try(py_Frame* self, py_TValue* sp) { int iblock = Frame__iblock(self); assert(iblock >= 0); - UnwindTarget* existing = Frame__find_unwind_target(self, iblock); - if(existing) { - existing->offset = sp - self->p0; - } else { - UnwindTarget* prev = self->uw_list; - self->uw_list = UnwindTarget__new(prev, iblock, sp - self->p0); - } + FrameExcInfo* info = c11_vector__emplace(&self->exc_stack); + info->iblock = iblock; + info->offset = (int)(sp - self->p0); + py_newnil(&info->exc); +} + +FrameExcInfo* Frame__top_exc_info(py_Frame* self) { + if(self->exc_stack.length == 0) return NULL; + return &c11_vector__back(FrameExcInfo, &self->exc_stack); } void Frame__gc_mark(py_Frame* self, c11_vector* p_stack) { pk__mark_value(self->globals); if(self->is_locals_special) pk__mark_value(self->locals); CodeObject__gc_mark(self->co, p_stack); + c11__foreach(FrameExcInfo, &self->exc_stack, info) { pk__mark_value(&info->exc); } } int Frame__lineno(const py_Frame* self) { diff --git a/src/interpreter/vm.c b/src/interpreter/vm.c index bc9f5979..0b25a015 100644 --- a/src/interpreter/vm.c +++ b/src/interpreter/vm.c @@ -93,13 +93,11 @@ void VM__ctor(VM* self) { self->callbacks.getchr = pk_default_getchr; self->last_retval = *py_NIL(); - self->curr_exception = *py_NIL(); + self->unhandled_exc = *py_NIL(); self->recursion_depth = 0; self->max_recursion_depth = 1000; - self->is_curr_exc_handled = false; - self->ctx = NULL; self->curr_class = NULL; self->curr_decl_based_function = NULL; @@ -463,6 +461,11 @@ static bool FrameResult VM__vectorcall(VM* self, uint16_t argc, uint16_t kwargc, bool opcall) { #ifndef NDEBUG pk_print_stack(self, self->top_frame, (Bytecode){0}); + + if(py_checkexc()) { + const char* name = py_tpname(self->unhandled_exc.type); + c11__abort("unhandled exception `%s` was set!", name); + } #endif py_Ref p1 = self->stack.sp - kwargc * 2; @@ -667,7 +670,7 @@ void ManagedHeap__mark(ManagedHeap* self) { } // mark vm's registers pk__mark_value(&vm->last_retval); - pk__mark_value(&vm->curr_exception); + pk__mark_value(&vm->unhandled_exc); for(int i = 0; i < c11__count_array(vm->reg); i++) { pk__mark_value(&vm->reg[i]); } diff --git a/src/modules/dis.c b/src/modules/dis.c index 213c4805..175ae2db 100644 --- a/src/modules/dis.c +++ b/src/modules/dis.c @@ -57,7 +57,6 @@ static bool disassemble(CodeObject* co) { c11_sbuf__write_int(&ss, byte.arg); switch(byte.op) { - // TODO: see `dis.py` there is a memory issue case OP_LOAD_CONST: { py_Ref value = c11__at(py_TValue, &co->consts, byte.arg); if(py_repr(value)) { diff --git a/src/modules/traceback.c b/src/modules/traceback.c index 5fec7afb..0c37c28d 100644 --- a/src/modules/traceback.c +++ b/src/modules/traceback.c @@ -1,14 +1,20 @@ #include "pocketpy/pocketpy.h" +#include "pocketpy/objects/exception.h" +#include "pocketpy/interpreter/vm.h" static bool traceback_format_exc(int argc, py_Ref argv) { PY_CHECK_ARGC(0); - char* s = py_formatexc(); - if(!s) { - py_newnone(py_retval()); - } else { - py_newstr(py_retval(), s); - PK_FREE(s); + VM* vm = pk_current_vm; + if(vm->top_frame) { + FrameExcInfo* info = Frame__top_exc_info(vm->top_frame); + if(info && !py_isnil(&info->exc)) { + char* res = formatexc_internal(&info->exc); + py_newstr(py_retval(), res); + PK_FREE(res); + return true; + } } + py_newnone(py_retval()); return true; } diff --git a/src/public/exec.c b/src/public/exec.c index 0df4537d..6a7f9a2a 100644 --- a/src/public/exec.c +++ b/src/public/exec.c @@ -22,7 +22,7 @@ bool _py_compile(CodeObject* out, Error* err = pk_compile(src, out); if(err) { py_exception(tp_SyntaxError, err->msg); - py_BaseException__stpush(NULL, &vm->curr_exception, err->src, err->lineno, NULL); + py_BaseException__stpush(NULL, &vm->unhandled_exc, err->src, err->lineno, NULL); PK_DECREF(src); PK_DECREF(err->src); diff --git a/src/public/internal.c b/src/public/internal.c index 82a5a2b2..832e30e7 100644 --- a/src/public/internal.c +++ b/src/public/internal.c @@ -152,15 +152,17 @@ bool py_call(py_Ref f, int argc, py_Ref argv) { #ifndef NDEBUG bool py_callcfunc(py_CFunction f, int argc, py_Ref argv) { + if(py_checkexc()) { + const char* name = py_tpname(pk_current_vm->unhandled_exc.type); + c11__abort("unhandled exception `%s` was set!", name); + } py_StackRef p0 = py_peek(0); // NOTE: sometimes users are using `py_retval()` to pass `argv` // It will be reset to `nil` and cause an exception py_newnil(py_retval()); bool ok = f(argc, argv); if(!ok) { - if(!py_checkexc(true)) { - c11__abort("py_CFunction returns `false` but no exception is set!"); - } + if(!py_checkexc()) { c11__abort("py_CFunction returns `false` but no exception is set!"); } return false; } if(py_peek(0) != p0) { @@ -170,8 +172,8 @@ bool py_callcfunc(py_CFunction f, int argc, py_Ref argv) { c11__abort( "py_CFunction returns nothing! Did you forget to call `py_newnone(py_retval())`?"); } - if(py_checkexc(true)) { - const char* name = py_tpname(pk_current_vm->curr_exception.type); + if(py_checkexc()) { + const char* name = py_tpname(pk_current_vm->unhandled_exc.type); c11__abort("py_CFunction returns `true`, but `%s` was set!", name); } return true; diff --git a/src/public/py_exception.c b/src/public/py_exception.c index 4fb0d117..3b97eac8 100644 --- a/src/public/py_exception.c +++ b/src/public/py_exception.c @@ -142,32 +142,22 @@ py_Type pk_StopIteration__register() { } ////////////////////////////////////////////////// -bool py_checkexc(bool ignore_handled) { +bool py_checkexc() { VM* vm = pk_current_vm; - if(ignore_handled && vm->is_curr_exc_handled) return false; - return !py_isnil(&vm->curr_exception); + return !py_isnil(&vm->unhandled_exc); } bool py_matchexc(py_Type type) { VM* vm = pk_current_vm; - if(vm->is_curr_exc_handled) return false; - if(py_isnil(&vm->curr_exception)) return false; - bool ok = py_issubclass(vm->curr_exception.type, type); - if(ok) { - // if match, then the exception is handled - vm->is_curr_exc_handled = true; - vm->last_retval = vm->curr_exception; - } + if(py_isnil(&vm->unhandled_exc)) return false; + bool ok = py_issubclass(vm->unhandled_exc.type, type); + if(ok) vm->last_retval = vm->unhandled_exc; return ok; } void py_clearexc(py_StackRef p0) { VM* vm = pk_current_vm; - vm->curr_exception = *py_NIL(); - vm->is_curr_exc_handled = false; - /* Don't clear this, because StopIteration() may corrupt the class definition */ - // vm->curr_class = NULL; - vm->curr_decl_based_function = NULL; + py_newnil(&vm->unhandled_exc); if(p0) vm->stack.sp = p0; } @@ -187,17 +177,37 @@ static void c11_sbuf__write_exc(c11_sbuf* self, py_Ref exc) { } const char* name = py_tpname(exc->type); - const char* message; - bool ok = py_str(exc); - if(!ok || !py_isstr(py_retval())) { - message = ""; - } else { - message = py_tostr(py_retval()); - } + char* message = safe_stringify_exception(exc); c11_sbuf__write_cstr(self, name); c11_sbuf__write_cstr(self, ": "); c11_sbuf__write_cstr(self, message); + + PK_FREE(message); +} + +char* safe_stringify_exception(py_Ref exc) { + VM* vm = pk_current_vm; + + const char* message = ""; + + py_Ref tmp = py_pushtmp(); + py_Ref old_unhandled_exc = py_pushtmp(); + *tmp = *exc; + *old_unhandled_exc = vm->unhandled_exc; + py_newnil(&vm->unhandled_exc); + + py_StackRef p0 = py_peek(0); + bool ok = py_str(tmp); + if(ok) { + if(py_isstr(py_retval())) message = py_tostr(py_retval()); + } else { + py_clearexc(p0); + } + + vm->unhandled_exc = *old_unhandled_exc; + py_shrink(2); + return c11_strdup(message); } void py_printexc() { @@ -210,24 +220,29 @@ void py_printexc() { char* py_formatexc() { VM* vm = pk_current_vm; - if(py_isnil(&vm->curr_exception)) return NULL; + if(py_isnil(&vm->unhandled_exc)) return NULL; + char* res = formatexc_internal(&vm->unhandled_exc); + if(py_debugger_isattached()) py_debugger_exceptionbreakpoint(&vm->unhandled_exc); + return res; +} - // when you call `py_formatexc()`, you are handling the exception - vm->is_curr_exc_handled = true; +char* formatexc_internal(py_Ref exc) { + c11__rtassert(exc != NULL); + c11__rtassert(py_issubclass(exc->type, tp_BaseException)); c11_sbuf ss; c11_sbuf__ctor(&ss); - BaseException* ud = py_touserdata(&vm->curr_exception); + BaseException* ud = py_touserdata(exc); py_Ref inner = &ud->inner_exc; if(py_isnil(inner)) { - c11_sbuf__write_exc(&ss, &vm->curr_exception); + c11_sbuf__write_exc(&ss, exc); } else { c11_sbuf__write_exc(&ss, inner); c11_sbuf__write_cstr( &ss, "\n\nDuring handling of the above exception, another exception occurred:\n\n"); - c11_sbuf__write_exc(&ss, &vm->curr_exception); + c11_sbuf__write_exc(&ss, exc); } c11_string* res = c11_sbuf__submit(&ss); @@ -235,15 +250,13 @@ char* py_formatexc() { memcpy(dup, res->data, res->size); dup[res->size] = '\0'; c11_string__delete(res); - - if(py_debugger_isattached()) py_debugger_exceptionbreakpoint(&vm->curr_exception); return dup; } bool py_exception(py_Type type, const char* fmt, ...) { #ifndef NDEBUG - if(py_checkexc(true)) { - const char* name = py_tpname(pk_current_vm->curr_exception.type); + if(py_checkexc()) { + const char* name = py_tpname(pk_current_vm->unhandled_exc.type); c11__abort("py_exception(): `%s` was already set!", name); } #endif @@ -268,12 +281,15 @@ bool py_exception(py_Type type, const char* fmt, ...) { bool py_raise(py_Ref exc) { assert(py_isinstance(exc, tp_BaseException)); VM* vm = pk_current_vm; - if(!py_isnil(&vm->curr_exception)) { - BaseException* ud = py_touserdata(&vm->curr_exception); - ud->inner_exc = vm->curr_exception; + if(vm->top_frame) { + FrameExcInfo* info = Frame__top_exc_info(vm->top_frame); + if(info && !py_isnil(&info->exc)) { + BaseException* ud = py_touserdata(exc); + ud->inner_exc = info->exc; + } } - vm->curr_exception = *exc; - vm->is_curr_exc_handled = false; + assert(py_isnil(&vm->unhandled_exc)); + vm->unhandled_exc = *exc; return false; } diff --git a/src/public/py_ops.c b/src/public/py_ops.c index 8a3fcaf5..16e63a83 100644 --- a/src/public/py_ops.c +++ b/src/public/py_ops.c @@ -111,8 +111,8 @@ int py_next(py_Ref val) { break; } } - if(vm->curr_exception.type == tp_StopIteration) { - vm->last_retval = vm->curr_exception; + if(vm->unhandled_exc.type == tp_StopIteration) { + vm->last_retval = vm->unhandled_exc; py_clearexc(NULL); return 0; } diff --git a/src2/main.c b/src2/main.c index 3477c4c8..2b9b9256 100644 --- a/src2/main.c +++ b/src2/main.c @@ -112,7 +112,7 @@ int main(int argc, char** argv) { } } - int code = py_checkexc(false) ? 1 : 0; + int code = py_checkexc() ? 1 : 0; py_finalize(); if(debug) py_debugger_exit(code); diff --git a/tests/28_exception.py b/tests/28_exception.py index 5fdd20f8..ba0f21ab 100644 --- a/tests/28_exception.py +++ b/tests/28_exception.py @@ -153,11 +153,12 @@ def g(): except KeyError: pass -if 0: - try: - raise IndexError - except IndexError: - g() +try: + raise IndexError +except IndexError: + g() + +a = 1 + 2 """ # finally, only