From 789b4e54fe020b5e58b0c1908a3cfeb6a6653bfb Mon Sep 17 00:00:00 2001 From: philippedistributive Date: Wed, 20 Dec 2023 12:13:18 -0500 Subject: [PATCH 01/58] Several List to Array conversion methods done Further List proxy work --- README.md | 3 +- include/PyProxyHandler.hh | 15 +- include/jsTypeFactory.hh | 10 + src/JSArrayProxy.cc | 3 +- src/PyProxyHandler.cc | 844 +++++++++++++++++++++++++++++++- src/jsTypeFactory.cc | 28 +- tests/python/test_arrays.py | 827 +++++++++++++++++++++++++++++++ tests/python/test_list_array.py | 11 + 8 files changed, 1729 insertions(+), 12 deletions(-) create mode 100644 tests/python/test_arrays.py diff --git a/README.md b/README.md index 61adf5ab..af96b1f9 100644 --- a/README.md +++ b/README.md @@ -56,6 +56,7 @@ js_eval("console.log")('hello, world') - [done] Python TypedArrays coerce to JS TypeArrays - [done] JS TypedArrays coerce to Python TypeArrays - [done] Python List coerce to JS Arrays +- [done] JS Arrays coerce to Python Lists ## Build Instructions @@ -250,7 +251,7 @@ that if you update an object in JavaScript, the corresponding Dict in Python wil | function | Function | object - most | pythonmonkey.JSObjectProxy (Dict) | object - Date | datetime -| object - Array | List +| object - Array | pythonmonkey.JSArrayProxy (List) | object - Promise | awaitable | object - ArrayBuffer | Buffer | object - type arrays | Buffer diff --git a/include/PyProxyHandler.hh b/include/PyProxyHandler.hh index 21188e26..7c97dd17 100644 --- a/include/PyProxyHandler.hh +++ b/include/PyProxyHandler.hh @@ -1,7 +1,7 @@ /** * @file PyProxy.hh - * @author Caleb Aikens (caleb@distributive.network) - * @brief Struct for creating JS proxy objects. Used by DictType for object coercion + * @author Caleb Aikens (caleb@distributive.network) and Philippe Laporte (philippe@distributive.network) + * @brief Struct for creating JS proxy objects. Used by DictType for object coercion and by ListType for List coercion * @version 0.1 * @date 2023-04-20 * @@ -30,6 +30,14 @@ public: bool isExtensible(JSContext *cx, JS::HandleObject proxy, bool *extensible) const override final; }; +enum ProxySlots {PyObjectSlot}; + +typedef struct { + const char *name; /* The name of the method */ + JSNative call; /* The C function that implements it */ + uint16_t nargs; /* The argument count for the method */ +} JSMethodDef; + /** * @brief This struct is the ProxyHandler for JS Proxy Objects pythonmonkey creates to handle coercion from python dicts to JS Objects * @@ -180,6 +188,9 @@ public: bool ownPropertyKeys(JSContext *cx, JS::HandleObject proxy, JS::MutableHandleIdVector props) const override; bool delete_(JSContext *cx, JS::HandleObject proxy, JS::HandleId id, JS::ObjectOpResult &result) const override; + bool isArray(JSContext *cx, JS::HandleObject proxy, JS::IsArrayAnswer *answer) const override; + bool getBuiltinClass(JSContext *cx, JS::Handle obj, js::ESClass *cls) const override; + const char *className(JSContext *cx, JS::HandleObject proxy) const override; }; /** diff --git a/include/jsTypeFactory.hh b/include/jsTypeFactory.hh index 6ec68412..23339c54 100644 --- a/include/jsTypeFactory.hh +++ b/include/jsTypeFactory.hh @@ -37,6 +37,16 @@ size_t UCS4ToUTF16(const uint32_t *chars, size_t length, uint16_t *outStr); * @return JS::Value - A JS::Value corresponding to the PyType */ JS::Value jsTypeFactory(JSContext *cx, PyObject *object); + +/** + * @brief Function that takes a PyObject and returns a corresponding JS::Value which is a copy, not a reference + * + * @param cx - Pointer to the JSContext + * @param object - Pointer to the PyListObject + * @return JS::Value - A JS::Value corresponding to the PyType + */ +JS::Value jsTypeFactoryCopy(JSContext *cx, PyObject *object); + /** * @brief same to jsTypeFactory, but it's guaranteed that no error would be set on the Python error stack, instead * return JS `null` on error, and output a warning in Python-land diff --git a/src/JSArrayProxy.cc b/src/JSArrayProxy.cc index 809df45a..b2ab2316 100644 --- a/src/JSArrayProxy.cc +++ b/src/JSArrayProxy.cc @@ -530,7 +530,7 @@ PyObject *JSArrayProxyMethodDefinitions::JSArrayProxy_repr(JSArrayProxy *self) { if (_PyUnicodeWriter_WriteChar(&writer, '[') < 0) { goto error; } - + /* Do repr() on each element. Note that this may mutate the list, so must refetch the list size on each iteration. */ for (Py_ssize_t index = 0; index < JSArrayProxy_length(self); index++) { if (index > 0) { @@ -752,6 +752,7 @@ int JSArrayProxyMethodDefinitions::JSArrayProxy_clear_slot(JSArrayProxy *self) { } int JSArrayProxyMethodDefinitions::JSArrayProxy_traverse(JSArrayProxy *self, visitproc visit, void *arg) { + // TODO untested JS::RootedObject *global = new JS::RootedObject(GLOBAL_CX, JS::GetNonCCWObjectGlobal(self->jsObject)); for (Py_ssize_t i = JSArrayProxy_length(self); --i >= 0; ) { JS::RootedValue *elementVal = new JS::RootedValue(GLOBAL_CX); diff --git a/src/PyProxyHandler.cc b/src/PyProxyHandler.cc index b9fdc0d6..d0b27f90 100644 --- a/src/PyProxyHandler.cc +++ b/src/PyProxyHandler.cc @@ -176,12 +176,832 @@ bool PyBaseProxyHandler::isExtensible(JSContext *cx, JS::HandleObject proxy, return true; } + + +// PyList ---------------------------------------------------------------------- const char PyListProxyHandler::family = 0; +static bool array_reverse(JSContext *cx, unsigned argc, JS::Value *vp) { + JS::CallArgs args = JS::CallArgsFromVp(argc, vp); + + JS::RootedObject proxy(cx, JS::ToObject(cx, args.thisv())); + if (!proxy) { + return false; + } + PyObject *self = JS::GetMaybePtrFromReservedSlot(proxy, PyObjectSlot); + + if (PyList_GET_SIZE(self) > 1) { + PyList_Reverse(self); + } + + args.rval().setUndefined(); + return true; +} + +static bool array_pop(JSContext *cx, unsigned argc, JS::Value *vp) { + JS::CallArgs args = JS::CallArgsFromVp(argc, vp); + + JS::RootedObject proxy(cx, JS::ToObject(cx, args.thisv())); + if (!proxy) { + return false; + } + PyObject *self = JS::GetMaybePtrFromReservedSlot(proxy, PyObjectSlot); + + if (PyList_GET_SIZE(self) == 0) { + args.rval().setUndefined(); + return true; + } + + PyObject *result = PyObject_CallMethod(self, "pop", NULL); + + if (!result) { + PyErr_Clear(); + args.rval().setUndefined(); + return true; + } + + args.rval().set(jsTypeFactory(cx, result)); + return true; +} + +static bool array_at(JSContext *cx, unsigned argc, JS::Value *vp) { + JS::CallArgs args = JS::CallArgsFromVp(argc, vp); + + if (!args.requireAtLeast(cx, "at", 1)) { + return false; + } + + JS::RootedObject proxy(cx, JS::ToObject(cx, args.thisv())); + if (!proxy) { + return false; + } + PyObject *self = JS::GetMaybePtrFromReservedSlot(proxy, PyObjectSlot); + + int64_t index; + if (!JS::ToInt64(cx, args[0], &index)) { + return false; + } + + Py_ssize_t selfSize = PyList_GET_SIZE(self); + + if (index < 0) { + index += selfSize; + } + + if (index < 0 || index >= selfSize) { + args.rval().setUndefined(); + return true; + } + + PyObject *result = PyList_GetItem(self, (Py_ssize_t)index); + + args.rval().set(jsTypeFactory(cx, result)); + return true; +} + +static bool array_join(JSContext *cx, unsigned argc, JS::Value *vp) { + JS::CallArgs args = JS::CallArgsFromVp(argc, vp); + + JS::RootedObject proxy(cx, JS::ToObject(cx, args.thisv())); + if (!proxy) { + return false; + } + PyObject *self = JS::GetMaybePtrFromReservedSlot(proxy, PyObjectSlot); + + // convert to JSArray (copy) then call join on it + JS::RootedObject selfCopy(cx, &jsTypeFactoryCopy(cx, self).toObject()); + + JS::RootedString sepstr(cx); + if (args.hasDefined(0)) { + sepstr = JS::ToString(cx, args[0]); + if (!sepstr) { + return false; + } + } else { + sepstr = JS_NewStringCopyZ(cx, ","); + } + + JS::Rooted> jArgs(cx); + jArgs[0].setString(sepstr); + JS::RootedValue jRet(cx); + if (!JS_CallFunctionName(cx, selfCopy, "join", jArgs, &jRet)) { + return false; + } + + args.rval().setString(jRet.toString()); + return true; +} + +static bool array_toString(JSContext *cx, unsigned argc, JS::Value *vp) { + JS::CallArgs args = JS::CallArgsFromVp(argc, vp); + + JS::RootedObject proxy(cx, JS::ToObject(cx, args.thisv())); + if (!proxy) { + return false; + } + PyObject *self = JS::GetMaybePtrFromReservedSlot(proxy, PyObjectSlot); + + JS::RootedObject selfCopy(cx, &jsTypeFactoryCopy(cx, self).toObject()); + + JS::RootedValue jRet(cx); + if (!JS_CallFunctionName(cx, selfCopy, "toString", JS::HandleValueArray::empty(), &jRet)) { + return false; + } + + args.rval().setString(jRet.toString()); + return true; +} + +static bool array_push(JSContext *cx, unsigned argc, JS::Value *vp) { // surely the function name is in there...review JSAPI examples + JS::CallArgs args = JS::CallArgsFromVp(argc, vp); + + JS::RootedObject proxy(cx, JS::ToObject(cx, args.thisv())); + if (!proxy) { + return false; + } + PyObject *self = JS::GetMaybePtrFromReservedSlot(proxy, PyObjectSlot); + + JS::RootedObject *global = new JS::RootedObject(cx, JS::GetNonCCWObjectGlobal(proxy)); + unsigned numArgs = args.length(); + for (unsigned index = 0; index < numArgs; index++) { + JS::RootedValue *elementVal = new JS::RootedValue(cx); + elementVal->set(args[index].get()); + PyList_Append(self, pyTypeFactory(cx, global, elementVal)->getPyObject()); + } + + args.rval().setInt32(PyList_GET_SIZE(self)); + return true; +} + +static bool array_shift(JSContext *cx, unsigned argc, JS::Value *vp) { + JS::CallArgs args = JS::CallArgsFromVp(argc, vp); + + JS::RootedObject proxy(cx, JS::ToObject(cx, args.thisv())); + if (!proxy) { + return false; + } + PyObject *self = JS::GetMaybePtrFromReservedSlot(proxy, PyObjectSlot); + + Py_ssize_t selfSize = PyList_GET_SIZE(self); + + if (selfSize == 0) { + args.rval().setUndefined(); + return true; + } + + PyObject *result = PyList_GetItem(self, 0); + PySequence_DelItem(self, 0); + + args.rval().set(jsTypeFactory(cx, result)); + return true; +} + +static bool array_unshift(JSContext *cx, unsigned argc, JS::Value *vp) { // surely the function name is in there...review JSAPI examples + JS::CallArgs args = JS::CallArgsFromVp(argc, vp); + + JS::RootedObject proxy(cx, JS::ToObject(cx, args.thisv())); + if (!proxy) { + return false; + } + PyObject *self = JS::GetMaybePtrFromReservedSlot(proxy, PyObjectSlot); + + JS::RootedObject *global = new JS::RootedObject(cx, JS::GetNonCCWObjectGlobal(proxy)); + for (int index = args.length() - 1; index >= 0; index--) { + JS::RootedValue *elementVal = new JS::RootedValue(cx); + elementVal->set(args[index].get()); + PyList_Insert(self, 0, pyTypeFactory(cx, global, elementVal)->getPyObject()); + } + + args.rval().setInt32(PyList_GET_SIZE(self)); + return true; +} + +static bool array_concat(JSContext *cx, unsigned argc, JS::Value *vp) { + JS::CallArgs args = JS::CallArgsFromVp(argc, vp); + + JS::RootedObject proxy(cx, JS::ToObject(cx, args.thisv())); + if (!proxy) { + return false; + } + PyObject *self = JS::GetMaybePtrFromReservedSlot(proxy, PyObjectSlot); + + JS::RootedObject selfCopy(cx, &jsTypeFactoryCopy(cx, self).toObject()); + + JS::HandleValueArray jArgs(args); + + JS::RootedValue jRet(cx); + if (!JS_CallFunctionName(cx, selfCopy, "concat", jArgs, &jRet)) { + return false; + } + + args.rval().setObject(jRet.toObject()); + return true; +} + +static inline uint64_t normalizeSliceTerm(int64_t value, uint64_t length) { + if (value < 0) { + value += length; + if (value < 0) { + return 0; + } + } + else if (double(value) > double(length)) { + return length; + } + return value; +} + +static bool array_slice(JSContext *cx, unsigned argc, JS::Value *vp) { + JS::CallArgs args = JS::CallArgsFromVp(argc, vp); + + if (!args.requireAtLeast(cx, "slice", 1)) { + return false; + } + + JS::RootedObject proxy(cx, JS::ToObject(cx, args.thisv())); + if (!proxy) { + return false; + } + PyObject *self = JS::GetMaybePtrFromReservedSlot(proxy, PyObjectSlot); + + Py_ssize_t selfLength = PyList_GET_SIZE(self); + + uint64_t start = 0; + uint64_t stop = selfLength; + if (args.length() > 0) { + int64_t d; + + if (!JS::ToInt64(cx, args[0], &d)) { + return false; + } + + start = normalizeSliceTerm(d, selfLength); + + if (args.hasDefined(1)) { + if (!JS::ToInt64(cx, args[1], &d)) { + return false; + } + + stop = normalizeSliceTerm(d, selfLength); + } + } + + PyObject *result = PyList_GetSlice(self, (Py_ssize_t)start, (Py_ssize_t)stop); + + args.rval().set(jsTypeFactory(cx, result)); + return true; +} + +static bool array_indexOf(JSContext *cx, unsigned argc, JS::Value *vp) { + JS::CallArgs args = JS::CallArgsFromVp(argc, vp); + + if (!args.requireAtLeast(cx, "indexOf", 1)) { + return false; + } + + JS::RootedObject proxy(cx, JS::ToObject(cx, args.thisv())); + if (!proxy) { + return false; + } + PyObject *self = JS::GetMaybePtrFromReservedSlot(proxy, PyObjectSlot); + + Py_ssize_t selfLength = PyList_GET_SIZE(self); + + if (selfLength == 0) { + args.rval().setInt32(-1); + return true; + } + + uint64_t start = 0; + if (args.length() > 1) { + int64_t n; + if (!JS::ToInt64(cx, args[1], &n)) { + return false; + } + + if (n >= selfLength) { + args.rval().setInt32(-1); + return true; + } + + if (n >= 0) { + start = uint64_t(n); + } + else { + int64_t d = selfLength + n; + if (d >= 0) { + start = d; + } + } + } + + JS::RootedObject *global = new JS::RootedObject(cx, JS::GetNonCCWObjectGlobal(proxy)); + JS::RootedValue *elementVal = new JS::RootedValue(cx, args[0].get()); + PyObject *result = PyObject_CallMethod(self, "index", "Oi", pyTypeFactory(cx, global, elementVal)->getPyObject(), start); + + if (!result) { + PyErr_Clear(); + args.rval().setInt32(-1); + return true; + } + + args.rval().set(jsTypeFactory(cx, result)); + return true; +} + +static bool array_lastIndexOf(JSContext *cx, unsigned argc, JS::Value *vp) { + JS::CallArgs args = JS::CallArgsFromVp(argc, vp); + + if (!args.requireAtLeast(cx, "lastIndexOf", 1)) { + return false; + } + + JS::RootedObject proxy(cx, JS::ToObject(cx, args.thisv())); + if (!proxy) { + return false; + } + PyObject *self = JS::GetMaybePtrFromReservedSlot(proxy, PyObjectSlot); + + JS::RootedObject selfCopy(cx, &jsTypeFactoryCopy(cx, self).toObject()); + + JS::HandleValueArray jArgs(args); + + JS::RootedValue jRet(cx); + if (!JS_CallFunctionName(cx, selfCopy, "lastIndexOf", jArgs, &jRet)) { + return false; + } + + args.rval().setInt32(jRet.toInt32()); + return true; +} + +static bool array_splice(JSContext *cx, unsigned argc, JS::Value *vp) { + JS::CallArgs args = JS::CallArgsFromVp(argc, vp); + + JS::RootedObject proxy(cx, JS::ToObject(cx, args.thisv())); + if (!proxy) { + return false; + } + PyObject *self = JS::GetMaybePtrFromReservedSlot(proxy, PyObjectSlot); + + uint64_t selfLength = (uint64_t)PyList_GET_SIZE(self); + + int64_t relativeStart; + if (!JS::ToInt64(cx, args.get(0), &relativeStart)) { + return false; + } + + /* actualStart is the index after which elements will be + deleted and/or new elements will be added */ + uint64_t actualStart; + if (relativeStart < 0) { + actualStart = uint64_t(std::max(double(selfLength) + relativeStart, 0.0)); + } else { + actualStart = uint64_t(std::min(double(relativeStart), double(selfLength))); + } + + unsigned int argsLength = args.length(); + + /* insertCount is the number of elements being added */ + uint32_t insertCount; + if (argsLength < 2) { + insertCount = 0; + } + else { + insertCount = argsLength - 2; + } + + /* actualDeleteCount is the number of elements being deleted */ + uint64_t actualDeleteCount; + if (argsLength < 1) { + actualDeleteCount = 0; + } + else if (argsLength < 2) { + actualDeleteCount = selfLength - actualStart; + } + else { + int64_t deleteCount; + if (!JS::ToInt64(cx, args.get(1), &deleteCount)) { + return false; + } + + actualDeleteCount = uint64_t(std::min(std::max(0.0, double(deleteCount)), double(selfLength - actualStart))); + } + + // get deleted items for return value + PyObject *deleted = PyList_GetSlice(self, actualStart, actualStart + actualDeleteCount); + + // build list for SetSlice call + PyObject *inserted = PyList_New(insertCount); + + JS::RootedObject *global = new JS::RootedObject(cx, JS::GetNonCCWObjectGlobal(proxy)); + for (int index = 0; index < insertCount; index++) { + JS::RootedValue *elementVal = new JS::RootedValue(cx, args[index + 2].get()); + PyList_SetItem(inserted, index, pyTypeFactory(cx, global, elementVal)->getPyObject()); + } + + PyList_SetSlice(self, actualStart, actualStart + actualDeleteCount, inserted); + + args.rval().set(jsTypeFactory(cx, deleted)); + return true; +} + +static bool array_sort(JSContext *cx, unsigned argc, JS::Value *vp) { + JS::CallArgs args = JS::CallArgsFromVp(argc, vp); + + JS::RootedObject proxy(cx, JS::ToObject(cx, args.thisv())); + if (!proxy) { + return false; + } + PyObject *self = JS::GetMaybePtrFromReservedSlot(proxy, PyObjectSlot); + + uint64_t selfLength = (uint64_t)PyList_GET_SIZE(self); + + if (selfLength > 0) { + if (args.length() < 1) { + PyList_Sort(self); + } + else { + JS::RootedObject *global = new JS::RootedObject(cx, JS::GetNonCCWObjectGlobal(proxy)); + + PyObject *pyFunc = pyTypeFactory(cx, global, new JS::RootedValue(cx, args[0].get()))->getPyObject(); + // check if JS or Python function + if (PyFunction_Check(pyFunc)) { + // it's a user-defined python function, more than 1-arg will get TypeError + PyObject *callable = PyObject_GetAttrString(self, "sort"); + if (callable == NULL) { + return false; + } + PyObject *result = PyObject_Call(callable, PyTuple_New(0), Py_BuildValue("{s:O}", "key", pyFunc)); + if (!result) { + return false; + } + } else { + // it's either a JS function or a builtin python func + int flags = PyCFunction_GetFlags(pyFunc); + + if (flags & METH_VARARGS && !(flags & METH_KEYWORDS)) { + // it's a JS func + + // copy to JS for sorting + JS::RootedObject selfCopy(cx, &jsTypeFactoryCopy(cx, self).toObject()); + + JS::RootedValue jReturnedArray(cx); + JS::HandleValueArray jArgs(args); + if (!JS_CallFunctionName(cx, selfCopy, "sort", jArgs, &jReturnedArray)) { + return false; + } + + // copy back into Python self + for (int index = 0; index < selfLength; index++) { + JS::RootedValue *elementVal = new JS::RootedValue(cx); + JS_GetElement(cx, selfCopy, index, elementVal); + PyList_SetItem(self, index, pyTypeFactory(cx, global, elementVal)->getPyObject()); + } + } else { + // it's a built-in python function, more than 1-arg will get TypeError + PyObject *callable = PyObject_GetAttrString(self, "sort"); + if (callable == NULL) { + return false; + } + PyObject *result = PyObject_Call(callable, PyTuple_New(0), Py_BuildValue("{s:O}", "key", pyFunc)); + if (!result) { + return false; + } + } + } + } + } + + // return ref to self + args.rval().set(jsTypeFactory(cx, self)); + return true; +} + +static bool array_fill(JSContext *cx, unsigned argc, JS::Value *vp) { + JS::CallArgs args = JS::CallArgsFromVp(argc, vp); + + if (!args.requireAtLeast(cx, "fill", 1)) { + return false; + } + + JS::RootedObject proxy(cx, JS::ToObject(cx, args.thisv())); + if (!proxy) { + return false; + } + PyObject *self = JS::GetMaybePtrFromReservedSlot(proxy, PyObjectSlot); + + uint64_t selfLength = (uint64_t)PyList_GET_SIZE(self); + + unsigned int argsLength = args.length(); + + JS::RootedValue *fillValue = new JS::RootedValue(cx, args[0].get()); + + int64_t relativeStart; + if (argsLength > 1) { + if (!JS::ToInt64(cx, args.get(1), &relativeStart)) { + return false; + } + } else { + relativeStart = 0; + } + + uint64_t actualStart; + if (relativeStart < 0) { + actualStart = uint64_t(std::max(double(selfLength) + relativeStart, 0.0)); + } else { + actualStart = uint64_t(std::min(double(relativeStart), double(selfLength))); + } + + int64_t relativeEnd; + if (argsLength > 2) { + if (!JS::ToInt64(cx, args.get(2), &relativeEnd)) { + return false; + } + } else { + relativeEnd = selfLength; + } + + uint64_t actualEnd; + if (relativeEnd < 0) { + actualEnd = uint64_t(std::max(double(selfLength) + relativeEnd, 0.0)); + } else { + actualEnd = uint64_t(std::min(double(relativeEnd), double(selfLength))); + } + + JS::RootedObject *global = new JS::RootedObject(cx, JS::GetNonCCWObjectGlobal(proxy)); + for (int index = actualStart; index < actualEnd; index++) { + PyList_SetItem(self, index, pyTypeFactory(cx, global, fillValue)->getPyObject()); + } + + // return ref to self + args.rval().set(jsTypeFactory(cx, self)); + return true; +} + +static bool array_copyWithin(JSContext *cx, unsigned argc, JS::Value *vp) { + JS::CallArgs args = JS::CallArgsFromVp(argc, vp); + + JS::RootedObject proxy(cx, JS::ToObject(cx, args.thisv())); + if (!proxy) { + return false; + } + PyObject *self = JS::GetMaybePtrFromReservedSlot(proxy, PyObjectSlot); + + uint64_t selfLength = (uint64_t)PyList_GET_SIZE(self); + + unsigned int argsLength = args.length(); + + int64_t relativeTarget; + if (argsLength > 0) { + if (!JS::ToInt64(cx, args.get(0), &relativeTarget)) { + return false; + } + } else { + relativeTarget = 0; + } + + uint64_t actualTarget; + if (relativeTarget < 0) { + actualTarget = uint64_t(std::max(double(selfLength) + relativeTarget, 0.0)); + } else { + actualTarget = uint64_t(std::min(double(relativeTarget), double(selfLength))); + } + + int64_t relativeStart; + if (argsLength > 1) { + if (!JS::ToInt64(cx, args.get(1), &relativeStart)) { + return false; + } + } else { + relativeStart = 0; + } + + uint64_t actualStart; + if (relativeStart < 0) { + actualStart = uint64_t(std::max(double(selfLength) + relativeStart, 0.0)); + } else { + actualStart = uint64_t(std::min(double(relativeStart), double(selfLength))); + } + + int64_t relativeEnd; + if (argsLength > 2) { + if (!JS::ToInt64(cx, args.get(2), &relativeEnd)) { + return false; + } + } else { + relativeEnd = selfLength; + } + + uint64_t actualEnd; + if (relativeEnd < 0) { + actualEnd = uint64_t(std::max(double(selfLength) + relativeEnd, 0.0)); + } else { + actualEnd = uint64_t(std::min(double(relativeEnd), double(selfLength))); + } + + int64_t count = int64_t(std::min(actualEnd - actualStart, selfLength - actualTarget)); + + if (actualStart < actualTarget && actualTarget < actualStart + count) { + actualStart = actualStart + count - 1; + actualTarget = actualTarget + count - 1; + + while (count > 0) { + PyObject *itemStart = PyList_GetItem(self, actualStart); + PyList_SetItem(self, actualTarget, itemStart); + + actualStart--; + actualTarget--; + count--; + } + } else { + while (count > 0) { + PyObject *itemStart = PyList_GetItem(self, actualStart); + PyList_SetItem(self, actualTarget, itemStart); + + actualStart++; + actualTarget++; + count--; + } + } + + // return ref to self + args.rval().set(jsTypeFactory(cx, self)); + return true; +} + +static bool array_copy_func(JSContext *cx, JS::CallArgs args, const char *fName, JS::MutableHandle rval) { + if (!args.requireAtLeast(cx, fName, 1)) { + return false; + } + + JS::RootedObject proxy(cx, JS::ToObject(cx, args.thisv())); + if (!proxy) { + return false; + } + PyObject *self = JS::GetMaybePtrFromReservedSlot(proxy, PyObjectSlot); + + JS::RootedObject selfCopy(cx, &jsTypeFactoryCopy(cx, self).toObject()); + + JS::HandleValueArray jArgs(args); + + if (!JS_CallFunctionName(cx, selfCopy, fName, jArgs, rval)) { + return false; + } + + return true; +} + +static bool array_includes(JSContext *cx, unsigned argc, JS::Value *vp) { + JS::CallArgs args = JS::CallArgsFromVp(argc, vp); + + JS::RootedValue jRet(cx); + + if (!array_copy_func(cx, args, "includes", &jRet)) { + return false; + } + + args.rval().setBoolean(jRet.toBoolean()); + return true; +} + +static bool array_forEach(JSContext *cx, unsigned argc, JS::Value *vp) { + JS::CallArgs args = JS::CallArgsFromVp(argc, vp); + + JS::RootedValue jRet(cx); + + if (!array_copy_func(cx, args, "forEach", &jRet)) { + return false; + } + + args.rval().setUndefined(); + return true; +} + +static bool array_map(JSContext *cx, unsigned argc, JS::Value *vp) { + JS::CallArgs args = JS::CallArgsFromVp(argc, vp); + + JS::RootedValue jRet(cx); + + if (!array_copy_func(cx, args, "map", &jRet)) { + return false; + } + + args.rval().setObject(jRet.toObject()); + return true; +} + +static bool array_filter(JSContext *cx, unsigned argc, JS::Value *vp) { + JS::CallArgs args = JS::CallArgsFromVp(argc, vp); + + JS::RootedValue jRet(cx); + + if (!array_copy_func(cx, args, "filter", &jRet)) { + return false; + } + + args.rval().setObject(jRet.toObject()); + return true; +} + +static bool array_reduce(JSContext *cx, unsigned argc, JS::Value *vp) { + JS::CallArgs args = JS::CallArgsFromVp(argc, vp); + + JS::RootedValue jRet(cx); + + if (!array_copy_func(cx, args, "reduce", &jRet)) { + return false; + } + + if (jRet.isNumber()) { + args.rval().setNumber(jRet.toNumber()); + } + else if (jRet.isString()) { + args.rval().setString(jRet.toString()); + } + else if (jRet.isObject()) { + args.rval().setObject(jRet.toObject()); + } + + return true; +} + +static bool array_reduceRight(JSContext *cx, unsigned argc, JS::Value *vp) { + JS::CallArgs args = JS::CallArgsFromVp(argc, vp); + + JS::RootedValue jRet(cx); + + if (!array_copy_func(cx, args, "reduceRight", &jRet)) { + return false; + } + + if (jRet.isNumber()) { + args.rval().setNumber(jRet.toNumber()); + } + else if (jRet.isString()) { + args.rval().setString(jRet.toString()); + } + else if (jRet.isObject()) { + args.rval().setObject(jRet.toObject()); + } + + return true; +} + + +static JSMethodDef array_methods[] = { + {"reverse", array_reverse, 0}, + {"pop", array_pop, 0}, + {"at", array_at, 1}, + {"join", array_join, 1}, + {"toString", array_toString, 0}, + {"push", array_push, 1}, + {"shift", array_shift, 0}, + {"unshift", array_unshift, 1}, + {"concat", array_concat, 1}, + {"slice", array_slice, 2}, + {"indexOf", array_indexOf, 1}, + {"lastIndexOf", array_lastIndexOf, 1}, + {"splice", array_splice, 2}, + {"sort", array_sort, 1}, + {"fill", array_fill, 3}, + {"copyWithin", array_copyWithin, 3}, + {"includes", array_includes, 1}, + {"forEach", array_forEach, 1}, + {"map", array_map, 1}, + {"filter", array_filter, 1}, + {"reduce", array_reduce, 1}, + {"reduceRight", array_reduceRight, 1}, + {NULL, NULL} +}; + + bool PyListProxyHandler::getOwnPropertyDescriptor( JSContext *cx, JS::HandleObject proxy, JS::HandleId id, JS::MutableHandle> desc ) const { + // see if we're calling a function + if (id.isString()) { + for (size_t index = 0;; index++) { + bool isThatFunction; + const char *methodName = array_methods[index].name; + if (methodName == NULL) { // reached end of list + break; + } + else if (JS_StringEqualsAscii(cx, id.toString(), methodName, &isThatFunction) && isThatFunction) { + JSFunction *newFunction = JS_NewFunction(cx, array_methods[index].call, array_methods[index].nargs, 0, NULL); + if (!newFunction) return false; + JS::RootedObject funObj(cx, JS_GetFunctionObject(newFunction)); + desc.set(mozilla::Some( + JS::PropertyDescriptor::Data( + JS::ObjectValue(*funObj), + {JS::PropertyAttribute::Enumerable} + ) + )); + return true; + } + } + } // We're trying to get the "length" property bool isLengthProperty; @@ -189,7 +1009,7 @@ bool PyListProxyHandler::getOwnPropertyDescriptor( // proxy.length = len(pyObject) desc.set(mozilla::Some( JS::PropertyDescriptor::Data( - JS::Int32Value(PySequence_Size(pyObject)) + JS::Int32Value(PyList_Size(pyObject)) ) )); return true; @@ -198,7 +1018,7 @@ bool PyListProxyHandler::getOwnPropertyDescriptor( // We're trying to get an item Py_ssize_t index; PyObject *item; - if (idToIndex(cx, id, &index) && (item = PySequence_GetItem(pyObject, index))) { + if (idToIndex(cx, id, &index) && (item = PyList_GetItem(pyObject, index))) { desc.set(mozilla::Some( JS::PropertyDescriptor::Data( jsTypeFactory(cx, item), @@ -231,7 +1051,7 @@ bool PyListProxyHandler::defineProperty( JS::RootedObject *global = new JS::RootedObject(cx, JS::GetNonCCWObjectGlobal(proxy)); JS::RootedValue *itemV = new JS::RootedValue(cx, desc.value()); PyObject *item = pyTypeFactory(cx, global, itemV)->getPyObject(); - if (PySequence_SetItem(pyObject, index, item) < 0) { + if (PyList_SetItem(pyObject, index, item) < 0) { return result.failBadIndex(); } return result.succeed(); @@ -239,7 +1059,7 @@ bool PyListProxyHandler::defineProperty( bool PyListProxyHandler::ownPropertyKeys(JSContext *cx, JS::HandleObject proxy, JS::MutableHandleIdVector props) const { // Modified from https://hg.mozilla.org/releases/mozilla-esr102/file/3b574e1/dom/base/RemoteOuterWindowProxy.cpp#l137 - int32_t length = PySequence_Size(pyObject); + int32_t length = PyList_Size(pyObject); if (!props.reserve(length + 1)) { return false; } @@ -259,8 +1079,22 @@ bool PyListProxyHandler::delete_(JSContext *cx, JS::HandleObject proxy, JS::Hand } // Set to undefined instead of actually deleting it - if (PySequence_SetItem(pyObject, index, Py_None) < 0) { + if (PyList_SetItem(pyObject, index, Py_None) < 0) { return result.failCantDelete(); // report failure } return result.succeed(); // report success } + +bool PyListProxyHandler::isArray(JSContext *cx, JS::HandleObject proxy, JS::IsArrayAnswer *answer) const { + return true; +} + +bool PyListProxyHandler::getBuiltinClass(JSContext *cx, JS::Handle obj, js::ESClass *cls) const { + *cls = js::ESClass::Array; + return true; +} + +const char *PyListProxyHandler::className(JSContext *cx, JS::HandleObject proxy) const { + // TODO untested + return "object"; +} \ No newline at end of file diff --git a/src/jsTypeFactory.cc b/src/jsTypeFactory.cc index fa631460..cc8daea5 100644 --- a/src/jsTypeFactory.cc +++ b/src/jsTypeFactory.cc @@ -172,6 +172,7 @@ JS::Value jsTypeFactory(JSContext *cx, PyObject *object) { JSObject *proxy; if (PyList_Check(object)) { proxy = js::NewProxyObject(cx, new PyListProxyHandler(object), v, NULL); + JS::SetReservedSlot(proxy, PyObjectSlot, JS::PrivateValue(object)); } else { proxy = js::NewProxyObject(cx, new PyProxyHandler(object), v, NULL); } @@ -198,6 +199,25 @@ JS::Value jsTypeFactory(JSContext *cx, PyObject *object) { return returnType; } +JS::Value jsTypeFactoryCopy(JSContext *cx, PyObject *object) { + JS::RootedValue returnType(cx); + if (PyList_Check(object)) { + Py_ssize_t listSize = PyList_Size(object); + JSObject *array = JS::NewArrayObject(cx, listSize); + JS::RootedObject arrayObj(cx, array); + for (Py_ssize_t index = 0; index < listSize; index++) { + JS::RootedValue jsValue(cx, jsTypeFactorySafe(cx, PyList_GetItem(object, index))); + JS_SetElement(cx, arrayObj, index, jsValue); + } + returnType.setObject(*array); + } + else { + returnType.setUndefined(); + } + + return returnType; +} + JS::Value jsTypeFactorySafe(JSContext *cx, PyObject *object) { JS::Value v = jsTypeFactory(cx, object); if (PyErr_Occurred()) { @@ -239,7 +259,9 @@ bool callPyFunc(JSContext *cx, unsigned int argc, JS::Value *vp) { JS::RootedObject *thisv = new JS::RootedObject(cx); JS_ValueToObject(cx, callargs.thisv(), thisv); - if (!callargs.length()) { + unsigned int callArgsLength = callargs.length(); + + if (!callArgsLength) { #if PY_VERSION_HEX >= 0x03090000 PyObject *pyRval = PyObject_CallNoArgs(pyFunc); #else @@ -255,8 +277,8 @@ bool callPyFunc(JSContext *cx, unsigned int argc, JS::Value *vp) { } // populate python args tuple - PyObject *pyArgs = PyTuple_New(callargs.length()); - for (size_t i = 0; i < callargs.length(); i++) { + PyObject *pyArgs = PyTuple_New(callArgsLength); + for (size_t i = 0; i < callArgsLength; i++) { JS::RootedValue *jsArg = new JS::RootedValue(cx, callargs[i]); PyType *pyArg = pyTypeFactory(cx, thisv, jsArg); if (!pyArg) return false; // error occurred diff --git a/tests/python/test_arrays.py b/tests/python/test_arrays.py new file mode 100644 index 00000000..16f471bf --- /dev/null +++ b/tests/python/test_arrays.py @@ -0,0 +1,827 @@ +import pythonmonkey as pm + +def test_assign(): + items = [1,2,3] + pm.eval("(arr) => {arr[0] = 42}")(items) + assert items[0] == 42 + +def test_get(): + items = [1,2,3] + result = [None] + pm.eval("(result, arr) => {result[0] = arr[1]}")(result, items) + assert result[0] == 2 + +def test_get_length(): + items = [1,2,3] + result = [None] + pm.eval("(result, arr) => {result[0] = arr.length}")(result, items) + assert result[0] == 3 + +def test_missing_func(): + items = [1,2,3] + try: + pm.eval("(arr) => {arr.after()}")(items) + assert (False) + except Exception as e: + assert str(type(e)) == "" + assert str(e).__contains__('TypeError: arr.after is not a function') + +#reverse +def test_reverse(): + items = [1,2,3] + pm.eval("(arr) => {arr.reverse()}")(items) + assert items == [3,2,1] + +def test_reverse_size_one(): + items = [1] + pm.eval("(arr) => {arr.reverse()}")(items) + assert items == [1] + +def test_reverse_size_zero(): + items = [] + pm.eval("(arr) => {arr.reverse()}")(items) + assert items == [] + +def test_reverse_returns_undefined(): + items = [1,2,3] + pm.eval("(arr) => {arr[0] = arr.reverse()}")(items) + assert items == [None,2,1] + + +def test_reverse_ignores_extra_args(): + items = [1,2,3] + pm.eval("(arr) => {arr.reverse(9)}")(items) + assert items == [3,2,1] + +#pop +def test_pop(): + items = [1,2,3] + result = [None] + pm.eval("(result, arr) => {result[0] = arr.pop()}")(result, items) + assert items == [1,2] + assert result[0] == 3 + +def test_pop_empty(): + items = [] + result = [None] + pm.eval("(result, arr) => {result[0] = arr.pop()}")(result, items) + assert items == [] + assert result[0] == None + +def test_pop_ignore_extra_args(): + items = [1,2,3] + result = [None] + pm.eval("(result, arr) => {result[0] = arr.pop(1)}")(result, items) + assert items == [1,2] + assert result[0] == 3 + +#at +def test_at_no_arg(): + items = [1,2,3] + try: + pm.eval("(arr) => {arr.at()}")(items) + assert (False) + except Exception as e: + assert str(type(e)) == "" + assert str(e).__contains__('TypeError: at: At least 1 argument required, but only 0 passed') + +def test_at(): + items = [1,2,3] + result = [None] + pm.eval("(result, arr) => {result[0] = arr.at(0)}")(result, items) + assert result[0] == 1 + +def test_at_wrong_index_type(): + items = [1,2,3] + result = [None] + pm.eval("(result, arr) => {result[0] = arr.at('f')}")(result, items) + assert result[0] == 1 + +def test_at_index_too_large(): + items = [1,2,3] + result = [None] + pm.eval("(result, arr) => {result[0] = arr.at(10)}")(result, items) + assert result[0] == None + +def test_at_index_negative(): + items = [1,2,3] + result = [None] + pm.eval("(result, arr) => {result[0] = arr.at(-2)}")(result, items) + assert result[0] == 2 + +#join +def test_join_no_arg(): + items = [1,2,3] + result = [None] + pm.eval("(result, arr) => {result[0] = arr.join()}")(result, items) + assert result[0] == '1,2,3' + +def test_join_no_arg_diff_types(): + items = [1,False,"END"] + result = [None] + pm.eval("(result, arr) => {result[0] = arr.join()}")(result, items) + assert result[0] == '1,false,END' + +def test_join_no_arg_with_embedded_list_type(): + items = [1,[2,3],"END"] + result = [None] + pm.eval("(result, arr) => {result[0] = arr.join()}")(result, items) + assert result[0] == '1,2,3,END' + +def test_join_with_sep(): + items = [1,2,3] + result = [None] + pm.eval("(result, arr) => {result[0] = arr.join('-')}")(result, items) + assert result[0] == '1-2-3' + +#toString +def test_toString(): + items = [1,2,3] + result = [None] + pm.eval("(result, arr) => {result[0] = arr.toString()}")(result, items) + assert result[0] == '1,2,3' + +#push +def test_push(): + items = [1,2,3] + result = [None] + pm.eval("(result, arr) => {result[0] = arr.push(4)}")(result, items) + assert items == [1,2,3,4] + assert result[0] == 4 + +def test_push_no_arg(): + items = [1,2,3] + result = [None] + pm.eval("(result, arr) => {result[0] = arr.push()}")(result, items) + assert items == [1,2,3,] + assert result[0] == 3 + +def test_push_two_args(): + items = [1,2,3] + result = [None] + pm.eval("(result, arr) => {result[0] = arr.push(4,false)}")(result, items) + assert items == [1,2,3,4,False] + assert result[0] == 5 + +def test_push_list(): + items = [1,2,3] + result = [None] + pm.eval("(result, arr) => {result[0] = arr.push([4,5])}")(result, items) + assert items == [1,2,3,[4,5]] + assert result[0] == 4 + +#shift +def test_shift(): + items = [1,2,3] + result = [None] + pm.eval("(result, arr) => {result[0] = arr.shift()}")(result, items) + assert items == [2,3] + assert result[0] == 1 + +def test_shift_empty(): + items = [] + result = [None] + pm.eval("(result, arr) => {result[0] = arr.shift()}")(result, items) + assert items == [] + assert result[0] == None + +#unshift +def test_unshift_zero_arg(): + items = [1,2,3] + result = [None] + pm.eval("(result, arr) => {result[0] = arr.unshift()}")(result, items) + assert items == [1,2,3] + assert result[0] == 3 + +def test_unshift_one_arg(): + items = [1,2,3] + result = [None] + pm.eval("(result, arr) => {result[0] = arr.unshift(6)}")(result, items) + assert items == [6,1,2,3] + assert result[0] == 4 + +def test_unshift_two_args(): + items = [1,2,3] + result = [None] + pm.eval("(result, arr) => {result[0] = arr.unshift(6,7)}")(result, items) + assert items == [6,7,1,2,3] + assert result[0] == 5 + +#concat +def test_concat_primitive(): + items = [1,2,3] + result = [None] + pm.eval("(result, arr) => {result[0] = arr.concat(4)}")(result, items) + assert items == [1,2,3] + assert result[0] == [1,2,3,4] + +def test_concat_array(): + items = [1,2,3] + result = [None] + pm.eval("(result, arr) => {result[0] = arr.concat([4,5])}")(result, items) + assert items == [1,2,3] + assert result[0] == [1,2,3,4,5] + +def test_concat_empty_arg(): + items = [1,2,3] + result = [None] + pm.eval("(result, arr) => {result[0] = arr.concat()}")(result, items) + assert items == [1,2,3] + assert result[0] == [1,2,3] + +def test_concat_two_arrays(): + items = [1,2,3] + result = [None] + pm.eval("(result, arr) => {result[0] = arr.concat([7,8], [0,1])}")(result, items) + assert items == [1,2,3] + assert result[0] == [1,2,3,7,8,0,1] + +def test_concat_mix(): + items = [1,2,3] + result = [None] + pm.eval("(result, arr) => {result[0] = arr.concat([7,8], true, [0,1])}")(result, items) + assert items == [1,2,3] + assert result[0] == [1,2,3,7,8,True, 0,1] + +#slice +def test_slice(): + items = [1,2,3] + result = [None] + pm.eval("(result, arr) => {result[0] = arr.slice(1,2)}")(result, items) + assert items == [1,2,3] + assert result[0] == [2] + +def test_slice_start_zero(): + items = [1,2,3] + result = [None] + pm.eval("(result, arr) => {result[0] = arr.slice(0,2)}")(result, items) + assert items == [1,2,3] + assert result[0] == [1,2] + +def test_slice_stop_length(): + items = [1,2,3] + result = [None] + pm.eval("(result, arr) => {result[0] = arr.slice(1,3)}")(result, items) + assert items == [1,2,3] + assert result[0] == [2,3] + +def test_slice_stop_beyond_length(): + items = [1,2,3] + result = [None] + pm.eval("(result, arr) => {result[0] = arr.slice(1,4)}")(result, items) + assert items == [1,2,3] + assert result[0] == [2,3] + +def test_slice_start_negative(): + items = [1,2,3] + result = [None] + pm.eval("(result, arr) => {result[0] = arr.slice(-3,-1)}")(result, items) + assert result[0] == [1,2] + +#indexOf +def test_indexOf(): + items = [1,2,3] + result = [None] + pm.eval("(result, arr) => {result[0] = arr.indexOf(1)}")(result, items) + assert result[0] == 0 + +def test_indexOf_with_start(): + items = [1,2,3] + result = [None] + pm.eval("(result, arr) => {result[0] = arr.indexOf(3, 1)}")(result, items) + assert result[0] == 2 + +def test_indexOf_with_negative_start(): + items = [1,2,3] + result = [None] + pm.eval("(result, arr) => {result[0] = arr.indexOf(3, -2)}")(result, items) + assert result[0] == 2 + +def test_indexOf_zero_size(): + items = [] + result = [None] + pm.eval("(result, arr) => {result[0] = arr.indexOf(1)}")(result, items) + assert result[0] == -1 + +def test_indexOf_start_beyond_length(): + items = [1,2,3] + result = [None] + pm.eval("(result, arr) => {result[0] = arr.indexOf(1, 10)}")(result, items) + assert result[0] == -1 + +def test_indexOf_not_found(): + items = [1,2,3] + result = [None] + pm.eval("(result, arr) => {result[0] = arr.indexOf(10)}")(result, items) + assert result[0] == -1 + +#lastIndexOf +def test_lastIndexOf(): + items = [1,2,3] + result = [None] + pm.eval("(result, arr) => {result[0] = arr.lastIndexOf(1)}")(result, items) + assert result[0] == 0 + +def test_lastIndexOf_dup(): + items = [1,2,3,1] + result = [None] + pm.eval("(result, arr) => {result[0] = arr.lastIndexOf(1)}")(result, items) + assert result[0] == 3 + +def test_lastIndexOf_with_from_index(): + items = [1,2,3] + result = [None] + pm.eval("(result, arr) => {result[0] = arr.lastIndexOf(1, 2)}")(result, items) + assert result[0] == 0 + +def test_lastIndexOf_with_from_index_greater_than_size(): + items = [1,2,3] + result = [None] + pm.eval("(result, arr) => {result[0] = arr.lastIndexOf(1, 10)}")(result, items) + assert result[0] == 0 + +def test_lastIndexOf_with_negative_from_index(): + items = [1,2,3] + result = [None] + pm.eval("(result, arr) => {result[0] = arr.lastIndexOf(1, -2)}")(result, items) + assert result[0] == 0 + +def test_lastIndexOf_not_found(): + items = [1,2,3] + result = [None] + pm.eval("(result, arr) => {result[0] = arr.lastIndexOf(3, 0)}")(result, items) + assert result[0] == -1 + +#splice +def test_splice_no_args(): + items = [1,2,3] + result = [None] + pm.eval("(result, arr) => {result[0] = arr.splice()}")(result, items) + assert items == [1,2,3] + assert result[0] == [] + +def test_splice_one_arg(): + items = [1,2,3] + result = [None] + pm.eval("(result, arr) => {result[0] = arr.splice(1)}")(result, items) + assert items == [1] + assert result[0] == [2,3] + +def test_splice_one_arg_negative(): + items = [1,2,3] + result = [None] + pm.eval("(result, arr) => {result[0] = arr.splice(-2)}")(result, items) + assert items == [1] + assert result[0] == [2,3] + +def test_splice_two_args_negative_count(): + items = [1,2,3] + result = [None] + pm.eval("(result, arr) => {result[0] = arr.splice(1, -1)}")(result, items) + assert items == [1,2,3] + assert result[0] == [] + +def test_splice_two_args_zero_count(): + items = [1,2,3] + result = [None] + pm.eval("(result, arr) => {result[0] = arr.splice(1, 0)}")(result, items) + assert items == [1,2,3] + assert result[0] == [] + +def test_splice_two_args_one_count(): + items = [1,2,3] + result = [None] + pm.eval("(result, arr) => {result[0] = arr.splice(1, 1)}")(result, items) + assert items == [1,3] + assert result[0] == [2] + +def test_splice_two_args_two_count(): + items = [1,2,3] + result = [None] + pm.eval("(result, arr) => {result[0] = arr.splice(1, 2)}")(result, items) + assert items == [1] + assert result[0] == [2,3] + +def test_splice_three_args_zero_count(): + items = [1,2,3] + result = [None] + pm.eval("(result, arr) => {result[0] = arr.splice(1,0,5)}")(result, items) + assert items == [1,5,2,3] + assert result[0] == [] + +def test_splice_three_args_one_count(): + items = [1,2,3] + result = [None] + pm.eval("(result, arr) => {result[0] = arr.splice(1,1,5)}")(result, items) + assert items == [1,5,3] + assert result[0] == [2] + +def test_splice_three_args_two_count(): + items = [1,2,3] + result = [None] + pm.eval("(result, arr) => {result[0] = arr.splice(1,2,5)}")(result, items) + assert items == [1,5] + assert result[0] == [2,3] + +def test_splice_four_args_zero_count(): + items = [1,2,3] + result = [None] + pm.eval("(result, arr) => {result[0] = arr.splice(1,0,5,6)}")(result, items) + assert items == [1,5,6,2,3] + assert result[0] == [] + +def test_splice_four_args_one_count(): + items = [1,2,3] + result = [None] + pm.eval("(result, arr) => {result[0] = arr.splice(1,1,5,6)}")(result, items) + assert items == [1,5,6,3] + assert result[0] == [2] + +def test_splice_four_args_two_count(): + items = [1,2,3] + result = [None] + pm.eval("(result, arr) => {result[0] = arr.splice(1,2,5,6)}")(result, items) + assert items == [1,5,6] + assert result[0] == [2,3] + +#fill +def test_fill_returns_ref_to_self(): + items = [1,2,3] + result = [None] + pm.eval("(result, arr) => {result[0] = arr.fill(8)}")(result, items) + assert items == [8,8,8] + assert result[0] == [8,8,8] + result[0][0] = 9 + assert items == [9,8,8] + +def test_fill_other_type(): + items = [1,2,3] + result = [None] + pm.eval("(result, arr) => {result[0] = arr.fill(false)}")(result, items) + assert items == [False,False,False] + +def test_fill_with_start(): + items = [1,2,3] + result = [None] + pm.eval("(result, arr) => {result[0] = arr.fill(8,1)}")(result, items) + assert items == [1,8,8] + +def test_fill_with_start_negative(): + items = [1,2,3] + result = [None] + pm.eval("(result, arr) => {result[0] = arr.fill(8,-2)}")(result, items) + assert items == [1,8,8] + +def test_fill_with_start_too_high(): + items = [1,2,3] + result = [None] + pm.eval("(result, arr) => {result[0] = arr.fill(8,7)}")(result, items) + assert items == [1,2,3] + +def test_fill_with_stop(): + items = [1,2,3] + result = [None] + pm.eval("(result, arr) => {result[0] = arr.fill(8,1,2)}")(result, items) + assert items == [1,8,3] + +def test_fill_with_negative_stop(): + items = [1,2,3] + result = [None] + pm.eval("(result, arr) => {result[0] = arr.fill(8,1,-1)}")(result, items) + assert items == [1,8,3] + +def test_fill_with_stop_too_high(): + items = [1,2,3] + result = [None] + pm.eval("(result, arr) => {result[0] = arr.fill(8,1,10)}")(result, items) + assert items == [1,8,8] + +#copyWithin +def test_copyWithin_returns_ref_to_self(): + items = [1,2,3] + result = [None] + pm.eval("(result, arr) => {result[0] = arr.copyWithin(0,1)}")(result, items) + assert items == [2,3,3] + assert result[0] == [2,3,3] + result[0][0] = 9 + assert items == [9,3,3] + +def test_copyWithin(): + items = [1,2,3] + result = [None] + pm.eval("(result, arr) => {result[0] = arr.copyWithin(0,1)}")(result, items) + assert items == [2,3,3] + assert result[0] == [2,3,3] + result[0][0] = 9 + assert items == [9,3,3] + +def test_copyWithin_no_args(): + items = [1,2,3] + result = [None] + pm.eval("(result, arr) => {result[0] = arr.copyWithin()}")(result, items) + assert items == [1,2,3] + +def test_copyWithin_target_only_overwrite_all(): + items = [1,2,3] + result = [None] + pm.eval("(result, arr) => {result[0] = arr.copyWithin(0)}")(result, items) + assert items == [1,2,3] + +def test_copyWithin_target_only(): + items = [1,2,3] + result = [None] + pm.eval("(result, arr) => {result[0] = arr.copyWithin(1)}")(result, items) + assert items == [1,1,2] + +def test_copyWithin_negative_target_only(): + items = [1,2,3] + result = [None] + pm.eval("(result, arr) => {result[0] = arr.copyWithin(-1)}")(result, items) + assert items == [1,2,1] + +def test_copyWithin_target_too_large(): + items = [1,2,3] + result = [None] + pm.eval("(result, arr) => {result[0] = arr.copyWithin(10)}")(result, items) + assert items == [1,2,3] + +def test_copyWithin_target_and_start(): + items = [1,2,3] + result = [None] + pm.eval("(result, arr) => {result[0] = arr.copyWithin(1, 2)}")(result, items) + assert items == [1,3,3] + +def test_copyWithin_target_and_start_too_large(): + items = [1,2,3] + result = [None] + pm.eval("(result, arr) => {result[0] = arr.copyWithin(1, 2)}")(result, items) + assert items == [1,3,3] + +def test_copyWithin_target_and_negative_start(): + items = [1,2,3] + result = [None] + pm.eval("(result, arr) => {result[0] = arr.copyWithin(1, -1)}")(result, items) + assert items == [1,3,3] + +def test_copyWithin_target_and_start_and_end(): + items = [1,2,3,4,5] + result = [None] + pm.eval("(result, arr) => {result[0] = arr.copyWithin(1,2,3)}")(result, items) + assert items == [1,3,3,4,5] + +def test_copyWithin_target_and_start_and_negative_end(): + items = [1,2,3,4,5] + result = [None] + pm.eval("(result, arr) => {result[0] = arr.copyWithin(1,2,-2)}")(result, items) + assert items == [1,3,3,4,5] + +#includes +def test_includes(): + items = [1,2,3] + result = [None] + pm.eval("(result, arr) => {result[0] = arr.includes(1)}")(result, items) + assert result[0] == True + +def test_includes_other_type(): + items = [1,2,'Hi'] + result = [None] + pm.eval("(result, arr) => {result[0] = arr.includes('Hi')}")(result, items) + assert result[0] == True + +def test_includes_not(): + items = [1,2,3] + result = [None] + pm.eval("(result, arr) => {result[0] = arr.includes(5)}")(result, items) + assert result[0] == False + +def test_includes_not_other_type(): + items = [1,2,'Hi'] + result = [None] + pm.eval("(result, arr) => {result[0] = arr.includes('Joe')}")(result, items) + assert result[0] == False + +def test_includes_too_few_args(): + items = [4,2,6,7] + try: + pm.eval("(arr) => {arr.includes()}")(items) + assert (False) + except Exception as e: + assert str(type(e)) == "" + assert str(e).__contains__("TypeError: includes: At least 1 argument required, but only 0 passed") + + +#sort +def test_sort_empty(): + items = [] + result = [None] + pm.eval("(result, arr) => {result[0] = arr.sort()}")(result, items) + assert result[0] == items + assert items == [] + +def test_sort_numbers(): + items = [4,2,6,7] + result = [None] + pm.eval("(result, arr) => {result[0] = arr.sort()}")(result, items) + assert result[0] == items + assert items == [2,4,6,7] + +def test_sort_strings(): + items = ['Four', 'Three', 'One'] + pm.eval("(arr) => {arr.sort()}")(items) + assert items == ['Four', 'One', 'Three'] + +def test_sort_with_two_args_keyfunc(): + items = [4,2,6,7] + def myFunc(e,f): + return len(e) - len(f) + try: + pm.eval("(arr, compareFun) => {arr.sort(compareFun)}")(items, myFunc) + assert (False) + except Exception as e: + assert str(type(e)) == "" + assert str(e).__contains__("myFunc() missing 1 required positional argument: 'f'") + +def test_sort_with_one_arg_keyfunc(): + items = ['Four', 'Three', 'One'] + def myFunc(e): + return len(e) + pm.eval("(arr, compareFun) => {arr.sort(compareFun)}")(items, myFunc) + assert items == ['One', 'Four', 'Three'] + +def test_sort_with_builtin_keyfunc(): + items = ['Four', 'Three', 'One'] + pm.eval("(arr, compareFun) => {arr.sort(compareFun)}")(items, len) + assert items == ['One', 'Four', 'Three'] + +def test_sort_with_builtin_keyfunc_wrong_data_type(): + items = [4,2,6,7] + try: + pm.eval("(arr, compareFun) => {arr.sort(compareFun)}")(items, len) + assert (False) + except Exception as e: + assert str(type(e)) == "" + assert str(e).__contains__("object of type 'int' has no len()") + +def test_sort_with_js_func(): + items = ['Four', 'Three', 'One'] + result = [None] + myFunc = pm.eval("((a, b) => a.toLocaleUpperCase() < b.toLocaleUpperCase() ? -1 : 1)") + pm.eval("(result, arr, compareFun) => {result[0] = arr.sort(compareFun)}")(result, items, myFunc) + assert result[0] == items + assert items == ['Four', 'One', 'Three'] + +def test_sort_with_js_func_wrong_data_type(): + items = [4,2,6,7] + myFunc = pm.eval("((a, b) => a.toLocaleUpperCase() < b.toLocaleUpperCase() ? -1 : 1)") + try: + pm.eval("(arr, compareFun) => {arr.sort(compareFun)}")(items, myFunc) + assert (False) + except Exception as e: + assert str(type(e)) == "" + assert str(e).__contains__("TypeError: a.toLocaleUpperCase is not a function") + +#forEach +def test_forEach(): + items = ['Four', 'Three', 'One'] + result = [''] + pm.eval("(result, arr) => {arr.forEach((element) => result[0] += element)}")(result, items) + assert items == ['Four', 'Three', 'One'] + assert result == ['FourThreeOne'] + +def test_forEach_check_index(): + items = ['Four', 'Three', 'One'] + result = [''] + pm.eval("(result, arr) => {arr.forEach((element, index) => result[0] += index)}")(result, items) + assert result == ['012'] + +def test_forEach_check_array(): + items = ['Four', 'Three', 'One'] + result = [''] + pm.eval("(result, arr) => {arr.forEach((element, index, array) => result[0] = array)}")(result, items) + assert result == [items] + +def test_forEach_check_this_arg(): + items = ['Four', 'Three', 'One'] + result = [None] + pm.eval("(result, arr) => {class Counter { constructor() { this.count = 0;} add(array) { array.forEach(function countEntry(entry) { ++this.count; }, this);}} const obj = new Counter(); obj.add(arr); result[0] = obj.count;}")(result, items) + assert result == [3] + +def test_forEach_too_few_args(): + items = [4,2,6,7] + try: + pm.eval("(arr) => {arr.forEach()}")(items) + assert (False) + except Exception as e: + assert str(type(e)) == "" + assert str(e).__contains__("TypeError: forEach: At least 1 argument required, but only 0 passed") + +#map +def test_map(): + items = [4,2,6,7] + result = [None] + pm.eval("(result, arr) => {result[0] = arr.map((x) => x * x)}")(result, items) + assert items == [4,2,6,7] + assert result[0] == [16,4,36,49] + +def test_map_too_few_args(): + items = [4,2,6,7] + try: + pm.eval("(arr) => {arr.map()}")(items) + assert (False) + except Exception as e: + assert str(type(e)) == "" + assert str(e).__contains__("TypeError: map: At least 1 argument required, but only 0 passed") + +def test_map_check_index(): + items = ['Four', 'Three', 'One'] + result = [''] + pm.eval("(result, arr) => {arr.map((element, index) => result[0] += index)}")(result, items) + assert result == ['012'] + +def test_map_check_array(): + items = ['Four', 'Three', 'One'] + result = [''] + pm.eval("(result, arr) => {arr.map((element, index, array) => result[0] = array)}")(result, items) + assert result == [items] + +#filter +def test_filter(): + words = ['spray', 'elite', 'exuberant', 'destruction', 'present'] + result = [None] + pm.eval("(result, arr) => {result[0] = arr.filter((word) => word.length > 6)}")(result, words) + assert words == ['spray', 'elite', 'exuberant', 'destruction', 'present'] + assert result[0] == ['exuberant', 'destruction', 'present'] + +def test_filter_too_few_args(): + items = [4,2,6,7] + try: + pm.eval("(arr) => {arr.filter()}")(items) + assert (False) + except Exception as e: + assert str(type(e)) == "" + assert str(e).__contains__("TypeError: filter: At least 1 argument required, but only 0 passed") + +#reduce index, array param, too few args same as previous few +def test_reduce(): + items = [1,2,3,4,5] + result = [None] + pm.eval("(result, arr) => {result[0] = arr.reduce((accumulator, currentValue) => accumulator + currentValue, 0)}")(result, items) + assert items == [1,2,3,4,5] + assert result[0] == 15 + +def test_reduce_float(): + items = [1.9, 4.6, 9.3, 16.5] + result = [None] + pm.eval("(result, arr) => {result[0] = arr.reduce((accumulator, currentValue) => accumulator + currentValue, 0)}")(result, items) + assert result[0] == 32.3 + +def test_reduce_string(): + items = ['Hi', 'There'] + result = [None] + pm.eval("(result, arr) => {result[0] = arr.reduce((accumulator, currentValue) => accumulator + currentValue, "")}")(result, items) + assert result[0] == 'HiThere' + +def test_reduce_initial_value_not_zero(): + items = [1,2,3,4,5] + result = [None] + pm.eval("(result, arr) => {result[0] = arr.reduce((accumulator, currentValue) => accumulator + currentValue, 5)}")(result, items) + assert items == [1,2,3,4,5] + assert result[0] == 20 + +def test_reduce_no_initial_value(): + items = [1,2,3,4,5] + result = [None] + pm.eval("(result, arr) => {result[0] = arr.reduce((accumulator, currentValue) => accumulator + currentValue)}")(result, items) + assert items == [1,2,3,4,5] + assert result[0] == 15 + +def test_reduce_list_meaningless(): + items = [['Hi', 'There']] + result = [None] + pm.eval("(result, arr) => {result[0] = arr.reduce((x) => x * 2)}")(result, items) + assert result[0] == ['Hi', 'There'] + +#reduceRight +def test_reduceRight_list_concat(): + items = [[0, 1],[2, 3],[4, 5]] + result = [None] + pm.eval("(result, arr) => {result[0] = arr.reduceRight((accumulator, currentValue) => accumulator.concat(currentValue))}")(result, items) + assert result[0] == [4, 5, 2, 3, 0, 1] + +def test_reduceRight_list_concat_with_initial_value(): + items = [[0, 1],[2, 3],[4, 5]] + result = [None] + pm.eval("(result, arr) => {result[0] = arr.reduceRight((accumulator, currentValue) => accumulator.concat(currentValue), [7,8])}")(result, items) + assert result[0] == [7, 8, 4, 5, 2, 3, 0, 1] + +def test_reduceRight(): + items = [0,1,2,3,4] + result = [None] + pm.eval("(result, arr) => {result[0] = arr.reduceRight((accumulator, currentValue, index, array) => accumulator + currentValue)}")(result, items) + assert result[0] == 10 + +def test_reduceRight_float(): + items = [1.9, 4.6, 9.3, 16.5] + result = [None] + pm.eval("(result, arr) => {result[0] = arr.reduceRight((accumulator, currentValue, index, array) => accumulator + currentValue)}")(result, items) + assert result[0] == 32.3 \ No newline at end of file diff --git a/tests/python/test_list_array.py b/tests/python/test_list_array.py index 4def6276..7d8182e8 100644 --- a/tests/python/test_list_array.py +++ b/tests/python/test_list_array.py @@ -8,3 +8,14 @@ def test_eval_array_is_list(): def test_eval_array_is_list_type_string(): pythonListTypeString = str(type(pm.eval('[]'))) assert pythonListTypeString == "" + +def test_eval_list_is_array(): + items = [1, 2, 3] + isArray = pm.eval('Array.isArray')(items) + assert isArray == True + +def test_typeof_array(): + items = [1, 2, 3] + result = [None] + pm.eval("(result, arr) => {result[0] = typeof arr}")(result, items) + assert result[0] == 'object' \ No newline at end of file From 0cf389a4356d2bede5f12384e289de0c8a3c8952 Mon Sep 17 00:00:00 2001 From: Philippe Laporte Date: Wed, 20 Dec 2023 17:01:23 -0500 Subject: [PATCH 02/58] comment --- src/JSArrayProxy.cc | 1 - 1 file changed, 1 deletion(-) diff --git a/src/JSArrayProxy.cc b/src/JSArrayProxy.cc index f43858f9..17cbac02 100644 --- a/src/JSArrayProxy.cc +++ b/src/JSArrayProxy.cc @@ -743,7 +743,6 @@ int JSArrayProxyMethodDefinitions::JSArrayProxy_clear_slot(JSArrayProxy *self) { } int JSArrayProxyMethodDefinitions::JSArrayProxy_traverse(JSArrayProxy *self, visitproc visit, void *arg) { - // TODO untested JS::RootedObject *global = new JS::RootedObject(GLOBAL_CX, JS::GetNonCCWObjectGlobal(self->jsObject)); for (Py_ssize_t i = JSArrayProxy_length(self); --i >= 0; ) { JS::RootedValue *elementVal = new JS::RootedValue(GLOBAL_CX); From 2239514982f05aef6cf4a9f1524f4f881d085c2e Mon Sep 17 00:00:00 2001 From: Philippe Laporte Date: Wed, 20 Dec 2023 17:32:05 -0500 Subject: [PATCH 03/58] All Python List Proxy methods in except for iterator-based ones --- src/PyProxyHandler.cc | 221 +++++++++--------------------------- tests/python/test_arrays.py | 212 ++++++++++++++++++++++++++++------ 2 files changed, 231 insertions(+), 202 deletions(-) diff --git a/src/PyProxyHandler.cc b/src/PyProxyHandler.cc index d0b27f90..2a22b32d 100644 --- a/src/PyProxyHandler.cc +++ b/src/PyProxyHandler.cc @@ -224,94 +224,6 @@ static bool array_pop(JSContext *cx, unsigned argc, JS::Value *vp) { return true; } -static bool array_at(JSContext *cx, unsigned argc, JS::Value *vp) { - JS::CallArgs args = JS::CallArgsFromVp(argc, vp); - - if (!args.requireAtLeast(cx, "at", 1)) { - return false; - } - - JS::RootedObject proxy(cx, JS::ToObject(cx, args.thisv())); - if (!proxy) { - return false; - } - PyObject *self = JS::GetMaybePtrFromReservedSlot(proxy, PyObjectSlot); - - int64_t index; - if (!JS::ToInt64(cx, args[0], &index)) { - return false; - } - - Py_ssize_t selfSize = PyList_GET_SIZE(self); - - if (index < 0) { - index += selfSize; - } - - if (index < 0 || index >= selfSize) { - args.rval().setUndefined(); - return true; - } - - PyObject *result = PyList_GetItem(self, (Py_ssize_t)index); - - args.rval().set(jsTypeFactory(cx, result)); - return true; -} - -static bool array_join(JSContext *cx, unsigned argc, JS::Value *vp) { - JS::CallArgs args = JS::CallArgsFromVp(argc, vp); - - JS::RootedObject proxy(cx, JS::ToObject(cx, args.thisv())); - if (!proxy) { - return false; - } - PyObject *self = JS::GetMaybePtrFromReservedSlot(proxy, PyObjectSlot); - - // convert to JSArray (copy) then call join on it - JS::RootedObject selfCopy(cx, &jsTypeFactoryCopy(cx, self).toObject()); - - JS::RootedString sepstr(cx); - if (args.hasDefined(0)) { - sepstr = JS::ToString(cx, args[0]); - if (!sepstr) { - return false; - } - } else { - sepstr = JS_NewStringCopyZ(cx, ","); - } - - JS::Rooted> jArgs(cx); - jArgs[0].setString(sepstr); - JS::RootedValue jRet(cx); - if (!JS_CallFunctionName(cx, selfCopy, "join", jArgs, &jRet)) { - return false; - } - - args.rval().setString(jRet.toString()); - return true; -} - -static bool array_toString(JSContext *cx, unsigned argc, JS::Value *vp) { - JS::CallArgs args = JS::CallArgsFromVp(argc, vp); - - JS::RootedObject proxy(cx, JS::ToObject(cx, args.thisv())); - if (!proxy) { - return false; - } - PyObject *self = JS::GetMaybePtrFromReservedSlot(proxy, PyObjectSlot); - - JS::RootedObject selfCopy(cx, &jsTypeFactoryCopy(cx, self).toObject()); - - JS::RootedValue jRet(cx); - if (!JS_CallFunctionName(cx, selfCopy, "toString", JS::HandleValueArray::empty(), &jRet)) { - return false; - } - - args.rval().setString(jRet.toString()); - return true; -} - static bool array_push(JSContext *cx, unsigned argc, JS::Value *vp) { // surely the function name is in there...review JSAPI examples JS::CallArgs args = JS::CallArgsFromVp(argc, vp); @@ -643,9 +555,12 @@ static bool array_sort(JSContext *cx, unsigned argc, JS::Value *vp) { if (flags & METH_VARARGS && !(flags & METH_KEYWORDS)) { // it's a JS func + // We don't want to put in all the sort code so we'll tolerate the following slight O(n) inefficiency + // copy to JS for sorting JS::RootedObject selfCopy(cx, &jsTypeFactoryCopy(cx, self).toObject()); + // sort JS::RootedValue jReturnedArray(cx); JS::HandleValueArray jArgs(args); if (!JS_CallFunctionName(cx, selfCopy, "sort", jArgs, &jReturnedArray)) { @@ -830,8 +745,11 @@ static bool array_copyWithin(JSContext *cx, unsigned argc, JS::Value *vp) { return true; } -static bool array_copy_func(JSContext *cx, JS::CallArgs args, const char *fName, JS::MutableHandle rval) { - if (!args.requireAtLeast(cx, fName, 1)) { +// private util +static bool array_copy_func(JSContext *cx, unsigned argc, JS::Value *vp, const char *fName, bool checkRequireAtLeastOne = true) { + JS::CallArgs args = JS::CallArgsFromVp(argc, vp); + + if (checkRequireAtLeastOne && !args.requireAtLeast(cx, fName, 1)) { return false; } @@ -844,117 +762,81 @@ static bool array_copy_func(JSContext *cx, JS::CallArgs args, const char *fName, JS::RootedObject selfCopy(cx, &jsTypeFactoryCopy(cx, self).toObject()); JS::HandleValueArray jArgs(args); + JS::RootedValue rval(cx); - if (!JS_CallFunctionName(cx, selfCopy, fName, jArgs, rval)) { + if (!JS_CallFunctionName(cx, selfCopy, fName, jArgs, &rval)) { return false; } + args.rval().set(rval); + return true; } static bool array_includes(JSContext *cx, unsigned argc, JS::Value *vp) { - JS::CallArgs args = JS::CallArgsFromVp(argc, vp); - - JS::RootedValue jRet(cx); - - if (!array_copy_func(cx, args, "includes", &jRet)) { - return false; - } - - args.rval().setBoolean(jRet.toBoolean()); - return true; + return array_copy_func(cx, argc, vp, "includes"); } static bool array_forEach(JSContext *cx, unsigned argc, JS::Value *vp) { - JS::CallArgs args = JS::CallArgsFromVp(argc, vp); - - JS::RootedValue jRet(cx); - - if (!array_copy_func(cx, args, "forEach", &jRet)) { - return false; - } - - args.rval().setUndefined(); - return true; + return array_copy_func(cx, argc, vp, "forEach"); } static bool array_map(JSContext *cx, unsigned argc, JS::Value *vp) { - JS::CallArgs args = JS::CallArgsFromVp(argc, vp); - - JS::RootedValue jRet(cx); - - if (!array_copy_func(cx, args, "map", &jRet)) { - return false; - } - - args.rval().setObject(jRet.toObject()); - return true; + return array_copy_func(cx, argc, vp, "map"); } static bool array_filter(JSContext *cx, unsigned argc, JS::Value *vp) { - JS::CallArgs args = JS::CallArgsFromVp(argc, vp); - - JS::RootedValue jRet(cx); - - if (!array_copy_func(cx, args, "filter", &jRet)) { - return false; - } - - args.rval().setObject(jRet.toObject()); - return true; + return array_copy_func(cx, argc, vp, "filter"); } static bool array_reduce(JSContext *cx, unsigned argc, JS::Value *vp) { - JS::CallArgs args = JS::CallArgsFromVp(argc, vp); + return array_copy_func(cx, argc, vp, "reduce"); +} - JS::RootedValue jRet(cx); +static bool array_reduceRight(JSContext *cx, unsigned argc, JS::Value *vp) { + return array_copy_func(cx, argc, vp, "reduceRight"); +} - if (!array_copy_func(cx, args, "reduce", &jRet)) { - return false; - } +static bool array_some(JSContext *cx, unsigned argc, JS::Value *vp) { + return array_copy_func(cx, argc, vp, "some"); +} - if (jRet.isNumber()) { - args.rval().setNumber(jRet.toNumber()); - } - else if (jRet.isString()) { - args.rval().setString(jRet.toString()); - } - else if (jRet.isObject()) { - args.rval().setObject(jRet.toObject()); - } +static bool array_every(JSContext *cx, unsigned argc, JS::Value *vp) { + return array_copy_func(cx, argc, vp, "every"); +} - return true; +static bool array_find(JSContext *cx, unsigned argc, JS::Value *vp) { + return array_copy_func(cx, argc, vp, "find"); } -static bool array_reduceRight(JSContext *cx, unsigned argc, JS::Value *vp) { - JS::CallArgs args = JS::CallArgsFromVp(argc, vp); +static bool array_findIndex(JSContext *cx, unsigned argc, JS::Value *vp) { + return array_copy_func(cx, argc, vp, "findIndex"); +} - JS::RootedValue jRet(cx); +static bool array_flat(JSContext *cx, unsigned argc, JS::Value *vp) { + return array_copy_func(cx, argc, vp, "flat", false); +} - if (!array_copy_func(cx, args, "reduceRight", &jRet)) { - return false; - } +static bool array_flatMap(JSContext *cx, unsigned argc, JS::Value *vp) { + return array_copy_func(cx, argc, vp, "flatMap"); +} - if (jRet.isNumber()) { - args.rval().setNumber(jRet.toNumber()); - } - else if (jRet.isString()) { - args.rval().setString(jRet.toString()); - } - else if (jRet.isObject()) { - args.rval().setObject(jRet.toObject()); - } +static bool array_join(JSContext *cx, unsigned argc, JS::Value *vp) { + return array_copy_func(cx, argc, vp, "join", false); +} - return true; +static bool array_toString(JSContext *cx, unsigned argc, JS::Value *vp) { + return array_copy_func(cx, argc, vp, "toString", false); +} + +static bool array_toLocaleString(JSContext *cx, unsigned argc, JS::Value *vp) { + return array_copy_func(cx, argc, vp, "toLocaleString", false); } static JSMethodDef array_methods[] = { {"reverse", array_reverse, 0}, {"pop", array_pop, 0}, - {"at", array_at, 1}, - {"join", array_join, 1}, - {"toString", array_toString, 0}, {"push", array_push, 1}, {"shift", array_shift, 0}, {"unshift", array_unshift, 1}, @@ -972,6 +854,15 @@ static JSMethodDef array_methods[] = { {"filter", array_filter, 1}, {"reduce", array_reduce, 1}, {"reduceRight", array_reduceRight, 1}, + {"some", array_some, 1}, + {"every", array_every, 1}, + {"find", array_find, 1}, + {"findIndex", array_findIndex, 1}, + {"flat", array_flat, 1}, + {"flatMap", array_flatMap, 1}, + {"join", array_join, 1}, + {"toString", array_toString, 0}, + {"toLocaleString", array_toLocaleString, 0}, {NULL, NULL} }; diff --git a/tests/python/test_arrays.py b/tests/python/test_arrays.py index 16f471bf..8ce0955a 100644 --- a/tests/python/test_arrays.py +++ b/tests/python/test_arrays.py @@ -75,40 +75,6 @@ def test_pop_ignore_extra_args(): assert items == [1,2] assert result[0] == 3 -#at -def test_at_no_arg(): - items = [1,2,3] - try: - pm.eval("(arr) => {arr.at()}")(items) - assert (False) - except Exception as e: - assert str(type(e)) == "" - assert str(e).__contains__('TypeError: at: At least 1 argument required, but only 0 passed') - -def test_at(): - items = [1,2,3] - result = [None] - pm.eval("(result, arr) => {result[0] = arr.at(0)}")(result, items) - assert result[0] == 1 - -def test_at_wrong_index_type(): - items = [1,2,3] - result = [None] - pm.eval("(result, arr) => {result[0] = arr.at('f')}")(result, items) - assert result[0] == 1 - -def test_at_index_too_large(): - items = [1,2,3] - result = [None] - pm.eval("(result, arr) => {result[0] = arr.at(10)}")(result, items) - assert result[0] == None - -def test_at_index_negative(): - items = [1,2,3] - result = [None] - pm.eval("(result, arr) => {result[0] = arr.at(-2)}")(result, items) - assert result[0] == 2 - #join def test_join_no_arg(): items = [1,2,3] @@ -141,6 +107,13 @@ def test_toString(): pm.eval("(result, arr) => {result[0] = arr.toString()}")(result, items) assert result[0] == '1,2,3' +#toString +def test_LocaleString(): + items = [1,2,3] + result = [None] + pm.eval("(result, arr) => {result[0] = arr.toString()}")(result, items) + assert result[0] == '1,2,3' + #push def test_push(): items = [1,2,3] @@ -684,9 +657,11 @@ def test_sort_with_js_func_wrong_data_type(): def test_forEach(): items = ['Four', 'Three', 'One'] result = [''] - pm.eval("(result, arr) => {arr.forEach((element) => result[0] += element)}")(result, items) + returnResult = [0] + pm.eval("(returnResult, result, arr) => {returnResult[0] = arr.forEach((element) => result[0] += element)}")(returnResult, result, items) assert items == ['Four', 'Three', 'One'] assert result == ['FourThreeOne'] + assert returnResult == [None] def test_forEach_check_index(): items = ['Four', 'Three', 'One'] @@ -761,7 +736,7 @@ def test_filter_too_few_args(): assert str(type(e)) == "" assert str(e).__contains__("TypeError: filter: At least 1 argument required, but only 0 passed") -#reduce index, array param, too few args same as previous few +#reduce index, array param, too few args same impl as previous few for all below def test_reduce(): items = [1,2,3,4,5] result = [None] @@ -824,4 +799,167 @@ def test_reduceRight_float(): items = [1.9, 4.6, 9.3, 16.5] result = [None] pm.eval("(result, arr) => {result[0] = arr.reduceRight((accumulator, currentValue, index, array) => accumulator + currentValue)}")(result, items) - assert result[0] == 32.3 \ No newline at end of file + assert result[0] == 32.3 + +#some +def test_some_true(): + items = [1, 2, 3, 4, 5] + result = [None] + pm.eval("(result, arr) => {result[0] = arr.some((element) => element % 2 === 0)}")(result, items) + assert items == [1, 2, 3, 4, 5] + assert result[0] == True + +def test_some_false(): + items = [1,3,5] + result = [None] + pm.eval("(result, arr) => {result[0] = arr.some((element) => element % 2 === 0)}")(result, items) + assert result[0] == False + +def test_some_truthy_conversion(): + result = [None] + pm.eval('(result) => {const TRUTHY_VALUES = [true, "true", 1]; function getBoolean(value) { if (typeof value === "string") { value = value.toLowerCase().trim(); } return TRUTHY_VALUES.some((t) => t === value);} result[0] = getBoolean(1);}')(result) + assert result[0] == True + +#every +def test_every_true(): + items = [2,4,6] + result = [None] + pm.eval("(result, arr) => {result[0] = arr.every((element) => element % 2 === 0)}")(result, items) + assert items == [2,4,6] + assert result[0] == True + +def test_every_false(): + items = [1,2,4,6] + result = [None] + pm.eval("(result, arr) => {result[0] = arr.every((element) => element % 2 === 0)}")(result, items) + assert result[0] == False + +#find +def test_find_found_once(): + items = [5, 12, 8, 130, 44] + result = [0] + pm.eval("(result, arr) => {result[0] = arr.find((element) => element > 100)}")(result, items) + assert items == [5, 12, 8, 130, 44] + assert result[0] == 130 + +def test_find_found_twice(): + items = [5, 12, 8, 130, 4] + result = [0] + pm.eval("(result, arr) => {result[0] = arr.find((element) => element > 10)}")(result, items) + assert result[0] == 12 + +def test_find_not_found(): + items = [5, 12, 8, 130, 44] + result = [0] + pm.eval("(result, arr) => {result[0] = arr.find((element) => element > 1000)}")(result, items) + assert result[0] == None + +#findIndex +def test_findIndex_found_once(): + items = [5, 12, 8, 130, 44] + result = [0] + pm.eval("(result, arr) => {result[0] = arr.findIndex((element) => element > 100)}")(result, items) + assert items == [5, 12, 8, 130, 44] + assert result[0] == 3 + +def test_findIndex_found_twice(): + items = [5, 12, 8, 130, 4] + result = [0] + pm.eval("(result, arr) => {result[0] = arr.findIndex((element) => element > 10)}")(result, items) + assert result[0] == 1 + +def test_findIndex_not_found(): + items = [5, 12, 8, 130, 4] + result = [0] + pm.eval("(result, arr) => {result[0] = arr.findIndex((element) => element > 1000)}")(result, items) + assert result[0] == -1 + +#flat +def test_flat(): + items = [0, 1, 2, [3, 4]] + result = [0] + pm.eval("(result, arr) => {result[0] = arr.flat()}")(result, items) + assert items == [0, 1, 2, [3, 4]] + assert result[0] == [0, 1, 2, 3, 4] + +def test_flat_depth_zero(): + items = [0, 1, [2, [3, [4, 5]]]] + result = [0] + pm.eval("(result, arr) => {result[0] = arr.flat(0)}")(result, items) + assert result[0] == [0, 1, [2, [3, [4, 5]]]] + +def test_flat_depth_one(): + items = [0, 1, [2, [3, [4, 5]]]] + result = [0] + pm.eval("(result, arr) => {result[0] = arr.flat(1)}")(result, items) + assert items == [0, 1, [2, [3, [4, 5]]]] + assert result[0] == [0, 1, 2, [3, [4, 5]]] + +########## TODO these two fail as tests but work in the interactive console ########### +#def test_flat_depth_two(): +# items = [0, 1, [2, [3, [4, 5]]]] +# result = [0] +# pm.eval("(result, arr) => {result[0] = arr.flat(2)}")(result, items) +# assert items == [0, 1, [2, [3, [4, 5]]]] +# assert result[0] == [0, 1, 2, 3, [4, 5]] + +#def test_flat_depth_infinite(): +# items = [0, 1, [2, [3, [4, 5]]]] +# result = [0] +# pm.eval("(result, arr) => {result[0] = arr.flat(Infinity)}")(result, items) +# assert result[0] == [0, 1, 2, 3, 4, 5] + +#flatMap +def test_flatMap(): + items = [1, 2, 1] + result = [0] + pm.eval("(result, arr) => {result[0] = arr.flatMap((num) => (num === 2 ? [2, 2] : 1))}")(result, items) + assert items == [1,2,1] + assert result[0] == [1,2,2,1] + +def test_flatMap_equivalence(): + items = [1, 2, 1] + result = [0] + result2 = [0] + pm.eval("(result, arr) => {result[0] = arr.flatMap((num) => (num === 2 ? [2, 2] : 1))}")(result, items) + pm.eval("(result, arr) => {result[0] = arr.map((num) => (num === 2 ? [2, 2] : 1)).flat()}")(result2, items) + assert result[0] == result2[0] + +# toLocaleString +def test_toLocaleString(): + result = [None] + pm.eval("(result) => {array1 = [1, 'a', new Date('21 Dec 1997 14:12:00 UTC')]; result[0] = array1.toLocaleString('en', { timeZone: 'UTC' })}")(result) + assert result[0] == '1,a,12/21/1997, 2:12:00 PM' + +def test_toLocaleString_no_args(): + result = [None] + pm.eval("(result) => {array1 = [1, 'a', new Date('21 Dec 1997 14:12:00 UTC')]; result[0] = array1.toLocaleString()}")(result) + assert result[0] == '1,a,1997-12-21, 9:12:00 a.m.' + +def test_toLocaleString_one_arg_en(): + result = [None] + pm.eval("(result) => {array1 = [1, 'a', new Date('21 Dec 1997 14:12:00 UTC')]; result[0] = array1.toLocaleString('en')}")(result) + assert result[0] == '1,a,12/21/1997, 9:12:00 AM' + +def test_toLocaleString_one_arg_fr(): + result = [None] + pm.eval("(result) => {array1 = [1, 'a', new Date('21 Dec 1997 14:12:00 UTC')]; result[0] = array1.toLocaleString('fr')}")(result) + assert result[0] == '1,a,21/12/1997 09:12:00' + +def test_toLocaleString_one_arg_invalid_locale(): + result = [None] + try: + pm.eval("(result) => {array1 = [1, 'a', new Date('21 Dec 1997 14:12:00 UTC')]; result[0] = array1.toLocaleString('endzfasdf')}")(result) + assert (False) + except Exception as e: + assert str(type(e)) == "" + assert str(e).__contains__("RangeError: invalid language tag:") + +def test_toLocaleString_two_args_invalid_timeZone(): + result = [None] + try: + pm.eval("(result) => {array1 = [1, 'a', new Date('21 Dec 1997 14:12:00 UTC')]; result[0] = array1.toLocaleString('en', { timeZone: 'UT' })}")(result) + assert (False) + except Exception as e: + assert str(type(e)) == "" + assert str(e).__contains__("RangeError: invalid time zone in DateTimeFormat(): UT") \ No newline at end of file From 3760ac58377658f4da08212b549231735abeb02e Mon Sep 17 00:00:00 2001 From: Philippe Laporte Date: Thu, 21 Dec 2023 01:32:34 -0500 Subject: [PATCH 04/58] fix leak introduced in philippe/176 --- src/JSObjectProxy.cc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/JSObjectProxy.cc b/src/JSObjectProxy.cc index 16d4338d..248e6576 100644 --- a/src/JSObjectProxy.cc +++ b/src/JSObjectProxy.cc @@ -331,8 +331,8 @@ PyObject *JSObjectProxyMethodDefinitions::JSObjectProxy_ior(JSObjectProxy *self, JS_GetProperty(GLOBAL_CX, *global, "Object", &Object); JS::RootedObject rootedObject(GLOBAL_CX, Object.toObjectOrNull()); - JS::RootedValue *ret = new JS::RootedValue(GLOBAL_CX); - if (!JS_CallFunctionName(GLOBAL_CX, rootedObject, "assign", args, ret)) { + JS::RootedValue ret(GLOBAL_CX); + if (!JS_CallFunctionName(GLOBAL_CX, rootedObject, "assign", args, &ret)) { PyErr_Format(PyExc_SystemError, "%s JSAPI call failed", JSObjectProxyType.tp_name); return NULL; } From ae060ae9eec0da33ea1c5b76e4979e058f6e9861 Mon Sep 17 00:00:00 2001 From: Philippe Laporte Date: Thu, 21 Dec 2023 01:38:18 -0500 Subject: [PATCH 05/58] better naming --- include/JSArrayProxy.hh | 2 +- src/JSArrayProxy.cc | 146 ++++++++++++++++++++-------------------- src/ListType.cc | 2 +- src/jsTypeFactory.cc | 2 +- 4 files changed, 76 insertions(+), 76 deletions(-) diff --git a/include/JSArrayProxy.hh b/include/JSArrayProxy.hh index d358ccc5..0e04b760 100644 --- a/include/JSArrayProxy.hh +++ b/include/JSArrayProxy.hh @@ -24,7 +24,7 @@ */ typedef struct { PyListObject list; - JS::RootedObject jsObject; + JS::RootedObject jsArray; } JSArrayProxy; /** diff --git a/src/JSArrayProxy.cc b/src/JSArrayProxy.cc index 17cbac02..f127c165 100644 --- a/src/JSArrayProxy.cc +++ b/src/JSArrayProxy.cc @@ -25,7 +25,7 @@ void JSArrayProxyMethodDefinitions::JSArrayProxy_dealloc(JSArrayProxy *self) { - self->jsObject.set(nullptr); + self->jsArray.set(nullptr); Py_TYPE(self)->tp_free((PyObject *)self); } @@ -43,7 +43,7 @@ int JSArrayProxyMethodDefinitions::JSArrayProxy_init(JSArrayProxy *self, PyObjec Py_ssize_t JSArrayProxyMethodDefinitions::JSArrayProxy_length(JSArrayProxy *self) { uint32_t length; - JS::GetArrayLength(GLOBAL_CX, self->jsObject, &length); + JS::GetArrayLength(GLOBAL_CX, self->jsArray, &length); return (Py_ssize_t)length; } @@ -56,9 +56,9 @@ PyObject *JSArrayProxyMethodDefinitions::JSArrayProxy_get(JSArrayProxy *self, Py } JS::RootedValue *value = new JS::RootedValue(GLOBAL_CX); - JS_GetPropertyById(GLOBAL_CX, self->jsObject, id, value); + JS_GetPropertyById(GLOBAL_CX, self->jsArray, id, value); - JS::RootedObject *global = new JS::RootedObject(GLOBAL_CX, JS::GetNonCCWObjectGlobal(self->jsObject)); + JS::RootedObject *global = new JS::RootedObject(GLOBAL_CX, JS::GetNonCCWObjectGlobal(self->jsArray)); // look through the methods for dispatch and return key if no method found for (size_t index = 0;; index++) { const char *methodName = JSArrayProxyType.tp_methods[index].ml_name; @@ -83,11 +83,11 @@ static PyObject *list_slice(JSArrayProxy *self, Py_ssize_t ilow, Py_ssize_t ihig jArgs[0].setInt32(ilow); jArgs[1].setInt32(ihigh); JS::RootedValue *jReturnedArray = new JS::RootedValue(GLOBAL_CX); - if (!JS_CallFunctionName(GLOBAL_CX, self->jsObject, "slice", jArgs, jReturnedArray)) { + if (!JS_CallFunctionName(GLOBAL_CX, self->jsArray, "slice", jArgs, jReturnedArray)) { PyErr_Format(PyExc_SystemError, "%s JSAPI call failed", JSArrayProxyType.tp_name); return NULL; } - return pyTypeFactory(GLOBAL_CX, new JS::RootedObject(GLOBAL_CX, JS::GetNonCCWObjectGlobal(self->jsObject)), jReturnedArray)->getPyObject(); + return pyTypeFactory(GLOBAL_CX, new JS::RootedObject(GLOBAL_CX, JS::GetNonCCWObjectGlobal(self->jsArray)), jReturnedArray)->getPyObject(); } PyObject *JSArrayProxyMethodDefinitions::JSArrayProxy_get_subscript(JSArrayProxy *self, PyObject *key) @@ -113,9 +113,9 @@ PyObject *JSArrayProxyMethodDefinitions::JSArrayProxy_get_subscript(JSArrayProxy JS_IndexToId(GLOBAL_CX, index, &id); JS::RootedValue *value = new JS::RootedValue(GLOBAL_CX); - JS_GetPropertyById(GLOBAL_CX, self->jsObject, id, value); + JS_GetPropertyById(GLOBAL_CX, self->jsArray, id, value); - return pyTypeFactory(GLOBAL_CX, new JS::RootedObject(GLOBAL_CX, JS::GetNonCCWObjectGlobal(self->jsObject)), value)->getPyObject(); + return pyTypeFactory(GLOBAL_CX, new JS::RootedObject(GLOBAL_CX, JS::GetNonCCWObjectGlobal(self->jsArray)), value)->getPyObject(); } else if (PySlice_Check(key)) { Py_ssize_t start, stop, step, slicelength, index; @@ -137,14 +137,14 @@ PyObject *JSArrayProxyMethodDefinitions::JSArrayProxy_get_subscript(JSArrayProxy JS::RootedValue elementVal(GLOBAL_CX); for (size_t cur = start, index = 0; index < slicelength; cur += (size_t)step, index++) { - JS_GetElement(GLOBAL_CX, self->jsObject, cur, &elementVal); + JS_GetElement(GLOBAL_CX, self->jsArray, cur, &elementVal); JS_SetElement(GLOBAL_CX, jCombinedArray, index, elementVal); } JS::RootedValue *jCombinedArrayValue = new JS::RootedValue(GLOBAL_CX); jCombinedArrayValue->setObjectOrNull(jCombinedArray); - return pyTypeFactory(GLOBAL_CX, new JS::RootedObject(GLOBAL_CX, JS::GetNonCCWObjectGlobal(self->jsObject)), jCombinedArrayValue)->getPyObject(); + return pyTypeFactory(GLOBAL_CX, new JS::RootedObject(GLOBAL_CX, JS::GetNonCCWObjectGlobal(self->jsArray)), jCombinedArrayValue)->getPyObject(); } } else { @@ -226,30 +226,30 @@ static int list_ass_slice(JSArrayProxy *self, Py_ssize_t ilow, Py_ssize_t ihigh, if (d < 0) { /* Delete -d items */ JS::RootedValue elementVal(GLOBAL_CX); for (size_t index = ihigh, count = 0; count < selfLength - ihigh; index++, count++) { - JS_GetElement(GLOBAL_CX, self->jsObject, index, &elementVal); - JS_SetElement(GLOBAL_CX, self->jsObject, index+d, elementVal); + JS_GetElement(GLOBAL_CX, self->jsArray, index, &elementVal); + JS_SetElement(GLOBAL_CX, self->jsArray, index+d, elementVal); } - JS::SetArrayLength(GLOBAL_CX, self->jsObject, selfLength + d); + JS::SetArrayLength(GLOBAL_CX, self->jsArray, selfLength + d); } else if (d > 0) { /* Insert d items */ k = selfLength; - JS::SetArrayLength(GLOBAL_CX, self->jsObject, k + d); + JS::SetArrayLength(GLOBAL_CX, self->jsArray, k + d); selfLength = k + d; JS::RootedValue elementVal(GLOBAL_CX); for (size_t index = ihigh, count = 0; count < k - ihigh; index++, count++) { - JS_GetElement(GLOBAL_CX, self->jsObject, index, &elementVal); - JS_SetElement(GLOBAL_CX, self->jsObject, index+d, elementVal); + JS_GetElement(GLOBAL_CX, self->jsArray, index, &elementVal); + JS_SetElement(GLOBAL_CX, self->jsArray, index+d, elementVal); } } JS::RootedValue elementVal(GLOBAL_CX); for (k = 0; k < n; k++, ilow++) { elementVal.set(jsTypeFactory(GLOBAL_CX, vitem[k])); - JS_SetElement(GLOBAL_CX, self->jsObject, ilow, elementVal); + JS_SetElement(GLOBAL_CX, self->jsArray, ilow, elementVal); } result = 0; @@ -282,10 +282,10 @@ int JSArrayProxyMethodDefinitions::JSArrayProxy_assign_key(JSArrayProxy *self, P if (value) { // we are setting a value JS::RootedValue jValue(GLOBAL_CX, jsTypeFactory(GLOBAL_CX, value)); - JS_SetPropertyById(GLOBAL_CX, self->jsObject, id, jValue); + JS_SetPropertyById(GLOBAL_CX, self->jsArray, id, jValue); } else { // we are deleting a value JS::ObjectOpResult ignoredResult; - JS_DeletePropertyById(GLOBAL_CX, self->jsObject, id, ignoredResult); + JS_DeletePropertyById(GLOBAL_CX, self->jsArray, id, ignoredResult); } return 0; @@ -341,8 +341,8 @@ int JSArrayProxyMethodDefinitions::JSArrayProxy_assign_key(JSArrayProxy *self, P } for (size_t index = cur, count = 0; count < lim; index++, count++) { - JS_GetElement(GLOBAL_CX, self->jsObject, index + 1, &elementVal); - JS_SetElement(GLOBAL_CX, self->jsObject, index - i, elementVal); + JS_GetElement(GLOBAL_CX, self->jsArray, index + 1, &elementVal); + JS_SetElement(GLOBAL_CX, self->jsArray, index - i, elementVal); } } @@ -350,12 +350,12 @@ int JSArrayProxyMethodDefinitions::JSArrayProxy_assign_key(JSArrayProxy *self, P if (cur < (size_t)selfSize) { for (size_t index = cur, count = 0; count < selfSize - cur; index++, count++) { - JS_GetElement(GLOBAL_CX, self->jsObject, index, &elementVal); - JS_SetElement(GLOBAL_CX, self->jsObject, index - slicelength, elementVal); + JS_GetElement(GLOBAL_CX, self->jsArray, index, &elementVal); + JS_SetElement(GLOBAL_CX, self->jsArray, index - slicelength, elementVal); } } - JS::SetArrayLength(GLOBAL_CX, self->jsObject, selfSize - slicelength); + JS::SetArrayLength(GLOBAL_CX, self->jsArray, selfSize - slicelength); return 0; } @@ -395,7 +395,7 @@ int JSArrayProxyMethodDefinitions::JSArrayProxy_assign_key(JSArrayProxy *self, P JS::RootedValue elementVal(GLOBAL_CX); for (cur = start, i = 0; i < slicelength; cur += (size_t)step, i++) { elementVal.set(jsTypeFactory(GLOBAL_CX, seqitems[i])); - JS_SetElement(GLOBAL_CX, self->jsObject, cur, elementVal); + JS_SetElement(GLOBAL_CX, self->jsArray, cur, elementVal); } Py_DECREF(seq); @@ -444,18 +444,18 @@ PyObject *JSArrayProxyMethodDefinitions::JSArrayProxy_richcompare(JSArrayProxy * } JS::RootedValue *elementVal; - JS::RootedObject *global = new JS::RootedObject(GLOBAL_CX, JS::GetNonCCWObjectGlobal(self->jsObject)); + JS::RootedObject *global = new JS::RootedObject(GLOBAL_CX, JS::GetNonCCWObjectGlobal(self->jsArray)); Py_ssize_t index; /* Search for the first index where items are different */ for (index = 0; index < selfLength && index < otherLength; index++) { elementVal = new JS::RootedValue(GLOBAL_CX); - JS_GetElement(GLOBAL_CX, self->jsObject, index, elementVal); + JS_GetElement(GLOBAL_CX, self->jsArray, index, elementVal); PyObject *leftItem = pyTypeFactory(GLOBAL_CX, global, elementVal)->getPyObject(); PyObject *rightItem; if (PyObject_TypeCheck(other, &JSArrayProxyType)) { - JS_GetElement(GLOBAL_CX, ((JSArrayProxy *)other)->jsObject, index, elementVal); + JS_GetElement(GLOBAL_CX, ((JSArrayProxy *)other)->jsArray, index, elementVal); rightItem = pyTypeFactory(GLOBAL_CX, global, elementVal)->getPyObject(); } else { rightItem = ((PyListObject *)other)->ob_item[index]; @@ -492,7 +492,7 @@ PyObject *JSArrayProxyMethodDefinitions::JSArrayProxy_richcompare(JSArrayProxy * } elementVal = new JS::RootedValue(GLOBAL_CX); - JS_GetElement(GLOBAL_CX, self->jsObject, index, elementVal); + JS_GetElement(GLOBAL_CX, self->jsArray, index, elementVal); /* Compare the final item again using the proper operator */ return PyObject_RichCompare(pyTypeFactory(GLOBAL_CX, global, elementVal)->getPyObject(), ((PyListObject *)other)->ob_item[index], op); } @@ -516,7 +516,7 @@ PyObject *JSArrayProxyMethodDefinitions::JSArrayProxy_repr(JSArrayProxy *self) { /* "[" + "1" + ", 2" * (len - 1) + "]" */ writer.min_length = 1 + 1 + (2 + 1) * (selfLength - 1) + 1; - JS::RootedObject *global = new JS::RootedObject(GLOBAL_CX, JS::GetNonCCWObjectGlobal(self->jsObject)); + JS::RootedObject *global = new JS::RootedObject(GLOBAL_CX, JS::GetNonCCWObjectGlobal(self->jsArray)); if (_PyUnicodeWriter_WriteChar(&writer, '[') < 0) { goto error; @@ -531,10 +531,10 @@ PyObject *JSArrayProxyMethodDefinitions::JSArrayProxy_repr(JSArrayProxy *self) { } JS::RootedValue *elementVal = new JS::RootedValue(GLOBAL_CX); - JS_GetElement(GLOBAL_CX, self->jsObject, index, elementVal); + JS_GetElement(GLOBAL_CX, self->jsArray, index, elementVal); PyObject *s; - if (&elementVal->toObject() == self->jsObject.get()) { + if (&elementVal->toObject() == self->jsArray.get()) { s = PyObject_Repr((PyObject *)self); } else { s = PyObject_Repr(pyTypeFactory(GLOBAL_CX, global, elementVal)->getPyObject()); @@ -569,10 +569,10 @@ PyObject *JSArrayProxyMethodDefinitions::JSArrayProxy_iter(JSArrayProxy *self) { PyObject *seq = PyList_New(selfLength); - JS::RootedObject *global = new JS::RootedObject(GLOBAL_CX, JS::GetNonCCWObjectGlobal(self->jsObject)); + JS::RootedObject *global = new JS::RootedObject(GLOBAL_CX, JS::GetNonCCWObjectGlobal(self->jsArray)); for (size_t index = 0; index < selfLength; index++) { JS::RootedValue *elementVal = new JS::RootedValue(GLOBAL_CX); - JS_GetElement(GLOBAL_CX, self->jsObject, index, elementVal); + JS_GetElement(GLOBAL_CX, self->jsArray, index, elementVal); PyList_SET_ITEM(seq, index, pyTypeFactory(GLOBAL_CX, global, elementVal)->getPyObject()); } @@ -612,13 +612,13 @@ PyObject *JSArrayProxyMethodDefinitions::JSArrayProxy_concat(JSArrayProxy *self, JS::RootedValue elementVal(GLOBAL_CX); for (Py_ssize_t inputIdx = 0; inputIdx < sizeSelf; inputIdx++) { - JS_GetElement(GLOBAL_CX, self->jsObject, inputIdx, &elementVal); + JS_GetElement(GLOBAL_CX, self->jsArray, inputIdx, &elementVal); JS_SetElement(GLOBAL_CX, jCombinedArray, inputIdx, elementVal); } if (PyObject_TypeCheck(value, &JSArrayProxyType)) { for (Py_ssize_t inputIdx = 0; inputIdx < sizeValue; inputIdx++) { - JS_GetElement(GLOBAL_CX, ((JSArrayProxy *)value)->jsObject, inputIdx, &elementVal); + JS_GetElement(GLOBAL_CX, ((JSArrayProxy *)value)->jsArray, inputIdx, &elementVal); JS_SetElement(GLOBAL_CX, jCombinedArray, sizeSelf + inputIdx, elementVal); } } else { @@ -632,7 +632,7 @@ PyObject *JSArrayProxyMethodDefinitions::JSArrayProxy_concat(JSArrayProxy *self, JS::RootedValue *jCombinedArrayValue = new JS::RootedValue(GLOBAL_CX); jCombinedArrayValue->setObjectOrNull(jCombinedArray); - return pyTypeFactory(GLOBAL_CX, new JS::RootedObject(GLOBAL_CX, JS::GetNonCCWObjectGlobal(self->jsObject)), jCombinedArrayValue)->getPyObject(); + return pyTypeFactory(GLOBAL_CX, new JS::RootedObject(GLOBAL_CX, JS::GetNonCCWObjectGlobal(self->jsArray)), jCombinedArrayValue)->getPyObject(); } PyObject *JSArrayProxyMethodDefinitions::JSArrayProxy_repeat(JSArrayProxy *self, Py_ssize_t n) { @@ -650,7 +650,7 @@ PyObject *JSArrayProxyMethodDefinitions::JSArrayProxy_repeat(JSArrayProxy *self, // one might think of using copyWithin but in SpiderMonkey it's implemented in JS! JS::RootedValue elementVal(GLOBAL_CX); for (Py_ssize_t inputIdx = 0; inputIdx < input_size; inputIdx++) { - JS_GetElement(GLOBAL_CX, self->jsObject, inputIdx, &elementVal); + JS_GetElement(GLOBAL_CX, self->jsArray, inputIdx, &elementVal); for (Py_ssize_t repeatIdx = 0; repeatIdx < n; repeatIdx++) { JS_SetElement(GLOBAL_CX, jCombinedArray, repeatIdx * input_size + inputIdx, elementVal); } @@ -659,7 +659,7 @@ PyObject *JSArrayProxyMethodDefinitions::JSArrayProxy_repeat(JSArrayProxy *self, JS::RootedValue jCombinedArrayValue(GLOBAL_CX); jCombinedArrayValue.setObjectOrNull(jCombinedArray); - return pyTypeFactory(GLOBAL_CX, new JS::RootedObject(GLOBAL_CX, JS::GetNonCCWObjectGlobal(self->jsObject)), &jCombinedArrayValue)->getPyObject(); + return pyTypeFactory(GLOBAL_CX, new JS::RootedObject(GLOBAL_CX, JS::GetNonCCWObjectGlobal(self->jsArray)), &jCombinedArrayValue)->getPyObject(); } int JSArrayProxyMethodDefinitions::JSArrayProxy_contains(JSArrayProxy *self, PyObject *element) { @@ -667,10 +667,10 @@ int JSArrayProxyMethodDefinitions::JSArrayProxy_contains(JSArrayProxy *self, PyO int cmp; Py_ssize_t numElements = JSArrayProxy_length(self); - JS::RootedObject *global = new JS::RootedObject(GLOBAL_CX, JS::GetNonCCWObjectGlobal(self->jsObject)); + JS::RootedObject *global = new JS::RootedObject(GLOBAL_CX, JS::GetNonCCWObjectGlobal(self->jsArray)); for (index = 0, cmp = 0; cmp == 0 && index < numElements; ++index) { JS::RootedValue *elementVal = new JS::RootedValue(GLOBAL_CX); - JS_GetElement(GLOBAL_CX, self->jsObject, index, elementVal); + JS_GetElement(GLOBAL_CX, self->jsArray, index, elementVal); PyObject *item = pyTypeFactory(GLOBAL_CX, global, elementVal)->getPyObject(); Py_INCREF(item); cmp = PyObject_RichCompareBool(item, element, Py_EQ); @@ -684,7 +684,7 @@ PyObject *JSArrayProxyMethodDefinitions::JSArrayProxy_inplace_concat(JSArrayProx Py_ssize_t valueLength = Py_SIZE(value); // allocate extra spacePy_SIZE - JS::SetArrayLength(GLOBAL_CX, self->jsObject, selfLength + valueLength); + JS::SetArrayLength(GLOBAL_CX, self->jsArray, selfLength + valueLength); JS::RootedValue jValue(GLOBAL_CX, jsTypeFactory(GLOBAL_CX, value)); JS::RootedObject jRootedValue = JS::RootedObject(GLOBAL_CX, jValue.toObjectOrNull()); @@ -692,7 +692,7 @@ PyObject *JSArrayProxyMethodDefinitions::JSArrayProxy_inplace_concat(JSArrayProx JS::RootedValue elementVal(GLOBAL_CX); for (Py_ssize_t inputIdx = 0; inputIdx < valueLength; inputIdx++) { JS_GetElement(GLOBAL_CX, jRootedValue, inputIdx, &elementVal); - JS_SetElement(GLOBAL_CX, self->jsObject, selfLength + inputIdx, elementVal); + JS_SetElement(GLOBAL_CX, self->jsArray, selfLength + inputIdx, elementVal); } Py_INCREF(self); @@ -716,15 +716,15 @@ PyObject *JSArrayProxyMethodDefinitions::JSArrayProxy_inplace_repeat(JSArrayProx return PyErr_NoMemory(); } - JS::SetArrayLength(GLOBAL_CX, self->jsObject, input_size * n); + JS::SetArrayLength(GLOBAL_CX, self->jsArray, input_size * n); // repeat within self // one might think of using copyWithin but in SpiderMonkey it's implemented in JS! JS::RootedValue elementVal(GLOBAL_CX); for (Py_ssize_t inputIdx = 0; inputIdx < input_size; inputIdx++) { - JS_GetElement(GLOBAL_CX, self->jsObject, inputIdx, &elementVal); + JS_GetElement(GLOBAL_CX, self->jsArray, inputIdx, &elementVal); for (Py_ssize_t repeatIdx = 0; repeatIdx < n; repeatIdx++) { - JS_SetElement(GLOBAL_CX, self->jsObject, repeatIdx * input_size + inputIdx, elementVal); + JS_SetElement(GLOBAL_CX, self->jsArray, repeatIdx * input_size + inputIdx, elementVal); } } @@ -733,7 +733,7 @@ PyObject *JSArrayProxyMethodDefinitions::JSArrayProxy_inplace_repeat(JSArrayProx } PyObject *JSArrayProxyMethodDefinitions::JSArrayProxy_clear(JSArrayProxy *self) { - JS::SetArrayLength(GLOBAL_CX, self->jsObject, 0); + JS::SetArrayLength(GLOBAL_CX, self->jsArray, 0); Py_RETURN_NONE; } @@ -743,10 +743,10 @@ int JSArrayProxyMethodDefinitions::JSArrayProxy_clear_slot(JSArrayProxy *self) { } int JSArrayProxyMethodDefinitions::JSArrayProxy_traverse(JSArrayProxy *self, visitproc visit, void *arg) { - JS::RootedObject *global = new JS::RootedObject(GLOBAL_CX, JS::GetNonCCWObjectGlobal(self->jsObject)); + JS::RootedObject *global = new JS::RootedObject(GLOBAL_CX, JS::GetNonCCWObjectGlobal(self->jsArray)); for (Py_ssize_t i = JSArrayProxy_length(self); --i >= 0; ) { JS::RootedValue *elementVal = new JS::RootedValue(GLOBAL_CX); - JS_GetElement(GLOBAL_CX, self->jsObject, i, elementVal); + JS_GetElement(GLOBAL_CX, self->jsArray, i, elementVal); Py_VISIT(pyTypeFactory(GLOBAL_CX, global, elementVal)->getPyObject()); } return 0; @@ -757,19 +757,19 @@ PyObject *JSArrayProxyMethodDefinitions::JSArrayProxy_copy(JSArrayProxy *self) { jArgs[0].setInt32(0); jArgs[1].setInt32(JSArrayProxy_length(self)); JS::RootedValue *jReturnedArray = new JS::RootedValue(GLOBAL_CX); - if (!JS_CallFunctionName(GLOBAL_CX, self->jsObject, "slice", jArgs, jReturnedArray)) { + if (!JS_CallFunctionName(GLOBAL_CX, self->jsArray, "slice", jArgs, jReturnedArray)) { PyErr_Format(PyExc_SystemError, "%s JSAPI call failed", JSArrayProxyType.tp_name); return NULL; } - return pyTypeFactory(GLOBAL_CX, new JS::RootedObject(GLOBAL_CX, JS::GetNonCCWObjectGlobal(self->jsObject)), jReturnedArray)->getPyObject(); + return pyTypeFactory(GLOBAL_CX, new JS::RootedObject(GLOBAL_CX, JS::GetNonCCWObjectGlobal(self->jsArray)), jReturnedArray)->getPyObject(); } PyObject *JSArrayProxyMethodDefinitions::JSArrayProxy_append(JSArrayProxy *self, PyObject *value) { Py_ssize_t len = JSArrayProxy_length(self); - JS::SetArrayLength(GLOBAL_CX, self->jsObject, len + 1); + JS::SetArrayLength(GLOBAL_CX, self->jsArray, len + 1); JS::RootedValue jValue(GLOBAL_CX, jsTypeFactory(GLOBAL_CX, value)); - JS_SetElement(GLOBAL_CX, self->jsObject, len, jValue); + JS_SetElement(GLOBAL_CX, self->jsArray, len, jValue); Py_RETURN_NONE; } @@ -816,7 +816,7 @@ PyObject *JSArrayProxyMethodDefinitions::JSArrayProxy_insert(JSArrayProxy *self, jArgs[2].set(jsTypeFactory(GLOBAL_CX, value)); JS::RootedValue jReturnedArray(GLOBAL_CX); - if (!JS_CallFunctionName(GLOBAL_CX, self->jsObject, "splice", jArgs, &jReturnedArray)) { + if (!JS_CallFunctionName(GLOBAL_CX, self->jsArray, "splice", jArgs, &jReturnedArray)) { PyErr_Format(PyExc_SystemError, "%s JSAPI call failed", JSArrayProxyType.tp_name); return NULL; } @@ -839,14 +839,14 @@ PyObject *JSArrayProxyMethodDefinitions::JSArrayProxy_extend(JSArrayProxy *self, Py_ssize_t m = JSArrayProxy_length(self); - JS::SetArrayLength(GLOBAL_CX, self->jsObject, m + n); + JS::SetArrayLength(GLOBAL_CX, self->jsArray, m + n); // populate the end of self with iterable's items. PyObject **src = PySequence_Fast_ITEMS(iterable); for (Py_ssize_t i = 0; i < n; i++) { PyObject *o = src[i]; JS::RootedValue jValue(GLOBAL_CX, jsTypeFactory(GLOBAL_CX, o)); - JS_SetElement(GLOBAL_CX, self->jsObject, m + i, jValue); + JS_SetElement(GLOBAL_CX, self->jsArray, m + i, jValue); } Py_DECREF(iterable); @@ -875,9 +875,9 @@ PyObject *JSArrayProxyMethodDefinitions::JSArrayProxy_extend(JSArrayProxy *self, break; } - JS::SetArrayLength(GLOBAL_CX, self->jsObject, len + 1); + JS::SetArrayLength(GLOBAL_CX, self->jsArray, len + 1); JS::RootedValue jValue(GLOBAL_CX, jsTypeFactory(GLOBAL_CX, item)); - JS_SetElement(GLOBAL_CX, self->jsObject, len, jValue); + JS_SetElement(GLOBAL_CX, self->jsArray, len, jValue); len++; } @@ -928,7 +928,7 @@ PyObject *JSArrayProxyMethodDefinitions::JSArrayProxy_pop(JSArrayProxy *self, Py jArgs[1].setInt32(1); JS::RootedValue jReturnedArray(GLOBAL_CX); - if (!JS_CallFunctionName(GLOBAL_CX, self->jsObject, "splice", jArgs, &jReturnedArray)) { + if (!JS_CallFunctionName(GLOBAL_CX, self->jsArray, "splice", jArgs, &jReturnedArray)) { PyErr_Format(PyExc_SystemError, "%s JSAPI call failed", JSArrayProxyType.tp_name); return NULL; } @@ -938,16 +938,16 @@ PyObject *JSArrayProxyMethodDefinitions::JSArrayProxy_pop(JSArrayProxy *self, Py JS::RootedValue *elementVal = new JS::RootedValue(GLOBAL_CX); JS_GetElement(GLOBAL_CX, rootedReturnedArray, 0, elementVal); - return pyTypeFactory(GLOBAL_CX, new JS::RootedObject(GLOBAL_CX, JS::GetNonCCWObjectGlobal(self->jsObject)), elementVal)->getPyObject(); + return pyTypeFactory(GLOBAL_CX, new JS::RootedObject(GLOBAL_CX, JS::GetNonCCWObjectGlobal(self->jsArray)), elementVal)->getPyObject(); } PyObject *JSArrayProxyMethodDefinitions::JSArrayProxy_remove(JSArrayProxy *self, PyObject *value) { Py_ssize_t selfSize = JSArrayProxy_length(self); - JS::RootedObject *global = new JS::RootedObject(GLOBAL_CX, JS::GetNonCCWObjectGlobal(self->jsObject)); + JS::RootedObject *global = new JS::RootedObject(GLOBAL_CX, JS::GetNonCCWObjectGlobal(self->jsArray)); for (Py_ssize_t index = 0; index < selfSize; index++) { JS::RootedValue *elementVal = new JS::RootedValue(GLOBAL_CX); - JS_GetElement(GLOBAL_CX, self->jsObject, index, elementVal); + JS_GetElement(GLOBAL_CX, self->jsArray, index, elementVal); PyObject *obj = pyTypeFactory(GLOBAL_CX, global, elementVal)->getPyObject(); Py_INCREF(obj); int cmp = PyObject_RichCompareBool(obj, value, Py_EQ); @@ -957,7 +957,7 @@ PyObject *JSArrayProxyMethodDefinitions::JSArrayProxy_remove(JSArrayProxy *self, jArgs[0].setInt32(index); jArgs[1].setInt32(1); JS::RootedValue jReturnedArray(GLOBAL_CX); - if (!JS_CallFunctionName(GLOBAL_CX, self->jsObject, "splice", jArgs, &jReturnedArray)) { + if (!JS_CallFunctionName(GLOBAL_CX, self->jsArray, "splice", jArgs, &jReturnedArray)) { PyErr_Format(PyExc_SystemError, "%s JSAPI call failed", JSArrayProxyType.tp_name); return NULL; } @@ -1010,10 +1010,10 @@ PyObject *JSArrayProxyMethodDefinitions::JSArrayProxy_index(JSArrayProxy *self, } } - JS::RootedObject *global = new JS::RootedObject(GLOBAL_CX, JS::GetNonCCWObjectGlobal(self->jsObject)); + JS::RootedObject *global = new JS::RootedObject(GLOBAL_CX, JS::GetNonCCWObjectGlobal(self->jsArray)); for (Py_ssize_t index = start; index < stop && index < selfSize; index++) { JS::RootedValue *elementVal = new JS::RootedValue(GLOBAL_CX); - JS_GetElement(GLOBAL_CX, self->jsObject, index, elementVal); + JS_GetElement(GLOBAL_CX, self->jsArray, index, elementVal); PyObject *obj = pyTypeFactory(GLOBAL_CX, global, elementVal)->getPyObject(); Py_INCREF(obj); int cmp = PyObject_RichCompareBool(obj, value, Py_EQ); @@ -1034,10 +1034,10 @@ PyObject *JSArrayProxyMethodDefinitions::JSArrayProxy_count(JSArrayProxy *self, Py_ssize_t count = 0; Py_ssize_t length = JSArrayProxy_length(self); - JS::RootedObject *global = new JS::RootedObject(GLOBAL_CX, JS::GetNonCCWObjectGlobal(self->jsObject)); + JS::RootedObject *global = new JS::RootedObject(GLOBAL_CX, JS::GetNonCCWObjectGlobal(self->jsArray)); for (Py_ssize_t index = 0; index < length; index++) { JS::RootedValue *elementVal = new JS::RootedValue(GLOBAL_CX); - JS_GetElement(GLOBAL_CX, self->jsObject, index, elementVal); + JS_GetElement(GLOBAL_CX, self->jsArray, index, elementVal); PyObject *obj = pyTypeFactory(GLOBAL_CX, global, elementVal)->getPyObject(); Py_INCREF(obj); int cmp = PyObject_RichCompareBool(obj, value, Py_EQ); @@ -1055,7 +1055,7 @@ PyObject *JSArrayProxyMethodDefinitions::JSArrayProxy_count(JSArrayProxy *self, PyObject *JSArrayProxyMethodDefinitions::JSArrayProxy_reverse(JSArrayProxy *self) { if (JSArrayProxy_length(self) > 1) { JS::RootedValue jReturnedArray(GLOBAL_CX); - if (!JS_CallFunctionName(GLOBAL_CX, self->jsObject, "reverse", JS::HandleValueArray::empty(), &jReturnedArray)) { + if (!JS_CallFunctionName(GLOBAL_CX, self->jsArray, "reverse", JS::HandleValueArray::empty(), &jReturnedArray)) { PyErr_Format(PyExc_SystemError, "%s JSAPI call failed", JSArrayProxyType.tp_name); return NULL; } @@ -1245,7 +1245,7 @@ PyObject *JSArrayProxyMethodDefinitions::JSArrayProxy_sort(JSArrayProxy *self, P JS::Rooted> jArgs(GLOBAL_CX); jArgs[0].setObject(*funObj); - if (!JS_CallFunctionName(GLOBAL_CX, self->jsObject, "sort", jArgs, &jReturnedArray)) { + if (!JS_CallFunctionName(GLOBAL_CX, self->jsArray, "sort", jArgs, &jReturnedArray)) { if (!PyErr_Occurred()) { PyErr_Format(PyExc_SystemError, "%s JSAPI call failed", JSArrayProxyType.tp_name); } @@ -1256,7 +1256,7 @@ PyObject *JSArrayProxyMethodDefinitions::JSArrayProxy_sort(JSArrayProxy *self, P // two-arg js-style JS::Rooted> jArgs(GLOBAL_CX); jArgs[0].set(jsTypeFactory(GLOBAL_CX, keyfunc)); - if (!JS_CallFunctionName(GLOBAL_CX, self->jsObject, "sort", jArgs, &jReturnedArray)) { + if (!JS_CallFunctionName(GLOBAL_CX, self->jsArray, "sort", jArgs, &jReturnedArray)) { PyErr_Format(PyExc_SystemError, "%s JSAPI call failed", JSArrayProxyType.tp_name); return NULL; } @@ -1274,7 +1274,7 @@ PyObject *JSArrayProxyMethodDefinitions::JSArrayProxy_sort(JSArrayProxy *self, P // we got a JS compare function, use it as-is JS::Rooted> jArgs(GLOBAL_CX); jArgs[0].set(jsTypeFactory(GLOBAL_CX, keyfunc)); - if (!JS_CallFunctionName(GLOBAL_CX, self->jsObject, "sort", jArgs, &jReturnedArray)) { + if (!JS_CallFunctionName(GLOBAL_CX, self->jsArray, "sort", jArgs, &jReturnedArray)) { PyErr_Format(PyExc_SystemError, "%s JSAPI call failed", JSArrayProxyType.tp_name); return NULL; } @@ -1302,7 +1302,7 @@ PyObject *JSArrayProxyMethodDefinitions::JSArrayProxy_sort(JSArrayProxy *self, P JS::Rooted> jArgs(GLOBAL_CX); jArgs[0].setObject(*funObj); - if (!JS_CallFunctionName(GLOBAL_CX, self->jsObject, "sort", jArgs, &jReturnedArray)) { + if (!JS_CallFunctionName(GLOBAL_CX, self->jsArray, "sort", jArgs, &jReturnedArray)) { if (!PyErr_Occurred()) { PyErr_Format(PyExc_SystemError, "%s JSAPI call failed", JSArrayProxyType.tp_name); } @@ -1329,7 +1329,7 @@ PyObject *JSArrayProxyMethodDefinitions::JSArrayProxy_sort(JSArrayProxy *self, P JS::Rooted> jArgs(GLOBAL_CX); jArgs[0].setObject(*funObj); - if (!JS_CallFunctionName(GLOBAL_CX, self->jsObject, "sort", jArgs, &jReturnedArray)) { + if (!JS_CallFunctionName(GLOBAL_CX, self->jsArray, "sort", jArgs, &jReturnedArray)) { PyErr_Format(PyExc_SystemError, "%s JSAPI call failed", JSArrayProxyType.tp_name); return NULL; } diff --git a/src/ListType.cc b/src/ListType.cc index cd619569..96eeec53 100644 --- a/src/ListType.cc +++ b/src/ListType.cc @@ -16,6 +16,6 @@ ListType::ListType(PyObject *object) : PyType(object) {} ListType::ListType(JSContext *cx, JS::HandleObject jsArrayObj) { JSArrayProxy *proxy = (JSArrayProxy *)PyObject_CallObject((PyObject *)&JSArrayProxyType, NULL); - proxy->jsObject.set(jsArrayObj); + proxy->jsArray.set(jsArrayObj); this->pyObject = (PyObject *)proxy; } \ No newline at end of file diff --git a/src/jsTypeFactory.cc b/src/jsTypeFactory.cc index cc8daea5..0c461bbc 100644 --- a/src/jsTypeFactory.cc +++ b/src/jsTypeFactory.cc @@ -165,7 +165,7 @@ JS::Value jsTypeFactory(JSContext *cx, PyObject *object) { returnType.setObject(*((JSObjectProxy *)object)->jsObject); } else if (PyObject_TypeCheck(object, &JSArrayProxyType)) { - returnType.setObject(*((JSArrayProxy *)object)->jsObject); + returnType.setObject(*((JSArrayProxy *)object)->jsArray); } else if (PyDict_Check(object) || PyList_Check(object)) { JS::RootedValue v(cx); From c4c9675c66c6b96b97c6d1d189e9e10c1104369a Mon Sep 17 00:00:00 2001 From: Philippe Laporte Date: Thu, 21 Dec 2023 01:42:48 -0500 Subject: [PATCH 06/58] improved naming --- tests/python/test_lists.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/python/test_lists.py b/tests/python/test_lists.py index 8dcf2b03..01d01b83 100644 --- a/tests/python/test_lists.py +++ b/tests/python/test_lists.py @@ -729,7 +729,7 @@ def myFunc(e): assert str(type(e)) == "" assert str(e) == "object of type 'float' has no len()" -def test_sort_with_function_and_reverse_false(): +def test_sort_with_function_two_arg_and_reverse_false(): def myFunc(e,f): return len(e) - len(f) a = pm.eval("(['Ford', 'Mitsubishi', 'BMW', 'VW'])") From e926e9f99b4c30eb2ac0e9f7ab23f8170912e5b5 Mon Sep 17 00:00:00 2001 From: philippedistributive Date: Thu, 21 Dec 2023 14:51:28 -0500 Subject: [PATCH 07/58] implemented valueOf --- src/JSObjectProxy.cc | 1 + src/PyProxyHandler.cc | 11 ++++++++-- tests/python/test_arrays.py | 44 +++++++++++++++++++++++-------------- 3 files changed, 37 insertions(+), 19 deletions(-) diff --git a/src/JSObjectProxy.cc b/src/JSObjectProxy.cc index 248e6576..1fc4a6b1 100644 --- a/src/JSObjectProxy.cc +++ b/src/JSObjectProxy.cc @@ -44,6 +44,7 @@ void JSObjectProxyMethodDefinitions::JSObjectProxy_dealloc(JSObjectProxy *self) { // TODO (Caleb Aikens): intentional override of PyDict_Type's tp_dealloc. Probably results in leaking dict memory self->jsObject.set(nullptr); + // PyObject_GC_UnTrack(self); TODO what is the history of this line? Py_TYPE(self)->tp_free((PyObject *)self); return; } diff --git a/src/PyProxyHandler.cc b/src/PyProxyHandler.cc index 2a22b32d..912e9100 100644 --- a/src/PyProxyHandler.cc +++ b/src/PyProxyHandler.cc @@ -833,6 +833,10 @@ static bool array_toLocaleString(JSContext *cx, unsigned argc, JS::Value *vp) { return array_copy_func(cx, argc, vp, "toLocaleString", false); } +static bool array_valueOf(JSContext *cx, unsigned argc, JS::Value *vp) { + return array_copy_func(cx, argc, vp, "valueOf", false); +} + static JSMethodDef array_methods[] = { {"reverse", array_reverse, 0}, @@ -863,9 +867,11 @@ static JSMethodDef array_methods[] = { {"join", array_join, 1}, {"toString", array_toString, 0}, {"toLocaleString", array_toLocaleString, 0}, + {"valueOf", array_valueOf, 0}, {NULL, NULL} }; +static bool sawValueOf = false; bool PyListProxyHandler::getOwnPropertyDescriptor( JSContext *cx, JS::HandleObject proxy, JS::HandleId id, @@ -873,6 +879,7 @@ bool PyListProxyHandler::getOwnPropertyDescriptor( ) const { // see if we're calling a function if (id.isString()) { + JS::RootedString message(cx, id.toString()); for (size_t index = 0;; index++) { bool isThatFunction; const char *methodName = array_methods[index].name; @@ -897,7 +904,6 @@ bool PyListProxyHandler::getOwnPropertyDescriptor( // We're trying to get the "length" property bool isLengthProperty; if (id.isString() && JS_StringEqualsLiteral(cx, id.toString(), "length", &isLengthProperty) && isLengthProperty) { - // proxy.length = len(pyObject) desc.set(mozilla::Some( JS::PropertyDescriptor::Data( JS::Int32Value(PyList_Size(pyObject)) @@ -977,6 +983,7 @@ bool PyListProxyHandler::delete_(JSContext *cx, JS::HandleObject proxy, JS::Hand } bool PyListProxyHandler::isArray(JSContext *cx, JS::HandleObject proxy, JS::IsArrayAnswer *answer) const { + *answer = JS::IsArrayAnswer::Array; return true; } @@ -987,5 +994,5 @@ bool PyListProxyHandler::getBuiltinClass(JSContext *cx, JS::Handle o const char *PyListProxyHandler::className(JSContext *cx, JS::HandleObject proxy) const { // TODO untested - return "object"; + return "Array"; } \ No newline at end of file diff --git a/tests/python/test_arrays.py b/tests/python/test_arrays.py index 8ce0955a..3f3a21ed 100644 --- a/tests/python/test_arrays.py +++ b/tests/python/test_arrays.py @@ -925,41 +925,51 @@ def test_flatMap_equivalence(): pm.eval("(result, arr) => {result[0] = arr.map((num) => (num === 2 ? [2, 2] : 1)).flat()}")(result2, items) assert result[0] == result2[0] -# toLocaleString +#valueOf +def test_valueOf(): + items = [1, 2, 1] + result = [0] + pm.eval("(result, arr) => {result[0] = arr.valueOf()}")(result, items) + assert result[0] == [1,2,1] + +#toLocaleString def test_toLocaleString(): + prices = ["ï¿¥7", 500, 8123, 12] result = [None] - pm.eval("(result) => {array1 = [1, 'a', new Date('21 Dec 1997 14:12:00 UTC')]; result[0] = array1.toLocaleString('en', { timeZone: 'UTC' })}")(result) - assert result[0] == '1,a,12/21/1997, 2:12:00 PM' + pm.eval("(result, arr) => {result[0] = arr.toLocaleString('ja-JP', { style: 'currency', currency: 'JPY' })}")(result, prices) + assert result[0] == 'ï¿¥7,ï¿¥500,ï¿¥8,123,ï¿¥12' def test_toLocaleString_no_args(): + prices = ["ï¿¥7", 500, 8123, 12] result = [None] - pm.eval("(result) => {array1 = [1, 'a', new Date('21 Dec 1997 14:12:00 UTC')]; result[0] = array1.toLocaleString()}")(result) - assert result[0] == '1,a,1997-12-21, 9:12:00 a.m.' + pm.eval("(result, arr) => {result[0] = arr.toLocaleString()}")(result, prices) + assert result[0] == 'ï¿¥7,500,8,123,12' -def test_toLocaleString_one_arg_en(): +def test_toLocaleString_one_arg_(): + prices = ["ï¿¥7", 500, 8123, 12] result = [None] - pm.eval("(result) => {array1 = [1, 'a', new Date('21 Dec 1997 14:12:00 UTC')]; result[0] = array1.toLocaleString('en')}")(result) - assert result[0] == '1,a,12/21/1997, 9:12:00 AM' - -def test_toLocaleString_one_arg_fr(): - result = [None] - pm.eval("(result) => {array1 = [1, 'a', new Date('21 Dec 1997 14:12:00 UTC')]; result[0] = array1.toLocaleString('fr')}")(result) - assert result[0] == '1,a,21/12/1997 09:12:00' + pm.eval("(result, arr) => {result[0] = arr.toLocaleString('ja-JP')}")(result, prices) + assert result[0] == 'ï¿¥7,500,8,123,12' def test_toLocaleString_one_arg_invalid_locale(): + prices = ["ï¿¥7", 500, 8123, 12] result = [None] try: - pm.eval("(result) => {array1 = [1, 'a', new Date('21 Dec 1997 14:12:00 UTC')]; result[0] = array1.toLocaleString('endzfasdf')}")(result) + pm.eval("(result, arr) => {result[0] = arr.toLocaleString('asfasfsafsdf')}")(result, prices) assert (False) except Exception as e: assert str(type(e)) == "" assert str(e).__contains__("RangeError: invalid language tag:") -def test_toLocaleString_two_args_invalid_timeZone(): +def test_toLocaleString_two_args_invalid_currency(): + prices = ["ï¿¥7", 500, 8123, 12] result = [None] try: - pm.eval("(result) => {array1 = [1, 'a', new Date('21 Dec 1997 14:12:00 UTC')]; result[0] = array1.toLocaleString('en', { timeZone: 'UT' })}")(result) + pm.eval("(result, arr) => {result[0] = arr.toLocaleString('ja-JP', { style: 'currency', currency: 'JPYsdagasfgas' })}")(result, prices) assert (False) except Exception as e: assert str(type(e)) == "" - assert str(e).__contains__("RangeError: invalid time zone in DateTimeFormat(): UT") \ No newline at end of file + assert str(e).__contains__("RangeError: invalid currency code in NumberFormat():") + +#entries + \ No newline at end of file From aa35a20d9cf0b7f5572a6bf0549eed5d1935b882 Mon Sep 17 00:00:00 2001 From: philippedistributive Date: Fri, 22 Dec 2023 11:14:56 -0500 Subject: [PATCH 08/58] Additional Python List to JS Array conversion functionality --- include/PyProxyHandler.hh | 2 +- src/JSObjectProxy.cc | 15 ++++++-- src/PyProxyHandler.cc | 72 +++++++++++++++++++++++++++++++++---- tests/python/test_arrays.py | 51 +++++++++++++++++++++++++- 4 files changed, 129 insertions(+), 11 deletions(-) diff --git a/include/PyProxyHandler.hh b/include/PyProxyHandler.hh index 7c97dd17..1ee9fcbc 100644 --- a/include/PyProxyHandler.hh +++ b/include/PyProxyHandler.hh @@ -169,7 +169,7 @@ public: /** * @brief This struct is the ProxyHandler for JS Proxy Objects pythonmonkey creates - * to handle coercion from python lists to JS Array-like objects + * to handle coercion from python lists to JS Array objects */ struct PyListProxyHandler : public PyBaseProxyHandler { public: diff --git a/src/JSObjectProxy.cc b/src/JSObjectProxy.cc index 1fc4a6b1..380feab8 100644 --- a/src/JSObjectProxy.cc +++ b/src/JSObjectProxy.cc @@ -302,7 +302,10 @@ PyObject *JSObjectProxyMethodDefinitions::JSObjectProxy_or(JSObjectProxy *self, // call Object.assign JS::RootedValue Object(GLOBAL_CX); - JS_GetProperty(GLOBAL_CX, *global, "Object", &Object); + if (!JS_GetProperty(GLOBAL_CX, *global, "Object", &Object)) { + PyErr_Format(PyExc_SystemError, "%s JSAPI call failed", JSObjectProxyType.tp_name); + return NULL; + } JS::RootedObject rootedObject(GLOBAL_CX, Object.toObjectOrNull()); JS::RootedValue *ret = new JS::RootedValue(GLOBAL_CX); @@ -329,7 +332,10 @@ PyObject *JSObjectProxyMethodDefinitions::JSObjectProxy_ior(JSObjectProxy *self, // call Object.assign JS::RootedValue Object(GLOBAL_CX); - JS_GetProperty(GLOBAL_CX, *global, "Object", &Object); + if (!JS_GetProperty(GLOBAL_CX, *global, "Object", &Object)) { + PyErr_Format(PyExc_SystemError, "%s JSAPI call failed", JSObjectProxyType.tp_name); + return NULL; + } JS::RootedObject rootedObject(GLOBAL_CX, Object.toObjectOrNull()); JS::RootedValue ret(GLOBAL_CX); @@ -457,7 +463,10 @@ PyObject *JSObjectProxyMethodDefinitions::JSObjectProxy_copy_method(JSObjectProx // call Object.assign JS::RootedValue Object(GLOBAL_CX); - JS_GetProperty(GLOBAL_CX, *global, "Object", &Object); + if (!JS_GetProperty(GLOBAL_CX, *global, "Object", &Object)) { + PyErr_Format(PyExc_SystemError, "%s JSAPI call failed", JSObjectProxyType.tp_name); + return NULL; + } JS::RootedObject rootedObject(GLOBAL_CX, Object.toObjectOrNull()); JS::RootedValue *ret = new JS::RootedValue(GLOBAL_CX); diff --git a/src/PyProxyHandler.cc b/src/PyProxyHandler.cc index 912e9100..ca80e805 100644 --- a/src/PyProxyHandler.cc +++ b/src/PyProxyHandler.cc @@ -834,7 +834,31 @@ static bool array_toLocaleString(JSContext *cx, unsigned argc, JS::Value *vp) { } static bool array_valueOf(JSContext *cx, unsigned argc, JS::Value *vp) { - return array_copy_func(cx, argc, vp, "valueOf", false); + JS::CallArgs args = JS::CallArgsFromVp(argc, vp); + + JS::RootedObject proxy(cx, JS::ToObject(cx, args.thisv())); + if (!proxy) { + return false; + } + PyObject *self = JS::GetMaybePtrFromReservedSlot(proxy, PyObjectSlot); + + // return ref to self + args.rval().set(jsTypeFactory(cx, self)); + return true; +} + +static bool array_entries(JSContext *cx, unsigned argc, JS::Value *vp) { + return array_copy_func(cx, argc, vp, "entries", false); +} + +/* see note in tests + static bool array_keys(JSContext *cx, unsigned argc, JS::Value *vp) { + return array_copy_func(cx, argc, vp, "keys", false); + } + */ + +static bool array_values(JSContext *cx, unsigned argc, JS::Value *vp) { + return array_copy_func(cx, argc, vp, "values", false); } @@ -868,10 +892,12 @@ static JSMethodDef array_methods[] = { {"toString", array_toString, 0}, {"toLocaleString", array_toLocaleString, 0}, {"valueOf", array_valueOf, 0}, - {NULL, NULL} + {"entries", array_entries, 0}, + // {"keys", array_keys, 0}, + {"values", array_values, 0}, + {NULL, NULL, 0} }; -static bool sawValueOf = false; bool PyListProxyHandler::getOwnPropertyDescriptor( JSContext *cx, JS::HandleObject proxy, JS::HandleId id, @@ -879,7 +905,6 @@ bool PyListProxyHandler::getOwnPropertyDescriptor( ) const { // see if we're calling a function if (id.isString()) { - JS::RootedString message(cx, id.toString()); for (size_t index = 0;; index++) { bool isThatFunction; const char *methodName = array_methods[index].name; @@ -901,7 +926,7 @@ bool PyListProxyHandler::getOwnPropertyDescriptor( } } - // We're trying to get the "length" property + // "length" property bool isLengthProperty; if (id.isString() && JS_StringEqualsLiteral(cx, id.toString(), "length", &isLengthProperty) && isLengthProperty) { desc.set(mozilla::Some( @@ -912,7 +937,42 @@ bool PyListProxyHandler::getOwnPropertyDescriptor( return true; } - // We're trying to get an item + // "constructor" property + bool isConstructorProperty; + if (id.isString() && JS_StringEqualsLiteral(cx, id.toString(), "constructor", &isConstructorProperty) && isConstructorProperty) { + JS::RootedObject global(cx, JS::GetNonCCWObjectGlobal(proxy)); + + JS::RootedValue Array(cx); + if (!JS_GetProperty(cx, global, "Array", &Array)) { + return false; + } + + JS::RootedObject rootedArray(cx, Array.toObjectOrNull()); + + JS::RootedValue Array_Prototype(cx); + if (!JS_GetProperty(cx, rootedArray, "prototype", &Array_Prototype)) { + return false; + } + + JS::RootedObject rootedArrayPrototype(cx, Array_Prototype.toObjectOrNull()); + + JS::RootedValue Array_Prototype_Constructor(cx); + if (!JS_GetProperty(cx, rootedArrayPrototype, "constructor", &Array_Prototype_Constructor)) { + return false; + } + + JS::RootedObject rootedArrayPrototypeConstructor(cx, Array_Prototype_Constructor.toObjectOrNull()); + + desc.set(mozilla::Some( + JS::PropertyDescriptor::Data( + JS::ObjectValue(*rootedArrayPrototypeConstructor), + {JS::PropertyAttribute::Enumerable} + ) + )); + return true; + } + + // item Py_ssize_t index; PyObject *item; if (idToIndex(cx, id, &index) && (item = PyList_GetItem(pyObject, index))) { diff --git a/tests/python/test_arrays.py b/tests/python/test_arrays.py index 3f3a21ed..8157db7f 100644 --- a/tests/python/test_arrays.py +++ b/tests/python/test_arrays.py @@ -931,6 +931,9 @@ def test_valueOf(): result = [0] pm.eval("(result, arr) => {result[0] = arr.valueOf()}")(result, items) assert result[0] == [1,2,1] + result[0][1] = 5 + assert result[0] == [1,5,1] + assert items == [1,5,1] #toLocaleString def test_toLocaleString(): @@ -972,4 +975,50 @@ def test_toLocaleString_two_args_invalid_currency(): assert str(e).__contains__("RangeError: invalid currency code in NumberFormat():") #entries - \ No newline at end of file +def test_entries_next(): + items = ['a', 'b', 'c'] + result = [0] + pm.eval("(result, arr) => {result[0] = arr.entries(); result[0] = result[0].next().value}")(result, items) + assert items == ['a', 'b', 'c'] + assert result[0] == [0, 'a'] + +def test_entries_next_next(): + items = ['a', 'b', 'c'] + result = [0] + pm.eval("(result, arr) => {result[0] = arr.entries(); result[0].next(); result[0] = result[0].next().value}")(result, items) + assert result[0] == [1, 'b'] + +def test_entries_next_next_undefined(): + items = ['a'] + result = [0] + pm.eval("(result, arr) => {result[0] = arr.entries(); result[0].next(); result[0] = result[0].next().value}")(result, items) + assert result[0] == None + +#keys this gets TypeError: iterator is not iterable +#def test_keys(): +# items = ['a', 'b', 'c'] +# result = [7,8,9] +# pm.eval("(result, arr) => { index = 0; iterator = arr.keys(); for (const key of iterator) { result[index] = key; index++;} }")(result, items) +# assert result == [0,1,2] + +#values +def test_values(): + items = ['a', 'b', 'c'] + result = [7,8,9] + pm.eval("(result, arr) => { index = 0; iterator = arr.values(); for (const key of iterator) { result[index] = key; index++;} }")(result, items) + items[0] = 'd' + assert result == ['a', 'b', 'c'] + +#constructor property +def test_constructor_creates_array(): + items = [1,2] + result = [0] + pm.eval("(result, arr) => { result[0] = arr.constructor; result[0] = new result[0]; result[0][0] = 9}")(result, items) + assert result[0] == [9] + +#length property +def test_constructor_creates_array(): + items = [1,2] + result = [0] + pm.eval("(result, arr) => { result[0] = arr.length}")(result, items) + assert result[0] == 2 \ No newline at end of file From c7e0f1c0cf285f7981b974f91b03d5c28f1941f9 Mon Sep 17 00:00:00 2001 From: philippedistributive Date: Fri, 22 Dec 2023 11:29:31 -0500 Subject: [PATCH 09/58] finalize outline --- src/PyProxyHandler.cc | 6 ++++++ src/jsTypeFactory.cc | 1 + 2 files changed, 7 insertions(+) diff --git a/src/PyProxyHandler.cc b/src/PyProxyHandler.cc index ca80e805..41e06e06 100644 --- a/src/PyProxyHandler.cc +++ b/src/PyProxyHandler.cc @@ -988,6 +988,12 @@ bool PyListProxyHandler::getOwnPropertyDescriptor( return true; } +void PyListProxyHandler::finalize(JS::GCContext *gcx, JSObject *proxy) const { + // TODO + // PyObject *self = JS::GetMaybePtrFromReservedSlot(proxy, PyObjectSlot); + // Py_DECREF(self); +} + bool PyListProxyHandler::defineProperty( JSContext *cx, JS::HandleObject proxy, JS::HandleId id, JS::Handle desc, JS::ObjectOpResult &result diff --git a/src/jsTypeFactory.cc b/src/jsTypeFactory.cc index 0c461bbc..813a92fd 100644 --- a/src/jsTypeFactory.cc +++ b/src/jsTypeFactory.cc @@ -173,6 +173,7 @@ JS::Value jsTypeFactory(JSContext *cx, PyObject *object) { if (PyList_Check(object)) { proxy = js::NewProxyObject(cx, new PyListProxyHandler(object), v, NULL); JS::SetReservedSlot(proxy, PyObjectSlot, JS::PrivateValue(object)); + //Py_INCREF(object); TODO } else { proxy = js::NewProxyObject(cx, new PyProxyHandler(object), v, NULL); } From 174501744fd40fdc19ac7bf5dfac9899776cc310 Mon Sep 17 00:00:00 2001 From: philippedistributive Date: Fri, 22 Dec 2023 11:31:02 -0500 Subject: [PATCH 10/58] cleanup --- include/PyProxyHandler.hh | 8 ++++++++ src/JSObjectProxy.cc | 1 - 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/include/PyProxyHandler.hh b/include/PyProxyHandler.hh index 1ee9fcbc..8b7e8916 100644 --- a/include/PyProxyHandler.hh +++ b/include/PyProxyHandler.hh @@ -176,6 +176,14 @@ public: PyListProxyHandler(PyObject *pyObj) : PyBaseProxyHandler(pyObj, &family) {}; static const char family; + /** + * @brief Handles python object reference count when JS Proxy object is finalized + * + * @param gcx pointer to JS::GCContext + * @param proxy the proxy object being finalized + */ + void finalize(JS::GCContext *gcx, JSObject *proxy) const override; + bool getOwnPropertyDescriptor( JSContext *cx, JS::HandleObject proxy, JS::HandleId id, JS::MutableHandle> desc diff --git a/src/JSObjectProxy.cc b/src/JSObjectProxy.cc index 380feab8..a152550f 100644 --- a/src/JSObjectProxy.cc +++ b/src/JSObjectProxy.cc @@ -44,7 +44,6 @@ void JSObjectProxyMethodDefinitions::JSObjectProxy_dealloc(JSObjectProxy *self) { // TODO (Caleb Aikens): intentional override of PyDict_Type's tp_dealloc. Probably results in leaking dict memory self->jsObject.set(nullptr); - // PyObject_GC_UnTrack(self); TODO what is the history of this line? Py_TYPE(self)->tp_free((PyObject *)self); return; } From 4e94f70b532cf20f4ce68bfcc77910ffc133807b Mon Sep 17 00:00:00 2001 From: philippedistributive Date: Fri, 22 Dec 2023 12:25:46 -0500 Subject: [PATCH 11/58] comments --- include/PyProxyHandler.hh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/include/PyProxyHandler.hh b/include/PyProxyHandler.hh index 8b7e8916..291c82e0 100644 --- a/include/PyProxyHandler.hh +++ b/include/PyProxyHandler.hh @@ -1,7 +1,7 @@ /** - * @file PyProxy.hh + * @file PyProxyHandler.hh * @author Caleb Aikens (caleb@distributive.network) and Philippe Laporte (philippe@distributive.network) - * @brief Struct for creating JS proxy objects. Used by DictType for object coercion and by ListType for List coercion + * @brief Structs for creating JS proxy objects. Used by DictType for object coercion and by ListType for List coercion * @version 0.1 * @date 2023-04-20 * From 44fd194a7a796afca65d29d42fa7d39a29da6cef Mon Sep 17 00:00:00 2001 From: philippedistributive Date: Fri, 22 Dec 2023 12:31:30 -0500 Subject: [PATCH 12/58] cleanup --- tests/python/test_arrays.py | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/tests/python/test_arrays.py b/tests/python/test_arrays.py index 8157db7f..55d0d440 100644 --- a/tests/python/test_arrays.py +++ b/tests/python/test_arrays.py @@ -105,15 +105,8 @@ def test_toString(): items = [1,2,3] result = [None] pm.eval("(result, arr) => {result[0] = arr.toString()}")(result, items) - assert result[0] == '1,2,3' - -#toString -def test_LocaleString(): - items = [1,2,3] - result = [None] - pm.eval("(result, arr) => {result[0] = arr.toString()}")(result, items) - assert result[0] == '1,2,3' - + assert result[0] == '1,2,3' + #push def test_push(): items = [1,2,3] From 7553c833979a97a5b5c470a6498b8f236efdb9f2 Mon Sep 17 00:00:00 2001 From: Philippe Laporte Date: Wed, 3 Jan 2024 16:15:22 -0500 Subject: [PATCH 13/58] Added Symbol.iterator as a defined property --- include/PyProxyHandler.hh | 2 +- src/PyProxyHandler.cc | 29 ++++++++++++++++++++++++----- 2 files changed, 25 insertions(+), 6 deletions(-) diff --git a/include/PyProxyHandler.hh b/include/PyProxyHandler.hh index 291c82e0..df28dd80 100644 --- a/include/PyProxyHandler.hh +++ b/include/PyProxyHandler.hh @@ -5,7 +5,7 @@ * @version 0.1 * @date 2023-04-20 * - * Copyright (c) 2023 Distributive Corp. + * Copyright (c) 2023-2024 Distributive Corp. * */ diff --git a/src/PyProxyHandler.cc b/src/PyProxyHandler.cc index 41e06e06..6c9bdb78 100644 --- a/src/PyProxyHandler.cc +++ b/src/PyProxyHandler.cc @@ -1,11 +1,11 @@ /** * @file PyProxyHandler.cc - * @author Caleb Aikens (caleb@distributive.network) - * @brief Struct for creating JS proxy objects. Used by DictType for object coercion + * @author Caleb Aikens (caleb@distributive.network) and Philippe Laporte (philippe@distributive.network) + * @brief Struct for creating JS proxy objects. Used by DictType for object coercion TODO * @version 0.1 * @date 2023-04-20 * - * Copyright (c) 2023 Distributive Corp. + * Copyright (c) 2023-2024 Distributive Corp. * */ @@ -19,6 +19,7 @@ #include #include #include +#include #include @@ -972,6 +973,24 @@ bool PyListProxyHandler::getOwnPropertyDescriptor( return true; } + // iterator symbol property + if (id.isSymbol()) { + JS::RootedSymbol rootedSymbol(cx, id.toSymbol()); + + if (JS::GetSymbolCode(rootedSymbol) == JS::SymbolCode::iterator) { + JSFunction *newFunction = JS_NewFunction(cx, array_values, 0, 0, NULL); + if (!newFunction) return false; + JS::RootedObject funObj(cx, JS_GetFunctionObject(newFunction)); + desc.set(mozilla::Some( + JS::PropertyDescriptor::Data( + JS::ObjectValue(*funObj), + {JS::PropertyAttribute::Enumerable} + ) + )); + return true; + } + } + // item Py_ssize_t index; PyObject *item; @@ -990,8 +1009,8 @@ bool PyListProxyHandler::getOwnPropertyDescriptor( void PyListProxyHandler::finalize(JS::GCContext *gcx, JSObject *proxy) const { // TODO - // PyObject *self = JS::GetMaybePtrFromReservedSlot(proxy, PyObjectSlot); - // Py_DECREF(self); + // PyObject *self = JS::GetMaybePtrFromReservedSlot(proxy, PyObjectSlot); + // Py_DECREF(self); } bool PyListProxyHandler::defineProperty( From 88de8bb737ff496491397973b0806f2a46aec51a Mon Sep 17 00:00:00 2001 From: philippedistributive <151072087+philippedistributive@users.noreply.github.com> Date: Wed, 3 Jan 2024 16:32:56 -0500 Subject: [PATCH 14/58] Update tests/python/test_arrays.py Co-authored-by: Caleb Aikens --- tests/python/test_arrays.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/python/test_arrays.py b/tests/python/test_arrays.py index 55d0d440..9275f078 100644 --- a/tests/python/test_arrays.py +++ b/tests/python/test_arrays.py @@ -194,6 +194,7 @@ def test_concat_empty_arg(): pm.eval("(result, arr) => {result[0] = arr.concat()}")(result, items) assert items == [1,2,3] assert result[0] == [1,2,3] + assert items is not result[0] def test_concat_two_arrays(): items = [1,2,3] From 3b9b6b736b480c9d2b5bd98e3559a78f82f3d382 Mon Sep 17 00:00:00 2001 From: Philippe Laporte Date: Wed, 3 Jan 2024 17:12:40 -0500 Subject: [PATCH 15/58] keys back in, better tests, reverse fix --- src/PyProxyHandler.cc | 15 +++---- tests/python/test_arrays.py | 86 +++++++++++++++++++++++++++++++------ 2 files changed, 80 insertions(+), 21 deletions(-) diff --git a/src/PyProxyHandler.cc b/src/PyProxyHandler.cc index 6c9bdb78..cbe8648b 100644 --- a/src/PyProxyHandler.cc +++ b/src/PyProxyHandler.cc @@ -195,7 +195,7 @@ static bool array_reverse(JSContext *cx, unsigned argc, JS::Value *vp) { PyList_Reverse(self); } - args.rval().setUndefined(); + args.rval().set(jsTypeFactory(cx, self)); return true; } @@ -852,11 +852,9 @@ static bool array_entries(JSContext *cx, unsigned argc, JS::Value *vp) { return array_copy_func(cx, argc, vp, "entries", false); } -/* see note in tests - static bool array_keys(JSContext *cx, unsigned argc, JS::Value *vp) { - return array_copy_func(cx, argc, vp, "keys", false); - } - */ +static bool array_keys(JSContext *cx, unsigned argc, JS::Value *vp) { + return array_copy_func(cx, argc, vp, "keys", false); +} static bool array_values(JSContext *cx, unsigned argc, JS::Value *vp) { return array_copy_func(cx, argc, vp, "values", false); @@ -894,7 +892,7 @@ static JSMethodDef array_methods[] = { {"toLocaleString", array_toLocaleString, 0}, {"valueOf", array_valueOf, 0}, {"entries", array_entries, 0}, - // {"keys", array_keys, 0}, + {"keys", array_keys, 0}, {"values", array_values, 0}, {NULL, NULL, 0} }; @@ -973,7 +971,7 @@ bool PyListProxyHandler::getOwnPropertyDescriptor( return true; } - // iterator symbol property + // symbol property if (id.isSymbol()) { JS::RootedSymbol rootedSymbol(cx, id.toSymbol()); @@ -1010,6 +1008,7 @@ bool PyListProxyHandler::getOwnPropertyDescriptor( void PyListProxyHandler::finalize(JS::GCContext *gcx, JSObject *proxy) const { // TODO // PyObject *self = JS::GetMaybePtrFromReservedSlot(proxy, PyObjectSlot); + // printf("finalize self=%p\n", self); // Py_DECREF(self); } diff --git a/tests/python/test_arrays.py b/tests/python/test_arrays.py index 9275f078..ee41ddc5 100644 --- a/tests/python/test_arrays.py +++ b/tests/python/test_arrays.py @@ -40,13 +40,14 @@ def test_reverse_size_one(): def test_reverse_size_zero(): items = [] pm.eval("(arr) => {arr.reverse()}")(items) - assert items == [] + assert items == [] -def test_reverse_returns_undefined(): +def test_reverse_returns_reference(): items = [1,2,3] - pm.eval("(arr) => {arr[0] = arr.reverse()}")(items) - assert items == [None,2,1] - + result = [None] + pm.eval("(result, arr) => {result[0] = arr.reverse(); result[0][0] = 4}")(result, items) + assert result[0] == [4,2,1] + assert items == [4,2,1] def test_reverse_ignores_extra_args(): items = [1,2,3] @@ -100,6 +101,18 @@ def test_join_with_sep(): pm.eval("(result, arr) => {result[0] = arr.join('-')}")(result, items) assert result[0] == '1-2-3' +def test_join_none(): + items = [None,2,3] + result = [None] + pm.eval("(result, arr) => {result[0] = arr.join()}")(result, items) + assert result[0] == ',2,3' + +def test_join_null(): + items = [pm.null,2,3] + result = [None] + pm.eval("(result, arr) => {result[0] = arr.join()}")(result, items) + assert result[0] == ',2,3' + #toString def test_toString(): items = [1,2,3] @@ -208,7 +221,18 @@ def test_concat_mix(): result = [None] pm.eval("(result, arr) => {result[0] = arr.concat([7,8], true, [0,1])}")(result, items) assert items == [1,2,3] - assert result[0] == [1,2,3,7,8,True, 0,1] + assert result[0] == [1,2,3,7,8,True,0,1] + +def test_concat_object_element(): + d = {"a":1} + items = [1, 2, d] + result = [None] + pm.eval("(result, arr) => {result[0] = arr.concat()}")(result, items) + assert items == [1, 2, d] + assert result[0] == [1, 2, d] + assert items is not result[0] + assert d is items[2] + assert d is result[0][2] #slice def test_slice(): @@ -218,6 +242,14 @@ def test_slice(): assert items == [1,2,3] assert result[0] == [2] +def test_slice_copy(): + items = [1,2,3] + result = [None] + pm.eval("(result, arr) => {result[0] = arr.slice(0,3)}")(result, items) + assert items == [1,2,3] + assert result[0] == [1,2,3] + assert items is not result[0] + def test_slice_start_zero(): items = [1,2,3] result = [None] @@ -988,12 +1020,12 @@ def test_entries_next_next_undefined(): pm.eval("(result, arr) => {result[0] = arr.entries(); result[0].next(); result[0] = result[0].next().value}")(result, items) assert result[0] == None -#keys this gets TypeError: iterator is not iterable -#def test_keys(): -# items = ['a', 'b', 'c'] -# result = [7,8,9] -# pm.eval("(result, arr) => { index = 0; iterator = arr.keys(); for (const key of iterator) { result[index] = key; index++;} }")(result, items) -# assert result == [0,1,2] +#keys +def test_keys(): + items = ['a', 'b', 'c'] + result = [7,8,9] + pm.eval("(result, arr) => { index = 0; iterator = arr.keys(); for (const key of iterator) { result[index] = key; index++;} }")(result, items) + assert result == ['0','1','2'] #values def test_values(): @@ -1015,4 +1047,32 @@ def test_constructor_creates_array(): items = [1,2] result = [0] pm.eval("(result, arr) => { result[0] = arr.length}")(result, items) - assert result[0] == 2 \ No newline at end of file + assert result[0] == 2 + +#iterator symbol property +def test_iterator_type_function(): + items = [1,2] + result = [0] + pm.eval("(result, arr) => { result[0] = typeof arr[Symbol.iterator]}")(result, items) + assert result[0] == 'function' + +def test_iterator_first_next(): + items = [1,2] + result = [0] + pm.eval("(result, arr) => { result[0] = arr[Symbol.iterator]().next()}")(result, items) + assert result[0].value == 1 + assert result[0].done == False + +def test_iterator_second_next(): + items = [1,2] + result = [0] + pm.eval("(result, arr) => { let iterator = arr[Symbol.iterator](); iterator.next(); result[0] = iterator.next()}")(result, items) + assert result[0].value == 2 + assert result[0].done == False + +def test_iterator_last_next(): + items = [1,2] + result = [0] + pm.eval("(result, arr) => { let iterator = arr[Symbol.iterator](); iterator.next(); iterator.next(); result[0] = iterator.next()}")(result, items) + assert result[0].value == None + assert result[0].done == True \ No newline at end of file From 138a84d0aedb1265c73a6e124af5ad682c29b2b4 Mon Sep 17 00:00:00 2001 From: Philippe Laporte Date: Thu, 11 Jan 2024 13:57:19 -0500 Subject: [PATCH 16/58] All copying methods replaced with non-copying ones --- include/PyProxyHandler.hh | 1 - include/jsTypeFactory.hh | 9 - setup.sh | 1 + src/PyProxyHandler.cc | 1492 +++++++++++++++++++++++++++++++---- src/jsTypeFactory.cc | 21 +- tests/python/test_arrays.py | 158 +++- 6 files changed, 1461 insertions(+), 221 deletions(-) diff --git a/include/PyProxyHandler.hh b/include/PyProxyHandler.hh index df28dd80..b95c4778 100644 --- a/include/PyProxyHandler.hh +++ b/include/PyProxyHandler.hh @@ -198,7 +198,6 @@ public: bool delete_(JSContext *cx, JS::HandleObject proxy, JS::HandleId id, JS::ObjectOpResult &result) const override; bool isArray(JSContext *cx, JS::HandleObject proxy, JS::IsArrayAnswer *answer) const override; bool getBuiltinClass(JSContext *cx, JS::Handle obj, js::ESClass *cls) const override; - const char *className(JSContext *cx, JS::HandleObject proxy) const override; }; /** diff --git a/include/jsTypeFactory.hh b/include/jsTypeFactory.hh index 23339c54..1ae6b131 100644 --- a/include/jsTypeFactory.hh +++ b/include/jsTypeFactory.hh @@ -38,15 +38,6 @@ size_t UCS4ToUTF16(const uint32_t *chars, size_t length, uint16_t *outStr); */ JS::Value jsTypeFactory(JSContext *cx, PyObject *object); -/** - * @brief Function that takes a PyObject and returns a corresponding JS::Value which is a copy, not a reference - * - * @param cx - Pointer to the JSContext - * @param object - Pointer to the PyListObject - * @return JS::Value - A JS::Value corresponding to the PyType - */ -JS::Value jsTypeFactoryCopy(JSContext *cx, PyObject *object); - /** * @brief same to jsTypeFactory, but it's guaranteed that no error would be set on the Python error stack, instead * return JS `null` on error, and output a warning in Python-land diff --git a/setup.sh b/setup.sh index 2b8c385e..71655d2d 100755 --- a/setup.sh +++ b/setup.sh @@ -53,6 +53,7 @@ sed -i'' -e '/"winheap.cpp"/d' ./memory/mozalloc/moz.build # https://bugzilla.mo sed -i'' -e 's/"install-name-tool"/"install_name_tool"/' ./moz.configure # `install-name-tool` does not exist, but we have `install_name_tool` sed -i'' -e 's/bool Unbox/JS_PUBLIC_API bool Unbox/g' ./js/public/Class.h # need to manually add JS_PUBLIC_API to js::Unbox until it gets fixed in Spidermonkey sed -i'' -e 's/bool js::Unbox/JS_PUBLIC_API bool js::Unbox/g' ./js/src/vm/JSObject.cpp # same here +sed -i'' -e 's/bool iteratorHelpers_ = false;/bool iteratorHelpers_ = true;/g' ./js/public/RealmOptions.h # turn this on, todo make it available to embedders, currently only available via command-line cd js/src mkdir -p _build cd _build diff --git a/src/PyProxyHandler.cc b/src/PyProxyHandler.cc index cbe8648b..c21d66ae 100644 --- a/src/PyProxyHandler.cc +++ b/src/PyProxyHandler.cc @@ -11,6 +11,7 @@ #include "include/PyProxyHandler.hh" +#include "include/JSArrayProxy.hh" #include "include/jsTypeFactory.hh" #include "include/pyTypeFactory.hh" #include "include/StrType.hh" @@ -20,6 +21,7 @@ #include #include #include +#include #include @@ -192,9 +194,12 @@ static bool array_reverse(JSContext *cx, unsigned argc, JS::Value *vp) { PyObject *self = JS::GetMaybePtrFromReservedSlot(proxy, PyObjectSlot); if (PyList_GET_SIZE(self) > 1) { - PyList_Reverse(self); + if (PyList_Reverse(self) < 0) { + return false; + } } + // return ref to self args.rval().set(jsTypeFactory(cx, self)); return true; } @@ -239,7 +244,9 @@ static bool array_push(JSContext *cx, unsigned argc, JS::Value *vp) { // surely for (unsigned index = 0; index < numArgs; index++) { JS::RootedValue *elementVal = new JS::RootedValue(cx); elementVal->set(args[index].get()); - PyList_Append(self, pyTypeFactory(cx, global, elementVal)->getPyObject()); + if (PyList_Append(self, pyTypeFactory(cx, global, elementVal)->getPyObject()) < 0) { + return false; + } } args.rval().setInt32(PyList_GET_SIZE(self)); @@ -263,7 +270,12 @@ static bool array_shift(JSContext *cx, unsigned argc, JS::Value *vp) { } PyObject *result = PyList_GetItem(self, 0); - PySequence_DelItem(self, 0); + if (!result) { + return false; + } + if (PySequence_DelItem(self, 0) < 0) { + return false; + } args.rval().set(jsTypeFactory(cx, result)); return true; @@ -282,35 +294,16 @@ static bool array_unshift(JSContext *cx, unsigned argc, JS::Value *vp) { // sure for (int index = args.length() - 1; index >= 0; index--) { JS::RootedValue *elementVal = new JS::RootedValue(cx); elementVal->set(args[index].get()); - PyList_Insert(self, 0, pyTypeFactory(cx, global, elementVal)->getPyObject()); + if (PyList_Insert(self, 0, pyTypeFactory(cx, global, elementVal)->getPyObject()) < 0) { + return false; + } } args.rval().setInt32(PyList_GET_SIZE(self)); return true; } -static bool array_concat(JSContext *cx, unsigned argc, JS::Value *vp) { - JS::CallArgs args = JS::CallArgsFromVp(argc, vp); - - JS::RootedObject proxy(cx, JS::ToObject(cx, args.thisv())); - if (!proxy) { - return false; - } - PyObject *self = JS::GetMaybePtrFromReservedSlot(proxy, PyObjectSlot); - - JS::RootedObject selfCopy(cx, &jsTypeFactoryCopy(cx, self).toObject()); - - JS::HandleValueArray jArgs(args); - - JS::RootedValue jRet(cx); - if (!JS_CallFunctionName(cx, selfCopy, "concat", jArgs, &jRet)) { - return false; - } - - args.rval().setObject(jRet.toObject()); - return true; -} - +// private util static inline uint64_t normalizeSliceTerm(int64_t value, uint64_t length) { if (value < 0) { value += length; @@ -360,6 +353,9 @@ static bool array_slice(JSContext *cx, unsigned argc, JS::Value *vp) { } PyObject *result = PyList_GetSlice(self, (Py_ssize_t)start, (Py_ssize_t)stop); + if (!result) { + return false; + } args.rval().set(jsTypeFactory(cx, result)); return true; @@ -422,32 +418,6 @@ static bool array_indexOf(JSContext *cx, unsigned argc, JS::Value *vp) { return true; } -static bool array_lastIndexOf(JSContext *cx, unsigned argc, JS::Value *vp) { - JS::CallArgs args = JS::CallArgsFromVp(argc, vp); - - if (!args.requireAtLeast(cx, "lastIndexOf", 1)) { - return false; - } - - JS::RootedObject proxy(cx, JS::ToObject(cx, args.thisv())); - if (!proxy) { - return false; - } - PyObject *self = JS::GetMaybePtrFromReservedSlot(proxy, PyObjectSlot); - - JS::RootedObject selfCopy(cx, &jsTypeFactoryCopy(cx, self).toObject()); - - JS::HandleValueArray jArgs(args); - - JS::RootedValue jRet(cx); - if (!JS_CallFunctionName(cx, selfCopy, "lastIndexOf", jArgs, &jRet)) { - return false; - } - - args.rval().setInt32(jRet.toInt32()); - return true; -} - static bool array_splice(JSContext *cx, unsigned argc, JS::Value *vp) { JS::CallArgs args = JS::CallArgsFromVp(argc, vp); @@ -503,17 +473,27 @@ static bool array_splice(JSContext *cx, unsigned argc, JS::Value *vp) { // get deleted items for return value PyObject *deleted = PyList_GetSlice(self, actualStart, actualStart + actualDeleteCount); + if (!deleted) { + return false; + } // build list for SetSlice call PyObject *inserted = PyList_New(insertCount); + if (!inserted) { + return false; + } JS::RootedObject *global = new JS::RootedObject(cx, JS::GetNonCCWObjectGlobal(proxy)); for (int index = 0; index < insertCount; index++) { JS::RootedValue *elementVal = new JS::RootedValue(cx, args[index + 2].get()); - PyList_SetItem(inserted, index, pyTypeFactory(cx, global, elementVal)->getPyObject()); + if (PyList_SetItem(inserted, index, pyTypeFactory(cx, global, elementVal)->getPyObject()) < 0) { + return false; + } } - PyList_SetSlice(self, actualStart, actualStart + actualDeleteCount, inserted); + if (PyList_SetSlice(self, actualStart, actualStart + actualDeleteCount, inserted) < 0) { + return false; + } args.rval().set(jsTypeFactory(cx, deleted)); return true; @@ -611,8 +591,6 @@ static bool array_fill(JSContext *cx, unsigned argc, JS::Value *vp) { unsigned int argsLength = args.length(); - JS::RootedValue *fillValue = new JS::RootedValue(cx, args[0].get()); - int64_t relativeStart; if (argsLength > 1) { if (!JS::ToInt64(cx, args.get(1), &relativeStart)) { @@ -647,7 +625,10 @@ static bool array_fill(JSContext *cx, unsigned argc, JS::Value *vp) { JS::RootedObject *global = new JS::RootedObject(cx, JS::GetNonCCWObjectGlobal(proxy)); for (int index = actualStart; index < actualEnd; index++) { - PyList_SetItem(self, index, pyTypeFactory(cx, global, fillValue)->getPyObject()); + JS::RootedValue *fillValue = new JS::RootedValue(cx, args[0].get()); + if (PyList_SetItem(self, index, pyTypeFactory(cx, global, fillValue)->getPyObject()) < 0) { + return false; + } } // return ref to self @@ -724,7 +705,9 @@ static bool array_copyWithin(JSContext *cx, unsigned argc, JS::Value *vp) { while (count > 0) { PyObject *itemStart = PyList_GetItem(self, actualStart); - PyList_SetItem(self, actualTarget, itemStart); + if (PyList_SetItem(self, actualTarget, itemStart) < 0) { + return false; + } actualStart--; actualTarget--; @@ -733,7 +716,9 @@ static bool array_copyWithin(JSContext *cx, unsigned argc, JS::Value *vp) { } else { while (count > 0) { PyObject *itemStart = PyList_GetItem(self, actualStart); - PyList_SetItem(self, actualTarget, itemStart); + if (PyList_SetItem(self, actualTarget, itemStart) < 0) { + return false; + } actualStart++; actualTarget++; @@ -746,11 +731,68 @@ static bool array_copyWithin(JSContext *cx, unsigned argc, JS::Value *vp) { return true; } -// private util -static bool array_copy_func(JSContext *cx, unsigned argc, JS::Value *vp, const char *fName, bool checkRequireAtLeastOne = true) { +static bool array_concat(JSContext *cx, unsigned argc, JS::Value *vp) { + JS::CallArgs args = JS::CallArgsFromVp(argc, vp); + + JS::RootedObject proxy(cx, JS::ToObject(cx, args.thisv())); + if (!proxy) { + return false; + } + PyObject *self = JS::GetMaybePtrFromReservedSlot(proxy, PyObjectSlot); + + JS::RootedObject *global = new JS::RootedObject(cx, JS::GetNonCCWObjectGlobal(proxy)); + + Py_ssize_t selfSize = PyList_GET_SIZE(self); + + PyObject *result = PyList_New(selfSize); + + for (Py_ssize_t index = 0; index < selfSize; index++) { + PyList_SetItem(result, index, PyList_GetItem(self, index)); + } + + unsigned numArgs = args.length(); + for (unsigned index = 0; index < numArgs; index++) { + JS::RootedValue *elementVal = new JS::RootedValue(cx); + elementVal->set(args[index].get()); + + PyObject *item = pyTypeFactory(cx, global, elementVal)->getPyObject(); + if (PyObject_TypeCheck(item, &JSArrayProxyType)) { + // flatten the array only a depth 1 + Py_ssize_t itemLength = JSArrayProxyMethodDefinitions::JSArrayProxy_length((JSArrayProxy *)item); + for (Py_ssize_t flatIndex = 0; flatIndex < itemLength; flatIndex++) { + elementVal = new JS::RootedValue(cx); + if (!JS_GetElement(cx, ((JSArrayProxy *)item)->jsArray, flatIndex, elementVal)) { + return false; + } + if (PyList_Append(result, pyTypeFactory(cx, global, elementVal)->getPyObject()) < 0) { + return false; + } + } + } + else if (PyObject_TypeCheck(item, &PyList_Type)) { + // flatten the array only at depth 1 + Py_ssize_t itemLength = PyList_GET_SIZE(item); + for (Py_ssize_t flatIndex = 0; flatIndex < itemLength; flatIndex++) { + if (PyList_Append(result, PyList_GetItem(item, flatIndex)) < 0) { + return false; + } + } + } + else { + if (PyList_Append(result, pyTypeFactory(cx, global, elementVal)->getPyObject()) < 0) { + return false; + } + } + } + + args.rval().set(jsTypeFactory(cx, result)); + return true; +} + +static bool array_lastIndexOf(JSContext *cx, unsigned argc, JS::Value *vp) { JS::CallArgs args = JS::CallArgsFromVp(argc, vp); - if (checkRequireAtLeastOne && !args.requireAtLeast(cx, fName, 1)) { + if (!args.requireAtLeast(cx, "lastIndexOf", 1)) { return false; } @@ -760,142 +802,1267 @@ static bool array_copy_func(JSContext *cx, unsigned argc, JS::Value *vp, const c } PyObject *self = JS::GetMaybePtrFromReservedSlot(proxy, PyObjectSlot); - JS::RootedObject selfCopy(cx, &jsTypeFactoryCopy(cx, self).toObject()); + Py_ssize_t selfLength = PyList_GET_SIZE(self); - JS::HandleValueArray jArgs(args); - JS::RootedValue rval(cx); + if (selfLength == 0) { + args.rval().setInt32(-1); + return true; + } - if (!JS_CallFunctionName(cx, selfCopy, fName, jArgs, &rval)) { - return false; + uint64_t start = selfLength - 1; + if (args.length() > 1) { + int64_t n; + if (!JS::ToInt64(cx, args[1], &n)) { + return false; + } + + if (n < 0) { + double d = double(selfLength) + n; + if (d < 0) { + args.rval().setInt32(-1); + return true; + } + start = uint64_t(d); + } else if (n < double(start)) { + start = uint64_t(n); + } } - args.rval().set(rval); + JS::RootedObject *global = new JS::RootedObject(cx, JS::GetNonCCWObjectGlobal(proxy)); + JS::RootedValue *elementVal = new JS::RootedValue(cx, args[0].get()); + PyObject *element = pyTypeFactory(cx, global, elementVal)->getPyObject(); + for (int64_t index = start; index >= 0; index--) { + PyObject *item = PyList_GetItem(self, index); + Py_INCREF(item); + int cmp = PyObject_RichCompareBool(item, element, Py_EQ); + Py_DECREF(item); + if (cmp < 0) { + return false; + } + else if (cmp == 1) { + args.rval().setInt32(index); + return true; + } + } + args.rval().setInt32(-1); return true; } static bool array_includes(JSContext *cx, unsigned argc, JS::Value *vp) { - return array_copy_func(cx, argc, vp, "includes"); + JS::CallArgs args = JS::CallArgsFromVp(argc, vp); + + if (!args.requireAtLeast(cx, "includes", 1)) { + return false; + } + + if (!array_indexOf(cx, argc, vp)) { + return false; + } + + args.rval().setBoolean(args.rval().get().toInt32() >= 0 ? true : false); + return true; } static bool array_forEach(JSContext *cx, unsigned argc, JS::Value *vp) { - return array_copy_func(cx, argc, vp, "forEach"); -} + JS::CallArgs args = JS::CallArgsFromVp(argc, vp); -static bool array_map(JSContext *cx, unsigned argc, JS::Value *vp) { - return array_copy_func(cx, argc, vp, "map"); -} + if (!args.requireAtLeast(cx, "forEach", 1)) { + return false; + } -static bool array_filter(JSContext *cx, unsigned argc, JS::Value *vp) { - return array_copy_func(cx, argc, vp, "filter"); -} + JS::RootedObject proxy(cx, JS::ToObject(cx, args.thisv())); + if (!proxy) { + return false; + } + PyObject *self = JS::GetMaybePtrFromReservedSlot(proxy, PyObjectSlot); -static bool array_reduce(JSContext *cx, unsigned argc, JS::Value *vp) { - return array_copy_func(cx, argc, vp, "reduce"); -} + JS::Value callbackfn = args[0].get(); -static bool array_reduceRight(JSContext *cx, unsigned argc, JS::Value *vp) { - return array_copy_func(cx, argc, vp, "reduceRight"); -} + if (!callbackfn.isObject() || !JS::IsCallable(&callbackfn.toObject())) { + JS_ReportErrorNumberASCII(cx, js::GetErrorMessage, nullptr, JSMSG_NOT_FUNCTION, "forEach: callback"); + return false; + } -static bool array_some(JSContext *cx, unsigned argc, JS::Value *vp) { - return array_copy_func(cx, argc, vp, "some"); -} + JS::RootedValue selfValue(cx, jsTypeFactorySafe(cx, self)); + JS::RootedValue callBack(cx, callbackfn); -static bool array_every(JSContext *cx, unsigned argc, JS::Value *vp) { - return array_copy_func(cx, argc, vp, "every"); -} + JS::Rooted> jArgs(cx); + JS::RootedValue rval(cx); -static bool array_find(JSContext *cx, unsigned argc, JS::Value *vp) { - return array_copy_func(cx, argc, vp, "find"); -} + Py_ssize_t len = PyList_GET_SIZE(self); -static bool array_findIndex(JSContext *cx, unsigned argc, JS::Value *vp) { - return array_copy_func(cx, argc, vp, "findIndex"); -} + JS::RootedObject *rootedThisArg = nullptr; + if (args.length() > 1) { + JS::Value thisArg = args[1].get(); + if (!thisArg.isObjectOrNull()) { + JS_ReportErrorNumberASCII(cx, js::GetErrorMessage, nullptr, JSMSG_NOT_OBJORNULL, "'this' argument"); + return false; + } -static bool array_flat(JSContext *cx, unsigned argc, JS::Value *vp) { - return array_copy_func(cx, argc, vp, "flat", false); -} + // TODO support null, currently gets TypeError + rootedThisArg = new JS::RootedObject(cx, thisArg.toObjectOrNull()); + } -static bool array_flatMap(JSContext *cx, unsigned argc, JS::Value *vp) { - return array_copy_func(cx, argc, vp, "flatMap"); -} + for (Py_ssize_t index = 0, toIndex = 0; index < len; index++) { + jArgs[0].set(jsTypeFactorySafe(cx, PyList_GetItem(self, index))); + jArgs[1].setInt32(index); + jArgs[2].set(selfValue); -static bool array_join(JSContext *cx, unsigned argc, JS::Value *vp) { - return array_copy_func(cx, argc, vp, "join", false); -} + if (args.length() > 1) { + if (!JS_CallFunctionValue(cx, *rootedThisArg, callBack, jArgs, &rval)) { + delete rootedThisArg; + return false; + } + } + else { + if (!JS_CallFunctionValue(cx, nullptr, callBack, jArgs, &rval)) { + return false; + } + } + } -static bool array_toString(JSContext *cx, unsigned argc, JS::Value *vp) { - return array_copy_func(cx, argc, vp, "toString", false); -} + if (rootedThisArg) { + delete rootedThisArg; + } -static bool array_toLocaleString(JSContext *cx, unsigned argc, JS::Value *vp) { - return array_copy_func(cx, argc, vp, "toLocaleString", false); + args.rval().setUndefined(); + return true; } -static bool array_valueOf(JSContext *cx, unsigned argc, JS::Value *vp) { +static bool array_map(JSContext *cx, unsigned argc, JS::Value *vp) { JS::CallArgs args = JS::CallArgsFromVp(argc, vp); + if (!args.requireAtLeast(cx, "map", 1)) { + return false; + } + JS::RootedObject proxy(cx, JS::ToObject(cx, args.thisv())); if (!proxy) { return false; } PyObject *self = JS::GetMaybePtrFromReservedSlot(proxy, PyObjectSlot); - // return ref to self - args.rval().set(jsTypeFactory(cx, self)); - return true; -} + JS::Value callbackfn = args[0].get(); -static bool array_entries(JSContext *cx, unsigned argc, JS::Value *vp) { - return array_copy_func(cx, argc, vp, "entries", false); -} + if (!callbackfn.isObject() || !JS::IsCallable(&callbackfn.toObject())) { + // Decompiling the faulty arg is not accessible through the JSAPI so we do the best effort for the error message + JS_ReportErrorNumberASCII(cx, js::GetErrorMessage, nullptr, JSMSG_NOT_FUNCTION, "map: callback"); + return false; + } -static bool array_keys(JSContext *cx, unsigned argc, JS::Value *vp) { - return array_copy_func(cx, argc, vp, "keys", false); -} + Py_ssize_t len = PyList_GET_SIZE(self); -static bool array_values(JSContext *cx, unsigned argc, JS::Value *vp) { - return array_copy_func(cx, argc, vp, "values", false); -} + JSObject *retArray = JS::NewArrayObject(cx, len); + JS::RootedObject rootedRetArray(cx, retArray); + JS::RootedValue selfValue(cx, jsTypeFactorySafe(cx, self)); + JS::RootedValue callBack(cx, callbackfn); -static JSMethodDef array_methods[] = { - {"reverse", array_reverse, 0}, - {"pop", array_pop, 0}, - {"push", array_push, 1}, - {"shift", array_shift, 0}, - {"unshift", array_unshift, 1}, - {"concat", array_concat, 1}, - {"slice", array_slice, 2}, - {"indexOf", array_indexOf, 1}, - {"lastIndexOf", array_lastIndexOf, 1}, - {"splice", array_splice, 2}, - {"sort", array_sort, 1}, - {"fill", array_fill, 3}, - {"copyWithin", array_copyWithin, 3}, - {"includes", array_includes, 1}, - {"forEach", array_forEach, 1}, - {"map", array_map, 1}, - {"filter", array_filter, 1}, - {"reduce", array_reduce, 1}, - {"reduceRight", array_reduceRight, 1}, - {"some", array_some, 1}, - {"every", array_every, 1}, - {"find", array_find, 1}, - {"findIndex", array_findIndex, 1}, - {"flat", array_flat, 1}, - {"flatMap", array_flatMap, 1}, - {"join", array_join, 1}, - {"toString", array_toString, 0}, - {"toLocaleString", array_toLocaleString, 0}, - {"valueOf", array_valueOf, 0}, - {"entries", array_entries, 0}, - {"keys", array_keys, 0}, - {"values", array_values, 0}, - {NULL, NULL, 0} -}; + JS::Rooted> jArgs(cx); + JS::RootedValue rval(cx); + + JS::RootedObject *rootedThisArg = nullptr; + if (args.length() > 1) { + JS::Value thisArg = args[1].get(); + if (!thisArg.isObjectOrNull()) { + JS_ReportErrorNumberASCII(cx, js::GetErrorMessage, nullptr, JSMSG_NOT_OBJORNULL, "'this' argument"); + return false; + } + + // TODO support null, currently gets TypeError + rootedThisArg = new JS::RootedObject(cx, thisArg.toObjectOrNull()); + } + + for (Py_ssize_t index = 0; index < len; index++) { + jArgs[0].set(jsTypeFactorySafe(cx, PyList_GetItem(self, index))); + jArgs[1].setInt32(index); + jArgs[2].set(selfValue); + + if (args.length() > 1) { + if (!JS_CallFunctionValue(cx, *rootedThisArg, callBack, jArgs, &rval)) { + delete rootedThisArg; + return false; + } + } + else { + if (!JS_CallFunctionValue(cx, nullptr, callBack, jArgs, &rval)) { + return false; + } + } + + JS_SetElement(cx, rootedRetArray, index, rval); + } + + if (rootedThisArg) { + delete rootedThisArg; + } + + args.rval().setObject(*retArray); + return true; +} + +static bool array_filter(JSContext *cx, unsigned argc, JS::Value *vp) { + JS::CallArgs args = JS::CallArgsFromVp(argc, vp); + + if (!args.requireAtLeast(cx, "filter", 1)) { + return false; + } + + JS::RootedObject proxy(cx, JS::ToObject(cx, args.thisv())); + if (!proxy) { + return false; + } + PyObject *self = JS::GetMaybePtrFromReservedSlot(proxy, PyObjectSlot); + + JS::Value callbackfn = args[0].get(); + + if (!callbackfn.isObject() || !JS::IsCallable(&callbackfn.toObject())) { + JS_ReportErrorNumberASCII(cx, js::GetErrorMessage, nullptr, JSMSG_NOT_FUNCTION, "filter: callback"); + return false; + } + + JS::RootedValue selfValue(cx, jsTypeFactorySafe(cx, self)); + JS::RootedValue callBack(cx, callbackfn); + + JS::Rooted> jArgs(cx); + JS::RootedValue rval(cx); + + JS::RootedValueVector retVector(cx); + + JS::RootedObject *rootedThisArg = nullptr; + if (args.length() > 1) { + JS::Value thisArg = args[1].get(); + if (!thisArg.isObjectOrNull()) { + JS_ReportErrorNumberASCII(cx, js::GetErrorMessage, nullptr, JSMSG_NOT_OBJORNULL, "'this' argument"); + return false; + } + + // TODO support null, currently gets TypeError + rootedThisArg = new JS::RootedObject(cx, thisArg.toObjectOrNull()); + } + + Py_ssize_t len = PyList_GET_SIZE(self); + for (Py_ssize_t index = 0, toIndex = 0; index < len; index++) { + JS::Value item = jsTypeFactorySafe(cx, PyList_GetItem(self, index)); + jArgs[0].set(item); + jArgs[1].setInt32(index); + jArgs[2].set(selfValue); + + if (args.length() > 1) { + if (!JS_CallFunctionValue(cx, *rootedThisArg, callBack, jArgs, &rval)) { + delete rootedThisArg; + return false; + } + } + else { + if (!JS_CallFunctionValue(cx, nullptr, callBack, jArgs, &rval)) { + return false; + } + } + + if (rval.toBoolean()) { + if (!retVector.append(item)) { + return false; + } + } + } + + if (rootedThisArg) { + delete rootedThisArg; + } + + JS::HandleValueArray jsValueArray(retVector); + JSObject *retArray = JS::NewArrayObject(cx, jsValueArray); + + args.rval().setObject(*retArray); + return true; +} + +static bool array_reduce(JSContext *cx, unsigned argc, JS::Value *vp) { + JS::CallArgs args = JS::CallArgsFromVp(argc, vp); + + if (!args.requireAtLeast(cx, "reduce", 1)) { + return false; + } + + JS::RootedObject proxy(cx, JS::ToObject(cx, args.thisv())); + if (!proxy) { + return false; + } + PyObject *self = JS::GetMaybePtrFromReservedSlot(proxy, PyObjectSlot); + + JS::Value callbackfn = args[0].get(); + + if (!callbackfn.isObject() || !JS::IsCallable(&callbackfn.toObject())) { + JS_ReportErrorNumberASCII(cx, js::GetErrorMessage, nullptr, JSMSG_NOT_FUNCTION, "reduce: callback"); + return false; + } + + JS::RootedValue selfValue(cx, jsTypeFactorySafe(cx, self)); + JS::RootedValue callBack(cx, callbackfn); + + JS::Rooted> jArgs(cx); + JS::RootedValue *accumulator; + + Py_ssize_t len = PyList_GET_SIZE(self); + + if (args.length() > 1) { + accumulator = new JS::RootedValue(cx, args[1].get()); + } + else { + if (len == 0) { + JS_ReportErrorNumberASCII(cx, js::GetErrorMessage, nullptr, JSMSG_EMPTY_ARRAY_REDUCE); + return false; + } + accumulator = new JS::RootedValue(cx, jsTypeFactory(cx, PyList_GetItem(self, 0))); + } + + for (Py_ssize_t index = args.length() > 1 ? 0 : 1; index < len; index++) { + jArgs[0].set(*accumulator); + jArgs[1].set(jsTypeFactorySafe(cx, PyList_GetItem(self, index))); + jArgs[2].setInt32(index); + jArgs[3].set(selfValue); + + if (!JS_CallFunctionValue(cx, nullptr, callBack, jArgs, accumulator)) { + delete accumulator; + return false; + } + } + + args.rval().set(accumulator->get()); + delete accumulator; + return true; +} + +static bool array_reduceRight(JSContext *cx, unsigned argc, JS::Value *vp) { + JS::CallArgs args = JS::CallArgsFromVp(argc, vp); + + if (!args.requireAtLeast(cx, "reduceRight", 1)) { + return false; + } + + JS::RootedObject proxy(cx, JS::ToObject(cx, args.thisv())); + if (!proxy) { + return false; + } + PyObject *self = JS::GetMaybePtrFromReservedSlot(proxy, PyObjectSlot); + + JS::Value callbackfn = args[0].get(); + + if (!callbackfn.isObject() || !JS::IsCallable(&callbackfn.toObject())) { + JS_ReportErrorNumberASCII(cx, js::GetErrorMessage, nullptr, JSMSG_NOT_FUNCTION, "reduceRight: callback"); + return false; + } + + JS::RootedValue selfValue(cx, jsTypeFactorySafe(cx, self)); + JS::RootedValue callBack(cx, callbackfn); + + JS::Rooted> jArgs(cx); + JS::RootedValue *accumulator; + + Py_ssize_t len = PyList_GET_SIZE(self); + + if (args.length() > 1) { + accumulator = new JS::RootedValue(cx, args[1].get()); + } + else { + if (len == 0) { + JS_ReportErrorNumberASCII(cx, js::GetErrorMessage, nullptr, JSMSG_EMPTY_ARRAY_REDUCE); + return false; + } + accumulator = new JS::RootedValue(cx, jsTypeFactory(cx, PyList_GetItem(self, len - 1))); + } + + for (int64_t index = args.length() > 1 ? len - 1 : len - 2; index >= 0; index--) { + jArgs[0].set(*accumulator); + jArgs[1].set(jsTypeFactorySafe(cx, PyList_GetItem(self, index))); + jArgs[2].setInt32(index); + jArgs[3].set(selfValue); + + if (!JS_CallFunctionValue(cx, nullptr, callBack, jArgs, accumulator)) { + delete accumulator; + return false; + } + } + + args.rval().set(accumulator->get()); + delete accumulator; + return true; +} + +static bool array_some(JSContext *cx, unsigned argc, JS::Value *vp) { + JS::CallArgs args = JS::CallArgsFromVp(argc, vp); + + if (!args.requireAtLeast(cx, "some", 1)) { + return false; + } + + JS::RootedObject proxy(cx, JS::ToObject(cx, args.thisv())); + if (!proxy) { + return false; + } + PyObject *self = JS::GetMaybePtrFromReservedSlot(proxy, PyObjectSlot); + + JS::Value callbackfn = args[0].get(); + + if (!callbackfn.isObject() || !JS::IsCallable(&callbackfn.toObject())) { + JS_ReportErrorNumberASCII(cx, js::GetErrorMessage, nullptr, JSMSG_NOT_FUNCTION, "some: callback"); + return false; + } + + JS::RootedValue selfValue(cx, jsTypeFactorySafe(cx, self)); + JS::RootedValue callBack(cx, callbackfn); + + JS::Rooted> jArgs(cx); + JS::RootedValue rval(cx); + + JS::RootedObject *rootedThisArg = nullptr; + if (args.length() > 1) { + JS::Value thisArg = args[1].get(); + if (!thisArg.isObjectOrNull()) { + JS_ReportErrorNumberASCII(cx, js::GetErrorMessage, nullptr, JSMSG_NOT_OBJORNULL, "'this' argument"); + return false; + } + + // TODO support null, currently gets TypeError + rootedThisArg = new JS::RootedObject(cx, thisArg.toObjectOrNull()); + } + + Py_ssize_t len = PyList_GET_SIZE(self); + for (Py_ssize_t index = 0, toIndex = 0; index < len; index++) { + jArgs[0].set(jsTypeFactorySafe(cx, PyList_GetItem(self, index))); + jArgs[1].setInt32(index); + jArgs[2].set(selfValue); + + if (args.length() > 1) { + if (!JS_CallFunctionValue(cx, *rootedThisArg, callBack, jArgs, &rval)) { + delete rootedThisArg; + return false; + } + } + else { + if (!JS_CallFunctionValue(cx, nullptr, callBack, jArgs, &rval)) { + return false; + } + } + + if (rval.toBoolean()) { + if (rootedThisArg) { + delete rootedThisArg; + } + args.rval().setBoolean(true); + return true; + } + } + + if (rootedThisArg) { + delete rootedThisArg; + } + + args.rval().setBoolean(false); + return true; +} + +static bool array_every(JSContext *cx, unsigned argc, JS::Value *vp) { + JS::CallArgs args = JS::CallArgsFromVp(argc, vp); + + if (!args.requireAtLeast(cx, "every", 1)) { + return false; + } + + JS::RootedObject proxy(cx, JS::ToObject(cx, args.thisv())); + if (!proxy) { + return false; + } + PyObject *self = JS::GetMaybePtrFromReservedSlot(proxy, PyObjectSlot); + + JS::Value callbackfn = args[0].get(); + + if (!callbackfn.isObject() || !JS::IsCallable(&callbackfn.toObject())) { + JS_ReportErrorNumberASCII(cx, js::GetErrorMessage, nullptr, JSMSG_NOT_FUNCTION, "every: callback"); + return false; + } + + JS::RootedValue selfValue(cx, jsTypeFactorySafe(cx, self)); + JS::RootedValue callBack(cx, callbackfn); + + JS::Rooted> jArgs(cx); + JS::RootedValue rval(cx); + + JS::RootedObject *rootedThisArg = nullptr; + if (args.length() > 1) { + JS::Value thisArg = args[1].get(); + if (!thisArg.isObjectOrNull()) { + JS_ReportErrorNumberASCII(cx, js::GetErrorMessage, nullptr, JSMSG_NOT_OBJORNULL, "'this' argument"); + return false; + } + + // TODO support null, currently gets TypeError + rootedThisArg = new JS::RootedObject(cx, thisArg.toObjectOrNull()); + } + + Py_ssize_t len = PyList_GET_SIZE(self); + for (Py_ssize_t index = 0, toIndex = 0; index < len; index++) { + jArgs[0].set(jsTypeFactorySafe(cx, PyList_GetItem(self, index))); + jArgs[1].setInt32(index); + jArgs[2].set(selfValue); + + if (args.length() > 1) { + if (!JS_CallFunctionValue(cx, *rootedThisArg, callBack, jArgs, &rval)) { + delete rootedThisArg; + return false; + } + } + else { + if (!JS_CallFunctionValue(cx, nullptr, callBack, jArgs, &rval)) { + return false; + } + } + + if (!rval.toBoolean()) { + if (rootedThisArg) { + delete rootedThisArg; + } + args.rval().setBoolean(false); + return true; + } + } + + if (rootedThisArg) { + delete rootedThisArg; + } + + args.rval().setBoolean(true); + return true; +} + +static bool array_find(JSContext *cx, unsigned argc, JS::Value *vp) { + JS::CallArgs args = JS::CallArgsFromVp(argc, vp); + + if (!args.requireAtLeast(cx, "find", 1)) { + return false; + } + + JS::RootedObject proxy(cx, JS::ToObject(cx, args.thisv())); + if (!proxy) { + return false; + } + PyObject *self = JS::GetMaybePtrFromReservedSlot(proxy, PyObjectSlot); + + JS::Value callbackfn = args[0].get(); + + if (!callbackfn.isObject() || !JS::IsCallable(&callbackfn.toObject())) { + JS_ReportErrorNumberASCII(cx, js::GetErrorMessage, nullptr, JSMSG_NOT_FUNCTION, "find: callback"); + return false; + } + + JS::RootedValue selfValue(cx, jsTypeFactorySafe(cx, self)); + JS::RootedValue callBack(cx, callbackfn); + + JS::Rooted> jArgs(cx); + JS::RootedValue rval(cx); + + JS::RootedObject *rootedThisArg = nullptr; + if (args.length() > 1) { + JS::Value thisArg = args[1].get(); + if (!thisArg.isObjectOrNull()) { + JS_ReportErrorNumberASCII(cx, js::GetErrorMessage, nullptr, JSMSG_NOT_OBJORNULL, "'this' argument"); + return false; + } + + // TODO support null, currently gets TypeError + rootedThisArg = new JS::RootedObject(cx, thisArg.toObjectOrNull()); + } + + Py_ssize_t len = PyList_GET_SIZE(self); + for (Py_ssize_t index = 0, toIndex = 0; index < len; index++) { + JS::Value item = jsTypeFactorySafe(cx, PyList_GetItem(self, index)); + jArgs[0].set(item); + jArgs[1].setInt32(index); + jArgs[2].set(selfValue); + + if (args.length() > 1) { + if (!JS_CallFunctionValue(cx, *rootedThisArg, callBack, jArgs, &rval)) { + delete rootedThisArg; + return false; + } + } + else { + if (!JS_CallFunctionValue(cx, nullptr, callBack, jArgs, &rval)) { + return false; + } + } + + if (rval.toBoolean()) { + if (rootedThisArg) { + delete rootedThisArg; + } + args.rval().set(item); + return true; + } + } + + if (rootedThisArg) { + delete rootedThisArg; + } + + args.rval().setUndefined(); + return true; +} + +static bool array_findIndex(JSContext *cx, unsigned argc, JS::Value *vp) { + JS::CallArgs args = JS::CallArgsFromVp(argc, vp); + + if (!args.requireAtLeast(cx, "findIndex", 1)) { + return false; + } + + JS::RootedObject proxy(cx, JS::ToObject(cx, args.thisv())); + if (!proxy) { + return false; + } + PyObject *self = JS::GetMaybePtrFromReservedSlot(proxy, PyObjectSlot); + + JS::Value callbackfn = args[0].get(); + + if (!callbackfn.isObject() || !JS::IsCallable(&callbackfn.toObject())) { + JS_ReportErrorNumberASCII(cx, js::GetErrorMessage, nullptr, JSMSG_NOT_FUNCTION, "findIndex: callback"); + return false; + } + + JS::RootedValue selfValue(cx, jsTypeFactorySafe(cx, self)); + JS::RootedValue callBack(cx, callbackfn); + + JS::Rooted> jArgs(cx); + JS::RootedValue rval(cx); + + JS::RootedObject *rootedThisArg = nullptr; + if (args.length() > 1) { + JS::Value thisArg = args[1].get(); + if (!thisArg.isObjectOrNull()) { + JS_ReportErrorNumberASCII(cx, js::GetErrorMessage, nullptr, JSMSG_NOT_OBJORNULL, "'this' argument"); + return false; + } + + // TODO support null, currently gets TypeError + rootedThisArg = new JS::RootedObject(cx, thisArg.toObjectOrNull()); + } + + Py_ssize_t len = PyList_GET_SIZE(self); + for (Py_ssize_t index = 0, toIndex = 0; index < len; index++) { + jArgs[0].set(jsTypeFactorySafe(cx, PyList_GetItem(self, index))); + jArgs[1].setInt32(index); + jArgs[2].set(selfValue); + + if (args.length() > 1) { + if (!JS_CallFunctionValue(cx, *rootedThisArg, callBack, jArgs, &rval)) { + delete rootedThisArg; + return false; + } + } + else { + if (!JS_CallFunctionValue(cx, nullptr, callBack, jArgs, &rval)) { + return false; + } + } + + if (rval.toBoolean()) { + if (rootedThisArg) { + delete rootedThisArg; + } + args.rval().setInt32(index); + return true; + } + } + + if (rootedThisArg) { + delete rootedThisArg; + } + + args.rval().setInt32(-1); + return true; +} + +// private +static uint32_t FlattenIntoArray(JSContext *cx, JS::HandleObject global, + JSObject *retArray, PyObject *source, + Py_ssize_t sourceLen, uint32_t start, uint32_t depth) { + uint32_t targetIndex = start; + + JS::RootedObject rootedGlobal(cx, global); + + JS::RootedValue elementVal(cx); + for (uint32_t sourceIndex = 0; sourceIndex < sourceLen; sourceIndex++) { + if (PyObject_TypeCheck(source, &JSArrayProxyType)) { + JS_GetElement(cx, ((JSArrayProxy *)source)->jsArray, sourceIndex, &elementVal); + } + else if (PyObject_TypeCheck(source, &PyList_Type)) { + elementVal.set(jsTypeFactory(cx, PyList_GetItem(source, sourceIndex))); + } + + PyObject *element = pyTypeFactory(cx, &rootedGlobal, &elementVal)->getPyObject(); + + bool shouldFlatten; + if (depth > 0) { + shouldFlatten = PyObject_TypeCheck(element, &JSArrayProxyType) || PyObject_TypeCheck(element, &PyList_Type); + } else { + shouldFlatten = false; + } + + if (shouldFlatten) { + Py_ssize_t elementLen; + if (PyObject_TypeCheck(element, &JSArrayProxyType)) { + elementLen = JSArrayProxyMethodDefinitions::JSArrayProxy_length((JSArrayProxy *)element); + } + else if (PyObject_TypeCheck(element, &PyList_Type)) { + elementLen = PyList_GET_SIZE(element); + } + + targetIndex = FlattenIntoArray(cx, global, + retArray, + element, + elementLen, + targetIndex, + depth - 1 + ); + } + else { + JS::RootedObject rootedRetArray(cx, retArray); + + uint32_t length; + JS::GetArrayLength(cx, rootedRetArray, &length); + if (targetIndex >= length) { + JS::SetArrayLength(cx, rootedRetArray, targetIndex + 1); + } + + JS_SetElement(cx, rootedRetArray, targetIndex, elementVal); + + targetIndex++; + } + } + + return targetIndex; +} + +// private +static uint32_t FlattenIntoArrayWithCallBack(JSContext *cx, JS::HandleObject global, + JSObject *retArray, PyObject *source, + Py_ssize_t sourceLen, uint32_t start, uint32_t depth, + JS::HandleValue callBack, JS::HandleObject thisArg) { + uint32_t targetIndex = start; + + JS::RootedObject rootedGlobal(cx, global); + JS::RootedValue sourceValue(cx, jsTypeFactorySafe(cx, source)); + JS::Rooted> jArgs(cx); + JS::RootedValue elementVal(cx); + JS::RootedValue retVal(cx); + + for (uint32_t sourceIndex = 0; sourceIndex < sourceLen; sourceIndex++) { + if (PyObject_TypeCheck(source, &JSArrayProxyType)) { + JS_GetElement(cx, ((JSArrayProxy *)source)->jsArray, sourceIndex, &elementVal); + } + else if (PyObject_TypeCheck(source, &PyList_Type)) { + elementVal.set(jsTypeFactory(cx, PyList_GetItem(source, sourceIndex))); + } + + jArgs[0].set(elementVal); + jArgs[1].setInt32(sourceIndex); + jArgs[2].set(sourceValue); + if (!JS_CallFunctionValue(cx, nullptr, callBack, jArgs, &retVal)) { + return false; + } + + PyObject *element = pyTypeFactory(cx, &rootedGlobal, &retVal)->getPyObject(); + + bool shouldFlatten; + if (depth > 0) { + shouldFlatten = PyObject_TypeCheck(element, &JSArrayProxyType) || PyObject_TypeCheck(element, &PyList_Type); + } else { + shouldFlatten = false; + } + + Py_ssize_t elementLen; + if (PyObject_TypeCheck(element, &JSArrayProxyType)) { + elementLen = JSArrayProxyMethodDefinitions::JSArrayProxy_length((JSArrayProxy *)element); + } + else if (PyObject_TypeCheck(element, &PyList_Type)) { + elementLen = PyList_GET_SIZE(element); + } + + if (shouldFlatten) { + targetIndex = FlattenIntoArrayWithCallBack(cx, global, + retArray, + element, + elementLen, + targetIndex, + depth - 1, + callBack, + thisArg + ); + } + else { + JS::RootedObject rootedRetArray(cx, retArray); + + uint32_t length; + JS::GetArrayLength(cx, rootedRetArray, &length); + + if (PyObject_TypeCheck(element, &JSArrayProxyType) || PyObject_TypeCheck(element, &PyList_Type)) { + // flatten array callBack result to depth 1 + JS::RootedValue elementIndexVal(cx); + for (uint32_t elementIndex = 0; elementIndex < elementLen; elementIndex++, targetIndex++) { + if (PyObject_TypeCheck(element, &JSArrayProxyType)) { + JS_GetElement(cx, ((JSArrayProxy *)element)->jsArray, elementIndex, &elementIndexVal); + } + else { + elementIndexVal.set(jsTypeFactory(cx, PyList_GetItem(element, elementIndex))); + } + + if (targetIndex >= length) { + JS::SetArrayLength(cx, rootedRetArray, length = targetIndex + 1); + } + + JS_SetElement(cx, rootedRetArray, targetIndex, elementIndexVal); + } + + return targetIndex; + } + else { + if (targetIndex >= length) { + JS::SetArrayLength(cx, rootedRetArray, targetIndex + 1); + } + + JS_SetElement(cx, rootedRetArray, targetIndex, retVal); + + targetIndex++; + } + } + } + + return targetIndex; +} + +static bool array_flat(JSContext *cx, unsigned argc, JS::Value *vp) { + JS::CallArgs args = JS::CallArgsFromVp(argc, vp); + + JS::RootedObject proxy(cx, JS::ToObject(cx, args.thisv())); + if (!proxy) { + return false; + } + PyObject *self = JS::GetMaybePtrFromReservedSlot(proxy, PyObjectSlot); + + Py_ssize_t sourceLen = PyList_GET_SIZE(self); + + uint32_t depthNum; + if (args.length() > 0) { + depthNum = args[0].get().toInt32(); + } + else { + depthNum = 1; + } + + JS::RootedObject *global = new JS::RootedObject(cx, JS::GetNonCCWObjectGlobal(proxy)); + + JSObject *retArray = JS::NewArrayObject(cx, sourceLen); // min end length + + FlattenIntoArray(cx, *global, retArray, self, sourceLen, 0, depthNum); + + args.rval().setObject(*retArray); + return true; +} + +static bool array_flatMap(JSContext *cx, unsigned argc, JS::Value *vp) { + JS::CallArgs args = JS::CallArgsFromVp(argc, vp); + + if (!args.requireAtLeast(cx, "flatMap", 1)) { + return false; + } + + JS::RootedObject proxy(cx, JS::ToObject(cx, args.thisv())); + if (!proxy) { + return false; + } + PyObject *self = JS::GetMaybePtrFromReservedSlot(proxy, PyObjectSlot); + + Py_ssize_t sourceLen = PyList_GET_SIZE(self); + + JS::Value callbackfn = args[0].get(); + + if (!callbackfn.isObject() || !JS::IsCallable(&callbackfn.toObject())) { + JS_ReportErrorNumberASCII(cx, js::GetErrorMessage, nullptr, JSMSG_NOT_FUNCTION, "flatMap: callback"); + return false; + } + + JS::RootedValue callBack(cx, callbackfn); + + JS::RootedObject *rootedThisArg = nullptr; + if (args.length() > 1) { + JS::Value thisArg = args[1].get(); + if (!thisArg.isObjectOrNull()) { + JS_ReportErrorNumberASCII(cx, js::GetErrorMessage, nullptr, JSMSG_NOT_OBJORNULL, "'this' argument"); + return false; + } + + // TODO support null, currently gets TypeError + rootedThisArg = new JS::RootedObject(cx, thisArg.toObjectOrNull()); + } + + JS::RootedObject *global = new JS::RootedObject(cx, JS::GetNonCCWObjectGlobal(proxy)); + + JSObject *retArray = JS::NewArrayObject(cx, sourceLen); // min end length + + FlattenIntoArrayWithCallBack(cx, *global, retArray, self, sourceLen, 0, 1, callBack, *rootedThisArg); + + delete rootedThisArg; + + args.rval().setObject(*retArray); + return true; +} + +static bool array_join(JSContext *cx, unsigned argc, JS::Value *vp) { + JS::CallArgs args = JS::CallArgsFromVp(argc, vp); + + JS::RootedObject proxy(cx, JS::ToObject(cx, args.thisv())); + if (!proxy) { + return false; + } + PyObject *self = JS::GetMaybePtrFromReservedSlot(proxy, PyObjectSlot); + + Py_ssize_t selfLength = PyList_GET_SIZE(self); + + if (selfLength == 0) { + args.rval().setString(JS_NewStringCopyZ(cx, "")); + return true; + } + + JS::RootedString *rootedSeparator; + if (args.hasDefined(0)) { + rootedSeparator = new JS::RootedString(cx, JS::ToString(cx, args[0])); + } + else { + rootedSeparator = new JS::RootedString(cx, JS_NewStringCopyZ(cx, ",")); + } + + JSString *writer = JS_NewStringCopyZ(cx, ""); + JS::RootedString rootedWriter(cx); + + for (Py_ssize_t index = 0; index < selfLength; index++) { + rootedWriter.set(writer); + if (index > 0) { + writer = JS_ConcatStrings(cx, rootedWriter, *rootedSeparator); + rootedWriter.set(writer); + } + + JS::RootedValue element(cx, jsTypeFactory(cx, PyList_GetItem(self, index))); + if (!element.isNullOrUndefined()) { + JS::RootedValue rval(cx); + + JS::RootedObject retObject(cx); + + if (!JS_ValueToObject(cx, element, &retObject)) { + delete rootedSeparator; + return false; + } + + if (!JS_CallFunctionName(cx, retObject, "toString", JS::HandleValueArray::empty(), &rval)) { + delete rootedSeparator; + return false; + } + + JS::RootedString retString(cx, rval.toString()); + writer = JS_ConcatStrings(cx, rootedWriter, retString); + } + } + + delete rootedSeparator; + + args.rval().setString(writer); + return true; +} + +static bool array_toString(JSContext *cx, unsigned argc, JS::Value *vp) { + return array_join(cx, argc, vp); +} + +static bool array_toLocaleString(JSContext *cx, unsigned argc, JS::Value *vp) { + JS::CallArgs args = JS::CallArgsFromVp(argc, vp); + + JS::RootedObject proxy(cx, JS::ToObject(cx, args.thisv())); + if (!proxy) { + return false; + } + PyObject *self = JS::GetMaybePtrFromReservedSlot(proxy, PyObjectSlot); + + Py_ssize_t selfLength = PyList_GET_SIZE(self); + + if (selfLength == 0) { + args.rval().setString(JS_NewStringCopyZ(cx, "")); + return true; + } + + JS::RootedString rootedSeparator(cx, JS_NewStringCopyZ(cx, ",")); + + JSString *writer = JS_NewStringCopyZ(cx, ""); + JS::RootedString rootedWriter(cx); + + JS::HandleValueArray jArgs(args); + + for (Py_ssize_t index = 0; index < selfLength; index++) { + rootedWriter.set(writer); + if (index > 0) { + writer = JS_ConcatStrings(cx, rootedWriter, rootedSeparator); + rootedWriter.set(writer); + } + + JS::RootedValue element(cx, jsTypeFactory(cx, PyList_GetItem(self, index))); + if (!element.isNullOrUndefined()) { + JS::RootedValue rval(cx); + + JS::RootedObject retObject(cx); + + if (!JS_ValueToObject(cx, element, &retObject)) { + return false; + } + + if (!JS_CallFunctionName(cx, retObject, "toLocaleString", jArgs, &rval)) { + return false; + } + + JS::RootedString retString(cx, rval.toString()); + writer = JS_ConcatStrings(cx, rootedWriter, retString); + } + } + + args.rval().setString(writer); + return true; +} + +static bool array_valueOf(JSContext *cx, unsigned argc, JS::Value *vp) { + JS::CallArgs args = JS::CallArgsFromVp(argc, vp); + + JS::RootedObject proxy(cx, JS::ToObject(cx, args.thisv())); + if (!proxy) { + return false; + } + PyObject *self = JS::GetMaybePtrFromReservedSlot(proxy, PyObjectSlot); + + // return ref to self + args.rval().set(jsTypeFactory(cx, self)); + return true; +} + + + +// ListIterator + + +#define ITEM_KIND_KEY 0 +#define ITEM_KIND_VALUE 1 +#define ITEM_KIND_KEY_AND_VALUE 2 + +enum { + ListIteratorSlotIteratedObject, + ListIteratorSlotNextIndex, + ListIteratorSlotItemKind, + ListIteratorSlotCount +}; + +static JSClass listIteratorClass = {"ListIterator", JSCLASS_HAS_RESERVED_SLOTS(ListIteratorSlotCount)}; + +static bool iterator_next(JSContext *cx, unsigned argc, JS::Value *vp) { + JS::CallArgs args = JS::CallArgsFromVp(argc, vp); + JS::RootedObject thisObj(cx); + if (!args.computeThis(cx, &thisObj)) return false; // TODO needed ? + + PyObject *self = JS::GetMaybePtrFromReservedSlot(thisObj, ListIteratorSlotIteratedObject); + + JS::RootedValue rootedNextIndex(cx, JS::GetReservedSlot(thisObj, ListIteratorSlotNextIndex)); + JS::RootedValue rootedItemKind(cx, JS::GetReservedSlot(thisObj, ListIteratorSlotItemKind)); + + int32_t nextIndex; + int32_t itemKind; + if (!JS::ToInt32(cx, rootedNextIndex, &nextIndex) || !JS::ToInt32(cx, rootedItemKind, &itemKind)) return false; + + JS::RootedObject result(cx, JS_NewPlainObject(cx)); + if (!result) return false; + + Py_ssize_t len = PyList_GET_SIZE(self); + + if (nextIndex >= len) { + // UnsafeSetReservedSlot(obj, ITERATOR_SLOT_TARGET, null); // TODO lose ref + JS::RootedValue done(cx, JS::BooleanValue(true)); + if (!JS_SetProperty(cx, result, "done", done)) return false; + args.rval().setObject(*result); + return result; + } + + JS::SetReservedSlot(thisObj, ListIteratorSlotNextIndex, JS::Int32Value(nextIndex + 1)); + + JS::RootedValue done(cx, JS::BooleanValue(false)); + if (!JS_SetProperty(cx, result, "done", done)) return false; + + if (itemKind == ITEM_KIND_VALUE) { + PyObject *item = PyList_GetItem(self, nextIndex); + if (!item) { + return false; + } + JS::RootedValue value(cx, jsTypeFactory(cx, item)); + if (!JS_SetProperty(cx, result, "value", value)) return false; + } + else if (itemKind == ITEM_KIND_KEY_AND_VALUE) { + JS::Rooted> items(cx); + + JS::RootedValue rootedNextIndex(cx, JS::Int32Value(nextIndex)); + items[0].set(rootedNextIndex); + + PyObject *item = PyList_GetItem(self, nextIndex); + if (!item) { + return false; + } + JS::RootedValue value(cx, jsTypeFactory(cx, item)); + items[1].set(value); + + JS::RootedValue pair(cx); + JSObject *array = JS::NewArrayObject(cx, items); + pair.setObject(*array); + if (!JS_SetProperty(cx, result, "value", pair)) return false; + } + else { // itemKind == ITEM_KIND_KEY + JS::RootedValue value(cx, JS::Int32Value(nextIndex)); + if (!JS_SetProperty(cx, result, "value", value)) return false; + } + + args.rval().setObject(*result); + return true; +} + +static JSFunctionSpec list_iterator_methods[] = { + JS_FN("next", iterator_next, 0, JSPROP_ENUMERATE), + JS_FS_END +}; + +static bool ListIteratorConstructor(JSContext *cx, unsigned argc, JS::Value *vp) { + JS::CallArgs args = JS::CallArgsFromVp(argc, vp); + + if (!args.isConstructing()) { + JS_ReportErrorASCII(cx, "You must call this constructor with 'new'"); + return false; + } + + JS::RootedObject thisObj(cx, JS_NewObjectForConstructor(cx, &listIteratorClass, args)); + if (!thisObj) { + return false; + } + + args.rval().setObject(*thisObj); + return true; +} + +static bool DefineListIterator(JSContext *cx, JS::HandleObject global) { + JS::RootedObject iteratorPrototype(cx); + if (!JS_GetClassPrototype(cx, JSProto_Iterator, &iteratorPrototype)) { + return false; + } + + JS::RootedObject protoObj(cx, + JS_InitClass(cx, global, + nullptr, iteratorPrototype, + "ListIterator", + ListIteratorConstructor, 0, + nullptr, list_iterator_methods, + nullptr, nullptr) + ); + + return protoObj; // != nullptr +} + +// private util +static bool array_iterator_func(JSContext *cx, unsigned argc, JS::Value *vp, int itemKind) { + JS::CallArgs args = JS::CallArgsFromVp(argc, vp); + + JS::RootedObject proxy(cx, JS::ToObject(cx, args.thisv())); + if (!proxy) { + return false; + } + PyObject *self = JS::GetMaybePtrFromReservedSlot(proxy, PyObjectSlot); + + JS::RootedObject global(cx, JS::GetNonCCWObjectGlobal(proxy)); + + JS::RootedValue constructor_val(cx); + if (!JS_GetProperty(cx, global, "ListIterator", &constructor_val)) return false; + if (!constructor_val.isObject()) { + if (!DefineListIterator(cx, global)) { + return false; + } + + if (!JS_GetProperty(cx, global, "ListIterator", &constructor_val)) return false; + if (!constructor_val.isObject()) { + JS_ReportErrorASCII(cx, "ListIterator is not a constructor"); + return false; + } + } + JS::RootedObject constructor(cx, &constructor_val.toObject()); + + JS::RootedObject obj(cx); + if (!JS::Construct(cx, constructor_val, JS::HandleValueArray::empty(), &obj)) return false; + if (!obj) return false; + + JS::SetReservedSlot(obj, ListIteratorSlotIteratedObject, JS::PrivateValue((void *)self)); + JS::SetReservedSlot(obj, ListIteratorSlotNextIndex, JS::Int32Value(0)); + JS::SetReservedSlot(obj, ListIteratorSlotItemKind, JS::Int32Value(itemKind)); + + args.rval().setObject(*obj); + return true; +} + +static bool array_entries(JSContext *cx, unsigned argc, JS::Value *vp) { + return array_iterator_func(cx, argc, vp, ITEM_KIND_KEY_AND_VALUE); +} + +static bool array_keys(JSContext *cx, unsigned argc, JS::Value *vp) { + return array_iterator_func(cx, argc, vp, ITEM_KIND_KEY); +} + +static bool array_values(JSContext *cx, unsigned argc, JS::Value *vp) { + return array_iterator_func(cx, argc, vp, ITEM_KIND_VALUE); +} + + +static JSMethodDef array_methods[] = { + {"reverse", array_reverse, 0}, + {"pop", array_pop, 0}, + {"push", array_push, 1}, + {"shift", array_shift, 0}, + {"unshift", array_unshift, 1}, + {"concat", array_concat, 1}, + {"slice", array_slice, 2}, + {"indexOf", array_indexOf, 1}, + {"lastIndexOf", array_lastIndexOf, 1}, + {"splice", array_splice, 2}, + {"sort", array_sort, 1}, + {"fill", array_fill, 3}, + {"copyWithin", array_copyWithin, 3}, + {"includes", array_includes, 1}, + {"forEach", array_forEach, 1}, + {"map", array_map, 1}, + {"filter", array_filter, 1}, + {"reduce", array_reduce, 1}, + {"reduceRight", array_reduceRight, 1}, + {"some", array_some, 1}, + {"every", array_every, 1}, + {"find", array_find, 1}, + {"findIndex", array_findIndex, 1}, + {"flat", array_flat, 1}, + {"flatMap", array_flatMap, 1}, + {"join", array_join, 1}, + {"toString", array_toString, 0}, + {"toLocaleString", array_toLocaleString, 0}, + {"valueOf", array_valueOf, 0}, + {"entries", array_entries, 0}, + {"keys", array_keys, 0}, + {"values", array_values, 0}, + {NULL, NULL, 0} +}; bool PyListProxyHandler::getOwnPropertyDescriptor( @@ -989,7 +2156,7 @@ bool PyListProxyHandler::getOwnPropertyDescriptor( } } - // item + // item Py_ssize_t index; PyObject *item; if (idToIndex(cx, id, &index) && (item = PyList_GetItem(pyObject, index))) { @@ -1010,6 +2177,8 @@ void PyListProxyHandler::finalize(JS::GCContext *gcx, JSObject *proxy) const { // PyObject *self = JS::GetMaybePtrFromReservedSlot(proxy, PyObjectSlot); // printf("finalize self=%p\n", self); // Py_DECREF(self); + + // JS::SetReservedSlot(proxy, PyObjectSlot, nullptr)); } bool PyListProxyHandler::defineProperty( @@ -1074,9 +2243,4 @@ bool PyListProxyHandler::isArray(JSContext *cx, JS::HandleObject proxy, JS::IsAr bool PyListProxyHandler::getBuiltinClass(JSContext *cx, JS::Handle obj, js::ESClass *cls) const { *cls = js::ESClass::Array; return true; -} - -const char *PyListProxyHandler::className(JSContext *cx, JS::HandleObject proxy) const { - // TODO untested - return "Array"; } \ No newline at end of file diff --git a/src/jsTypeFactory.cc b/src/jsTypeFactory.cc index 813a92fd..3cbf8985 100644 --- a/src/jsTypeFactory.cc +++ b/src/jsTypeFactory.cc @@ -173,7 +173,7 @@ JS::Value jsTypeFactory(JSContext *cx, PyObject *object) { if (PyList_Check(object)) { proxy = js::NewProxyObject(cx, new PyListProxyHandler(object), v, NULL); JS::SetReservedSlot(proxy, PyObjectSlot, JS::PrivateValue(object)); - //Py_INCREF(object); TODO + // Py_INCREF(object); TODO } else { proxy = js::NewProxyObject(cx, new PyProxyHandler(object), v, NULL); } @@ -200,25 +200,6 @@ JS::Value jsTypeFactory(JSContext *cx, PyObject *object) { return returnType; } -JS::Value jsTypeFactoryCopy(JSContext *cx, PyObject *object) { - JS::RootedValue returnType(cx); - if (PyList_Check(object)) { - Py_ssize_t listSize = PyList_Size(object); - JSObject *array = JS::NewArrayObject(cx, listSize); - JS::RootedObject arrayObj(cx, array); - for (Py_ssize_t index = 0; index < listSize; index++) { - JS::RootedValue jsValue(cx, jsTypeFactorySafe(cx, PyList_GetItem(object, index))); - JS_SetElement(cx, arrayObj, index, jsValue); - } - returnType.setObject(*array); - } - else { - returnType.setUndefined(); - } - - return returnType; -} - JS::Value jsTypeFactorySafe(JSContext *cx, PyObject *object) { JS::Value v = jsTypeFactory(cx, object); if (PyErr_Occurred()) { diff --git a/tests/python/test_arrays.py b/tests/python/test_arrays.py index ee41ddc5..5635f973 100644 --- a/tests/python/test_arrays.py +++ b/tests/python/test_arrays.py @@ -81,7 +81,13 @@ def test_join_no_arg(): items = [1,2,3] result = [None] pm.eval("(result, arr) => {result[0] = arr.join()}")(result, items) - assert result[0] == '1,2,3' + assert result[0] == '1,2,3' + +def test_join_empty_array(): + items = [] + result = [None] + pm.eval("(result, arr) => {result[0] = arr.join()}")(result, items) + assert result[0] == '' def test_join_no_arg_diff_types(): items = [1,False,"END"] @@ -111,7 +117,13 @@ def test_join_null(): items = [pm.null,2,3] result = [None] pm.eval("(result, arr) => {result[0] = arr.join()}")(result, items) - assert result[0] == ',2,3' + assert result[0] == ',2,3' + +def test_join_utf8(): + prices = ["ï¿¥7", 500, 8123, 12] + result = [None] + pm.eval("(result, arr) => {result[0] = arr.join()}")(result, prices) + assert result[0] == 'ï¿¥7,500,8123,12' #toString def test_toString(): @@ -608,7 +620,6 @@ def test_includes_too_few_args(): assert str(type(e)) == "" assert str(e).__contains__("TypeError: includes: At least 1 argument required, but only 0 passed") - #sort def test_sort_empty(): items = [] @@ -669,6 +680,13 @@ def test_sort_with_js_func(): assert result[0] == items assert items == ['Four', 'One', 'Three'] +#def test_sort_numbers_tricky(): +# items = [1, 30, 4, 21, 100000] +# result = [None] +# pm.eval("(result, arr) => {result[0] = arr.sort()}")(result, items) +# assert result[0] is items +# assert items == [1, 100000, 21, 30, 4] + def test_sort_with_js_func_wrong_data_type(): items = [4,2,6,7] myFunc = pm.eval("((a, b) => a.toLocaleUpperCase() < b.toLocaleUpperCase() ? -1 : 1)") @@ -677,7 +695,7 @@ def test_sort_with_js_func_wrong_data_type(): assert (False) except Exception as e: assert str(type(e)) == "" - assert str(e).__contains__("TypeError: a.toLocaleUpperCase is not a function") + assert str(e).__contains__("TypeError: a.toLocaleUpperCase is not a function") #forEach def test_forEach(): @@ -707,6 +725,28 @@ def test_forEach_check_this_arg(): pm.eval("(result, arr) => {class Counter { constructor() { this.count = 0;} add(array) { array.forEach(function countEntry(entry) { ++this.count; }, this);}} const obj = new Counter(); obj.add(arr); result[0] = obj.count;}")(result, items) assert result == [3] +def test_forEach_check_this_arg_wrong_type(): + items = ['Four', 'Three', 'One'] + result = [None] + a = 9 + try: + pm.eval("(result, arr, a) => {class Counter { constructor() { this.count = 0;} add(array) { array.forEach(function countEntry(entry) { ++this.count; }, a);}} const obj = new Counter(); obj.add(arr); result[0] = obj.count;}")(result, items, a) + assert (False) + except Exception as e: + assert str(type(e)) == "" + assert str(e).__contains__("TypeError: 'this' argument is not an object or null") + +# TODO should not pass +def test_forEach_check_this_arg_null(): + items = ['Four', 'Three', 'One'] + result = [None] + try: + pm.eval("(result, arr) => {class Counter { constructor() { this.count = 0;} add(array) { array.forEach(function countEntry(entry) { ++this.count; }, null);}} const obj = new Counter(); obj.add(arr); result[0] = obj.count;}")(result, items) + assert (False) + except Exception as e: + assert str(type(e)) == "" + assert str(e).__contains__("TypeError: this is null") + def test_forEach_too_few_args(): items = [4,2,6,7] try: @@ -733,17 +773,27 @@ def test_map_too_few_args(): assert str(type(e)) == "" assert str(e).__contains__("TypeError: map: At least 1 argument required, but only 0 passed") +def test_map_arg_wrong_type(): + items = [4,2,6,7] + try: + pm.eval("(arr) => {arr.map(8)}")(items) + assert (False) + except Exception as e: + assert str(type(e)) == "" + assert str(e).__contains__("TypeError: map: callback is not a function") + def test_map_check_index(): items = ['Four', 'Three', 'One'] result = [''] pm.eval("(result, arr) => {arr.map((element, index) => result[0] += index)}")(result, items) assert result == ['012'] -def test_map_check_array(): +def test_map_check_array_mutation(): items = ['Four', 'Three', 'One'] result = [''] - pm.eval("(result, arr) => {arr.map((element, index, array) => result[0] = array)}")(result, items) - assert result == [items] + pm.eval("(result, arr) => {arr.map((element, index, array) => {array[0] = 'Ten'; result[0] = array})}")(result, items) + assert result[0] == ['Ten', 'Three', 'One'] + assert items == ['Ten', 'Three', 'One'] #filter def test_filter(): @@ -762,7 +812,7 @@ def test_filter_too_few_args(): assert str(type(e)) == "" assert str(e).__contains__("TypeError: filter: At least 1 argument required, but only 0 passed") -#reduce index, array param, too few args same impl as previous few for all below +#reduce index, array param, wrong arg type, too few args same impl as previous few for all below def test_reduce(): items = [1,2,3,4,5] result = [None] @@ -770,6 +820,15 @@ def test_reduce(): assert items == [1,2,3,4,5] assert result[0] == 15 +def test_reduce_empty_array_no_accumulator(): + items = [] + try: + pm.eval("(arr) => {arr.reduce((accumulator, currentValue) => accumulator + currentValue)}")(items) + assert (False) + except Exception as e: + assert str(type(e)) == "" + assert str(e).__contains__("TypeError: reduce of empty array with no initial value") + def test_reduce_float(): items = [1.9, 4.6, 9.3, 16.5] result = [None] @@ -794,7 +853,19 @@ def test_reduce_no_initial_value(): result = [None] pm.eval("(result, arr) => {result[0] = arr.reduce((accumulator, currentValue) => accumulator + currentValue)}")(result, items) assert items == [1,2,3,4,5] - assert result[0] == 15 + assert result[0] == 15 + +def test_reduce_length_one_with_initial_value(): + items = [1] + result = [None] + pm.eval("(result, arr) => {result[0] = arr.reduce((accumulator, currentValue) => accumulator + currentValue, 2)}")(result, items) + assert result[0] == 3 + +def test_reduce_length_one_no_initial_value(): + items = [1] + result = [None] + pm.eval("(result, arr) => {result[0] = arr.reduce((accumulator, currentValue) => accumulator + currentValue)}")(result, items) + assert result[0] == 1 def test_reduce_list_meaningless(): items = [['Hi', 'There']] @@ -819,7 +890,13 @@ def test_reduceRight(): items = [0,1,2,3,4] result = [None] pm.eval("(result, arr) => {result[0] = arr.reduceRight((accumulator, currentValue, index, array) => accumulator + currentValue)}")(result, items) - assert result[0] == 10 + assert result[0] == 10 + +def test_reduceRight_with_initial_value(): + items = [0,1,2,3,4] + result = [None] + pm.eval("(result, arr) => {result[0] = arr.reduceRight((accumulator, currentValue, index, array) => accumulator + currentValue, 5)}")(result, items) + assert result[0] == 15 def test_reduceRight_float(): items = [1.9, 4.6, 9.3, 16.5] @@ -908,6 +985,13 @@ def test_flat(): assert items == [0, 1, 2, [3, 4]] assert result[0] == [0, 1, 2, 3, 4] +def test_flat_with_js_array(): + items = [0, 1, 2, [3, 4]] + result = [0] + pm.eval("(result, arr) => {arr[1] = [10,11]; result[0] = arr.flat()}")(result, items) + assert items == [0, [10, 11], 2, [3, 4]] + assert result[0] == [0, 10, 11, 2, 3, 4] + def test_flat_depth_zero(): items = [0, 1, [2, [3, [4, 5]]]] result = [0] @@ -921,19 +1005,18 @@ def test_flat_depth_one(): assert items == [0, 1, [2, [3, [4, 5]]]] assert result[0] == [0, 1, 2, [3, [4, 5]]] -########## TODO these two fail as tests but work in the interactive console ########### -#def test_flat_depth_two(): -# items = [0, 1, [2, [3, [4, 5]]]] -# result = [0] -# pm.eval("(result, arr) => {result[0] = arr.flat(2)}")(result, items) -# assert items == [0, 1, [2, [3, [4, 5]]]] -# assert result[0] == [0, 1, 2, 3, [4, 5]] +def test_flat_depth_two(): + items = [0, 1, [2, [3, [4, 5]]]] + result = [0] + pm.eval("(result, arr) => {result[0] = arr.flat(2)}")(result, items) + assert items == [0, 1, [2, [3, [4, 5]]]] + assert result[0] == [0, 1, 2, 3, [4, 5]] -#def test_flat_depth_infinite(): -# items = [0, 1, [2, [3, [4, 5]]]] -# result = [0] -# pm.eval("(result, arr) => {result[0] = arr.flat(Infinity)}")(result, items) -# assert result[0] == [0, 1, 2, 3, 4, 5] +def test_flat_depth_large(): + items = [0, 1, [2, [3, [4, 5]]]] + result = [0] + pm.eval("(result, arr) => {result[0] = arr.flat(10)}")(result, items) + assert result[0] == [0, 1, 2, 3, 4, 5] #flatMap def test_flatMap(): @@ -943,6 +1026,27 @@ def test_flatMap(): assert items == [1,2,1] assert result[0] == [1,2,2,1] +def test_flatMap_with_js_array(): + items = [1,2,2,1] + result = [0] + pm.eval("(result, arr) => {arr[1] = [10,11]; result[0] = arr.flatMap((num) => (num === 2 ? [2, 2] : 1))}")(result, items) + assert items == [1, [10, 11], 2, 1] + assert result[0] == [1, 1, 2, 2, 1] + +def test_flatMap_no_replace(): + items = [1,2,[4,5]] + result = [0] + pm.eval("(result, arr) => {result[0] = arr.flatMap((num) => (num === 2 ? [2, 2] : 1))}")(result, items) + assert items == [1, 2, [4, 5]] + assert result[0] == [1, 2, 2, 1] + +def test_flatMap_no_replace_depth_one(): + items = [1,2,[4,5]] + result = [0] + pm.eval("(result, arr) => {result[0] = arr.flatMap((num) => (num === 2 ? [2, [2, 2]] : 1))}")(result, items) + assert items == [1, 2, [4, 5]] + assert result[0] == [1, 2, [2, 2], 1] + def test_flatMap_equivalence(): items = [1, 2, 1] result = [0] @@ -1018,17 +1122,17 @@ def test_entries_next_next_undefined(): items = ['a'] result = [0] pm.eval("(result, arr) => {result[0] = arr.entries(); result[0].next(); result[0] = result[0].next().value}")(result, items) - assert result[0] == None + assert result[0] == None #keys -def test_keys(): +def test_keys_iterator(): items = ['a', 'b', 'c'] result = [7,8,9] pm.eval("(result, arr) => { index = 0; iterator = arr.keys(); for (const key of iterator) { result[index] = key; index++;} }")(result, items) - assert result == ['0','1','2'] + assert result == [0,1,2] #values -def test_values(): +def test_values_iterator(): items = ['a', 'b', 'c'] result = [7,8,9] pm.eval("(result, arr) => { index = 0; iterator = arr.values(); for (const key of iterator) { result[index] = key; index++;} }")(result, items) @@ -1074,5 +1178,5 @@ def test_iterator_last_next(): items = [1,2] result = [0] pm.eval("(result, arr) => { let iterator = arr[Symbol.iterator](); iterator.next(); iterator.next(); result[0] = iterator.next()}")(result, items) - assert result[0].value == None + assert result[0].value == None assert result[0].done == True \ No newline at end of file From 694c1e60003a7ede41aa40a84574b83349a6d6ea Mon Sep 17 00:00:00 2001 From: Philippe Laporte Date: Thu, 11 Jan 2024 13:59:53 -0500 Subject: [PATCH 17/58] improved docs --- README.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index af96b1f9..d69a1ab1 100644 --- a/README.md +++ b/README.md @@ -55,8 +55,9 @@ js_eval("console.log")('hello, world') - Python host environment supplies basic subsets of NodeJS's fs, path, process, etc, modules; as-needed by dcp-client (other project?) - [done] Python TypedArrays coerce to JS TypeArrays - [done] JS TypedArrays coerce to Python TypeArrays -- [done] Python List coerce to JS Arrays -- [done] JS Arrays coerce to Python Lists +- [done] JS Arrays coerce to Python Lists, providing all List methods implemented on Arrays +- [done] Python List coerce to JS Arrays, providing all Array methods implemented on Lists + ## Build Instructions From 3ddc130640de0ce68890decfe5cde28c6744f9e8 Mon Sep 17 00:00:00 2001 From: Philippe Laporte Date: Thu, 11 Jan 2024 14:01:52 -0500 Subject: [PATCH 18/58] cleanup --- README.md | 1 - include/jsTypeFactory.hh | 1 - 2 files changed, 2 deletions(-) diff --git a/README.md b/README.md index d69a1ab1..1fa5dbfd 100644 --- a/README.md +++ b/README.md @@ -58,7 +58,6 @@ js_eval("console.log")('hello, world') - [done] JS Arrays coerce to Python Lists, providing all List methods implemented on Arrays - [done] Python List coerce to JS Arrays, providing all Array methods implemented on Lists - ## Build Instructions Read this if you want to build a local version. diff --git a/include/jsTypeFactory.hh b/include/jsTypeFactory.hh index 1ae6b131..6ec68412 100644 --- a/include/jsTypeFactory.hh +++ b/include/jsTypeFactory.hh @@ -37,7 +37,6 @@ size_t UCS4ToUTF16(const uint32_t *chars, size_t length, uint16_t *outStr); * @return JS::Value - A JS::Value corresponding to the PyType */ JS::Value jsTypeFactory(JSContext *cx, PyObject *object); - /** * @brief same to jsTypeFactory, but it's guaranteed that no error would be set on the Python error stack, instead * return JS `null` on error, and output a warning in Python-land From c6b82eee72fb7a053d92577ae7264fbff40ed21e Mon Sep 17 00:00:00 2001 From: Philippe Laporte Date: Thu, 11 Jan 2024 14:03:10 -0500 Subject: [PATCH 19/58] improved naming --- tests/python/test_lists.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/python/test_lists.py b/tests/python/test_lists.py index 01d01b83..0942e8b2 100644 --- a/tests/python/test_lists.py +++ b/tests/python/test_lists.py @@ -729,7 +729,7 @@ def myFunc(e): assert str(type(e)) == "" assert str(e) == "object of type 'float' has no len()" -def test_sort_with_function_two_arg_and_reverse_false(): +def test_sort_with_function_two_args_and_reverse_false(): def myFunc(e,f): return len(e) - len(f) a = pm.eval("(['Ford', 'Mitsubishi', 'BMW', 'VW'])") From 519beab4ee9647f60bc19ab01a5255fa06374b66 Mon Sep 17 00:00:00 2001 From: philippedistributive <151072087+philippedistributive@users.noreply.github.com> Date: Thu, 11 Jan 2024 14:05:27 -0500 Subject: [PATCH 20/58] Update tests/python/test_arrays.py Co-authored-by: Caleb Aikens --- tests/python/test_arrays.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/python/test_arrays.py b/tests/python/test_arrays.py index 5635f973..963b2447 100644 --- a/tests/python/test_arrays.py +++ b/tests/python/test_arrays.py @@ -174,7 +174,7 @@ def test_shift_empty(): result = [None] pm.eval("(result, arr) => {result[0] = arr.shift()}")(result, items) assert items == [] - assert result[0] == None + assert result[0] is None #unshift def test_unshift_zero_arg(): From 79c830e6ff87b51b6d9e91f5a9cf8b20df95ef02 Mon Sep 17 00:00:00 2001 From: philippedistributive <151072087+philippedistributive@users.noreply.github.com> Date: Thu, 11 Jan 2024 14:05:58 -0500 Subject: [PATCH 21/58] Update tests/python/test_arrays.py Co-authored-by: Caleb Aikens --- tests/python/test_arrays.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/python/test_arrays.py b/tests/python/test_arrays.py index 963b2447..9c1bdd28 100644 --- a/tests/python/test_arrays.py +++ b/tests/python/test_arrays.py @@ -67,7 +67,7 @@ def test_pop_empty(): result = [None] pm.eval("(result, arr) => {result[0] = arr.pop()}")(result, items) assert items == [] - assert result[0] == None + assert result[0] is None def test_pop_ignore_extra_args(): items = [1,2,3] From c541f5a5d4b73ff2b93ccec56b0761faf7825fc9 Mon Sep 17 00:00:00 2001 From: philippedistributive <151072087+philippedistributive@users.noreply.github.com> Date: Thu, 11 Jan 2024 14:06:40 -0500 Subject: [PATCH 22/58] Update tests/python/test_arrays.py Co-authored-by: Caleb Aikens --- tests/python/test_arrays.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/python/test_arrays.py b/tests/python/test_arrays.py index 9c1bdd28..c6afd02e 100644 --- a/tests/python/test_arrays.py +++ b/tests/python/test_arrays.py @@ -291,7 +291,7 @@ def test_slice_start_negative(): #indexOf def test_indexOf(): - items = [1,2,3] + items = [1,2,3,1] result = [None] pm.eval("(result, arr) => {result[0] = arr.indexOf(1)}")(result, items) assert result[0] == 0 From b05ae54df04498d1a4da702420f18798a74f266d Mon Sep 17 00:00:00 2001 From: philippedistributive <151072087+philippedistributive@users.noreply.github.com> Date: Thu, 11 Jan 2024 14:07:19 -0500 Subject: [PATCH 23/58] Update tests/python/test_arrays.py Co-authored-by: Caleb Aikens --- tests/python/test_arrays.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/python/test_arrays.py b/tests/python/test_arrays.py index c6afd02e..5bcaa552 100644 --- a/tests/python/test_arrays.py +++ b/tests/python/test_arrays.py @@ -297,10 +297,10 @@ def test_indexOf(): assert result[0] == 0 def test_indexOf_with_start(): - items = [1,2,3] + items = [1,2,3,4,3] result = [None] - pm.eval("(result, arr) => {result[0] = arr.indexOf(3, 1)}")(result, items) - assert result[0] == 2 + pm.eval("(result, arr) => {result[0] = arr.indexOf(3, 3)}")(result, items) + assert result[0] == 4 def test_indexOf_with_negative_start(): items = [1,2,3] From 5d1bb31ca246816412ac916f98820993ab8f4c44 Mon Sep 17 00:00:00 2001 From: philippedistributive <151072087+philippedistributive@users.noreply.github.com> Date: Thu, 11 Jan 2024 14:07:49 -0500 Subject: [PATCH 24/58] Update tests/python/test_arrays.py Co-authored-by: Caleb Aikens --- tests/python/test_arrays.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/python/test_arrays.py b/tests/python/test_arrays.py index 5bcaa552..5ea1a147 100644 --- a/tests/python/test_arrays.py +++ b/tests/python/test_arrays.py @@ -340,7 +340,7 @@ def test_lastIndexOf_dup(): assert result[0] == 3 def test_lastIndexOf_with_from_index(): - items = [1,2,3] + items = [1,2,3,1] result = [None] pm.eval("(result, arr) => {result[0] = arr.lastIndexOf(1, 2)}")(result, items) assert result[0] == 0 From ae2ac52e34e98ae86b2e8051700ab81db37693c3 Mon Sep 17 00:00:00 2001 From: philippedistributive <151072087+philippedistributive@users.noreply.github.com> Date: Thu, 11 Jan 2024 14:26:12 -0500 Subject: [PATCH 25/58] Update tests/python/test_arrays.py Co-authored-by: Caleb Aikens --- tests/python/test_arrays.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/tests/python/test_arrays.py b/tests/python/test_arrays.py index 5ea1a147..22f156af 100644 --- a/tests/python/test_arrays.py +++ b/tests/python/test_arrays.py @@ -461,9 +461,7 @@ def test_fill_returns_ref_to_self(): result = [None] pm.eval("(result, arr) => {result[0] = arr.fill(8)}")(result, items) assert items == [8,8,8] - assert result[0] == [8,8,8] - result[0][0] = 9 - assert items == [9,8,8] + assert items is result[0] def test_fill_other_type(): items = [1,2,3] From 558b39bffedcb869e90e67b63c0179d09a146c3f Mon Sep 17 00:00:00 2001 From: philippedistributive <151072087+philippedistributive@users.noreply.github.com> Date: Thu, 11 Jan 2024 14:27:24 -0500 Subject: [PATCH 26/58] Update tests/python/test_arrays.py Co-authored-by: Caleb Aikens --- tests/python/test_arrays.py | 8 -------- 1 file changed, 8 deletions(-) diff --git a/tests/python/test_arrays.py b/tests/python/test_arrays.py index 22f156af..ced368ff 100644 --- a/tests/python/test_arrays.py +++ b/tests/python/test_arrays.py @@ -506,14 +506,6 @@ def test_fill_with_stop_too_high(): assert items == [1,8,8] #copyWithin -def test_copyWithin_returns_ref_to_self(): - items = [1,2,3] - result = [None] - pm.eval("(result, arr) => {result[0] = arr.copyWithin(0,1)}")(result, items) - assert items == [2,3,3] - assert result[0] == [2,3,3] - result[0][0] = 9 - assert items == [9,3,3] def test_copyWithin(): items = [1,2,3] From b78d156fb9ff67d895a7dac8c054d3e2621d5540 Mon Sep 17 00:00:00 2001 From: philippedistributive <151072087+philippedistributive@users.noreply.github.com> Date: Thu, 11 Jan 2024 14:27:50 -0500 Subject: [PATCH 27/58] Update tests/python/test_arrays.py Co-authored-by: Caleb Aikens --- tests/python/test_arrays.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/tests/python/test_arrays.py b/tests/python/test_arrays.py index ced368ff..85386eaf 100644 --- a/tests/python/test_arrays.py +++ b/tests/python/test_arrays.py @@ -512,9 +512,7 @@ def test_copyWithin(): result = [None] pm.eval("(result, arr) => {result[0] = arr.copyWithin(0,1)}")(result, items) assert items == [2,3,3] - assert result[0] == [2,3,3] - result[0][0] = 9 - assert items == [9,3,3] + assert items is result[0] def test_copyWithin_no_args(): items = [1,2,3] From 05610a96e74e57f7d43e32a87e8e80f0e1754de6 Mon Sep 17 00:00:00 2001 From: philippedistributive <151072087+philippedistributive@users.noreply.github.com> Date: Thu, 11 Jan 2024 14:30:17 -0500 Subject: [PATCH 28/58] Update tests/python/test_arrays.py Co-authored-by: Caleb Aikens --- tests/python/test_arrays.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/python/test_arrays.py b/tests/python/test_arrays.py index 85386eaf..c51cdea2 100644 --- a/tests/python/test_arrays.py +++ b/tests/python/test_arrays.py @@ -553,8 +553,8 @@ def test_copyWithin_target_and_start(): def test_copyWithin_target_and_start_too_large(): items = [1,2,3] result = [None] - pm.eval("(result, arr) => {result[0] = arr.copyWithin(1, 2)}")(result, items) - assert items == [1,3,3] + pm.eval("(result, arr) => {result[0] = arr.copyWithin(1, 10)}")(result, items) + assert items == [1,2,3] def test_copyWithin_target_and_negative_start(): items = [1,2,3] From ff80136726019d722585b1499834c37ed458492e Mon Sep 17 00:00:00 2001 From: philippedistributive <151072087+philippedistributive@users.noreply.github.com> Date: Thu, 11 Jan 2024 14:33:47 -0500 Subject: [PATCH 29/58] Update tests/python/test_arrays.py Co-authored-by: Caleb Aikens --- tests/python/test_arrays.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/python/test_arrays.py b/tests/python/test_arrays.py index c51cdea2..bf0cf310 100644 --- a/tests/python/test_arrays.py +++ b/tests/python/test_arrays.py @@ -705,7 +705,8 @@ def test_forEach_check_array(): items = ['Four', 'Three', 'One'] result = [''] pm.eval("(result, arr) => {arr.forEach((element, index, array) => result[0] = array)}")(result, items) - assert result == [items] + assert result == [items] + assert result[0] is items def test_forEach_check_this_arg(): items = ['Four', 'Three', 'One'] From 5444f7d5a269bb6548f64132f90d5be974a7b328 Mon Sep 17 00:00:00 2001 From: philippedistributive <151072087+philippedistributive@users.noreply.github.com> Date: Thu, 11 Jan 2024 14:34:29 -0500 Subject: [PATCH 30/58] Update tests/python/test_arrays.py Co-authored-by: Caleb Aikens --- tests/python/test_arrays.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tests/python/test_arrays.py b/tests/python/test_arrays.py index bf0cf310..37fab67b 100644 --- a/tests/python/test_arrays.py +++ b/tests/python/test_arrays.py @@ -1125,8 +1125,7 @@ def test_values_iterator(): items = ['a', 'b', 'c'] result = [7,8,9] pm.eval("(result, arr) => { index = 0; iterator = arr.values(); for (const key of iterator) { result[index] = key; index++;} }")(result, items) - items[0] = 'd' - assert result == ['a', 'b', 'c'] + assert result is not items #constructor property def test_constructor_creates_array(): From 70227591475cd35a6986de38ca49f24adb66c8f5 Mon Sep 17 00:00:00 2001 From: philippedistributive <151072087+philippedistributive@users.noreply.github.com> Date: Thu, 11 Jan 2024 14:34:48 -0500 Subject: [PATCH 31/58] Update tests/python/test_arrays.py Co-authored-by: Caleb Aikens --- tests/python/test_arrays.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/python/test_arrays.py b/tests/python/test_arrays.py index 37fab67b..a348322f 100644 --- a/tests/python/test_arrays.py +++ b/tests/python/test_arrays.py @@ -1124,7 +1124,7 @@ def test_keys_iterator(): def test_values_iterator(): items = ['a', 'b', 'c'] result = [7,8,9] - pm.eval("(result, arr) => { index = 0; iterator = arr.values(); for (const key of iterator) { result[index] = key; index++;} }")(result, items) + pm.eval("(result, arr) => { index = 0; iterator = arr.values(); for (const value of iterator) { result[index] = value; index++;} }")(result, items) assert result is not items #constructor property From 72a5025827bb7edcaab5e76ba7bf84497cf4ceff Mon Sep 17 00:00:00 2001 From: philippedistributive <151072087+philippedistributive@users.noreply.github.com> Date: Thu, 11 Jan 2024 14:35:23 -0500 Subject: [PATCH 32/58] Update tests/python/test_arrays.py Co-authored-by: Caleb Aikens --- tests/python/test_arrays.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/tests/python/test_arrays.py b/tests/python/test_arrays.py index a348322f..f5572f93 100644 --- a/tests/python/test_arrays.py +++ b/tests/python/test_arrays.py @@ -1049,10 +1049,8 @@ def test_valueOf(): items = [1, 2, 1] result = [0] pm.eval("(result, arr) => {result[0] = arr.valueOf()}")(result, items) - assert result[0] == [1,2,1] - result[0][1] = 5 - assert result[0] == [1,5,1] - assert items == [1,5,1] + assert items == [1,2,1] + assert result[0] is items #toLocaleString def test_toLocaleString(): From 58c736de821558d57c35a4fd1665c0d655336000 Mon Sep 17 00:00:00 2001 From: philippedistributive <151072087+philippedistributive@users.noreply.github.com> Date: Thu, 11 Jan 2024 14:35:55 -0500 Subject: [PATCH 33/58] Update tests/python/test_arrays.py Co-authored-by: Caleb Aikens --- tests/python/test_arrays.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/python/test_arrays.py b/tests/python/test_arrays.py index f5572f93..efcd2ea0 100644 --- a/tests/python/test_arrays.py +++ b/tests/python/test_arrays.py @@ -944,7 +944,7 @@ def test_find_not_found(): items = [5, 12, 8, 130, 44] result = [0] pm.eval("(result, arr) => {result[0] = arr.find((element) => element > 1000)}")(result, items) - assert result[0] == None + assert result[0] is None #findIndex def test_findIndex_found_once(): From 5a6a033d63ef3f25ec8546d509fcd9b6bd4725cc Mon Sep 17 00:00:00 2001 From: philippedistributive <151072087+philippedistributive@users.noreply.github.com> Date: Thu, 11 Jan 2024 14:37:10 -0500 Subject: [PATCH 34/58] Update tests/python/test_arrays.py Co-authored-by: Caleb Aikens --- tests/python/test_arrays.py | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/tests/python/test_arrays.py b/tests/python/test_arrays.py index efcd2ea0..7a591fa2 100644 --- a/tests/python/test_arrays.py +++ b/tests/python/test_arrays.py @@ -909,8 +909,20 @@ def test_some_false(): def test_some_truthy_conversion(): result = [None] - pm.eval('(result) => {const TRUTHY_VALUES = [true, "true", 1]; function getBoolean(value) { if (typeof value === "string") { value = value.toLowerCase().trim(); } return TRUTHY_VALUES.some((t) => t === value);} result[0] = getBoolean(1);}')(result) - assert result[0] == True + pm.eval( + """ + (result) => { + const TRUTHY_VALUES = [true, "true", 1, {}]; + function getBoolean(value) { + if (typeof value === "string") { + value = value.toLowerCase().trim(); + } + return TRUTHY_VALUES.some((t) => t === value); + } + result[0] = getBoolean(1); + } + """)(result) + assert result[0] == True #every def test_every_true(): From e088b356143264ea850189f19fc0603b73adcfd2 Mon Sep 17 00:00:00 2001 From: philippedistributive <151072087+philippedistributive@users.noreply.github.com> Date: Thu, 11 Jan 2024 14:37:52 -0500 Subject: [PATCH 35/58] Update tests/python/test_arrays.py Co-authored-by: Caleb Aikens --- tests/python/test_arrays.py | 25 +++++++++++++++++++++---- 1 file changed, 21 insertions(+), 4 deletions(-) diff --git a/tests/python/test_arrays.py b/tests/python/test_arrays.py index 7a591fa2..69298f17 100644 --- a/tests/python/test_arrays.py +++ b/tests/python/test_arrays.py @@ -709,10 +709,27 @@ def test_forEach_check_array(): assert result[0] is items def test_forEach_check_this_arg(): - items = ['Four', 'Three', 'One'] - result = [None] - pm.eval("(result, arr) => {class Counter { constructor() { this.count = 0;} add(array) { array.forEach(function countEntry(entry) { ++this.count; }, this);}} const obj = new Counter(); obj.add(arr); result[0] = obj.count;}")(result, items) - assert result == [3] + items = ['Four', 'Three', 'One'] + result = [None] + pm.eval( + """ + (result, arr) => { + class Counter { + constructor() + { + this.count = 0; + } + add(array) { + array.forEach(function countEntry(entry) { ++this.count; }, this); + } + } + const obj = new Counter(); + obj.add(arr); + result[0] = obj.count; + } + """ + )(result, items) + assert result == [3] def test_forEach_check_this_arg_wrong_type(): items = ['Four', 'Three', 'One'] From 132bba82a798532aefeb61b6ba4b4748d090b0c8 Mon Sep 17 00:00:00 2001 From: philippedistributive <151072087+philippedistributive@users.noreply.github.com> Date: Thu, 11 Jan 2024 14:38:26 -0500 Subject: [PATCH 36/58] Update tests/python/test_arrays.py Co-authored-by: Caleb Aikens --- tests/python/test_arrays.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/python/test_arrays.py b/tests/python/test_arrays.py index 69298f17..791271f9 100644 --- a/tests/python/test_arrays.py +++ b/tests/python/test_arrays.py @@ -665,7 +665,7 @@ def test_sort_with_js_func(): result = [None] myFunc = pm.eval("((a, b) => a.toLocaleUpperCase() < b.toLocaleUpperCase() ? -1 : 1)") pm.eval("(result, arr, compareFun) => {result[0] = arr.sort(compareFun)}")(result, items, myFunc) - assert result[0] == items + assert result[0] is items assert items == ['Four', 'One', 'Three'] #def test_sort_numbers_tricky(): From 66a4cc8dcac8e791a6f2c8feb4da997c6416ea4b Mon Sep 17 00:00:00 2001 From: philippedistributive <151072087+philippedistributive@users.noreply.github.com> Date: Thu, 11 Jan 2024 14:39:20 -0500 Subject: [PATCH 37/58] Update tests/python/test_arrays.py Co-authored-by: Caleb Aikens --- tests/python/test_arrays.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/python/test_arrays.py b/tests/python/test_arrays.py index 791271f9..11715042 100644 --- a/tests/python/test_arrays.py +++ b/tests/python/test_arrays.py @@ -613,7 +613,7 @@ def test_sort_empty(): items = [] result = [None] pm.eval("(result, arr) => {result[0] = arr.sort()}")(result, items) - assert result[0] == items + assert result[0] is items assert items == [] def test_sort_numbers(): From 0df2e1f31932d02d3cf22bf30957713e2c06748b Mon Sep 17 00:00:00 2001 From: philippedistributive <151072087+philippedistributive@users.noreply.github.com> Date: Thu, 11 Jan 2024 14:39:47 -0500 Subject: [PATCH 38/58] Update tests/python/test_arrays.py Co-authored-by: Caleb Aikens --- tests/python/test_arrays.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/python/test_arrays.py b/tests/python/test_arrays.py index 11715042..5bcb8782 100644 --- a/tests/python/test_arrays.py +++ b/tests/python/test_arrays.py @@ -620,7 +620,7 @@ def test_sort_numbers(): items = [4,2,6,7] result = [None] pm.eval("(result, arr) => {result[0] = arr.sort()}")(result, items) - assert result[0] == items + assert result[0] is items assert items == [2,4,6,7] def test_sort_strings(): From 15063d7feab2326c828cc7b17052f95f5c93fac9 Mon Sep 17 00:00:00 2001 From: Philippe Laporte Date: Thu, 11 Jan 2024 15:10:02 -0500 Subject: [PATCH 39/58] fixes and improved tests --- include/jsTypeFactory.hh | 10 ++++++ src/PyProxyHandler.cc | 25 +++++++------- src/jsTypeFactory.cc | 19 +++++++++++ tests/python/test_arrays.py | 66 +++++++++++++++++++++++++++++++++++-- 4 files changed, 106 insertions(+), 14 deletions(-) diff --git a/include/jsTypeFactory.hh b/include/jsTypeFactory.hh index 6ec68412..23339c54 100644 --- a/include/jsTypeFactory.hh +++ b/include/jsTypeFactory.hh @@ -37,6 +37,16 @@ size_t UCS4ToUTF16(const uint32_t *chars, size_t length, uint16_t *outStr); * @return JS::Value - A JS::Value corresponding to the PyType */ JS::Value jsTypeFactory(JSContext *cx, PyObject *object); + +/** + * @brief Function that takes a PyObject and returns a corresponding JS::Value which is a copy, not a reference + * + * @param cx - Pointer to the JSContext + * @param object - Pointer to the PyListObject + * @return JS::Value - A JS::Value corresponding to the PyType + */ +JS::Value jsTypeFactoryCopy(JSContext *cx, PyObject *object); + /** * @brief same to jsTypeFactory, but it's guaranteed that no error would be set on the Python error stack, instead * return JS `null` on error, and output a warning in Python-land diff --git a/src/PyProxyHandler.cc b/src/PyProxyHandler.cc index c21d66ae..c69c41c0 100644 --- a/src/PyProxyHandler.cc +++ b/src/PyProxyHandler.cc @@ -624,9 +624,10 @@ static bool array_fill(JSContext *cx, unsigned argc, JS::Value *vp) { } JS::RootedObject *global = new JS::RootedObject(cx, JS::GetNonCCWObjectGlobal(proxy)); + JS::RootedValue *fillValue = new JS::RootedValue(cx, args[0].get()); + PyObject *fillValueItem = pyTypeFactory(cx, global, fillValue)->getPyObject(); for (int index = actualStart; index < actualEnd; index++) { - JS::RootedValue *fillValue = new JS::RootedValue(cx, args[0].get()); - if (PyList_SetItem(self, index, pyTypeFactory(cx, global, fillValue)->getPyObject()) < 0) { + if (PyList_SetItem(self, index, fillValueItem) < 0) { return false; } } @@ -645,7 +646,7 @@ static bool array_copyWithin(JSContext *cx, unsigned argc, JS::Value *vp) { } PyObject *self = JS::GetMaybePtrFromReservedSlot(proxy, PyObjectSlot); - uint64_t selfLength = (uint64_t)PyList_GET_SIZE(self); + int64_t selfLength = (uint64_t)PyList_GET_SIZE(self); unsigned int argsLength = args.length(); @@ -658,11 +659,11 @@ static bool array_copyWithin(JSContext *cx, unsigned argc, JS::Value *vp) { relativeTarget = 0; } - uint64_t actualTarget; + int64_t actualTarget; if (relativeTarget < 0) { - actualTarget = uint64_t(std::max(double(selfLength) + relativeTarget, 0.0)); + actualTarget = int64_t(std::max(double(selfLength) + relativeTarget, 0.0)); } else { - actualTarget = uint64_t(std::min(double(relativeTarget), double(selfLength))); + actualTarget = int64_t(std::min(double(relativeTarget), double(selfLength))); } int64_t relativeStart; @@ -674,11 +675,11 @@ static bool array_copyWithin(JSContext *cx, unsigned argc, JS::Value *vp) { relativeStart = 0; } - uint64_t actualStart; + int64_t actualStart; if (relativeStart < 0) { - actualStart = uint64_t(std::max(double(selfLength) + relativeStart, 0.0)); + actualStart = int64_t(std::max(double(selfLength) + relativeStart, 0.0)); } else { - actualStart = uint64_t(std::min(double(relativeStart), double(selfLength))); + actualStart = int64_t(std::min(double(relativeStart), double(selfLength))); } int64_t relativeEnd; @@ -690,11 +691,11 @@ static bool array_copyWithin(JSContext *cx, unsigned argc, JS::Value *vp) { relativeEnd = selfLength; } - uint64_t actualEnd; + int64_t actualEnd; if (relativeEnd < 0) { - actualEnd = uint64_t(std::max(double(selfLength) + relativeEnd, 0.0)); + actualEnd = int64_t(std::max(double(selfLength) + relativeEnd, 0.0)); } else { - actualEnd = uint64_t(std::min(double(relativeEnd), double(selfLength))); + actualEnd = int64_t(std::min(double(relativeEnd), double(selfLength))); } int64_t count = int64_t(std::min(actualEnd - actualStart, selfLength - actualTarget)); diff --git a/src/jsTypeFactory.cc b/src/jsTypeFactory.cc index 3cbf8985..a5696eca 100644 --- a/src/jsTypeFactory.cc +++ b/src/jsTypeFactory.cc @@ -200,6 +200,25 @@ JS::Value jsTypeFactory(JSContext *cx, PyObject *object) { return returnType; } +JS::Value jsTypeFactoryCopy(JSContext *cx, PyObject *object) { + JS::RootedValue returnType(cx); + if (PyList_Check(object)) { + Py_ssize_t listSize = PyList_Size(object); + JSObject *array = JS::NewArrayObject(cx, listSize); + JS::RootedObject arrayObj(cx, array); + for (Py_ssize_t index = 0; index < listSize; index++) { + JS::RootedValue jsValue(cx, jsTypeFactorySafe(cx, PyList_GetItem(object, index))); + JS_SetElement(cx, arrayObj, index, jsValue); + } + returnType.setObject(*array); + } + else { + returnType.setUndefined(); + } + + return returnType; +} + JS::Value jsTypeFactorySafe(JSContext *cx, PyObject *object) { JS::Value v = jsTypeFactory(cx, object); if (PyErr_Occurred()) { diff --git a/tests/python/test_arrays.py b/tests/python/test_arrays.py index 5bcb8782..4adfb34c 100644 --- a/tests/python/test_arrays.py +++ b/tests/python/test_arrays.py @@ -324,7 +324,13 @@ def test_indexOf_not_found(): items = [1,2,3] result = [None] pm.eval("(result, arr) => {result[0] = arr.indexOf(10)}")(result, items) - assert result[0] == -1 + assert result[0] == -1 + +def test_indexOf_small_start(): + items = [1,2,3,1] + result = [None] + pm.eval("(result, arr) => {result[0] = arr.indexOf(1, -10)}")(result, items) + assert result[0] == 0 #lastIndexOf def test_lastIndexOf(): @@ -363,6 +369,12 @@ def test_lastIndexOf_not_found(): pm.eval("(result, arr) => {result[0] = arr.lastIndexOf(3, 0)}")(result, items) assert result[0] == -1 +def test_lastIndexOf_small_start(): + items = [1,2,3] + result = [None] + pm.eval("(result, arr) => {result[0] = arr.lastIndexOf(1, -10)}")(result, items) + assert result[0] == -1 + #splice def test_splice_no_args(): items = [1,2,3] @@ -485,7 +497,13 @@ def test_fill_with_start_too_high(): items = [1,2,3] result = [None] pm.eval("(result, arr) => {result[0] = arr.fill(8,7)}")(result, items) - assert items == [1,2,3] + assert items == [1,2,3] + +def test_fill_with_stop_smaller_than_start(): + items = [1,2,3] + result = [None] + pm.eval("(result, arr) => {result[0] = arr.fill(8,7,2)}")(result, items) + assert items == [1,2,3] def test_fill_with_stop(): items = [1,2,3] @@ -499,12 +517,26 @@ def test_fill_with_negative_stop(): pm.eval("(result, arr) => {result[0] = arr.fill(8,1,-1)}")(result, items) assert items == [1,8,3] +def test_fill_with_negative_stop_too_low(): + items = [1,2,3] + result = [None] + pm.eval("(result, arr) => {result[0] = arr.fill(8,1,-10)}")(result, items) + assert items == [1,2,3] + def test_fill_with_stop_too_high(): items = [1,2,3] result = [None] pm.eval("(result, arr) => {result[0] = arr.fill(8,1,10)}")(result, items) assert items == [1,8,8] +def test_fill_object(): + items = [1,2,3] + result = [None] + pm.eval("(result, arr) => {let a = {a:1}; result[0] = arr.fill(a)}")(result, items) + assert items == [{"a":1},{"a":1},{"a":1}] + assert items is result[0] + assert items[0] is items[1] is items[2] + #copyWithin def test_copyWithin(): @@ -574,6 +606,36 @@ def test_copyWithin_target_and_start_and_negative_end(): pm.eval("(result, arr) => {result[0] = arr.copyWithin(1,2,-2)}")(result, items) assert items == [1,3,3,4,5] +def test_copyWithin_target_too_small_and_start(): + items = [1,2,3] + result = [None] + pm.eval("(result, arr) => {result[0] = arr.copyWithin(-10,2)}")(result, items) + assert items == [3,2,3] + +def test_copyWithin_target_greater_than_start(): + items = [1,2,3] + result = [None] + pm.eval("(result, arr) => { result[0] = arr.copyWithin(2,1)}")(result, items) + assert items == [1,2,2] + +def test_copyWithin_target_and_start_too_small(): + items = [1,2,3] + result = [None] + pm.eval("(result, arr) => {result[0] = arr.copyWithin(1, -10)}")(result, items) + assert items == [1,1,2] + +def test_copyWithin_target_and_start_and_end_too_large(): + items = [1,2,3,4,5] + result = [None] + pm.eval("(result, arr) => {result[0] = arr.copyWithin(2,1,10)}")(result, items) + assert items == [1,2,2,3,4] + +def test_copyWithin_target_and_start_greater_than_end(): + items = [1,2,3,4,5] + result = [None] + pm.eval("(result, arr) => {result[0] = arr.copyWithin(2,3,2)}")(result, items) + assert items == [1,2,3,4,5] + #includes def test_includes(): items = [1,2,3] From 2b61ec3bd281ce1729c2390384e7687bec664ffc Mon Sep 17 00:00:00 2001 From: Philippe Laporte Date: Thu, 11 Jan 2024 16:25:01 -0500 Subject: [PATCH 40/58] improved tests --- tests/python/test_arrays.py | 323 ++++++++++++++++++++++++++++++++++-- 1 file changed, 313 insertions(+), 10 deletions(-) diff --git a/tests/python/test_arrays.py b/tests/python/test_arrays.py index 4adfb34c..12faadee 100644 --- a/tests/python/test_arrays.py +++ b/tests/python/test_arrays.py @@ -1,4 +1,5 @@ import pythonmonkey as pm +from datetime import datetime def test_assign(): items = [1,2,3] @@ -538,7 +539,6 @@ def test_fill_object(): assert items[0] is items[1] is items[2] #copyWithin - def test_copyWithin(): items = [1,2,3] result = [None] @@ -624,6 +624,12 @@ def test_copyWithin_target_and_start_too_small(): pm.eval("(result, arr) => {result[0] = arr.copyWithin(1, -10)}")(result, items) assert items == [1,1,2] +def test_copyWithin_target_and_start_and_end_too_small(): + items = [1,2,3,4,5] + result = [None] + pm.eval("(result, arr) => {result[0] = arr.copyWithin(1,2,-10)}")(result, items) + assert items == [1,2,3,4,5] + def test_copyWithin_target_and_start_and_end_too_large(): items = [1,2,3,4,5] result = [None] @@ -802,7 +808,20 @@ def test_forEach_check_this_arg_wrong_type(): assert (False) except Exception as e: assert str(type(e)) == "" - assert str(e).__contains__("TypeError: 'this' argument is not an object or null") + assert str(e).__contains__("TypeError: 'this' argument is not an object or null") + +# TODO python function support + +# this one does not get the result into items +#def test_forEach_with_python_function(): +# def func(element, index, array): +# return "to each his own" +# items = ['Four', 'Three', 'One'] +# returnResult = [0] +# pm.eval("(returnResult, arr, func) => {returnResult[0] = arr.forEach(func)}")(returnResult, items, func) +# assert items == ['to each his own', 'to each his own', 'to each his own'] +# assert returnResult == [None] + # TODO should not pass def test_forEach_check_this_arg_null(): @@ -832,6 +851,43 @@ def test_map(): assert items == [4,2,6,7] assert result[0] == [16,4,36,49] +def test_map_check_index(): + items = [4,2,6,7] + result = [''] + pm.eval("(result, arr) => {arr.map((x, index) => result[0] += index)}")(result, items) + assert items == [4,2,6,7] + assert result[0] == '0123' + +def test_map_check_array(): + items = ['Four', 'Three', 'One'] + result = [''] + pm.eval("(result, arr) => {arr.map((element, index, array) => result[0] = array)}")(result, items) + assert result == [items] + assert result[0] is items + +def test_map_check_this_arg(): + items = ['Four', 'Three', 'One'] + result = [None] + pm.eval( + """ + (result, arr) => { + class Counter { + constructor() + { + this.count = 0; + } + add(array) { + array.map(function countEntry(entry) { ++this.count; }, this); + } + } + const obj = new Counter(); + obj.add(arr); + result[0] = obj.count; + } + """ + )(result, items) + assert result == [3] + def test_map_too_few_args(): items = [4,2,6,7] try: @@ -850,12 +906,6 @@ def test_map_arg_wrong_type(): assert str(type(e)) == "" assert str(e).__contains__("TypeError: map: callback is not a function") -def test_map_check_index(): - items = ['Four', 'Three', 'One'] - result = [''] - pm.eval("(result, arr) => {arr.map((element, index) => result[0] += index)}")(result, items) - assert result == ['012'] - def test_map_check_array_mutation(): items = ['Four', 'Three', 'One'] result = [''] @@ -871,6 +921,43 @@ def test_filter(): assert words == ['spray', 'elite', 'exuberant', 'destruction', 'present'] assert result[0] == ['exuberant', 'destruction', 'present'] +def test_filter_check_index(): + items = [4,2,6,7] + result = [''] + pm.eval("(result, arr) => {arr.filter((x, index) => result[0] += index)}")(result, items) + assert items == [4,2,6,7] + assert result[0] == '0123' + +def test_filter_check_array(): + items = ['Four', 'Three', 'One'] + result = [''] + pm.eval("(result, arr) => {arr.filter((element, index, array) => result[0] = array)}")(result, items) + assert result == [items] + assert result[0] is items + +def test_filter_check_this_arg(): + items = ['Four', 'Three', 'One'] + result = [None] + pm.eval( + """ + (result, arr) => { + class Counter { + constructor() + { + this.count = 0; + } + add(array) { + array.filter(function countEntry(entry) { ++this.count; }, this); + } + } + const obj = new Counter(); + obj.add(arr); + result[0] = obj.count; + } + """ + )(result, items) + assert result == [3] + def test_filter_too_few_args(): items = [4,2,6,7] try: @@ -972,6 +1059,19 @@ def test_reduceRight_float(): pm.eval("(result, arr) => {result[0] = arr.reduceRight((accumulator, currentValue, index, array) => accumulator + currentValue)}")(result, items) assert result[0] == 32.3 +def test_reduceRight_check_index(): + items = [1.9, 4.6, 9.3, 16.5] + result = [''] + pm.eval("(result, arr) => {arr.reduceRight((accumulator, currentValue, index, array) => {accumulator + currentValue; result[0] += index})}")(result, items) + assert result[0] == '210' + +def test_reduceRight_check_array(): + items = ['Four', 'Three', 'One'] + result = [None] + pm.eval("(result, arr) => {arr.reduceRight((accumulator, currentValue, index, array) => {accumulator + currentValue; result[0] = array})}")(result, items) + assert result == [items] + assert result[0] is items + #some def test_some_true(): items = [1, 2, 3, 4, 5] @@ -984,7 +1084,44 @@ def test_some_false(): items = [1,3,5] result = [None] pm.eval("(result, arr) => {result[0] = arr.some((element) => element % 2 === 0)}")(result, items) - assert result[0] == False + assert result[0] == False + +def test_some_check_index(): + items = [4,2,6,7] + result = [''] + pm.eval("(result, arr) => {arr.some((x, index) => result[0] += index)}")(result, items) + assert items == [4,2,6,7] + assert result[0] == '0123' + +def test_some_check_array(): + items = ['Four', 'Three', 'One'] + result = [''] + pm.eval("(result, arr) => {arr.some((element, index, array) => result[0] = array)}")(result, items) + assert result == [items] + assert result[0] is items + +def test_some_check_this_arg(): + items = ['Four', 'Three', 'One'] + result = [None] + pm.eval( + """ + (result, arr) => { + class Counter { + constructor() + { + this.count = 0; + } + add(array) { + array.some(function countEntry(entry) { ++this.count; }, this); + } + } + const obj = new Counter(); + obj.add(arr); + result[0] = obj.count; + } + """ + )(result, items) + assert result == [3] def test_some_truthy_conversion(): result = [None] @@ -1017,6 +1154,43 @@ def test_every_false(): pm.eval("(result, arr) => {result[0] = arr.every((element) => element % 2 === 0)}")(result, items) assert result[0] == False +def test_every_check_index(): + items = [4,2,6,7] + result = [''] + pm.eval("(result, arr) => {arr.every((x, index) => result[0] += index)}")(result, items) + assert items == [4,2,6,7] + assert result[0] == '0' + +def test_every_check_array(): + items = ['Four', 'Three', 'One'] + result = [''] + pm.eval("(result, arr) => {arr.every((element, index, array) => result[0] = array)}")(result, items) + assert result == [items] + assert result[0] is items + +def test_every_check_this_arg(): + items = ['Four', 'Three', 'One'] + result = [None] + pm.eval( + """ + (result, arr) => { + class Counter { + constructor() + { + this.count = 0; + } + add(array) { + array.every(function countEntry(entry) { ++this.count; }, this); + } + } + const obj = new Counter(); + obj.add(arr); + result[0] = obj.count; + } + """ + )(result, items) + assert result == [1] + #find def test_find_found_once(): items = [5, 12, 8, 130, 44] @@ -1037,6 +1211,43 @@ def test_find_not_found(): pm.eval("(result, arr) => {result[0] = arr.find((element) => element > 1000)}")(result, items) assert result[0] is None +def test_find_check_index(): + items = [4,2,6,7] + result = [''] + pm.eval("(result, arr) => {arr.find((x, index) => result[0] += index)}")(result, items) + assert items == [4,2,6,7] + assert result[0] == '0123' + +def test_find_check_array(): + items = ['Four', 'Three', 'One'] + result = [''] + pm.eval("(result, arr) => {arr.find((element, index, array) => result[0] = array)}")(result, items) + assert result == [items] + assert result[0] is items + +def test_find_check_this_arg(): + items = ['Four', 'Three', 'One'] + result = [None] + pm.eval( + """ + (result, arr) => { + class Counter { + constructor() + { + this.count = 0; + } + add(array) { + array.find(function countEntry(entry) { ++this.count; }, this); + } + } + const obj = new Counter(); + obj.add(arr); + result[0] = obj.count; + } + """ + )(result, items) + assert result == [3] + #findIndex def test_findIndex_found_once(): items = [5, 12, 8, 130, 44] @@ -1055,7 +1266,44 @@ def test_findIndex_not_found(): items = [5, 12, 8, 130, 4] result = [0] pm.eval("(result, arr) => {result[0] = arr.findIndex((element) => element > 1000)}")(result, items) - assert result[0] == -1 + assert result[0] == -1 + +def test_findIndex_check_index(): + items = [4,2,6,7] + result = [''] + pm.eval("(result, arr) => {arr.findIndex((x, index) => result[0] += index)}")(result, items) + assert items == [4,2,6,7] + assert result[0] == '0123' + +def test_findIndex_check_array(): + items = ['Four', 'Three', 'One'] + result = [''] + pm.eval("(result, arr) => {arr.findIndex((element, index, array) => result[0] = array)}")(result, items) + assert result == [items] + assert result[0] is items + +def test_findIndex_check_this_arg(): + items = ['Four', 'Three', 'One'] + result = [None] + pm.eval( + """ + (result, arr) => { + class Counter { + constructor() + { + this.count = 0; + } + add(array) { + array.findIndex(function countEntry(entry) { ++this.count; }, this); + } + } + const obj = new Counter(); + obj.add(arr); + result[0] = obj.count; + } + """ + )(result, items) + assert result == [3] #flat def test_flat(): @@ -1135,6 +1383,43 @@ def test_flatMap_equivalence(): pm.eval("(result, arr) => {result[0] = arr.map((num) => (num === 2 ? [2, 2] : 1)).flat()}")(result2, items) assert result[0] == result2[0] +def test_flatMap_check_index(): + items = [4,2,6,7] + result = [''] + pm.eval("(result, arr) => {arr.flatMap((x, index) => result[0] += index)}")(result, items) + assert items == [4,2,6,7] + assert result[0] == '0123' + +def test_flatMap_check_array(): + items = ['Four', 'Three', 'One'] + result = [''] + pm.eval("(result, arr) => {arr.flatMap((element, index, array) => result[0] = array)}")(result, items) + assert result == [items] + assert result[0] is items + +def test_flatMap_check_this_arg(): + items = ['Four', 'Three', 'One'] + result = [None] + pm.eval( + """ + (result, arr) => { + class Counter { + constructor() + { + this.count = 0; + } + add(array) { + array.flatMap(function countEntry(entry) { ++this.count; }, this); + } + } + const obj = new Counter(); + obj.add(arr); + result[0] = obj.count; + } + """ + )(result, items) + assert result == [0] + #valueOf def test_valueOf(): items = [1, 2, 1] @@ -1150,6 +1435,18 @@ def test_toLocaleString(): pm.eval("(result, arr) => {result[0] = arr.toLocaleString('ja-JP', { style: 'currency', currency: 'JPY' })}")(result, prices) assert result[0] == 'ï¿¥7,ï¿¥500,ï¿¥8,123,ï¿¥12' +def test_toLocaleString_with_none(): + prices = ["ï¿¥7", 500, 8123, None] + result = [None] + pm.eval("(result, arr) => {result[0] = arr.toLocaleString('ja-JP', { style: 'currency', currency: 'JPY' })}")(result, prices) + assert result[0] == 'ï¿¥7,ï¿¥500,ï¿¥8,123,' + +def test_toLocaleString_with_null(): + prices = ["ï¿¥7", 500, 8123, pm.null] + result = [None] + pm.eval("(result, arr) => {result[0] = arr.toLocaleString('ja-JP', { style: 'currency', currency: 'JPY' })}")(result, prices) + assert result[0] == 'ï¿¥7,ï¿¥500,ï¿¥8,123,' + def test_toLocaleString_no_args(): prices = ["ï¿¥7", 500, 8123, 12] result = [None] @@ -1182,6 +1479,12 @@ def test_toLocaleString_two_args_invalid_currency(): assert str(type(e)) == "" assert str(e).__contains__("RangeError: invalid currency code in NumberFormat():") +def test_toLocaleString_with_datetime(): + prices = [500, datetime(year=2020, month=1, day=31, hour=13, minute=14, second=31)] + result = [None] + pm.eval("(result, arr) => {result[0] = arr.toLocaleString('en', {timeZone: 'UTC'})}")(result, prices) + assert result[0] == '500,1/31/2020, 6:14:31 PM' + #entries def test_entries_next(): items = ['a', 'b', 'c'] From 9cf3306fa5245d387e3bfefb458f7da5231fa04a Mon Sep 17 00:00:00 2001 From: Philippe Laporte Date: Thu, 11 Jan 2024 16:29:33 -0500 Subject: [PATCH 41/58] use latest spider monkey esr --- setup.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/setup.sh b/setup.sh index 71655d2d..c718629f 100755 --- a/setup.sh +++ b/setup.sh @@ -39,9 +39,9 @@ $POETRY_BIN self add 'poetry-dynamic-versioning[plugin]' echo "Done installing dependencies" echo "Downloading spidermonkey source code" -wget -c -q https://ftp.mozilla.org/pub/firefox/releases/115.1.0esr/source/firefox-115.1.0esr.source.tar.xz +wget -c -q https://ftp.mozilla.org/pub/firefox/releases/115.6.0esr/source/firefox-115.6.0esr.source.tar.xz mkdir -p firefox-source -tar xf firefox-115.1.0esr.source.tar.xz -C firefox-source --strip-components=1 # strip the root folder +tar xf firefox-115.6.0esr.source.tar.xz -C firefox-source --strip-components=1 # strip the root folder echo "Done downloading spidermonkey source code" echo "Building spidermonkey" From 72d3e7774b0913bb4893291a58b06544c57259dc Mon Sep 17 00:00:00 2001 From: Philippe Laporte Date: Thu, 11 Jan 2024 16:37:27 -0500 Subject: [PATCH 42/58] added node license --- LICENSE | 49 ++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 48 insertions(+), 1 deletion(-) diff --git a/LICENSE b/LICENSE index e488de74..c4867839 100644 --- a/LICENSE +++ b/LICENSE @@ -72,4 +72,51 @@ products or services of Licensee, or any third party. 8. By copying, installing or otherwise using Python, Licensee agrees to be bound by the terms and conditions of this License -Agreement. \ No newline at end of file +Agreement. + +------------------------------------------------------------------------------- + +Copyright Node.js contributors. All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to +deal in the Software without restriction, including without limitation the +rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +sell copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +IN THE SOFTWARE. +""" + +This license applies to parts of Node.js originating from the +https://github.com/joyent/node repository: + +""" +Copyright Joyent, Inc. and other Node contributors. All rights reserved. +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to +deal in the Software without restriction, including without limitation the +rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +sell copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +IN THE SOFTWARE. +""" \ No newline at end of file From 390f3da8c098b6a95ebb0715cf905ba632d275e1 Mon Sep 17 00:00:00 2001 From: Philippe Laporte Date: Thu, 11 Jan 2024 17:09:28 -0500 Subject: [PATCH 43/58] test fix --- tests/python/test_arrays.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/python/test_arrays.py b/tests/python/test_arrays.py index 12faadee..7c32674e 100644 --- a/tests/python/test_arrays.py +++ b/tests/python/test_arrays.py @@ -1482,8 +1482,8 @@ def test_toLocaleString_two_args_invalid_currency(): def test_toLocaleString_with_datetime(): prices = [500, datetime(year=2020, month=1, day=31, hour=13, minute=14, second=31)] result = [None] - pm.eval("(result, arr) => {result[0] = arr.toLocaleString('en', {timeZone: 'UTC'})}")(result, prices) - assert result[0] == '500,1/31/2020, 6:14:31 PM' + pm.eval("(result, arr) => {result[0] = arr.toLocaleString('en-uk')}")(result, prices) + assert result[0] == '500,31/01/2020, 13:14:31' #entries def test_entries_next(): From 0a78d759ba2ee0e29f225becadabcf30c141670f Mon Sep 17 00:00:00 2001 From: Philippe Laporte Date: Thu, 11 Jan 2024 17:14:28 -0500 Subject: [PATCH 44/58] restore firefox version --- setup.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/setup.sh b/setup.sh index c718629f..71655d2d 100755 --- a/setup.sh +++ b/setup.sh @@ -39,9 +39,9 @@ $POETRY_BIN self add 'poetry-dynamic-versioning[plugin]' echo "Done installing dependencies" echo "Downloading spidermonkey source code" -wget -c -q https://ftp.mozilla.org/pub/firefox/releases/115.6.0esr/source/firefox-115.6.0esr.source.tar.xz +wget -c -q https://ftp.mozilla.org/pub/firefox/releases/115.1.0esr/source/firefox-115.1.0esr.source.tar.xz mkdir -p firefox-source -tar xf firefox-115.6.0esr.source.tar.xz -C firefox-source --strip-components=1 # strip the root folder +tar xf firefox-115.1.0esr.source.tar.xz -C firefox-source --strip-components=1 # strip the root folder echo "Done downloading spidermonkey source code" echo "Building spidermonkey" From 056419fec8c172ec5818369882bcdb17bf564ea6 Mon Sep 17 00:00:00 2001 From: Philippe Laporte Date: Thu, 11 Jan 2024 18:52:47 -0500 Subject: [PATCH 45/58] improved mem usage in flatX methods --- src/PyProxyHandler.cc | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/src/PyProxyHandler.cc b/src/PyProxyHandler.cc index c69c41c0..6f72d906 100644 --- a/src/PyProxyHandler.cc +++ b/src/PyProxyHandler.cc @@ -1493,16 +1493,17 @@ static uint32_t FlattenIntoArray(JSContext *cx, JS::HandleObject global, JS::RootedObject rootedGlobal(cx, global); - JS::RootedValue elementVal(cx); for (uint32_t sourceIndex = 0; sourceIndex < sourceLen; sourceIndex++) { + JS::RootedValue *elementVal = new JS::RootedValue(cx); + if (PyObject_TypeCheck(source, &JSArrayProxyType)) { - JS_GetElement(cx, ((JSArrayProxy *)source)->jsArray, sourceIndex, &elementVal); + JS_GetElement(cx, ((JSArrayProxy *)source)->jsArray, sourceIndex, elementVal); } else if (PyObject_TypeCheck(source, &PyList_Type)) { - elementVal.set(jsTypeFactory(cx, PyList_GetItem(source, sourceIndex))); + elementVal->set(jsTypeFactory(cx, PyList_GetItem(source, sourceIndex))); } - PyObject *element = pyTypeFactory(cx, &rootedGlobal, &elementVal)->getPyObject(); + PyObject *element = pyTypeFactory(cx, &rootedGlobal, elementVal)->getPyObject(); bool shouldFlatten; if (depth > 0) { @@ -1537,7 +1538,7 @@ static uint32_t FlattenIntoArray(JSContext *cx, JS::HandleObject global, JS::SetArrayLength(cx, rootedRetArray, targetIndex + 1); } - JS_SetElement(cx, rootedRetArray, targetIndex, elementVal); + JS_SetElement(cx, rootedRetArray, targetIndex, *elementVal); targetIndex++; } @@ -1567,14 +1568,16 @@ static uint32_t FlattenIntoArrayWithCallBack(JSContext *cx, JS::HandleObject glo elementVal.set(jsTypeFactory(cx, PyList_GetItem(source, sourceIndex))); } + JS::RootedValue *retVal = new JS::RootedValue(cx); + jArgs[0].set(elementVal); jArgs[1].setInt32(sourceIndex); jArgs[2].set(sourceValue); - if (!JS_CallFunctionValue(cx, nullptr, callBack, jArgs, &retVal)) { + if (!JS_CallFunctionValue(cx, nullptr, callBack, jArgs, retVal)) { return false; } - PyObject *element = pyTypeFactory(cx, &rootedGlobal, &retVal)->getPyObject(); + PyObject *element = pyTypeFactory(cx, &rootedGlobal, retVal)->getPyObject(); bool shouldFlatten; if (depth > 0) { @@ -1633,7 +1636,7 @@ static uint32_t FlattenIntoArrayWithCallBack(JSContext *cx, JS::HandleObject glo JS::SetArrayLength(cx, rootedRetArray, targetIndex + 1); } - JS_SetElement(cx, rootedRetArray, targetIndex, retVal); + JS_SetElement(cx, rootedRetArray, targetIndex, *retVal); targetIndex++; } From b6963cb466530414ae76efb7611937c5775a8009 Mon Sep 17 00:00:00 2001 From: Philippe Laporte Date: Thu, 11 Jan 2024 19:22:50 -0500 Subject: [PATCH 46/58] fix memory issue bad rootedobject usage --- src/PyProxyHandler.cc | 53 ++++++++++++++++++++----------------- tests/python/test_arrays.py | 2 +- 2 files changed, 29 insertions(+), 26 deletions(-) diff --git a/src/PyProxyHandler.cc b/src/PyProxyHandler.cc index 6f72d906..adeb958f 100644 --- a/src/PyProxyHandler.cc +++ b/src/PyProxyHandler.cc @@ -885,7 +885,7 @@ static bool array_forEach(JSContext *cx, unsigned argc, JS::Value *vp) { return false; } - JS::RootedValue selfValue(cx, jsTypeFactorySafe(cx, self)); + JS::RootedValue selfValue(cx, jsTypeFactory(cx, self)); JS::RootedValue callBack(cx, callbackfn); JS::Rooted> jArgs(cx); @@ -906,7 +906,7 @@ static bool array_forEach(JSContext *cx, unsigned argc, JS::Value *vp) { } for (Py_ssize_t index = 0, toIndex = 0; index < len; index++) { - jArgs[0].set(jsTypeFactorySafe(cx, PyList_GetItem(self, index))); + jArgs[0].set(jsTypeFactory(cx, PyList_GetItem(self, index))); jArgs[1].setInt32(index); jArgs[2].set(selfValue); @@ -957,7 +957,7 @@ static bool array_map(JSContext *cx, unsigned argc, JS::Value *vp) { JSObject *retArray = JS::NewArrayObject(cx, len); JS::RootedObject rootedRetArray(cx, retArray); - JS::RootedValue selfValue(cx, jsTypeFactorySafe(cx, self)); + JS::RootedValue selfValue(cx, jsTypeFactory(cx, self)); JS::RootedValue callBack(cx, callbackfn); JS::Rooted> jArgs(cx); @@ -976,7 +976,7 @@ static bool array_map(JSContext *cx, unsigned argc, JS::Value *vp) { } for (Py_ssize_t index = 0; index < len; index++) { - jArgs[0].set(jsTypeFactorySafe(cx, PyList_GetItem(self, index))); + jArgs[0].set(jsTypeFactory(cx, PyList_GetItem(self, index))); jArgs[1].setInt32(index); jArgs[2].set(selfValue); @@ -1023,7 +1023,7 @@ static bool array_filter(JSContext *cx, unsigned argc, JS::Value *vp) { return false; } - JS::RootedValue selfValue(cx, jsTypeFactorySafe(cx, self)); + JS::RootedValue selfValue(cx, jsTypeFactory(cx, self)); JS::RootedValue callBack(cx, callbackfn); JS::Rooted> jArgs(cx); @@ -1045,7 +1045,7 @@ static bool array_filter(JSContext *cx, unsigned argc, JS::Value *vp) { Py_ssize_t len = PyList_GET_SIZE(self); for (Py_ssize_t index = 0, toIndex = 0; index < len; index++) { - JS::Value item = jsTypeFactorySafe(cx, PyList_GetItem(self, index)); + JS::Value item = jsTypeFactory(cx, PyList_GetItem(self, index)); jArgs[0].set(item); jArgs[1].setInt32(index); jArgs[2].set(selfValue); @@ -1100,7 +1100,7 @@ static bool array_reduce(JSContext *cx, unsigned argc, JS::Value *vp) { return false; } - JS::RootedValue selfValue(cx, jsTypeFactorySafe(cx, self)); + JS::RootedValue selfValue(cx, jsTypeFactory(cx, self)); JS::RootedValue callBack(cx, callbackfn); JS::Rooted> jArgs(cx); @@ -1121,7 +1121,7 @@ static bool array_reduce(JSContext *cx, unsigned argc, JS::Value *vp) { for (Py_ssize_t index = args.length() > 1 ? 0 : 1; index < len; index++) { jArgs[0].set(*accumulator); - jArgs[1].set(jsTypeFactorySafe(cx, PyList_GetItem(self, index))); + jArgs[1].set(jsTypeFactory(cx, PyList_GetItem(self, index))); jArgs[2].setInt32(index); jArgs[3].set(selfValue); @@ -1156,7 +1156,7 @@ static bool array_reduceRight(JSContext *cx, unsigned argc, JS::Value *vp) { return false; } - JS::RootedValue selfValue(cx, jsTypeFactorySafe(cx, self)); + JS::RootedValue selfValue(cx, jsTypeFactory(cx, self)); JS::RootedValue callBack(cx, callbackfn); JS::Rooted> jArgs(cx); @@ -1177,7 +1177,7 @@ static bool array_reduceRight(JSContext *cx, unsigned argc, JS::Value *vp) { for (int64_t index = args.length() > 1 ? len - 1 : len - 2; index >= 0; index--) { jArgs[0].set(*accumulator); - jArgs[1].set(jsTypeFactorySafe(cx, PyList_GetItem(self, index))); + jArgs[1].set(jsTypeFactory(cx, PyList_GetItem(self, index))); jArgs[2].setInt32(index); jArgs[3].set(selfValue); @@ -1212,7 +1212,7 @@ static bool array_some(JSContext *cx, unsigned argc, JS::Value *vp) { return false; } - JS::RootedValue selfValue(cx, jsTypeFactorySafe(cx, self)); + JS::RootedValue selfValue(cx, jsTypeFactory(cx, self)); JS::RootedValue callBack(cx, callbackfn); JS::Rooted> jArgs(cx); @@ -1232,7 +1232,7 @@ static bool array_some(JSContext *cx, unsigned argc, JS::Value *vp) { Py_ssize_t len = PyList_GET_SIZE(self); for (Py_ssize_t index = 0, toIndex = 0; index < len; index++) { - jArgs[0].set(jsTypeFactorySafe(cx, PyList_GetItem(self, index))); + jArgs[0].set(jsTypeFactory(cx, PyList_GetItem(self, index))); jArgs[1].setInt32(index); jArgs[2].set(selfValue); @@ -1285,7 +1285,7 @@ static bool array_every(JSContext *cx, unsigned argc, JS::Value *vp) { return false; } - JS::RootedValue selfValue(cx, jsTypeFactorySafe(cx, self)); + JS::RootedValue selfValue(cx, jsTypeFactory(cx, self)); JS::RootedValue callBack(cx, callbackfn); JS::Rooted> jArgs(cx); @@ -1305,7 +1305,7 @@ static bool array_every(JSContext *cx, unsigned argc, JS::Value *vp) { Py_ssize_t len = PyList_GET_SIZE(self); for (Py_ssize_t index = 0, toIndex = 0; index < len; index++) { - jArgs[0].set(jsTypeFactorySafe(cx, PyList_GetItem(self, index))); + jArgs[0].set(jsTypeFactory(cx, PyList_GetItem(self, index))); jArgs[1].setInt32(index); jArgs[2].set(selfValue); @@ -1358,7 +1358,7 @@ static bool array_find(JSContext *cx, unsigned argc, JS::Value *vp) { return false; } - JS::RootedValue selfValue(cx, jsTypeFactorySafe(cx, self)); + JS::RootedValue selfValue(cx, jsTypeFactory(cx, self)); JS::RootedValue callBack(cx, callbackfn); JS::Rooted> jArgs(cx); @@ -1378,7 +1378,7 @@ static bool array_find(JSContext *cx, unsigned argc, JS::Value *vp) { Py_ssize_t len = PyList_GET_SIZE(self); for (Py_ssize_t index = 0, toIndex = 0; index < len; index++) { - JS::Value item = jsTypeFactorySafe(cx, PyList_GetItem(self, index)); + JS::Value item = jsTypeFactory(cx, PyList_GetItem(self, index)); jArgs[0].set(item); jArgs[1].setInt32(index); jArgs[2].set(selfValue); @@ -1432,7 +1432,7 @@ static bool array_findIndex(JSContext *cx, unsigned argc, JS::Value *vp) { return false; } - JS::RootedValue selfValue(cx, jsTypeFactorySafe(cx, self)); + JS::RootedValue selfValue(cx, jsTypeFactory(cx, self)); JS::RootedValue callBack(cx, callbackfn); JS::Rooted> jArgs(cx); @@ -1452,7 +1452,7 @@ static bool array_findIndex(JSContext *cx, unsigned argc, JS::Value *vp) { Py_ssize_t len = PyList_GET_SIZE(self); for (Py_ssize_t index = 0, toIndex = 0; index < len; index++) { - jArgs[0].set(jsTypeFactorySafe(cx, PyList_GetItem(self, index))); + jArgs[0].set(jsTypeFactory(cx, PyList_GetItem(self, index))); jArgs[1].setInt32(index); jArgs[2].set(selfValue); @@ -1489,6 +1489,7 @@ static bool array_findIndex(JSContext *cx, unsigned argc, JS::Value *vp) { static uint32_t FlattenIntoArray(JSContext *cx, JS::HandleObject global, JSObject *retArray, PyObject *source, Py_ssize_t sourceLen, uint32_t start, uint32_t depth) { + uint32_t targetIndex = start; JS::RootedObject rootedGlobal(cx, global); @@ -1552,10 +1553,11 @@ static uint32_t FlattenIntoArrayWithCallBack(JSContext *cx, JS::HandleObject glo JSObject *retArray, PyObject *source, Py_ssize_t sourceLen, uint32_t start, uint32_t depth, JS::HandleValue callBack, JS::HandleObject thisArg) { + uint32_t targetIndex = start; JS::RootedObject rootedGlobal(cx, global); - JS::RootedValue sourceValue(cx, jsTypeFactorySafe(cx, source)); + JS::RootedValue sourceValue(cx, jsTypeFactory(cx, source)); JS::Rooted> jArgs(cx); JS::RootedValue elementVal(cx); JS::RootedValue retVal(cx); @@ -1573,7 +1575,7 @@ static uint32_t FlattenIntoArrayWithCallBack(JSContext *cx, JS::HandleObject glo jArgs[0].set(elementVal); jArgs[1].setInt32(sourceIndex); jArgs[2].set(sourceValue); - if (!JS_CallFunctionValue(cx, nullptr, callBack, jArgs, retVal)) { + if (!JS_CallFunctionValue(cx, thisArg, callBack, jArgs, retVal)) { return false; } @@ -1699,7 +1701,7 @@ static bool array_flatMap(JSContext *cx, unsigned argc, JS::Value *vp) { JS::RootedValue callBack(cx, callbackfn); - JS::RootedObject *rootedThisArg = nullptr; + JS::RootedObject rootedThisArg(cx); if (args.length() > 1) { JS::Value thisArg = args[1].get(); if (!thisArg.isObjectOrNull()) { @@ -1708,16 +1710,17 @@ static bool array_flatMap(JSContext *cx, unsigned argc, JS::Value *vp) { } // TODO support null, currently gets TypeError - rootedThisArg = new JS::RootedObject(cx, thisArg.toObjectOrNull()); + rootedThisArg.set(thisArg.toObjectOrNull()); + } + else { + rootedThisArg.set(nullptr); } JS::RootedObject *global = new JS::RootedObject(cx, JS::GetNonCCWObjectGlobal(proxy)); JSObject *retArray = JS::NewArrayObject(cx, sourceLen); // min end length - FlattenIntoArrayWithCallBack(cx, *global, retArray, self, sourceLen, 0, 1, callBack, *rootedThisArg); - - delete rootedThisArg; + FlattenIntoArrayWithCallBack(cx, *global, retArray, self, sourceLen, 0, 1, callBack, rootedThisArg); args.rval().setObject(*retArray); return true; diff --git a/tests/python/test_arrays.py b/tests/python/test_arrays.py index 7c32674e..3e87f975 100644 --- a/tests/python/test_arrays.py +++ b/tests/python/test_arrays.py @@ -1418,7 +1418,7 @@ class Counter { } """ )(result, items) - assert result == [0] + assert result == [3] #valueOf def test_valueOf(): From 11dc2d61526ba7c90b67acb241c76a4012455464 Mon Sep 17 00:00:00 2001 From: Philippe Laporte Date: Thu, 11 Jan 2024 19:32:26 -0500 Subject: [PATCH 47/58] improved test --- tests/python/test_arrays.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/python/test_arrays.py b/tests/python/test_arrays.py index 3e87f975..de3279d5 100644 --- a/tests/python/test_arrays.py +++ b/tests/python/test_arrays.py @@ -1517,8 +1517,9 @@ def test_values_iterator(): items = ['a', 'b', 'c'] result = [7,8,9] pm.eval("(result, arr) => { index = 0; iterator = arr.values(); for (const value of iterator) { result[index] = value; index++;} }")(result, items) + assert result == ['a', 'b', 'c'] assert result is not items - + #constructor property def test_constructor_creates_array(): items = [1,2] From 1015419c8b49a2a6c58843758ef95d7fc9ab7f1c Mon Sep 17 00:00:00 2001 From: Philippe Laporte Date: Fri, 12 Jan 2024 11:49:39 -0500 Subject: [PATCH 48/58] fix memory usage, cleanup --- src/PyProxyHandler.cc | 117 +++++++++++++++--------------------- tests/python/test_arrays.py | 2 +- 2 files changed, 51 insertions(+), 68 deletions(-) diff --git a/src/PyProxyHandler.cc b/src/PyProxyHandler.cc index adeb958f..cd15efab 100644 --- a/src/PyProxyHandler.cc +++ b/src/PyProxyHandler.cc @@ -515,6 +515,13 @@ static bool array_sort(JSContext *cx, unsigned argc, JS::Value *vp) { PyList_Sort(self); } else { + JS::Value callbackfn = args[0].get(); + + if (!callbackfn.isObject() || !JS::IsCallable(&callbackfn.toObject())) { + JS_ReportErrorNumberASCII(cx, js::GetErrorMessage, nullptr, JSMSG_BAD_SORT_ARG, "sort: callback"); + return false; + } + JS::RootedObject *global = new JS::RootedObject(cx, JS::GetNonCCWObjectGlobal(proxy)); PyObject *pyFunc = pyTypeFactory(cx, global, new JS::RootedValue(cx, args[0].get()))->getPyObject(); @@ -893,7 +900,7 @@ static bool array_forEach(JSContext *cx, unsigned argc, JS::Value *vp) { Py_ssize_t len = PyList_GET_SIZE(self); - JS::RootedObject *rootedThisArg = nullptr; + JS::RootedObject rootedThisArg(cx); if (args.length() > 1) { JS::Value thisArg = args[1].get(); if (!thisArg.isObjectOrNull()) { @@ -902,7 +909,10 @@ static bool array_forEach(JSContext *cx, unsigned argc, JS::Value *vp) { } // TODO support null, currently gets TypeError - rootedThisArg = new JS::RootedObject(cx, thisArg.toObjectOrNull()); + rootedThisArg.set(thisArg.toObjectOrNull()); + } + else { + rootedThisArg.set(nullptr); } for (Py_ssize_t index = 0, toIndex = 0; index < len; index++) { @@ -911,8 +921,7 @@ static bool array_forEach(JSContext *cx, unsigned argc, JS::Value *vp) { jArgs[2].set(selfValue); if (args.length() > 1) { - if (!JS_CallFunctionValue(cx, *rootedThisArg, callBack, jArgs, &rval)) { - delete rootedThisArg; + if (!JS_CallFunctionValue(cx, rootedThisArg, callBack, jArgs, &rval)) { return false; } } @@ -923,10 +932,6 @@ static bool array_forEach(JSContext *cx, unsigned argc, JS::Value *vp) { } } - if (rootedThisArg) { - delete rootedThisArg; - } - args.rval().setUndefined(); return true; } @@ -963,7 +968,7 @@ static bool array_map(JSContext *cx, unsigned argc, JS::Value *vp) { JS::Rooted> jArgs(cx); JS::RootedValue rval(cx); - JS::RootedObject *rootedThisArg = nullptr; + JS::RootedObject rootedThisArg(cx); if (args.length() > 1) { JS::Value thisArg = args[1].get(); if (!thisArg.isObjectOrNull()) { @@ -972,7 +977,10 @@ static bool array_map(JSContext *cx, unsigned argc, JS::Value *vp) { } // TODO support null, currently gets TypeError - rootedThisArg = new JS::RootedObject(cx, thisArg.toObjectOrNull()); + rootedThisArg.set(thisArg.toObjectOrNull()); + } + else { + rootedThisArg.set(nullptr); } for (Py_ssize_t index = 0; index < len; index++) { @@ -981,8 +989,7 @@ static bool array_map(JSContext *cx, unsigned argc, JS::Value *vp) { jArgs[2].set(selfValue); if (args.length() > 1) { - if (!JS_CallFunctionValue(cx, *rootedThisArg, callBack, jArgs, &rval)) { - delete rootedThisArg; + if (!JS_CallFunctionValue(cx, rootedThisArg, callBack, jArgs, &rval)) { return false; } } @@ -995,10 +1002,6 @@ static bool array_map(JSContext *cx, unsigned argc, JS::Value *vp) { JS_SetElement(cx, rootedRetArray, index, rval); } - if (rootedThisArg) { - delete rootedThisArg; - } - args.rval().setObject(*retArray); return true; } @@ -1031,7 +1034,7 @@ static bool array_filter(JSContext *cx, unsigned argc, JS::Value *vp) { JS::RootedValueVector retVector(cx); - JS::RootedObject *rootedThisArg = nullptr; + JS::RootedObject rootedThisArg(cx); if (args.length() > 1) { JS::Value thisArg = args[1].get(); if (!thisArg.isObjectOrNull()) { @@ -1040,7 +1043,10 @@ static bool array_filter(JSContext *cx, unsigned argc, JS::Value *vp) { } // TODO support null, currently gets TypeError - rootedThisArg = new JS::RootedObject(cx, thisArg.toObjectOrNull()); + rootedThisArg.set(thisArg.toObjectOrNull()); + } + else { + rootedThisArg.set(nullptr); } Py_ssize_t len = PyList_GET_SIZE(self); @@ -1051,8 +1057,7 @@ static bool array_filter(JSContext *cx, unsigned argc, JS::Value *vp) { jArgs[2].set(selfValue); if (args.length() > 1) { - if (!JS_CallFunctionValue(cx, *rootedThisArg, callBack, jArgs, &rval)) { - delete rootedThisArg; + if (!JS_CallFunctionValue(cx, rootedThisArg, callBack, jArgs, &rval)) { return false; } } @@ -1069,10 +1074,6 @@ static bool array_filter(JSContext *cx, unsigned argc, JS::Value *vp) { } } - if (rootedThisArg) { - delete rootedThisArg; - } - JS::HandleValueArray jsValueArray(retVector); JSObject *retArray = JS::NewArrayObject(cx, jsValueArray); @@ -1218,7 +1219,7 @@ static bool array_some(JSContext *cx, unsigned argc, JS::Value *vp) { JS::Rooted> jArgs(cx); JS::RootedValue rval(cx); - JS::RootedObject *rootedThisArg = nullptr; + JS::RootedObject rootedThisArg(cx); if (args.length() > 1) { JS::Value thisArg = args[1].get(); if (!thisArg.isObjectOrNull()) { @@ -1227,7 +1228,10 @@ static bool array_some(JSContext *cx, unsigned argc, JS::Value *vp) { } // TODO support null, currently gets TypeError - rootedThisArg = new JS::RootedObject(cx, thisArg.toObjectOrNull()); + rootedThisArg.set(thisArg.toObjectOrNull()); + } + else { + rootedThisArg.set(nullptr); } Py_ssize_t len = PyList_GET_SIZE(self); @@ -1237,8 +1241,7 @@ static bool array_some(JSContext *cx, unsigned argc, JS::Value *vp) { jArgs[2].set(selfValue); if (args.length() > 1) { - if (!JS_CallFunctionValue(cx, *rootedThisArg, callBack, jArgs, &rval)) { - delete rootedThisArg; + if (!JS_CallFunctionValue(cx, rootedThisArg, callBack, jArgs, &rval)) { return false; } } @@ -1257,10 +1260,6 @@ static bool array_some(JSContext *cx, unsigned argc, JS::Value *vp) { } } - if (rootedThisArg) { - delete rootedThisArg; - } - args.rval().setBoolean(false); return true; } @@ -1291,7 +1290,7 @@ static bool array_every(JSContext *cx, unsigned argc, JS::Value *vp) { JS::Rooted> jArgs(cx); JS::RootedValue rval(cx); - JS::RootedObject *rootedThisArg = nullptr; + JS::RootedObject rootedThisArg(cx); if (args.length() > 1) { JS::Value thisArg = args[1].get(); if (!thisArg.isObjectOrNull()) { @@ -1300,7 +1299,10 @@ static bool array_every(JSContext *cx, unsigned argc, JS::Value *vp) { } // TODO support null, currently gets TypeError - rootedThisArg = new JS::RootedObject(cx, thisArg.toObjectOrNull()); + rootedThisArg.set(thisArg.toObjectOrNull()); + } + else { + rootedThisArg.set(nullptr); } Py_ssize_t len = PyList_GET_SIZE(self); @@ -1310,8 +1312,7 @@ static bool array_every(JSContext *cx, unsigned argc, JS::Value *vp) { jArgs[2].set(selfValue); if (args.length() > 1) { - if (!JS_CallFunctionValue(cx, *rootedThisArg, callBack, jArgs, &rval)) { - delete rootedThisArg; + if (!JS_CallFunctionValue(cx, rootedThisArg, callBack, jArgs, &rval)) { return false; } } @@ -1322,18 +1323,11 @@ static bool array_every(JSContext *cx, unsigned argc, JS::Value *vp) { } if (!rval.toBoolean()) { - if (rootedThisArg) { - delete rootedThisArg; - } args.rval().setBoolean(false); return true; } } - if (rootedThisArg) { - delete rootedThisArg; - } - args.rval().setBoolean(true); return true; } @@ -1364,7 +1358,7 @@ static bool array_find(JSContext *cx, unsigned argc, JS::Value *vp) { JS::Rooted> jArgs(cx); JS::RootedValue rval(cx); - JS::RootedObject *rootedThisArg = nullptr; + JS::RootedObject rootedThisArg(cx); if (args.length() > 1) { JS::Value thisArg = args[1].get(); if (!thisArg.isObjectOrNull()) { @@ -1373,7 +1367,10 @@ static bool array_find(JSContext *cx, unsigned argc, JS::Value *vp) { } // TODO support null, currently gets TypeError - rootedThisArg = new JS::RootedObject(cx, thisArg.toObjectOrNull()); + rootedThisArg.set(thisArg.toObjectOrNull()); + } + else { + rootedThisArg.set(nullptr); } Py_ssize_t len = PyList_GET_SIZE(self); @@ -1384,8 +1381,7 @@ static bool array_find(JSContext *cx, unsigned argc, JS::Value *vp) { jArgs[2].set(selfValue); if (args.length() > 1) { - if (!JS_CallFunctionValue(cx, *rootedThisArg, callBack, jArgs, &rval)) { - delete rootedThisArg; + if (!JS_CallFunctionValue(cx, rootedThisArg, callBack, jArgs, &rval)) { return false; } } @@ -1396,18 +1392,11 @@ static bool array_find(JSContext *cx, unsigned argc, JS::Value *vp) { } if (rval.toBoolean()) { - if (rootedThisArg) { - delete rootedThisArg; - } args.rval().set(item); return true; } } - if (rootedThisArg) { - delete rootedThisArg; - } - args.rval().setUndefined(); return true; } @@ -1438,7 +1427,7 @@ static bool array_findIndex(JSContext *cx, unsigned argc, JS::Value *vp) { JS::Rooted> jArgs(cx); JS::RootedValue rval(cx); - JS::RootedObject *rootedThisArg = nullptr; + JS::RootedObject rootedThisArg(cx); if (args.length() > 1) { JS::Value thisArg = args[1].get(); if (!thisArg.isObjectOrNull()) { @@ -1447,7 +1436,10 @@ static bool array_findIndex(JSContext *cx, unsigned argc, JS::Value *vp) { } // TODO support null, currently gets TypeError - rootedThisArg = new JS::RootedObject(cx, thisArg.toObjectOrNull()); + rootedThisArg.set(thisArg.toObjectOrNull()); + } + else { + rootedThisArg.set(nullptr); } Py_ssize_t len = PyList_GET_SIZE(self); @@ -1457,8 +1449,7 @@ static bool array_findIndex(JSContext *cx, unsigned argc, JS::Value *vp) { jArgs[2].set(selfValue); if (args.length() > 1) { - if (!JS_CallFunctionValue(cx, *rootedThisArg, callBack, jArgs, &rval)) { - delete rootedThisArg; + if (!JS_CallFunctionValue(cx, rootedThisArg, callBack, jArgs, &rval)) { return false; } } @@ -1469,18 +1460,11 @@ static bool array_findIndex(JSContext *cx, unsigned argc, JS::Value *vp) { } if (rval.toBoolean()) { - if (rootedThisArg) { - delete rootedThisArg; - } args.rval().setInt32(index); return true; } } - if (rootedThisArg) { - delete rootedThisArg; - } - args.rval().setInt32(-1); return true; } @@ -1879,7 +1863,7 @@ static JSClass listIteratorClass = {"ListIterator", JSCLASS_HAS_RESERVED_SLOTS(L static bool iterator_next(JSContext *cx, unsigned argc, JS::Value *vp) { JS::CallArgs args = JS::CallArgsFromVp(argc, vp); JS::RootedObject thisObj(cx); - if (!args.computeThis(cx, &thisObj)) return false; // TODO needed ? + if (!args.computeThis(cx, &thisObj)) return false; PyObject *self = JS::GetMaybePtrFromReservedSlot(thisObj, ListIteratorSlotIteratedObject); @@ -1891,7 +1875,6 @@ static bool iterator_next(JSContext *cx, unsigned argc, JS::Value *vp) { if (!JS::ToInt32(cx, rootedNextIndex, &nextIndex) || !JS::ToInt32(cx, rootedItemKind, &itemKind)) return false; JS::RootedObject result(cx, JS_NewPlainObject(cx)); - if (!result) return false; Py_ssize_t len = PyList_GET_SIZE(self); diff --git a/tests/python/test_arrays.py b/tests/python/test_arrays.py index de3279d5..8aea92b1 100644 --- a/tests/python/test_arrays.py +++ b/tests/python/test_arrays.py @@ -967,7 +967,7 @@ def test_filter_too_few_args(): assert str(type(e)) == "" assert str(e).__contains__("TypeError: filter: At least 1 argument required, but only 0 passed") -#reduce index, array param, wrong arg type, too few args same impl as previous few for all below +#reduce def test_reduce(): items = [1,2,3,4,5] result = [None] From a3efcd8f85fa6777df1112b57dd322a8c5aed8ad Mon Sep 17 00:00:00 2001 From: Philippe Laporte Date: Fri, 12 Jan 2024 17:34:26 -0500 Subject: [PATCH 49/58] sort done for lists as arrays --- include/jsTypeFactory.hh | 9 - src/JSArrayProxy.cc | 31 ++-- src/PyProxyHandler.cc | 317 ++++++++++++++++++++++++++---------- src/jsTypeFactory.cc | 19 --- tests/python/test_arrays.py | 58 +++++-- 5 files changed, 294 insertions(+), 140 deletions(-) diff --git a/include/jsTypeFactory.hh b/include/jsTypeFactory.hh index 23339c54..1ae6b131 100644 --- a/include/jsTypeFactory.hh +++ b/include/jsTypeFactory.hh @@ -38,15 +38,6 @@ size_t UCS4ToUTF16(const uint32_t *chars, size_t length, uint16_t *outStr); */ JS::Value jsTypeFactory(JSContext *cx, PyObject *object); -/** - * @brief Function that takes a PyObject and returns a corresponding JS::Value which is a copy, not a reference - * - * @param cx - Pointer to the JSContext - * @param object - Pointer to the PyListObject - * @return JS::Value - A JS::Value corresponding to the PyType - */ -JS::Value jsTypeFactoryCopy(JSContext *cx, PyObject *object); - /** * @brief same to jsTypeFactory, but it's guaranteed that no error would be set on the Python error stack, instead * return JS `null` on error, and output a warning in Python-land diff --git a/src/JSArrayProxy.cc b/src/JSArrayProxy.cc index f127c165..0d2c3c84 100644 --- a/src/JSArrayProxy.cc +++ b/src/JSArrayProxy.cc @@ -1085,17 +1085,17 @@ static bool sort_compare_key_func(JSContext *cx, unsigned argc, JS::Value *vp) { bool reverse = reverseValue.toBoolean(); - JS::RootedObject *global = new JS::RootedObject(GLOBAL_CX, JS::GetNonCCWObjectGlobal(&args.callee())); + JS::RootedObject *global = new JS::RootedObject(cx, JS::GetNonCCWObjectGlobal(&args.callee())); - JS::RootedValue *elementVal = new JS::RootedValue(GLOBAL_CX, args[0]); - PyObject *args_0 = pyTypeFactory(GLOBAL_CX, global, elementVal)->getPyObject(); + JS::RootedValue *elementVal = new JS::RootedValue(cx, args[0]); + PyObject *args_0 = pyTypeFactory(cx, global, elementVal)->getPyObject(); PyObject *args_0_result = PyObject_CallFunction(keyfunc, "O", args_0); if (!args_0_result) { return false; } - elementVal = new JS::RootedValue(GLOBAL_CX, args[1]); - PyObject *args_1 = pyTypeFactory(GLOBAL_CX, global, elementVal)->getPyObject(); + elementVal = new JS::RootedValue(cx, args[1]); + PyObject *args_1 = pyTypeFactory(cx, global, elementVal)->getPyObject(); PyObject *args_1_result = PyObject_CallFunction(keyfunc, "O", args_1); if (!args_1_result) { return false; @@ -1135,13 +1135,13 @@ static bool sort_compare_default(JSContext *cx, unsigned argc, JS::Value *vp) { } bool reverse = reverseValue.toBoolean(); - JS::RootedObject *global = new JS::RootedObject(GLOBAL_CX, JS::GetNonCCWObjectGlobal(&args.callee())); + JS::RootedObject *global = new JS::RootedObject(cx, JS::GetNonCCWObjectGlobal(&args.callee())); - JS::RootedValue *elementVal = new JS::RootedValue(GLOBAL_CX, args[0]); - PyObject *args_0 = pyTypeFactory(GLOBAL_CX, global, elementVal)->getPyObject(); + JS::RootedValue *elementVal = new JS::RootedValue(cx, args[0]); + PyObject *args_0 = pyTypeFactory(cx, global, elementVal)->getPyObject(); - elementVal = new JS::RootedValue(GLOBAL_CX, args[1]); - PyObject *args_1 = pyTypeFactory(GLOBAL_CX, global, elementVal)->getPyObject(); + elementVal = new JS::RootedValue(cx, args[1]); + PyObject *args_1 = pyTypeFactory(cx, global, elementVal)->getPyObject(); int cmp = PyObject_RichCompareBool(args_0, args_1, Py_LT); if (cmp > 0) { @@ -1251,6 +1251,17 @@ PyObject *JSArrayProxyMethodDefinitions::JSArrayProxy_sort(JSArrayProxy *self, P } return NULL; } + + // cleanup + if (!JS_DeleteProperty(GLOBAL_CX, funObj, "_key_func_param")) { + PyErr_Format(PyExc_SystemError, "%s JSAPI call failed", JSArrayProxyType.tp_name); + return NULL; + } + + if (!JS_DeleteProperty(GLOBAL_CX, funObj, "_reverse_param")) { + PyErr_Format(PyExc_SystemError, "%s JSAPI call failed", JSArrayProxyType.tp_name); + return NULL; + } } else { // two-arg js-style diff --git a/src/PyProxyHandler.cc b/src/PyProxyHandler.cc index cd15efab..774e43d8 100644 --- a/src/PyProxyHandler.cc +++ b/src/PyProxyHandler.cc @@ -499,88 +499,6 @@ static bool array_splice(JSContext *cx, unsigned argc, JS::Value *vp) { return true; } -static bool array_sort(JSContext *cx, unsigned argc, JS::Value *vp) { - JS::CallArgs args = JS::CallArgsFromVp(argc, vp); - - JS::RootedObject proxy(cx, JS::ToObject(cx, args.thisv())); - if (!proxy) { - return false; - } - PyObject *self = JS::GetMaybePtrFromReservedSlot(proxy, PyObjectSlot); - - uint64_t selfLength = (uint64_t)PyList_GET_SIZE(self); - - if (selfLength > 0) { - if (args.length() < 1) { - PyList_Sort(self); - } - else { - JS::Value callbackfn = args[0].get(); - - if (!callbackfn.isObject() || !JS::IsCallable(&callbackfn.toObject())) { - JS_ReportErrorNumberASCII(cx, js::GetErrorMessage, nullptr, JSMSG_BAD_SORT_ARG, "sort: callback"); - return false; - } - - JS::RootedObject *global = new JS::RootedObject(cx, JS::GetNonCCWObjectGlobal(proxy)); - - PyObject *pyFunc = pyTypeFactory(cx, global, new JS::RootedValue(cx, args[0].get()))->getPyObject(); - // check if JS or Python function - if (PyFunction_Check(pyFunc)) { - // it's a user-defined python function, more than 1-arg will get TypeError - PyObject *callable = PyObject_GetAttrString(self, "sort"); - if (callable == NULL) { - return false; - } - PyObject *result = PyObject_Call(callable, PyTuple_New(0), Py_BuildValue("{s:O}", "key", pyFunc)); - if (!result) { - return false; - } - } else { - // it's either a JS function or a builtin python func - int flags = PyCFunction_GetFlags(pyFunc); - - if (flags & METH_VARARGS && !(flags & METH_KEYWORDS)) { - // it's a JS func - - // We don't want to put in all the sort code so we'll tolerate the following slight O(n) inefficiency - - // copy to JS for sorting - JS::RootedObject selfCopy(cx, &jsTypeFactoryCopy(cx, self).toObject()); - - // sort - JS::RootedValue jReturnedArray(cx); - JS::HandleValueArray jArgs(args); - if (!JS_CallFunctionName(cx, selfCopy, "sort", jArgs, &jReturnedArray)) { - return false; - } - - // copy back into Python self - for (int index = 0; index < selfLength; index++) { - JS::RootedValue *elementVal = new JS::RootedValue(cx); - JS_GetElement(cx, selfCopy, index, elementVal); - PyList_SetItem(self, index, pyTypeFactory(cx, global, elementVal)->getPyObject()); - } - } else { - // it's a built-in python function, more than 1-arg will get TypeError - PyObject *callable = PyObject_GetAttrString(self, "sort"); - if (callable == NULL) { - return false; - } - PyObject *result = PyObject_Call(callable, PyTuple_New(0), Py_BuildValue("{s:O}", "key", pyFunc)); - if (!result) { - return false; - } - } - } - } - } - - // return ref to self - args.rval().set(jsTypeFactory(cx, self)); - return true; -} - static bool array_fill(JSContext *cx, unsigned argc, JS::Value *vp) { JS::CallArgs args = JS::CallArgsFromVp(argc, vp); @@ -1252,9 +1170,6 @@ static bool array_some(JSContext *cx, unsigned argc, JS::Value *vp) { } if (rval.toBoolean()) { - if (rootedThisArg) { - delete rootedThisArg; - } args.rval().setBoolean(true); return true; } @@ -1843,6 +1758,238 @@ static bool array_valueOf(JSContext *cx, unsigned argc, JS::Value *vp) { } +////// Sorting + + +static void swapItems(PyObject *list, int i, int j) { + if (i != j) { + PyObject *list_i = PyList_GetItem(list, i); + PyObject *list_j = PyList_GetItem(list, j); + Py_INCREF(list_i); + Py_INCREF(list_j); + PyList_SetItem(list, i, list_j); + PyList_SetItem(list, j, list_i); + } +} + +static int invokeCallBack(PyObject *list, int index, JS::HandleValue leftValue, JSContext *cx, JS::HandleFunction callBack) { + JS::Rooted> jArgs(cx); + + jArgs[0].set(jsTypeFactory(cx, PyList_GetItem(list, index))); + jArgs[1].set(leftValue); + + JS::RootedValue retVal(cx); + if (!JS_CallFunction(cx, nullptr, callBack, jArgs, &retVal)) { + throw "JS_CallFunction failed"; + } + + return retVal.toInt32(); +} + +static void quickSort(PyObject *list, int left, int right, JSContext *cx, JS::HandleFunction callBack) { + if (left >= right) { + // base case + return; + } + + swapItems(list, left, (left + right) / 2); + + JS::RootedValue leftValue(cx, jsTypeFactory(cx, PyList_GetItem(list, left))); + + int last = left; + for (int index = left + 1; index <= right; index++) { + if (invokeCallBack(list, index, leftValue, cx, callBack) < 0) { + swapItems(list, ++last, index); + } + } + + swapItems(list, left, last); + + quickSort(list, left, last - 1, cx, callBack); + + quickSort(list, last + 1, right, cx, callBack); +} + +// private +static bool js_sort_compare_default(JSContext *cx, unsigned argc, JS::Value *vp) { + JS::CallArgs args = JS::CallArgsFromVp(argc, vp); + + JS::RootedValue leftVal(cx, args[0]); + JS::RootedValue rightVal(cx, args[1]); + + // check for undefined + if (leftVal.isNullOrUndefined()) { + if (rightVal.isNullOrUndefined()) { + args.rval().setInt32(0); + } + else { + args.rval().setInt32(1); + } + return true; + } + else if (rightVal.isNullOrUndefined()) { + args.rval().setInt32(-1); + return true; + } + + JS::RootedObject leftObject(cx); + if (!JS_ValueToObject(cx, leftVal, &leftObject)) { + return false; + } + JS::RootedValue leftToStringVal(cx); + if (!JS_CallFunctionName(cx, leftObject, "toString", JS::HandleValueArray::empty(), &leftToStringVal)) { + return false; + } + + JS::RootedObject rightObject(cx); + if (!JS_ValueToObject(cx, rightVal, &rightObject)) { + return false; + } + JS::RootedValue rightToStringVal(cx); + if (!JS_CallFunctionName(cx, rightObject, "toString", JS::HandleValueArray::empty(), &rightToStringVal)) { + return false; + } + + int32_t cmpResult; + if (!JS_CompareStrings(cx, leftToStringVal.toString(), rightToStringVal.toString(), &cmpResult)) { + return false; + } + + args.rval().setInt32(cmpResult); + return true; +} + +// private +static bool js_sort_compare_key_func(JSContext *cx, unsigned argc, JS::Value *vp) { + JS::CallArgs args = JS::CallArgsFromVp(argc, vp); + + JS::RootedObject callee(cx, &args.callee()); + + JS::RootedValue keyFunc(cx); + if (!JS_GetProperty(cx, callee, "_key_func_param", &keyFunc)) { + PyErr_Format(PyExc_SystemError, "JSAPI call failed"); + return false; + } + PyObject *keyfunc = (PyObject *)keyFunc.toPrivate(); + + JS::RootedObject *global = new JS::RootedObject(cx, JS::GetNonCCWObjectGlobal(&args.callee())); + + JS::RootedValue *elementVal = new JS::RootedValue(cx, args[0]); + PyObject *args_0 = pyTypeFactory(cx, global, elementVal)->getPyObject(); + + elementVal = new JS::RootedValue(cx, args[1]); + PyObject *args_1 = pyTypeFactory(cx, global, elementVal)->getPyObject(); + + PyObject *result = PyObject_CallFunction(keyfunc, "OO", args_0, args_1); + if (!result) { + return false; + } + + if (PyLong_Check(result)) { + args.rval().setInt32((int32_t)PyLong_AsLongLong(result)); + return true; + } + else { + PyErr_Format(PyExc_TypeError, "incorrect compare function return type"); + return false; + } +} + +static bool array_sort(JSContext *cx, unsigned argc, JS::Value *vp) { + JS::CallArgs args = JS::CallArgsFromVp(argc, vp); + + JS::RootedObject proxy(cx, JS::ToObject(cx, args.thisv())); + if (!proxy) { + return false; + } + PyObject *self = JS::GetMaybePtrFromReservedSlot(proxy, PyObjectSlot); + + Py_ssize_t len = PyList_GET_SIZE(self); + + if (len > 1) { + if (args.length() < 1) { + JS::RootedFunction funObj(cx, JS_NewFunction(cx, js_sort_compare_default, 2, 0, NULL)); + + try { + quickSort(self, 0, len - 1, cx, funObj); + } catch (const char *message) { + return false; + } + } + else { + JS::Value callbackfn = args[0].get(); + + if (!callbackfn.isObject() || !JS::IsCallable(&callbackfn.toObject())) { + JS_ReportErrorNumberASCII(cx, js::GetErrorMessage, nullptr, JSMSG_BAD_SORT_ARG, "sort: callback"); + return false; + } + + JS::RootedValue callBack(cx, callbackfn); + + JS::RootedObject *global = new JS::RootedObject(cx, JS::GetNonCCWObjectGlobal(proxy)); + + PyObject *pyFunc = pyTypeFactory(cx, global, new JS::RootedValue(cx, args[0].get()))->getPyObject(); + // check if JS or Python function + if (PyFunction_Check(pyFunc)) { + + // it's a user-defined python function, check has two arguments + PyObject *code = PyFunction_GetCode(pyFunc); + if (((PyCodeObject *)code)->co_argcount == 1) { + JS_ReportErrorNumberASCII(cx, js::GetErrorMessage, nullptr, JSMSG_BAD_SORT_ARG); + return false; + } + else { + JSFunction *compareFun = JS_NewFunction(cx, js_sort_compare_key_func, 2, 0, NULL); + JS::RootedFunction rootedFun(cx, compareFun); + JS::RootedObject rootedFunObj(cx, JS_GetFunctionObject(compareFun)); + + JS::RootedValue privateValue(cx, JS::PrivateValue(pyFunc)); + if (!JS_SetProperty(cx, rootedFunObj, "_key_func_param", privateValue)) { // JS::SetReservedSlot(functionObj, KeyFuncSlot, JS::PrivateValue(keyfunc)); does not work + PyErr_Format(PyExc_SystemError, "JSAPI call failed"); + return NULL; + } + + try { + quickSort(self, 0, len - 1, cx, rootedFun); + } catch (const char *message) { + return false; + } + + // cleanup + if (!JS_DeleteProperty(cx, rootedFunObj, "_key_func_param")) { + PyErr_Format(PyExc_SystemError, "JSAPI call failed"); + return false; + } + } + } else { + // it's either a JS function or a builtin python func + int flags = PyCFunction_GetFlags(pyFunc); + + if (flags & METH_VARARGS && !(flags & METH_KEYWORDS)) { + // it's a user-defined JS func + JS::RootedFunction funObj(cx, JS_ValueToFunction(cx, callBack)); + + try { + quickSort(self, 0, len - 1, cx, funObj); + } catch (const char *message) { + return false; + } + } + else { + // it's a built-in python function + JS_ReportErrorNumberASCII(cx, js::GetErrorMessage, nullptr, JSMSG_BAD_SORT_ARG); + return false; + } + } + } + } + + // return ref to self + args.rval().set(jsTypeFactory(cx, self)); + return true; +} + + // ListIterator diff --git a/src/jsTypeFactory.cc b/src/jsTypeFactory.cc index a5696eca..3cbf8985 100644 --- a/src/jsTypeFactory.cc +++ b/src/jsTypeFactory.cc @@ -200,25 +200,6 @@ JS::Value jsTypeFactory(JSContext *cx, PyObject *object) { return returnType; } -JS::Value jsTypeFactoryCopy(JSContext *cx, PyObject *object) { - JS::RootedValue returnType(cx); - if (PyList_Check(object)) { - Py_ssize_t listSize = PyList_Size(object); - JSObject *array = JS::NewArrayObject(cx, listSize); - JS::RootedObject arrayObj(cx, array); - for (Py_ssize_t index = 0; index < listSize; index++) { - JS::RootedValue jsValue(cx, jsTypeFactorySafe(cx, PyList_GetItem(object, index))); - JS_SetElement(cx, arrayObj, index, jsValue); - } - returnType.setObject(*array); - } - else { - returnType.setUndefined(); - } - - return returnType; -} - JS::Value jsTypeFactorySafe(JSContext *cx, PyObject *object) { JS::Value v = jsTypeFactory(cx, object); if (PyErr_Occurred()) { diff --git a/tests/python/test_arrays.py b/tests/python/test_arrays.py index 8aea92b1..e037b307 100644 --- a/tests/python/test_arrays.py +++ b/tests/python/test_arrays.py @@ -684,6 +684,13 @@ def test_sort_empty(): assert result[0] is items assert items == [] +def test_sort_one(): + items = [5] + result = [None] + pm.eval("(result, arr) => {result[0] = arr.sort()}")(result, items) + assert result[0] is items + assert items == [5] + def test_sort_numbers(): items = [4,2,6,7] result = [None] @@ -694,9 +701,27 @@ def test_sort_numbers(): def test_sort_strings(): items = ['Four', 'Three', 'One'] pm.eval("(arr) => {arr.sort()}")(items) - assert items == ['Four', 'One', 'Three'] + assert items == ['Four', 'One', 'Three'] def test_sort_with_two_args_keyfunc(): + items = ['Four', 'Three', 'One'] + def myFunc(e, f): + return len(e) - len(f) + pm.eval("(arr, compareFun) => {arr.sort(compareFun)}")(items, myFunc) + assert items == ['One', 'Four', 'Three'] + +def test_sort_with_two_args_keyfunc_wrong_return_type(): + items = ['Four', 'Three', 'One'] + def myFunc(e,f): + return e + f + try: + pm.eval("(arr, compareFun) => {arr.sort(compareFun)}")(items, myFunc) + assert (False) + except Exception as e: + assert str(type(e)) == "" + assert str(e) == "incorrect compare function return type" + +def test_sort_with_two_args_keyfunc_wrong_data_type(): items = [4,2,6,7] def myFunc(e,f): return len(e) - len(f) @@ -705,28 +730,27 @@ def myFunc(e,f): assert (False) except Exception as e: assert str(type(e)) == "" - assert str(e).__contains__("myFunc() missing 1 required positional argument: 'f'") + assert str(e) == "object of type 'float' has no len()" def test_sort_with_one_arg_keyfunc(): items = ['Four', 'Three', 'One'] def myFunc(e): return len(e) - pm.eval("(arr, compareFun) => {arr.sort(compareFun)}")(items, myFunc) - assert items == ['One', 'Four', 'Three'] + try: + pm.eval("(arr, compareFun) => {arr.sort(compareFun)}")(items, myFunc) + assert (False) + except Exception as e: + assert str(type(e)) == "" + assert str(e).__contains__("invalid Array.prototype.sort argument") def test_sort_with_builtin_keyfunc(): items = ['Four', 'Three', 'One'] - pm.eval("(arr, compareFun) => {arr.sort(compareFun)}")(items, len) - assert items == ['One', 'Four', 'Three'] - -def test_sort_with_builtin_keyfunc_wrong_data_type(): - items = [4,2,6,7] try: pm.eval("(arr, compareFun) => {arr.sort(compareFun)}")(items, len) assert (False) except Exception as e: - assert str(type(e)) == "" - assert str(e).__contains__("object of type 'int' has no len()") + assert str(type(e)) == "" + assert str(e).__contains__("invalid Array.prototype.sort argument") def test_sort_with_js_func(): items = ['Four', 'Three', 'One'] @@ -736,12 +760,12 @@ def test_sort_with_js_func(): assert result[0] is items assert items == ['Four', 'One', 'Three'] -#def test_sort_numbers_tricky(): -# items = [1, 30, 4, 21, 100000] -# result = [None] -# pm.eval("(result, arr) => {result[0] = arr.sort()}")(result, items) -# assert result[0] is items -# assert items == [1, 100000, 21, 30, 4] +def test_sort_numbers_tricky(): + items = [1, 30, 4, 21, 100000] + result = [None] + pm.eval("(result, arr) => {result[0] = arr.sort()}")(result, items) + assert result[0] is items + assert items == [1, 100000, 21, 30, 4] def test_sort_with_js_func_wrong_data_type(): items = [4,2,6,7] From d489be8ac8fc476aa0a929788b84b9416109c948 Mon Sep 17 00:00:00 2001 From: Philippe Laporte Date: Fri, 12 Jan 2024 17:36:06 -0500 Subject: [PATCH 50/58] cleanup --- include/jsTypeFactory.hh | 1 - 1 file changed, 1 deletion(-) diff --git a/include/jsTypeFactory.hh b/include/jsTypeFactory.hh index 1ae6b131..6ec68412 100644 --- a/include/jsTypeFactory.hh +++ b/include/jsTypeFactory.hh @@ -37,7 +37,6 @@ size_t UCS4ToUTF16(const uint32_t *chars, size_t length, uint16_t *outStr); * @return JS::Value - A JS::Value corresponding to the PyType */ JS::Value jsTypeFactory(JSContext *cx, PyObject *object); - /** * @brief same to jsTypeFactory, but it's guaranteed that no error would be set on the Python error stack, instead * return JS `null` on error, and output a warning in Python-land From 931e772d436940b0370bee102a1c22f0247d3de1 Mon Sep 17 00:00:00 2001 From: Philippe Laporte Date: Fri, 12 Jan 2024 17:42:45 -0500 Subject: [PATCH 51/58] logic cleanup --- src/PyProxyHandler.cc | 78 ++++++++----------------------------------- 1 file changed, 14 insertions(+), 64 deletions(-) diff --git a/src/PyProxyHandler.cc b/src/PyProxyHandler.cc index 774e43d8..9ec8165f 100644 --- a/src/PyProxyHandler.cc +++ b/src/PyProxyHandler.cc @@ -838,15 +838,8 @@ static bool array_forEach(JSContext *cx, unsigned argc, JS::Value *vp) { jArgs[1].setInt32(index); jArgs[2].set(selfValue); - if (args.length() > 1) { - if (!JS_CallFunctionValue(cx, rootedThisArg, callBack, jArgs, &rval)) { - return false; - } - } - else { - if (!JS_CallFunctionValue(cx, nullptr, callBack, jArgs, &rval)) { - return false; - } + if (!JS_CallFunctionValue(cx, rootedThisArg, callBack, jArgs, &rval)) { + return false; } } @@ -906,15 +899,8 @@ static bool array_map(JSContext *cx, unsigned argc, JS::Value *vp) { jArgs[1].setInt32(index); jArgs[2].set(selfValue); - if (args.length() > 1) { - if (!JS_CallFunctionValue(cx, rootedThisArg, callBack, jArgs, &rval)) { - return false; - } - } - else { - if (!JS_CallFunctionValue(cx, nullptr, callBack, jArgs, &rval)) { - return false; - } + if (!JS_CallFunctionValue(cx, rootedThisArg, callBack, jArgs, &rval)) { + return false; } JS_SetElement(cx, rootedRetArray, index, rval); @@ -974,15 +960,8 @@ static bool array_filter(JSContext *cx, unsigned argc, JS::Value *vp) { jArgs[1].setInt32(index); jArgs[2].set(selfValue); - if (args.length() > 1) { - if (!JS_CallFunctionValue(cx, rootedThisArg, callBack, jArgs, &rval)) { - return false; - } - } - else { - if (!JS_CallFunctionValue(cx, nullptr, callBack, jArgs, &rval)) { - return false; - } + if (!JS_CallFunctionValue(cx, rootedThisArg, callBack, jArgs, &rval)) { + return false; } if (rval.toBoolean()) { @@ -1158,15 +1137,8 @@ static bool array_some(JSContext *cx, unsigned argc, JS::Value *vp) { jArgs[1].setInt32(index); jArgs[2].set(selfValue); - if (args.length() > 1) { - if (!JS_CallFunctionValue(cx, rootedThisArg, callBack, jArgs, &rval)) { - return false; - } - } - else { - if (!JS_CallFunctionValue(cx, nullptr, callBack, jArgs, &rval)) { - return false; - } + if (!JS_CallFunctionValue(cx, rootedThisArg, callBack, jArgs, &rval)) { + return false; } if (rval.toBoolean()) { @@ -1226,15 +1198,8 @@ static bool array_every(JSContext *cx, unsigned argc, JS::Value *vp) { jArgs[1].setInt32(index); jArgs[2].set(selfValue); - if (args.length() > 1) { - if (!JS_CallFunctionValue(cx, rootedThisArg, callBack, jArgs, &rval)) { - return false; - } - } - else { - if (!JS_CallFunctionValue(cx, nullptr, callBack, jArgs, &rval)) { - return false; - } + if (!JS_CallFunctionValue(cx, rootedThisArg, callBack, jArgs, &rval)) { + return false; } if (!rval.toBoolean()) { @@ -1295,15 +1260,8 @@ static bool array_find(JSContext *cx, unsigned argc, JS::Value *vp) { jArgs[1].setInt32(index); jArgs[2].set(selfValue); - if (args.length() > 1) { - if (!JS_CallFunctionValue(cx, rootedThisArg, callBack, jArgs, &rval)) { - return false; - } - } - else { - if (!JS_CallFunctionValue(cx, nullptr, callBack, jArgs, &rval)) { - return false; - } + if (!JS_CallFunctionValue(cx, rootedThisArg, callBack, jArgs, &rval)) { + return false; } if (rval.toBoolean()) { @@ -1363,15 +1321,8 @@ static bool array_findIndex(JSContext *cx, unsigned argc, JS::Value *vp) { jArgs[1].setInt32(index); jArgs[2].set(selfValue); - if (args.length() > 1) { - if (!JS_CallFunctionValue(cx, rootedThisArg, callBack, jArgs, &rval)) { - return false; - } - } - else { - if (!JS_CallFunctionValue(cx, nullptr, callBack, jArgs, &rval)) { - return false; - } + if (!JS_CallFunctionValue(cx, rootedThisArg, callBack, jArgs, &rval)) { + return false; } if (rval.toBoolean()) { @@ -1931,7 +1882,6 @@ static bool array_sort(JSContext *cx, unsigned argc, JS::Value *vp) { PyObject *pyFunc = pyTypeFactory(cx, global, new JS::RootedValue(cx, args[0].get()))->getPyObject(); // check if JS or Python function if (PyFunction_Check(pyFunc)) { - // it's a user-defined python function, check has two arguments PyObject *code = PyFunction_GetCode(pyFunc); if (((PyCodeObject *)code)->co_argcount == 1) { From e82e3de751af792860b45e6897d7b4bc9de36c18 Mon Sep 17 00:00:00 2001 From: Philippe Laporte Date: Fri, 12 Jan 2024 17:46:54 -0500 Subject: [PATCH 52/58] cleanup --- src/PyProxyHandler.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/PyProxyHandler.cc b/src/PyProxyHandler.cc index 9ec8165f..6cb048ed 100644 --- a/src/PyProxyHandler.cc +++ b/src/PyProxyHandler.cc @@ -1871,7 +1871,7 @@ static bool array_sort(JSContext *cx, unsigned argc, JS::Value *vp) { JS::Value callbackfn = args[0].get(); if (!callbackfn.isObject() || !JS::IsCallable(&callbackfn.toObject())) { - JS_ReportErrorNumberASCII(cx, js::GetErrorMessage, nullptr, JSMSG_BAD_SORT_ARG, "sort: callback"); + JS_ReportErrorNumberASCII(cx, js::GetErrorMessage, nullptr, JSMSG_BAD_SORT_ARG); return false; } From ff84e703b4e1374db3e4bba3bf4b7fcf797f27ac Mon Sep 17 00:00:00 2001 From: Philippe Laporte Date: Mon, 15 Jan 2024 15:11:13 -0500 Subject: [PATCH 53/58] improvements: instanceof Array now works on Python Lists, --- src/PyProxyHandler.cc | 14 +++----------- src/jsTypeFactory.cc | 8 ++++++-- tests/python/test_dicts.py | 10 ++++++---- tests/python/test_list_array.py | 20 +++++++++++++++++++- 4 files changed, 34 insertions(+), 18 deletions(-) diff --git a/src/PyProxyHandler.cc b/src/PyProxyHandler.cc index 6cb048ed..a8de98f2 100644 --- a/src/PyProxyHandler.cc +++ b/src/PyProxyHandler.cc @@ -1737,6 +1737,7 @@ static int invokeCallBack(PyObject *list, int index, JS::HandleValue leftValue, return retVal.toInt32(); } +// Adapted from Kernigan&Ritchie's C book static void quickSort(PyObject *list, int left, int right, JSContext *cx, JS::HandleFunction callBack) { if (left >= right) { // base case @@ -2195,20 +2196,11 @@ bool PyListProxyHandler::getOwnPropertyDescriptor( if (id.isString() && JS_StringEqualsLiteral(cx, id.toString(), "constructor", &isConstructorProperty) && isConstructorProperty) { JS::RootedObject global(cx, JS::GetNonCCWObjectGlobal(proxy)); - JS::RootedValue Array(cx); - if (!JS_GetProperty(cx, global, "Array", &Array)) { + JS::RootedObject rootedArrayPrototype(cx); + if (!JS_GetClassPrototype(cx, JSProto_Array, &rootedArrayPrototype)) { return false; } - JS::RootedObject rootedArray(cx, Array.toObjectOrNull()); - - JS::RootedValue Array_Prototype(cx); - if (!JS_GetProperty(cx, rootedArray, "prototype", &Array_Prototype)) { - return false; - } - - JS::RootedObject rootedArrayPrototype(cx, Array_Prototype.toObjectOrNull()); - JS::RootedValue Array_Prototype_Constructor(cx); if (!JS_GetProperty(cx, rootedArrayPrototype, "constructor", &Array_Prototype_Constructor)) { return false; diff --git a/src/jsTypeFactory.cc b/src/jsTypeFactory.cc index 3cbf8985..79b3c424 100644 --- a/src/jsTypeFactory.cc +++ b/src/jsTypeFactory.cc @@ -171,11 +171,15 @@ JS::Value jsTypeFactory(JSContext *cx, PyObject *object) { JS::RootedValue v(cx); JSObject *proxy; if (PyList_Check(object)) { - proxy = js::NewProxyObject(cx, new PyListProxyHandler(object), v, NULL); + JS::RootedObject arrayPrototype(cx); + JS_GetClassPrototype(cx, JSProto_Array, &arrayPrototype); // so that instanceof will work, not that prototype methods will + proxy = js::NewProxyObject(cx, new PyListProxyHandler(object), v, arrayPrototype.get()); JS::SetReservedSlot(proxy, PyObjectSlot, JS::PrivateValue(object)); // Py_INCREF(object); TODO } else { - proxy = js::NewProxyObject(cx, new PyProxyHandler(object), v, NULL); + JS::RootedObject objectPrototype(cx); + JS_GetClassPrototype(cx, JSProto_Object, &objectPrototype); // so that instanceof will work, not that prototype methods will + proxy = js::NewProxyObject(cx, new PyProxyHandler(object), v, objectPrototype.get()); } returnType.setObject(*proxy); } diff --git a/tests/python/test_dicts.py b/tests/python/test_dicts.py index 5e8bd4d2..1be75e61 100644 --- a/tests/python/test_dicts.py +++ b/tests/python/test_dicts.py @@ -133,9 +133,11 @@ def test_eval_objects_jsproxy_inplace_or_true_dict_left(): assert b == {'d': 6.0} assert True == pm.eval("(o) => Object.preventExtensions(o) === o")({ "abc": 1 }) -def test_eval_objects_proxy_proto(): - assert pm.null == pm.eval("(o) => Object.getPrototypeOf(o)")({}) - assert pm.null == pm.eval("(o) => Object.getPrototypeOf(o)")({ "abc": 1 }) +def test_instanceof_object(): + a = {'c':5.0} + result = [None] + pm.eval("(result, obj) => {result[0] = obj instanceof Object}")(result, a) + assert result[0] == True def test_eval_objects_proxy_iterate(): obj = pm.eval("({ a: 123, b: 'test' })") @@ -252,4 +254,4 @@ def test_eval_objects_jsproxy_inplace_or_true_dict_left(): b = pm.eval("({'d':6})") a |= b assert a == {'c': 5.0, 'd': 6.0} - assert b == {'d': 6.0} \ No newline at end of file + assert b == {'d': 6.0} \ No newline at end of file diff --git a/tests/python/test_list_array.py b/tests/python/test_list_array.py index 7d8182e8..476c3adf 100644 --- a/tests/python/test_list_array.py +++ b/tests/python/test_list_array.py @@ -18,4 +18,22 @@ def test_typeof_array(): items = [1, 2, 3] result = [None] pm.eval("(result, arr) => {result[0] = typeof arr}")(result, items) - assert result[0] == 'object' \ No newline at end of file + assert result[0] == 'object' + +def test_instanceof_array(): + items = [1, 2, 3] + result = [None] + pm.eval("(result, arr) => {result[0] = arr instanceof Array}")(result, items) + assert result[0] == True + +def test_instanceof_object(): + items = [1, 2, 3] + result = [None] + pm.eval("(result, arr) => {result[0] = arr instanceof Object}")(result, items) + assert result[0] == True + +def test_not_instanceof_string(): + items = [1, 2, 3] + result = [None] + pm.eval("(result, arr) => {result[0] = arr instanceof String}")(result, items) + assert result[0] == False \ No newline at end of file From 46046c9bbb8fc700744fad2bcb3d5a0464ee7656 Mon Sep 17 00:00:00 2001 From: Philippe Laporte Date: Mon, 15 Jan 2024 15:53:25 -0500 Subject: [PATCH 54/58] added getBuiltInClass for Dirt proxy --- src/JSObjectProxy.cc | 3 ++- src/PyProxyHandler.cc | 15 ++++++++------- src/jsTypeFactory.cc | 1 - 3 files changed, 10 insertions(+), 9 deletions(-) diff --git a/src/JSObjectProxy.cc b/src/JSObjectProxy.cc index a152550f..7e7e7bec 100644 --- a/src/JSObjectProxy.cc +++ b/src/JSObjectProxy.cc @@ -5,7 +5,7 @@ * @version 0.1 * @date 2023-06-26 * - * Copyright (c) 2023 Distributive Corp. + * Copyright (c) 2023-2024 Distributive Corp. * */ @@ -44,6 +44,7 @@ void JSObjectProxyMethodDefinitions::JSObjectProxy_dealloc(JSObjectProxy *self) { // TODO (Caleb Aikens): intentional override of PyDict_Type's tp_dealloc. Probably results in leaking dict memory self->jsObject.set(nullptr); + PyObject_GC_UnTrack(self); Py_TYPE(self)->tp_free((PyObject *)self); return; } diff --git a/src/PyProxyHandler.cc b/src/PyProxyHandler.cc index a8de98f2..0bf9e043 100644 --- a/src/PyProxyHandler.cc +++ b/src/PyProxyHandler.cc @@ -158,6 +158,12 @@ bool PyProxyHandler::defineProperty(JSContext *cx, JS::HandleObject proxy, return result.failInvalidDescriptor(); } +bool PyProxyHandler::getBuiltinClass(JSContext *cx, JS::HandleObject proxy, + js::ESClass *cls) const { + *cls = js::ESClass::Object; + return true; +} + bool PyBaseProxyHandler::getPrototypeIfOrdinary(JSContext *cx, JS::HandleObject proxy, bool *isOrdinary, JS::MutableHandleObject protop) const { @@ -2252,12 +2258,7 @@ bool PyListProxyHandler::getOwnPropertyDescriptor( } void PyListProxyHandler::finalize(JS::GCContext *gcx, JSObject *proxy) const { - // TODO - // PyObject *self = JS::GetMaybePtrFromReservedSlot(proxy, PyObjectSlot); - // printf("finalize self=%p\n", self); - // Py_DECREF(self); - - // JS::SetReservedSlot(proxy, PyObjectSlot, nullptr)); + JS::SetReservedSlot(proxy, PyObjectSlot, JS::PrivateValue(nullptr)); } bool PyListProxyHandler::defineProperty( @@ -2319,7 +2320,7 @@ bool PyListProxyHandler::isArray(JSContext *cx, JS::HandleObject proxy, JS::IsAr return true; } -bool PyListProxyHandler::getBuiltinClass(JSContext *cx, JS::Handle obj, js::ESClass *cls) const { +bool PyListProxyHandler::getBuiltinClass(JSContext *cx, JS::HandleObject proxy, js::ESClass *cls) const { *cls = js::ESClass::Array; return true; } \ No newline at end of file diff --git a/src/jsTypeFactory.cc b/src/jsTypeFactory.cc index 79b3c424..fba294d3 100644 --- a/src/jsTypeFactory.cc +++ b/src/jsTypeFactory.cc @@ -175,7 +175,6 @@ JS::Value jsTypeFactory(JSContext *cx, PyObject *object) { JS_GetClassPrototype(cx, JSProto_Array, &arrayPrototype); // so that instanceof will work, not that prototype methods will proxy = js::NewProxyObject(cx, new PyListProxyHandler(object), v, arrayPrototype.get()); JS::SetReservedSlot(proxy, PyObjectSlot, JS::PrivateValue(object)); - // Py_INCREF(object); TODO } else { JS::RootedObject objectPrototype(cx); JS_GetClassPrototype(cx, JSProto_Object, &objectPrototype); // so that instanceof will work, not that prototype methods will From 5020a5e1cdc2532743948872b20d11804cb43c3a Mon Sep 17 00:00:00 2001 From: Philippe Laporte Date: Mon, 15 Jan 2024 15:56:19 -0500 Subject: [PATCH 55/58] should have been part of previous commit --- include/PyProxyHandler.hh | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/include/PyProxyHandler.hh b/include/PyProxyHandler.hh index b95c4778..8bb99af8 100644 --- a/include/PyProxyHandler.hh +++ b/include/PyProxyHandler.hh @@ -165,6 +165,8 @@ public: JS::HandleId id, JS::Handle desc, JS::ObjectOpResult &result) const override; + + bool getBuiltinClass(JSContext *cx, JS::HandleObject proxy, js::ESClass *cls) const override; }; /** @@ -197,7 +199,7 @@ public: bool ownPropertyKeys(JSContext *cx, JS::HandleObject proxy, JS::MutableHandleIdVector props) const override; bool delete_(JSContext *cx, JS::HandleObject proxy, JS::HandleId id, JS::ObjectOpResult &result) const override; bool isArray(JSContext *cx, JS::HandleObject proxy, JS::IsArrayAnswer *answer) const override; - bool getBuiltinClass(JSContext *cx, JS::Handle obj, js::ESClass *cls) const override; + bool getBuiltinClass(JSContext *cx, JS::HandleObject proxy, js::ESClass *cls) const override; }; /** From 475708599154ad8972d8c1c16281e353a93da1ef Mon Sep 17 00:00:00 2001 From: philippedistributive Date: Wed, 24 Jan 2024 11:37:36 -0500 Subject: [PATCH 56/58] remove unused variable --- src/PyProxyHandler.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/PyProxyHandler.cc b/src/PyProxyHandler.cc index 0bf9e043..af79f477 100644 --- a/src/PyProxyHandler.cc +++ b/src/PyProxyHandler.cc @@ -839,7 +839,7 @@ static bool array_forEach(JSContext *cx, unsigned argc, JS::Value *vp) { rootedThisArg.set(nullptr); } - for (Py_ssize_t index = 0, toIndex = 0; index < len; index++) { + for (Py_ssize_t index = 0; index < len; index++) { jArgs[0].set(jsTypeFactory(cx, PyList_GetItem(self, index))); jArgs[1].setInt32(index); jArgs[2].set(selfValue); From ab460cacf964d913586780c6c3eb6ee7298d408f Mon Sep 17 00:00:00 2001 From: philippedistributive Date: Wed, 24 Jan 2024 11:46:55 -0500 Subject: [PATCH 57/58] proper way to turn on iterator helpers --- setup.sh | 1 - src/modules/pythonmonkey/pythonmonkey.cc | 5 ++++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/setup.sh b/setup.sh index 71655d2d..2b8c385e 100755 --- a/setup.sh +++ b/setup.sh @@ -53,7 +53,6 @@ sed -i'' -e '/"winheap.cpp"/d' ./memory/mozalloc/moz.build # https://bugzilla.mo sed -i'' -e 's/"install-name-tool"/"install_name_tool"/' ./moz.configure # `install-name-tool` does not exist, but we have `install_name_tool` sed -i'' -e 's/bool Unbox/JS_PUBLIC_API bool Unbox/g' ./js/public/Class.h # need to manually add JS_PUBLIC_API to js::Unbox until it gets fixed in Spidermonkey sed -i'' -e 's/bool js::Unbox/JS_PUBLIC_API bool js::Unbox/g' ./js/src/vm/JSObject.cpp # same here -sed -i'' -e 's/bool iteratorHelpers_ = false;/bool iteratorHelpers_ = true;/g' ./js/public/RealmOptions.h # turn this on, todo make it available to embedders, currently only available via command-line cd js/src mkdir -p _build cd _build diff --git a/src/modules/pythonmonkey/pythonmonkey.cc b/src/modules/pythonmonkey/pythonmonkey.cc index 6bfc106c..bbb9bf77 100644 --- a/src/modules/pythonmonkey/pythonmonkey.cc +++ b/src/modules/pythonmonkey/pythonmonkey.cc @@ -381,7 +381,10 @@ PyMODINIT_FUNC PyInit_pythonmonkey(void) return NULL; } - JS::RealmOptions options; + JS::RealmCreationOptions creationOptions = JS::RealmCreationOptions(); + JS::RealmBehaviors behaviours = JS::RealmBehaviors(); + creationOptions.setIteratorHelpersEnabled(true); + JS::RealmOptions options = JS::RealmOptions(creationOptions, behaviours); static JSClass globalClass = {"global", JSCLASS_GLOBAL_FLAGS, &JS::DefaultGlobalClassOps}; global = new JS::RootedObject(GLOBAL_CX, JS_NewGlobalObject(GLOBAL_CX, &globalClass, nullptr, JS::FireOnNewGlobalHook, options)); if (!global) { From 84f8412a3a8eed00ef01e9d433f85381218f7311 Mon Sep 17 00:00:00 2001 From: philippedistributive Date: Wed, 24 Jan 2024 12:01:54 -0500 Subject: [PATCH 58/58] enhanced tests --- tests/python/test_arrays.py | 180 ++++++++++++++++++++++++++++++++++-- 1 file changed, 174 insertions(+), 6 deletions(-) diff --git a/tests/python/test_arrays.py b/tests/python/test_arrays.py index e037b307..d8ccc977 100644 --- a/tests/python/test_arrays.py +++ b/tests/python/test_arrays.py @@ -649,6 +649,30 @@ def test_includes(): pm.eval("(result, arr) => {result[0] = arr.includes(1)}")(result, items) assert result[0] == True +def test_includes_start_index(): + items = [1,2,3] + result = [None] + pm.eval("(result, arr) => {result[0] = arr.includes(1, 1)}")(result, items) + assert result[0] == False + +def test_includes_start_index_negative(): + items = [1,2,3] + result = [None] + pm.eval("(result, arr) => {result[0] = arr.includes(1, -1)}")(result, items) + assert result[0] == False + +def test_includes_start_index_negative_large(): + items = [1,2,3] + result = [None] + pm.eval("(result, arr) => {result[0] = arr.includes(1, -10)}")(result, items) + assert result[0] == True + +def test_includes_start_index_large(): + items = [1,2,3] + result = [None] + pm.eval("(result, arr) => {result[0] = arr.includes(1, 10)}")(result, items) + assert result[0] == False + def test_includes_other_type(): items = [1,2,'Hi'] result = [None] @@ -844,7 +868,25 @@ def test_forEach_check_this_arg_wrong_type(): # returnResult = [0] # pm.eval("(returnResult, arr, func) => {returnResult[0] = arr.forEach(func)}")(returnResult, items, func) # assert items == ['to each his own', 'to each his own', 'to each his own'] -# assert returnResult == [None] +# assert returnResult == [None] + +#def test_forEach_self(): +# items = ['Four', 'Three', 'One'] +# class Counter: +# def __init__(self): +# self.count = 0 +# def increment(self): +# self.count += 1 + +# obj = Counter() +# result = pm.eval(""" +# (arr, increment, result) => { +# let jsObj = {count: 0} +# arr.forEach(increment, jsObj); +# return jsObj.count; +# } +# """)(items, obj.increment) +# assert result == 3 # TODO should not pass @@ -936,6 +978,24 @@ def test_map_check_array_mutation(): pm.eval("(result, arr) => {arr.map((element, index, array) => {array[0] = 'Ten'; result[0] = array})}")(result, items) assert result[0] == ['Ten', 'Three', 'One'] assert items == ['Ten', 'Three', 'One'] + +#def test_map_self(): +# items = ['Four', 'Three', 'One'] +# class Counter: +# def __init__(self): +# self.count = 0 +# def increment(self): +# self.count += 1 + +# obj = Counter() +# result = pm.eval(""" +# (arr, increment, result) => { +# let jsObj = {count: 0} +# arr.map(increment, jsObj); +# return jsObj.count; +# } +# """)(items, obj.increment) +# assert result == 3 #filter def test_filter(): @@ -989,7 +1049,25 @@ def test_filter_too_few_args(): assert (False) except Exception as e: assert str(type(e)) == "" - assert str(e).__contains__("TypeError: filter: At least 1 argument required, but only 0 passed") + assert str(e).__contains__("TypeError: filter: At least 1 argument required, but only 0 passed") + +#def test_filter_self(): +# items = ['Four', 'Three', 'One'] +# class Counter: +# def __init__(self): +# self.count = 0 +# def increment(self): +# self.count += 1 + +# obj = Counter() +# result = pm.eval(""" +# (arr, increment, result) => { +# let jsObj = {count: 0} +# arr.filter(increment, jsObj); +# return jsObj.count; +# } +# """)(items, obj.increment) +# assert result == 3 #reduce def test_reduce(): @@ -1164,6 +1242,24 @@ def test_some_truthy_conversion(): """)(result) assert result[0] == True +#def test_some_self(): +# items = ['Four', 'Three', 'One'] +# class Counter: +# def __init__(self): +# self.count = 0 +# def increment(self): +# self.count += 1 + +# obj = Counter() +# result = pm.eval(""" +# (arr, increment, result) => { +# let jsObj = {count: 0} +# arr.some(increment, jsObj); +# return jsObj.count; +# } +# """)(items, obj.increment) +# assert result == 3 + #every def test_every_true(): items = [2,4,6] @@ -1213,7 +1309,25 @@ class Counter { } """ )(result, items) - assert result == [1] + assert result == [1] + +#def test_every_self(): +# items = ['Four', 'Three', 'One'] +# class Counter: +# def __init__(self): +# self.count = 0 +# def increment(self): +# self.count += 1 + +# obj = Counter() +# result = pm.eval(""" +# (arr, increment, result) => { +# let jsObj = {count: 0} +# arr.every(increment, jsObj); +# return jsObj.count; +# } +# """)(items, obj.increment) +# assert result == 3 #find def test_find_found_once(): @@ -1270,7 +1384,25 @@ class Counter { } """ )(result, items) - assert result == [3] + assert result == [3] + +#def test_find_self(): +# items = ['Four', 'Three', 'One'] +# class Counter: +# def __init__(self): +# self.count = 0 +# def increment(self): +# self.count += 1 + +# obj = Counter() +# result = pm.eval(""" +# (arr, increment, result) => { +# let jsObj = {count: 0} +# arr.find(increment, jsObj); +# return jsObj.count; +# } +# """)(items, obj.increment) +# assert result == 3 #findIndex def test_findIndex_found_once(): @@ -1327,7 +1459,25 @@ class Counter { } """ )(result, items) - assert result == [3] + assert result == [3] + +#def test_findIndex_self(): +# items = ['Four', 'Three', 'One'] +# class Counter: +# def __init__(self): +# self.count = 0 +# def increment(self): +# self.count += 1 + +# obj = Counter() +# result = pm.eval(""" +# (arr, increment, result) => { +# let jsObj = {count: 0} +# arr.findIndex(increment, jsObj); +# return jsObj.count; +# } +# """)(items, obj.increment) +# assert result == 3 #flat def test_flat(): @@ -1442,7 +1592,25 @@ class Counter { } """ )(result, items) - assert result == [3] + assert result == [3] + +#def test_flatMap_self(): +# items = ['Four', 'Three', 'One'] +# class Counter: +# def __init__(self): +# self.count = 0 +# def increment(self): +# self.count += 1 + +# obj = Counter() +# result = pm.eval(""" +# (arr, increment, result) => { +# let jsObj = {count: 0} +# arr.flatMap(increment, jsObj); +# return jsObj.count; +# } +# """)(items, obj.increment) +# assert result == 3 #valueOf def test_valueOf():