123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830 |
- #include <Python.h>
- #include <time.h>
- #include "third-party/quickjs.h"
- // Node of Python callable that the context needs to keep available.
- typedef struct PythonCallableNode PythonCallableNode;
- struct PythonCallableNode {
- PyObject *obj;
- // Internal ID of the callable function. "magic" is QuickJS terminology.
- int magic;
- PythonCallableNode *next;
- };
- // Keeps track of the time if we are using a time limit.
- typedef struct {
- clock_t start;
- clock_t limit;
- } InterruptData;
- // The data of the type _quickjs.Context.
- typedef struct {
- PyObject_HEAD JSRuntime *runtime;
- JSContext *context;
- int has_time_limit;
- clock_t time_limit;
- // Used when releasing the GIL.
- PyThreadState *thread_state;
- InterruptData interrupt_data;
- // NULL-terminated singly linked list of callable Python objects that we need to keep alive.
- PythonCallableNode *python_callables;
- } ContextData;
- // The data of the type _quickjs.Object.
- typedef struct {
- PyObject_HEAD;
- ContextData *context;
- JSValue object;
- } ObjectData;
- // The exception raised by this module.
- static PyObject *JSException = NULL;
- static PyObject *StackOverflow = NULL;
- // Converts a JSValue to a Python object.
- //
- // Takes ownership of the JSValue and will deallocate it (refcount reduced by 1).
- static PyObject *quickjs_to_python(ContextData *context_obj, JSValue value);
- // Whether converting item to QuickJS would be possible.
- static int python_to_quickjs_possible(ContextData *context, PyObject *item);
- // Converts item to QuickJS.
- //
- // If the Python object is not possible to convert to JS, undefined will be returned. This fallback
- // will not be used if python_to_quickjs_possible returns 1.
- static JSValueConst python_to_quickjs(ContextData *context, PyObject *item);
- static PyTypeObject Object;
- // Returns nonzero if we should stop due to a time limit.
- static int js_interrupt_handler(JSRuntime *rt, void *opaque) {
- InterruptData *data = opaque;
- if (clock() - data->start >= data->limit) {
- return 1;
- } else {
- return 0;
- }
- }
- // Sets up a context and an InterruptData struct if the context has a time limit.
- static void setup_time_limit(ContextData *context, InterruptData *interrupt_data) {
- if (context->has_time_limit) {
- JS_SetInterruptHandler(context->runtime, js_interrupt_handler, interrupt_data);
- interrupt_data->limit = context->time_limit;
- interrupt_data->start = clock();
- }
- }
- // Restores the context if the context has a time limit.
- static void teardown_time_limit(ContextData *context) {
- if (context->has_time_limit) {
- JS_SetInterruptHandler(context->runtime, NULL, NULL);
- }
- }
- // This method is always called in a context before running JS code in QuickJS. It sets up time
- // limites, releases the GIL etc.
- static void prepare_call_js(ContextData *context) {
- // We release the GIL in order to speed things up for certain use cases.
- assert(!context->thread_state);
- context->thread_state = PyEval_SaveThread();
- setup_time_limit(context, &context->interrupt_data);
- }
- // This method is called right after returning from running JS code. Aquires the GIL etc.
- static void end_call_js(ContextData *context) {
- teardown_time_limit(context);
- assert(context->thread_state);
- PyEval_RestoreThread(context->thread_state);
- context->thread_state = NULL;
- }
- // Called when Python is called again from inside QuickJS.
- static void prepare_call_python(ContextData *context) {
- assert(context->thread_state);
- PyEval_RestoreThread(context->thread_state);
- context->thread_state = NULL;
- }
- // Called when the operation started by prepare_call_python is done.
- static void end_call_python(ContextData *context) {
- assert(!context->thread_state);
- context->thread_state = PyEval_SaveThread();
- }
- // GC traversal.
- static int object_traverse(ObjectData *self, visitproc visit, void *arg) {
- Py_VISIT(self->context);
- return 0;
- }
- // Creates an instance of the Object class.
- static PyObject *object_new(PyTypeObject *type, PyObject *args, PyObject *kwds) {
- ObjectData *self = PyObject_GC_New(ObjectData, type);
- if (self != NULL) {
- self->context = NULL;
- }
- return (PyObject *)self;
- }
- // Deallocates an instance of the Object class.
- static void object_dealloc(ObjectData *self) {
- if (self->context) {
- PyObject_GC_UnTrack(self);
- JS_FreeValue(self->context->context, self->object);
- // We incremented the refcount of the context when we created this object, so we should
- // decrease it now so we don't leak memory.
- Py_DECREF(self->context);
- }
- PyObject_GC_Del(self);
- }
- // _quickjs.Object.get
- //
- // Gets a Javascript property of the object.
- static PyObject *object_get(ObjectData *self, PyObject *args) {
- const char *name;
- if (!PyArg_ParseTuple(args, "s", &name)) {
- return NULL;
- }
- JSValue value = JS_GetPropertyStr(self->context->context, self->object, name);
- return quickjs_to_python(self->context, value);
- }
- static JSValue js_c_function(
- JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv, int magic) {
- ContextData *context = (ContextData *)JS_GetContextOpaque(ctx);
- if (context->has_time_limit) {
- return JS_ThrowInternalError(ctx, "Can not call into Python with a time limit set.");
- }
- PythonCallableNode *node = context->python_callables;
- while (node && node->magic != magic) {
- node = node->next;
- }
- if (!node) {
- return JS_ThrowInternalError(ctx, "Internal error.");
- }
- prepare_call_python(context);
- PyObject *args = PyTuple_New(argc);
- if (!args) {
- end_call_python(context);
- return JS_ThrowOutOfMemory(ctx);
- }
- int tuple_success = 1;
- for (int i = 0; i < argc; ++i) {
- PyObject *arg = quickjs_to_python(context, JS_DupValue(ctx, argv[i]));
- if (!arg) {
- tuple_success = 0;
- break;
- }
- PyTuple_SET_ITEM(args, i, arg);
- }
- if (!tuple_success) {
- Py_DECREF(args);
- end_call_python(context);
- return JS_ThrowInternalError(ctx, "Internal error: could not convert args.");
- }
- PyObject *result = PyObject_CallObject(node->obj, args);
- Py_DECREF(args);
- if (!result) {
- end_call_python(context);
- return JS_ThrowInternalError(ctx, "Python call failed.");
- }
- JSValue js_result = JS_NULL;
- if (python_to_quickjs_possible(context, result)) {
- js_result = python_to_quickjs(context, result);
- } else {
- PyErr_Clear();
- js_result = JS_ThrowInternalError(ctx, "Can not convert Python result to JS.");
- }
- Py_DECREF(result);
- end_call_python(context);
- return js_result;
- }
- // _quickjs.Object.set
- //
- // Sets a Javascript property to the object. Callables are supported.
- static PyObject *object_set(ObjectData *self, PyObject *args) {
- const char *name;
- PyObject *item;
- if (!PyArg_ParseTuple(args, "sO", &name, &item)) {
- return NULL;
- }
- int ret = 0;
- if (PyCallable_Check(item) && (!PyObject_IsInstance(item, (PyObject *)&Object) || JS_IsFunction(
- self->context->context, ((ObjectData *)item)->object))) {
- PythonCallableNode *node = PyMem_Malloc(sizeof(PythonCallableNode));
- if (!node) {
- return NULL;
- }
- Py_INCREF(item);
- node->magic = 0;
- if (self->context->python_callables) {
- node->magic = self->context->python_callables->magic + 1;
- }
- node->obj = item;
- node->next = self->context->python_callables;
- self->context->python_callables = node;
- JSValue function = JS_NewCFunctionMagic(
- self->context->context,
- js_c_function,
- name,
- 0, // TODO: Should we allow setting the .length of the function to something other than 0?
- JS_CFUNC_generic_magic,
- node->magic);
- // If this fails we don't notify the caller of this function.
- ret = JS_SetPropertyStr(self->context->context, self->object, name, function);
- if (ret != 1) {
- PyErr_SetString(PyExc_TypeError, "Failed setting the variable as a callable.");
- return NULL;
- } else {
- Py_RETURN_NONE;
- }
- } else {
- if (python_to_quickjs_possible(self->context, item)) {
- ret = JS_SetPropertyStr(self->context->context, self->object, name,
- python_to_quickjs(self->context, item));
- if (ret != 1) {
- PyErr_SetString(PyExc_TypeError, "Failed setting the variable.");
- }
- }
- if (ret == 1) {
- Py_RETURN_NONE;
- } else {
- return NULL;
- }
- }
- }
- // _quickjs.Object.__call__
- static PyObject *object_call(ObjectData *self, PyObject *args, PyObject *kwds);
- // _quickjs.Object.json
- //
- // Returns the JSON representation of the object as a Python string.
- static PyObject *object_json(ObjectData *self) {
- JSContext *context = self->context->context;
- JSValue json_string = JS_JSONStringify(context, self->object, JS_UNDEFINED, JS_UNDEFINED);
- return quickjs_to_python(self->context, json_string);
- }
- // All methods of the _quickjs.Object class.
- static PyMethodDef object_methods[] = {
- {"get", (PyCFunction)object_get, METH_VARARGS, "Gets a Javascript property of the object."},
- {"set", (PyCFunction)object_set, METH_VARARGS, "Sets a Javascript property to the object."},
- {"json", (PyCFunction)object_json, METH_NOARGS, "Converts to a JSON string."},
- {NULL} /* Sentinel */
- };
- // Define the quickjs.Object type.
- static PyTypeObject Object = {PyVarObject_HEAD_INIT(NULL, 0).tp_name = "_quickjs.Object",
- .tp_doc = "Quickjs object",
- .tp_basicsize = sizeof(ObjectData),
- .tp_itemsize = 0,
- .tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC,
- .tp_traverse = (traverseproc)object_traverse,
- .tp_new = object_new,
- .tp_dealloc = (destructor)object_dealloc,
- .tp_call = (ternaryfunc)object_call,
- .tp_methods = object_methods};
- // Whether converting item to QuickJS would be possible.
- static int python_to_quickjs_possible(ContextData *context, PyObject *item) {
- if (PyBool_Check(item)) {
- return 1;
- } else if (PyLong_Check(item)) {
- return 1;
- } else if (PyFloat_Check(item)) {
- return 1;
- } else if (item == Py_None) {
- return 1;
- } else if (PyUnicode_Check(item)) {
- return 1;
- } else if (PyObject_IsInstance(item, (PyObject *)&Object)) {
- ObjectData *object = (ObjectData *)item;
- if (object->context != context) {
- PyErr_Format(PyExc_ValueError, "Can not mix JS objects from different contexts.");
- return 0;
- }
- return 1;
- } else {
- PyErr_Format(PyExc_TypeError,
- "Unsupported type when converting a Python object to quickjs: %s.",
- Py_TYPE(item)->tp_name);
- return 0;
- }
- }
- // Converts item to QuickJS.
- //
- // If the Python object is not possible to convert to JS, undefined will be returned. This fallback
- // will not be used if python_to_quickjs_possible returns 1.
- static JSValueConst python_to_quickjs(ContextData *context, PyObject *item) {
- if (PyBool_Check(item)) {
- return JS_MKVAL(JS_TAG_BOOL, item == Py_True ? 1 : 0);
- } else if (PyLong_Check(item)) {
- int overflow;
- long value = PyLong_AsLongAndOverflow(item, &overflow);
- if (overflow) {
- PyObject *float_value = PyNumber_Float(item);
- double double_value = PyFloat_AsDouble(float_value);
- Py_DECREF(float_value);
- return JS_NewFloat64(context->context, double_value);
- } else {
- return JS_MKVAL(JS_TAG_INT, value);
- }
- } else if (PyFloat_Check(item)) {
- return JS_NewFloat64(context->context, PyFloat_AsDouble(item));
- } else if (item == Py_None) {
- return JS_NULL;
- } else if (PyUnicode_Check(item)) {
- return JS_NewString(context->context, PyUnicode_AsUTF8(item));
- } else if (PyObject_IsInstance(item, (PyObject *)&Object)) {
- return JS_DupValue(context->context, ((ObjectData *)item)->object);
- } else {
- // Can not happen if python_to_quickjs_possible passes.
- return JS_UNDEFINED;
- }
- }
- // _quickjs.Object.__call__
- static PyObject *object_call(ObjectData *self, PyObject *args, PyObject *kwds) {
- if (self->context == NULL) {
- // This object does not have a context and has not been created by this module.
- Py_RETURN_NONE;
- }
- // We first loop through all arguments and check that they are supported without doing anything.
- // This makes the cleanup code simpler for the case where we have to raise an error.
- const int nargs = PyTuple_Size(args);
- for (int i = 0; i < nargs; ++i) {
- PyObject *item = PyTuple_GetItem(args, i);
- if (!python_to_quickjs_possible(self->context, item)) {
- return NULL;
- }
- }
- // Now we know that all arguments are supported and we can convert them.
- JSValueConst *jsargs = malloc(nargs * sizeof(JSValueConst));
- for (int i = 0; i < nargs; ++i) {
- PyObject *item = PyTuple_GetItem(args, i);
- jsargs[i] = python_to_quickjs(self->context, item);
- }
- prepare_call_js(self->context);
- JSValue value;
- value = JS_Call(self->context->context, self->object, JS_NULL, nargs, jsargs);
- for (int i = 0; i < nargs; ++i) {
- JS_FreeValue(self->context->context, jsargs[i]);
- }
- free(jsargs);
- end_call_js(self->context);
- return quickjs_to_python(self->context, value);
- }
- // Converts the current Javascript exception to a Python exception via a C string.
- static void quickjs_exception_to_python(JSContext *context) {
- JSValue exception = JS_GetException(context);
- const char *cstring = JS_ToCString(context, exception);
- const char *stack_cstring = NULL;
- if (!JS_IsNull(exception) && !JS_IsUndefined(exception)) {
- JSValue stack = JS_GetPropertyStr(context, exception, "stack");
- if (!JS_IsException(stack)) {
- stack_cstring = JS_ToCString(context, stack);
- JS_FreeValue(context, stack);
- }
- }
- if (cstring != NULL) {
- const char *safe_stack_cstring = stack_cstring ? stack_cstring : "";
- if (strstr(cstring, "stack overflow") != NULL) {
- PyErr_Format(StackOverflow, "%s\n%s", cstring, safe_stack_cstring);
- } else {
- PyErr_Format(JSException, "%s\n%s", cstring, safe_stack_cstring);
- }
- } else {
- // This has been observed to happen when different threads have used the same QuickJS
- // runtime, but not at the same time.
- // Could potentially be another problem though, since JS_ToCString may return NULL.
- PyErr_Format(JSException,
- "(Failed obtaining QuickJS error string. Concurrency issue?)");
- }
- JS_FreeCString(context, cstring);
- JS_FreeCString(context, stack_cstring);
- JS_FreeValue(context, exception);
- }
- // Converts a JSValue to a Python object.
- //
- // Takes ownership of the JSValue and will deallocate it (refcount reduced by 1).
- static PyObject *quickjs_to_python(ContextData *context_obj, JSValue value) {
- JSContext *context = context_obj->context;
- int tag = JS_VALUE_GET_TAG(value);
- // A return value of NULL means an exception.
- PyObject *return_value = NULL;
- if (tag == JS_TAG_INT) {
- return_value = Py_BuildValue("i", JS_VALUE_GET_INT(value));
- } else if (tag == JS_TAG_BIG_INT) {
- const char *cstring = JS_ToCString(context, value);
- return_value = PyLong_FromString(cstring, NULL, 10);
- JS_FreeCString(context, cstring);
- } else if (tag == JS_TAG_BOOL) {
- return_value = Py_BuildValue("O", JS_VALUE_GET_BOOL(value) ? Py_True : Py_False);
- } else if (tag == JS_TAG_NULL) {
- return_value = Py_None;
- } else if (tag == JS_TAG_UNDEFINED) {
- return_value = Py_None;
- } else if (tag == JS_TAG_EXCEPTION) {
- quickjs_exception_to_python(context);
- } else if (tag == JS_TAG_FLOAT64) {
- return_value = Py_BuildValue("d", JS_VALUE_GET_FLOAT64(value));
- } else if (tag == JS_TAG_STRING) {
- const char *cstring = JS_ToCString(context, value);
- return_value = Py_BuildValue("s", cstring);
- JS_FreeCString(context, cstring);
- } else if (tag == JS_TAG_OBJECT || tag == JS_TAG_MODULE || tag == JS_TAG_SYMBOL) {
- // This is a Javascript object or function. We wrap it in a _quickjs.Object.
- return_value = PyObject_CallObject((PyObject *)&Object, NULL);
- ObjectData *object = (ObjectData *)return_value;
- // This is important. Otherwise, the context may be deallocated before the object, which
- // will result in a segfault with high probability.
- Py_INCREF(context_obj);
- object->context = context_obj;
- PyObject_GC_Track(object);
- object->object = JS_DupValue(context, value);
- } else {
- PyErr_Format(PyExc_TypeError, "Unknown quickjs tag: %d", tag);
- }
- JS_FreeValue(context, value);
- if (return_value == Py_None) {
- // Can not simply return PyNone for refcounting reasons.
- Py_RETURN_NONE;
- }
- return return_value;
- }
- static PyObject *test(PyObject *self, PyObject *args) {
- return Py_BuildValue("i", 42);
- }
- // Global state of the module. Currently none.
- struct module_state {};
- // GC traversal.
- static int context_traverse(ContextData *self, visitproc visit, void *arg) {
- PythonCallableNode *node = self->python_callables;
- while (node) {
- Py_VISIT(node->obj);
- node = node->next;
- }
- return 0;
- }
- // GC clearing. Object does not have a clearing method, therefore dependency cycles
- // between Context and Object will always be cleared starting here.
- static int context_clear(ContextData *self) {
- PythonCallableNode *node = self->python_callables;
- while (node) {
- Py_CLEAR(node->obj);
- node = node->next;
- }
- return 0;
- }
- // Creates an instance of the _quickjs.Context class.
- static PyObject *context_new(PyTypeObject *type, PyObject *args, PyObject *kwds) {
- ContextData *self = PyObject_GC_New(ContextData, type);
- if (self != NULL) {
- // We never have different contexts for the same runtime. This way, different
- // _quickjs.Context can be used concurrently.
- self->runtime = JS_NewRuntime();
- self->context = JS_NewContext(self->runtime);
- self->has_time_limit = 0;
- self->time_limit = 0;
- self->thread_state = NULL;
- self->python_callables = NULL;
- JS_SetContextOpaque(self->context, self);
- PyObject_GC_Track(self);
- }
- return (PyObject *)self;
- }
- // Deallocates an instance of the _quickjs.Context class.
- static void context_dealloc(ContextData *self) {
- JS_FreeContext(self->context);
- JS_FreeRuntime(self->runtime);
- PyObject_GC_UnTrack(self);
- PythonCallableNode *node = self->python_callables;
- self->python_callables = NULL;
- while (node) {
- PythonCallableNode *this = node;
- node = node->next;
- // this->obj may already be NULL if GC'ed right before through context_clear.
- Py_XDECREF(this->obj);
- PyMem_Free(this);
- }
- PyObject_GC_Del(self);
- }
- // Evaluates a Python string as JS and returns the result as a Python object. Will return
- // _quickjs.Object for complex types (other than e.g. str, int).
- static PyObject *context_eval_internal(ContextData *self, PyObject *args, int eval_type) {
- const char *code;
- if (!PyArg_ParseTuple(args, "s", &code)) {
- return NULL;
- }
- prepare_call_js(self);
- JSValue value;
- value = JS_Eval(self->context, code, strlen(code), "<input>", eval_type);
- end_call_js(self);
- return quickjs_to_python(self, value);
- }
- // _quickjs.Context.eval
- //
- // Evaluates a Python string as JS and returns the result as a Python object. Will return
- // _quickjs.Object for complex types (other than e.g. str, int).
- static PyObject *context_eval(ContextData *self, PyObject *args) {
- return context_eval_internal(self, args, JS_EVAL_TYPE_GLOBAL);
- }
- // _quickjs.Context.module
- //
- // Evaluates a Python string as JS module. Otherwise identical to eval.
- static PyObject *context_module(ContextData *self, PyObject *args) {
- return context_eval_internal(self, args, JS_EVAL_TYPE_MODULE);
- }
- // _quickjs.Context.execute_pending_job
- //
- // If there are pending jobs, executes one and returns True. Else returns False.
- static PyObject *context_execute_pending_job(ContextData *self) {
- prepare_call_js(self);
- JSContext *ctx;
- int ret = JS_ExecutePendingJob(self->runtime, &ctx);
- end_call_js(self);
- if (ret > 0) {
- Py_RETURN_TRUE;
- } else if (ret == 0) {
- Py_RETURN_FALSE;
- } else {
- quickjs_exception_to_python(ctx);
- return NULL;
- }
- }
- // _quickjs.Context.parse_json
- //
- // Evaluates a Python string as JSON and returns the result as a Python object. Will
- // return _quickjs.Object for complex types (other than e.g. str, int).
- static PyObject *context_parse_json(ContextData *self, PyObject *args) {
- const char *data;
- if (!PyArg_ParseTuple(args, "s", &data)) {
- return NULL;
- }
- JSValue value;
- Py_BEGIN_ALLOW_THREADS;
- value = JS_ParseJSON(self->context, data, strlen(data), "context_parse_json.json");
- Py_END_ALLOW_THREADS;
- return quickjs_to_python(self, value);
- }
- // _quickjs.Context.get_global
- //
- // Retrieves the global object of the JS context.
- static PyObject *context_get_global(ContextData *self) {
- return quickjs_to_python(self, JS_GetGlobalObject(self->context));
- }
- // _quickjs.Context.get
- //
- // Retrieves a global variable from the JS context.
- static PyObject *context_get(ContextData *self, PyObject *args) {
- PyErr_WarnEx(PyExc_DeprecationWarning,
- "Context.get is deprecated, use Context.get_global().get instead.", 1);
- PyObject *global = context_get_global(self);
- if (global == NULL) {
- return NULL;
- }
- PyObject *ret = object_get((ObjectData *)global, args);
- Py_DECREF(global);
- return ret;
- }
- // _quickjs.Context.set
- //
- // Sets a global variable to the JS context.
- static PyObject *context_set(ContextData *self, PyObject *args) {
- PyErr_WarnEx(PyExc_DeprecationWarning,
- "Context.set is deprecated, use Context.get_global().set instead.", 1);
- PyObject *global = context_get_global(self);
- if (global == NULL) {
- return NULL;
- }
- PyObject *ret = object_set((ObjectData *)global, args);
- Py_DECREF(global);
- return ret;
- }
- // _quickjs.Context.set_memory_limit
- //
- // Sets the memory limit of the context.
- static PyObject *context_set_memory_limit(ContextData *self, PyObject *args) {
- Py_ssize_t limit;
- if (!PyArg_ParseTuple(args, "n", &limit)) {
- return NULL;
- }
- JS_SetMemoryLimit(self->runtime, limit);
- Py_RETURN_NONE;
- }
- // _quickjs.Context.set_time_limit
- //
- // Sets the CPU time limit of the context. This will be used in an interrupt handler.
- static PyObject *context_set_time_limit(ContextData *self, PyObject *args) {
- double limit;
- if (!PyArg_ParseTuple(args, "d", &limit)) {
- return NULL;
- }
- if (limit < 0) {
- self->has_time_limit = 0;
- } else {
- self->has_time_limit = 1;
- self->time_limit = (clock_t)(limit * CLOCKS_PER_SEC);
- }
- Py_RETURN_NONE;
- }
- // _quickjs.Context.set_max_stack_size
- //
- // Sets the max stack size in bytes.
- static PyObject *context_set_max_stack_size(ContextData *self, PyObject *args) {
- Py_ssize_t limit;
- if (!PyArg_ParseTuple(args, "n", &limit)) {
- return NULL;
- }
- JS_SetMaxStackSize(self->runtime, limit);
- Py_RETURN_NONE;
- }
- // _quickjs.Context.memory
- //
- // Sets the CPU time limit of the context. This will be used in an interrupt handler.
- static PyObject *context_memory(ContextData *self) {
- PyObject *dict = PyDict_New();
- if (dict == NULL) {
- return NULL;
- }
- JSMemoryUsage usage;
- JS_ComputeMemoryUsage(self->runtime, &usage);
- #define MEM_USAGE_ADD_TO_DICT(key) \
- { \
- PyObject *value = PyLong_FromLongLong(usage.key); \
- if (PyDict_SetItemString(dict, #key, value) != 0) { \
- return NULL; \
- } \
- Py_DECREF(value); \
- }
- MEM_USAGE_ADD_TO_DICT(malloc_size);
- MEM_USAGE_ADD_TO_DICT(malloc_limit);
- MEM_USAGE_ADD_TO_DICT(memory_used_size);
- MEM_USAGE_ADD_TO_DICT(malloc_count);
- MEM_USAGE_ADD_TO_DICT(memory_used_count);
- MEM_USAGE_ADD_TO_DICT(atom_count);
- MEM_USAGE_ADD_TO_DICT(atom_size);
- MEM_USAGE_ADD_TO_DICT(str_count);
- MEM_USAGE_ADD_TO_DICT(str_size);
- MEM_USAGE_ADD_TO_DICT(obj_count);
- MEM_USAGE_ADD_TO_DICT(obj_size);
- MEM_USAGE_ADD_TO_DICT(prop_count);
- MEM_USAGE_ADD_TO_DICT(prop_size);
- MEM_USAGE_ADD_TO_DICT(shape_count);
- MEM_USAGE_ADD_TO_DICT(shape_size);
- MEM_USAGE_ADD_TO_DICT(js_func_count);
- MEM_USAGE_ADD_TO_DICT(js_func_size);
- MEM_USAGE_ADD_TO_DICT(js_func_code_size);
- MEM_USAGE_ADD_TO_DICT(js_func_pc2line_count);
- MEM_USAGE_ADD_TO_DICT(js_func_pc2line_size);
- MEM_USAGE_ADD_TO_DICT(c_func_count);
- MEM_USAGE_ADD_TO_DICT(array_count);
- MEM_USAGE_ADD_TO_DICT(fast_array_count);
- MEM_USAGE_ADD_TO_DICT(fast_array_elements);
- MEM_USAGE_ADD_TO_DICT(binary_object_count);
- MEM_USAGE_ADD_TO_DICT(binary_object_size);
- return dict;
- }
- // _quickjs.Context.gc
- //
- // Runs garbage collection.
- static PyObject *context_gc(ContextData *self) {
- JS_RunGC(self->runtime);
- Py_RETURN_NONE;
- }
- static PyObject *context_add_callable(ContextData *self, PyObject *args) {
- PyErr_WarnEx(PyExc_DeprecationWarning,
- "Context.add_callable is deprecated, use Context.get_global().set instead.", 1);
- PyObject *global = context_get_global(self);
- if (global == NULL) {
- return NULL;
- }
- PyObject *ret = object_set((ObjectData *)global, args);
- Py_DECREF(global);
- return ret;
- }
- // All methods of the _quickjs.Context class.
- static PyMethodDef context_methods[] = {
- {"eval", (PyCFunction)context_eval, METH_VARARGS, "Evaluates a Javascript string."},
- {"module",
- (PyCFunction)context_module,
- METH_VARARGS,
- "Evaluates a Javascript string as a module."},
- {"execute_pending_job", (PyCFunction)context_execute_pending_job, METH_NOARGS, "Executes a pending job."},
- {"parse_json", (PyCFunction)context_parse_json, METH_VARARGS, "Parses a JSON string."},
- {"get_global", (PyCFunction)context_get_global, METH_NOARGS, "Gets the Javascript global object."},
- {"get", (PyCFunction)context_get, METH_VARARGS, "Gets a Javascript global variable."},
- {"set", (PyCFunction)context_set, METH_VARARGS, "Sets a Javascript global variable."},
- {"set_memory_limit",
- (PyCFunction)context_set_memory_limit,
- METH_VARARGS,
- "Sets the memory limit in bytes."},
- {"set_time_limit",
- (PyCFunction)context_set_time_limit,
- METH_VARARGS,
- "Sets the CPU time limit in seconds (C function clock() is used)."},
- {"set_max_stack_size",
- (PyCFunction)context_set_max_stack_size,
- METH_VARARGS,
- "Sets the maximum stack size in bytes. Default is 256kB."},
- {"memory", (PyCFunction)context_memory, METH_NOARGS, "Returns the memory usage as a dict."},
- {"gc", (PyCFunction)context_gc, METH_NOARGS, "Runs garbage collection."},
- {"add_callable", (PyCFunction)context_add_callable, METH_VARARGS, "Wraps a Python callable."},
- {NULL} /* Sentinel */
- };
- // Define the _quickjs.Context type.
- static PyTypeObject Context = {PyVarObject_HEAD_INIT(NULL, 0).tp_name = "_quickjs.Context",
- .tp_doc = "Quickjs context",
- .tp_basicsize = sizeof(ContextData),
- .tp_itemsize = 0,
- .tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC,
- .tp_traverse = (traverseproc)context_traverse,
- .tp_clear = (inquiry)context_clear,
- .tp_new = context_new,
- .tp_dealloc = (destructor)context_dealloc,
- .tp_methods = context_methods};
- // All global methods in _quickjs.
- static PyMethodDef myextension_methods[] = {{"test", (PyCFunction)test, METH_NOARGS, NULL},
- {NULL, NULL}};
- // Define the _quickjs module.
- static struct PyModuleDef moduledef = {PyModuleDef_HEAD_INIT,
- "quickjs",
- NULL,
- sizeof(struct module_state),
- myextension_methods,
- NULL,
- NULL,
- NULL,
- NULL};
- // This function runs when the module is first imported.
- PyMODINIT_FUNC PyInit__quickjs(void) {
- if (PyType_Ready(&Context) < 0) {
- return NULL;
- }
- if (PyType_Ready(&Object) < 0) {
- return NULL;
- }
- PyObject *module = PyModule_Create(&moduledef);
- if (module == NULL) {
- return NULL;
- }
- JSException = PyErr_NewException("_quickjs.JSException", NULL, NULL);
- if (JSException == NULL) {
- return NULL;
- }
- StackOverflow = PyErr_NewException("_quickjs.StackOverflow", JSException, NULL);
- if (StackOverflow == NULL) {
- return NULL;
- }
- Py_INCREF(&Context);
- PyModule_AddObject(module, "Context", (PyObject *)&Context);
- Py_INCREF(&Object);
- PyModule_AddObject(module, "Object", (PyObject *)&Object);
- PyModule_AddObject(module, "JSException", JSException);
- PyModule_AddObject(module, "StackOverflow", StackOverflow);
- return module;
- }
|