mirror of
https://github.com/pocketpy/pocketpy
synced 2026-08-12 17:17:11 +08:00
Compare commits
6 Commits
14d1572328
...
9c65c7f1b3
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9c65c7f1b3 | ||
|
|
71f06c4ff1 | ||
|
|
fed2bcea78 | ||
|
|
799829a8aa | ||
|
|
a06e6ac6ba | ||
|
|
46cc281099 |
4
.github/workflows/main.yml
vendored
4
.github/workflows/main.yml
vendored
@ -56,11 +56,11 @@ jobs:
|
|||||||
- name: Setup Clang
|
- name: Setup Clang
|
||||||
uses: egor-tensin/setup-clang@v1
|
uses: egor-tensin/setup-clang@v1
|
||||||
with:
|
with:
|
||||||
version: 17
|
version: 19
|
||||||
platform: x64
|
platform: x64
|
||||||
- name: Run Sanitizers
|
- name: Run Sanitizers
|
||||||
run: |
|
run: |
|
||||||
sudo apt-get install -y libclang-rt-17-dev
|
sudo apt-get install -y libclang-rt-19-dev
|
||||||
bash build_g.sh
|
bash build_g.sh
|
||||||
bash run_tests.sh
|
bash run_tests.sh
|
||||||
rm -rf ./main
|
rm -rf ./main
|
||||||
|
|||||||
@ -4,7 +4,7 @@
|
|||||||
|
|
||||||
typedef struct PyObject PyObject;
|
typedef struct PyObject PyObject;
|
||||||
typedef struct VM VM;
|
typedef struct VM VM;
|
||||||
extern _Thread_local VM* pk_current_vm;
|
extern PK_THREAD_LOCAL VM* pk_current_vm;
|
||||||
|
|
||||||
typedef struct py_TValue {
|
typedef struct py_TValue {
|
||||||
py_Type type;
|
py_Type type;
|
||||||
|
|||||||
@ -78,6 +78,13 @@ typedef struct py_Callbacks {
|
|||||||
PY_MAYBENULL void (*gc_mark)(void (*f)(py_Ref val, void* ctx), void* ctx);
|
PY_MAYBENULL void (*gc_mark)(void (*f)(py_Ref val, void* ctx), void* ctx);
|
||||||
/// Used by `PRINT_EXPR` bytecode.
|
/// Used by `PRINT_EXPR` bytecode.
|
||||||
PY_MAYBENULL bool (*displayhook)(py_Ref val) PY_RAISE;
|
PY_MAYBENULL bool (*displayhook)(py_Ref val) PY_RAISE;
|
||||||
|
// open_file hook contributed by fdtd.io (Hector), 2026.
|
||||||
|
/// Consulted before a script-reachable file operation. `path` is the target path;
|
||||||
|
/// `mode` is the fopen mode string for `io.FileIO`, or the literal "delete" for
|
||||||
|
/// `os.remove`. Return true to allow the operation, false to reject it (the binding
|
||||||
|
/// then raises OSError). NULL (the default) allows everything, so existing embedders
|
||||||
|
/// are unaffected. Lets an embedder enforce its own path policy.
|
||||||
|
PY_MAYBENULL bool (*open_file)(const char* path, const char* mode);
|
||||||
} py_Callbacks;
|
} py_Callbacks;
|
||||||
|
|
||||||
/// A struct contains the application-level callbacks.
|
/// A struct contains the application-level callbacks.
|
||||||
|
|||||||
@ -17,7 +17,7 @@ rm -rf .coverage
|
|||||||
mkdir .coverage
|
mkdir .coverage
|
||||||
|
|
||||||
UNITS=$(find ./ -name "*.gcno")
|
UNITS=$(find ./ -name "*.gcno")
|
||||||
llvm-cov-17 gcov ${UNITS} -r -s include/ -r -s src/ >> .coverage/coverage.txt
|
llvm-cov-19 gcov ${UNITS} -r -s include/ -r -s src/ >> .coverage/coverage.txt
|
||||||
|
|
||||||
mv *.gcov .coverage
|
mv *.gcov .coverage
|
||||||
rm *.gcda
|
rm *.gcda
|
||||||
|
|||||||
@ -189,8 +189,8 @@ static Error* LexerError(Lexer* self, const char* fmt, ...) {
|
|||||||
err->src = self->src;
|
err->src = self->src;
|
||||||
PK_INCREF(self->src);
|
PK_INCREF(self->src);
|
||||||
err->lineno = self->current_line;
|
err->lineno = self->current_line;
|
||||||
const char* end = self->src->source->data + self->src->source->size;
|
const char* p_end = self->src->source->data + self->src->source->size;
|
||||||
if(self->curr_char <= end && *self->curr_char == '\n') { err->lineno--; }
|
if(self->curr_char <= p_end && *self->curr_char == '\n') { err->lineno--; }
|
||||||
va_list args;
|
va_list args;
|
||||||
va_start(args, fmt);
|
va_start(args, fmt);
|
||||||
vsnprintf(err->msg, sizeof(err->msg), fmt, args);
|
vsnprintf(err->msg, sizeof(err->msg), fmt, args);
|
||||||
@ -282,9 +282,16 @@ static Error* _eat_string(Lexer* self, c11_sbuf* buff, char quote, enum StringTy
|
|||||||
case 'b': c11_sbuf__write_char(buff, '\b'); break;
|
case 'b': c11_sbuf__write_char(buff, '\b'); break;
|
||||||
case 'f': c11_sbuf__write_char(buff, '\f'); break;
|
case 'f': c11_sbuf__write_char(buff, '\f'); break;
|
||||||
case 'v': c11_sbuf__write_char(buff, '\v'); break;
|
case 'v': c11_sbuf__write_char(buff, '\v'); break;
|
||||||
// Special case for the often used \0 while we don't have full support for octal literals.
|
// Special case for the often used \0 while we don't have full support for octal
|
||||||
|
// literals.
|
||||||
case '0': c11_sbuf__write_char(buff, '\0'); break;
|
case '0': c11_sbuf__write_char(buff, '\0'); break;
|
||||||
case 'x': {
|
case 'x': {
|
||||||
|
// check there are at least 2 chars can read
|
||||||
|
const char* p_end = self->src->source->data + self->src->source->size;
|
||||||
|
if(p_end - self->curr_char < 2) {
|
||||||
|
return LexerError(self, "invalid hex escape");
|
||||||
|
}
|
||||||
|
|
||||||
char hex[3] = {eatchar(self), eatchar(self), '\0'};
|
char hex[3] = {eatchar(self), eatchar(self), '\0'};
|
||||||
int code;
|
int code;
|
||||||
if(sscanf(hex, "%x", &code) != 1 || code > 0xFF) {
|
if(sscanf(hex, "%x", &code) != 1 || code > 0xFF) {
|
||||||
|
|||||||
@ -92,6 +92,7 @@ void VM__ctor(VM* self) {
|
|||||||
self->callbacks.print = pk_default_print;
|
self->callbacks.print = pk_default_print;
|
||||||
self->callbacks.flush = pk_default_flush;
|
self->callbacks.flush = pk_default_flush;
|
||||||
self->callbacks.getchr = pk_default_getchr;
|
self->callbacks.getchr = pk_default_getchr;
|
||||||
|
self->callbacks.open_file = NULL;
|
||||||
|
|
||||||
self->last_retval = *py_NIL();
|
self->last_retval = *py_NIL();
|
||||||
self->unhandled_exc = *py_NIL();
|
self->unhandled_exc = *py_NIL();
|
||||||
@ -579,8 +580,9 @@ FrameResult VM__vectorcall(VM* self, uint16_t argc, uint16_t kwargc, bool opcall
|
|||||||
}
|
}
|
||||||
|
|
||||||
if(p0->type == tp_type) {
|
if(p0->type == tp_type) {
|
||||||
|
py_Type p0_type = py_totype(p0);
|
||||||
// [cls, NULL, args..., kwargs...]
|
// [cls, NULL, args..., kwargs...]
|
||||||
py_Ref new_f = py_tpfindmagic(py_totype(p0), __new__);
|
py_Ref new_f = py_tpfindmagic(p0_type, __new__);
|
||||||
assert(new_f && py_isnil(p0 + 1));
|
assert(new_f && py_isnil(p0 + 1));
|
||||||
bool is_default_new = new_f->type == tp_nativefunc && new_f->_cfunc == pk__object_new;
|
bool is_default_new = new_f->type == tp_nativefunc && new_f->_cfunc == pk__object_new;
|
||||||
|
|
||||||
@ -598,14 +600,16 @@ FrameResult VM__vectorcall(VM* self, uint16_t argc, uint16_t kwargc, bool opcall
|
|||||||
// NOTE: previously we use `get_unbound_method` but here we just use `tpfindmagic`
|
// NOTE: previously we use `get_unbound_method` but here we just use `tpfindmagic`
|
||||||
// >> [cls, NULL, args..., kwargs...]
|
// >> [cls, NULL, args..., kwargs...]
|
||||||
// >> py_retval() is the new instance
|
// >> py_retval() is the new instance
|
||||||
py_Ref init_f = py_tpfindmagic(py_totype(p0), __init__);
|
py_Ref init_f = py_tpfindmagic(p0_type, __init__);
|
||||||
if(init_f) {
|
if(init_f) {
|
||||||
// do an inplace patch
|
if(py_isinstance(py_retval(), p0_type)) {
|
||||||
*p0 = *init_f; // __init__
|
// do an inplace patch
|
||||||
p0[1] = self->last_retval; // self
|
*p0 = *init_f; // __init__
|
||||||
// [__init__, self, args..., kwargs...]
|
p0[1] = self->last_retval; // self
|
||||||
if(VM__vectorcall(self, argc, kwargc, false) == RES_ERROR) return RES_ERROR;
|
// [__init__, self, args..., kwargs...]
|
||||||
*py_retval() = p0[1]; // restore the new instance
|
if(VM__vectorcall(self, argc, kwargc, false) == RES_ERROR) return RES_ERROR;
|
||||||
|
*py_retval() = p0[1]; // restore the new instance
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
if(is_default_new) {
|
if(is_default_new) {
|
||||||
if(argc != 0 || kwargc != 0) {
|
if(argc != 0 || kwargc != 0) {
|
||||||
@ -713,53 +717,62 @@ void ManagedHeap__mark(ManagedHeap* self) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void* ud = PyObject__userdata(obj);
|
if(obj->type > tp_object) {
|
||||||
switch(obj->type) {
|
// NOTE: `defaultdict` -> `dict` -> `object`
|
||||||
case tp_list: {
|
// NOTE: native types must extend from `object`.
|
||||||
List* self = ud;
|
py_TypeInfo* ti = pk_typeinfo(obj->type);
|
||||||
for(int i = 0; i < self->length; i++) {
|
while(ti->base != tp_object) {
|
||||||
py_TValue* val = c11__at(py_TValue, self, i);
|
ti = ti->base_ti;
|
||||||
pk__mark_value(val);
|
}
|
||||||
|
|
||||||
|
void* ud = PyObject__userdata(obj);
|
||||||
|
switch(ti->index) {
|
||||||
|
case tp_list: {
|
||||||
|
List* self = ud;
|
||||||
|
for(int i = 0; i < self->length; i++) {
|
||||||
|
py_TValue* val = c11__at(py_TValue, self, i);
|
||||||
|
pk__mark_value(val);
|
||||||
|
}
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
break;
|
case tp_dict: {
|
||||||
}
|
Dict* self = ud;
|
||||||
case tp_dict: {
|
for(int i = 0; i < self->entries.length; i++) {
|
||||||
Dict* self = ud;
|
DictEntry* entry = c11__at(DictEntry, &self->entries, i);
|
||||||
for(int i = 0; i < self->entries.length; i++) {
|
if(py_isnil(&entry->key)) continue;
|
||||||
DictEntry* entry = c11__at(DictEntry, &self->entries, i);
|
pk__mark_value(&entry->key);
|
||||||
if(py_isnil(&entry->key)) continue;
|
pk__mark_value(&entry->val);
|
||||||
pk__mark_value(&entry->key);
|
}
|
||||||
pk__mark_value(&entry->val);
|
break;
|
||||||
}
|
}
|
||||||
break;
|
case tp_generator: {
|
||||||
}
|
Generator* self = ud;
|
||||||
case tp_generator: {
|
if(self->frame) Frame__gc_mark(self->frame, p_stack);
|
||||||
Generator* self = ud;
|
break;
|
||||||
if(self->frame) Frame__gc_mark(self->frame, p_stack);
|
}
|
||||||
break;
|
case tp_function: {
|
||||||
}
|
function__gc_mark(ud, p_stack);
|
||||||
case tp_function: {
|
break;
|
||||||
function__gc_mark(ud, p_stack);
|
}
|
||||||
break;
|
case tp_BaseException: {
|
||||||
}
|
BaseException* self = ud;
|
||||||
case tp_BaseException: {
|
pk__mark_value(&self->args);
|
||||||
BaseException* self = ud;
|
pk__mark_value(&self->inner_exc);
|
||||||
pk__mark_value(&self->args);
|
c11__foreach(BaseExceptionFrame, &self->stacktrace, frame) {
|
||||||
pk__mark_value(&self->inner_exc);
|
pk__mark_value(&frame->locals);
|
||||||
c11__foreach(BaseExceptionFrame, &self->stacktrace, frame) {
|
pk__mark_value(&frame->globals);
|
||||||
pk__mark_value(&frame->locals);
|
}
|
||||||
pk__mark_value(&frame->globals);
|
break;
|
||||||
|
}
|
||||||
|
case tp_code: {
|
||||||
|
CodeObject* self = ud;
|
||||||
|
CodeObject__gc_mark(self, p_stack);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case tp_chunked_array2d: {
|
||||||
|
c11_chunked_array2d__mark(ud, p_stack);
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
break;
|
|
||||||
}
|
|
||||||
case tp_code: {
|
|
||||||
CodeObject* self = ud;
|
|
||||||
CodeObject__gc_mark(self, p_stack);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case tp_chunked_array2d: {
|
|
||||||
c11_chunked_array2d__mark(ud, p_stack);
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -71,6 +71,10 @@ static bool os_remove(int argc, py_Ref argv) {
|
|||||||
PY_CHECK_ARGC(1);
|
PY_CHECK_ARGC(1);
|
||||||
PY_CHECK_ARG_TYPE(0, tp_str);
|
PY_CHECK_ARG_TYPE(0, tp_str);
|
||||||
const char* path = py_tostr(py_arg(0));
|
const char* path = py_tostr(py_arg(0));
|
||||||
|
// open_file policy hook: "delete" pseudo-mode for os.remove.
|
||||||
|
if(pk_current_vm->callbacks.open_file && !pk_current_vm->callbacks.open_file(path, "delete")) {
|
||||||
|
return OSError("os.remove not permitted: '%s'", path);
|
||||||
|
}
|
||||||
int code = remove(path);
|
int code = remove(path);
|
||||||
if(code != 0) {
|
if(code != 0) {
|
||||||
const char* msg = strerror(errno);
|
const char* msg = strerror(errno);
|
||||||
@ -111,6 +115,11 @@ static bool io_FileIO__new__(int argc, py_Ref argv) {
|
|||||||
io_FileIO* ud = py_newobject(py_retval(), cls, 0, sizeof(io_FileIO));
|
io_FileIO* ud = py_newobject(py_retval(), cls, 0, sizeof(io_FileIO));
|
||||||
ud->path = py_tostr(py_arg(1));
|
ud->path = py_tostr(py_arg(1));
|
||||||
ud->mode = py_tostr(py_arg(2));
|
ud->mode = py_tostr(py_arg(2));
|
||||||
|
// open_file policy hook: consulted with the fopen mode string before the open.
|
||||||
|
if(pk_current_vm->callbacks.open_file &&
|
||||||
|
!pk_current_vm->callbacks.open_file(ud->path, ud->mode)) {
|
||||||
|
return OSError("file open not permitted: '%s' (mode '%s')", ud->path, ud->mode);
|
||||||
|
}
|
||||||
ud->file = fopen(ud->path, ud->mode);
|
ud->file = fopen(ud->path, ud->mode);
|
||||||
if(ud->file == NULL) {
|
if(ud->file == NULL) {
|
||||||
const char* msg = strerror(errno);
|
const char* msg = strerror(errno);
|
||||||
|
|||||||
@ -5,7 +5,7 @@
|
|||||||
#include "pocketpy/common/name.h"
|
#include "pocketpy/common/name.h"
|
||||||
#include "pocketpy/interpreter/vm.h"
|
#include "pocketpy/interpreter/vm.h"
|
||||||
|
|
||||||
_Thread_local VM* pk_current_vm;
|
PK_THREAD_LOCAL VM* pk_current_vm;
|
||||||
|
|
||||||
static bool pk_initialized;
|
static bool pk_initialized;
|
||||||
static bool pk_finalized;
|
static bool pk_finalized;
|
||||||
|
|||||||
@ -168,3 +168,13 @@ class DerivedClass(BaseClass):
|
|||||||
|
|
||||||
|
|
||||||
assert DerivedClass.f() == 'BaseClass'
|
assert DerivedClass.f() == 'BaseClass'
|
||||||
|
|
||||||
|
# bad __init__
|
||||||
|
class A:
|
||||||
|
def __new__(cls, *args, **kwargs):
|
||||||
|
return 1
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
assert False
|
||||||
|
|
||||||
|
A()
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user