Skip to content

refactor: Port LDAP_Type to heap type #546

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

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all 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
136 changes: 89 additions & 47 deletions Modules/LDAPObject.c
Original file line number Diff line number Diff line change
Expand Up @@ -12,36 +12,75 @@

static void free_attrs(char ***);

/* constructor */
/* global heap type object */
PyTypeObject *LDAP_Type;

/* constructor */
LDAPObject *
newLDAPObject(LDAP *l)
{
LDAPObject *self = (LDAPObject *)PyObject_NEW(LDAPObject, &LDAP_Type);
LDAPObject *self = (LDAPObject *)PyObject_GC_New(LDAPObject, LDAP_Type);

if (self == NULL)
if (self == NULL) {
return NULL;
}
#if PY_VERSION_HEX < 0x03080000
// Python 3.6 and 3.7 do not increase refcount of type object
Py_INCREF(Py_TYPE(self));
#ifdef Py_LIMITED_API
// The workaround is incompatible with limited API.
#error "python-ldap does not supported limited API with Python < 3.8"
#endif // Py_LIMITED_API
#endif // PY_VERSION_HEX
self->ldap = l;
self->_save = NULL;
self->valid = 1;
return self;
}

/* destructor */
// Py_TPFLAGS_DISALLOW_INSTANTIATION was introduced in 3.10.
static PyObject *
l_ldap_new(PyObject *type, PyObject *args, PyObject *kwargs)
{
PyErr_SetString(PyExc_TypeError, "cannot create 'LDAP' instances");
return NULL;
}

/* GC protocol for heap types */
static int
l_ldap_traverse(PyObject *self, visitproc visit, void *arg)
{
Py_VISIT((PyObject *)Py_TYPE(self));
return 0;
}

static int
l_ldap_clear(PyObject *self)
{
return 0;
}

