Skip to content

Commit

Permalink
Merge branch 'master' of github.com:ronaldoussoren/pyobjc
Browse files Browse the repository at this point in the history
  • Loading branch information
ronaldoussoren committed Jun 5, 2021
2 parents 009be7b + 63597c6 commit ec144af
Show file tree
Hide file tree
Showing 296 changed files with 2,237 additions and 794 deletions.
3 changes: 3 additions & 0 deletions docs/api/module-objc.rst
Original file line number Diff line number Diff line change
Expand Up @@ -494,6 +494,9 @@ Dynamic modification of classes

The name of the class must be the same as the argument to :class:`Category`.

This will only add new methods to existing Objective-C classes, it is in
particular not possible to add new members/slots to existing classes.


Plugin bundles
--------------
Expand Down
63 changes: 63 additions & 0 deletions docs/changelog.rst
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,69 @@ What's new in PyObjC

An overview of the relevant changes in new, and older, releases.

Version 7.3
-----------

* #356: Explicitly error out when building for unsupported architectures

"python setup.py build" will now fail with a clear error when
trying to build PyObjC for a CPU architecture that is no longer
supported (such as 32-bit Intel)

* #319: Use memset instead of bzero in C code to clear memory

Based on a PR by GitHub user stbdang.

* #348: Fix platform version guard for using protocols in
MetalPerformanceShaders bindings

* #344: Fix test for CFMessagePortCreateLocal

The tests didn't actually test calling the callback function
for CFMessagePortCreateLocal.

* #349: Change calls to htonl in pyobjc-core to avoid compiler warning

The original code had a 32-bit assumption (using 'long' to represent
a 32-bit value), and that causes problems for some users build from
source.

* #315: Fix binding for ``SecAddSharedWebCredential`` (Security framework)

Trying to use this function will no longer crash Python.

* #357: Calling ``Metal.MTLCopyAllDevices()`` no longer crashes

The reference count of the result of this function was handled incorrect,
causing access to an already deallocated value when the Python reference
was garbage collected.

* #260: Add manual bindings for AXValueCreate and AXValueGetValue in ApplicationServices

Calling these crashed in previous versions.

* #320, #324: Fix the type encoding for a number of CoreFoundation types in the Security bindings

* #336: Add core support for 'final' classes

It is now possible to mark Objective-C classes as final,
that is to disable subclassing for such classes.

This is primarily meant to be used in framework bindings for
matching Objective-C semantics.

This adds two new APIs:

1. A keyword argument "final" when defining a new class::

class MyClass (NSObject, final=True):
pass

2. An read-write attribute "__objc_final__" on all subclasses
of NSObject.

Note that this is a separate concept from :func:`typing.final`.

Version 7.2
-----------

Expand Down
2 changes: 1 addition & 1 deletion docs/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@
# built documents.
#
# The short X.Y version.
version = "7.2"
version = "7.3"
# The full version, including alpha/beta/rc tags.
release = version

