Skip to content

Commit c93913d

Browse files
authored
Merge pull request #197 from Distributive-Network/philippe/fix/195
Philippe/fix/195
2 parents a0c7c14 + 25d05cb commit c93913d

File tree

4 files changed

+384
-15
lines changed

4 files changed

+384
-15
lines changed

include/JSObjectProxy.hh

+98-4
Original file line numberDiff line numberDiff line change
@@ -129,9 +129,9 @@ public:
129129
* @return the string representation (a PyUnicodeObject) on success, NULL on failure
130130
*/
131131
static PyObject *JSObjectProxy_repr(JSObjectProxy *self);
132-
132+
133133
/**
134-
* @brief Set union method
134+
* @brief Set union operation
135135
*
136136
* @param self - The JSObjectProxy
137137
* @param other - The other PyObject to be or'd, expected to be dict or JSObjectProxy
@@ -140,17 +140,93 @@ public:
140140
static PyObject *JSObjectProxy_or(JSObjectProxy *self, PyObject *other);
141141

142142
/**
143-
* @brief Set union method, in place
143+
* @brief Set union operation, in place
144144
*
145145
* @param self - The JSObjectProxy
146146
* @param other - The other PyObject to be or'd, expected to be dict or JSObjectProxy
147147
* @return PyObject* The resulting new dict, must be same object as self
148148
*/
149149
static PyObject *JSObjectProxy_ior(JSObjectProxy *self, PyObject *other);
150150

151+
/**
152+
* @brief get method
153+
*
154+
* @param self - The JSObjectProxy
155+
* @param args - arguments to the method
156+
* @param nargs - number of args to the method
157+
* @return PyObject* the value for key if first arg key is in the dictionary, else second arg default
158+
*/
159+
static PyObject *JSObjectProxy_get_method(JSObjectProxy *self, PyObject *const *args, Py_ssize_t nargs);
160+
161+
/**
162+
* @brief setdefault method
163+
*
164+
* @param self - The JSObjectProxy
165+
* @param args - arguments to the method
166+
* @param nargs - number of args to the method
167+
* @return PyObject* the value for key if first arg key is in the dictionary, else second default
168+
*/
169+
static PyObject *JSObjectProxy_setdefault_method(JSObjectProxy *self, PyObject *const *args, Py_ssize_t nargs);
170+
171+
/**
172+
* @brief pop method
173+
*
174+
* @param self - The JSObjectProxy
175+
* @param args - arguments to the method
176+
* @param nargs - number of args to the method
177+
* @return PyObject* If the first arg key is not found, return the second arg default if given; otherwise raise a KeyError
178+
*/
179+
static PyObject *JSObjectProxy_pop_method(JSObjectProxy *self, PyObject *const *args, Py_ssize_t nargs);
180+
181+
/**
182+
* @brief clear method
183+
*
184+
* @param self - The JSObjectProxy
185+
* @return None
186+
*/
187+
static PyObject *JSObjectProxy_clear_method(JSObjectProxy *self);
188+
189+
/**
190+
* @brief copy method
191+
*
192+
* @param self - The JSObjectProxy
193+
* @return PyObject* copy of the dict
194+
*/
195+
static PyObject *JSObjectProxy_copy_method(JSObjectProxy *self);
151196
};
152197

153198

199+
// docs for methods, copied from cpython
200+
PyDoc_STRVAR(dict_get__doc__,
201+
"get($self, key, default=None, /)\n"
202+
"--\n"
203+
"\n"
204+
"Return the value for key if key is in the dictionary, else default.");
205+
206+
PyDoc_STRVAR(dict_setdefault__doc__,
207+
"setdefault($self, key, default=None, /)\n"
208+
"--\n"
209+
"\n"
210+
"Insert key with a value of default if key is not in the dictionary.\n"
211+
"\n"
212+
"Return the value for key if key is in the dictionary, else default.");
213+
214+
PyDoc_STRVAR(dict_pop__doc__,
215+
"pop($self, key, default=<unrepresentable>, /)\n"
216+
"--\n"
217+
"\n"
218+
"D.pop(k[,d]) -> v, remove specified key and return the corresponding value.\n"
219+
"\n"
220+
"If the key is not found, return the default if given; otherwise,\n"
221+
"raise a KeyError.");
222+
223+
PyDoc_STRVAR(clear__doc__,
224+
"D.clear() -> None. Remove all items from D.");
225+
226+
PyDoc_STRVAR(copy__doc__,
227+
"D.copy() -> a shallow copy of D");
228+
229+
154230
/**
155231
* @brief Struct for the methods that define the Mapping protocol
156232
*
@@ -161,6 +237,10 @@ static PyMappingMethods JSObjectProxy_mapping_methods = {
161237
.mp_ass_subscript = (objobjargproc)JSObjectProxyMethodDefinitions::JSObjectProxy_assign
162238
};
163239

240+
/**
241+
* @brief Struct for the methods that define the Sequence protocol
242+
*
243+
*/
164244
static PySequenceMethods JSObjectProxy_sequence_methods = {
165245
.sq_contains = (objobjproc)JSObjectProxyMethodDefinitions::JSObjectProxy_contains
166246
};
@@ -170,7 +250,21 @@ static PyNumberMethods JSObjectProxy_number_methods = {
170250
.nb_inplace_or = (binaryfunc)JSObjectProxyMethodDefinitions::JSObjectProxy_ior
171251
};
172252

