Skip to content

Commit

Permalink
Issue #608: options.m is free-threading safe
Browse files Browse the repository at this point in the history
All usage of options.m is now compatible with free-threading.
  • Loading branch information
ronaldoussoren committed Dec 7, 2024
1 parent 3368957 commit 9edf80a
Show file tree
Hide file tree
Showing 8 changed files with 272 additions and 193 deletions.
17 changes: 2 additions & 15 deletions pyobjc-core/Modules/objc/class-builder.m
Original file line number Diff line number Diff line change
Expand Up @@ -291,21 +291,8 @@ Class _Nullable PyObjCClass_BuildClass(Class super_class, PyObject* protocols, c
return Nil; // LCOV_EXCL_LINE
}

if (PyObjC_processClassDict == NULL || PyObjC_processClassDict == Py_None) {
PyErr_SetString(
PyObjCExc_InternalError,
"Cannot create class because 'objc.options._processClassDict' is not set");
goto error_cleanup;
}
PyObject* py_name = PyUnicode_FromString(name);
if (py_name == NULL) {
goto error_cleanup;
}
PyObject* args[] = {NULL, py_name, class_dict, meta_dict, py_superclass,
protocols, hiddenSelectors, hiddenClassSelectors};
PyObject* rv = PyObject_Vectorcall(PyObjC_processClassDict, args + 1,
7 | PY_VECTORCALL_ARGUMENTS_OFFSET, NULL);
Py_DECREF(py_name);
PyObject* rv = PyObjC_ProcessClassDict(name, class_dict, meta_dict, py_superclass,
protocols, hiddenSelectors, hiddenClassSelectors);
if (rv == NULL) {
goto error_cleanup;
}
Expand Down
15 changes: 0 additions & 15 deletions pyobjc-core/Modules/objc/objc-class.h
Original file line number Diff line number Diff line change
Expand Up @@ -37,21 +37,6 @@ extern PyTypeObject PyObjCClass_Type;
#define PyObjCClass_Check(obj) PyObject_TypeCheck(obj, &PyObjCClass_Type)
#define PyObjCMetaClass_Check(obj) PyObject_TypeCheck(obj, &PyObjCMetaClass_Type)

// The @const is not correct, but what else can we use here?
/*!
* @const PyObjC_ClassExtender
* @discussion
* PyObjC_ClassExtender is either NULL or a Python function that can
* update a class dictionary.
*
* The interface for the extender function is:
* extender(super_class, class_name, class_dict)
*
* The return value of the function is ignored, it should update the
* class_dict (which represents the __dict__ of an Objective-C class.
*/
extern PyObject* PyObjC_ClassExtender;

/*!
* @struct PyObjCClassObject
* @abstract The type struct for Objective-C classes (Python 2.3 and later)
Expand Down
155 changes: 35 additions & 120 deletions pyobjc-core/Modules/objc/objc-class.m
Original file line number Diff line number Diff line change
Expand Up @@ -117,8 +117,6 @@
"method signatures and will not be visible in the list of base-classes\n"
"of the created class.");

static int update_convenience_methods(PyObject* cls);

/*
*
* Class Registry
Expand Down Expand Up @@ -364,22 +362,24 @@ static Class _Nullable objc_metaclass_locate(PyObject* meta_class)
return result;
}

if (PyObjC_genericNewClass != NULL && PyObjC_genericNewClass != Py_None) {
PyObject* new = PyObject_GetAttr((PyObject*)type, PyObjCNM___new__);
if (new == NULL) { /* Shouldn't happen */
Py_DECREF(result);
return NULL;
}
PyObject* new = PyObject_GetAttr((PyObject*)type, PyObjCNM___new__);
if (new == NULL) { /* Shouldn't happen */
Py_DECREF(result);
return NULL;
}

int r = PyObject_TypeCheck(new, (PyTypeObject*)PyObjC_genericNewClass);
int r = PyObjC_IsGenericNew(new);
if (r == -1) {
Py_DECREF(new);
if (r != 0) {
return result;
}
Py_DECREF(result);
return NULL;
} else if (r) {
/* the __new__ of the class is the generic __new__,
* don't call __init__.
*/
return result;
}