Expand Down
3 changes: 1 addition & 2 deletions pyobjc-core/Modules/objc/class-builder.m
Original file line number Diff line number Diff line change
Expand Up @@ -406,7 +406,6 @@ static void object_method_copyWithZone_(ffi_cif* cif, void* resp, void** args,
int first_python_gen = 0;
Class new_class = NULL;
Class new_meta_class = NULL;
Class cur_class;
PyObject* py_superclass = NULL;
int have_intermediate = 0;
PyObject* instance_variables = NULL;
Expand All @@ -431,7 +430,7 @@ static void object_method_copyWithZone_(ffi_cif* cif, void* resp, void** args,
goto error_cleanup;
}

if ((cur_class = objc_lookUpClass(name)) != NULL) {
if ( objc_lookUpClass(name) != NULL) {
/*
* NOTE: we used to allow redefinition of a class if the
* redefinition is in the same module. This code was removed
Expand Down
18 changes: 3 additions & 15 deletions pyobjc-core/Modules/objc/libffi_support.m
Original file line number Diff line number Diff line change
Expand Up @@ -3928,12 +3928,6 @@
}
#endif /* !__arm64__ */

#define USE_ALLOCA(bufsize) 0

#if 0
#define USE_ALLOCA(bufsize) ((bufsize) < 1 << 12)
#endif

PyObject*
PyObjCFFI_Caller(PyObject* aMeth, PyObject* self, PyObject* args)
{
Expand Down Expand Up @@ -4064,11 +4058,7 @@
goto error_cleanup;
}

if (likely(USE_ALLOCA(argbuf_len))) {
argbuf = alloca(argbuf_len);
} else {
argbuf = PyMem_Malloc(argbuf_len);
}
argbuf = PyMem_Malloc(argbuf_len);
if (argbuf == 0) {
PyErr_NoMemory();
goto error_cleanup;
Expand Down Expand Up @@ -4248,9 +4238,7 @@
}
}

if (unlikely(!USE_ALLOCA(argbuf_len))) {
PyMem_Free(argbuf);
}
PyMem_Free(argbuf);
argbuf = NULL;
methinfo = NULL;

Expand All @@ -4277,7 +4265,7 @@
PyObjCFFI_FreeByRef(Py_SIZE(methinfo), byref, byref_attr);
}

if (argbuf && !USE_ALLOCA(argbuf_len)) {
if (argbuf) {
PyMem_Free(argbuf);
argbuf = NULL;
}
Expand Down
40 changes: 40 additions & 0 deletions pyobjc-core/Modules/objc/objc-class.h
Original file line number Diff line number Diff line change
Expand Up @@ -83,12 +83,14 @@ typedef struct _PyObjCClassObject {
PyObject* delmethod;
PyObject* hiddenSelectors;
PyObject* hiddenClassSelectors;
PyObject* lookup_cache;

Py_ssize_t dictoffset;
Py_ssize_t generation;
unsigned int useKVO : 1;
unsigned int hasPythonImpl : 1;
unsigned int isCFWrapper : 1;
unsigned int isFinal: 1;
} PyObjCClassObject;

extern PyObject* PyObjCClass_DefaultModule;
Expand All @@ -115,12 +117,50 @@ extern PyObject* PyObjCClass_TryResolveSelector(PyObject* base, PyObject* name,
extern PyObject* PyObjCMetaClass_TryResolveSelector(PyObject* base, PyObject* name,
SEL sel);

static inline PyObject*
PyObjCClass_GetLookupCache(PyTypeObject* tp)
{
return ((PyObjCClassObject*)tp)->lookup_cache;
}

static inline int
PyObjCClass_AddToLookupCache(PyTypeObject* _tp, PyObject* name, PyObject* value)
{
#ifdef PyObjC_ENABLE_LOOKUP_CACHE
int r;
PyObjCClassObject* tp = (PyObjCClassObject*)_tp;
if (tp->lookup_cache == NULL) {
tp->lookup_cache = PyDict_New();
if (tp->lookup_cache == NULL) {
return -1;
}
}
r = PyDict_SetItem(tp->lookup_cache, name, value);
return r;
#else
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wunused-value"

&_tp; &name; &value;
#pragma clang diagnostic pop
return 0;
#endif
}


static inline int
PyObjCClass_IsCFWrapper(PyTypeObject* tp)
{
return ((PyObjCClassObject*)tp)->isCFWrapper;
}

static inline int
PyObjCClass_IsFinal(PyTypeObject* tp)
{
return ((PyObjCClassObject*)tp)->isFinal;
}


PyObject* objc_class_locate(Class objc_class);

#endif /* PyObjC_OBJC_CLASS_H */
42 changes: 39 additions & 3 deletions pyobjc-core/Modules/objc/objc-class.m
Original file line number Diff line number Diff line change
Expand Up @@ -377,7 +377,7 @@
class_new(PyTypeObject* type __attribute__((__unused__)), PyObject* args, PyObject* kwds)
{
static PyObject* all_python_classes = NULL;
static char* keywords[] = {"name", "bases", "dict", "protocols", NULL};
static char* keywords[] = {"name", "bases", "dict", "protocols", "final", NULL};
char* name;
PyObject* bases;
PyObject* dict;
Expand All @@ -403,9 +403,10 @@
PyObject* arg_protocols = NULL;
BOOL isCFProxyClass = NO;
int r;
int final = 0;

if (!PyArg_ParseTupleAndKeywords(args, kwds, "sOO|O", keywords, &name, &bases, &dict,
&arg_protocols)) {
if (!PyArg_ParseTupleAndKeywords(args, kwds, "sOO|Op", keywords, &name, &bases, &dict,
&arg_protocols, &final)) {
return NULL;
}

Expand Down Expand Up @@ -438,6 +439,12 @@
return NULL;
}

if (PyObjCClass_IsFinal((PyTypeObject*)py_super_class)) {
PyErr_Format(PyExc_TypeError, "super class %s is final",
((PyTypeObject*)py_super_class)->tp_name);
return NULL;
}

super_class = PyObjCClass_GetClass(py_super_class);
if (super_class) {
if (PyObjCClass_CheckMethodList(py_super_class, 1) < 0) {
Expand Down Expand Up @@ -952,8 +959,10 @@
info->delmethod = delmethod;
info->hasPythonImpl = 1;
info->isCFWrapper = 0;
info->isFinal = final;
info->hiddenSelectors = hiddenSelectors;
info->hiddenClassSelectors = hiddenClassSelectors;
info->lookup_cache = NULL;

var = class_getInstanceVariable(objc_class, "__dict__");
if (var != NULL) {
Expand Down Expand Up @@ -1923,6 +1932,25 @@
return 0;
}

static PyObject*
cls_get_final(PyObject* self, void* closure __attribute__((__unused__)))
{
return PyBool_FromLong(((PyObjCClassObject*)self)->isFinal);
}

static int
cls_set_final(PyObject* self, PyObject* newVal,
void* closure __attribute__((__unused__)))
{
if (newVal == NULL) {
PyErr_SetString(PyExc_TypeError, "Cannot delete __objc_final__ attribute");
return -1;
}

((PyObjCClassObject*)self)->isFinal = PyObject_IsTrue(newVal);
return 0;
}

static PyGetSetDef class_getset[] = {
{
.name = "pyobjc_classMethods",
Expand All @@ -1946,6 +1974,12 @@
.set = cls_set_useKVO,
.doc = "Use KVO notifications when setting attributes from Python",
},
{
.name = "__objc_final__",
.get = cls_get_final,
.set = cls_set_final,
.doc = "True if the class cannot be subclassed",
},
{
/* Access __name__ through a property: Objective-C name
* might change due to posing.
Expand Down Expand Up @@ -2148,7 +2182,9 @@
info->delmethod = NULL;
info->hasPythonImpl = 0;
info->isCFWrapper = 0;
info->isFinal = 0;
info->hiddenSelectors = hiddenSelectors;
info->lookup_cache = NULL;

objc_class_register(objc_class, result);

Expand Down
20 changes: 20 additions & 0 deletions pyobjc-core/Modules/objc/objc-object.m
Original file line number Diff line number Diff line change
Expand Up @@ -302,6 +302,7 @@
{
Py_ssize_t i, n;
PyObject * mro, *base, *dict;
PyObject* first_class = NULL;
PyObject* descr = NULL;
PyObject* res;
SEL sel = PyObjCSelector_DefaultSelector(PyObjC_Unicode_Fast_Bytes(name));
Expand All @@ -319,6 +320,20 @@
base = PyTuple_GET_ITEM(mro, i);

if (PyObjCClass_Check(base)) {
if (i == 0) {
first_class = base;
}
PyObject* cache = PyObjCClass_GetLookupCache((PyTypeObject*)base);
if (cache != NULL) {
descr = PyDict_GetItemWithError(cache, name);
if (descr == NULL && PyErr_Occurred()) {
return NULL;
}
if (descr != NULL) {
break;
}
}

if (PyObjCClass_CheckMethodList(base, 0) < 0) {
return NULL;
}
Expand All @@ -338,6 +353,11 @@
if (descr == NULL && PyErr_Occurred()) {
return NULL;
} else if (descr != NULL) {
if (first_class != NULL) {
if (PyObjCClass_AddToLookupCache((PyTypeObject*)first_class, name, descr) == -1) {
PyErr_Clear();
}
}
break;
}

Expand Down
15 changes: 13 additions & 2 deletions pyobjc-core/Modules/objc/pyobjc.h
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
* Central include file for PyObjC.
*/

#define OBJC_VERSION "7.2"
#define OBJC_VERSION "7.3"

#define PY_SSIZE_T_CLEAN
#include <Python.h>
Expand Down Expand Up @@ -55,7 +55,18 @@
*
* NOTE: Option is present for performance testing.
*/
/*#define PyObjC_FAST_BUT_INEXACT 1*/
/* #define PyObjC_FAST_BUT_INEXACT 1 */

/* PyObjC_ENABLE_LOOKUP_CACHE: If defined the _type_lookup will cache
* found entries in leaf classes.
*
* A side effect of this is that methods introduced in categories might not
* be seen in the Python proxy and as such suffers from the same issue as
* PyObjC_FAST_BUT_INEXACT.
*
* NOTE: Option is present for performance testing.
*/
/* #define PyObjC_ENABLE_LOOKUP_CACHE 1 */

/*
* End of configuration block
Expand Down
Loading

0 comments on commit ec144af

Please sign in to comment.