module.c 28 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830
  1. #include <Python.h>
  2. #include <time.h>
  3. #include "third-party/quickjs.h"
  4. // Node of Python callable that the context needs to keep available.
  5. typedef struct PythonCallableNode PythonCallableNode;
  6. struct PythonCallableNode {
  7. PyObject *obj;
  8. // Internal ID of the callable function. "magic" is QuickJS terminology.
  9. int magic;
  10. PythonCallableNode *next;
  11. };
  12. // Keeps track of the time if we are using a time limit.
  13. typedef struct {
  14. clock_t start;
  15. clock_t limit;
  16. } InterruptData;
  17. // The data of the type _quickjs.Context.
  18. typedef struct {
  19. PyObject_HEAD JSRuntime *runtime;
  20. JSContext *context;
  21. int has_time_limit;
  22. clock_t time_limit;
  23. // Used when releasing the GIL.
  24. PyThreadState *thread_state;
  25. InterruptData interrupt_data;
  26. // NULL-terminated singly linked list of callable Python objects that we need to keep alive.
  27. PythonCallableNode *python_callables;
  28. } ContextData;
  29. // The data of the type _quickjs.Object.
  30. typedef struct {
  31. PyObject_HEAD;
  32. ContextData *context;
  33. JSValue object;
  34. } ObjectData;
  35. // The exception raised by this module.
  36. static PyObject *JSException = NULL;
  37. static PyObject *StackOverflow = NULL;
  38. // Converts a JSValue to a Python object.
  39. //
  40. // Takes ownership of the JSValue and will deallocate it (refcount reduced by 1).
  41. static PyObject *quickjs_to_python(ContextData *context_obj, JSValue value);
  42. // Whether converting item to QuickJS would be possible.
  43. static int python_to_quickjs_possible(ContextData *context, PyObject *item);
  44. // Converts item to QuickJS.
  45. //
  46. // If the Python object is not possible to convert to JS, undefined will be returned. This fallback
  47. // will not be used if python_to_quickjs_possible returns 1.
  48. static JSValueConst python_to_quickjs(ContextData *context, PyObject *item);
  49. static PyTypeObject Object;
  50. // Returns nonzero if we should stop due to a time limit.
  51. static int js_interrupt_handler(JSRuntime *rt, void *opaque) {
  52. InterruptData *data = opaque;
  53. if (clock() - data->start >= data->limit) {
  54. return 1;
  55. } else {
  56. return 0;
  57. }
  58. }
  59. // Sets up a context and an InterruptData struct if the context has a time limit.
  60. static void setup_time_limit(ContextData *context, InterruptData *interrupt_data) {
  61. if (context->has_time_limit) {
  62. JS_SetInterruptHandler(context->runtime, js_interrupt_handler, interrupt_data);
  63. interrupt_data->limit = context->time_limit;
  64. interrupt_data->start = clock();
  65. }
  66. }
  67. // Restores the context if the context has a time limit.
  68. static void teardown_time_limit(ContextData *context) {
  69. if (context->has_time_limit) {
  70. JS_SetInterruptHandler(context->runtime, NULL, NULL);
  71. }
  72. }
  73. // This method is always called in a context before running JS code in QuickJS. It sets up time
  74. // limites, releases the GIL etc.
  75. static void prepare_call_js(ContextData *context) {
  76. // We release the GIL in order to speed things up for certain use cases.
  77. assert(!context->thread_state);
  78. context->thread_state = PyEval_SaveThread();
  79. setup_time_limit(context, &context->interrupt_data);
  80. }
  81. // This method is called right after returning from running JS code. Aquires the GIL etc.
  82. static void end_call_js(ContextData *context) {
  83. teardown_time_limit(context);
  84. assert(context->thread_state);
  85. PyEval_RestoreThread(context->thread_state);
  86. context->thread_state = NULL;
  87. }
  88. // Called when Python is called again from inside QuickJS.
  89. static void prepare_call_python(ContextData *context) {
  90. assert(context->thread_state);
  91. PyEval_RestoreThread(context->thread_state);
  92. context->thread_state = NULL;
  93. }
  94. // Called when the operation started by prepare_call_python is done.
  95. static void end_call_python(ContextData *context) {
  96. assert(!context->thread_state);
  97. context->thread_state = PyEval_SaveThread();
  98. }
  99. // GC traversal.
  100. static int object_traverse(ObjectData *self, visitproc visit, void *arg) {
  101. Py_VISIT(self->context);
  102. return 0;
  103. }
  104. // Creates an instance of the Object class.
  105. static PyObject *object_new(PyTypeObject *type, PyObject *args, PyObject *kwds) {
  106. ObjectData *self = PyObject_GC_New(ObjectData, type);
  107. if (self != NULL) {
  108. self->context = NULL;
  109. }
  110. return (PyObject *)self;
  111. }
  112. // Deallocates an instance of the Object class.
  113. static void object_dealloc(ObjectData *self) {
  114. if (self->context) {
  115. PyObject_GC_UnTrack(self);
  116. JS_FreeValue(self->context->context, self->object);
  117. // We incremented the refcount of the context when we created this object, so we should
  118. // decrease it now so we don't leak memory.
  119. Py_DECREF(self->context);
  120. }
  121. PyObject_GC_Del(self);
  122. }
  123. // _quickjs.Object.get
  124. //
  125. // Gets a Javascript property of the object.
  126. static PyObject *object_get(ObjectData *self, PyObject *args) {
  127. const char *name;
  128. if (!PyArg_ParseTuple(args, "s", &name)) {
  129. return NULL;
  130. }
  131. JSValue value = JS_GetPropertyStr(self->context->context, self->object, name);
  132. return quickjs_to_python(self->context, value);
  133. }
  134. static JSValue js_c_function(
  135. JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv, int magic) {
  136. ContextData *context = (ContextData *)JS_GetContextOpaque(ctx);
  137. if (context->has_time_limit) {
  138. return JS_ThrowInternalError(ctx, "Can not call into Python with a time limit set.");
  139. }
  140. PythonCallableNode *node = context->python_callables;
  141. while (node && node->magic != magic) {
  142. node = node->next;
  143. }
  144. if (!node) {
  145. return JS_ThrowInternalError(ctx, "Internal error.");
  146. }
  147. prepare_call_python(context);
  148. PyObject *args = PyTuple_New(argc);
  149. if (!args) {
  150. end_call_python(context);
  151. return JS_ThrowOutOfMemory(ctx);
  152. }
  153. int tuple_success = 1;
  154. for (int i = 0; i < argc; ++i) {
  155. PyObject *arg = quickjs_to_python(context, JS_DupValue(ctx, argv[i]));
  156. if (!arg) {
  157. tuple_success = 0;
  158. break;
  159. }
  160. PyTuple_SET_ITEM(args, i, arg);
  161. }
  162. if (!tuple_success) {
  163. Py_DECREF(args);
  164. end_call_python(context);
  165. return JS_ThrowInternalError(ctx, "Internal error: could not convert args.");
  166. }
  167. PyObject *result = PyObject_CallObject(node->obj, args);
  168. Py_DECREF(args);
  169. if (!result) {
  170. end_call_python(context);
  171. return JS_ThrowInternalError(ctx, "Python call failed.");
  172. }
  173. JSValue js_result = JS_NULL;
  174. if (python_to_quickjs_possible(context, result)) {
  175. js_result = python_to_quickjs(context, result);
  176. } else {
  177. PyErr_Clear();
  178. js_result = JS_ThrowInternalError(ctx, "Can not convert Python result to JS.");
  179. }
  180. Py_DECREF(result);
  181. end_call_python(context);
  182. return js_result;
  183. }
  184. // _quickjs.Object.set
  185. //
  186. // Sets a Javascript property to the object. Callables are supported.
  187. static PyObject *object_set(ObjectData *self, PyObject *args) {
  188. const char *name;
  189. PyObject *item;
  190. if (!PyArg_ParseTuple(args, "sO", &name, &item)) {
  191. return NULL;
  192. }
  193. int ret = 0;
  194. if (PyCallable_Check(item) && (!PyObject_IsInstance(item, (PyObject *)&Object) || JS_IsFunction(
  195. self->context->context, ((ObjectData *)item)->object))) {
  196. PythonCallableNode *node = PyMem_Malloc(sizeof(PythonCallableNode));
  197. if (!node) {
  198. return NULL;
  199. }
  200. Py_INCREF(item);
  201. node->magic = 0;
  202. if (self->context->python_callables) {
  203. node->magic = self->context->python_callables->magic + 1;
  204. }
  205. node->obj = item;
  206. node->next = self->context->python_callables;
  207. self->context->python_callables = node;
  208. JSValue function = JS_NewCFunctionMagic(
  209. self->context->context,
  210. js_c_function,
  211. name,
  212. 0, // TODO: Should we allow setting the .length of the function to something other than 0?
  213. JS_CFUNC_generic_magic,
  214. node->magic);
  215. // If this fails we don't notify the caller of this function.
  216. ret = JS_SetPropertyStr(self->context->context, self->object, name, function);
  217. if (ret != 1) {
  218. PyErr_SetString(PyExc_TypeError, "Failed setting the variable as a callable.");
  219. return NULL;
  220. } else {
  221. Py_RETURN_NONE;
  222. }
  223. } else {
  224. if (python_to_quickjs_possible(self->context, item)) {
  225. ret = JS_SetPropertyStr(self->context->context, self->object, name,
  226. python_to_quickjs(self->context, item));
  227. if (ret != 1) {
  228. PyErr_SetString(PyExc_TypeError, "Failed setting the variable.");
  229. }
  230. }
  231. if (ret == 1) {
  232. Py_RETURN_NONE;
  233. } else {
  234. return NULL;
  235. }
  236. }
  237. }
  238. // _quickjs.Object.__call__
  239. static PyObject *object_call(ObjectData *self, PyObject *args, PyObject *kwds);
  240. // _quickjs.Object.json
  241. //
  242. // Returns the JSON representation of the object as a Python string.
  243. static PyObject *object_json(ObjectData *self) {
  244. JSContext *context = self->context->context;
  245. JSValue json_string = JS_JSONStringify(context, self->object, JS_UNDEFINED, JS_UNDEFINED);
  246. return quickjs_to_python(self->context, json_string);
  247. }
  248. // All methods of the _quickjs.Object class.
  249. static PyMethodDef object_methods[] = {
  250. {"get", (PyCFunction)object_get, METH_VARARGS, "Gets a Javascript property of the object."},
  251. {"set", (PyCFunction)object_set, METH_VARARGS, "Sets a Javascript property to the object."},
  252. {"json", (PyCFunction)object_json, METH_NOARGS, "Converts to a JSON string."},
  253. {NULL} /* Sentinel */
  254. };
  255. // Define the quickjs.Object type.
  256. static PyTypeObject Object = {PyVarObject_HEAD_INIT(NULL, 0).tp_name = "_quickjs.Object",
  257. .tp_doc = "Quickjs object",
  258. .tp_basicsize = sizeof(ObjectData),
  259. .tp_itemsize = 0,
  260. .tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC,
  261. .tp_traverse = (traverseproc)object_traverse,
  262. .tp_new = object_new,
  263. .tp_dealloc = (destructor)object_dealloc,
  264. .tp_call = (ternaryfunc)object_call,
  265. .tp_methods = object_methods};
  266. // Whether converting item to QuickJS would be possible.
  267. static int python_to_quickjs_possible(ContextData *context, PyObject *item) {
  268. if (PyBool_Check(item)) {
  269. return 1;
  270. } else if (PyLong_Check(item)) {
  271. return 1;
  272. } else if (PyFloat_Check(item)) {
  273. return 1;
  274. } else if (item == Py_None) {
  275. return 1;
  276. } else if (PyUnicode_Check(item)) {
  277. return 1;
  278. } else if (PyObject_IsInstance(item, (PyObject *)&Object)) {
  279. ObjectData *object = (ObjectData *)item;
  280. if (object->context != context) {
  281. PyErr_Format(PyExc_ValueError, "Can not mix JS objects from different contexts.");
  282. return 0;
  283. }
  284. return 1;
  285. } else {
  286. PyErr_Format(PyExc_TypeError,
  287. "Unsupported type when converting a Python object to quickjs: %s.",
  288. Py_TYPE(item)->tp_name);
  289. return 0;
  290. }
  291. }
  292. // Converts item to QuickJS.
  293. //
  294. // If the Python object is not possible to convert to JS, undefined will be returned. This fallback
  295. // will not be used if python_to_quickjs_possible returns 1.
  296. static JSValueConst python_to_quickjs(ContextData *context, PyObject *item) {
  297. if (PyBool_Check(item)) {
  298. return JS_MKVAL(JS_TAG_BOOL, item == Py_True ? 1 : 0);
  299. } else if (PyLong_Check(item)) {
  300. int overflow;
  301. long value = PyLong_AsLongAndOverflow(item, &overflow);
  302. if (overflow) {
  303. PyObject *float_value = PyNumber_Float(item);
  304. double double_value = PyFloat_AsDouble(float_value);
  305. Py_DECREF(float_value);
  306. return JS_NewFloat64(context->context, double_value);
  307. } else {
  308. return JS_MKVAL(JS_TAG_INT, value);
  309. }
  310. } else if (PyFloat_Check(item)) {
  311. return JS_NewFloat64(context->context, PyFloat_AsDouble(item));
  312. } else if (item == Py_None) {
  313. return JS_NULL;
  314. } else if (PyUnicode_Check(item)) {
  315. return JS_NewString(context->context, PyUnicode_AsUTF8(item));
  316. } else if (PyObject_IsInstance(item, (PyObject *)&Object)) {
  317. return JS_DupValue(context->context, ((ObjectData *)item)->object);
  318. } else {
  319. // Can not happen if python_to_quickjs_possible passes.
  320. return JS_UNDEFINED;
  321. }
  322. }
  323. // _quickjs.Object.__call__
  324. static PyObject *object_call(ObjectData *self, PyObject *args, PyObject *kwds) {
  325. if (self->context == NULL) {
  326. // This object does not have a context and has not been created by this module.
  327. Py_RETURN_NONE;
  328. }
  329. // We first loop through all arguments and check that they are supported without doing anything.
  330. // This makes the cleanup code simpler for the case where we have to raise an error.
  331. const int nargs = PyTuple_Size(args);
  332. for (int i = 0; i < nargs; ++i) {
  333. PyObject *item = PyTuple_GetItem(args, i);
  334. if (!python_to_quickjs_possible(self->context, item)) {
  335. return NULL;
  336. }
  337. }
  338. // Now we know that all arguments are supported and we can convert them.
  339. JSValueConst *jsargs = malloc(nargs * sizeof(JSValueConst));
  340. for (int i = 0; i < nargs; ++i) {
  341. PyObject *item = PyTuple_GetItem(args, i);
  342. jsargs[i] = python_to_quickjs(self->context, item);
  343. }
  344. prepare_call_js(self->context);
  345. JSValue value;
  346. value = JS_Call(self->context->context, self->object, JS_NULL, nargs, jsargs);
  347. for (int i = 0; i < nargs; ++i) {
  348. JS_FreeValue(self->context->context, jsargs[i]);
  349. }
  350. free(jsargs);
  351. end_call_js(self->context);
  352. return quickjs_to_python(self->context, value);
  353. }
  354. // Converts the current Javascript exception to a Python exception via a C string.
  355. static void quickjs_exception_to_python(JSContext *context) {
  356. JSValue exception = JS_GetException(context);
  357. const char *cstring = JS_ToCString(context, exception);
  358. const char *stack_cstring = NULL;
  359. if (!JS_IsNull(exception) && !JS_IsUndefined(exception)) {
  360. JSValue stack = JS_GetPropertyStr(context, exception, "stack");
  361. if (!JS_IsException(stack)) {
  362. stack_cstring = JS_ToCString(context, stack);
  363. JS_FreeValue(context, stack);
  364. }
  365. }
  366. if (cstring != NULL) {
  367. const char *safe_stack_cstring = stack_cstring ? stack_cstring : "";
  368. if (strstr(cstring, "stack overflow") != NULL) {
  369. PyErr_Format(StackOverflow, "%s\n%s", cstring, safe_stack_cstring);
  370. } else {
  371. PyErr_Format(JSException, "%s\n%s", cstring, safe_stack_cstring);
  372. }
  373. } else {
  374. // This has been observed to happen when different threads have used the same QuickJS
  375. // runtime, but not at the same time.
  376. // Could potentially be another problem though, since JS_ToCString may return NULL.
  377. PyErr_Format(JSException,
  378. "(Failed obtaining QuickJS error string. Concurrency issue?)");
  379. }
  380. JS_FreeCString(context, cstring);
  381. JS_FreeCString(context, stack_cstring);
  382. JS_FreeValue(context, exception);
  383. }
  384. // Converts a JSValue to a Python object.
  385. //
  386. // Takes ownership of the JSValue and will deallocate it (refcount reduced by 1).
  387. static PyObject *quickjs_to_python(ContextData *context_obj, JSValue value) {
  388. JSContext *context = context_obj->context;
  389. int tag = JS_VALUE_GET_TAG(value);
  390. // A return value of NULL means an exception.
  391. PyObject *return_value = NULL;
  392. if (tag == JS_TAG_INT) {
  393. return_value = Py_BuildValue("i", JS_VALUE_GET_INT(value));
  394. } else if (tag == JS_TAG_BIG_INT) {
  395. const char *cstring = JS_ToCString(context, value);
  396. return_value = PyLong_FromString(cstring, NULL, 10);
  397. JS_FreeCString(context, cstring);
  398. } else if (tag == JS_TAG_BOOL) {
  399. return_value = Py_BuildValue("O", JS_VALUE_GET_BOOL(value) ? Py_True : Py_False);
  400. } else if (tag == JS_TAG_NULL) {
  401. return_value = Py_None;
  402. } else if (tag == JS_TAG_UNDEFINED) {
  403. return_value = Py_None;
  404. } else if (tag == JS_TAG_EXCEPTION) {
  405. quickjs_exception_to_python(context);
  406. } else if (tag == JS_TAG_FLOAT64) {
  407. return_value = Py_BuildValue("d", JS_VALUE_GET_FLOAT64(value));
  408. } else if (tag == JS_TAG_STRING) {
  409. const char *cstring = JS_ToCString(context, value);
  410. return_value = Py_BuildValue("s", cstring);
  411. JS_FreeCString(context, cstring);
  412. } else if (tag == JS_TAG_OBJECT || tag == JS_TAG_MODULE || tag == JS_TAG_SYMBOL) {
  413. // This is a Javascript object or function. We wrap it in a _quickjs.Object.
  414. return_value = PyObject_CallObject((PyObject *)&Object, NULL);
  415. ObjectData *object = (ObjectData *)return_value;
  416. // This is important. Otherwise, the context may be deallocated before the object, which
  417. // will result in a segfault with high probability.
  418. Py_INCREF(context_obj);
  419. object->context = context_obj;
  420. PyObject_GC_Track(object);
  421. object->object = JS_DupValue(context, value);
  422. } else {
  423. PyErr_Format(PyExc_TypeError, "Unknown quickjs tag: %d", tag);
  424. }
  425. JS_FreeValue(context, value);
  426. if (return_value == Py_None) {
  427. // Can not simply return PyNone for refcounting reasons.
  428. Py_RETURN_NONE;
  429. }
  430. return return_value;
  431. }
  432. static PyObject *test(PyObject *self, PyObject *args) {
  433. return Py_BuildValue("i", 42);
  434. }
  435. // Global state of the module. Currently none.
  436. struct module_state {};
  437. // GC traversal.
  438. static int context_traverse(ContextData *self, visitproc visit, void *arg) {
  439. PythonCallableNode *node = self->python_callables;
  440. while (node) {
  441. Py_VISIT(node->obj);
  442. node = node->next;
  443. }
  444. return 0;
  445. }
  446. // GC clearing. Object does not have a clearing method, therefore dependency cycles
  447. // between Context and Object will always be cleared starting here.
  448. static int context_clear(ContextData *self) {
  449. PythonCallableNode *node = self->python_callables;
  450. while (node) {
  451. Py_CLEAR(node->obj);
  452. node = node->next;
  453. }
  454. return 0;
  455. }
  456. // Creates an instance of the _quickjs.Context class.
  457. static PyObject *context_new(PyTypeObject *type, PyObject *args, PyObject *kwds) {
  458. ContextData *self = PyObject_GC_New(ContextData, type);
  459. if (self != NULL) {
  460. // We never have different contexts for the same runtime. This way, different
  461. // _quickjs.Context can be used concurrently.
  462. self->runtime = JS_NewRuntime();
  463. self->context = JS_NewContext(self->runtime);
  464. self->has_time_limit = 0;
  465. self->time_limit = 0;
  466. self->thread_state = NULL;
  467. self->python_callables = NULL;
  468. JS_SetContextOpaque(self->context, self);
  469. PyObject_GC_Track(self);
  470. }
  471. return (PyObject *)self;
  472. }
  473. // Deallocates an instance of the _quickjs.Context class.
  474. static void context_dealloc(ContextData *self) {
  475. JS_FreeContext(self->context);
  476. JS_FreeRuntime(self->runtime);
  477. PyObject_GC_UnTrack(self);
  478. PythonCallableNode *node = self->python_callables;
  479. self->python_callables = NULL;
  480. while (node) {
  481. PythonCallableNode *this = node;
  482. node = node->next;
  483. // this->obj may already be NULL if GC'ed right before through context_clear.
  484. Py_XDECREF(this->obj);
  485. PyMem_Free(this);
  486. }
  487. PyObject_GC_Del(self);
  488. }
  489. // Evaluates a Python string as JS and returns the result as a Python object. Will return
  490. // _quickjs.Object for complex types (other than e.g. str, int).
  491. static PyObject *context_eval_internal(ContextData *self, PyObject *args, int eval_type) {
  492. const char *code;
  493. if (!PyArg_ParseTuple(args, "s", &code)) {
  494. return NULL;
  495. }
  496. prepare_call_js(self);
  497. JSValue value;
  498. value = JS_Eval(self->context, code, strlen(code), "<input>", eval_type);
  499. end_call_js(self);
  500. return quickjs_to_python(self, value);
  501. }
  502. // _quickjs.Context.eval
  503. //
  504. // Evaluates a Python string as JS and returns the result as a Python object. Will return
  505. // _quickjs.Object for complex types (other than e.g. str, int).
  506. static PyObject *context_eval(ContextData *self, PyObject *args) {
  507. return context_eval_internal(self, args, JS_EVAL_TYPE_GLOBAL);
  508. }
  509. // _quickjs.Context.module
  510. //
  511. // Evaluates a Python string as JS module. Otherwise identical to eval.
  512. static PyObject *context_module(ContextData *self, PyObject *args) {
  513. return context_eval_internal(self, args, JS_EVAL_TYPE_MODULE);
  514. }
  515. // _quickjs.Context.execute_pending_job
  516. //
  517. // If there are pending jobs, executes one and returns True. Else returns False.
  518. static PyObject *context_execute_pending_job(ContextData *self) {
  519. prepare_call_js(self);
  520. JSContext *ctx;
  521. int ret = JS_ExecutePendingJob(self->runtime, &ctx);
  522. end_call_js(self);
  523. if (ret > 0) {
  524. Py_RETURN_TRUE;
  525. } else if (ret == 0) {
  526. Py_RETURN_FALSE;
  527. } else {
  528. quickjs_exception_to_python(ctx);
  529. return NULL;
  530. }
  531. }
  532. // _quickjs.Context.parse_json
  533. //
  534. // Evaluates a Python string as JSON and returns the result as a Python object. Will
  535. // return _quickjs.Object for complex types (other than e.g. str, int).
  536. static PyObject *context_parse_json(ContextData *self, PyObject *args) {
  537. const char *data;
  538. if (!PyArg_ParseTuple(args, "s", &data)) {
  539. return NULL;
  540. }
  541. JSValue value;
  542. Py_BEGIN_ALLOW_THREADS;
  543. value = JS_ParseJSON(self->context, data, strlen(data), "context_parse_json.json");
  544. Py_END_ALLOW_THREADS;
  545. return quickjs_to_python(self, value);
  546. }
  547. // _quickjs.Context.get_global
  548. //
  549. // Retrieves the global object of the JS context.
  550. static PyObject *context_get_global(ContextData *self) {
  551. return quickjs_to_python(self, JS_GetGlobalObject(self->context));
  552. }
  553. // _quickjs.Context.get
  554. //
  555. // Retrieves a global variable from the JS context.
  556. static PyObject *context_get(ContextData *self, PyObject *args) {
  557. PyErr_WarnEx(PyExc_DeprecationWarning,
  558. "Context.get is deprecated, use Context.get_global().get instead.", 1);
  559. PyObject *global = context_get_global(self);
  560. if (global == NULL) {
  561. return NULL;
  562. }
  563. PyObject *ret = object_get((ObjectData *)global, args);
  564. Py_DECREF(global);
  565. return ret;
  566. }
  567. // _quickjs.Context.set
  568. //
  569. // Sets a global variable to the JS context.
  570. static PyObject *context_set(ContextData *self, PyObject *args) {
  571. PyErr_WarnEx(PyExc_DeprecationWarning,
  572. "Context.set is deprecated, use Context.get_global().set instead.", 1);
  573. PyObject *global = context_get_global(self);
  574. if (global == NULL) {
  575. return NULL;
  576. }
  577. PyObject *ret = object_set((ObjectData *)global, args);
  578. Py_DECREF(global);
  579. return ret;
  580. }
  581. // _quickjs.Context.set_memory_limit
  582. //
  583. // Sets the memory limit of the context.
  584. static PyObject *context_set_memory_limit(ContextData *self, PyObject *args) {
  585. Py_ssize_t limit;
  586. if (!PyArg_ParseTuple(args, "n", &limit)) {
  587. return NULL;
  588. }
  589. JS_SetMemoryLimit(self->runtime, limit);
  590. Py_RETURN_NONE;
  591. }
  592. // _quickjs.Context.set_time_limit
  593. //
  594. // Sets the CPU time limit of the context. This will be used in an interrupt handler.
  595. static PyObject *context_set_time_limit(ContextData *self, PyObject *args) {
  596. double limit;
  597. if (!PyArg_ParseTuple(args, "d", &limit)) {
  598. return NULL;
  599. }
  600. if (limit < 0) {
  601. self->has_time_limit = 0;
  602. } else {
  603. self->has_time_limit = 1;
  604. self->time_limit = (clock_t)(limit * CLOCKS_PER_SEC);
  605. }
  606. Py_RETURN_NONE;
  607. }
  608. // _quickjs.Context.set_max_stack_size
  609. //
  610. // Sets the max stack size in bytes.
  611. static PyObject *context_set_max_stack_size(ContextData *self, PyObject *args) {
  612. Py_ssize_t limit;
  613. if (!PyArg_ParseTuple(args, "n", &limit)) {
  614. return NULL;
  615. }
  616. JS_SetMaxStackSize(self->runtime, limit);
  617. Py_RETURN_NONE;
  618. }
  619. // _quickjs.Context.memory
  620. //
  621. // Sets the CPU time limit of the context. This will be used in an interrupt handler.
  622. static PyObject *context_memory(ContextData *self) {
  623. PyObject *dict = PyDict_New();
  624. if (dict == NULL) {
  625. return NULL;
  626. }
  627. JSMemoryUsage usage;
  628. JS_ComputeMemoryUsage(self->runtime, &usage);
  629. #define MEM_USAGE_ADD_TO_DICT(key) \
  630. { \
  631. PyObject *value = PyLong_FromLongLong(usage.key); \
  632. if (PyDict_SetItemString(dict, #key, value) != 0) { \
  633. return NULL; \
  634. } \
  635. Py_DECREF(value); \
  636. }
  637. MEM_USAGE_ADD_TO_DICT(malloc_size);
  638. MEM_USAGE_ADD_TO_DICT(malloc_limit);
  639. MEM_USAGE_ADD_TO_DICT(memory_used_size);
  640. MEM_USAGE_ADD_TO_DICT(malloc_count);
  641. MEM_USAGE_ADD_TO_DICT(memory_used_count);
  642. MEM_USAGE_ADD_TO_DICT(atom_count);
  643. MEM_USAGE_ADD_TO_DICT(atom_size);
  644. MEM_USAGE_ADD_TO_DICT(str_count);
  645. MEM_USAGE_ADD_TO_DICT(str_size);
  646. MEM_USAGE_ADD_TO_DICT(obj_count);
  647. MEM_USAGE_ADD_TO_DICT(obj_size);
  648. MEM_USAGE_ADD_TO_DICT(prop_count);
  649. MEM_USAGE_ADD_TO_DICT(prop_size);
  650. MEM_USAGE_ADD_TO_DICT(shape_count);
  651. MEM_USAGE_ADD_TO_DICT(shape_size);
  652. MEM_USAGE_ADD_TO_DICT(js_func_count);
  653. MEM_USAGE_ADD_TO_DICT(js_func_size);
  654. MEM_USAGE_ADD_TO_DICT(js_func_code_size);
  655. MEM_USAGE_ADD_TO_DICT(js_func_pc2line_count);
  656. MEM_USAGE_ADD_TO_DICT(js_func_pc2line_size);
  657. MEM_USAGE_ADD_TO_DICT(c_func_count);
  658. MEM_USAGE_ADD_TO_DICT(array_count);
  659. MEM_USAGE_ADD_TO_DICT(fast_array_count);
  660. MEM_USAGE_ADD_TO_DICT(fast_array_elements);
  661. MEM_USAGE_ADD_TO_DICT(binary_object_count);
  662. MEM_USAGE_ADD_TO_DICT(binary_object_size);
  663. return dict;
  664. }
  665. // _quickjs.Context.gc
  666. //
  667. // Runs garbage collection.
  668. static PyObject *context_gc(ContextData *self) {
  669. JS_RunGC(self->runtime);
  670. Py_RETURN_NONE;
  671. }
  672. static PyObject *context_add_callable(ContextData *self, PyObject *args) {
  673. PyErr_WarnEx(PyExc_DeprecationWarning,
  674. "Context.add_callable is deprecated, use Context.get_global().set instead.", 1);
  675. PyObject *global = context_get_global(self);
  676. if (global == NULL) {
  677. return NULL;
  678. }
  679. PyObject *ret = object_set((ObjectData *)global, args);
  680. Py_DECREF(global);
  681. return ret;
  682. }
  683. // All methods of the _quickjs.Context class.
  684. static PyMethodDef context_methods[] = {
  685. {"eval", (PyCFunction)context_eval, METH_VARARGS, "Evaluates a Javascript string."},
  686. {"module",
  687. (PyCFunction)context_module,
  688. METH_VARARGS,
  689. "Evaluates a Javascript string as a module."},
  690. {"execute_pending_job", (PyCFunction)context_execute_pending_job, METH_NOARGS, "Executes a pending job."},
  691. {"parse_json", (PyCFunction)context_parse_json, METH_VARARGS, "Parses a JSON string."},
  692. {"get_global", (PyCFunction)context_get_global, METH_NOARGS, "Gets the Javascript global object."},
  693. {"get", (PyCFunction)context_get, METH_VARARGS, "Gets a Javascript global variable."},
  694. {"set", (PyCFunction)context_set, METH_VARARGS, "Sets a Javascript global variable."},
  695. {"set_memory_limit",
  696. (PyCFunction)context_set_memory_limit,
  697. METH_VARARGS,
  698. "Sets the memory limit in bytes."},
  699. {"set_time_limit",
  700. (PyCFunction)context_set_time_limit,
  701. METH_VARARGS,
  702. "Sets the CPU time limit in seconds (C function clock() is used)."},
  703. {"set_max_stack_size",
  704. (PyCFunction)context_set_max_stack_size,
  705. METH_VARARGS,
  706. "Sets the maximum stack size in bytes. Default is 256kB."},
  707. {"memory", (PyCFunction)context_memory, METH_NOARGS, "Returns the memory usage as a dict."},
  708. {"gc", (PyCFunction)context_gc, METH_NOARGS, "Runs garbage collection."},
  709. {"add_callable", (PyCFunction)context_add_callable, METH_VARARGS, "Wraps a Python callable."},
  710. {NULL} /* Sentinel */
  711. };
  712. // Define the _quickjs.Context type.
  713. static PyTypeObject Context = {PyVarObject_HEAD_INIT(NULL, 0).tp_name = "_quickjs.Context",
  714. .tp_doc = "Quickjs context",
  715. .tp_basicsize = sizeof(ContextData),
  716. .tp_itemsize = 0,
  717. .tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC,
  718. .tp_traverse = (traverseproc)context_traverse,
  719. .tp_clear = (inquiry)context_clear,
  720. .tp_new = context_new,
  721. .tp_dealloc = (destructor)context_dealloc,
  722. .tp_methods = context_methods};
  723. // All global methods in _quickjs.
  724. static PyMethodDef myextension_methods[] = {{"test", (PyCFunction)test, METH_NOARGS, NULL},
  725. {NULL, NULL}};
  726. // Define the _quickjs module.
  727. static struct PyModuleDef moduledef = {PyModuleDef_HEAD_INIT,
  728. "quickjs",
  729. NULL,
  730. sizeof(struct module_state),
  731. myextension_methods,
  732. NULL,
  733. NULL,
  734. NULL,
  735. NULL};
  736. // This function runs when the module is first imported.
  737. PyMODINIT_FUNC PyInit__quickjs(void) {
  738. if (PyType_Ready(&Context) < 0) {
  739. return NULL;
  740. }
  741. if (PyType_Ready(&Object) < 0) {
  742. return NULL;
  743. }
  744. PyObject *module = PyModule_Create(&moduledef);
  745. if (module == NULL) {
  746. return NULL;
  747. }
  748. JSException = PyErr_NewException("_quickjs.JSException", NULL, NULL);
  749. if (JSException == NULL) {
  750. return NULL;
  751. }
  752. StackOverflow = PyErr_NewException("_quickjs.StackOverflow", JSException, NULL);
  753. if (StackOverflow == NULL) {
  754. return NULL;
  755. }
  756. Py_INCREF(&Context);
  757. PyModule_AddObject(module, "Context", (PyObject *)&Context);
  758. Py_INCREF(&Object);
  759. PyModule_AddObject(module, "Object", (PyObject *)&Object);
  760. PyModule_AddObject(module, "JSException", JSException);
  761. PyModule_AddObject(module, "StackOverflow", StackOverflow);
  762. return module;
  763. }