Skip to content

Commit ee6cfb8

Browse files
committed
Fixes global handling of "function foo() {}"
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.
1 parent 1e5357f commit ee6cfb8

File tree

4 files changed

+102
-2
lines changed

4 files changed

+102
-2
lines changed

THANKS

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,3 +19,7 @@ Mike West
1919
* Reported bug in Context.max_time
2020
* Better test for test_exceeds_time
2121
* Reported missing pkg-config requirement
22+
23+
Riccardo Pelizzi
24+
* Bug report for global handlers and "function foo() {}" syntax.
25+

spidermonkey/context.c

Lines changed: 56 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,60 @@
1313
#include <jsobj.h>
1414
#include <jscntxt.h>
1515

16+
JSBool
17+
add_prop(JSContext* jscx, JSObject* jsobj, jsval key, jsval* rval)
18+
{
19+
JSObject* obj = NULL;
20+
21+
if(!JSVAL_IS_OBJECT(*rval)) return JS_TRUE;
22+
23+
obj = JSVAL_TO_OBJECT(*rval);
24+
if(JS_ObjectIsFunction(jscx, obj)) return set_prop(jscx, jsobj, key, rval);
25+
return JS_TRUE;
26+
}
27+
28+
JSBool
29+
del_prop(JSContext* jscx, JSObject* jsobj, jsval key, jsval* rval)
30+
{
31+
Context* pycx = NULL;
32+
PyObject* pykey = NULL;
33+
PyObject* pyval = NULL;
34+
JSBool ret = JS_FALSE;
35+
36+
pycx = (Context*) JS_GetContextPrivate(jscx);
37+
if(pycx == NULL)
38+
{
39+
JS_ReportError(jscx, "Failed to get Python context.");
40+
goto done;
41+
}
42+
43+
// Bail if there's no registered global handler.
44+
if(pycx->global == NULL)
45+
{
46+
ret = JS_TRUE;
47+
goto done;
48+
}
49+
50+
// Bail if the global doesn't have a __delitem__
51+
if(!PyObject_HasAttrString(pycx->global, "__delitem__"))
52+
{
53+
ret = JS_TRUE;
54+
goto done;
55+
}
56+
57+
pykey = js2py(pycx, key);
58+
if(pykey == NULL) goto done;
59+
60+
if(PyObject_DelItem(pycx->global, pykey) < 0) goto done;
61+
62+
ret = JS_TRUE;
63+
64+
done:
65+
Py_XDECREF(pykey);
66+
Py_XDECREF(pyval);
67+
return ret;
68+
}
69+
1670
JSBool
1771
get_prop(JSContext* jscx, JSObject* jsobj, jsval key, jsval* rval)
1872
{
@@ -152,8 +206,8 @@ static JSClass
152206
js_global_class = {
153207
"JSGlobalObjectClass",
154208
JSCLASS_GLOBAL_FLAGS,
155-
JS_PropertyStub,
156-
JS_PropertyStub,
209+
add_prop,
210+
del_prop,
157211
get_prop,
158212
set_prop,
159213
JS_EnumerateStub,

tests/t.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,8 @@ def eq(a, b):
4545
assert a == b, "%r != %r" % (a, b)
4646

4747
def ne(a, b):
48+
print "%r" % a
49+
print "%r" % b
4850
assert a != b, "%r == %r" % (a, b)
4951

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

80+
def is_js_object(obj):
81+
assert isinstance(obj, spidermonkey.Object), \
82+
"%r is not an instance of spdermonkey.Object." % obj

tests/test-global.py

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,21 @@ def test_py_set_global(rt):
8282
t.eq(cx.execute("foo;"), 71);
8383
t.eq(glbl["foo"], 71)
8484

85+
class FunctionTest(object):
86+
def __init__(self):
87+
self.data = {}
88+
def __getitem__(self, key):
89+
return self.data[key]
90+
def __setitem__(self, key, value):
91+
self.data[key] = value
92+
93+
@t.rt()
94+
def test_py_set_function_global(rt):
95+
glbl = FunctionTest()
96+
cx = rt.new_context(glbl)
97+
cx.execute("function foo() {};")
98+
t.is_js_object(glbl["foo"])
99+
85100
class ActiveGlobal(object):
86101
def __init__(self):
87102
self.data = {}
@@ -90,6 +105,28 @@ def __getitem__(self, key):
90105
def __setitem__(self, key, value):
91106
self.data[key] = value * 2
92107

108+
@t.rt()
109+
def test_py_no_del_item(rt):
110+
glbl = ActiveGlobal()
111+
cx = rt.new_context(glbl)
112+
cx.execute('foo = 4;')
113+
t.eq(glbl.data["foo"], 8)
114+
cx.execute("delete foo;")
115+
t.isin("foo", glbl.data)
116+
117+
class ActiveGlobalWithDel(ActiveGlobal):
118+
def __delitem__(self, key):
119+
del self.data[key]
120+
121+
@t.rt()
122+
def test_py_del_global(rt):
123+
glbl = ActiveGlobalWithDel()
124+
cx = rt.new_context(glbl)
125+
cx.execute("foo = 4;")
126+
t.eq(glbl.data["foo"], 8)
127+
cx.execute("delete foo;")
128+
t.isnotin("foo", glbl.data)
129+
93130
@t.rt()
94131
def test_py_with_active_global(rt):
95132
glbl = ActiveGlobal()

0 commit comments

Comments
 (0)