253+
/**
254+
* @brief Struct for the other methods
255+
*
256+
*/
257+
static PyMethodDef JSObjectProxy_methods[] = {
258+
{"get", (PyCFunction)JSObjectProxyMethodDefinitions::JSObjectProxy_get_method, METH_FASTCALL, dict_get__doc__},
259+
{"setdefault", (PyCFunction)JSObjectProxyMethodDefinitions::JSObjectProxy_setdefault_method, METH_FASTCALL, dict_setdefault__doc__},
260+
{"pop", (PyCFunction)JSObjectProxyMethodDefinitions::JSObjectProxy_pop_method, METH_FASTCALL, dict_pop__doc__},
261+
// {"popitem", (PyCFunction)JSObjectProxyMethodDefinitions::JSObjectProxy_popitem_method, METH_NOARGS, ""}, TODO not popular and quite a bit strange
262+
{"clear", (PyCFunction)JSObjectProxyMethodDefinitions::JSObjectProxy_clear_method, METH_NOARGS, clear__doc__},
263+
{"copy", (PyCFunction)JSObjectProxyMethodDefinitions::JSObjectProxy_copy_method, METH_NOARGS, copy__doc__},
264+
{NULL, NULL} /* sentinel */
265+
};
266+
173267
/**
174268
* @brief Struct for the JSObjectProxyType, used by all JSObjectProxy objects
175269
*/
176-
extern PyTypeObject JSObjectProxyType;
270+
extern PyTypeObject JSObjectProxyType;

src/JSObjectProxy.cc