/* destructor */
static void
dealloc(LDAPObject *self)
l_ldap_dealloc(PyObject *self)
{
if (self->ldap) {
if (self->valid) {
LDAP_BEGIN_ALLOW_THREADS(self);
ldap_unbind_ext(self->ldap, NULL, NULL);
LDAP_END_ALLOW_THREADS(self);
self->valid = 0;
LDAPObject *l = (LDAPObject *)self;
PyTypeObject *tp = Py_TYPE(self);

PyObject_GC_UnTrack(self);

if (l->ldap) {
if (l->valid) {
LDAP_BEGIN_ALLOW_THREADS(l);
ldap_unbind_ext(l->ldap, NULL, NULL);
LDAP_END_ALLOW_THREADS(l);
l->valid = 0;
}
self->ldap = NULL;
l->ldap = NULL;
}
PyObject_DEL(self);

PyObject_GC_Del(self);
Py_DECREF(tp);
}

/*------------------------------------------------------------
Expand Down Expand Up @@ -1473,8 +1512,7 @@ l_ldap_extended_operation(LDAPObject *self, PyObject *args)
}

/* methods */

static PyMethodDef methods[] = {
static PyMethodDef l_ldap_methods[] = {
{"unbind_ext", (PyCFunction)l_ldap_unbind_ext, METH_VARARGS},
{"abandon_ext", (PyCFunction)l_ldap_abandon_ext, METH_VARARGS},
{"add_ext", (PyCFunction)l_ldap_add_ext, METH_VARARGS},
Expand Down Expand Up @@ -1505,37 +1543,41 @@ static PyMethodDef methods[] = {
};

/* type entry */
static PyType_Slot ldap_type_slots[] = {
{Py_tp_methods, l_ldap_methods},
{Py_tp_new, l_ldap_new},
{Py_tp_dealloc, l_ldap_dealloc},
{Py_tp_traverse, l_ldap_traverse},
{Py_tp_clear, l_ldap_clear},
{0, 0}
};

PyTypeObject LDAP_Type = {
PyVarObject_HEAD_INIT(NULL, 0)
"LDAP", /*tp_name */
sizeof(LDAPObject), /*tp_basicsize */
0, /*tp_itemsize */
/* methods */
(destructor) dealloc, /*tp_dealloc */
0, /*tp_print */
0, /*tp_getattr */
0, /*tp_setattr */
0, /*tp_compare */
0, /*tp_repr */
0, /*tp_as_number */
0, /*tp_as_sequence */
0, /*tp_as_mapping */
0, /*tp_hash */
0, /*tp_call */
0, /*tp_str */
0, /*tp_getattro */
0, /*tp_setattro */
0, /*tp_as_buffer */
0, /*tp_flags */
0, /*tp_doc */
0, /*tp_traverse */
0, /*tp_clear */
0, /*tp_richcompare */
0, /*tp_weaklistoffset */
0, /*tp_iter */
0, /*tp_iternext */
methods, /*tp_methods */
0, /*tp_members */
0, /*tp_getset */
static PyType_Spec ldap_type_spec = {
.name = "_ldap.LDAP",
.basicsize = sizeof(LDAPObject),
.flags = (Py_TPFLAGS_DEFAULT |
#ifdef Py_TPFLAGS_DISALLOW_INSTANTIATION
Py_TPFLAGS_DISALLOW_INSTANTIATION |
#endif
#ifdef Py_TPFLAGS_IMMUTABLETYPE
Py_TPFLAGS_IMMUTABLETYPE |
#endif
Py_TPFLAGS_HAVE_GC),
.slots = ldap_type_slots
};

int
LDAPMod_init_type(PyObject *m)
{
if (LDAP_Type == NULL) {
#ifdef HAVE_PYTYPE_GETMODULESTATE
// PyType_GetModuleState() needs PyType_FromModuleAndSpec()
LDAP_Type =
(PyTypeObject *) PyType_FromModuleAndSpec(m, &ldap_type_spec,
NULL);
#else
LDAP_Type = (PyTypeObject *) PyType_FromSpec(&ldap_type_spec);
#endif
}
return LDAP_Type != NULL ? 0 : -1;
}
2 changes: 1 addition & 1 deletion Modules/ldapmodule.c
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ PyInit__ldap()
m = PyModule_Create(&ldap_moduledef);

/* Initialize LDAP class */
if (PyType_Ready(&LDAP_Type) < 0) {
if (LDAPMod_init_type(m) < 0) {
Py_DECREF(m);
return NULL;
}
Expand Down
8 changes: 7 additions & 1 deletion Modules/pythonldap.h
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,11 @@
#include <ldap.h>
#include <ldap_features.h>

#if PY_VERSION_HEX >= 0x030a0000 && (!defined(Py_LIMITED_API) || Py_LIMITED_API >= 0x030a0000)
// Python 3.10 stable ABI introduced PyType_GetModuleState()
#define HAVE_PYTYPE_GETMODULESTATE 1
#endif

#if LDAP_VENDOR_VERSION < 20400
#error Current python-ldap requires OpenLDAP 2.4.x
#endif
Expand Down Expand Up @@ -95,8 +100,9 @@ typedef struct {
int valid;
} LDAPObject;

PYLDAP_DATA(PyTypeObject) LDAP_Type;
PYLDAP_DATA(PyTypeObject *)LDAP_Type;
PYLDAP_FUNC(LDAPObject *) newLDAPObject(LDAP *);
PYLDAP_FUNC(int) LDAPMod_init_type(PyObject *module);

/* macros to allow thread saving in the context of an LDAP connection */

Expand Down
12 changes: 12 additions & 0 deletions Tests/t_cext.py
Original file line number Diff line number Diff line change
Expand Up @@ -240,6 +240,18 @@ def test_test_flags(self):
else:
self.assertTrue(_ldap.SASL_AVAIL)

def test_ldap_type(self):
ldap_type = type(self._open_conn())
# Python 3.10+ versions use qualified class name
self.assertIn(
repr(ldap_type),
["<class 'LDAP'>", "<class '_ldap.LDAP'>"],
)
with self.assertRaisesRegex(
TypeError, "cannot create '.*LDAP' instances"
):
ldap_type()

def test_simple_bind(self):
l = self._open_conn()

Expand Down