/* Only call __init__ when the generic new implementation is not used */

type = Py_TYPE(result);
if (type->tp_init != NULL) {
int res = type->tp_init(result, args, kwds);
Expand Down Expand Up @@ -881,30 +881,14 @@ static Class _Nullable objc_metaclass_locate(PyObject* meta_class)
// LCOV_EXCL_STOP
}

if (PyObjC_MakeBundleForClass != NULL && PyObjC_MakeBundleForClass != Py_None) {
PyObject* args[1] = {NULL};

PyObject* m = PyObject_Vectorcall(PyObjC_MakeBundleForClass, args + 1,
0 | PY_VECTORCALL_ARGUMENTS_OFFSET, NULL);
if (m == NULL) {
if (objc_class != Nil) {
(void)PyObjCClass_UnbuildClass(objc_class);
}
Py_XDECREF(orig_slots);
Py_DECREF(old_dict);
Py_DECREF(protocols);
Py_DECREF(real_bases);
Py_DECREF(metadict);
Py_DECREF(hiddenSelectors);
Py_DECREF(hiddenClassSelectors);
return NULL;
}
if (PyObjCPythonSelector_Check(m)) {
((PyObjCSelector*)m)->sel_class = objc_class;
PyObject* bundleForClass = PyObjC_GetBundleForClassMethod();
if (bundleForClass != NULL) {
if (PyObjCPythonSelector_Check(bundleForClass)) {
((PyObjCSelector*)bundleForClass)->sel_class = objc_class;
}

r = PyDict_SetItem(dict, PyObjCNM_bundleForClass, m);
Py_DECREF(m);
r = PyDict_SetItem(dict, PyObjCNM_bundleForClass, bundleForClass);
Py_CLEAR(bundleForClass);
if (r == -1) {
if (objc_class != Nil) {
(void)PyObjCClass_UnbuildClass(objc_class);
Expand All @@ -918,6 +902,18 @@ static Class _Nullable objc_metaclass_locate(PyObject* meta_class)
Py_DECREF(hiddenClassSelectors);
return NULL;
}

} else if (PyErr_Occurred()) {
if (objc_class != Nil) {
(void)PyObjCClass_UnbuildClass(objc_class);
}
Py_XDECREF(orig_slots);
Py_DECREF(old_dict);
Py_DECREF(protocols);
Py_DECREF(real_bases);
Py_DECREF(metadict);
Py_DECREF(hiddenSelectors);
Py_DECREF(hiddenClassSelectors);
}

PyTypeObject* metatype;
Expand Down Expand Up @@ -1147,17 +1143,11 @@ static Class _Nullable objc_metaclass_locate(PyObject* meta_class)

PyObjC_Assert(info->hasPythonImpl, NULL);

if (!has_dunder_new && PyObjC_setDunderNew != NULL && PyObjC_setDunderNew != Py_None) {
PyObject* args[2] = {NULL, res};
PyObject* rv;

rv = PyObject_Vectorcall(PyObjC_setDunderNew, args + 1,
1 | PY_VECTORCALL_ARGUMENTS_OFFSET, NULL);
if (rv == NULL) {
if (!has_dunder_new) {
if (PyObjC_SetDunderNew(res) == -1) {
Py_DECREF(res);
return NULL;
}
Py_DECREF(rv);
}

Py_INCREF(res);
Expand Down Expand Up @@ -1245,7 +1235,7 @@ static Class _Nullable objc_metaclass_locate(PyObject* meta_class)
int r;
info->generation = PyObjC_MappingCount;

r = update_convenience_methods(cls);
r = PyObjC_CallClassExtender(cls);
if (r < 0) {
return -1;
}
Expand Down Expand Up @@ -3332,81 +3322,6 @@ Class _Nullable PyObjCClass_GetClass(PyObject* cls)
return info->hasPythonImpl;
}

/*!
* @function update_convenience_methods
* @abstract Update the convenience methods for a class
* @param cls An Objective-C class wrapper
* @result Returns -1 on error, 0 on success
* @discussion
* This function calls the PyObjC_ClassExtender function (if one is
* registered) and then updates the class __dict__.
*
* NOTE: We change the __dict__ using a setattro function because the type
* doesn't notice the existence of new special methods otherwise.
*
* NOTE2: We use PyType_Type.tp_setattro instead of PyObject_SetAttr because
* the tp_setattro of Objective-C class wrappers does not allow some of
* the assignments we do here.
*/
static int
update_convenience_methods(PyObject* cls)
{
PyObject* res;
PyObject* dict;
PyObject* k;
PyObject* v;
Py_ssize_t pos;

if (PyObjC_ClassExtender == NULL || PyObjC_ClassExtender == Py_None || cls == NULL)
return 0;

if (!PyObjCClass_Check(cls)) {
PyErr_SetString(PyExc_TypeError, "not a class");
return -1;
}

dict = PyDict_New();
if (dict == NULL) { // LCOV_BR_EXCL_LINE
return -1; // LCOV_EXCL_LINE
}

PyObject* args[3] = {NULL, cls, dict};

res = PyObject_Vectorcall(PyObjC_ClassExtender, args + 1,
2 | PY_VECTORCALL_ARGUMENTS_OFFSET, NULL);
if (res == NULL) {
return -1;
}
Py_DECREF(res);

pos = 0;
while (PyDict_Next(dict, &pos, &k, &v)) {
if (PyUnicode_Check(k)) {
if (PyObjC_is_ascii_string(k, "__dict__")
|| PyObjC_is_ascii_string(k, "__bases__")
|| PyObjC_is_ascii_string(k, "__slots__")
|| PyObjC_is_ascii_string(k, "__mro__")) {

continue;
}

} else {
if (PyDict_SetItem(PyObjC_get_tp_dict((PyTypeObject*)cls), k, v) == -1) {
PyErr_Clear();
}
continue;
}

if (PyType_Type.tp_setattro(cls, k, v) == -1) {
PyErr_Clear();
continue;
}
}

Py_DECREF(dict);
return 0;
}

PyObject* _Nullable PyObjCClass_ClassForMetaClass(PyObject* meta)
{
if (meta == NULL)
Expand Down
11 changes: 1 addition & 10 deletions pyobjc-core/Modules/objc/objc_support.m
Original file line number Diff line number Diff line change
Expand Up @@ -253,16 +253,7 @@ - (PyObject* _Nullable)__pyobjc_PythonObject__

rval = PyObjC_FindPythonProxy(self);
if (rval == NULL) {
rval = PyObjCObject_New(self, PyObjCObject_kDEFAULT, YES);

if (PyObjC_NSNumberWrapper && rval) {
PyObject* val = rval;

PyObject* args[2] = {NULL, val};
rval = PyObject_Vectorcall(PyObjC_NSNumberWrapper, args + 1,
1 | PY_VECTORCALL_ARGUMENTS_OFFSET, NULL);
Py_DECREF(val);
}
rval = PyObjC_CreateNSNumberProxy(self);
}
return rval;
}
Expand Down
2 changes: 0 additions & 2 deletions pyobjc-core/Modules/objc/objc_util.h
Original file line number Diff line number Diff line change
Expand Up @@ -178,8 +178,6 @@ extern PyObject* PyObjCNM___struct_pack__;
extern PyObject* PyObjCNM_pyobjcSetValue_;
extern PyObject* PyObjCNM_tzinfo;

extern PyObject* _Nullable PyObjC_TransformAttribute(PyObject*, PyObject*, PyObject*,
PyObject*);
extern int PyObjC_RemoveInternalTypeCodes(char*);

extern PyObject* _Nullable PyObjCSequence_Tuple(PyObject* value, const char* context);
Expand Down
12 changes: 0 additions & 12 deletions pyobjc-core/Modules/objc/objc_util.m
Original file line number Diff line number Diff line change
Expand Up @@ -1637,18 +1637,6 @@
return res;
}

PyObject* _Nullable PyObjC_TransformAttribute(PyObject* name, PyObject* value,
PyObject* class_object, PyObject* protocols)
{
if (PyObjC_transformAttribute == NULL || PyObjC_transformAttribute == Py_None) {
Py_INCREF(value);
return value;
}
PyObject* args[5] = {NULL, name, value, class_object, protocols};
return PyObject_Vectorcall(PyObjC_transformAttribute, args + 1,
4 | PY_VECTORCALL_ARGUMENTS_OFFSET, NULL);
}

bool
version_is_deprecated(int version)
{
Expand Down
25 changes: 13 additions & 12 deletions pyobjc-core/Modules/objc/options.h
Original file line number Diff line number Diff line change
Expand Up @@ -18,14 +18,6 @@ extern PyObjC_ATOMIC BOOL PyObjC_StructsWritable;
extern PyObjC_ATOMIC int PyObjC_DeprecationVersion;
extern PyObjC_ATOMIC Py_ssize_t PyObjC_MappingCount;

extern PyObject* _Nullable PyObjC_ClassExtender;
extern PyObject* _Nullable PyObjC_MakeBundleForClass;
extern PyObject* _Nullable PyObjC_NSNumberWrapper;
extern PyObject* _Nullable PyObjC_transformAttribute;
extern PyObject* _Nullable PyObjC_processClassDict;
extern PyObject* _Nullable PyObjC_setDunderNew;
extern PyObject* _Nullable PyObjC_genericNewClass;

extern int PyObjC_SetupOptions(PyObject* m);

extern int PyObjC_encodeWithCoder(PyObject* pyObject, NSCoder* coder) __attribute__((warn_unused_result));
Expand All @@ -42,10 +34,19 @@ extern int PyObjC_IsListLike(PyObject* object);
extern int PyObjC_IsSetLike(PyObject* object);
extern int PyObjC_IsDateLike(PyObject* object);
extern int PyObjC_IsPathLike(PyObject* object);
extern PyObject* _Nullable PyObjC_DateFromTimestamp(double timestamp);
extern PyObject* _Nullable PyObjC_DatetimeFromTimestamp(double timestamp, id tzinfo);
extern PyObject* _Nullable PyObjC_GetCallableDocString(PyObject* callable, void* _Nullable closure);
extern PyObject* _Nullable PyObjC_GetCallableSignature(PyObject* callable, void* _Nullable closure);
extern PyObject* _Nullable PyObjC_DateFromTimestamp(double timestamp) __attribute__((warn_unused_result));
extern PyObject* _Nullable PyObjC_DatetimeFromTimestamp(double timestamp, id tzinfo) __attribute__((warn_unused_result));
extern PyObject* _Nullable PyObjC_GetCallableDocString(PyObject* callable, void* _Nullable closure) __attribute__((warn_unused_result));
extern PyObject* _Nullable PyObjC_GetCallableSignature(PyObject* callable, void* _Nullable closure) __attribute__((warn_unused_result));
extern int PyObjC_CallClassExtender(PyObject* cls) __attribute__((warn_unused_result));
extern PyObject* _Nullable PyObjC_GetBundleForClassMethod(void) __attribute__((warn_unused_result));
extern PyObject* _Nullable PyObjC_CreateNSNumberProxy(NSNumber* value) __attribute__((warn_unused_result));
extern PyObject* _Nullable PyObjC_TransformAttribute(PyObject*, PyObject*, PyObject*, PyObject*) __attribute__((warn_unused_result));
extern int PyObjC_SetDunderNew(PyObject* value) __attribute__((warn_unused_result));
extern int PyObjC_IsGenericNew(PyObject* value) __attribute__((warn_unused_result));

extern PyObject* _Nullable PyObjC_ProcessClassDict(const char* name, PyObject* class_dict, PyObject* meta_dict, PyObject* py_superclass,
PyObject* protocols, PyObject* hiddenSelectors, PyObject* hiddenClassSelectors) __attribute__((warn_unused_result));

NS_ASSUME_NONNULL_END

Expand Down
Loading

0 comments on commit 9edf80a

Please sign in to comment.