Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Remove use of tp_new #1217

Merged
merged 26 commits into from
Sep 30, 2024
Merged
Show file tree
Hide file tree
Changes from 19 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .azure/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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.0-rc.1'
jdk.version: '17'
# Windows
windows-py3.8-jdk8:
imageName: "windows-2019"
Expand Down
Empty file added native/python/*.c
Thrameos marked this conversation as resolved.
Show resolved Hide resolved
Empty file.
182 changes: 101 additions & 81 deletions native/python/pyjp_class.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -58,82 +58,18 @@ static int PyJPClass_clear(PyJPClass *self)
return 0;
}

PyObject *PyJPClass_new(PyTypeObject *type, PyObject *args, PyObject *kwargs)
{
JP_PY_TRY("PyJPClass_new");
if (PyTuple_Size(args) != 3)
JP_RAISE(PyExc_TypeError, "Java class meta required 3 arguments");

JP_BLOCK("PyJPClass_new::verify")
{
// Watch for final classes
PyObject *bases = PyTuple_GetItem(args, 1);
Py_ssize_t len = PyTuple_Size(bases);
for (Py_ssize_t i = 0; i < len; ++i)
{
PyObject *item = PyTuple_GetItem(bases, i);
JPClass *cls = PyJPClass_getJPClass(item);
if (cls != nullptr && cls->isFinal())
{
PyErr_Format(PyExc_TypeError, "Cannot extend final class '%s'",
((PyTypeObject*) item)->tp_name);
}
}
}

int magic = 0;
if (kwargs == PyJPClassMagic || (kwargs != nullptr && PyDict_GetItemString(kwargs, "internal") != nullptr))
{
magic = 1;
kwargs = nullptr;
}
if (magic == 0)
{
PyErr_Format(PyExc_TypeError, "Java classes cannot be extended in Python");
return nullptr;
}

auto *typenew = (PyTypeObject*) PyType_Type.tp_new(type, args, kwargs);

// GCOVR_EXCL_START
// Sanity checks. Not testable
if (typenew == nullptr)
return nullptr;
if (typenew->tp_finalize != nullptr && typenew->tp_finalize != (destructor) PyJPValue_finalize)
{
Py_DECREF(typenew);
PyErr_SetString(PyExc_TypeError, "finalizer conflict");
return nullptr;
}

// This sanity check is trigger if the user attempts to build their own
// type wrapper with a __del__ method defined. It is hard to trigger.
if (typenew->tp_alloc != (allocfunc) PyJPValue_alloc
&& typenew->tp_alloc != PyBaseObject_Type.tp_alloc)
{
Py_DECREF(typenew);
PyErr_SetString(PyExc_TypeError, "alloc conflict");
return nullptr;
}
// GCOVR_EXCL_STOP

typenew->tp_alloc = (allocfunc) PyJPValue_alloc;
typenew->tp_finalize = (destructor) PyJPValue_finalize;

if (PyObject_IsSubclass((PyObject*) typenew, (PyObject*) PyJPException_Type))
{
typenew->tp_new = PyJPException_Type->tp_new;
}
((PyJPClass*) typenew)->m_Doc = nullptr;
return (PyObject*) typenew;
JP_PY_CATCH(nullptr);
}

PyObject* examine(PyObject *module, PyObject *other);

PyObject* PyJPClass_FromSpecWithBases(PyType_Spec *spec, PyObject *bases)
{
JP_PY_TRY("PyJPClass_FromSpecWithBases");
#if PY_VERSION_HEX>=0x030c0000
// Starting in Python 3.12 there is a function for creating from a meta class
// that replaces this madeness.
PyTypeObject *type = (PyTypeObject*) PyType_FromMetaclass((PyTypeObject*) PyJPClass_Type, NULL, spec, bases);
if (type == nullptr)
return (PyObject*) type;
#else
// Python lacks a FromSpecWithMeta so we are going to have to fake it here.
auto* type = (PyTypeObject*) PyJPClass_Type->tp_alloc(PyJPClass_Type, 0);
auto* heap = (PyHeapTypeObject*) type;
Expand Down Expand Up @@ -174,6 +110,12 @@ PyObject* PyJPClass_FromSpecWithBases(PyType_Spec *spec, PyObject *bases)
{
switch (slot->slot)
{
case Py_tp_finalize:
type->tp_finalize = (destructor) slot->pfunc;
break;
case Py_tp_alloc:
type->tp_alloc = (allocfunc) slot->pfunc;
break;
case Py_tp_free:
type->tp_free = (freefunc) slot->pfunc;
break;
Expand Down Expand Up @@ -293,7 +235,7 @@ PyObject* PyJPClass_FromSpecWithBases(PyType_Spec *spec, PyObject *bases)
}

// GC objects are required to implement clear and traverse, this is a
// safety check to make sure we implemented all properly. This error should
// safety check to make sure we implemented all properly. This error should
// never happen in production code.
if (PyType_IS_GC(type) && (
type->tp_traverse==nullptr ||
Expand All @@ -302,6 +244,13 @@ PyObject* PyJPClass_FromSpecWithBases(PyType_Spec *spec, PyObject *bases)
PyErr_Format(PyExc_TypeError, "GC requirements failed for %s", spec->name);
JP_RAISE_PYTHON();
}

#endif

// Make sure our memory model is used
type->tp_alloc = (allocfunc) PyJPValue_alloc;
type->tp_finalize = (destructor) PyJPValue_finalize;

PyType_Ready(type);
PyDict_SetItemString(type->tp_dict, "__module__", PyUnicode_FromString("_jpype"));
return (PyObject*) type;
Expand All @@ -311,8 +260,34 @@ PyObject* PyJPClass_FromSpecWithBases(PyType_Spec *spec, PyObject *bases)
int PyJPClass_init(PyObject *self, PyObject *args, PyObject *kwargs)
{
JP_PY_TRY("PyJPClass_init");
if (PyTuple_Size(args) == 1)
return 0;

if (!PyObject_IsInstance(self, (PyObject*) PyJPClass_Type))
{
PyErr_SetString(PyExc_TypeError, "Type incorrect");
return -1;
}

PyTypeObject *type = (PyTypeObject*) self;

#if PY_VERSION_HEX >= 0x030d0000
// Python 3.13 - 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

// Verify that we were called internally
int magic = 0;
if (kwargs == PyJPClassMagic || (kwargs != nullptr && PyDict_GetItemString(kwargs, "internal") != nullptr))
{
magic = 1;
kwargs = nullptr;
}
if (magic == 0)
{
PyErr_Format(PyExc_TypeError, "Java classes cannot be extended in Python");
return -1;
}

// Set the host object
PyObject *name = nullptr;
Expand All @@ -327,20 +302,66 @@ int PyJPClass_init(PyObject *self, PyObject *args, PyObject *kwargs)
PyErr_SetString(PyExc_TypeError, "Bases must be a tuple");
return -1;
}
for (int i = 0; i < PyTuple_Size(bases); ++i)

JP_BLOCK("PyJPClass_new::verify")
{
if (!PyJPClass_Check(PyTuple_GetItem(bases, i)))
// Watch for final classes
Py_ssize_t len = PyTuple_Size(bases);
for (Py_ssize_t i = 0; i < len; ++i)
{
PyErr_SetString(PyExc_TypeError, "All bases must be Java types");
return -1;
PyObject *item = PyTuple_GetItem(bases, i);
JPClass *cls = PyJPClass_getJPClass(item);
if (cls != nullptr)
{
PyErr_SetString(PyExc_TypeError, "All bases must be Java types");
return -1;
}
if (cls->isFinal())
{
PyErr_Format(PyExc_TypeError, "Cannot extend final class '%s'",
((PyTypeObject*) item)->tp_name);
}
}
}

// We must make sure that all classes have our allocator
type->tp_alloc = (allocfunc) PyJPValue_alloc;
type->tp_finalize = (destructor) PyJPValue_finalize;
((PyJPClass*) self)->m_Doc = nullptr;

// Call the type init
int rc = PyType_Type.tp_init(self, args, nullptr);
if (rc == -1)
return rc; // GCOVR_EXCL_LINE no clue how to trigger this one

// GCOVR_EXCL_START
// Sanity checks. Not testable
if (type == nullptr)
return -1;
if (type->tp_finalize != nullptr && type->tp_finalize != (destructor) PyJPValue_finalize)
{
PyErr_SetString(PyExc_TypeError, "finalizer conflict");
return -1;
}

// This sanity check is trigger if the user attempts to build their own
// type wrapper with a __del__ method defined. It is hard to trigger.
if (type->tp_alloc != (allocfunc) PyJPValue_alloc
&& type->tp_alloc != PyBaseObject_Type.tp_alloc)
{
PyErr_SetString(PyExc_TypeError, "alloc conflict");
return -1;
}
// GCOVR_EXCL_STOP

#if PY_VERSION_HEX < 0x03090000
// This was required at one point but I don't know what version it applied to.
if (PyObject_IsSubclass((PyObject*) type, (PyObject*) PyJPException_Type))
{
type->tp_new = PyJPException_Type->tp_new;
}
#endif

return rc;
JP_PY_CATCH(-1);
}
Expand Down Expand Up @@ -990,7 +1011,6 @@ static PyGetSetDef classGetSets[] = {
static PyType_Slot classSlots[] = {
{ Py_tp_alloc, (void*) PyJPValue_alloc},
{ Py_tp_finalize, (void*) PyJPValue_finalize},
{ Py_tp_new, (void*) PyJPClass_new},
{ Py_tp_init, (void*) PyJPClass_init},
{ Py_tp_dealloc, (void*) PyJPClass_dealloc},
{ Py_tp_traverse, (void*) PyJPClass_traverse},
Expand Down Expand Up @@ -1130,7 +1150,7 @@ JPPyObject PyJPClass_getBases(JPJavaFrame &frame, JPClass* cls)
* Internal method for wrapping a returned Java class instance.
*
* This checks the cache for existing wrappers and then
* transfers control to JClassFactory. This is required because all of
* transfers control to JClassFactory. This is required because all of
* the post load stuff needs to be in Python.
*
* @param cls
Expand Down Expand Up @@ -1201,7 +1221,7 @@ void PyJPClass_hook(JPJavaFrame &frame, JPClass* cls)

JP_TRACE("type new");
// Create the type using the meta class magic
JPPyObject vself = JPPyObject::call(PyJPClass_Type->tp_new(PyJPClass_Type, rc.get(), PyJPClassMagic));
JPPyObject vself = JPPyObject::call(PyJPClass_Type->tp_call((PyObject*) PyJPClass_Type, rc.get(), PyJPClassMagic));
auto *self = (PyJPClass*) vself.get();

// Attach the javaSlot
Expand Down
2 changes: 2 additions & 0 deletions native/python/pyjp_module.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand Down Expand Up @@ -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);

Expand Down
14 changes: 14 additions & 0 deletions native/python/pyjp_number.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -299,6 +299,17 @@ static PyObject* PyJPBoolean_str(PyObject* self)
JP_PY_CATCH(nullptr);
}

static PyObject *PyJPNumber_initSubclass(PyObject *cls, PyObject* args, PyObject *kwargs)
{
Py_RETURN_NONE;
}

static PyMethodDef numberMethods[] = {
{"__init_subclass__", (PyCFunction) PyJPNumber_initSubclass, METH_CLASS | METH_VARARGS | METH_KEYWORDS, ""},
{0}
};


static PyType_Slot numberLongSlots[] = {
{Py_tp_new, (void*) &PyJPNumber_new},
{Py_tp_getattro, (void*) &PyJPValue_getattro},
Expand All @@ -309,6 +320,7 @@ static PyType_Slot numberLongSlots[] = {
{Py_tp_repr, (void*) &PyJPNumberLong_repr},
{Py_tp_hash, (void*) &PyJPNumberLong_hash},
{Py_tp_richcompare, (void*) &PyJPNumberLong_compare},
{Py_tp_methods, (void*) numberMethods},
{0}
};

Expand All @@ -331,6 +343,7 @@ static PyType_Slot numberFloatSlots[] = {
{Py_tp_repr, (void*) &PyJPNumberFloat_repr},
{Py_tp_hash, (void*) &PyJPNumberFloat_hash},
{Py_tp_richcompare, (void*) &PyJPNumberFloat_compare},
{Py_tp_methods, (void*) numberMethods},
{0}
};

Expand All @@ -353,6 +366,7 @@ static PyType_Slot numberBooleanSlots[] = {
{Py_nb_float, (void*) PyJPNumberLong_float},
{Py_tp_hash, (void*) PyJPNumberLong_hash},
{Py_tp_richcompare, (void*) PyJPNumberLong_compare},
{Py_tp_methods, (void*) numberMethods},
{0}
};

Expand Down
18 changes: 16 additions & 2 deletions native/python/pyjp_object.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -222,7 +222,19 @@ static PyObject *PyJPObject_repr(PyObject *self)
JP_PY_CATCH(nullptr); // GCOVR_EXCL_LINE
}

static PyObject *PyJPObject_initSubclass(PyObject *cls, PyObject* args, PyObject *kwargs)
{
Py_RETURN_NONE;
}

static PyMethodDef objectMethods[] = {
{"__init_subclass__", (PyCFunction) PyJPObject_initSubclass, METH_CLASS | METH_VARARGS | METH_KEYWORDS, ""},
{0}
};

static PyType_Slot objectSlots[] = {
{Py_tp_alloc, (void*) &PyJPValue_alloc},
{Py_tp_finalize, (void*) &PyJPValue_finalize},
{Py_tp_new, (void*) &PyJPObject_new},
{Py_tp_free, (void*) &PyJPValue_free},
{Py_tp_getattro, (void*) &PyJPValue_getattro},
Expand All @@ -231,6 +243,7 @@ static PyType_Slot objectSlots[] = {
{Py_tp_repr, (void*) &PyJPObject_repr},
{Py_tp_richcompare, (void*) &PyJPObject_compare},
{Py_tp_hash, (void*) &PyJPObject_hash},
{Py_tp_methods, (void*) objectMethods},
{0}
};

Expand Down Expand Up @@ -361,12 +374,13 @@ static PyType_Spec comparableSpec = {

void PyJPObject_initType(PyObject* module)
{
PyJPObject_Type = (PyTypeObject*) PyJPClass_FromSpecWithBases(&objectSpec, nullptr);
JPPyObject bases = JPPyObject::call(PyTuple_Pack(1, &PyBaseObject_Type));
PyJPObject_Type = (PyTypeObject*) PyJPClass_FromSpecWithBases(&objectSpec, bases.get());
JP_PY_CHECK(); // GCOVR_EXCL_LINE
PyModule_AddObject(module, "_JObject", (PyObject*) PyJPObject_Type);
JP_PY_CHECK(); // GCOVR_EXCL_LINE

JPPyObject bases = JPPyObject::call(PyTuple_Pack(2, PyExc_Exception, PyJPObject_Type));
bases = JPPyObject::call(PyTuple_Pack(2, PyExc_Exception, PyJPObject_Type));
PyJPException_Type = (PyTypeObject*) PyJPClass_FromSpecWithBases(&excSpec, bases.get());
JP_PY_CHECK(); // GCOVR_EXCL_LINE
PyModule_AddObject(module, "_JException", (PyObject*) PyJPException_Type);
Expand Down
Loading
Loading