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}'` | ✅ |
| Unpacking | `a, b = 1, 2` | ✅ |
| Star Unpacking | `a, *b = [1, 2, 3]` | ✅ |
| Exception | `raise/try..catch..finally` | ✅ |
| Exception | `raise/try..except..` | ✅ |
| Dynamic Code | `eval()/exec()` | ✅ |
| Reflection | `hasattr()/getattr()/setattr()` | ✅ |
| 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}'` | ✅ |
| Unpacking | `a, b = 1, 2` | ✅ |
| Star Unpacking | `a, *b = [1, 2, 3]` | ✅ |
| Exception | `raise/try..catch..finally` | ✅ |
| Exception | `raise/try..except..` | ✅ |
| Dynamic Code | `eval()/exec()` | ✅ |
| Reflection | `hasattr()/getattr()/setattr()` | ✅ |
| Import | `import/from..import` | ✅ |

View File

@ -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

View File

@ -11,20 +11,14 @@ typedef struct ValueStack {
py_TValue* sp;
py_TValue* end;
// 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;
void ValueStack__ctor(ValueStack* self);
void ValueStack__dtor(ValueStack* self);
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;
@ -35,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 {
@ -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_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);

View File

@ -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

View File

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

View File

@ -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);

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;
/// 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);

View File

@ -116,16 +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(BEGIN_FINALLY)
OPCODE(END_FINALLY)
/**************************/
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);
break;
}
case CodeBlockType_EXCEPT: {
Ctx__emit_(self, OP_END_EXC_HANDLING, 1, line);
case CodeBlockType_TRY: {
Ctx__emit_(self, OP_END_TRY, BC_NOARG, line);
break;
}
case CodeBlockType_FINALLY: {
Ctx__emit_(self, OP_END_FINALLY, 1, line);
case CodeBlockType_EXCEPT: {
Ctx__emit_(self, OP_END_TRY, BC_NOARG, line);
break;
}
default: break;
@ -1919,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;
@ -2000,7 +2002,7 @@ static Error* exprSlice0(Compiler* self) {
check(EXPR(self));
slice->step = Ctx__s_popx(ctx());
} // else ::
} // else :
} // else :
return NULL;
}
@ -2627,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,
@ -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
bool has_finally = curr()->type == TK_FINALLY;
if(!has_finally) {
patches[patches_length++] = Ctx__emit_(ctx(), OP_JUMP_FORWARD, BC_NOARG, BC_KEEPLINE);
}
Ctx__exit_block(ctx());
if(has_finally) return SyntaxError(self, "finally clause is not supported yet");
if(has_finally) {
consume(TK_FINALLY);
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;
}
patches[patches_length++] = Ctx__emit_(ctx(), OP_JUMP_FORWARD, BC_NOARG, BC_KEEPLINE);
Ctx__exit_block(ctx());
do {
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);
// 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);
@ -2692,27 +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++)
for(int i = 0; i < patches_length; 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;
}
@ -2815,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: {

View File

@ -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

View File

@ -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,46 +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);
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);
}
FrameExcInfo* info = Frame__top_exc_info(frame);
assert(info != NULL && !py_isnil(&info->exc));
PUSH(&info->exc);
DISPATCH();
}
//////////////////
@ -1224,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);

View File

@ -7,13 +7,6 @@
#include <stdbool.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) {
py_StackRef dict = py_pushtmp();
py_newdict(dict);
@ -38,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,
@ -68,57 +51,47 @@ 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;
int Frame__goto_exception_handler(py_Frame* self, ValueStack* value_stack, py_Ref exc) {
FrameExcInfo* p = self->exc_stack.data;
for(int i = self->exc_stack.length - 1; i >= 0; i--) {
if(py_isnil(&p[i].exc)) {
value_stack->sp = (self->p0 + p[i].offset); // unwind the stack
return c11__at(CodeBlock, &self->co->blocks, p[i].iblock)->end;
} else {
self->exc_stack.length--;
}
}
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;
return -1;
}
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) {

View File

@ -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;
@ -110,7 +108,8 @@ void VM__ctor(VM* self) {
FixedMemoryPool__ctor(&self->pool_frame, sizeof(py_Frame), 32);
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);
NameDict__ctor(&self->compile_time_funcs, PK_TYPE_ATTR_LOAD_FACTOR);
@ -288,11 +287,11 @@ void VM__dtor(VM* self) {
// destroy all objects
ManagedHeap__dtor(&self->heap);
// clear frames
while(self->top_frame)
while(self->top_frame) {
VM__pop_frame(self);
}
BinTree__dtor(&self->modules);
FixedMemoryPool__dtor(&self->pool_frame);
ValueStack__dtor(&self->stack);
CachedNames__dtor(&self->cached_names);
NameDict__dtor(&self->compile_time_funcs);
c11_vector__dtor(&self->types);
@ -462,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;
@ -666,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]);
}

View File

@ -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)) {

View File

@ -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;
}

View File

@ -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);

View File

@ -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;
@ -258,7 +260,7 @@ bool pk_loadmethod(py_StackRef self, py_Name name) {
self[0] = *py_getslot(cls_var, 0);
self[1] = ti->self;
break;
default: c11__unreachable();
default: return false;
}
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;
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;
}

View File

@ -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;
}

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

View File

@ -146,7 +146,62 @@ except SyntaxError as e:
ok = True
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
def finally_only():
try:
@ -224,5 +279,4 @@ def finally_return():
return 1
assert finally_return() == 1
"""

View File

@ -169,4 +169,14 @@ for x in xs:
xs.append(x+1)
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