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
5759int 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
91116int JSObjectProxyMethodDefinitions::JSObjectProxy_contains (JSObjectProxy *self, PyObject *key)
@@ -207,12 +232,11 @@ bool JSObjectProxyMethodDefinitions::JSObjectProxy_richcompare_helper(JSObjectPr
207232}
208233
209234PyObject *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+ }
0 commit comments