Skip to content

Commit

Permalink
Fixes global handling of "function foo() {}"
Browse files Browse the repository at this point in the history
JavaScript acts a bit odd in this case in that the body of 'foo' is passed to
JS_Class.add_property but not JS_Class.set_property. For now I added an add_prop
method that special cases when the passed in value is a function and delegates
to set_prop.

While I was at it, I went ahead and added support for deleting global properties
as well. If your global handler has __delitem__ defined, it will now get called
for the JavaScript statement "delete foo;"

Thanks to Riccardo Pelizzi for the bug report.
  • Loading branch information
davisp committed May 17, 2009
1 parent 1e5357f commit ee6cfb8
Show file tree
Hide file tree
Showing 4 changed files with 102 additions and 2 deletions.
4 changes: 4 additions & 0 deletions THANKS
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,7 @@ Mike West
* Reported bug in Context.max_time
* Better test for test_exceeds_time
* Reported missing pkg-config requirement

Riccardo Pelizzi
* Bug report for global handlers and "function foo() {}" syntax.

58 changes: 56 additions & 2 deletions spidermonkey/context.c
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,60 @@
#include <jsobj.h>
#include <jscntxt.h>

JSBool
add_prop(JSContext* jscx, JSObject* jsobj, jsval key, jsval* rval)
{
JSObject* obj = NULL;

if(!JSVAL_IS_OBJECT(*rval)) return JS_TRUE;

obj = JSVAL_TO_OBJECT(*rval);
if(JS_ObjectIsFunction(jscx, obj)) return set_prop(jscx, jsobj, key, rval);
return JS_TRUE;
}

JSBool
del_prop(JSContext* jscx, JSObject* jsobj, jsval key, jsval* rval)
{
Context* pycx = NULL;
PyObject* pykey = NULL;
PyObject* pyval = NULL;
JSBool ret = JS_FALSE;

pycx = (Context*) JS_GetContextPrivate(jscx);
if(pycx == NULL)
{
JS_ReportError(jscx, "Failed to get Python context.");
goto done;
}

// Bail if there's no registered global handler.
if(pycx->global == NULL)
{
ret = JS_TRUE;
goto done;
}

// Bail if the global doesn't have a __delitem__
if(!PyObject_HasAttrString(pycx->global, "__delitem__"))
{
ret = JS_TRUE;
goto done;
}

pykey = js2py(pycx, key);
if(pykey == NULL) goto done;

if(PyObject_DelItem(pycx->global, pykey) < 0) goto done;

ret = JS_TRUE;

done:
Py_XDECREF(pykey);
Py_XDECREF(pyval);
return ret;
}

JSBool
get_prop(JSContext* jscx, JSObject* jsobj, jsval key, jsval* rval)
{
Expand Down Expand Up @@ -152,8 +206,8 @@ static JSClass
js_global_class = {
"JSGlobalObjectClass",
JSCLASS_GLOBAL_FLAGS,
JS_PropertyStub,
JS_PropertyStub,
add_prop,
del_prop,
get_prop,
set_prop,
JS_EnumerateStub,
Expand Down
5 changes: 5 additions & 0 deletions tests/t.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,8 @@ def eq(a, b):
assert a == b, "%r != %r" % (a, b)

def ne(a, b):
print "%r" % a
print "%r" % b
assert a != b, "%r == %r" % (a, b)

def lt(a, b):
Expand Down Expand Up @@ -75,3 +77,6 @@ def raises(exctype, func, *args, **kwargs):
raise AssertionError("Function %s did not raise %s" % (
func_name, exctype.__name__))

def is_js_object(obj):
assert isinstance(obj, spidermonkey.Object), \
"%r is not an instance of spdermonkey.Object." % obj
37 changes: 37 additions & 0 deletions tests/test-global.py
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,21 @@ def test_py_set_global(rt):
t.eq(cx.execute("foo;"), 71);
t.eq(glbl["foo"], 71)

class FunctionTest(object):
def __init__(self):
self.data = {}
def __getitem__(self, key):
return self.data[key]
def __setitem__(self, key, value):
self.data[key] = value

@t.rt()
def test_py_set_function_global(rt):
glbl = FunctionTest()
cx = rt.new_context(glbl)
cx.execute("function foo() {};")
t.is_js_object(glbl["foo"])

class ActiveGlobal(object):
def __init__(self):
self.data = {}
Expand All @@ -90,6 +105,28 @@ def __getitem__(self, key):
def __setitem__(self, key, value):
self.data[key] = value * 2

@t.rt()
def test_py_no_del_item(rt):
glbl = ActiveGlobal()
cx = rt.new_context(glbl)
cx.execute('foo = 4;')
t.eq(glbl.data["foo"], 8)
cx.execute("delete foo;")
t.isin("foo", glbl.data)

class ActiveGlobalWithDel(ActiveGlobal):
def __delitem__(self, key):
del self.data[key]

@t.rt()
def test_py_del_global(rt):
glbl = ActiveGlobalWithDel()
cx = rt.new_context(glbl)
cx.execute("foo = 4;")
t.eq(glbl.data["foo"], 8)
cx.execute("delete foo;")
t.isnotin("foo", glbl.data)

@t.rt()
def test_py_with_active_global(rt):
glbl = ActiveGlobal()
Expand Down

0 comments on commit ee6cfb8

Please sign in to comment.