From 09d6350f3b7b91e305c30c98d5b17c0853f904e4 Mon Sep 17 00:00:00 2001 From: Karl Nelson Date: Sun, 25 Aug 2024 23:03:37 -0700 Subject: [PATCH 01/13] Work on Python 3.13 --- native/python/pyjp_class.cpp | 5 ++++ native/python/pyjp_value.cpp | 55 +++++++++--------------------------- 2 files changed, 19 insertions(+), 41 deletions(-) diff --git a/native/python/pyjp_class.cpp b/native/python/pyjp_class.cpp index fc633e11..4b0b5820 100644 --- a/native/python/pyjp_class.cpp +++ b/native/python/pyjp_class.cpp @@ -125,6 +125,11 @@ PyObject *PyJPClass_new(PyTypeObject *type, PyObject *args, PyObject *kwargs) typenew->tp_new = PyJPException_Type->tp_new; } ((PyJPClass*) typenew)->m_Doc = nullptr; + + // This flag will try to place the dictionary are part of the object which + // adds an unknown number of bytes to the end of the object making it impossible + // to attach our needed data. If we kill the flag then we get usable behavior. + typenew->tp_flags &= ~Py_TPFLAGS_INLINE_VALUES; return (PyObject*) typenew; JP_PY_CATCH(nullptr); } diff --git a/native/python/pyjp_value.cpp b/native/python/pyjp_value.cpp index 1deecaa9..370049db 100644 --- a/native/python/pyjp_value.cpp +++ b/native/python/pyjp_value.cpp @@ -16,6 +16,7 @@ #include "jpype.h" #include "pyjp.h" #include "jp_stringtype.h" +#include #ifdef __cplusplus extern "C" @@ -40,42 +41,15 @@ extern "C" PyObject* PyJPValue_alloc(PyTypeObject* type, Py_ssize_t nitems) { JP_PY_TRY("PyJPValue_alloc"); - // Modification from Python to add size elements - const size_t size = _PyObject_VAR_SIZE(type, nitems + 1) + sizeof (JPValue); - PyObject *obj = nullptr; - if (PyType_IS_GC(type)) - { - // Horrible kludge because python lacks an API for allocating a GC type with extra memory - // The private method _PyObject_GC_Alloc is no longer visible, so we are forced to allocate - // a different type with the extra memory and then hot swap the type to the real one. - PyTypeObject type2; - type2.tp_basicsize = size; - type2.tp_itemsize = 0; - type2.tp_name = nullptr; - type2.tp_flags = type->tp_flags; - type2.tp_traverse = type->tp_traverse; - - // Allocate the fake type - obj = PyObject_GC_New(PyObject, &type2); - - // Note the object will be inited twice which should not leak. (fingers crossed) - } - else - { - obj = (PyObject*) PyObject_MALLOC(size); - } - if (obj == nullptr) - return PyErr_NoMemory(); // GCOVR_EXCL_LINE - memset(obj, 0, size); - - Py_ssize_t refcnt = ((PyObject*) type)->ob_refcnt; - obj->ob_type = type; - if (type->tp_itemsize == 0) - PyObject_Init(obj, type); - else - PyObject_InitVar((PyVarObject *) obj, type, nitems); + // 1) allocate memory (+pre +inline) + // 2) gc link + // 3) init (set type, ref type, set ob_size) + // 4) set up inline dict past the length of object (if inline) + type->tp_basicsize += sizeof(JPValue); + PyObject* obj = PyType_GenericAlloc(type, nitems); + type->tp_basicsize -= sizeof(JPValue); // This line is required to deal with Python bug (GH-11661) // Some versions of Python fail to increment the reference counter of @@ -83,10 +57,6 @@ PyObject* PyJPValue_alloc(PyTypeObject* type, Py_ssize_t nitems) if (refcnt == ((PyObject*) type)->ob_refcnt) Py_INCREF(type); // GCOVR_EXCL_LINE - if (PyType_IS_GC(type)) - { - PyObject_GC_Track(obj); - } JP_TRACE("alloc", type->tp_name, obj); return obj; JP_PY_CATCH(nullptr); @@ -107,17 +77,20 @@ Py_ssize_t PyJPValue_getJavaSlotOffset(PyObject* self) if (type == nullptr || type->tp_alloc != (allocfunc) PyJPValue_alloc || type->tp_finalize != (destructor) PyJPValue_finalize) + { return 0; - Py_ssize_t offset; - Py_ssize_t sz = 0; + } + Py_ssize_t offset = 0; + Py_ssize_t sz = 0; + #if PY_VERSION_HEX>=0x030c0000 // starting in 3.12 there is no longer ob_size in PyLong if (PyType_HasFeature(self->ob_type, Py_TPFLAGS_LONG_SUBCLASS)) sz = (((PyLongObject*)self)->long_value.lv_tag) >> 3; // Private NON_SIZE_BITS else #endif - if (type->tp_itemsize != 0) + if (type->tp_itemsize != 0) sz = Py_SIZE(self); // PyLong abuses ob_size with negative values prior to 3.12 if (sz < 0) From f0ed92c302e96955770efc6a871c30d1c43c3d14 Mon Sep 17 00:00:00 2001 From: Karl Nelson Date: Sun, 25 Aug 2024 23:28:27 -0700 Subject: [PATCH 02/13] Porting backward --- native/python/pyjp_class.cpp | 2 ++ native/python/pyjp_value.cpp | 31 +++++++++++++++++++++++++------ 2 files changed, 27 insertions(+), 6 deletions(-) diff --git a/native/python/pyjp_class.cpp b/native/python/pyjp_class.cpp index 4b0b5820..4cdb8f1a 100644 --- a/native/python/pyjp_class.cpp +++ b/native/python/pyjp_class.cpp @@ -126,10 +126,12 @@ PyObject *PyJPClass_new(PyTypeObject *type, PyObject *args, PyObject *kwargs) } ((PyJPClass*) typenew)->m_Doc = nullptr; +#if PY_VERSION_HEX >= 0x030d0000 // This flag will try to place the dictionary are part of the object which // adds an unknown number of bytes to the end of the object making it impossible // to attach our needed data. If we kill the flag then we get usable behavior. typenew->tp_flags &= ~Py_TPFLAGS_INLINE_VALUES; +#endif return (PyObject*) typenew; JP_PY_CATCH(nullptr); } diff --git a/native/python/pyjp_value.cpp b/native/python/pyjp_value.cpp index 370049db..0a28ee36 100644 --- a/native/python/pyjp_value.cpp +++ b/native/python/pyjp_value.cpp @@ -41,8 +41,32 @@ extern "C" PyObject* PyJPValue_alloc(PyTypeObject* type, Py_ssize_t nitems) { JP_PY_TRY("PyJPValue_alloc"); + +#if PY_VERSION_HEX<0x030c0000 Py_ssize_t refcnt = ((PyObject*) type)->ob_refcnt; + // Modification from Python to add size elements + const size_t size = _PyObject_VAR_SIZE(type, nitems + 1) + sizeof (JPValue); + PyObject *obj = (PyType_IS_GC(type)) ? _PyObject_GC_Malloc(size) + : (PyObject *) PyObject_MALLOC(size); + if (obj == NULL) + return PyErr_NoMemory(); // GCOVR_EXCL_LINE + memset(obj, 0, size); + + if (type->tp_itemsize == 0) + PyObject_Init(obj, type); + else + PyObject_InitVar((PyVarObject *) obj, type, nitems); + + // This line is required to deal with Python bug (GH-11661) + // Some versions of Python fail to increment the reference counter of + // heap types properly. + if (refcnt == ((PyObject*) type)->ob_refcnt) + Py_INCREF(type); // GCOVR_EXCL_LINE + + if (PyType_IS_GC(type)) + PyObject_GC_Track(obj); +#else // 1) allocate memory (+pre +inline) // 2) gc link // 3) init (set type, ref type, set ob_size) @@ -50,12 +74,7 @@ PyObject* PyJPValue_alloc(PyTypeObject* type, Py_ssize_t nitems) type->tp_basicsize += sizeof(JPValue); PyObject* obj = PyType_GenericAlloc(type, nitems); type->tp_basicsize -= sizeof(JPValue); - - // This line is required to deal with Python bug (GH-11661) - // Some versions of Python fail to increment the reference counter of - // heap types properly. - if (refcnt == ((PyObject*) type)->ob_refcnt) - Py_INCREF(type); // GCOVR_EXCL_LINE +#endif JP_TRACE("alloc", type->tp_name, obj); return obj; From 897e60c3d718890630ebfa84034f59773d22df86 Mon Sep 17 00:00:00 2001 From: Karl Nelson Date: Sun, 25 Aug 2024 23:59:41 -0700 Subject: [PATCH 03/13] Fix for 3.11 --- native/python/pyjp_value.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/native/python/pyjp_value.cpp b/native/python/pyjp_value.cpp index 0a28ee36..f796c535 100644 --- a/native/python/pyjp_value.cpp +++ b/native/python/pyjp_value.cpp @@ -42,7 +42,7 @@ PyObject* PyJPValue_alloc(PyTypeObject* type, Py_ssize_t nitems) { JP_PY_TRY("PyJPValue_alloc"); -#if PY_VERSION_HEX<0x030c0000 +#if PY_VERSION_HEX<0x030b0000 Py_ssize_t refcnt = ((PyObject*) type)->ob_refcnt; // Modification from Python to add size elements const size_t size = _PyObject_VAR_SIZE(type, nitems + 1) + sizeof (JPValue); From 141b09dd8ef25f82f72db0183b7518ff02098bcb Mon Sep 17 00:00:00 2001 From: "Martin K. Scherer" Date: Wed, 28 Aug 2024 10:43:24 +0200 Subject: [PATCH 04/13] [ci] added Python 3.13 to matrix --- .azure/build.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.azure/build.yml b/.azure/build.yml index 1ebe2455..629444ca 100644 --- a/.azure/build.yml +++ b/.azure/build.yml @@ -71,6 +71,10 @@ jobs: imageName: "ubuntu-latest" python.version: '3.12' jdk.version: '17' + linux-py3.13-jdk17: + imageName: "ubuntu-latest" + python.version: '3.13' + jdk.version: '17' # Windows windows-py3.8-jdk8: imageName: "windows-2019" From ff1454c7fd82b9576836582ffbe7abc33937c19a Mon Sep 17 00:00:00 2001 From: "Martin K. Scherer" Date: Wed, 28 Aug 2024 10:51:38 +0200 Subject: [PATCH 05/13] try to use 3.13-rc1 --- .azure/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.azure/build.yml b/.azure/build.yml index 629444ca..a8bf0c1b 100644 --- a/.azure/build.yml +++ b/.azure/build.yml @@ -73,7 +73,7 @@ jobs: jdk.version: '17' linux-py3.13-jdk17: imageName: "ubuntu-latest" - python.version: '3.13' + python.version: '3.13.0-rc.1' jdk.version: '17' # Windows windows-py3.8-jdk8: From 6cc959fb1937e05de71f4f658bf00737845aeab0 Mon Sep 17 00:00:00 2001 From: Karl Nelson Date: Sat, 14 Sep 2024 12:15:15 -0700 Subject: [PATCH 06/13] Unstable API call to create memory for out object --- native/python/pyjp_value.cpp | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/native/python/pyjp_value.cpp b/native/python/pyjp_value.cpp index f796c535..b5281dac 100644 --- a/native/python/pyjp_value.cpp +++ b/native/python/pyjp_value.cpp @@ -67,13 +67,9 @@ PyObject* PyJPValue_alloc(PyTypeObject* type, Py_ssize_t nitems) PyObject_GC_Track(obj); #else - // 1) allocate memory (+pre +inline) - // 2) gc link - // 3) init (set type, ref type, set ob_size) - // 4) set up inline dict past the length of object (if inline) - type->tp_basicsize += sizeof(JPValue); - PyObject* obj = PyType_GenericAlloc(type, nitems); - type->tp_basicsize -= sizeof(JPValue); + PyObject* obj = PyUnstable_Object_GC_NewWithExtraData(type, size - _PyObject_SIZE(tp)); + if (type->tp_itemsize > 0) { + Py_SET_SIZE((PyVarObject*) obj, nitems); #endif JP_TRACE("alloc", type->tp_name, obj); From c115b76c73b2551668a071e6ecd05d5b577b9e8a Mon Sep 17 00:00:00 2001 From: Karl Nelson Date: Sat, 14 Sep 2024 13:26:02 -0700 Subject: [PATCH 07/13] Second attempt at hack --- native/python/pyjp_value.cpp | 32 +++++++++++++++++++++++--------- 1 file changed, 23 insertions(+), 9 deletions(-) diff --git a/native/python/pyjp_value.cpp b/native/python/pyjp_value.cpp index b5281dac..8b7b9de5 100644 --- a/native/python/pyjp_value.cpp +++ b/native/python/pyjp_value.cpp @@ -44,10 +44,10 @@ PyObject* PyJPValue_alloc(PyTypeObject* type, Py_ssize_t nitems) #if PY_VERSION_HEX<0x030b0000 Py_ssize_t refcnt = ((PyObject*) type)->ob_refcnt; - // Modification from Python to add size elements const size_t size = _PyObject_VAR_SIZE(type, nitems + 1) + sizeof (JPValue); + // Modification from Python to add size elements PyObject *obj = (PyType_IS_GC(type)) ? _PyObject_GC_Malloc(size) - : (PyObject *) PyObject_MALLOC(size); + : (PyObject *) PyObject_MALLOC(size); if (obj == NULL) return PyErr_NoMemory(); // GCOVR_EXCL_LINE memset(obj, 0, size); @@ -67,9 +67,23 @@ PyObject* PyJPValue_alloc(PyTypeObject* type, Py_ssize_t nitems) PyObject_GC_Track(obj); #else - PyObject* obj = PyUnstable_Object_GC_NewWithExtraData(type, size - _PyObject_SIZE(tp)); - if (type->tp_itemsize > 0) { - Py_SET_SIZE((PyVarObject*) obj, nitems); + + // 1) allocate memory (+pre +inline) + // 2) gc link + // 3) init (set type, ref type, set ob_size) + // 4) set up inline dict past the length of object (if inline) + PyObject* obj = nullptr; + if (type->tp_itemsize != 0) { + Py_ssize_t extra = (sizeof(JPValue))/type->tp_itemsize; + if (extra == 0) + extra = 1; + obj = PyType_GenericAlloc(type, nitems+extra); + Py_SET_SIZE(obj, nitems); + } + else { + // why do we need twice the allocation. Not sure, nothing in our logic says that it should be necessary + obj = PyUnstable_Object_GC_NewWithExtraData(type, 2*sizeof(JPValue)); + } #endif JP_TRACE("alloc", type->tp_name, obj); @@ -92,13 +106,13 @@ Py_ssize_t PyJPValue_getJavaSlotOffset(PyObject* self) if (type == nullptr || type->tp_alloc != (allocfunc) PyJPValue_alloc || type->tp_finalize != (destructor) PyJPValue_finalize) - { + { return 0; - } + } Py_ssize_t offset = 0; Py_ssize_t sz = 0; - + #if PY_VERSION_HEX>=0x030c0000 // starting in 3.12 there is no longer ob_size in PyLong if (PyType_HasFeature(self->ob_type, Py_TPFLAGS_LONG_SUBCLASS)) @@ -209,7 +223,7 @@ PyObject* PyJPValue_str(PyObject* self) Py_INCREF(cache); return cache; } - auto jstr = (jstring) value->getValue().l; + auto jstr = (jstring) value->getValue().l; string str; str = frame.toStringUTF8(jstr); cache = JPPyString::fromStringUTF8(str).keep(); From bf4f5592f258c838c4b4090a6130ab1a68285e3c Mon Sep 17 00:00:00 2001 From: Karl Nelson Date: Sat, 14 Sep 2024 15:35:40 -0700 Subject: [PATCH 08/13] I give up --- native/python/pyjp_value.cpp | 19 +- native/python/pyjp_value2.cpp | 331 ++++++++++++++++++++++++++++++++++ 2 files changed, 345 insertions(+), 5 deletions(-) create mode 100644 native/python/pyjp_value2.cpp diff --git a/native/python/pyjp_value.cpp b/native/python/pyjp_value.cpp index 8b7b9de5..629f8983 100644 --- a/native/python/pyjp_value.cpp +++ b/native/python/pyjp_value.cpp @@ -66,14 +66,23 @@ PyObject* PyJPValue_alloc(PyTypeObject* type, Py_ssize_t nitems) if (PyType_IS_GC(type)) PyObject_GC_Track(obj); +#elif PY_VERSION_HEX<0x030c0000 + // At some point Python 3.11 backported changes corresponding to Python 3.12 + // without the support for allocation of method, and with bugs. We have + // to mutilate the base size of the object to get the extra memory we need + // And the size we must request appears to be larger tha needed. This + // coresponds to the same bug in PyUnstable_Object_GC_NewWithExtraData. + // + // This is a horrible hack and I can't guarantee anything about the stability + // of it! + type->tp_basicsize += 2*sizeof(JPValue); + PyObject* obj = PyType_GenericAlloc(type, nitems); + type->tp_basicsize -= 2*sizeof(JPValue); #else - - // 1) allocate memory (+pre +inline) - // 2) gc link - // 3) init (set type, ref type, set ob_size) - // 4) set up inline dict past the length of object (if inline) PyObject* obj = nullptr; if (type->tp_itemsize != 0) { + // There is no corresponding PyUnstable_VarObject_GC_NewWithExtraData method + // Without one we are just going to aske for our needed memory though backdoor methods. Py_ssize_t extra = (sizeof(JPValue))/type->tp_itemsize; if (extra == 0) extra = 1; diff --git a/native/python/pyjp_value2.cpp b/native/python/pyjp_value2.cpp new file mode 100644 index 00000000..0a28ee36 --- /dev/null +++ b/native/python/pyjp_value2.cpp @@ -0,0 +1,331 @@ +/***************************************************************************** + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + See NOTICE file for details. + *****************************************************************************/ +#include "jpype.h" +#include "pyjp.h" +#include "jp_stringtype.h" +#include + +#ifdef __cplusplus +extern "C" +{ +#endif + +/** + * Allocate a new Python object with a slot for Java. + * + * We need extra space to store our values, but because there + * is no way to do so without disturbing the object layout. + * Fortunately, Python already handles this for dict and weakref. + * Python aligns the ends of the structure and increases the + * base type size to add additional slots to a standard object. + * + * We will use the same trick to add an additional slot for Java + * after the end of the object outside of where Python is looking. + * As the memory is aligned this is safe to do. We will use + * the alloc and finalize slot to recognize which objects have this + * extra slot appended. + */ +PyObject* PyJPValue_alloc(PyTypeObject* type, Py_ssize_t nitems) +{ + JP_PY_TRY("PyJPValue_alloc"); + +#if PY_VERSION_HEX<0x030c0000 + Py_ssize_t refcnt = ((PyObject*) type)->ob_refcnt; + // Modification from Python to add size elements + const size_t size = _PyObject_VAR_SIZE(type, nitems + 1) + sizeof (JPValue); + PyObject *obj = (PyType_IS_GC(type)) ? _PyObject_GC_Malloc(size) + : (PyObject *) PyObject_MALLOC(size); + if (obj == NULL) + return PyErr_NoMemory(); // GCOVR_EXCL_LINE + memset(obj, 0, size); + + if (type->tp_itemsize == 0) + PyObject_Init(obj, type); + else + PyObject_InitVar((PyVarObject *) obj, type, nitems); + + // This line is required to deal with Python bug (GH-11661) + // Some versions of Python fail to increment the reference counter of + // heap types properly. + if (refcnt == ((PyObject*) type)->ob_refcnt) + Py_INCREF(type); // GCOVR_EXCL_LINE + + if (PyType_IS_GC(type)) + PyObject_GC_Track(obj); + +#else + // 1) allocate memory (+pre +inline) + // 2) gc link + // 3) init (set type, ref type, set ob_size) + // 4) set up inline dict past the length of object (if inline) + type->tp_basicsize += sizeof(JPValue); + PyObject* obj = PyType_GenericAlloc(type, nitems); + type->tp_basicsize -= sizeof(JPValue); +#endif + + JP_TRACE("alloc", type->tp_name, obj); + return obj; + JP_PY_CATCH(nullptr); +} + +bool PyJPValue_hasJavaSlot(PyTypeObject* type) +{ + if (type == nullptr + || type->tp_alloc != (allocfunc) PyJPValue_alloc + || type->tp_finalize != (destructor) PyJPValue_finalize) + return false; // GCOVR_EXCL_LINE + return true; +} + +Py_ssize_t PyJPValue_getJavaSlotOffset(PyObject* self) +{ + PyTypeObject *type = Py_TYPE(self); + if (type == nullptr + || type->tp_alloc != (allocfunc) PyJPValue_alloc + || type->tp_finalize != (destructor) PyJPValue_finalize) + { + return 0; + } + + Py_ssize_t offset = 0; + Py_ssize_t sz = 0; + +#if PY_VERSION_HEX>=0x030c0000 + // starting in 3.12 there is no longer ob_size in PyLong + if (PyType_HasFeature(self->ob_type, Py_TPFLAGS_LONG_SUBCLASS)) + sz = (((PyLongObject*)self)->long_value.lv_tag) >> 3; // Private NON_SIZE_BITS + else +#endif + if (type->tp_itemsize != 0) + sz = Py_SIZE(self); + // PyLong abuses ob_size with negative values prior to 3.12 + if (sz < 0) + sz = -sz; + if (type->tp_itemsize == 0) + offset = _PyObject_VAR_SIZE(type, 1); + else + offset = _PyObject_VAR_SIZE(type, sz + 1); + return offset; +} + +/** + * Get the Java value if attached. + * + * The Java class is guaranteed not to be nullptr on success. + * + * @param obj + * @return the Java value or 0 if not found. + */ +JPValue* PyJPValue_getJavaSlot(PyObject* self) +{ + Py_ssize_t offset = PyJPValue_getJavaSlotOffset(self); + if (offset == 0) + return nullptr; + auto value = (JPValue*) (((char*) self) + offset); + if (value->getClass() == nullptr) + return nullptr; + return value; +} + +void PyJPValue_free(void* obj) +{ + JP_PY_TRY("PyJPValue_free", obj); + // Normally finalize is not run on simple classes. + PyTypeObject *type = Py_TYPE(obj); + if (type->tp_finalize != nullptr) + type->tp_finalize((PyObject*) obj); + if (type->tp_flags & Py_TPFLAGS_HAVE_GC) + PyObject_GC_Del(obj); + else + PyObject_Free(obj); // GCOVR_EXCL_LINE + JP_PY_CATCH_NONE(); +} + +void PyJPValue_finalize(void* obj) +{ + JP_PY_TRY("PyJPValue_finalize", obj); + JP_TRACE("type", Py_TYPE(obj)->tp_name); + JPValue* value = PyJPValue_getJavaSlot((PyObject*) obj); + if (value == nullptr) + return; + JPContext *context = JPContext_global; + if (context == nullptr || !context->isRunning()) + return; + JPJavaFrame frame = JPJavaFrame::outer(context); + JPClass* cls = value->getClass(); + // This one can't check for initialized because we may need to delete a stale + // resource after shutdown. + if (cls != nullptr && context->isRunning() && !cls->isPrimitive()) + { + JP_TRACE("Value", cls->getCanonicalName(), &(value->getValue())); + JP_TRACE("Dereference object"); + context->ReleaseGlobalRef(value->getValue().l); + *value = JPValue(); + } + JP_PY_CATCH_NONE(); +} + +/** This is the way to convert an object into a python string. */ +PyObject* PyJPValue_str(PyObject* self) +{ + JP_PY_TRY("PyJPValue_str", self); + JPContext *context = PyJPModule_getContext(); + JPJavaFrame frame = JPJavaFrame::outer(context); + JPValue* value = PyJPValue_getJavaSlot(self); + if (value == nullptr) + { + PyErr_SetString(PyExc_TypeError, "Not a Java value"); + return nullptr; + } + + JPClass* cls = value->getClass(); + if (cls->isPrimitive()) + { + PyErr_SetString(PyExc_TypeError, "toString requires a Java object"); + return nullptr; + } + + if (value->getValue().l == nullptr) + return JPPyString::fromStringUTF8("null").keep(); + + if (cls == context->_java_lang_String) + { + PyObject *cache; + JPPyObject dict = JPPyObject::accept(PyObject_GenericGetDict(self, nullptr)); + if (!dict.isNull()) + { + cache = PyDict_GetItemString(dict.get(), "_jstr"); + if (cache) + { + Py_INCREF(cache); + return cache; + } + auto jstr = (jstring) value->getValue().l; + string str; + str = frame.toStringUTF8(jstr); + cache = JPPyString::fromStringUTF8(str).keep(); + PyDict_SetItemString(dict.get(), "_jstr", cache); + return cache; + } + } + + // In general toString is not immutable, so we won't cache it. + return JPPyString::fromStringUTF8(frame.toString(value->getValue().l)).keep(); + JP_PY_CATCH(nullptr); +} + +PyObject *PyJPValue_getattro(PyObject *obj, PyObject *name) +{ + JP_PY_TRY("PyJPObject_getattro"); + if (!PyUnicode_Check(name)) + { + PyErr_Format(PyExc_TypeError, + "attribute name must be string, not '%.200s'", + Py_TYPE(name)->tp_name); + return nullptr; + } + + // Private members are accessed directly + PyObject* pyattr = PyBaseObject_Type.tp_getattro(obj, name); + if (pyattr == nullptr) + return nullptr; + JPPyObject attr = JPPyObject::accept(pyattr); + + // Private members go regardless + if (PyUnicode_GetLength(name) && PyUnicode_ReadChar(name, 0) == '_') + return attr.keep(); + + // Methods + if (Py_TYPE(attr.get()) == (PyTypeObject*) PyJPMethod_Type) + return attr.keep(); + + // Don't allow properties to be rewritten + if (!PyObject_IsInstance(attr.get(), (PyObject*) & PyProperty_Type)) + return attr.keep(); + + PyErr_Format(PyExc_AttributeError, "Field '%U' is static", name); + return nullptr; + JP_PY_CATCH(nullptr); +} + +int PyJPValue_setattro(PyObject *self, PyObject *name, PyObject *value) +{ + JP_PY_TRY("PyJPObject_setattro"); + + // Private members are accessed directly + if (PyUnicode_GetLength(name) && PyUnicode_ReadChar(name, 0) == '_') + return PyObject_GenericSetAttr(self, name, value); + JPPyObject f = JPPyObject::accept(PyJP_GetAttrDescriptor(Py_TYPE(self), name)); + if (f.isNull()) + { + PyErr_Format(PyExc_AttributeError, "Field '%U' is not found", name); + return -1; + } + descrsetfunc desc = Py_TYPE(f.get())->tp_descr_set; + if (desc != nullptr) + return desc(f.get(), self, value); + + // Not a descriptor + PyErr_Format(PyExc_AttributeError, + "Field '%U' is not settable on Java '%s' object", name, Py_TYPE(self)->tp_name); + return -1; + JP_PY_CATCH(-1); +} + +#ifdef __cplusplus +} +#endif + +// These are from the internal methods when we already have the jvalue + +void PyJPValue_assignJavaSlot(JPJavaFrame &frame, PyObject* self, const JPValue& value) +{ + Py_ssize_t offset = PyJPValue_getJavaSlotOffset(self); + // GCOVR_EXCL_START + if (offset == 0) + { + std::stringstream ss; + ss << "Missing Java slot on `" << Py_TYPE(self)->tp_name << "`"; + JP_RAISE(PyExc_SystemError, ss.str()); + } + // GCOVR_EXCL_STOP + + auto* slot = (JPValue*) (((char*) self) + offset); + // GCOVR_EXCL_START + // This is a sanity check that should never trigger in normal operations. + if (slot->getClass() != nullptr) + { + JP_RAISE(PyExc_SystemError, "Slot assigned twice"); + } + // GCOVR_EXCL_STOP + JPClass* cls = value.getClass(); + if (cls != nullptr && !cls->isPrimitive()) + { + jvalue q; + q.l = frame.NewGlobalRef(value.getValue().l); + *slot = JPValue(cls, q); + } else + *slot = value; +} + +bool PyJPValue_isSetJavaSlot(PyObject* self) +{ + Py_ssize_t offset = PyJPValue_getJavaSlotOffset(self); + if (offset == 0) + return false; // GCOVR_EXCL_LINE + auto* slot = (JPValue*) (((char*) self) + offset); + return slot->getClass() != nullptr; +} From fff9e6b9adde78c1c49d0f750a3bbfe59a9a940c Mon Sep 17 00:00:00 2001 From: Karl Nelson Date: Sat, 14 Sep 2024 15:37:37 -0700 Subject: [PATCH 09/13] Remove file used for comparison --- native/python/pyjp_value2.cpp | 331 ---------------------------------- 1 file changed, 331 deletions(-) delete mode 100644 native/python/pyjp_value2.cpp diff --git a/native/python/pyjp_value2.cpp b/native/python/pyjp_value2.cpp deleted file mode 100644 index 0a28ee36..00000000 --- a/native/python/pyjp_value2.cpp +++ /dev/null @@ -1,331 +0,0 @@ -/***************************************************************************** - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - - See NOTICE file for details. - *****************************************************************************/ -#include "jpype.h" -#include "pyjp.h" -#include "jp_stringtype.h" -#include - -#ifdef __cplusplus -extern "C" -{ -#endif - -/** - * Allocate a new Python object with a slot for Java. - * - * We need extra space to store our values, but because there - * is no way to do so without disturbing the object layout. - * Fortunately, Python already handles this for dict and weakref. - * Python aligns the ends of the structure and increases the - * base type size to add additional slots to a standard object. - * - * We will use the same trick to add an additional slot for Java - * after the end of the object outside of where Python is looking. - * As the memory is aligned this is safe to do. We will use - * the alloc and finalize slot to recognize which objects have this - * extra slot appended. - */ -PyObject* PyJPValue_alloc(PyTypeObject* type, Py_ssize_t nitems) -{ - JP_PY_TRY("PyJPValue_alloc"); - -#if PY_VERSION_HEX<0x030c0000 - Py_ssize_t refcnt = ((PyObject*) type)->ob_refcnt; - // Modification from Python to add size elements - const size_t size = _PyObject_VAR_SIZE(type, nitems + 1) + sizeof (JPValue); - PyObject *obj = (PyType_IS_GC(type)) ? _PyObject_GC_Malloc(size) - : (PyObject *) PyObject_MALLOC(size); - if (obj == NULL) - return PyErr_NoMemory(); // GCOVR_EXCL_LINE - memset(obj, 0, size); - - if (type->tp_itemsize == 0) - PyObject_Init(obj, type); - else - PyObject_InitVar((PyVarObject *) obj, type, nitems); - - // This line is required to deal with Python bug (GH-11661) - // Some versions of Python fail to increment the reference counter of - // heap types properly. - if (refcnt == ((PyObject*) type)->ob_refcnt) - Py_INCREF(type); // GCOVR_EXCL_LINE - - if (PyType_IS_GC(type)) - PyObject_GC_Track(obj); - -#else - // 1) allocate memory (+pre +inline) - // 2) gc link - // 3) init (set type, ref type, set ob_size) - // 4) set up inline dict past the length of object (if inline) - type->tp_basicsize += sizeof(JPValue); - PyObject* obj = PyType_GenericAlloc(type, nitems); - type->tp_basicsize -= sizeof(JPValue); -#endif - - JP_TRACE("alloc", type->tp_name, obj); - return obj; - JP_PY_CATCH(nullptr); -} - -bool PyJPValue_hasJavaSlot(PyTypeObject* type) -{ - if (type == nullptr - || type->tp_alloc != (allocfunc) PyJPValue_alloc - || type->tp_finalize != (destructor) PyJPValue_finalize) - return false; // GCOVR_EXCL_LINE - return true; -} - -Py_ssize_t PyJPValue_getJavaSlotOffset(PyObject* self) -{ - PyTypeObject *type = Py_TYPE(self); - if (type == nullptr - || type->tp_alloc != (allocfunc) PyJPValue_alloc - || type->tp_finalize != (destructor) PyJPValue_finalize) - { - return 0; - } - - Py_ssize_t offset = 0; - Py_ssize_t sz = 0; - -#if PY_VERSION_HEX>=0x030c0000 - // starting in 3.12 there is no longer ob_size in PyLong - if (PyType_HasFeature(self->ob_type, Py_TPFLAGS_LONG_SUBCLASS)) - sz = (((PyLongObject*)self)->long_value.lv_tag) >> 3; // Private NON_SIZE_BITS - else -#endif - if (type->tp_itemsize != 0) - sz = Py_SIZE(self); - // PyLong abuses ob_size with negative values prior to 3.12 - if (sz < 0) - sz = -sz; - if (type->tp_itemsize == 0) - offset = _PyObject_VAR_SIZE(type, 1); - else - offset = _PyObject_VAR_SIZE(type, sz + 1); - return offset; -} - -/** - * Get the Java value if attached. - * - * The Java class is guaranteed not to be nullptr on success. - * - * @param obj - * @return the Java value or 0 if not found. - */ -JPValue* PyJPValue_getJavaSlot(PyObject* self) -{ - Py_ssize_t offset = PyJPValue_getJavaSlotOffset(self); - if (offset == 0) - return nullptr; - auto value = (JPValue*) (((char*) self) + offset); - if (value->getClass() == nullptr) - return nullptr; - return value; -} - -void PyJPValue_free(void* obj) -{ - JP_PY_TRY("PyJPValue_free", obj); - // Normally finalize is not run on simple classes. - PyTypeObject *type = Py_TYPE(obj); - if (type->tp_finalize != nullptr) - type->tp_finalize((PyObject*) obj); - if (type->tp_flags & Py_TPFLAGS_HAVE_GC) - PyObject_GC_Del(obj); - else - PyObject_Free(obj); // GCOVR_EXCL_LINE - JP_PY_CATCH_NONE(); -} - -void PyJPValue_finalize(void* obj) -{ - JP_PY_TRY("PyJPValue_finalize", obj); - JP_TRACE("type", Py_TYPE(obj)->tp_name); - JPValue* value = PyJPValue_getJavaSlot((PyObject*) obj); - if (value == nullptr) - return; - JPContext *context = JPContext_global; - if (context == nullptr || !context->isRunning()) - return; - JPJavaFrame frame = JPJavaFrame::outer(context); - JPClass* cls = value->getClass(); - // This one can't check for initialized because we may need to delete a stale - // resource after shutdown. - if (cls != nullptr && context->isRunning() && !cls->isPrimitive()) - { - JP_TRACE("Value", cls->getCanonicalName(), &(value->getValue())); - JP_TRACE("Dereference object"); - context->ReleaseGlobalRef(value->getValue().l); - *value = JPValue(); - } - JP_PY_CATCH_NONE(); -} - -/** This is the way to convert an object into a python string. */ -PyObject* PyJPValue_str(PyObject* self) -{ - JP_PY_TRY("PyJPValue_str", self); - JPContext *context = PyJPModule_getContext(); - JPJavaFrame frame = JPJavaFrame::outer(context); - JPValue* value = PyJPValue_getJavaSlot(self); - if (value == nullptr) - { - PyErr_SetString(PyExc_TypeError, "Not a Java value"); - return nullptr; - } - - JPClass* cls = value->getClass(); - if (cls->isPrimitive()) - { - PyErr_SetString(PyExc_TypeError, "toString requires a Java object"); - return nullptr; - } - - if (value->getValue().l == nullptr) - return JPPyString::fromStringUTF8("null").keep(); - - if (cls == context->_java_lang_String) - { - PyObject *cache; - JPPyObject dict = JPPyObject::accept(PyObject_GenericGetDict(self, nullptr)); - if (!dict.isNull()) - { - cache = PyDict_GetItemString(dict.get(), "_jstr"); - if (cache) - { - Py_INCREF(cache); - return cache; - } - auto jstr = (jstring) value->getValue().l; - string str; - str = frame.toStringUTF8(jstr); - cache = JPPyString::fromStringUTF8(str).keep(); - PyDict_SetItemString(dict.get(), "_jstr", cache); - return cache; - } - } - - // In general toString is not immutable, so we won't cache it. - return JPPyString::fromStringUTF8(frame.toString(value->getValue().l)).keep(); - JP_PY_CATCH(nullptr); -} - -PyObject *PyJPValue_getattro(PyObject *obj, PyObject *name) -{ - JP_PY_TRY("PyJPObject_getattro"); - if (!PyUnicode_Check(name)) - { - PyErr_Format(PyExc_TypeError, - "attribute name must be string, not '%.200s'", - Py_TYPE(name)->tp_name); - return nullptr; - } - - // Private members are accessed directly - PyObject* pyattr = PyBaseObject_Type.tp_getattro(obj, name); - if (pyattr == nullptr) - return nullptr; - JPPyObject attr = JPPyObject::accept(pyattr); - - // Private members go regardless - if (PyUnicode_GetLength(name) && PyUnicode_ReadChar(name, 0) == '_') - return attr.keep(); - - // Methods - if (Py_TYPE(attr.get()) == (PyTypeObject*) PyJPMethod_Type) - return attr.keep(); - - // Don't allow properties to be rewritten - if (!PyObject_IsInstance(attr.get(), (PyObject*) & PyProperty_Type)) - return attr.keep(); - - PyErr_Format(PyExc_AttributeError, "Field '%U' is static", name); - return nullptr; - JP_PY_CATCH(nullptr); -} - -int PyJPValue_setattro(PyObject *self, PyObject *name, PyObject *value) -{ - JP_PY_TRY("PyJPObject_setattro"); - - // Private members are accessed directly - if (PyUnicode_GetLength(name) && PyUnicode_ReadChar(name, 0) == '_') - return PyObject_GenericSetAttr(self, name, value); - JPPyObject f = JPPyObject::accept(PyJP_GetAttrDescriptor(Py_TYPE(self), name)); - if (f.isNull()) - { - PyErr_Format(PyExc_AttributeError, "Field '%U' is not found", name); - return -1; - } - descrsetfunc desc = Py_TYPE(f.get())->tp_descr_set; - if (desc != nullptr) - return desc(f.get(), self, value); - - // Not a descriptor - PyErr_Format(PyExc_AttributeError, - "Field '%U' is not settable on Java '%s' object", name, Py_TYPE(self)->tp_name); - return -1; - JP_PY_CATCH(-1); -} - -#ifdef __cplusplus -} -#endif - -// These are from the internal methods when we already have the jvalue - -void PyJPValue_assignJavaSlot(JPJavaFrame &frame, PyObject* self, const JPValue& value) -{ - Py_ssize_t offset = PyJPValue_getJavaSlotOffset(self); - // GCOVR_EXCL_START - if (offset == 0) - { - std::stringstream ss; - ss << "Missing Java slot on `" << Py_TYPE(self)->tp_name << "`"; - JP_RAISE(PyExc_SystemError, ss.str()); - } - // GCOVR_EXCL_STOP - - auto* slot = (JPValue*) (((char*) self) + offset); - // GCOVR_EXCL_START - // This is a sanity check that should never trigger in normal operations. - if (slot->getClass() != nullptr) - { - JP_RAISE(PyExc_SystemError, "Slot assigned twice"); - } - // GCOVR_EXCL_STOP - JPClass* cls = value.getClass(); - if (cls != nullptr && !cls->isPrimitive()) - { - jvalue q; - q.l = frame.NewGlobalRef(value.getValue().l); - *slot = JPValue(cls, q); - } else - *slot = value; -} - -bool PyJPValue_isSetJavaSlot(PyObject* self) -{ - Py_ssize_t offset = PyJPValue_getJavaSlotOffset(self); - if (offset == 0) - return false; // GCOVR_EXCL_LINE - auto* slot = (JPValue*) (((char*) self) + offset); - return slot->getClass() != nullptr; -} From 6bea925142d0940b96dc48714898a08e8004ca54 Mon Sep 17 00:00:00 2001 From: Karl Nelson Date: Sun, 15 Sep 2024 10:12:08 -0700 Subject: [PATCH 10/13] Switch to another hack --- native/python/pyjp_module.cpp | 2 + native/python/pyjp_value.cpp | 113 +++++++++++++++++++--------------- 2 files changed, 64 insertions(+), 51 deletions(-) diff --git a/native/python/pyjp_module.cpp b/native/python/pyjp_module.cpp index a2110efe..80d87f61 100644 --- a/native/python/pyjp_module.cpp +++ b/native/python/pyjp_module.cpp @@ -37,6 +37,7 @@ extern void PyJPNumber_initType(PyObject* module); extern void PyJPClassHints_initType(PyObject* module); extern void PyJPPackage_initType(PyObject* module); extern void PyJPChar_initType(PyObject* module); +extern void PyJPValue_initType(PyObject* module); static PyObject *PyJPModule_convertBuffer(JPPyBuffer& buffer, PyObject *dtype); @@ -739,6 +740,7 @@ PyMODINIT_FUNC PyInit__jpype() PyJPClassMagic = PyDict_New(); // Initialize each of the python extension types + PyJPValue_initType(module); PyJPClass_initType(module); PyJPObject_initType(module); diff --git a/native/python/pyjp_value.cpp b/native/python/pyjp_value.cpp index 629f8983..35cd780c 100644 --- a/native/python/pyjp_value.cpp +++ b/native/python/pyjp_value.cpp @@ -23,6 +23,9 @@ extern "C" { #endif +// Create a dummy type which we will use only for allocation +PyTypeObject* PyJPAlloc_Type = nullptr; + /** * Allocate a new Python object with a slot for Java. * @@ -42,58 +45,31 @@ PyObject* PyJPValue_alloc(PyTypeObject* type, Py_ssize_t nitems) { JP_PY_TRY("PyJPValue_alloc"); -#if PY_VERSION_HEX<0x030b0000 - Py_ssize_t refcnt = ((PyObject*) type)->ob_refcnt; - const size_t size = _PyObject_VAR_SIZE(type, nitems + 1) + sizeof (JPValue); - // Modification from Python to add size elements - PyObject *obj = (PyType_IS_GC(type)) ? _PyObject_GC_Malloc(size) - : (PyObject *) PyObject_MALLOC(size); - if (obj == NULL) - return PyErr_NoMemory(); // GCOVR_EXCL_LINE - memset(obj, 0, size); - - if (type->tp_itemsize == 0) - PyObject_Init(obj, type); - else - PyObject_InitVar((PyVarObject *) obj, type, nitems); - - // This line is required to deal with Python bug (GH-11661) - // Some versions of Python fail to increment the reference counter of - // heap types properly. - if (refcnt == ((PyObject*) type)->ob_refcnt) - Py_INCREF(type); // GCOVR_EXCL_LINE - - if (PyType_IS_GC(type)) - PyObject_GC_Track(obj); - -#elif PY_VERSION_HEX<0x030c0000 - // At some point Python 3.11 backported changes corresponding to Python 3.12 - // without the support for allocation of method, and with bugs. We have - // to mutilate the base size of the object to get the extra memory we need - // And the size we must request appears to be larger tha needed. This - // coresponds to the same bug in PyUnstable_Object_GC_NewWithExtraData. - // - // This is a horrible hack and I can't guarantee anything about the stability - // of it! - type->tp_basicsize += 2*sizeof(JPValue); - PyObject* obj = PyType_GenericAlloc(type, nitems); - type->tp_basicsize -= 2*sizeof(JPValue); -#else - PyObject* obj = nullptr; - if (type->tp_itemsize != 0) { - // There is no corresponding PyUnstable_VarObject_GC_NewWithExtraData method - // Without one we are just going to aske for our needed memory though backdoor methods. - Py_ssize_t extra = (sizeof(JPValue))/type->tp_itemsize; - if (extra == 0) - extra = 1; - obj = PyType_GenericAlloc(type, nitems+extra); - Py_SET_SIZE(obj, nitems); - } - else { - // why do we need twice the allocation. Not sure, nothing in our logic says that it should be necessary - obj = PyUnstable_Object_GC_NewWithExtraData(type, 2*sizeof(JPValue)); - } +#if PY_VERSION_HEX >= 0x030d0000 + // This flag will try to place the dictionary are part of the object which + // adds an unknown number of bytes to the end of the object making it impossible + // to attach our needed data. If we kill the flag then we get usable behavior. + if (PyType_HasFeature(type, Py_TPFLAGS_INLINE_VALUES)) { + PyErr_Format(PyExc_RuntimeError, "Unhandled object layout"); + return 0; + } #endif + + // Mutate the allocator type + PyJPAlloc_Type->tp_flags = type->tp_flags; + PyJPAlloc_Type->tp_basicsize = type->tp_basicsize + sizeof (JPValue); + PyJPAlloc_Type->tp_itemsize = type->tp_itemsize; + + // Create a new allocation for the dummy type + PyObject* obj = PyType_GenericAlloc(PyJPAlloc_Type, nitems); + + // Watch for memory errors + if (obj == nullptr) + return nullptr; + + // Polymorph the type to match our desired type + obj->ob_type = type; + Py_INCREF(type); JP_TRACE("alloc", type->tp_name, obj); return obj; @@ -348,3 +324,38 @@ bool PyJPValue_isSetJavaSlot(PyObject* self) auto* slot = (JPValue*) (((char*) self) + offset); return slot->getClass() != nullptr; } + +/***************** Create a dummy type for use when allocating. ************************/ +static int PyJPAlloc_traverse(PyObject *self, visitproc visit, void *arg) +{ + return 0; +} + +static int PyJPAlloc_clear(PyObject *self) +{ + return 0; +} + + +static PyType_Slot allocSlots[] = { + { Py_tp_traverse, (void*) PyJPAlloc_traverse}, + { Py_tp_clear, (void*) PyJPAlloc_clear}, + {0, NULL} // Sentinel +}; + +static PyType_Spec allocSpec = { + "_jpype._JAlloc", + sizeof(PyObject), + 0, + Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE | Py_TPFLAGS_HAVE_GC, + allocSlots +}; + +void PyJPValue_initType(PyObject* module) +{ + PyObject *bases = PyTuple_Pack(1, &PyBaseObject_Type); + PyJPAlloc_Type = (PyTypeObject*) PyType_FromSpecWithBases(&allocSpec, bases); + Py_DECREF(bases); + Py_INCREF(PyJPAlloc_Type); + JP_PY_CHECK(); +} From bde9e6b0df5f4d67b585a02c09e8ed38871ccd38 Mon Sep 17 00:00:00 2001 From: Karl Nelson Date: Sun, 15 Sep 2024 10:18:52 -0700 Subject: [PATCH 11/13] Fix tabs --- native/python/pyjp_value.cpp | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/native/python/pyjp_value.cpp b/native/python/pyjp_value.cpp index 35cd780c..07243c9a 100644 --- a/native/python/pyjp_value.cpp +++ b/native/python/pyjp_value.cpp @@ -50,26 +50,26 @@ PyObject* PyJPValue_alloc(PyTypeObject* type, Py_ssize_t nitems) // adds an unknown number of bytes to the end of the object making it impossible // to attach our needed data. If we kill the flag then we get usable behavior. if (PyType_HasFeature(type, Py_TPFLAGS_INLINE_VALUES)) { - PyErr_Format(PyExc_RuntimeError, "Unhandled object layout"); - return 0; - } + PyErr_Format(PyExc_RuntimeError, "Unhandled object layout"); + return 0; + } #endif - // Mutate the allocator type - PyJPAlloc_Type->tp_flags = type->tp_flags; - PyJPAlloc_Type->tp_basicsize = type->tp_basicsize + sizeof (JPValue); - PyJPAlloc_Type->tp_itemsize = type->tp_itemsize; + // Mutate the allocator type + PyJPAlloc_Type->tp_flags = type->tp_flags; + PyJPAlloc_Type->tp_basicsize = type->tp_basicsize + sizeof (JPValue); + PyJPAlloc_Type->tp_itemsize = type->tp_itemsize; - // Create a new allocation for the dummy type + // Create a new allocation for the dummy type PyObject* obj = PyType_GenericAlloc(PyJPAlloc_Type, nitems); - // Watch for memory errors - if (obj == nullptr) - return nullptr; + // Watch for memory errors + if (obj == nullptr) + return nullptr; - // Polymorph the type to match our desired type - obj->ob_type = type; - Py_INCREF(type); + // Polymorph the type to match our desired type + obj->ob_type = type; + Py_INCREF(type); JP_TRACE("alloc", type->tp_name, obj); return obj; @@ -340,7 +340,7 @@ static int PyJPAlloc_clear(PyObject *self) static PyType_Slot allocSlots[] = { { Py_tp_traverse, (void*) PyJPAlloc_traverse}, { Py_tp_clear, (void*) PyJPAlloc_clear}, - {0, NULL} // Sentinel + {0, NULL} // Sentinel }; static PyType_Spec allocSpec = { @@ -356,6 +356,6 @@ void PyJPValue_initType(PyObject* module) PyObject *bases = PyTuple_Pack(1, &PyBaseObject_Type); PyJPAlloc_Type = (PyTypeObject*) PyType_FromSpecWithBases(&allocSpec, bases); Py_DECREF(bases); - Py_INCREF(PyJPAlloc_Type); + Py_INCREF(PyJPAlloc_Type); JP_PY_CHECK(); } From 260bcf59dacd3a71634d6fef2d55fce78b60da3c Mon Sep 17 00:00:00 2001 From: Karl Nelson Date: Sun, 15 Sep 2024 10:33:56 -0700 Subject: [PATCH 12/13] Attempt to add critical section --- native/python/pyjp_value.cpp | 23 +++++++++++++++-------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/native/python/pyjp_value.cpp b/native/python/pyjp_value.cpp index 07243c9a..f3b167d6 100644 --- a/native/python/pyjp_value.cpp +++ b/native/python/pyjp_value.cpp @@ -17,12 +17,15 @@ #include "pyjp.h" #include "jp_stringtype.h" #include +#include #ifdef __cplusplus extern "C" { #endif +std::mutex mtx; + // Create a dummy type which we will use only for allocation PyTypeObject* PyJPAlloc_Type = nullptr; @@ -54,14 +57,18 @@ PyObject* PyJPValue_alloc(PyTypeObject* type, Py_ssize_t nitems) return 0; } #endif - - // Mutate the allocator type - PyJPAlloc_Type->tp_flags = type->tp_flags; - PyJPAlloc_Type->tp_basicsize = type->tp_basicsize + sizeof (JPValue); - PyJPAlloc_Type->tp_itemsize = type->tp_itemsize; - - // Create a new allocation for the dummy type - PyObject* obj = PyType_GenericAlloc(PyJPAlloc_Type, nitems); + + PyObject* obj = nullptr; + { + std::lock_guard lock(mtx); + // Mutate the allocator type + PyJPAlloc_Type->tp_flags = type->tp_flags; + PyJPAlloc_Type->tp_basicsize = type->tp_basicsize + sizeof (JPValue); + PyJPAlloc_Type->tp_itemsize = type->tp_itemsize; + + // Create a new allocation for the dummy type + obj = PyType_GenericAlloc(PyJPAlloc_Type, nitems); + } // Watch for memory errors if (obj == nullptr) From edeafa93aa0bad6e9b8e5d37e2b1218583f65be1 Mon Sep 17 00:00:00 2001 From: "Martin K. Scherer" Date: Mon, 16 Sep 2024 13:22:26 +0200 Subject: [PATCH 13/13] try to fix python 3.13 rc version --- .azure/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.azure/build.yml b/.azure/build.yml index a8bf0c1b..e3d8bf59 100644 --- a/.azure/build.yml +++ b/.azure/build.yml @@ -73,7 +73,7 @@ jobs: jdk.version: '17' linux-py3.13-jdk17: imageName: "ubuntu-latest" - python.version: '3.13.0-rc.1' + python.version: '3.13.0-rc.2' jdk.version: '17' # Windows windows-py3.8-jdk8: