Skip to content

Commit 2fabb7c

Browse files
committed
Add a way of setting the maximum stack size.
1 parent 4bdcf8e commit 2fabb7c

File tree

4 files changed

+53
-2
lines changed

4 files changed

+53
-2
lines changed

module.c

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ typedef struct {
2121

2222
// The exception raised by this module.
2323
static PyObject *JSException = NULL;
24+
static PyObject *StackOverflow = NULL;
2425
// Converts a JSValue to a Python object.
2526
//
2627
// Takes ownership of the JSValue and will deallocate it (refcount reduced by 1).
@@ -210,7 +211,11 @@ static PyObject *quickjs_to_python(ContextData *context_obj, JSValue value) {
210211
JSValue exception = JS_GetException(context);
211212
JSValue error_string = JS_ToString(context, exception);
212213
const char *cstring = JS_ToCString(context, error_string);
213-
PyErr_Format(JSException, "%s", cstring);
214+
if (strstr(cstring, "stack overflow") != NULL) {
215+
PyErr_Format(StackOverflow, "%s", cstring);
216+
} else {
217+
PyErr_Format(JSException, "%s", cstring);
218+
}
214219
JS_FreeCString(context, cstring);
215220
JS_FreeValue(context, error_string);
216221
JS_FreeValue(context, exception);
@@ -349,6 +354,18 @@ static PyObject *context_set_time_limit(ContextData *self, PyObject *args) {
349354
Py_RETURN_NONE;
350355
}
351356

357+
// _quickjs.Context.set_max_stack_size
358+
//
359+
// Sets the max stack size in bytes.
360+
static PyObject *context_set_max_stack_size(ContextData *self, PyObject *args) {
361+
Py_ssize_t limit;
362+
if (!PyArg_ParseTuple(args, "n", &limit)) {
363+
return NULL;
364+
}
365+
JS_SetMaxStackSize(self->context, limit);
366+
Py_RETURN_NONE;
367+
}
368+
352369
// _quickjs.Context.memory
353370
//
354371
// Sets the CPU time limit of the context. This will be used in an interrupt handler.
@@ -420,6 +437,10 @@ static PyMethodDef context_methods[] = {
420437
(PyCFunction)context_set_time_limit,
421438
METH_VARARGS,
422439
"Sets the CPU time limit in seconds (C function clock() is used)."},
440+
{"set_max_stack_size",
441+
(PyCFunction)context_set_max_stack_size,
442+
METH_VARARGS,
443+
"Sets the maximum stack size in bytes. Default is 256kB."},
423444
{"memory", (PyCFunction)context_memory, METH_NOARGS, "Returns the memory usage as a dict."},
424445
{"gc", (PyCFunction)context_gc, METH_NOARGS, "Runs garbage collection."},
425446
{NULL} /* Sentinel */
@@ -468,11 +489,16 @@ PyMODINIT_FUNC PyInit__quickjs(void) {
468489
if (JSException == NULL) {
469490
return NULL;
470491
}
492+
StackOverflow = PyErr_NewException("_quickjs.StackOverflow", JSException, NULL);
493+
if (StackOverflow == NULL) {
494+
return NULL;
495+
}
471496

472497
Py_INCREF(&Context);
473498
PyModule_AddObject(module, "Context", (PyObject *)&Context);
474499
Py_INCREF(&Object);
475500
PyModule_AddObject(module, "Object", (PyObject *)&Object);
476501
PyModule_AddObject(module, "JSException", JSException);
502+
PyModule_AddObject(module, "StackOverflow", StackOverflow);
477503
return module;
478504
}

quickjs/__init__.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ def test():
1111
Context = _quickjs.Context
1212
Object = _quickjs.Object
1313
JSException = _quickjs.JSException
14+
StackOverflow = _quickjs.StackOverflow
1415

1516

1617
class Function:
@@ -32,6 +33,10 @@ def set_time_limit(self, limit):
3233
with self._lock:
3334
return self._context.set_time_limit(limit)
3435

36+
def set_max_stack_size(self, limit):
37+
with self._lock:
38+
return self._context.set_max_stack_size(limit)
39+
3540
def memory(self):
3641
with self._lock:
3742
return self._context.memory()

setup.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
# on computers where it is not installed.
2323
extra_link_args = ["-Wl,-Bstatic", "-lpthread"]
2424

25+
2526
def get_c_sources(include_headers=False):
2627
sources = ['module.c'] + glob.glob("third-party/*.c")
2728
if include_headers:
@@ -45,7 +46,7 @@ def get_c_sources(include_headers=False):
4546
author_email="petter.strandmark@gmail.com",
4647
name='quickjs',
4748
url='https://github.com/PetterS/quickjs',
48-
version='1.3.1',
49+
version='1.4.0',
4950
description='Wrapping the quickjs C library.',
5051
long_description=long_description,
5152
packages=["quickjs"],

test_quickjs.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -308,6 +308,25 @@ def test_garbage_collection(self):
308308
f.gc()
309309
self.assertLessEqual(f.memory()["obj_count"], initial_count)
310310

311+
def test_deep_recursion(self):
312+
f = quickjs.Function(
313+
"f", """
314+
function f(v) {
315+
if (v <= 0) {
316+
return 0;
317+
} else {
318+
return 1 + f(v - 1);
319+
}
320+
}
321+
""")
322+
323+
self.assertEqual(f(100), 100)
324+
limit = 500
325+
with self.assertRaises(quickjs.StackOverflow):
326+
f(limit)
327+
f.set_max_stack_size(2000 * limit)
328+
self.assertEqual(f(limit), limit)
329+
311330

312331
class Strings(unittest.TestCase):
313332
def test_unicode(self):

0 commit comments

Comments
 (0)