blueloveTH 6 月之前
父節點
當前提交
c658b5a7a4

+ 2 - 2
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

+ 9 - 13
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);

+ 1 - 3
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
 

+ 2 - 0
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);

+ 8 - 11
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 <T> 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);
 

+ 3 - 3
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)
 /**************************/

+ 28 - 17
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: {

+ 5 - 11
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 = "<exception str() failed>";
-    } 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

+ 28 - 27
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);

+ 18 - 45
src/interpreter/frame.c

@@ -7,7 +7,6 @@
 #include <stdbool.h>
 #include <assert.h>
 
-
 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) {

+ 7 - 4
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]);
     }

+ 0 - 1
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)) {

+ 12 - 6
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;
 }
 

+ 1 - 1
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);

+ 7 - 5
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;

+ 54 - 38
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 = "<exception str() failed>";
-    } 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 = "<exception str() failed>";
+
+    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;
 }
 

+ 2 - 2
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;
     }

+ 1 - 1
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);

+ 6 - 5
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