diff --git a/spidermonkey/context.c b/spidermonkey/context.c index 81c4a5f..b5bf779 100644 --- a/spidermonkey/context.c +++ b/spidermonkey/context.c @@ -16,6 +16,12 @@ // Forward decl for add_prop JSBool set_prop(JSContext* jscx, JSObject* jsobj, jsval key, jsval* rval); +typedef struct { + JSObject* scope; + int is_named; + jsval jskey; // cannot keep jsid here, because it gets GC'd and you cannot AddRoot jsid +} scope_struc; + JSBool add_prop(JSContext* jscx, JSObject* jsobj, jsval key, jsval* rval) { @@ -200,7 +206,7 @@ resolve(JSContext* jscx, JSObject* jsobj, jsval key) goto done; } - if(!js_DefineProperty(jscx, pycx->root, pid, JSVAL_VOID, NULL, NULL, + if(!js_DefineProperty(jscx, pycx->root0, pid, JSVAL_VOID, NULL, NULL, JSPROP_SHARED, NULL)) { JS_ReportError(jscx, "Failed to define property."); @@ -217,7 +223,7 @@ done: static JSClass js_global_class = { "JSGlobalObjectClass", - JSCLASS_GLOBAL_FLAGS, + JSCLASS_HAS_PRIVATE | JSCLASS_GLOBAL_FLAGS, add_prop, del_prop, get_prop, @@ -229,6 +235,21 @@ js_global_class = { JSCLASS_NO_OPTIONAL_MEMBERS }; +static JSClass +js_scope_class = { + "JSScopeObjectClass", + JSCLASS_HAS_PRIVATE, + JS_PropertyStub, + JS_PropertyStub, + JS_PropertyStub, + JS_PropertyStub, + JS_EnumerateStub, + JS_ResolveStub, + JS_ConvertStub, + JS_FinalizeStub, + JSCLASS_NO_OPTIONAL_MEMBERS +}; + #define MAX(a, b) ((a) > (b) ? (a) : (b)) JSBool branch_cb(JSContext* jscx, JSScript* script) @@ -354,6 +375,7 @@ Context_new(PyTypeObject* type, PyObject* args, PyObject* kwargs) PyErr_SetString(PyExc_RuntimeError, "Error creating root object."); goto error; } + self->root0 = self->root; if(!JS_InitStandardClasses(self->cx, self->root)) { @@ -403,6 +425,31 @@ Context_init(Context* self, PyObject* args, PyObject* kwargs) void Context_dealloc(Context* self) { + + JSObject *parent; + + if(self->root) + { + JS_BeginRequest(self->cx); + while ((parent = JS_GetParent(self->cx, self->root)) != NULL) + { + scope_struc* ss = (scope_struc *) JS_GetPrivate(self->cx, parent); + + if(!JS_RemoveRoot(self->cx, &ss->scope)) + { + PyErr_SetString(PyExc_RuntimeError, "Could not remove scope from GC roots."); + } + if(ss->is_named && !JS_RemoveRoot(self->cx, &ss->jskey)) + { + PyErr_SetString(PyExc_RuntimeError, "Could not remove scope name from GC roots."); + } + free(ss); + + self->root = parent; + } + JS_EndRequest(self->cx); + } + if(self->cx != NULL) { JS_DestroyContext(self->cx); @@ -440,7 +487,7 @@ Context_add_global(Context* self, PyObject* args, PyObject* kwargs) jsv = py2js(self, pyval); if(jsv == JSVAL_VOID) goto error; - if(!js_SetProperty(self->cx, self->root, kid, &jsv)) + if(!js_SetProperty(self->cx, self->root0, kid, &jsv)) { PyErr_SetString(PyExc_AttributeError, "Failed to set global property."); goto error; @@ -475,7 +522,7 @@ Context_rem_global(Context* self, PyObject* args, PyObject* kwargs) PyErr_SetString(JSError, "Failed to create key id."); } - if(!js_GetProperty(self->cx, self->root, kid, &jsv)) + if(!js_GetProperty(self->cx, self->root0, kid, &jsv)) { PyErr_SetString(JSError, "Failed to get global property."); goto error; @@ -484,7 +531,7 @@ Context_rem_global(Context* self, PyObject* args, PyObject* kwargs) ret = js2py(self, jsv); if(ret == NULL) goto error; - if(!js_DeleteProperty(self->cx, self->root, kid, &jsv)) + if(!js_DeleteProperty(self->cx, self->root0, kid, &jsv)) { PyErr_SetString(JSError, "Failed to remove global property."); goto error; @@ -598,6 +645,141 @@ success: } PyObject* +Context_enter_scope(Context* self, PyObject* args, PyObject* kwargs) +{ + PyObject* ret = NULL; + PyObject* pykey = NULL; + jsval jsk = JSVAL_VOID; + jsid kid; + jsval jsv; + + JS_BeginRequest(self->cx); + + // Prepare name, if any + if(!PyArg_ParseTuple(args, "|O", &pykey)) goto error; + if(pykey != NULL) + { + jsk = py2js(self, pykey); + if(jsk == JSVAL_VOID) goto error; + if(!JS_ValueToId(self->cx, jsk, &kid)) + { + PyErr_SetString(PyExc_AttributeError, "Failed to create scope key id."); + goto error; + } + } + + // Make scope + scope_struc* ss = (scope_struc*) malloc(sizeof(scope_struc)); + ss->is_named = 0; + ss->scope = JS_NewObject(self->cx, &js_scope_class, NULL, self->root); + ss->jskey = jsk; + if(ss->scope == NULL) + { + PyErr_SetString(PyExc_RuntimeError, "Error creating new scope object."); + goto error; + } + if(!JS_AddRoot(self->cx, &ss->scope)) + { + PyErr_SetString(PyExc_RuntimeError, "Error adding scope to GC roots."); + goto error; + } + if(!JS_SetPrivate(self->cx, self->root, ss)) + { + PyErr_SetString(PyExc_RuntimeError, "Error setting private scope field."); + goto error; + } + + // Add to globals under name + if(pykey != NULL) + { + jsv = OBJECT_TO_JSVAL((ss->scope)); + if(!js_SetProperty(self->cx, self->root0, kid, &jsv)) + { + PyErr_SetString(PyExc_AttributeError, "Failed to set scope as global property."); + goto error; + } + ss->is_named = 1; + JS_AddRoot(self->cx, &ss->jskey); + } + + self->root = ss->scope; + + JS_EndRequest(self->cx); + ret = Py_None; + Py_INCREF(ret); + goto success; + +error: + JS_EndRequest(self->cx); +success: + return ret; +} + +PyObject* +Context_leave_scope(Context* self, PyObject* args, PyObject* kwargs) +{ + PyObject* ret = NULL; + scope_struc* ss = NULL; + + JS_BeginRequest(self->cx); + + // Get parent scope and remove from GC root + JSObject *old_root = JS_GetParent(self->cx, self->root); + if(old_root == NULL) + { + PyErr_SetString(PyExc_RuntimeError, "Cannot leave scope if it was not entered: root has no parent."); + goto error; + } + ss = (scope_struc *) JS_GetPrivate(self->cx, old_root); + if(!JS_RemoveRoot(self->cx, &ss->scope)) + { + PyErr_SetString(PyExc_RuntimeError, "Error removing scope from GC roots."); + goto error; + } + + // Remove from globals if named + if(ss->is_named) + { + jsval jsv; + jsid kid; + + if(!JS_ValueToId(self->cx, ss->jskey, &kid)) + { + PyErr_SetString(PyExc_AttributeError, "Failed to create scope key id."); + goto error; + } + + if(!js_GetProperty(self->cx, self->root0, kid, &jsv)) + { + PyErr_SetString(JSError, "Failed to get scoep global property."); + goto error; + } + // Did somebody else remove this property manually + if(!(jsv == JSVAL_NULL || jsv == JSVAL_VOID)) + { + if(!js_DeleteProperty(self->cx, self->root0, kid, &jsv)) + { + PyErr_SetString(JSError, "Failed to remove scope global property."); + goto error; + } + } + JS_RemoveRoot(self->cx, &ss->jskey); + } + + self->root = old_root; + JS_EndRequest(self->cx); + ret = Py_None; + Py_INCREF(ret); + goto success; + +error: + JS_EndRequest(self->cx); +success: + if(ss) free(ss); + return ret; +} + +PyObject* Context_gc(Context* self, PyObject* args, PyObject* kwargs) { JS_GC(self->cx); @@ -687,6 +869,18 @@ static PyMethodDef Context_methods[] = { METH_VARARGS, "Get/Set the maximum time a context can execute for." }, + { + "enter_scope", + (PyCFunction)Context_enter_scope, + METH_VARARGS, + "Enter a new scope onto the scope stack." + }, + { + "leave_scope", + (PyCFunction)Context_leave_scope, + METH_VARARGS, + "Leave the current scope by removing it from the scope stack." + }, {NULL} }; diff --git a/spidermonkey/context.h b/spidermonkey/context.h index b8b0921..9c8f602 100644 --- a/spidermonkey/context.h +++ b/spidermonkey/context.h @@ -27,6 +27,7 @@ typedef struct { long max_heap; time_t max_time; time_t start_time; + JSObject* root0; } Context; PyObject* Context_get_class(Context* cx, const char* key);