Merge branch 'pocketpy:main' into gsoc-2025-debugger

This commit is contained in:
lightovernight 2025-08-31 16:03:02 +08:00 committed by GitHub
commit 28c079cd8c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
23 changed files with 271 additions and 253 deletions

View File

@ -159,7 +159,7 @@ __ERROR:
| F-String | `f'value is {x}'` | ✅ | | F-String | `f'value is {x}'` | ✅ |
| Unpacking | `a, b = 1, 2` | ✅ | | Unpacking | `a, b = 1, 2` | ✅ |
| Star Unpacking | `a, *b = [1, 2, 3]` | ✅ | | Star Unpacking | `a, *b = [1, 2, 3]` | ✅ |
| Exception | `raise/try..catch..finally` | ✅ | | Exception | `raise/try..except..` | ✅ |
| Dynamic Code | `eval()/exec()` | ✅ | | Dynamic Code | `eval()/exec()` | ✅ |
| Reflection | `hasattr()/getattr()/setattr()` | ✅ | | Reflection | `hasattr()/getattr()/setattr()` | ✅ |
| Import | `import/from..import` | ✅ | | Import | `import/from..import` | ✅ |

View File

@ -20,7 +20,7 @@ The following table shows the basic features of pkpy with respect to [cpython](h
| F-String | `f'value is {x}'` | ✅ | | F-String | `f'value is {x}'` | ✅ |
| Unpacking | `a, b = 1, 2` | ✅ | | Unpacking | `a, b = 1, 2` | ✅ |
| Star Unpacking | `a, *b = [1, 2, 3]` | ✅ | | Star Unpacking | `a, *b = [1, 2, 3]` | ✅ |
| Exception | `raise/try..catch..finally` | ✅ | | Exception | `raise/try..except..` | ✅ |
| Dynamic Code | `eval()/exec()` | ✅ | | Dynamic Code | `eval()/exec()` | ✅ |
| Reflection | `hasattr()/getattr()/setattr()` | ✅ | | Reflection | `hasattr()/getattr()/setattr()` | ✅ |
| Import | `import/from..import` | ✅ | | Import | `import/from..import` | ✅ |

View File

@ -1,10 +1,10 @@
#pragma once #pragma once
// clang-format off // clang-format off
#define PK_VERSION "2.1.1" #define PK_VERSION "2.1.2"
#define PK_VERSION_MAJOR 2 #define PK_VERSION_MAJOR 2
#define PK_VERSION_MINOR 1 #define PK_VERSION_MINOR 1
#define PK_VERSION_PATCH 1 #define PK_VERSION_PATCH 2
/*************** feature settings ***************/ /*************** feature settings ***************/
#ifndef PK_ENABLE_OS // can be overridden by cmake #ifndef PK_ENABLE_OS // can be overridden by cmake

View File

@ -11,20 +11,14 @@ typedef struct ValueStack {
py_TValue* sp; py_TValue* sp;
py_TValue* end; py_TValue* end;
// We allocate extra places to keep `_sp` valid to detect stack overflow // We allocate extra places to keep `_sp` valid to detect stack overflow
py_TValue begin[PK_VM_STACK_SIZE + PK_MAX_CO_VARNAMES * 2]; py_TValue begin[PK_VM_STACK_SIZE + PK_MAX_CO_VARNAMES];
} ValueStack; } ValueStack;
void ValueStack__ctor(ValueStack* self); typedef struct FrameExcInfo {
void ValueStack__dtor(ValueStack* self); int iblock; // try block index
int offset; // stack offset from p0
typedef struct UnwindTarget { py_TValue exc; // handled exception
struct UnwindTarget* next; } FrameExcInfo;
int iblock;
int offset;
} UnwindTarget;
UnwindTarget* UnwindTarget__new(UnwindTarget* next, int iblock, int offset);
void UnwindTarget__delete(UnwindTarget* self);
typedef struct py_Frame { typedef struct py_Frame {
struct py_Frame* f_back; struct py_Frame* f_back;
@ -35,7 +29,7 @@ typedef struct py_Frame {
py_Ref locals; py_Ref locals;
bool is_locals_special; bool is_locals_special;
int ip; int ip;
UnwindTarget* uw_list; c11_vector /*T=FrameExcInfo*/ exc_stack;
} py_Frame; } py_Frame;
typedef struct SourceLocation { typedef struct SourceLocation {
@ -61,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_Ref Frame__getclosure(py_Frame* self, py_Name name);
py_StackRef Frame__getlocal_noproxy(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*); int Frame__goto_exception_handler(py_Frame* self, ValueStack*, py_Ref);
void Frame__begin_try(py_Frame* self, py_TValue* sp);
UnwindTarget* Frame__find_unwind_target(py_Frame* self, int iblock); FrameExcInfo* Frame__top_exc_info(py_Frame* self);
void Frame__set_unwind_target(py_Frame* self, py_TValue* sp);
void Frame__gc_mark(py_Frame* self, c11_vector* p_stack); void Frame__gc_mark(py_Frame* self, c11_vector* p_stack);
SourceLocation Frame__source_location(py_Frame* self); SourceLocation Frame__source_location(py_Frame* self);

View File

@ -53,13 +53,11 @@ typedef struct VM {
py_Callbacks callbacks; py_Callbacks callbacks;
py_TValue last_retval; py_TValue last_retval;
py_TValue curr_exception; py_TValue unhandled_exc;
int recursion_depth; int recursion_depth;
int max_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 py_TValue reg[8]; // users' registers
void* ctx; // user-defined context void* ctx; // user-defined context

View File

@ -35,7 +35,6 @@ typedef enum CodeBlockType {
CodeBlockType_WITH, CodeBlockType_WITH,
/* context blocks (flag-based) */ /* context blocks (flag-based) */
CodeBlockType_EXCEPT, CodeBlockType_EXCEPT,
CodeBlockType_FINALLY,
} CodeBlockType; } CodeBlockType;
typedef enum Opcode { typedef enum Opcode {

View File

@ -17,3 +17,5 @@ typedef struct BaseException {
c11_vector /*T=BaseExceptionFrame*/ stacktrace; c11_vector /*T=BaseExceptionFrame*/ stacktrace;
} BaseException; } BaseException;
char* safe_stringify_exception(py_Ref exc);
char* formatexc_internal(py_Ref exc);

View File

@ -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; PK_API bool py_exception(py_Type type, const char* fmt, ...) PY_RAISE;
/// Raise an exception object. Always return false. /// Raise an exception object. Always return false.
PK_API bool py_raise(py_Ref) PY_RAISE; PK_API bool py_raise(py_Ref) PY_RAISE;
/// Print the current exception. /// Print the unhandled exception.
/// The exception will be set as handled.
PK_API void py_printexc(); PK_API void py_printexc();
/// Format the current exception and return a null-terminated string. /// Format the unhandled exception and return a null-terminated string.
/// The result should be freed by the caller. /// The returned string should be freed by the caller.
/// The exception will be set as handled.
PK_API char* py_formatexc(); PK_API char* py_formatexc();
/// Check if an exception is raised. /// Check if there is an unhandled exception.
PK_API bool py_checkexc(bool ignore_handled); PK_API bool py_checkexc();
/// Check if the exception is an instance of the given type. /// Check if the unhandled 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()`.
/// If match, the exception will be stored in `py_retval()` as handled.
PK_API bool py_matchexc(py_Type type) PY_RETURN; 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. /// @param p0 the unwinding point. Use `NULL` if not needed.
PK_API void py_clearexc(py_StackRef p0); PK_API void py_clearexc(py_StackRef p0);

View File

@ -116,16 +116,14 @@ OPCODE(ADD_CLASS_ANNOTATION)
OPCODE(WITH_ENTER) OPCODE(WITH_ENTER)
OPCODE(WITH_EXIT) OPCODE(WITH_EXIT)
/**************************/ /**************************/
OPCODE(TRY_ENTER) OPCODE(BEGIN_TRY)
OPCODE(END_TRY)
OPCODE(EXCEPTION_MATCH) OPCODE(EXCEPTION_MATCH)
OPCODE(HANDLE_EXCEPTION)
OPCODE(RAISE) OPCODE(RAISE)
OPCODE(RAISE_ASSERT) OPCODE(RAISE_ASSERT)
OPCODE(RE_RAISE) OPCODE(RE_RAISE)
OPCODE(PUSH_EXCEPTION) OPCODE(PUSH_EXCEPTION)
OPCODE(BEGIN_EXC_HANDLING)
OPCODE(END_EXC_HANDLING)
OPCODE(BEGIN_FINALLY)
OPCODE(END_FINALLY)
/**************************/ /**************************/
OPCODE(FORMAT_STRING) OPCODE(FORMAT_STRING)
/**************************/ /**************************/

View File

@ -1132,12 +1132,12 @@ static int Ctx__prepare_loop_divert(Ctx* self, int line, bool is_break) {
Ctx__emit_(self, OP_POP_TOP, BC_NOARG, line); Ctx__emit_(self, OP_POP_TOP, BC_NOARG, line);
break; break;
} }
case CodeBlockType_EXCEPT: { case CodeBlockType_TRY: {
Ctx__emit_(self, OP_END_EXC_HANDLING, 1, line); Ctx__emit_(self, OP_END_TRY, BC_NOARG, line);
break; break;
} }
case CodeBlockType_FINALLY: { case CodeBlockType_EXCEPT: {
Ctx__emit_(self, OP_END_FINALLY, 1, line); Ctx__emit_(self, OP_END_TRY, BC_NOARG, line);
break; break;
} }
default: break; default: break;
@ -1919,9 +1919,11 @@ static Error* exprCompileTimeCall(Compiler* self, py_ItemRef func, int line) {
} while(match(TK_COMMA)); } while(match(TK_COMMA));
consume(TK_RPAREN); consume(TK_RPAREN);
py_StackRef p0 = py_peek(0);
bool ok = py_vectorcall(argc, kwargc); bool ok = py_vectorcall(argc, kwargc);
if(!ok) { if(!ok) {
char* msg = py_formatexc(); char* msg = py_formatexc();
py_clearexc(p0);
err = SyntaxError(self, "compile-time call error:\n%s", msg); err = SyntaxError(self, "compile-time call error:\n%s", msg);
PK_FREE(msg); PK_FREE(msg);
return err; return err;
@ -2000,7 +2002,7 @@ static Error* exprSlice0(Compiler* self) {
check(EXPR(self)); check(EXPR(self));
slice->step = Ctx__s_popx(ctx()); slice->step = Ctx__s_popx(ctx());
} // else :: } // else ::
} // else : } // else :
return NULL; return NULL;
} }
@ -2627,8 +2629,9 @@ static Error* compile_try_except(Compiler* self) {
int patches_length = 0; int patches_length = 0;
Ctx__enter_block(ctx(), CodeBlockType_TRY); 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)); 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 // 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, /* If finally is present, it specifies a cleanup handler. The try clause is executed,
@ -2644,23 +2647,10 @@ static Error* compile_try_except(Compiler* self) {
// A return, break, continue in try/except block will make the finally block not executed // A return, break, continue in try/except block will make the finally block not executed
bool has_finally = curr()->type == TK_FINALLY; bool has_finally = curr()->type == TK_FINALLY;
if(!has_finally) { 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());
if(has_finally) { patches[patches_length++] = Ctx__emit_(ctx(), OP_JUMP_FORWARD, BC_NOARG, BC_KEEPLINE);
consume(TK_FINALLY); Ctx__exit_block(ctx());
Ctx__emit_(ctx(), OP_BEGIN_FINALLY, BC_NOARG, prev()->line);
// finally only, no except block
Ctx__enter_block(ctx(), CodeBlockType_FINALLY);
check(compile_block_body(self));
Ctx__exit_block(ctx());
Ctx__emit_(ctx(), OP_END_FINALLY, BC_NOARG, BC_KEEPLINE);
// re-raise if needed
Ctx__emit_(ctx(), OP_RE_RAISE, BC_NOARG, BC_KEEPLINE);
return NULL;
}
do { do {
if(patches_length == 8) { if(patches_length == 8) {
@ -2684,7 +2674,7 @@ static Error* compile_try_except(Compiler* self) {
} }
int patch = Ctx__emit_(ctx(), OP_POP_JUMP_IF_FALSE, BC_NOARG, BC_KEEPLINE); int patch = Ctx__emit_(ctx(), OP_POP_JUMP_IF_FALSE, BC_NOARG, BC_KEEPLINE);
// on match // 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) { if(as_name) {
Ctx__emit_(ctx(), OP_PUSH_EXCEPTION, BC_NOARG, BC_KEEPLINE); Ctx__emit_(ctx(), OP_PUSH_EXCEPTION, BC_NOARG, BC_KEEPLINE);
Ctx__emit_store_name(ctx(), name_scope(self), as_name, BC_KEEPLINE); Ctx__emit_store_name(ctx(), name_scope(self), as_name, BC_KEEPLINE);
@ -2692,27 +2682,20 @@ static Error* compile_try_except(Compiler* self) {
Ctx__enter_block(ctx(), CodeBlockType_EXCEPT); Ctx__enter_block(ctx(), CodeBlockType_EXCEPT);
check(compile_block_body(self)); check(compile_block_body(self));
Ctx__exit_block(ctx()); 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); patches[patches_length++] = Ctx__emit_(ctx(), OP_JUMP_FORWARD, BC_NOARG, BC_KEEPLINE);
Ctx__patch_jump(ctx(), patch); Ctx__patch_jump(ctx(), patch);
} while(curr()->type == TK_EXCEPT); } while(curr()->type == TK_EXCEPT);
// no match, re-raise // no match, re-raise
// ... Ctx__emit_(ctx(), OP_RE_RAISE, BC_NOARG, BC_KEEPLINE);
// match one & handled, jump to the end // match one & handled, jump to the end
for(int i = 0; i < patches_length; i++) for(int i = 0; i < patches_length; i++) {
Ctx__patch_jump(ctx(), patches[i]); Ctx__patch_jump(ctx(), patches[i]);
if(match(TK_FINALLY)) {
Ctx__emit_(ctx(), OP_BEGIN_FINALLY, BC_NOARG, prev()->line);
Ctx__enter_block(ctx(), CodeBlockType_FINALLY);
check(compile_block_body(self));
Ctx__exit_block(ctx());
Ctx__emit_(ctx(), OP_END_FINALLY, BC_NOARG, BC_KEEPLINE);
} }
// 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; return NULL;
} }
@ -2815,9 +2798,19 @@ static Error* compile_stmt(Compiler* self) {
consume_end_stmt(); consume_end_stmt();
break; break;
case TK_RAISE: { case TK_RAISE: {
check(EXPR(self)); if(is_expression(self, false)) {
Ctx__s_emit_top(ctx()); check(EXPR(self));
Ctx__emit_(ctx(), OP_RAISE, BC_NOARG, kw_line); 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(); consume_end_stmt();
} break; } break;
case TK_DEL: { case TK_DEL: {

View File

@ -108,22 +108,15 @@ void c11_debugger_exception_on_trace(py_Ref exc) {
BaseException* ud = py_touserdata(exc); BaseException* ud = py_touserdata(exc);
c11_vector* stacktrace = &ud->stacktrace; c11_vector* stacktrace = &ud->stacktrace;
const char* name = py_tpname(exc->type); const char* name = py_tpname(exc->type);
const char* message; const char* message = safe_stringify_exception(exc);
bool ok = py_str(exc);
if(!ok || !py_isstr(py_retval())) {
message = "<exception str() failed>";
} else {
message = c11_strdup(py_tostr(py_retval()));
}
debugger.exception_stacktrace = stacktrace; debugger.exception_stacktrace = stacktrace;
debugger.isexceptionmode = true; debugger.isexceptionmode = true;
debugger.current_excname = name; debugger.current_excname = name;
debugger.current_excmessage = message; debugger.current_excmessage = message;
clear_structures(); clear_structures();
} }
const char* c11_debugger_excinfo(const char ** message){ const char* c11_debugger_excinfo(const char** message) {
*message = debugger.current_excmessage; *message = debugger.current_excmessage;
return debugger.current_excname; 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() { 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; C11_STOP_REASON pause_resaon = C11_DEBUGGER_NOSTOP;
int is_out = debugger.curr_stack_depth <= debugger.pause_allowed_depth; int is_out = debugger.curr_stack_depth <= debugger.pause_allowed_depth;
int is_new_line = debugger.current_line != debugger.step_line; 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 #undef python_vars
#endif // PK_ENABLE_OS #endif // PK_ENABLE_OS

View File

@ -13,9 +13,6 @@
static bool stack_format_object(VM* self, c11_sv spec); 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() \ #define DISPATCH() \
do { \ do { \
frame->ip++; \ frame->ip++; \
@ -787,7 +784,6 @@ __NEXT_STEP:
DISPATCH(); DISPATCH();
} }
case OP_RETURN_VALUE: { case OP_RETURN_VALUE: {
CHECK_RETURN_FROM_EXCEPT_OR_FINALLY();
if(byte.arg == BC_NOARG) { if(byte.arg == BC_NOARG) {
self->last_retval = POPX(); self->last_retval = POPX();
} else { } else {
@ -804,7 +800,6 @@ __NEXT_STEP:
DISPATCH(); DISPATCH();
} }
case OP_YIELD_VALUE: { case OP_YIELD_VALUE: {
CHECK_RETURN_FROM_EXCEPT_OR_FINALLY();
if(byte.arg == 1) { if(byte.arg == 1) {
py_newnone(py_retval()); py_newnone(py_retval());
} else { } else {
@ -814,7 +809,6 @@ __NEXT_STEP:
return RES_YIELD; return RES_YIELD;
} }
case OP_FOR_ITER_YIELD_VALUE: { case OP_FOR_ITER_YIELD_VALUE: {
CHECK_RETURN_FROM_EXCEPT_OR_FINALLY();
int res = py_next(TOP()); int res = py_next(TOP());
if(res == -1) goto __ERROR; if(res == -1) goto __ERROR;
if(res) { if(res) {
@ -1135,16 +1129,27 @@ __NEXT_STEP:
DISPATCH(); DISPATCH();
} }
/////////// ///////////
case OP_TRY_ENTER: { case OP_BEGIN_TRY: {
Frame__set_unwind_target(frame, SP()); Frame__begin_try(frame, SP());
DISPATCH();
}
case OP_END_TRY: {
c11_vector__pop(&frame->exc_stack);
DISPATCH(); DISPATCH();
} }
case OP_EXCEPTION_MATCH: { case OP_EXCEPTION_MATCH: {
if(!py_checktype(TOP(), tp_type)) goto __ERROR; 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); py_newbool(TOP(), ok);
DISPATCH(); 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: { case OP_RAISE: {
// [exception] // [exception]
if(py_istype(TOP(), tp_type)) { if(py_istype(TOP(), tp_type)) {
@ -1169,46 +1174,18 @@ __NEXT_STEP:
goto __ERROR; goto __ERROR;
} }
case OP_RE_RAISE: { case OP_RE_RAISE: {
if(self->curr_exception.type) { if(py_isnil(&self->unhandled_exc)) {
assert(!self->is_curr_exc_handled); FrameExcInfo* info = Frame__top_exc_info(frame);
goto __ERROR_RE_RAISE; 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: { case OP_PUSH_EXCEPTION: {
assert(self->curr_exception.type); FrameExcInfo* info = Frame__top_exc_info(frame);
PUSH(&self->curr_exception); assert(info != NULL && !py_isnil(&info->exc));
DISPATCH(); PUSH(&info->exc);
}
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);
DISPATCH();
}
case OP_BEGIN_FINALLY: {
if(self->curr_exception.type) {
assert(!self->is_curr_exc_handled);
// temporarily handle the exception if any
self->is_curr_exc_handled = true;
}
DISPATCH();
}
case OP_END_FINALLY: {
if(byte.arg == BC_NOARG) {
if(self->curr_exception.type) {
assert(self->is_curr_exc_handled);
// revert the exception handling if needed
self->is_curr_exc_handled = false;
}
} else {
// break or continue inside finally block
py_clearexc(NULL);
}
DISPATCH(); DISPATCH();
} }
////////////////// //////////////////
@ -1224,16 +1201,19 @@ __NEXT_STEP:
c11__unreachable(); c11__unreachable();
__ERROR: __ERROR:
assert(!py_isnil(&self->unhandled_exc));
py_BaseException__stpush(frame, py_BaseException__stpush(frame,
&self->curr_exception, &self->unhandled_exc,
frame->co->src, frame->co->src,
Frame__lineno(frame), Frame__lineno(frame),
!frame->is_locals_special ? frame->co->name->data : NULL); !frame->is_locals_special ? frame->co->name->data : NULL);
__ERROR_RE_RAISE: __ERROR_RE_RAISE:
do { do {
self->curr_class = NULL;
self->curr_decl_based_function = NULL;
} while(0); } 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) { if(target >= 0) {
// 1. Exception can be handled inside the current frame // 1. Exception can be handled inside the current frame
DISPATCH_JUMP_ABSOLUTE(target); DISPATCH_JUMP_ABSOLUTE(target);

View File

@ -7,13 +7,6 @@
#include <stdbool.h> #include <stdbool.h>
#include <assert.h> #include <assert.h>
void ValueStack__ctor(ValueStack* self) {
self->sp = self->begin;
self->end = self->begin + PK_VM_STACK_SIZE;
}
void ValueStack__dtor(ValueStack* self) { self->sp = self->begin; }
void FastLocals__to_dict(py_TValue* locals, const CodeObject* co) { void FastLocals__to_dict(py_TValue* locals, const CodeObject* co) {
py_StackRef dict = py_pushtmp(); py_StackRef dict = py_pushtmp();
py_newdict(dict); py_newdict(dict);
@ -38,16 +31,6 @@ NameDict* FastLocals__to_namedict(py_TValue* locals, const CodeObject* co) {
return dict; 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_Frame* Frame__new(const CodeObject* co,
py_StackRef p0, py_StackRef p0,
py_GlobalRef module, py_GlobalRef module,
@ -68,57 +51,47 @@ py_Frame* Frame__new(const CodeObject* co,
self->locals = locals; self->locals = locals;
self->is_locals_special = is_locals_special; self->is_locals_special = is_locals_special;
self->ip = -1; self->ip = -1;
self->uw_list = NULL; c11_vector__ctor(&self->exc_stack, sizeof(FrameExcInfo));
return self; return self;
} }
void Frame__delete(py_Frame* self) { void Frame__delete(py_Frame* self) {
while(self->uw_list) { c11_vector__dtor(&self->exc_stack);
UnwindTarget* p = self->uw_list;
self->uw_list = p->next;
UnwindTarget__delete(p);
}
FixedMemoryPool__dealloc(&pk_current_vm->pool_frame, self); FixedMemoryPool__dealloc(&pk_current_vm->pool_frame, self);
} }
int Frame__prepare_jump_exception_handler(py_Frame* self, ValueStack* _s) { int Frame__goto_exception_handler(py_Frame* self, ValueStack* value_stack, py_Ref exc) {
// try to find a parent try block FrameExcInfo* p = self->exc_stack.data;
int iblock = Frame__iblock(self); for(int i = self->exc_stack.length - 1; i >= 0; i--) {
while(iblock >= 0) { if(py_isnil(&p[i].exc)) {
CodeBlock* block = c11__at(CodeBlock, &self->co->blocks, iblock); value_stack->sp = (self->p0 + p[i].offset); // unwind the stack
if(block->type == CodeBlockType_TRY) break; return c11__at(CodeBlock, &self->co->blocks, p[i].iblock)->end;
iblock = block->parent; } else {
self->exc_stack.length--;
}
} }
if(iblock < 0) return -1; 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;
} }
UnwindTarget* Frame__find_unwind_target(py_Frame* self, int iblock) { void Frame__begin_try(py_Frame* self, py_TValue* sp) {
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) {
int iblock = Frame__iblock(self); int iblock = Frame__iblock(self);
assert(iblock >= 0); assert(iblock >= 0);
UnwindTarget* existing = Frame__find_unwind_target(self, iblock); FrameExcInfo* info = c11_vector__emplace(&self->exc_stack);
if(existing) { info->iblock = iblock;
existing->offset = sp - self->p0; info->offset = (int)(sp - self->p0);
} else { py_newnil(&info->exc);
UnwindTarget* prev = self->uw_list; }
self->uw_list = UnwindTarget__new(prev, iblock, sp - self->p0);
} 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) { void Frame__gc_mark(py_Frame* self, c11_vector* p_stack) {
pk__mark_value(self->globals); pk__mark_value(self->globals);
if(self->is_locals_special) pk__mark_value(self->locals); if(self->is_locals_special) pk__mark_value(self->locals);
CodeObject__gc_mark(self->co, p_stack); 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) { int Frame__lineno(const py_Frame* self) {

View File

@ -93,13 +93,11 @@ void VM__ctor(VM* self) {
self->callbacks.getchr = pk_default_getchr; self->callbacks.getchr = pk_default_getchr;
self->last_retval = *py_NIL(); self->last_retval = *py_NIL();
self->curr_exception = *py_NIL(); self->unhandled_exc = *py_NIL();
self->recursion_depth = 0; self->recursion_depth = 0;
self->max_recursion_depth = 1000; self->max_recursion_depth = 1000;
self->is_curr_exc_handled = false;
self->ctx = NULL; self->ctx = NULL;
self->curr_class = NULL; self->curr_class = NULL;
self->curr_decl_based_function = NULL; self->curr_decl_based_function = NULL;
@ -110,7 +108,8 @@ void VM__ctor(VM* self) {
FixedMemoryPool__ctor(&self->pool_frame, sizeof(py_Frame), 32); FixedMemoryPool__ctor(&self->pool_frame, sizeof(py_Frame), 32);
ManagedHeap__ctor(&self->heap); ManagedHeap__ctor(&self->heap);
ValueStack__ctor(&self->stack); self->stack.sp = self->stack.begin;
self->stack.end = self->stack.begin + PK_VM_STACK_SIZE;
CachedNames__ctor(&self->cached_names); CachedNames__ctor(&self->cached_names);
NameDict__ctor(&self->compile_time_funcs, PK_TYPE_ATTR_LOAD_FACTOR); NameDict__ctor(&self->compile_time_funcs, PK_TYPE_ATTR_LOAD_FACTOR);
@ -288,11 +287,11 @@ void VM__dtor(VM* self) {
// destroy all objects // destroy all objects
ManagedHeap__dtor(&self->heap); ManagedHeap__dtor(&self->heap);
// clear frames // clear frames
while(self->top_frame) while(self->top_frame) {
VM__pop_frame(self); VM__pop_frame(self);
}
BinTree__dtor(&self->modules); BinTree__dtor(&self->modules);
FixedMemoryPool__dtor(&self->pool_frame); FixedMemoryPool__dtor(&self->pool_frame);
ValueStack__dtor(&self->stack);
CachedNames__dtor(&self->cached_names); CachedNames__dtor(&self->cached_names);
NameDict__dtor(&self->compile_time_funcs); NameDict__dtor(&self->compile_time_funcs);
c11_vector__dtor(&self->types); c11_vector__dtor(&self->types);
@ -462,6 +461,11 @@ static bool
FrameResult VM__vectorcall(VM* self, uint16_t argc, uint16_t kwargc, bool opcall) { FrameResult VM__vectorcall(VM* self, uint16_t argc, uint16_t kwargc, bool opcall) {
#ifndef NDEBUG #ifndef NDEBUG
pk_print_stack(self, self->top_frame, (Bytecode){0}); 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 #endif
py_Ref p1 = self->stack.sp - kwargc * 2; py_Ref p1 = self->stack.sp - kwargc * 2;
@ -666,7 +670,7 @@ void ManagedHeap__mark(ManagedHeap* self) {
} }
// mark vm's registers // mark vm's registers
pk__mark_value(&vm->last_retval); 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++) { for(int i = 0; i < c11__count_array(vm->reg); i++) {
pk__mark_value(&vm->reg[i]); pk__mark_value(&vm->reg[i]);
} }

View File

@ -57,7 +57,6 @@ static bool disassemble(CodeObject* co) {
c11_sbuf__write_int(&ss, byte.arg); c11_sbuf__write_int(&ss, byte.arg);
switch(byte.op) { switch(byte.op) {
// TODO: see `dis.py` there is a memory issue
case OP_LOAD_CONST: { case OP_LOAD_CONST: {
py_Ref value = c11__at(py_TValue, &co->consts, byte.arg); py_Ref value = c11__at(py_TValue, &co->consts, byte.arg);
if(py_repr(value)) { if(py_repr(value)) {

View File

@ -1,14 +1,20 @@
#include "pocketpy/pocketpy.h" #include "pocketpy/pocketpy.h"
#include "pocketpy/objects/exception.h"
#include "pocketpy/interpreter/vm.h"
static bool traceback_format_exc(int argc, py_Ref argv) { static bool traceback_format_exc(int argc, py_Ref argv) {
PY_CHECK_ARGC(0); PY_CHECK_ARGC(0);
char* s = py_formatexc(); VM* vm = pk_current_vm;
if(!s) { if(vm->top_frame) {
py_newnone(py_retval()); FrameExcInfo* info = Frame__top_exc_info(vm->top_frame);
} else { if(info && !py_isnil(&info->exc)) {
py_newstr(py_retval(), s); char* res = formatexc_internal(&info->exc);
PK_FREE(s); py_newstr(py_retval(), res);
PK_FREE(res);
return true;
}
} }
py_newnone(py_retval());
return true; return true;
} }

View File

@ -22,7 +22,7 @@ bool _py_compile(CodeObject* out,
Error* err = pk_compile(src, out); Error* err = pk_compile(src, out);
if(err) { if(err) {
py_exception(tp_SyntaxError, err->msg); 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(src);
PK_DECREF(err->src); PK_DECREF(err->src);

View File

@ -152,15 +152,17 @@ bool py_call(py_Ref f, int argc, py_Ref argv) {
#ifndef NDEBUG #ifndef NDEBUG
bool py_callcfunc(py_CFunction f, int argc, py_Ref argv) { 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); py_StackRef p0 = py_peek(0);
// NOTE: sometimes users are using `py_retval()` to pass `argv` // NOTE: sometimes users are using `py_retval()` to pass `argv`
// It will be reset to `nil` and cause an exception // It will be reset to `nil` and cause an exception
py_newnil(py_retval()); py_newnil(py_retval());
bool ok = f(argc, argv); bool ok = f(argc, argv);
if(!ok) { if(!ok) {
if(!py_checkexc(true)) { if(!py_checkexc()) { c11__abort("py_CFunction returns `false` but no exception is set!"); }
c11__abort("py_CFunction returns `false` but no exception is set!");
}
return false; return false;
} }
if(py_peek(0) != p0) { if(py_peek(0) != p0) {
@ -170,8 +172,8 @@ bool py_callcfunc(py_CFunction f, int argc, py_Ref argv) {
c11__abort( c11__abort(
"py_CFunction returns nothing! Did you forget to call `py_newnone(py_retval())`?"); "py_CFunction returns nothing! Did you forget to call `py_newnone(py_retval())`?");
} }
if(py_checkexc(true)) { if(py_checkexc()) {
const char* name = py_tpname(pk_current_vm->curr_exception.type); const char* name = py_tpname(pk_current_vm->unhandled_exc.type);
c11__abort("py_CFunction returns `true`, but `%s` was set!", name); c11__abort("py_CFunction returns `true`, but `%s` was set!", name);
} }
return true; return true;
@ -258,7 +260,7 @@ bool pk_loadmethod(py_StackRef self, py_Name name) {
self[0] = *py_getslot(cls_var, 0); self[0] = *py_getslot(cls_var, 0);
self[1] = ti->self; self[1] = ti->self;
break; break;
default: c11__unreachable(); default: return false;
} }
return true; return true;
} }

View File

@ -142,32 +142,22 @@ py_Type pk_StopIteration__register() {
} }
////////////////////////////////////////////////// //////////////////////////////////////////////////
bool py_checkexc(bool ignore_handled) { bool py_checkexc() {
VM* vm = pk_current_vm; VM* vm = pk_current_vm;
if(ignore_handled && vm->is_curr_exc_handled) return false; return !py_isnil(&vm->unhandled_exc);
return !py_isnil(&vm->curr_exception);
} }
bool py_matchexc(py_Type type) { bool py_matchexc(py_Type type) {
VM* vm = pk_current_vm; VM* vm = pk_current_vm;
if(vm->is_curr_exc_handled) return false; if(py_isnil(&vm->unhandled_exc)) return false;
if(py_isnil(&vm->curr_exception)) return false; bool ok = py_issubclass(vm->unhandled_exc.type, type);
bool ok = py_issubclass(vm->curr_exception.type, type); if(ok) vm->last_retval = vm->unhandled_exc;
if(ok) {
// if match, then the exception is handled
vm->is_curr_exc_handled = true;
vm->last_retval = vm->curr_exception;
}
return ok; return ok;
} }
void py_clearexc(py_StackRef p0) { void py_clearexc(py_StackRef p0) {
VM* vm = pk_current_vm; VM* vm = pk_current_vm;
vm->curr_exception = *py_NIL(); py_newnil(&vm->unhandled_exc);
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;
if(p0) vm->stack.sp = p0; 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* name = py_tpname(exc->type);
const char* message; char* message = safe_stringify_exception(exc);
bool ok = py_str(exc);
if(!ok || !py_isstr(py_retval())) {
message = "<exception str() failed>";
} else {
message = py_tostr(py_retval());
}
c11_sbuf__write_cstr(self, name); c11_sbuf__write_cstr(self, name);
c11_sbuf__write_cstr(self, ": "); c11_sbuf__write_cstr(self, ": ");
c11_sbuf__write_cstr(self, message); 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() { void py_printexc() {
@ -210,24 +220,29 @@ void py_printexc() {
char* py_formatexc() { char* py_formatexc() {
VM* vm = pk_current_vm; 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 char* formatexc_internal(py_Ref exc) {
vm->is_curr_exc_handled = true; c11__rtassert(exc != NULL);
c11__rtassert(py_issubclass(exc->type, tp_BaseException));
c11_sbuf ss; c11_sbuf ss;
c11_sbuf__ctor(&ss); c11_sbuf__ctor(&ss);
BaseException* ud = py_touserdata(&vm->curr_exception); BaseException* ud = py_touserdata(exc);
py_Ref inner = &ud->inner_exc; py_Ref inner = &ud->inner_exc;
if(py_isnil(inner)) { if(py_isnil(inner)) {
c11_sbuf__write_exc(&ss, &vm->curr_exception); c11_sbuf__write_exc(&ss, exc);
} else { } else {
c11_sbuf__write_exc(&ss, inner); c11_sbuf__write_exc(&ss, inner);
c11_sbuf__write_cstr( c11_sbuf__write_cstr(
&ss, &ss,
"\n\nDuring handling of the above exception, another exception occurred:\n\n"); "\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); c11_string* res = c11_sbuf__submit(&ss);
@ -235,15 +250,13 @@ char* py_formatexc() {
memcpy(dup, res->data, res->size); memcpy(dup, res->data, res->size);
dup[res->size] = '\0'; dup[res->size] = '\0';
c11_string__delete(res); c11_string__delete(res);
if(py_debugger_isattached()) py_debugger_exceptionbreakpoint(&vm->curr_exception);
return dup; return dup;
} }
bool py_exception(py_Type type, const char* fmt, ...) { bool py_exception(py_Type type, const char* fmt, ...) {
#ifndef NDEBUG #ifndef NDEBUG
if(py_checkexc(true)) { if(py_checkexc()) {
const char* name = py_tpname(pk_current_vm->curr_exception.type); const char* name = py_tpname(pk_current_vm->unhandled_exc.type);
c11__abort("py_exception(): `%s` was already set!", name); c11__abort("py_exception(): `%s` was already set!", name);
} }
#endif #endif
@ -268,12 +281,15 @@ bool py_exception(py_Type type, const char* fmt, ...) {
bool py_raise(py_Ref exc) { bool py_raise(py_Ref exc) {
assert(py_isinstance(exc, tp_BaseException)); assert(py_isinstance(exc, tp_BaseException));
VM* vm = pk_current_vm; VM* vm = pk_current_vm;
if(!py_isnil(&vm->curr_exception)) { if(vm->top_frame) {
BaseException* ud = py_touserdata(&vm->curr_exception); FrameExcInfo* info = Frame__top_exc_info(vm->top_frame);
ud->inner_exc = vm->curr_exception; if(info && !py_isnil(&info->exc)) {
BaseException* ud = py_touserdata(exc);
ud->inner_exc = info->exc;
}
} }
vm->curr_exception = *exc; assert(py_isnil(&vm->unhandled_exc));
vm->is_curr_exc_handled = false; vm->unhandled_exc = *exc;
return false; return false;
} }

View File

@ -111,8 +111,8 @@ int py_next(py_Ref val) {
break; break;
} }
} }
if(vm->curr_exception.type == tp_StopIteration) { if(vm->unhandled_exc.type == tp_StopIteration) {
vm->last_retval = vm->curr_exception; vm->last_retval = vm->unhandled_exc;
py_clearexc(NULL); py_clearexc(NULL);
return 0; return 0;
} }

View File

@ -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(); py_finalize();
if(debug) py_debugger_exit(code); if(debug) py_debugger_exit(code);

View File

@ -146,7 +146,62 @@ except SyntaxError as e:
ok = True ok = True
assert ok assert ok
# nested try
def g():
try:
raise KeyError
except KeyError:
pass
try:
raise IndexError
except IndexError:
g()
a = []
for i in range(10):
a.append(i)
try:
try:
if i % 2 == 0:
raise KeyError(i)
else:
raise IndexError(i)
except KeyError as e:
assert i % 2 == 0
assert e.args[0] == i
raise
except IndexError as e:
assert i % 2 == 1
assert e.args[0] == i
raise
except Exception as e:
assert e.args[0] == i
assert a == list(range(10))
# inner exc
x = 0
try:
try:
[][1]
except:
raise KeyError
except KeyError:
x = 5
assert x == 5
a = []
for i in range(6):
try:
[][1]
except IndexError:
if i == 2:
continue
else:
a.append(i)
assert a == [0, 1, 3, 4, 5]
"""
# finally, only # finally, only
def finally_only(): def finally_only():
try: try:
@ -224,5 +279,4 @@ def finally_return():
return 1 return 1
assert finally_return() == 1 assert finally_return() == 1
"""

View File

@ -169,4 +169,14 @@ for x in xs:
xs.append(x+1) xs.append(x+1)
assert res == list(range(101)) assert res == list(range(101))
assert xs == res assert xs == res
# call property
from vmath import vec2
a = vec2(1, 2)
try:
x = a.x()
exit(1)
except TypeError:
pass