+157-11
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
/**
22
* @file JSObjectProxy.cc
3-
* @author Caleb Aikens ([email protected]) & Tom Tang ([email protected])
3+
* @author Caleb Aikens ([email protected]), Tom Tang (xmader@distributive.network) and Philippe Laporte (philippe@distributive.network)
44
* @brief JSObjectProxy is a custom C-implemented python type that derives from dict. It acts as a proxy for JSObjects from Spidermonkey, and behaves like a dict would.
55
* @version 0.1
66
* @date 2023-06-26
@@ -44,6 +44,8 @@ void JSObjectProxyMethodDefinitions::JSObjectProxy_dealloc(JSObjectProxy *self)
4444
{
4545
// TODO (Caleb Aikens): intentional override of PyDict_Type's tp_dealloc. Probably results in leaking dict memory
4646
self->jsObject.set(nullptr);
47+
PyObject_GC_UnTrack(self);
48+
Py_TYPE(self)->tp_free((PyObject *)self);
4749
return;
4850
}
4951

@@ -56,6 +58,9 @@ PyObject *JSObjectProxyMethodDefinitions::JSObjectProxy_new(PyTypeObject *subtyp
5658

5759
int JSObjectProxyMethodDefinitions::JSObjectProxy_init(JSObjectProxy *self, PyObject *args, PyObject *kwds)
5860
{
61+
if (PyDict_Type.tp_init((PyObject *)self, args, kwds) < 0) {
62+
return -1;
63+
}
5964
// make fresh JSObject for proxy
6065
self->jsObject.set(JS_NewPlainObject(GLOBAL_CX));
6166
return 0;
@@ -83,9 +88,29 @@ PyObject *JSObjectProxyMethodDefinitions::JSObjectProxy_get(JSObjectProxy *self,
8388

8489
JS::RootedValue *value = new JS::RootedValue(GLOBAL_CX);
8590
JS_GetPropertyById(GLOBAL_CX, self->jsObject, id, value);
91+
8692
JS::RootedObject *global = new JS::RootedObject(GLOBAL_CX, JS::GetNonCCWObjectGlobal(self->jsObject));
87-
return pyTypeFactory(GLOBAL_CX, global, value)->getPyObject();
88-
// TODO value and global appear to be leaking, but deleting them causes crashes
93+
94+
if (!value->isUndefined()) {
95+
return pyTypeFactory(GLOBAL_CX, global, value)->getPyObject();
96+
}
97+
else {
98+
// look through the methods for dispatch
99+
for (size_t index = 0;; index++) {
100+
const char *methodName = JSObjectProxyType.tp_methods[index].ml_name;
101+
if (methodName == NULL) { // reached end of list
102+
return pyTypeFactory(GLOBAL_CX, global, value)->getPyObject();
103+
}
104+
else if (PyUnicode_Check(key)) {
105+
if (strcmp(methodName, PyUnicode_AsUTF8(key)) == 0) {
106+
return PyObject_GenericGetAttr((PyObject *)self, key);
107+
}
108+
}
109+
else {
110+
return pyTypeFactory(GLOBAL_CX, global, value)->getPyObject();
111+
}
112+
}
113+
}
89114
}
90115

91116
int JSObjectProxyMethodDefinitions::JSObjectProxy_contains(JSObjectProxy *self, PyObject *key)
@@ -207,12 +232,11 @@ bool JSObjectProxyMethodDefinitions::JSObjectProxy_richcompare_helper(JSObjectPr
207232
}
208233

209234
PyObject *JSObjectProxyMethodDefinitions::JSObjectProxy_iter(JSObjectProxy *self) {
210-
JSContext *cx = GLOBAL_CX;
211-
JS::RootedObject *global = new JS::RootedObject(cx, JS::GetNonCCWObjectGlobal(self->jsObject));
235+
JS::RootedObject *global = new JS::RootedObject(GLOBAL_CX, JS::GetNonCCWObjectGlobal(self->jsObject));
212236

213237
// Get **enumerable** own properties
214-
JS::RootedIdVector props(cx);
215-
if (!js::GetPropertyKeys(cx, self->jsObject, JSITER_OWNONLY, &props)) {
238+
JS::RootedIdVector props(GLOBAL_CX);
239+
if (!js::GetPropertyKeys(GLOBAL_CX, self->jsObject, JSITER_OWNONLY, &props)) {
216240
return NULL;
217241
}
218242

@@ -222,11 +246,11 @@ PyObject *JSObjectProxyMethodDefinitions::JSObjectProxy_iter(JSObjectProxy *self
222246
PyObject *seq = PyTuple_New(length);
223247
for (size_t i = 0; i < length; i++) {
224248
JS::HandleId id = props[i];
225-
PyObject *key = idToKey(cx, id);
249+
PyObject *key = idToKey(GLOBAL_CX, id);
226250

227-
JS::RootedValue *jsVal = new JS::RootedValue(cx);
228-
JS_GetPropertyById(cx, self->jsObject, id, jsVal);
229-
PyObject *value = pyTypeFactory(cx, global, jsVal)->getPyObject();
251+
JS::RootedValue *jsVal = new JS::RootedValue(GLOBAL_CX);
252+
JS_GetPropertyById(GLOBAL_CX, self->jsObject, id, jsVal);
253+
PyObject *value = pyTypeFactory(GLOBAL_CX, global, jsVal)->getPyObject();
230254

231255
PyTuple_SetItem(seq, i, PyTuple_Pack(2, key, value));
232256
}
@@ -318,3 +342,125 @@ PyObject *JSObjectProxyMethodDefinitions::JSObjectProxy_ior(JSObjectProxy *self,
318342
Py_INCREF(self);
319343
return (PyObject *)self;
320344
}
345+
346+
PyObject *JSObjectProxyMethodDefinitions::JSObjectProxy_get_method(JSObjectProxy *self, PyObject *const *args, Py_ssize_t nargs) {
347+
PyObject *key;
348+
PyObject *default_value = Py_None;
349+
350+
if (!_PyArg_CheckPositional("get", nargs, 1, 2)) {
351+
return NULL;
352+
}
353+
key = args[0];
354+
if (nargs < 2) {
355+
goto skip_optional;
356+
}
357+
358+
default_value = args[1];
359+
360+
skip_optional:
361+
362+
PyObject *value = JSObjectProxy_get(self, key);
363+
if (value == Py_None) {
364+
value = default_value;
365+
}
366+
Py_XINCREF(value);
367+
return value;
368+
}
369+
370+
PyObject *JSObjectProxyMethodDefinitions::JSObjectProxy_setdefault_method(JSObjectProxy *self, PyObject *const *args, Py_ssize_t nargs) {
371+
PyObject *key;
372+
PyObject *default_value = Py_None;
373+
374+
if (!_PyArg_CheckPositional("setdefault", nargs, 1, 2)) {
375+
return NULL;
376+
}
377+
key = args[0];
378+
if (nargs < 2) {
379+
goto skip_optional;
380+
}
381+
382+
default_value = args[1];
383+
384+
skip_optional:
385+
386+
PyObject* value = JSObjectProxy_get(self, key);
387+
if (value == Py_None) {
388+
JSObjectProxy_assign(self, key, default_value);
389+
Py_XINCREF(default_value);
390+
return default_value;
391+
}
392+
393+
Py_XINCREF(value);
394+
return value;
395+
}
396+
397+
PyObject *JSObjectProxyMethodDefinitions::JSObjectProxy_pop_method(JSObjectProxy *self, PyObject *const *args, Py_ssize_t nargs) {
398+
PyObject *key;
399+
PyObject *default_value = NULL;
400+
401+
if (!_PyArg_CheckPositional("pop", nargs, 1, 2)) {
402+
return NULL;
403+
}
404+
key = args[0];
405+
if (nargs < 2) {
406+
goto skip_optional;
407+
}
408+
default_value = args[1];
409+
410+
skip_optional:
411+
JS::RootedId id(GLOBAL_CX);
412+
if (!keyToId(key, &id)) {
413+
// TODO (Caleb Aikens): raise exception here PyObject *seq = PyTuple_New(length);
414+
return NULL;
415+
}
416+
417+
JS::RootedValue *value = new JS::RootedValue(GLOBAL_CX);
418+
JS_GetPropertyById(GLOBAL_CX, self->jsObject, id, value);
419+
if (value->isUndefined()) {
420+
if (default_value != NULL) {
421+
Py_INCREF(default_value);
422+
return default_value;
423+
}
424+
_PyErr_SetKeyError(key);
425+
return NULL;
426+
} else {
427+
JS::ObjectOpResult ignoredResult;
428+
JS_DeletePropertyById(GLOBAL_CX, self->jsObject, id, ignoredResult);
429+
430+
return pyTypeFactory(GLOBAL_CX, global, value)->getPyObject();
431+
}
432+
}
433+
434+
PyObject *JSObjectProxyMethodDefinitions::JSObjectProxy_clear_method(JSObjectProxy *self) {
435+
JS::RootedIdVector props(GLOBAL_CX);
436+
if (!js::GetPropertyKeys(GLOBAL_CX, self->jsObject, JSITER_OWNONLY, &props))
437+
{
438+
// @TODO (Caleb Aikens) raise exception here
439+
return NULL;
440+
}
441+
442+
JS::ObjectOpResult ignoredResult;
443+
size_t length = props.length();
444+
for (size_t index = 0; index < length; index++) {
445+
JS_DeletePropertyById(GLOBAL_CX, self->jsObject, props[index], ignoredResult);
446+
}
447+
448+
Py_RETURN_NONE;
449+
}
450+
451+
PyObject *JSObjectProxyMethodDefinitions::JSObjectProxy_copy_method(JSObjectProxy *self) {
452+
JS::Rooted<JS::ValueArray<2>> args(GLOBAL_CX);
453+
args[0].setObjectOrNull(JS_NewPlainObject(GLOBAL_CX));
454+
args[1].setObjectOrNull(self->jsObject);
455+
456+
JS::RootedObject *global = new JS::RootedObject(GLOBAL_CX, JS::GetNonCCWObjectGlobal(self->jsObject));
457+
458+
// call Object.assign
459+
JS::RootedValue Object(GLOBAL_CX);
460+
JS_GetProperty(GLOBAL_CX, *global, "Object", &Object);
461+
462+
JS::RootedObject rootedObject(GLOBAL_CX, Object.toObjectOrNull());
463+
JS::RootedValue ret(GLOBAL_CX);
464+
if (!JS_CallFunctionName(GLOBAL_CX, rootedObject, "assign", args, &ret)) return NULL;
465+
return pyTypeFactory(GLOBAL_CX, global, &ret)->getPyObject();
466+
}

src/modules/pythonmonkey/pythonmonkey.cc

+2
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@ PyTypeObject JSObjectProxyType = {
7272
.ob_base = PyVarObject_HEAD_INIT(NULL, 0)
7373
.tp_name = "pythonmonkey.JSObjectProxy",
7474
.tp_basicsize = sizeof(JSObjectProxy),
75+
.tp_itemsize = 0,
7576
.tp_dealloc = (destructor)JSObjectProxyMethodDefinitions::JSObjectProxy_dealloc,
7677
.tp_repr = (reprfunc)JSObjectProxyMethodDefinitions::JSObjectProxy_repr,
7778
.tp_as_number = &JSObjectProxy_number_methods,
@@ -84,6 +85,7 @@ PyTypeObject JSObjectProxyType = {
8485
.tp_doc = PyDoc_STR("Javascript Object proxy dict"),
8586
.tp_richcompare = (richcmpfunc)JSObjectProxyMethodDefinitions::JSObjectProxy_richcompare,
8687
.tp_iter = (getiterfunc)JSObjectProxyMethodDefinitions::JSObjectProxy_iter,
88+
.tp_methods = JSObjectProxy_methods,
8789
.tp_base = &PyDict_Type,
8890
.tp_init = (initproc)JSObjectProxyMethodDefinitions::JSObjectProxy_init,
8991
.tp_new = JSObjectProxyMethodDefinitions::JSObjectProxy_new,

0 commit comments

Comments
 (0)